diff --git a/.github/workflows/av1enc.yml b/.github/workflows/av1enc.yml new file mode 100644 index 0000000000..a54265ce4d --- /dev/null +++ b/.github/workflows/av1enc.yml @@ -0,0 +1,41 @@ +name: Shared AV1 Encoder + +on: + push: + branches: [main] + paths: ["crates/av1enc/**", "Cargo.toml", "Cargo.lock", ".github/workflows/av1enc.yml"] + pull_request: + paths: ["crates/av1enc/**", "Cargo.toml", "Cargo.lock", ".github/workflows/av1enc.yml"] + workflow_dispatch: + +permissions: + contents: read + +concurrency: + group: shared-av1enc-${{ github.ref }} + cancel-in-progress: true + +jobs: + check: + timeout-minutes: 15 + strategy: + fail-fast: false + matrix: + os: [ubuntu-latest, windows-latest] + runs-on: ${{ matrix.os }} + steps: + - uses: actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1 # v7.0.1 + with: + persist-credentials: false + - uses: dtolnay/rust-toolchain@b725eda525362e7cf34af9a7243cac1e3f01a7ec # stable + with: + toolchain: "1.95" + components: rustfmt, clippy + targets: wasm32-unknown-unknown + - uses: Swatinem/rust-cache@d1527c8f67ef9af357290475d4af4b0fab8f12e0 # v2.9.3 + with: + key: craft-av1enc-${{ matrix.os }} + - run: cargo fmt -p craft-av1enc --check + - run: cargo clippy -p craft-av1enc --all-targets --locked -- -D warnings + - run: cargo test -p craft-av1enc --locked + - run: cargo check -p craft-av1enc --locked --target wasm32-unknown-unknown diff --git a/.github/workflows/codecs.yml b/.github/workflows/codecs.yml new file mode 100644 index 0000000000..a8c84d1c8c --- /dev/null +++ b/.github/workflows/codecs.yml @@ -0,0 +1,41 @@ +name: Shared Video Codecs Umbrella + +on: + push: + branches: [main] + paths: ["crates/codecs/**", "Cargo.toml", "Cargo.lock", ".github/workflows/codecs.yml"] + pull_request: + paths: ["crates/codecs/**", "Cargo.toml", "Cargo.lock", ".github/workflows/codecs.yml"] + workflow_dispatch: + +permissions: + contents: read + +concurrency: + group: shared-codecs-${{ github.ref }} + cancel-in-progress: true + +jobs: + check: + timeout-minutes: 15 + strategy: + fail-fast: false + matrix: + os: [ubuntu-latest, windows-latest] + runs-on: ${{ matrix.os }} + steps: + - uses: actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1 # v7.0.1 + with: + persist-credentials: false + - uses: dtolnay/rust-toolchain@b725eda525362e7cf34af9a7243cac1e3f01a7ec # stable + with: + toolchain: "1.95" + components: rustfmt, clippy + targets: wasm32-unknown-unknown + - uses: Swatinem/rust-cache@d1527c8f67ef9af357290475d4af4b0fab8f12e0 # v2.9.3 + with: + key: craft-codecs-${{ matrix.os }} + - run: cargo fmt -p craft-codecs --check + - run: cargo clippy -p craft-codecs --all-targets --locked -- -D warnings + - run: cargo test -p craft-codecs --locked + - run: cargo check -p craft-codecs --locked --target wasm32-unknown-unknown diff --git a/.github/workflows/dnx.yml b/.github/workflows/dnx.yml new file mode 100644 index 0000000000..f5271fa4f1 --- /dev/null +++ b/.github/workflows/dnx.yml @@ -0,0 +1,46 @@ +name: Shared DNx + +on: + push: + branches: [main] + paths: ["crates/dnx/**", "Cargo.toml", "Cargo.lock", ".github/workflows/dnx.yml"] + pull_request: + paths: ["crates/dnx/**", "Cargo.toml", "Cargo.lock", ".github/workflows/dnx.yml"] + workflow_dispatch: + +permissions: + contents: read + +concurrency: + group: shared-dnx-${{ github.ref }} + cancel-in-progress: true + +jobs: + check: + timeout-minutes: 15 + strategy: + fail-fast: false + matrix: + os: [ubuntu-latest, windows-latest] + runs-on: ${{ matrix.os }} + steps: + - uses: actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1 # v7.0.1 + with: + persist-credentials: false + - uses: dtolnay/rust-toolchain@b725eda525362e7cf34af9a7243cac1e3f01a7ec # stable + with: + toolchain: "1.95" + components: rustfmt, clippy + targets: wasm32-unknown-unknown + - uses: Swatinem/rust-cache@d1527c8f67ef9af357290475d4af4b0fab8f12e0 # v2.9.3 + with: + key: craft-dnx-${{ matrix.os }} + - name: Install ffmpeg (Linux) + if: runner.os == 'Linux' + run: | + sudo rm -f /var/lib/man-db/auto-update + sudo apt-get update -qq && sudo apt-get install -y -qq --no-install-recommends ffmpeg + - run: cargo fmt -p craft-dnx --check + - run: cargo clippy -p craft-dnx --all-targets --locked -- -D warnings + - run: cargo test -p craft-dnx --locked + - run: cargo check -p craft-dnx --locked --target wasm32-unknown-unknown diff --git a/.github/workflows/h264enc.yml b/.github/workflows/h264enc.yml new file mode 100644 index 0000000000..712e940d81 --- /dev/null +++ b/.github/workflows/h264enc.yml @@ -0,0 +1,41 @@ +name: Shared H.264 Encoder + +on: + push: + branches: [main] + paths: ["crates/h264enc/**", "Cargo.toml", "Cargo.lock", ".github/workflows/h264enc.yml"] + pull_request: + paths: ["crates/h264enc/**", "Cargo.toml", "Cargo.lock", ".github/workflows/h264enc.yml"] + workflow_dispatch: + +permissions: + contents: read + +concurrency: + group: shared-h264enc-${{ github.ref }} + cancel-in-progress: true + +jobs: + check: + timeout-minutes: 15 + strategy: + fail-fast: false + matrix: + os: [ubuntu-latest, windows-latest] + runs-on: ${{ matrix.os }} + steps: + - uses: actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1 # v7.0.1 + with: + persist-credentials: false + - uses: dtolnay/rust-toolchain@b725eda525362e7cf34af9a7243cac1e3f01a7ec # stable + with: + toolchain: "1.95" + components: rustfmt, clippy + targets: wasm32-unknown-unknown + - uses: Swatinem/rust-cache@d1527c8f67ef9af357290475d4af4b0fab8f12e0 # v2.9.3 + with: + key: craft-h264enc-${{ matrix.os }} + - run: cargo fmt -p craft-h264enc --check + - run: cargo clippy -p craft-h264enc --all-targets --locked -- -D warnings + - run: cargo test -p craft-h264enc --locked + - run: cargo check -p craft-h264enc --locked --target wasm32-unknown-unknown diff --git a/.github/workflows/hevcenc.yml b/.github/workflows/hevcenc.yml new file mode 100644 index 0000000000..09553cc9f4 --- /dev/null +++ b/.github/workflows/hevcenc.yml @@ -0,0 +1,41 @@ +name: Shared HEVC Encoder + +on: + push: + branches: [main] + paths: ["crates/hevcenc/**", "Cargo.toml", "Cargo.lock", ".github/workflows/hevcenc.yml"] + pull_request: + paths: ["crates/hevcenc/**", "Cargo.toml", "Cargo.lock", ".github/workflows/hevcenc.yml"] + workflow_dispatch: + +permissions: + contents: read + +concurrency: + group: shared-hevcenc-${{ github.ref }} + cancel-in-progress: true + +jobs: + check: + timeout-minutes: 15 + strategy: + fail-fast: false + matrix: + os: [ubuntu-latest, windows-latest] + runs-on: ${{ matrix.os }} + steps: + - uses: actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1 # v7.0.1 + with: + persist-credentials: false + - uses: dtolnay/rust-toolchain@b725eda525362e7cf34af9a7243cac1e3f01a7ec # stable + with: + toolchain: "1.95" + components: rustfmt, clippy + targets: wasm32-unknown-unknown + - uses: Swatinem/rust-cache@d1527c8f67ef9af357290475d4af4b0fab8f12e0 # v2.9.3 + with: + key: craft-hevcenc-${{ matrix.os }} + - run: cargo fmt -p craft-hevcenc --check + - run: cargo clippy -p craft-hevcenc --all-targets --locked -- -D warnings + - run: cargo test -p craft-hevcenc --locked + - run: cargo check -p craft-hevcenc --locked --target wasm32-unknown-unknown diff --git a/.github/workflows/prores.yml b/.github/workflows/prores.yml new file mode 100644 index 0000000000..643cfb5bd8 --- /dev/null +++ b/.github/workflows/prores.yml @@ -0,0 +1,46 @@ +name: Shared ProRes + +on: + push: + branches: [main] + paths: ["crates/prores/**", "Cargo.toml", "Cargo.lock", ".github/workflows/prores.yml"] + pull_request: + paths: ["crates/prores/**", "Cargo.toml", "Cargo.lock", ".github/workflows/prores.yml"] + workflow_dispatch: + +permissions: + contents: read + +concurrency: + group: shared-prores-${{ github.ref }} + cancel-in-progress: true + +jobs: + check: + timeout-minutes: 15 + strategy: + fail-fast: false + matrix: + os: [ubuntu-latest, windows-latest] + runs-on: ${{ matrix.os }} + steps: + - uses: actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1 # v7.0.1 + with: + persist-credentials: false + - uses: dtolnay/rust-toolchain@b725eda525362e7cf34af9a7243cac1e3f01a7ec # stable + with: + toolchain: "1.95" + components: rustfmt, clippy + targets: wasm32-unknown-unknown + - uses: Swatinem/rust-cache@d1527c8f67ef9af357290475d4af4b0fab8f12e0 # v2.9.3 + with: + key: craft-prores-${{ matrix.os }} + - name: Install ffmpeg (Linux) + if: runner.os == 'Linux' + run: | + sudo rm -f /var/lib/man-db/auto-update + sudo apt-get update -qq && sudo apt-get install -y -qq --no-install-recommends ffmpeg + - run: cargo fmt -p craft-prores --check + - run: cargo clippy -p craft-prores --all-targets --locked -- -D warnings + - run: cargo test -p craft-prores --locked + - run: cargo check -p craft-prores --locked --target wasm32-unknown-unknown diff --git a/.github/workflows/testkit.yml b/.github/workflows/testkit.yml new file mode 100644 index 0000000000..c3f20b2f83 --- /dev/null +++ b/.github/workflows/testkit.yml @@ -0,0 +1,46 @@ +name: Shared Testkit + +on: + push: + branches: [main] + paths: ["crates/testkit/**", "Cargo.toml", "Cargo.lock", ".github/workflows/testkit.yml"] + pull_request: + paths: ["crates/testkit/**", "Cargo.toml", "Cargo.lock", ".github/workflows/testkit.yml"] + workflow_dispatch: + +permissions: + contents: read + +concurrency: + group: shared-testkit-${{ github.ref }} + cancel-in-progress: true + +jobs: + check: + timeout-minutes: 15 + strategy: + fail-fast: false + matrix: + os: [ubuntu-latest, windows-latest] + runs-on: ${{ matrix.os }} + steps: + - uses: actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1 # v7.0.1 + with: + persist-credentials: false + - uses: dtolnay/rust-toolchain@b725eda525362e7cf34af9a7243cac1e3f01a7ec # stable + with: + toolchain: "1.95" + components: rustfmt, clippy + targets: wasm32-unknown-unknown + - uses: Swatinem/rust-cache@d1527c8f67ef9af357290475d4af4b0fab8f12e0 # v2.9.3 + with: + key: craft-testkit-${{ matrix.os }} + - name: Install ffmpeg (Linux) + if: runner.os == 'Linux' + run: | + sudo rm -f /var/lib/man-db/auto-update + sudo apt-get update -qq && sudo apt-get install -y -qq --no-install-recommends ffmpeg + - run: cargo fmt -p craft-testkit --check + - run: cargo clippy -p craft-testkit --all-targets --locked -- -D warnings + - run: cargo test -p craft-testkit --locked + - run: cargo check -p craft-testkit --locked --target wasm32-unknown-unknown diff --git a/.github/workflows/vp8.yml b/.github/workflows/vp8.yml new file mode 100644 index 0000000000..a1e2d970f7 --- /dev/null +++ b/.github/workflows/vp8.yml @@ -0,0 +1,46 @@ +name: Shared VP8 + +on: + push: + branches: [main] + paths: ["crates/vp8/**", "Cargo.toml", "Cargo.lock", ".github/workflows/vp8.yml"] + pull_request: + paths: ["crates/vp8/**", "Cargo.toml", "Cargo.lock", ".github/workflows/vp8.yml"] + workflow_dispatch: + +permissions: + contents: read + +concurrency: + group: shared-vp8-${{ github.ref }} + cancel-in-progress: true + +jobs: + check: + timeout-minutes: 15 + strategy: + fail-fast: false + matrix: + os: [ubuntu-latest, windows-latest] + runs-on: ${{ matrix.os }} + steps: + - uses: actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1 # v7.0.1 + with: + persist-credentials: false + - uses: dtolnay/rust-toolchain@b725eda525362e7cf34af9a7243cac1e3f01a7ec # stable + with: + toolchain: "1.95" + components: rustfmt, clippy + targets: wasm32-unknown-unknown + - uses: Swatinem/rust-cache@d1527c8f67ef9af357290475d4af4b0fab8f12e0 # v2.9.3 + with: + key: craft-vp8-${{ matrix.os }} + - name: Install ffmpeg (Linux) + if: runner.os == 'Linux' + run: | + sudo rm -f /var/lib/man-db/auto-update + sudo apt-get update -qq && sudo apt-get install -y -qq --no-install-recommends ffmpeg + - run: cargo fmt -p craft-vp8 --check + - run: cargo clippy -p craft-vp8 --all-targets --locked -- -D warnings + - run: cargo test -p craft-vp8 --locked + - run: cargo check -p craft-vp8 --locked --target wasm32-unknown-unknown diff --git a/.github/workflows/vp9enc.yml b/.github/workflows/vp9enc.yml new file mode 100644 index 0000000000..c0606b1df4 --- /dev/null +++ b/.github/workflows/vp9enc.yml @@ -0,0 +1,41 @@ +name: Shared VP9 Encoder + +on: + push: + branches: [main] + paths: ["crates/vp9enc/**", "Cargo.toml", "Cargo.lock", ".github/workflows/vp9enc.yml"] + pull_request: + paths: ["crates/vp9enc/**", "Cargo.toml", "Cargo.lock", ".github/workflows/vp9enc.yml"] + workflow_dispatch: + +permissions: + contents: read + +concurrency: + group: shared-vp9enc-${{ github.ref }} + cancel-in-progress: true + +jobs: + check: + timeout-minutes: 15 + strategy: + fail-fast: false + matrix: + os: [ubuntu-latest, windows-latest] + runs-on: ${{ matrix.os }} + steps: + - uses: actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1 # v7.0.1 + with: + persist-credentials: false + - uses: dtolnay/rust-toolchain@b725eda525362e7cf34af9a7243cac1e3f01a7ec # stable + with: + toolchain: "1.95" + components: rustfmt, clippy + targets: wasm32-unknown-unknown + - uses: Swatinem/rust-cache@d1527c8f67ef9af357290475d4af4b0fab8f12e0 # v2.9.3 + with: + key: craft-vp9enc-${{ matrix.os }} + - run: cargo fmt -p craft-vp9enc --check + - run: cargo clippy -p craft-vp9enc --all-targets --locked -- -D warnings + - run: cargo test -p craft-vp9enc --locked + - run: cargo check -p craft-vp9enc --locked --target wasm32-unknown-unknown diff --git a/ATTRIBUTION.md b/ATTRIBUTION.md index 0e85749fac..cf39e3abf3 100644 --- a/ATTRIBUTION.md +++ b/ATTRIBUTION.md @@ -77,3 +77,16 @@ and the exact transfer revision are recorded in each vendored crate's SOURCE.jso | `hayro-interpret 0.7.0 / assets/CGATS001Compat-v2-micro.icc` | Clinton Ingram (saucecontrol/Compact-ICC-Profiles) | https://github.com/storytold/pdfcraft/blob/7437bde378f43856eada40af25b983812e615e8a/vendor/hayro-interpret/assets/CGATS001Compat-v2-micro.icc | CC0-1.0 | `73e1ba37d2bad5bab2a964f40a9eed96209666efc067c3322626214bbef234a0` | Default CMYK colour conversion (pinned hayro-interpret) | | `hayro-cmap 0.1.0 / assets/cmaps.brotli` | Adobe | https://github.com/storytold/pdfcraft/blob/a2ec27b4775f53c40cad3456936d9406611e3a8d/vendor/hayro-cmap/assets/cmaps.brotli | BSD-3-Clause | `ddf37af521d904d5646c2fc37b2b7e912dc1b56f0d210190b61f598154758e46` | Character-code mapping tables required to read CJK PDFs. Non-visual dependency data; existing embedding unchanged | | `hayro-interpret 0.7.0 / src/font/generated/metrics.rs` | Adobe (Core 14 AFM metrics, PDF specification encodings); tables by The Hayro Authors | https://github.com/storytold/pdfcraft/blob/7437bde378f43856eada40af25b983812e615e8a/vendor/hayro-interpret/src/font/generated/metrics.rs | MIT OR Apache-2.0 | `2ec9e608573eda459de1e1d9bda3bb494315e36bee885acc88912a11fe0caa41` | Glyph widths and encodings for non-embedded standard fonts. Non-visual dependency data; existing embedding unchanged | + +## Video Codec Tables and Normative Specification Derivations + +The pure-Rust video codec crates derive and extract tables strictly from normative open standards specifications: + +| Crate / file | Specification / Author | Extraction / Source | Licence | Use | +| --- | --- | --- | --- | --- | +| `crates/dnx/tools/extract_tables.py`, `crates/dnx/src/tables.rs` | SMPTE ST 2019-1:2016 | Extracted from SMPTE ST 2019-1 document text via committed Python script | SMPTE normative constants | DNxHD / DNxHR macroblock, VLC, and quantization tables | +| `crates/vp8/tools/extract_tables.py`, `crates/vp8/src/tables.rs` | IETF RFC 6386 (VP8) | Extracted from RFC 6386 normative specification text | RFC 6386 / IETF Trust | VP8 tree probabilities and quantizer tables | +| `crates/av1enc/src/tables.rs`, `crates/av1enc/src/cdf.rs` | Alliance for Open Media (AOMedia) | AV1 Bitstream & Decoding Process Specification v1.0.0 with Errata 1 | Alliance for Open Media Patent and Copyright License (MIT OR Apache-2.0) | Normative AV1 transform, scan, and symbol CDF tables | +| `crates/hevcenc/src/tables.rs` | ITU-T H.265 | ITU-T Recommendation H.265 | ITU-T normative tables | HEVC scan orders, deblock thresholds, and scaling matrices | +| `crates/vp9enc/src/tables.rs` | VP9 Bitstream & Decoding Process Specification | VP9 specification text | MIT OR Apache-2.0 | VP9 scan orders, intra mode contexts, and MV contexts | + diff --git a/Cargo.lock b/Cargo.lock index 5da9397766..c3305fe8c2 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -824,6 +824,10 @@ dependencies = [ "thiserror 2.0.21", ] +[[package]] +name = "craft-av1enc" +version = "0.1.0" + [[package]] name = "craft-cms" version = "0.1.0" @@ -832,6 +836,19 @@ dependencies = [ "rayon", ] +[[package]] +name = "craft-codecs" +version = "0.1.0" +dependencies = [ + "craft-av1enc", + "craft-dnx", + "craft-h264enc", + "craft-hevcenc", + "craft-prores", + "craft-vp8", + "craft-vp9enc", +] + [[package]] name = "craft-crw" version = "0.1.0" @@ -855,6 +872,15 @@ dependencies = [ "rfd", ] +[[package]] +name = "craft-dnx" +version = "0.1.0" +dependencies = [ + "craft-testkit", + "rayon", + "thiserror 2.0.21", +] + [[package]] name = "craft-doc" version = "0.1.0" @@ -949,6 +975,19 @@ dependencies = [ "craft-fonts-build", ] +[[package]] +name = "craft-h264enc" +version = "0.1.0" +dependencies = [ + "craft-media", + "rayon", + "thiserror 2.0.21", +] + +[[package]] +name = "craft-hevcenc" +version = "0.1.0" + [[package]] name = "craft-i18n" version = "0.1.0" @@ -1116,6 +1155,16 @@ dependencies = [ "unicode-normalization", ] +[[package]] +name = "craft-prores" +version = "0.1.0" +dependencies = [ + "craft-media", + "craft-testkit", + "rayon", + "thiserror 2.0.21", +] + [[package]] name = "craft-quit" version = "0.1.0" @@ -1138,6 +1187,13 @@ dependencies = [ "toml_edit", ] +[[package]] +name = "craft-testkit" +version = "0.1.0" +dependencies = [ + "craft-media-testkit", +] + [[package]] name = "craft-text" version = "0.1.0" @@ -1217,6 +1273,18 @@ dependencies = [ "thiserror 2.0.21", ] +[[package]] +name = "craft-vp8" +version = "0.1.0" +dependencies = [ + "craft-testkit", + "thiserror 2.0.21", +] + +[[package]] +name = "craft-vp9enc" +version = "0.1.0" + [[package]] name = "craft-web-api" version = "0.1.0" diff --git a/Cargo.toml b/Cargo.toml index f1c5adcddd..185d0726c6 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -55,6 +55,15 @@ craft-vectorcraft-format = { path = "crates/vectorcraft-format" } craft-web-api = { path = "crates/web-api" } craft-window = { path = "crates/window" } craft-web-host = { path = "crates/web-host" } +craft-h264enc = { path = "crates/h264enc" } +craft-prores = { path = "crates/prores" } +craft-hevcenc = { path = "crates/hevcenc" } +craft-av1enc = { path = "crates/av1enc" } +craft-vp9enc = { path = "crates/vp9enc" } +craft-dnx = { path = "crates/dnx" } +craft-vp8 = { path = "crates/vp8" } +craft-testkit = { path = "crates/testkit" } +craft-codecs = { path = "crates/codecs" } egui = { version = "0.37", default-features = false } rayon = "1" serde = { version = "1", features = ["derive"] } diff --git a/NOTICE b/NOTICE index bd88d43e7a..7384d9270b 100644 --- a/NOTICE +++ b/NOTICE @@ -252,3 +252,30 @@ Its white-level plateau estimate (saturation_plateau) is LightCraft's vendor::wh same revision. The CRW compression was derived clean-room from CC0 sample files and the maintainer's own photographs by black-box comparison with a reference decoder's command-line output; no raw-decoder source was consulted (crates/crw/clean-room/sources.md). + +Video codecs source provenance + +The pure-Rust video codec crates (crates/h264enc, crates/prores, crates/dnx, crates/vp8, +crates/hevcenc, crates/vp9enc, crates/av1enc) adapt and port video codec implementations +from FilmCraft (https://github.com/storytold/filmcraft) pinned at revision +68919ed87aa6a097a4d21414ec7c438883f5fe22: +- crates/h264enc is ported from FilmCraft's crates/h264enc (filmcraft-h264enc). +- crates/prores is ported from FilmCraft's crates/prores (filmcraft-prores). +- crates/dnx is ported from FilmCraft's crates/dnx (filmcraft-dnx). +- crates/vp8 is ported from FilmCraft's crates/vp8 (filmcraft-vp8). +- crates/hevcenc: ITU-T H.265 intra/inter prediction, deblocking, and constant tables are + adapted and mirrored from FilmCraft's crates/hevc (filmcraft-hevc decoder). +- crates/vp9enc: VP9 normative tables and boolean entropy coding are adapted and transcribed + from FilmCraft's crates/vp9 (filmcraft-vp9 decoder). +- crates/av1enc: Inverse transform routines and OBU bitstream syntax are adapted and mirrored + from FilmCraft's crates/av1 (filmcraft-av1 decoder), alongside normative specification tables + from the AOMedia AV1 specification. + +FilmCraft contributors to these crates include: aiongg, dylanmtaylor, earlvanze, echelon, +g1obalworlds, jahvari, EthanSuquet, JordyUSA, MrCloudy2, Silvertree2010, Tom1tk, +adventurelands, cirocueto2013-wq, kler, marshiyar, skylartaylor, thecyberbutler, vangel76, +and the FilmCraft contributors. +Source: https://github.com/storytold/filmcraft +Copyright (c) 2026 ArtCraft Team and the FilmCraft contributors. +Licensed under MIT OR Apache-2.0. + diff --git a/crates/av1enc/Cargo.toml b/crates/av1enc/Cargo.toml new file mode 100644 index 0000000000..d7228d1add --- /dev/null +++ b/crates/av1enc/Cargo.toml @@ -0,0 +1,13 @@ +[package] +name = "craft-av1enc" +description = "Clean-room pure-Rust AV1 encoder for the Craft ecosystem" +version.workspace = true +edition.workspace = true +license.workspace = true +rust-version.workspace = true +repository.workspace = true + +[lints] +workspace = true + +[dependencies] diff --git a/crates/av1enc/README.md b/crates/av1enc/README.md new file mode 100644 index 0000000000..1bff955be2 --- /dev/null +++ b/crates/av1enc/README.md @@ -0,0 +1,68 @@ +# craft-av1enc + +Clean-room, pure-Rust AV1 encoder for MP4 / WebM export (layer L0, `std` only, +no `unsafe`, builds for `wasm32-unknown-unknown`). + +## What is implemented + +- **Stream**: Main profile (`seq_profile` 0), 4:2:0, 8 or 10 bit, BT.709 colour description, + limited or full range. Sequence header with one operating point; `seq_level_idx` given or the + lowest Annex A level whose MaxPicSize / MaxH/VSize / MaxDisplayRate (and, in bitrate mode, Main + tier MaxBitrate) fit. Order hints, film grain, superres, CDEF, loop restoration, intra edge + filter, filter intra, inter-intra, masked / distance compound, warped motion and dual filter + are disabled in the sequence header. +- **Packets**: one temporal unit per frame as stored in MP4 / Matroska samples: OBUs with size + fields, no temporal delimiter; key frames start with the sequence header OBU, followed by one + `OBU_FRAME` (frame header + tile group). Every frame is shown, in input order. + `Encoder::av1c()` returns the `AV1CodecConfigurationRecord` (also the WebM `CodecPrivate`). +- **Frames**: a `KEY_FRAME` every `keyint` frames, `INTER_FRAME`s otherwise. Inter frames use + slot 0 (the previous reconstruction) for all seven references and refresh slot 0 only, so + `LAST_FRAME` is always the previous frame. CDFs adapt per symbol and are saved at the end of + every frame; inter frames load them from `LAST_FRAME` (`primary_ref_frame` = 0). One tile, + `TX_MODE_LARGEST`, `reduced_tx_set`, no segmentation, no delta q / lf. +- **Deblocking**: the normative loop filter (7.14) runs on the reconstruction; the frame level + (the same for both luma directions and both chroma planes, no deltas, sharpness 0) is searched + per frame for the lowest luma error. +- **Tiles**: the multi-symbol arithmetic encoder is the exact inverse of the spec's symbol + decoder (8.2), including the exit process padding; CDF adaptation as in 8.2.6. +- **Partitions**: 64x64 superblocks, `PARTITION_NONE` / `PARTITION_SPLIT` down to 8x8 chosen by + a rate-distortion cost (SSE + λ·estimated bits); blocks crossing the frame edge are split as + the syntax requires. 64x64 blocks are only used as inter skip blocks. +- **Intra**: DC, V, H, Paeth, smooth, smooth-V, smooth-H for luma and chroma, + ranked by SATD with a rate-distortion choice among the best three; the spec's edge preparation + and above-right / below-left availability. +- **Inter**: single reference (`LAST_FRAME`), `NEWMV` / `NEARESTMV` / `NEARMV` / `GLOBALMV` + chosen from the spec's motion vector prediction stack (7.10.2, mirrored exactly); motion search + from neighbour / co-located / parent candidates, a whole-sample diamond search and half- then + quarter-sample refinement (`allow_high_precision_mv` = 0); the normative 8-tap `EIGHTTAP` + prediction for luma and chroma; skip blocks; per-block intra / inter choice. +- **Residual**: one transform per plane and block (4x4 ... 32x32): for intra luma `DCT_DCT` or + the mode's preferred `ADST` combination (rate-distortion choice over the two best modes), the + mode-dependent type for intra chroma, `DCT_DCT` for inter blocks; dead-zone quantisation with the + spec's `Dc_Qlookup` / `Ac_Qlookup`; the normative dequantisation and inverse transforms in the + reconstruction loop; full coefficient coding (all_zero, tx type, eob_pt, eob_extra, + coeff_base_eob, coeff_base, coeff_br, dc_sign, Golomb) with the exact contexts. +- **Rate control**: `ConstantQ(base_q_idx)`; `Bitrate { kbps }` adapts `base_q_idx` per frame + towards the average target (key frames get a lower quantizer and a larger share of the budget of + their group of `keyint` frames, the inter frames the rest; the first key frame is searched in a + few passes, a later key frame that misses its budget by more than 19 % is coded again; the + deviation from the budget is paid back over 16 frames). + +## Limits + +- One tile: width and height up to 4096 and at most 2304 superblocks (4096x2304 samples). +- No CDEF or loop restoration, no compound prediction, no rectangular or 4xN blocks, no + directional intra modes other than V / H, no CfL or palette, no transform-size splits, so + compression is below mature encoders. +- 4:2:0 only (Main profile), no 12 bit. + +## Specification and provenance + +Implemented from the **AV1 Bitstream & Decoding Process Specification, version 1.0.0 with +Errata 1** (AOMediaCodec, 2019) and the **AV1 Codec ISO Media File Format Binding** (v1.2.0) for `av1C`. + +The constant tables (default CDFs, scan orders, quantizer lookup tables, interpolation filter +taps, smooth weights, block / transform size tables, ...) and the processes the encoder must +reproduce (inverse transforms, intra and inter prediction rounding, context derivations, +motion vector prediction) are derived directly from the normative AV1 specification. +No source of libaom, dav1d, rav1e, SVT-AV1, or ffmpeg was consulted. diff --git a/crates/av1enc/src/bitw.rs b/crates/av1enc/src/bitw.rs new file mode 100644 index 0000000000..e4c8a1e691 --- /dev/null +++ b/crates/av1enc/src/bitw.rs @@ -0,0 +1,87 @@ +//! MSB-first bit writer for the OBU headers (spec 4.10 `f(n)` descriptors) and `leb128()`. + +#[derive(Default)] +pub(crate) struct BitWriter { + pub buf: Vec, + nbits: usize, +} + +impl BitWriter { + pub fn new() -> Self { + BitWriter::default() + } + + /// `f(n)`: writes the low `n` bits of `v`, most significant first. + pub fn f(&mut self, n: u32, v: u32) { + for i in (0..n).rev() { + self.bit((v >> i) & 1 != 0); + } + } + + pub fn bit(&mut self, b: bool) { + if self.nbits.is_multiple_of(8) { + self.buf.push(0); + } + if b { + let last = self.buf.len() - 1; + self.buf[last] |= 0x80 >> (self.nbits % 8); + } + self.nbits += 1; + } + + /// `byte_alignment()`: zero bits up to the next byte boundary. + pub fn byte_align(&mut self) { + while !self.nbits.is_multiple_of(8) { + self.bit(false); + } + } + + /// `trailing_bits()`: a one bit followed by zero bits up to the byte boundary. + pub fn trailing_bits(&mut self) { + self.bit(true); + self.byte_align(); + } + + pub fn into_bytes(self) -> Vec { + self.buf + } +} + +/// `leb128()` encoding of an OBU size. +pub(crate) fn leb128(mut v: usize, out: &mut Vec) { + loop { + let mut b = (v & 0x7f) as u8; + v >>= 7; + if v != 0 { + b |= 0x80; + } + out.push(b); + if v == 0 { + break; + } + } +} + +/// An OBU with `obu_has_size_field` = 1 and no extension header. +pub(crate) fn obu(obu_type: u8, payload: &[u8], out: &mut Vec) { + out.push((obu_type << 3) | 0x02); + leb128(payload.len(), out); + out.extend_from_slice(payload); +} + +#[cfg(test)] +mod tests { + use super::*; + + #[test] + fn bits_and_leb128() { + let mut w = BitWriter::new(); + w.f(3, 0b101); + w.f(6, 0b110011); + w.trailing_bits(); + assert_eq!(w.into_bytes(), vec![0b1011_1001, 0b1100_0000]); + let mut v = Vec::new(); + leb128(300, &mut v); + assert_eq!(v, vec![0xAC, 0x02]); + } +} diff --git a/crates/av1enc/src/cdf.rs b/crates/av1enc/src/cdf.rs new file mode 100644 index 0000000000..1740206df8 --- /dev/null +++ b/crates/av1enc/src/cdf.rs @@ -0,0 +1,172 @@ +//! The adaptive CDFs this encoder codes with (the subset of spec 6.8.2 init_non_coeff_cdfs / +//! init_coeff_cdfs that its syntax uses). Each innermost array holds N cumulative values +//! (the last is 32768) plus the adaptation counter. + +use crate::tables::{ + DEFAULT_ANGLE_DELTA_CDF, DEFAULT_COEFF_BASE_CDF, DEFAULT_COEFF_BASE_EOB_CDF, DEFAULT_COEFF_BR_CDF, DEFAULT_DC_SIGN_CDF, DEFAULT_DRL_MODE_CDF, + DEFAULT_EOB_EXTRA_CDF, DEFAULT_EOB_PT_16_CDF, DEFAULT_EOB_PT_32_CDF, DEFAULT_EOB_PT_64_CDF, DEFAULT_EOB_PT_128_CDF, DEFAULT_EOB_PT_256_CDF, + DEFAULT_EOB_PT_512_CDF, DEFAULT_EOB_PT_1024_CDF, DEFAULT_INTER_TX_TYPE_SET3_CDF, DEFAULT_INTRA_TX_TYPE_SET2_CDF, DEFAULT_IS_INTER_CDF, + DEFAULT_MV_BIT_CDF, DEFAULT_MV_CLASS_CDF, DEFAULT_MV_CLASS0_BIT_CDF, DEFAULT_MV_CLASS0_FR_CDF, DEFAULT_MV_FR_CDF, DEFAULT_MV_JOINT_CDF, + DEFAULT_MV_SIGN_CDF, DEFAULT_NEW_MV_CDF, DEFAULT_PARTITION_W8_CDF, DEFAULT_PARTITION_W16_CDF, DEFAULT_PARTITION_W32_CDF, + DEFAULT_PARTITION_W64_CDF, DEFAULT_REF_MV_CDF, DEFAULT_SINGLE_REF_CDF, DEFAULT_SKIP_CDF, DEFAULT_TXB_SKIP_CDF, DEFAULT_UV_MODE_CFL_ALLOWED_CDF, + DEFAULT_UV_MODE_CFL_NOT_ALLOWED_CDF, DEFAULT_Y_MODE_CDF, DEFAULT_ZERO_MV_CDF, +}; + +#[derive(Clone)] +pub(crate) struct Cdfs { + pub y_mode: [[u16; 14]; 4], + pub uv_mode_cfl_not_allowed: [[u16; 14]; 13], + pub uv_mode_cfl_allowed: [[u16; 15]; 13], + pub angle_delta: [[u16; 8]; 8], + pub partition_w8: [[u16; 5]; 4], + pub partition_w16: [[u16; 11]; 4], + pub partition_w32: [[u16; 11]; 4], + pub partition_w64: [[u16; 11]; 4], + pub skip: [[u16; 3]; 3], + pub is_inter: [[u16; 3]; 4], + pub single_ref: [[[u16; 3]; 6]; 3], + pub new_mv: [[u16; 3]; 6], + pub zero_mv: [[u16; 3]; 2], + pub ref_mv: [[u16; 3]; 6], + pub drl_mode: [[u16; 3]; 3], + pub mv_joint: [u16; 5], + pub mv_class: [[u16; 12]; 2], + pub mv_class0_bit: [[u16; 3]; 2], + pub mv_fr: [[u16; 5]; 2], + pub mv_class0_fr: [[[u16; 5]; 2]; 2], + pub mv_sign: [[u16; 3]; 2], + pub mv_bit: [[[u16; 3]; 10]; 2], + pub intra_tx_type_set2: [[[u16; 6]; 13]; 3], + pub inter_tx_type_set3: [[u16; 3]; 4], + pub txb_skip: [[[u16; 3]; 13]; 5], + pub eob_pt_16: [[[u16; 6]; 2]; 2], + pub eob_pt_32: [[[u16; 7]; 2]; 2], + pub eob_pt_64: [[[u16; 8]; 2]; 2], + pub eob_pt_128: [[[u16; 9]; 2]; 2], + pub eob_pt_256: [[[u16; 10]; 2]; 2], + pub eob_pt_512: [[u16; 11]; 2], + pub eob_pt_1024: [[u16; 12]; 2], + pub eob_extra: [[[[u16; 3]; 9]; 2]; 5], + pub dc_sign: [[[u16; 3]; 3]; 2], + pub coeff_base_eob: [[[[u16; 4]; 4]; 2]; 5], + pub coeff_base: [[[[u16; 5]; 42]; 2]; 5], + pub coeff_br: [[[[u16; 5]; 21]; 2]; 5], +} + +impl Cdfs { + /// init_non_coeff_cdfs( ) followed by init_coeff_cdfs( ) for `base_q_idx`. + pub fn new(base_q_idx: u32) -> Box { + let idx = match base_q_idx { + 0..=20 => 0, + 21..=60 => 1, + 61..=120 => 2, + _ => 3, + }; + Box::new(Cdfs { + y_mode: DEFAULT_Y_MODE_CDF, + uv_mode_cfl_not_allowed: DEFAULT_UV_MODE_CFL_NOT_ALLOWED_CDF, + uv_mode_cfl_allowed: DEFAULT_UV_MODE_CFL_ALLOWED_CDF, + angle_delta: DEFAULT_ANGLE_DELTA_CDF, + partition_w8: DEFAULT_PARTITION_W8_CDF, + partition_w16: DEFAULT_PARTITION_W16_CDF, + partition_w32: DEFAULT_PARTITION_W32_CDF, + partition_w64: DEFAULT_PARTITION_W64_CDF, + skip: DEFAULT_SKIP_CDF, + is_inter: DEFAULT_IS_INTER_CDF, + single_ref: DEFAULT_SINGLE_REF_CDF, + new_mv: DEFAULT_NEW_MV_CDF, + zero_mv: DEFAULT_ZERO_MV_CDF, + ref_mv: DEFAULT_REF_MV_CDF, + drl_mode: DEFAULT_DRL_MODE_CDF, + mv_joint: DEFAULT_MV_JOINT_CDF, + mv_class: DEFAULT_MV_CLASS_CDF, + mv_class0_bit: [DEFAULT_MV_CLASS0_BIT_CDF; 2], + mv_fr: DEFAULT_MV_FR_CDF, + mv_class0_fr: DEFAULT_MV_CLASS0_FR_CDF, + mv_sign: [DEFAULT_MV_SIGN_CDF; 2], + mv_bit: [DEFAULT_MV_BIT_CDF; 2], + intra_tx_type_set2: DEFAULT_INTRA_TX_TYPE_SET2_CDF, + inter_tx_type_set3: DEFAULT_INTER_TX_TYPE_SET3_CDF, + txb_skip: DEFAULT_TXB_SKIP_CDF[idx], + eob_pt_16: DEFAULT_EOB_PT_16_CDF[idx], + eob_pt_32: DEFAULT_EOB_PT_32_CDF[idx], + eob_pt_64: DEFAULT_EOB_PT_64_CDF[idx], + eob_pt_128: DEFAULT_EOB_PT_128_CDF[idx], + eob_pt_256: DEFAULT_EOB_PT_256_CDF[idx], + eob_pt_512: DEFAULT_EOB_PT_512_CDF[idx], + eob_pt_1024: DEFAULT_EOB_PT_1024_CDF[idx], + eob_extra: DEFAULT_EOB_EXTRA_CDF[idx], + dc_sign: DEFAULT_DC_SIGN_CDF[idx], + coeff_base_eob: DEFAULT_COEFF_BASE_EOB_CDF[idx], + coeff_base: DEFAULT_COEFF_BASE_CDF[idx], + coeff_br: DEFAULT_COEFF_BR_CDF[idx], + }) + } +} + +impl Cdfs { + /// The counter reset of load_cdfs( ): the last entry of every CDF array is set to 0. + pub fn clear_counts(&mut self) { + macro_rules! clr { + ($($f:ident),*) => { $( self.$f.clear_count(); )* }; + } + clr!( + y_mode, + uv_mode_cfl_not_allowed, + uv_mode_cfl_allowed, + angle_delta, + partition_w8, + partition_w16, + partition_w32, + partition_w64, + skip, + is_inter, + single_ref, + new_mv, + zero_mv, + ref_mv, + drl_mode, + mv_joint, + mv_class, + mv_class0_bit, + mv_fr, + mv_class0_fr, + mv_sign, + mv_bit, + intra_tx_type_set2, + inter_tx_type_set3, + txb_skip, + eob_pt_16, + eob_pt_32, + eob_pt_64, + eob_pt_128, + eob_pt_256, + eob_pt_512, + eob_pt_1024, + eob_extra, + dc_sign, + coeff_base_eob, + coeff_base, + coeff_br + ); + } +} + +/// Zeroes the adaptation counter (last element) of every innermost CDF array. +trait ClearCount { + fn clear_count(&mut self); +} + +impl ClearCount for [u16; N] { + fn clear_count(&mut self) { + self[N - 1] = 0; + } +} + +impl ClearCount for [T; M] { + fn clear_count(&mut self) { + for e in self.iter_mut() { + e.clear_count(); + } + } +} diff --git a/crates/av1enc/src/decide.rs b/crates/av1enc/src/decide.rs new file mode 100644 index 0000000000..c2ce97fef5 --- /dev/null +++ b/crates/av1enc/src/decide.rs @@ -0,0 +1,1076 @@ +//! Mode decision and reconstruction (not normative): partitions, intra modes, motion vectors, +//! transform coefficients. Every prediction and reconstruction step uses the normative +//! processes, so the reconstruction built here is exactly what a decoder produces; the syntax +//! is written afterwards by `writer.rs`. + +use crate::pred::{INTRA_MODES_USED, InterBlock, IntraEdge, Plane, edges, half_planes, predict_inter, predict_intra}; +use crate::tables::{ + AC_QLOOKUP, BLOCK_8X8, BLOCK_64X64, DC_PRED, DC_QLOOKUP, DCT_DCT, DEFAULT_SCAN_4X4, DEFAULT_SCAN_8X8, DEFAULT_SCAN_16X16, DEFAULT_SCAN_32X32, + MODE_TO_TXFM, NEWMV, NUM_4X4_BLOCKS_WIDE, PARTITION_SPLIT, PARTITION_SUBSIZE, TX_4X4, TX_8X8, TX_16X16, TX_32X32, TX_64X64, TX_SET_DCTONLY, + TX_SET_INTER_3, TX_SET_INTRA_2, TX_SIZE_SQR_UP, TX_TYPE_IN_SET_INTER, TX_TYPE_IN_SET_INTRA, +}; +use crate::transform::{forward_2d, inverse_2d}; + +/// Decisions for one coded block. +#[derive(Clone)] +pub(crate) struct Leaf { + pub bsize: usize, + pub is_inter: bool, + pub y_mode: usize, + pub uv_mode: usize, + pub tx_type_y: usize, + /// Motion vector [row, col] in 1/8 luma samples (inter blocks). + pub mv: [i32; 2], + pub skip: bool, + /// Quantised coefficients of the single transform block of each plane (raster order). + pub coefs: [Vec; 3], +} + +pub(crate) enum Node { + Out, + Leaf(Box), + Split(Box<[Node; 4]>), +} + +/// A reconstructed frame kept as a reference. +pub(crate) struct RefFrame { + pub planes: [Plane; 3], + /// Motion vectors chosen for the frame (per 4x4), used as search candidates. + pub mvs: Vec<[i32; 2]>, + /// The frame's loop filter level. + pub lf_level: u32, + /// The CDFs saved at the end of the frame (loaded by the next frame). + pub cdfs: Box, +} + +pub(crate) struct FrameGeom { + pub width: usize, + pub height: usize, + pub mi_cols: usize, + pub mi_rows: usize, + pub sb_cols: usize, + pub sb_rows: usize, +} + +impl FrameGeom { + pub fn new(width: usize, height: usize) -> FrameGeom { + let mi_cols = 2 * width.div_ceil(8); + let mi_rows = 2 * height.div_ceil(8); + FrameGeom { width, height, mi_cols, mi_rows, sb_cols: mi_cols.div_ceil(16), sb_rows: mi_rows.div_ceil(16) } + } + /// Padded plane size (whole superblocks). + pub fn padded(&self, plane: usize) -> (usize, usize) { + let s = (plane > 0) as usize; + ((self.sb_cols * 64) >> s, (self.sb_rows * 64) >> s) + } + /// Visible plane size. + pub fn visible(&self, plane: usize) -> (usize, usize) { + if plane == 0 { (self.width, self.height) } else { (self.width.div_ceil(2), self.height.div_ceil(2)) } + } +} + +pub(crate) fn tx_size_for(log2: u32) -> usize { + match log2 { + 2 => TX_4X4, + 3 => TX_8X8, + 4 => TX_16X16, + 5 => TX_32X32, + _ => TX_64X64, + } +} + +/// get_tx_set( ) for the square sizes used here with reduced_tx_set = 1. +pub(crate) fn tx_set(tx_sz: usize, is_inter: bool) -> usize { + let sqr_up = TX_SIZE_SQR_UP[tx_sz] as usize; + if sqr_up > TX_32X32 { + return TX_SET_DCTONLY; + } + if is_inter { + TX_SET_INTER_3 + } else if sqr_up == TX_32X32 { + TX_SET_DCTONLY + } else { + TX_SET_INTRA_2 + } +} + +pub(crate) fn tx_in_set(set: usize, is_inter: bool, tx_type: usize) -> bool { + if is_inter { TX_TYPE_IN_SET_INTER[set][tx_type] == 1 } else { TX_TYPE_IN_SET_INTRA[set][tx_type] == 1 } +} + +/// The chroma transform type of an intra block (compute_tx_type for plane > 0). +pub(crate) fn intra_uv_tx_type(uv_mode: usize, uv_tx: usize) -> usize { + let t = MODE_TO_TXFM[uv_mode] as usize; + if tx_in_set(tx_set(uv_tx, false), false, t) { t } else { DCT_DCT } +} + +/// Geometry and coding mode of one square transform block. +#[derive(Clone, Copy)] +struct TransformBlock { + plane: usize, + x: usize, + y: usize, + log2: u32, + tx_type: usize, + intra: bool, +} + +struct TxResult { + levels: Vec, + nonzero: bool, + dist: f64, + bits: f64, +} + +/// The output and scratch buffers of one reconstruction, each `n * n` entries long. +struct ReconBuffers<'a> { + deq: &'a mut [i32], + resid: &'a mut [i32], + recon: &'a mut [u16], +} + +/// Outcome of trying one transform block; the levels and the reconstruction stay in `TxScratch`. +#[derive(Clone, Copy)] +struct TxEval { + nonzero: bool, + dist: f64, + bits: f64, +} + +/// Buffers of the transform path, sized for the largest coded transform block (32x32). Only the +/// first `n * n` entries are used; they are reused across candidates instead of being allocated. +struct TxScratch { + res: [i32; 1024], + coef: [f32; 1024], + levels: [i32; 1024], + deq: [i32; 1024], + resid: [i32; 1024], + recon: [u16; 1024], +} + +impl Default for TxScratch { + fn default() -> Self { + TxScratch { res: [0; 1024], coef: [0.0; 1024], levels: [0; 1024], deq: [0; 1024], resid: [0; 1024], recon: [0; 1024] } + } +} + +/// Saved encoder state of a block area (reconstruction, BlockDecoded flags). +struct Snapshot { + planes: [Vec; 3], + decoded: Box<[[[bool; 35]; 35]; 3]>, +} + +pub(crate) struct Decider<'a> { + pub g: &'a FrameGeom, + pub bd: u32, + pub intra_only: bool, + pub src: &'a [Plane; 3], + pub rec: [Plane; 3], + pub refr: Option<&'a RefFrame>, + decoded: Box<[[[bool; 35]; 35]; 3]>, + /// Decision motion vectors per 4x4 (search candidates). + pub mvs: Vec<[i32; 2]>, + dc_q: f32, + ac_q: f32, + dc_qi: i64, + ac_qi: i64, + /// Smallest magnitude that quantises to a non-zero level, `[dc, ac][intra]`. + dead_zone: [[f32; 2]; 2], + lambda: f64, + lambda_sad: f64, + /// Half-sample interpolations of the reference luma (visible area): (8, 0), (0, 8), (8, 8). + half: [Vec; 3], + /// Transform buffers, taken out while a block is being tried (see `code_tx`). + scratch: Option>, +} + +const MODE_BITS_INTRA: f64 = 3.0; +/// Dead-zone quantiser rounding offsets (below one half) for inter and intra blocks. +const INTER_RND: f32 = 0.30; +const INTRA_RND: f32 = 0.38; + +impl<'a> Decider<'a> { + pub fn new(g: &'a FrameGeom, bd: u32, qidx: u8, intra_only: bool, src: &'a [Plane; 3], refr: Option<&'a RefFrame>) -> Self { + let bdi = ((bd - 8) >> 1) as usize; + let dc_qi = DC_QLOOKUP[bdi][qidx as usize] as i64; + let ac_qi = AC_QLOOKUP[bdi][qidx as usize] as i64; + // Lagrangian multiplier in squared-sample units of the coded bit depth. + let step = ac_qi as f64 / 8.0; + let lambda = 0.25 * step * step; + let rec = std::array::from_fn(|p| { + let (w, h) = g.padded(p); + Plane::new(w, h) + }); + Decider { + g, + bd, + intra_only, + src, + rec, + refr, + decoded: Box::new([[[false; 35]; 35]; 3]), + mvs: vec![[0, 0]; g.mi_cols * g.mi_rows], + dc_q: dc_qi as f32, + ac_q: ac_qi as f32, + dc_qi, + ac_qi, + dead_zone: [dc_qi, ac_qi].map(|q| [INTER_RND, INTRA_RND].map(|rnd| zero_threshold(q as f32, rnd))), + lambda, + lambda_sad: lambda.sqrt(), + half: match refr { + Some(rf) => half_planes(&rf.planes[0], g.width, g.height, bd), + None => Default::default(), + }, + scratch: None, + } + } + + /// Decides one 64x64 superblock (reconstruction is written to `rec`). + pub fn superblock(&mut self, r: usize, c: usize) -> Node { + // clear_block_decoded_flags( r, c, sbSize4 ) + for plane in 0..3 { + let s = (plane > 0) as usize; + let sb_w4 = (self.g.mi_cols as isize - c as isize) >> s; + let sb_h4 = (self.g.mi_rows as isize - r as isize) >> s; + let bd = &mut self.decoded[plane]; + for y in -1..=((16 >> s) as isize) { + for x in -1..=((16 >> s) as isize) { + let v = if y < 0 && x < sb_w4 { true } else { x < 0 && y < sb_h4 }; + bd[(y + 1) as usize][(x + 1) as usize] = v; + } + } + bd[(16 >> s) + 1][0] = false; + } + self.partition(r, c, BLOCK_64X64, None).0 + } + + fn decoded_at(&self, plane: usize, y: isize, x: isize) -> bool { + let (yy, xx) = (y + 1, x + 1); + if yy < 0 || xx < 0 || yy > 34 || xx > 34 { + return false; + } + self.decoded[plane][yy as usize][xx as usize] + } + + /// Marks the transform block of `plane` covering the block at (r, c) as decoded. + fn mark_decoded(&mut self, plane: usize, r: usize, c: usize, n4: usize) { + let s = (plane > 0) as usize; + let by = (r & 15) >> s; + let bx = (c & 15) >> s; + let step = (n4 >> s).max(1); + for i in 0..step { + for j in 0..step { + if by + i + 1 < 35 && bx + j + 1 < 35 { + self.decoded[plane][by + i + 1][bx + j + 1] = true; + } + } + } + } + + fn snapshot(&self, r: usize, c: usize, n4: usize) -> Snapshot { + let planes = std::array::from_fn(|p| { + let s = (p > 0) as usize; + let (x0, y0, n) = ((c * 4) >> s, (r * 4) >> s, (n4 * 4) >> s); + let pl = &self.rec[p]; + let mut v = Vec::with_capacity(n * n); + for y in y0..y0 + n { + v.extend_from_slice(&pl.data[y * pl.stride + x0..y * pl.stride + x0 + n]); + } + v + }); + Snapshot { planes, decoded: self.decoded.clone() } + } + + fn restore(&mut self, snap: &Snapshot, r: usize, c: usize, n4: usize) { + for p in 0..3 { + let s = (p > 0) as usize; + let (x0, y0, n) = ((c * 4) >> s, (r * 4) >> s, (n4 * 4) >> s); + let pl = &mut self.rec[p]; + for (i, y) in (y0..y0 + n).enumerate() { + pl.data[y * pl.stride + x0..y * pl.stride + x0 + n].copy_from_slice(&snap.planes[p][i * n..i * n + n]); + } + } + self.decoded.clone_from(&snap.decoded); + } + + fn set_mvs(&mut self, r: usize, c: usize, n4: usize, mv: [i32; 2]) { + for y in r..(r + n4).min(self.g.mi_rows) { + for x in c..(c + n4).min(self.g.mi_cols) { + self.mvs[y * self.g.mi_cols + x] = mv; + } + } + } + + fn partition(&mut self, r: usize, c: usize, bsize: usize, parent_mv: Option<[i32; 2]>) -> (Node, f64) { + let g = self.g; + if r >= g.mi_rows || c >= g.mi_cols { + return (Node::Out, 0.0); + } + let n4 = NUM_4X4_BLOCKS_WIDE[bsize] as usize; + let half = n4 >> 1; + let has_rows = r + half < g.mi_rows; + let has_cols = c + half < g.mi_cols; + let can_none = has_rows && has_cols && (bsize != BLOCK_64X64 || !self.intra_only); + let can_split = bsize > BLOCK_8X8; + let sub = PARTITION_SUBSIZE[PARTITION_SPLIT][bsize] as usize; + if !can_none { + return self.split(r, c, sub, half, parent_mv); + } + let before = self.snapshot(r, c, n4); + let (leaf, cost_none) = self.leaf(r, c, bsize, parent_mv); + let cost_none = cost_none + self.lambda * 1.0; + if !can_split { + return (Node::Leaf(Box::new(leaf)), cost_none); + } + // a cheap skip block rarely gains from splitting + if leaf.skip && cost_none < self.lambda * 8.0 { + return (Node::Leaf(Box::new(leaf)), cost_none); + } + let after_none = self.snapshot(r, c, n4); + self.restore(&before, r, c, n4); + let pm = if leaf.is_inter { Some(leaf.mv) } else { parent_mv }; + let (node, cost_split) = self.split(r, c, sub, half, pm); + let cost_split = cost_split + self.lambda * 2.0; + if cost_split < cost_none { + (node, cost_split) + } else { + self.restore(&after_none, r, c, n4); + self.set_mvs(r, c, n4, if leaf.is_inter { leaf.mv } else { [0, 0] }); + (Node::Leaf(Box::new(leaf)), cost_none) + } + } + + fn split(&mut self, r: usize, c: usize, sub: usize, half: usize, pm: Option<[i32; 2]>) -> (Node, f64) { + let (n0, c0) = self.partition(r, c, sub, pm); + let (n1, c1) = self.partition(r, c + half, sub, pm); + let (n2, c2) = self.partition(r + half, c, sub, pm); + let (n3, c3) = self.partition(r + half, c + half, sub, pm); + (Node::Split(Box::new([n0, n1, n2, n3])), c0 + c1 + c2 + c3) + } + + /// Best coding of the block as a single (NONE partition) block. + fn leaf(&mut self, r: usize, c: usize, bsize: usize, parent_mv: Option<[i32; 2]>) -> (Leaf, f64) { + let n4 = NUM_4X4_BLOCKS_WIDE[bsize] as usize; + if self.intra_only { + let res = self.intra_leaf(r, c, bsize); + self.set_mvs(r, c, n4, [0, 0]); + return res; + } + let before = self.snapshot(r, c, n4); + let (inter, cost_inter) = self.inter_leaf(r, c, bsize, parent_mv); + if bsize == BLOCK_64X64 || inter.skip { + self.set_mvs(r, c, n4, inter.mv); + return (inter, cost_inter); + } + let after_inter = self.snapshot(r, c, n4); + self.restore(&before, r, c, n4); + let (intra, cost_intra) = self.intra_leaf(r, c, bsize); + if cost_intra < cost_inter { + self.set_mvs(r, c, n4, [0, 0]); + (intra, cost_intra) + } else { + self.restore(&after_inter, r, c, n4); + self.set_mvs(r, c, n4, inter.mv); + (inter, cost_inter) + } + } + + fn intra_leaf(&mut self, r: usize, c: usize, bsize: usize) -> (Leaf, f64) { + let n4 = NUM_4X4_BLOCKS_WIDE[bsize] as usize; + let log2 = (n4 * 4).trailing_zeros(); + let (sbr, sbc) = ((r & 15) as isize, (c & 15) as isize); + let bd = self.bd; + let mut pred = [0u16; 32 * 32]; + let mut best_pred = [0u16; 32 * 32]; + let mut best_levels = [0i32; 32 * 32]; + let mut best_recon = [0u16; 32 * 32]; + let n = 1usize << log2; + // luma + let e = IntraEdge { + x: c * 4, + y: r * 4, + have_left: c > 0, + have_above: r > 0, + have_above_right: self.decoded_at(0, sbr - 1, sbc + n4 as isize), + have_below_left: self.decoded_at(0, sbr + n4 as isize, sbc - 1), + log2, + max_x: (self.g.mi_cols * 4) as i32 - 1, + max_y: (self.g.mi_rows * 4) as i32 - 1, + bit_depth: bd, + }; + let ed = edges(&self.rec[0], &e); + // modes ranked by SATD, then a rate-distortion choice of mode and transform type among + // the best three + let mut ranked: [(f64, usize); INTRA_MODES_USED.len()] = INTRA_MODES_USED.map(|mode| { + predict_intra(&ed, &e, mode, &mut pred); + let sad = self.satd(0, c * 4, r * 4, n, &pred[..n * n]) as f64; + (sad + self.lambda_sad * if mode == DC_PRED { 1.0 } else { MODE_BITS_INTRA }, mode) + }); + ranked.sort_by(|a, b| a.0.total_cmp(&b.0)); + let tx_y = tx_size_for(log2); + let mut best: Option<(f64, usize, usize, TxEval, bool)> = None; + let mut sc = self.scratch.take().unwrap_or_default(); + for &(_, mode) in ranked.iter().take(3) { + predict_intra(&ed, &e, mode, &mut pred); + let mut types = [DCT_DCT; 2]; + let mut n_types = 1; + let alt = MODE_TO_TXFM[mode] as usize; + if alt != DCT_DCT && tx_in_set(tx_set(tx_y, false), false, alt) { + types[1] = alt; + n_types = 2; + } + for &t in &types[..n_types] { + let (res, has_recon) = + self.try_tx(&mut sc, TransformBlock { plane: 0, x: c * 4, y: r * 4, log2, tx_type: t, intra: true }, &pred[..n * n]); + let type_bits = if !res.nonzero || tx_set(tx_y, false) == TX_SET_DCTONLY { + 0.0 + } else if t == DCT_DCT { + 1.0 + } else { + 2.0 + }; + let mode_bits = if mode == DC_PRED { 1.0 } else { MODE_BITS_INTRA }; + let cost = res.dist + self.lambda * (res.bits + type_bits + mode_bits); + if best.as_ref().is_none_or(|b| cost < b.0) { + best_pred[..n * n].copy_from_slice(&pred[..n * n]); + best_levels[..n * n].copy_from_slice(&sc.levels[..n * n]); + if has_recon { + best_recon[..n * n].copy_from_slice(&sc.recon[..n * n]); + } + best = Some((cost, mode, t, res, has_recon)); + } + } + } + self.scratch = Some(sc); + // Every ranked mode tries DCT_DCT, so `best` is always set; without a candidate the block + // can't be coded intra (an infinite cost keeps it from being chosen). + let Some((_, y_mode, tx_type_y, ty, has_recon)) = best else { + let leaf = Leaf { + bsize, + is_inter: false, + y_mode: DC_PRED, + uv_mode: DC_PRED, + tx_type_y: DCT_DCT, + mv: [0, 0], + skip: true, + coefs: Default::default(), + }; + return (leaf, f64::INFINITY); + }; + self.put(0, c * 4, r * 4, n, if has_recon { &best_recon[..n * n] } else { &best_pred[..n * n] }); + let ty = TxResult { levels: best_levels[..n * n].to_vec(), nonzero: ty.nonzero, dist: ty.dist, bits: ty.bits }; + let tx_type_y = if ty.nonzero { tx_type_y } else { DCT_DCT }; + self.mark_decoded(0, r, c, n4); + // chroma (one mode for both planes) + let cn = n >> 1; + let clog2 = log2 - 1; + let (csbr, csbc) = (sbr >> 1, sbc >> 1); + let ce: [IntraEdge; 2] = std::array::from_fn(|i| IntraEdge { + x: c * 2, + y: r * 2, + have_left: c > 0, + have_above: r > 0, + have_above_right: self.decoded_at(1 + i, csbr - 1, csbc + (n4 >> 1) as isize), + have_below_left: self.decoded_at(1 + i, csbr + (n4 >> 1) as isize, csbc - 1), + log2: clog2, + max_x: (self.g.mi_cols * 2) as i32 - 1, + max_y: (self.g.mi_rows * 2) as i32 - 1, + bit_depth: bd, + }); + let ced = [edges(&self.rec[1], &ce[0]), edges(&self.rec[2], &ce[1])]; + let mut best_uv = (f64::MAX, DC_PRED); + for &mode in INTRA_MODES_USED.iter() { + let mut sad = 0u64; + for i in 0..2 { + predict_intra(&ced[i], &ce[i], mode, &mut pred); + sad += self.satd(1 + i, c * 2, r * 2, cn, &pred[..cn * cn]); + } + let cost = sad as f64 + self.lambda_sad * if mode == DC_PRED { 1.0 } else { 2.5 }; + if cost < best_uv.0 { + best_uv = (cost, mode); + } + } + let uv_mode = best_uv.1; + let uv_tx = tx_size_for(clog2); + let uv_type = intra_uv_tx_type(uv_mode, uv_tx); + let mut code_uv = |i: usize| { + predict_intra(&ced[i], &ce[i], uv_mode, &mut pred); + let t = self.code_tx(TransformBlock { plane: 1 + i, x: c * 2, y: r * 2, log2: clog2, tx_type: uv_type, intra: true }, &pred[..cn * cn]); + self.mark_decoded(1 + i, r, c, n4); + t + }; + let u = code_uv(0); + let v = code_uv(1); + let skip = !ty.nonzero && !u.nonzero && !v.nonzero; + let dist = ty.dist + u.dist + v.dist; + let bits = if skip { 1.0 } else { ty.bits + u.bits + v.bits + 1.0 } + MODE_BITS_INTRA + 2.0; + let leaf = Leaf { bsize, is_inter: false, y_mode, uv_mode, tx_type_y, mv: [0, 0], skip, coefs: [ty.levels, u.levels, v.levels] }; + (leaf, dist + self.lambda * bits) + } + + fn inter_leaf(&mut self, r: usize, c: usize, bsize: usize, parent_mv: Option<[i32; 2]>) -> (Leaf, f64) { + let n4 = NUM_4X4_BLOCKS_WIDE[bsize] as usize; + let n = n4 * 4; + let log2 = n.trailing_zeros(); + // Inter blocks are only decided with a reference frame; without one, code intra. + let Some(refr) = self.refr else { return self.intra_leaf(r, c, bsize) }; + let (mv, mv_bits) = self.motion_search(r, c, n, parent_mv); + let mut pred = vec![0u16; n * n]; + let mut tx = Vec::with_capacity(3); + let mut dist = 0.0; + for p in 0..3 { + let s = (p > 0) as usize; + let (vw, vh) = self.g.visible(p); + let pn = n >> s; + let (x, y) = ((c * 4) >> s, (r * 4) >> s); + predict_inter( + &refr.planes[p], + &InterBlock { last_x: vw as i32 - 1, last_y: vh as i32 - 1, x, y, width: pn, height: pn, mv, subsampling: s, bit_depth: self.bd }, + &mut pred[..pn * pn], + ); + if bsize == BLOCK_64X64 { + // coded as a skip block: the prediction is the reconstruction + dist += self.sse(p, x, y, pn, &pred[..pn * pn]); + self.put(p, x, y, pn, &pred[..pn * pn]); + } else { + let t = self.code_tx(TransformBlock { plane: p, x, y, log2: log2 - s as u32, tx_type: DCT_DCT, intra: false }, &pred[..pn * pn]); + dist += t.dist; + tx.push(t); + } + self.mark_decoded(p, r, c, n4); + } + let mode_bits = if mv == [0, 0] { 1.5 } else { 2.0 + mv_bits }; + if bsize == BLOCK_64X64 { + let leaf = Leaf { bsize, is_inter: true, y_mode: NEWMV, uv_mode: DC_PRED, tx_type_y: DCT_DCT, mv, skip: true, coefs: Default::default() }; + return (leaf, dist + self.lambda * (mode_bits + 1.0)); + } + let skip = tx.iter().all(|t| !t.nonzero); + let bits = mode_bits + if skip { 1.0 } else { 1.0 + tx.iter().map(|t| t.bits).sum::() }; + let [y, u, v]: [TxResult; 3] = match tx.try_into() { + Ok(planes) => planes, + // One transform block per plane was coded above. + Err(_) => { + return ( + Leaf { bsize, is_inter: true, y_mode: NEWMV, uv_mode: DC_PRED, tx_type_y: DCT_DCT, mv, skip: true, coefs: Default::default() }, + f64::INFINITY, + ); + } + }; + let leaf = + Leaf { bsize, is_inter: true, y_mode: NEWMV, uv_mode: DC_PRED, tx_type_y: DCT_DCT, mv, skip, coefs: [y.levels, u.levels, v.levels] }; + (leaf, dist + self.lambda * bits) + } + + /// Full-sample motion search: candidate vectors then a shrinking diamond search. + /// Returns the vector and an estimate of its coding cost in bits. + fn motion_search(&self, r: usize, c: usize, n: usize, parent_mv: Option<[i32; 2]>) -> ([i32; 2], f64) { + let g = self.g; + let Some(refr) = self.refr else { return ([0, 0], 0.0) }; + let mi = |rr: usize, cc: usize| self.mvs[rr * g.mi_cols + cc]; + let mut pred_mv = [0, 0]; + let mut cands: Vec<[i32; 2]> = vec![[0, 0]]; + if c > 0 { + pred_mv = mi(r, c - 1); + cands.push(pred_mv); + } + if r > 0 { + cands.push(mi(r - 1, c)); + let cr = c + n / 4; + if cr < g.mi_cols { + cands.push(mi(r - 1, cr)); + } + } + if let Some(p) = parent_mv { + cands.push(p); + } + cands.push(refr.mvs[r * g.mi_cols + c]); + let (x0, y0) = ((c * 4) as i32, (r * 4) as i32); + let (w, h) = (g.width as i32, g.height as i32); + // keep the referenced block within 64 samples of the picture + let lim_x = (-x0 - n as i32 - 64, w - x0 + 64); + let lim_y = (-y0 - n as i32 - 64, h - y0 + 64); + let valid = |m: [i32; 2]| { + let (dy, dx) = (m[0] >> 3, m[1] >> 3); + dx >= lim_x.0 && dx <= lim_x.1 && dy >= lim_y.0 && dy <= lim_y.1 + }; + let lg = log2_table(); + let mv_bits = |m: [i32; 2]| -> f64 { + let comp = |d: i32| if d == 0 { 0.5 } else { 2.0 + 2.0 * log2_of(lg, (d.abs() / 8) as usize + 1) }; + comp(m[0] - pred_mv[0]) + comp(m[1] - pred_mv[1]) + }; + let cost = |m: [i32; 2]| -> f64 { + self.sad_ref(&refr.planes[0], x0 + (m[1] >> 3), y0 + (m[0] >> 3), c * 4, r * 4, n) as f64 + self.lambda_sad * mv_bits(m) + }; + let mut best = ([0, 0], f64::MAX); + for &m in &cands { + let m = [m[0] & !7, m[1] & !7]; + if !valid(m) { + continue; + } + let cst = cost(m); + if cst < best.1 { + best = (m, cst); + } + } + let mut step = 8 * if n >= 32 { + 16 + } else if n == 16 { + 8 + } else { + 4 + }; + while step >= 8 { + let mut moved = true; + let mut iters = 0; + while moved && iters < 16 { + moved = false; + iters += 1; + let center = best.0; + for d in [[-step, 0], [step, 0], [0, -step], [0, step]] { + let m = [center[0] + d[0], center[1] + d[1]]; + if !valid(m) { + continue; + } + let cst = cost(m); + if cst < best.1 { + best = (m, cst); + moved = true; + } + } + } + step >>= 1; + } + // half- then quarter-sample refinement (plus the exact neighbour candidates) + let mut buf = vec![0u16; n * n]; + let sub_cost = |m: [i32; 2], buf: &mut Vec| -> f64 { + self.search_block(m, c * 4, r * 4, n, buf); + self.sad(0, c * 4, r * 4, n, buf) as f64 + self.lambda_sad * mv_bits(m) + }; + for m in cands { + if m[0] & 7 != 0 || m[1] & 7 != 0 { + let m = [m[0] & !1, m[1] & !1]; + if valid(m) { + let cst = sub_cost(m, &mut buf); + if cst < best.1 { + best = (m, cst); + } + } + } + } + for step in [4, 2] { + let center = best.0; + for d in [[-1, -1], [-1, 0], [-1, 1], [0, -1], [0, 1], [1, -1], [1, 0], [1, 1]] { + let m = [center[0] + d[0] * step, center[1] + d[1] * step]; + if !valid(m) { + continue; + } + let cst = sub_cost(m, &mut buf); + if cst < best.1 { + best = (m, cst); + } + } + } + (best.0, mv_bits(best.0)) + } + + /// Approximate luma prediction for motion search: exact at whole and half-sample + /// positions (precomputed planes), averaged neighbours at quarter-sample positions. + fn search_block(&self, mv: [i32; 2], x: usize, y: usize, n: usize, out: &mut [u16]) { + let Some(refr) = self.refr else { return }; + let (w, h) = (self.g.width as i32, self.g.height as i32); + // per axis: up to two (integer offset, half) taps + let axis = |v: i32| -> ([(i32, bool); 2], usize) { + let p = 2 * v; // 1/16 units + let (i, f) = (p >> 4, p & 15); + match f { + 0 => ([(i, false), (i, false)], 1), + 8 => ([(i, true), (i, true)], 1), + 4 => ([(i, false), (i, true)], 2), + _ => ([(i, true), (i + 1, false)], 2), + } + }; + let (ax, nx) = axis(mv[1]); + let (ay, ny) = axis(mv[0]); + let taps = nx.max(ny); + let fetch = |k: usize, xx: i32, yy: i32| -> u16 { + let (ox, hx) = ax[k.min(nx - 1)]; + let (oy, hy) = ay[k.min(ny - 1)]; + let sx = (xx + ox).clamp(0, w - 1) as usize; + let sy = (yy + oy).clamp(0, h - 1) as usize; + match (hx, hy) { + (false, false) => refr.planes[0].at(sx, sy), + (true, false) => self.half[0][sy * w as usize + sx], + (false, true) => self.half[1][sy * w as usize + sx], + (true, true) => self.half[2][sy * w as usize + sx], + } + }; + let vw = n.min(self.g.width.saturating_sub(x)); + let vh = n.min(self.g.height.saturating_sub(y)); + // fast path: every tap inside the picture + let (x0, y0) = (x as i32, y as i32); + let inside = (0..taps).all(|k| { + let (ox, _) = ax[k.min(nx - 1)]; + let (oy, _) = ay[k.min(ny - 1)]; + x0 + ox >= 0 && y0 + oy >= 0 && x0 + ox + vw as i32 <= w && y0 + oy + vh as i32 <= h + }); + if inside { + let src = |k: usize| -> (&[u16], usize, usize) { + let (ox, hx) = ax[k.min(nx - 1)]; + let (oy, hy) = ay[k.min(ny - 1)]; + let (sx, sy) = ((x0 + ox) as usize, (y0 + oy) as usize); + match (hx, hy) { + (false, false) => (&refr.planes[0].data, refr.planes[0].stride, sy * refr.planes[0].stride + sx), + (true, false) => (&self.half[0], w as usize, sy * w as usize + sx), + (false, true) => (&self.half[1], w as usize, sy * w as usize + sx), + (true, true) => (&self.half[2], w as usize, sy * w as usize + sx), + } + }; + let (a, sa, oa) = src(0); + if taps == 1 { + for i in 0..vh { + out[i * n..i * n + vw].copy_from_slice(&a[oa + i * sa..oa + i * sa + vw]); + } + } else { + let (b, sb, ob) = src(1); + for i in 0..vh { + let (ra, rb) = (&a[oa + i * sa..oa + i * sa + vw], &b[ob + i * sb..ob + i * sb + vw]); + for (o, (&p, &q)) in out[i * n..i * n + vw].iter_mut().zip(ra.iter().zip(rb)) { + *o = ((p as u32 + q as u32 + 1) >> 1) as u16; + } + } + } + return; + } + for i in 0..vh { + for j in 0..vw { + let (xx, yy) = (x0 + j as i32, y0 + i as i32); + out[i * n + j] = if taps == 1 { fetch(0, xx, yy) } else { ((fetch(0, xx, yy) as u32 + fetch(1, xx, yy) as u32 + 1) >> 1) as u16 }; + } + } + } + + /// SAD of the source block at (x, y) (luma) against the reference block at (rx, ry). + fn sad_ref(&self, refp: &Plane, rx: i32, ry: i32, x: usize, y: usize, n: usize) -> u64 { + let (vw, vh) = (self.g.width, self.g.height); + let w = n.min(vw.saturating_sub(x)); + let h = n.min(vh.saturating_sub(y)); + let src = &self.src[0]; + let (lx, ly) = (vw as i32 - 1, vh as i32 - 1); + let mut sad = 0u64; + let inside = rx >= 0 && ry >= 0 && rx + w as i32 - 1 <= lx && ry + h as i32 - 1 <= ly; + for i in 0..h { + let s = &src.row(y + i)[x..x + w]; + let rr = refp.row((ry + i as i32).clamp(0, ly) as usize); + if inside { + let rrow = &rr[rx as usize..rx as usize + w]; + sad += s.iter().zip(rrow).map(|(&a, &b)| (a as i32 - b as i32).unsigned_abs()).sum::() as u64; + } else { + for j in 0..w { + let b = rr[(rx + j as i32).clamp(0, lx) as usize]; + sad += (s[j] as i32 - b as i32).unsigned_abs() as u64; + } + } + } + sad + } + + /// Sum of absolute Hadamard-transformed differences (4x4 or 8x8 sub-blocks) of a prediction + /// against the source, scaled to about the size of the SAD. + fn satd(&self, p: usize, x: usize, y: usize, n: usize, pred: &[u16]) -> u64 { + let src = &self.src[p]; + let k = n.min(8); + let mut total = 0u64; + let mut d = [0i32; 64]; + for by in (0..n).step_by(k) { + for bx in (0..n).step_by(k) { + for i in 0..k { + let row = &src.row(y + by + i)[x + bx..x + bx + k]; + for j in 0..k { + d[i * k + j] = row[j] as i32 - pred[(by + i) * n + bx + j] as i32; + } + } + for i in 0..k { + hadamard(&mut d[i * k..i * k + k]); + } + let mut col = [0i32; 8]; + let mut s = 0u64; + for j in 0..k { + for i in 0..k { + col[i] = d[i * k + j]; + } + hadamard(&mut col[..k]); + s += col[..k].iter().map(|v| v.unsigned_abs() as u64).sum::(); + } + total += s / (k as u64 / 2); + } + } + total + } + + /// SAD of a prediction against the source over the visible part of the block. + fn sad(&self, p: usize, x: usize, y: usize, n: usize, pred: &[u16]) -> u64 { + let (vw, vh) = self.g.visible(p); + let w = n.min(vw.saturating_sub(x)); + let h = n.min(vh.saturating_sub(y)); + let src = &self.src[p]; + let mut s = 0u64; + for i in 0..h { + let row = &src.row(y + i)[x..x + w]; + s += row.iter().zip(&pred[i * n..i * n + w]).map(|(&a, &b)| (a as i32 - b as i32).unsigned_abs()).sum::() as u64; + } + s + } + + fn sse(&self, p: usize, x: usize, y: usize, n: usize, blk: &[u16]) -> f64 { + let (vw, vh) = self.g.visible(p); + let w = n.min(vw.saturating_sub(x)); + let h = n.min(vh.saturating_sub(y)); + let src = &self.src[p]; + let mut s = 0u64; + for i in 0..h { + let row = &src.row(y + i)[x..x + w]; + s += row + .iter() + .zip(&blk[i * n..i * n + w]) + .map(|(&a, &b)| { + let d = a as i64 - b as i64; + (d * d) as u64 + }) + .sum::(); + } + s as f64 + } + + fn put(&mut self, p: usize, x: usize, y: usize, n: usize, blk: &[u16]) { + let pl = &mut self.rec[p]; + for i in 0..n { + let o = (y + i) * pl.stride + x; + pl.data[o..o + n].copy_from_slice(&blk[i * n..i * n + n]); + } + } + + /// Transform, quantise and reconstruct one square transform block of `plane` at (x, y) + /// over the prediction `pred`; the reconstruction is written to `rec`. + fn code_tx(&mut self, block: TransformBlock, pred: &[u16]) -> TxResult { + let TransformBlock { plane: p, x, y, log2, .. } = block; + let mut sc = self.scratch.take().unwrap_or_default(); + let (res, has_recon) = self.try_tx(&mut sc, block, pred); + let n = 1usize << log2; + self.put(p, x, y, n, if has_recon { &sc.recon[..n * n] } else { pred }); + let levels = sc.levels[..n * n].to_vec(); + self.scratch = Some(sc); + TxResult { levels, nonzero: res.nonzero, dist: res.dist, bits: res.bits } + } + + /// Transform and quantise one block without writing it. The levels are left in `sc.levels`; + /// the flag tells whether `sc.recon` holds the reconstruction (false: it is the prediction). + fn try_tx(&self, sc: &mut TxScratch, block: TransformBlock, pred: &[u16]) -> (TxEval, bool) { + let TransformBlock { plane: p, x, y, log2, tx_type, intra } = block; + let n = 1usize << log2; + let nn = n * n; + let tx_sz = tx_size_for(log2); + let bd = self.bd; + let src = &self.src[p]; + let TxScratch { res, coef, levels, deq, resid, recon } = sc; + let (res, coef, levels) = (&mut res[..nn], &mut coef[..nn], &mut levels[..nn]); + for i in 0..n { + let row = &src.row(y + i)[x..x + n]; + for j in 0..n { + res[i * n + j] = row[j] as i32 - pred[i * n + j] as i32; + } + } + forward_2d(res, coef, n, tx_type); + // dead-zone quantiser: rounding offset below one half + let rnd = if intra { INTRA_RND } else { INTER_RND }; + let [dc_thr, ac_thr] = [self.dead_zone[0][intra as usize], self.dead_zone[1][intra as usize]]; + levels.fill(0); + let mut nonzero = false; + for (k, (&cf, l)) in coef.iter().zip(levels.iter_mut()).enumerate() { + let (q, thr) = if k == 0 { (self.dc_q, dc_thr) } else { (self.ac_q, ac_thr) }; + // dead zone: below the exact threshold the level is zero, no division needed + if cf.abs() < thr { + continue; + } + // the operand is >= 0, so truncation equals floor + let a = (cf.abs() / q + rnd) as i32; + if a != 0 { + *l = if cf < 0.0 { -a.min(1 << 16) } else { a.min(1 << 16) }; + nonzero = true; + } + } + let zero_dist = self.sse(p, x, y, n, pred); + if !nonzero { + return (TxEval { nonzero, dist: zero_dist, bits: 1.0 }, false); + } + let recon = &mut recon[..nn]; + self.reconstruct(levels, ReconBuffers { deq: &mut deq[..nn], resid: &mut resid[..nn], recon }, tx_sz, tx_type, pred, bd); + let dist = self.sse(p, x, y, n, recon); + let bits = estimate_bits(levels, tx_sz); + if dist + self.lambda * bits >= zero_dist + self.lambda { + levels.fill(0); + return (TxEval { nonzero: false, dist: zero_dist, bits: 1.0 }, false); + } + (TxEval { nonzero: true, dist, bits }, true) + } + + /// Dequantisation (7.12.3) and the inverse transform, added to the prediction; the result is + /// written to `recon`. `deq` and `resid` are scratch buffers of `n * n` entries. + fn reconstruct(&self, levels: &[i32], bufs: ReconBuffers, tx_sz: usize, tx_type: usize, pred: &[u16], bd: u32) { + let ReconBuffers { deq, resid, recon } = bufs; + let denom: i64 = if tx_sz == TX_32X32 { 2 } else { 1 }; + let lim = 1i64 << (7 + bd); + deq.fill(0); + for (k, (&l, d)) in levels.iter().zip(deq.iter_mut()).enumerate() { + if l == 0 { + continue; + } + let q = if k == 0 { self.dc_qi } else { self.ac_qi }; + let dq = l as i64 * q; + let mag = (dq.abs() & 0xFF_FFFF) / denom; + *d = (if dq < 0 { -mag } else { mag }).clamp(-lim, lim - 1) as i32; + } + inverse_2d(deq, resid, tx_sz, tx_type, bd); + let max = (1i32 << bd) - 1; + for ((o, &p), &r) in recon.iter_mut().zip(pred).zip(resid.iter()) { + *o = (p as i32 + r).clamp(0, max) as u16; + } + } +} + +/// Smallest `x >= 0` with `(x / q + rnd) as i32 >= 1` in f32 arithmetic. The expression is +/// monotone in `x`, so a binary search over the bit patterns finds the exact boundary. +fn zero_threshold(q: f32, rnd: f32) -> f32 { + let (mut lo, mut hi) = (0u32, f32::MAX.to_bits()); + while lo < hi { + let mid = lo + (hi - lo) / 2; + if (f32::from_bits(mid) / q + rnd) as i32 >= 1 { + hi = mid; + } else { + lo = mid + 1; + } + } + f32::from_bits(lo) +} + +const LOG2_TABLE_LEN: usize = 1025; + +/// `log2(i)` for `i < LOG2_TABLE_LEN`, computed with the same `f64::log2` call as the direct form. +fn log2_table() -> &'static [f64; LOG2_TABLE_LEN] { + static TABLE: std::sync::OnceLock<[f64; LOG2_TABLE_LEN]> = std::sync::OnceLock::new(); + TABLE.get_or_init(|| std::array::from_fn(|i| (i as f64).log2())) +} + +fn log2_of(table: &[f64; LOG2_TABLE_LEN], v: usize) -> f64 { + if v < LOG2_TABLE_LEN { table[v] } else { (v as f64).log2() } +} + +/// Unnormalised Walsh-Hadamard transform of 4 or 8 values, in place. +fn hadamard(v: &mut [i32]) { + let n = v.len(); + let mut h = 1; + while h < n { + for i in (0..n).step_by(2 * h) { + for j in i..i + h { + let (a, b) = (v[j], v[j + h]); + v[j] = a + b; + v[j + h] = a - b; + } + } + h *= 2; + } +} + +/// Rough coefficient coding cost in bits. +fn estimate_bits(levels: &[i32], tx_sz: usize) -> f64 { + let scan = scan_for(tx_sz); + let mut eob = 0; + for (i, &pos) in scan.iter().enumerate() { + if levels[pos as usize] != 0 { + eob = i + 1; + } + } + if eob == 0 { + return 1.0; + } + let lg = log2_table(); + let mut bits = 3.0 + log2_of(lg, eob); + for &pos in &scan[..eob] { + let a = levels[pos as usize].unsigned_abs(); + bits += match a { + 0 => 0.7, + 1 => 2.5, + _ => 3.5 + 1.6 * log2_of(lg, a as usize), + }; + } + bits +} + +pub(crate) fn scan_for(tx_sz: usize) -> &'static [u16] { + match tx_sz { + TX_4X4 => &DEFAULT_SCAN_4X4, + TX_8X8 => &DEFAULT_SCAN_8X8, + TX_16X16 => &DEFAULT_SCAN_16X16, + _ => &DEFAULT_SCAN_32X32, + } +} + +#[cfg(test)] +mod tests { + use super::*; + use crate::tables::{AC_QLOOKUP, DC_QLOOKUP}; + + /// The quantiser as it was before the dead-zone threshold and the truncation. + fn quant_ref(cf: f32, q: f32, rnd: f32) -> i32 { + (cf.abs() / q + rnd).floor() as i32 + } + + fn quant_new(cf: f32, q: f32, rnd: f32, thr: f32) -> i32 { + if cf.abs() < thr { + return 0; + } + (cf.abs() / q + rnd) as i32 + } + + #[test] + fn dead_zone_threshold_is_exact() { + let mut state = 0x1234_5678_9abc_def0u64; + let mut next = move || { + state ^= state << 13; + state ^= state >> 7; + state ^= state << 17; + state + }; + for table in AC_QLOOKUP.iter().chain(DC_QLOOKUP.iter()) { + for &qi in table.iter() { + let q = qi as f32; + for rnd in [INTER_RND, INTRA_RND] { + let thr = zero_threshold(q, rnd); + // the boundary itself and its neighbours + let b = thr.to_bits(); + for bits in b.saturating_sub(3)..=b + 3 { + let cf = f32::from_bits(bits); + assert_eq!(quant_ref(cf, q, rnd), quant_new(cf, q, rnd, thr), "q {q} rnd {rnd} cf {cf}"); + assert_eq!(quant_ref(-cf, q, rnd), quant_new(-cf, q, rnd, thr)); + } + for _ in 0..200 { + let cf = f32::from_bits((next() >> 32) as u32 & 0x7fff_ffff); + if cf.is_nan() { + continue; + } + assert_eq!(quant_ref(cf, q, rnd), quant_new(cf, q, rnd, thr)); + let cf = (next() % 100_000) as f32 / 100.0; + assert_eq!(quant_ref(cf, q, rnd), quant_new(cf, q, rnd, thr)); + } + } + } + } + } + + #[test] + fn log2_table_matches_direct_log2() { + let t = log2_table(); + for v in 1..3000usize { + assert_eq!(log2_of(t, v).to_bits(), (v as f64).log2().to_bits(), "{v}"); + } + } +} diff --git a/crates/av1enc/src/lib.rs b/crates/av1enc/src/lib.rs new file mode 100644 index 0000000000..b31928f2a5 --- /dev/null +++ b/crates/av1enc/src/lib.rs @@ -0,0 +1,682 @@ +//! Clean-room pure-Rust AV1 encoder (Main profile, 4:2:0, 8 and 10 bit), implemented from the +//! AV1 Bitstream & Decoding Process Specification v1.0.0 with Errata 1. +//! +//! Key frames and single-reference inter frames (LAST_FRAME = the previous frame), 64x64 +//! superblocks split down to 8x8 by rate-distortion decisions, intra modes DC / V / H / Paeth / +//! smooth, full-sample motion search with the spec's motion vector prediction, DCT / ADST +//! transforms up to 32x32, one tile per frame, in-loop filters off. See the crate README. +//! +//! Public API for AV1 video encoding pipelines. + +#![deny(clippy::unwrap_used, clippy::expect_used, clippy::panic, clippy::unimplemented, clippy::todo, clippy::unreachable)] +#![allow(clippy::needless_range_loop)] + +mod bitw; +mod cdf; +mod decide; +mod loopfilter; +mod pred; +mod symbol; +mod tables; +mod transform; +mod writer; + +use bitw::{BitWriter, obu}; +use decide::{Decider, FrameGeom, RefFrame}; +use loopfilter::{LfInfo, loop_filter}; +use pred::Plane; +use tables::AC_QLOOKUP; +use writer::TileWriter; + +/// Rate control. +#[derive(Clone, Copy, Debug, PartialEq, Eq)] +pub enum RateControl { + /// Constant quantizer index (`base_q_idx`, 1–255; lower is better). + ConstantQ(u8), + /// Average bitrate target in kbit/s (frame-level quantizer adaptation). + Bitrate { kbps: u32 }, +} + +/// Encoder settings. Main profile (seq_profile 0), 4:2:0. +#[derive(Clone, Debug, PartialEq)] +pub struct EncoderConfig { + pub width: u32, + pub height: u32, + pub fps_num: u32, + pub fps_den: u32, + /// 8 or 10. + pub bit_depth: u8, + /// `seq_level_idx` (e.g. 8 = level 4.0); `None` picks the lowest level that fits. + pub level_idx: Option, + pub rate: RateControl, + /// Frames between key frames (1 = all key frames). + pub keyint: u32, + /// BT.709 limited range is signalled unless this is set. + pub full_range: bool, +} + +impl EncoderConfig { + pub fn new(width: u32, height: u32, fps_num: u32, fps_den: u32) -> Self { + EncoderConfig { + width, + height, + fps_num, + fps_den, + bit_depth: 8, + level_idx: None, + rate: RateControl::ConstantQ(100), + keyint: 60, + full_range: false, + } + } +} + +/// One input picture, 4:2:0, samples at the configured bit depth. +#[derive(Clone, Copy, Debug)] +pub struct Frame<'a> { + pub y: &'a [u16], + pub u: &'a [u16], + pub v: &'a [u16], + pub y_stride: usize, + pub uv_stride: usize, +} + +/// One temporal unit as stored in MP4 / WebM samples: OBUs with `obu_has_size_field` = 1 and +/// no temporal delimiter (key frames start with the sequence header OBU). +#[derive(Clone, Debug, PartialEq, Eq)] +pub struct Packet { + pub data: Vec, + pub keyframe: bool, +} + +#[derive(Debug, PartialEq, Eq)] +pub enum Error { + InvalidConfig(String), + InvalidFrame(String), +} + +impl std::fmt::Display for Error { + fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { + match self { + Error::InvalidConfig(s) => write!(f, "invalid AV1 encoder config: {s}"), + Error::InvalidFrame(s) => write!(f, "invalid AV1 input frame: {s}"), + } + } +} + +impl std::error::Error for Error {} + +const OBU_SEQUENCE_HEADER: u8 = 1; +const OBU_FRAME: u8 = 6; + +/// Annex A level limits: (seq_level_idx, MaxPicSize, MaxHSize, MaxVSize, MaxDisplayRate, +/// MaxBitrate (Main tier, Mbit/s)). +const LEVELS: [(u8, u64, u32, u32, u64, u64); 14] = [ + (0, 147_456, 2048, 1152, 4_423_680, 1_500_000), + (1, 278_784, 2816, 1584, 8_363_520, 3_000_000), + (4, 665_856, 4352, 2448, 19_975_680, 6_000_000), + (5, 1_065_024, 5504, 3096, 31_950_720, 10_000_000), + (8, 2_359_296, 6144, 3456, 70_778_880, 12_000_000), + (9, 2_359_296, 6144, 3456, 141_557_760, 20_000_000), + (12, 8_912_896, 8192, 4352, 267_386_880, 30_000_000), + (13, 8_912_896, 8192, 4352, 534_773_760, 40_000_000), + (14, 8_912_896, 8192, 4352, 1_069_547_520, 60_000_000), + (15, 8_912_896, 8192, 4352, 1_069_547_520, 60_000_000), + (16, 35_651_584, 16384, 8704, 1_069_547_520, 60_000_000), + (17, 35_651_584, 16384, 8704, 2_139_095_040, 100_000_000), + (18, 35_651_584, 16384, 8704, 4_278_190_080, 160_000_000), + (19, 35_651_584, 16384, 8704, 4_278_190_080, 160_000_000), +]; + +/// The lowest level whose limits fit the stream (31 = no level limits when none does). +fn pick_level(cfg: &EncoderConfig) -> u8 { + let pic = cfg.width as u64 * cfg.height as u64; + let rate = pic * cfg.fps_num as u64 / cfg.fps_den.max(1) as u64; + let bitrate = match cfg.rate { + RateControl::Bitrate { kbps } => kbps as u64 * 1000, + RateControl::ConstantQ(_) => 0, + }; + for &(idx, max_pic, max_h, max_v, max_rate, max_br) in LEVELS.iter() { + if pic <= max_pic && cfg.width <= max_h && cfg.height <= max_v && rate <= max_rate && bitrate <= max_br { + return idx; + } + } + 31 +} + +/// Frame-level rate control state for `RateControl::Bitrate`. +struct RateState { + /// Bits per frame at the target rate. + frame_bits: f64, + /// Quantizer index for inter frames (continuous). + q: f64, + /// Key frame quantizer index minus the inter frame one. + key_off: f64, + /// Quantizer index steps per halving of the frame size, measured from consecutive inter + /// frames (the size/quantizer curve is much steeper than the default on noisy content). + slope: f64, + /// Quantizer index and size (in bits) of the previous inter frame. + prev_inter: Option<(f64, f64)>, + /// Accumulated (spent - budget) bits. + debt: f64, +} + +/// Quantizer index steps per halving of the frame size before any measurement. +const RC_SLOPE: f64 = 28.0; +/// Fraction of the quantizer step the size error asks for that is applied per frame. +const RC_GAIN: f64 = 0.8; +/// Frames over which an accumulated deviation from the budget is paid back. +const RC_PAYBACK: f64 = 16.0; + +impl RateState { + /// The nominal bit budget and the starting quantizer index of a key or inter frame. A key + /// frame gets a larger share, and the inter frames of the group get the rest, so that a whole + /// group of `keyint` frames adds up to `keyint * frame_bits`. + fn frame_budget(&self, keyint: u32, key: bool) -> (f64, f64) { + let keyint = keyint.max(1) as f64; + let boost = (1.0 + (keyint.clamp(1.0, 60.0) - 1.0).sqrt() * 0.75).min(0.5 * keyint + 0.5); + if key { + (self.frame_bits * boost, (self.q + self.key_off).clamp(1.0, 255.0)) + } else if keyint > 1.0 { + (self.frame_bits * (keyint - boost) / (keyint - 1.0), self.q) + } else { + (self.frame_bits, self.q) + } + } + + /// Adapts the quantizers to the size of the frame just coded at `q_used` against `target`. + fn update(&mut self, key: bool, first: bool, q_used: f64, bits: f64, target: f64) { + let err = (bits / target).log2().clamp(-1.5, 1.5); + if first && key { + // four passes ran: the key frame quantizer is known, the inter one follows from the offset + self.q = (q_used - self.key_off).clamp(1.0, 255.0); + } else if key { + self.key_off = q_used - self.q + RC_GAIN * self.slope * err; + } else { + if let Some((pq, pbits)) = self.prev_inter { + let (dq, dr) = (q_used - pq, (pbits / bits).log2()); + if dq.abs() >= 3.0 && dr.abs() >= 0.15 && dq * dr > 0.0 { + self.slope = (0.6 * self.slope + 0.4 * (dq / dr)).clamp(3.0, 45.0); + } + } + self.prev_inter = Some((q_used, bits)); + self.q = (q_used + RC_GAIN * self.slope * err).clamp(1.0, 255.0); + } + } +} + +/// Most passes on the first frame, and how far from the target (octaves, 3 %) its size may be +/// before it is accepted. +const RC_FIRST_PASSES: usize = 4; +const RC_FIRST_TOLERANCE: f64 = 0.04; +/// Quantizer index steps per halving of the size, for the first correction of the first frame +/// (the starting quantizer comes from the bits per sample; the first pass shows how far off it +/// is, and from there on the passes are placed by `next_probe_q`). +const RC_FIRST_SLOPE: f64 = 30.0; +/// The same for a key frame after the first: coded again when its size is more than 0.25 octaves +/// (19 %) off the target. +const RC_KEY_PASSES: usize = 3; +const RC_KEY_TOLERANCE: f64 = 0.25; +/// Octaves of frame size per octave of quantizer step assumed for the first correction of a frame. +const RC_PROBE_SLOPE: f64 = 1.0; +/// Largest change of the quantizer step (octaves) between two passes on one frame. +const RC_PROBE_MAX_STEP: f64 = 2.0; + +/// The quantizer index whose step size is closest to `2^x`. +fn q_for_step(bit_depth: u8, x: f64) -> u8 { + let table = &AC_QLOOKUP[((bit_depth - 8) / 2) as usize]; + let mut best = 1; + for q in 1..=255usize { + if ((table[q] as f64).log2() - x).abs() < ((table[best] as f64).log2() - x).abs() { + best = q; + } + } + best as u8 +} + +/// The next quantizer to try for a frame, from the `(quantizer, log2(size / target))` +/// pairs coded so far. The size follows the quantizer step (not the index, which is linear in the +/// step only below about 60) in a near log-log line: false position between the closest pair of +/// passes that enclose the target, otherwise a step from the latest one with the slope of the +/// last two. `None` when no untried quantizer lies between the enclosing passes. +fn next_probe_q(bit_depth: u8, tried: &[(u8, f64)]) -> Option { + let table = &AC_QLOOKUP[((bit_depth - 8) / 2) as usize]; + let x = |q: u8| (table[q as usize] as f64).log2(); + let over = tried.iter().filter(|t| t.1 > 0.0).max_by_key(|t| t.0); + let under = tried.iter().filter(|t| t.1 <= 0.0).min_by_key(|t| t.0); + if let (Some(&(q_lo, e_lo)), Some(&(q_hi, e_hi))) = (over, under) { + if q_hi - q_lo < 2 { + return None; + } + let next = x(q_lo) + (x(q_hi) - x(q_lo)) * e_lo / (e_lo - e_hi); + return Some(q_for_step(bit_depth, next).clamp(q_lo + 1, q_hi - 1)); + } + let &(q, err) = tried.last()?; + let mut slope = RC_PROBE_SLOPE; + if let [.., (q0, e0), (q1, e1)] = tried + && (x(*q1) - x(*q0)).abs() > 0.05 + && (e0 - e1) / (x(*q1) - x(*q0)) > 0.0 + { + slope = ((e0 - e1) / (x(*q1) - x(*q0))).clamp(0.25, 3.0); + } + Some(q_for_step(bit_depth, x(q) + (err / slope).clamp(-RC_PROBE_MAX_STEP, RC_PROBE_MAX_STEP))) +} + +/// AV1 encoder. Frames come out in input order (no hidden/reordered frames). +pub struct Encoder { + cfg: EncoderConfig, + geom: FrameGeom, + level: u8, + seq_obu: Vec, + frame_num: u64, + refr: Option, + rc: Option, +} + +impl Encoder { + pub fn new(cfg: EncoderConfig) -> Result { + if cfg.width == 0 || cfg.height == 0 { + return Err(Error::InvalidConfig("empty frame".into())); + } + if cfg.width > 4096 || cfg.height > 4096 { + return Err(Error::InvalidConfig(format!("{}x{} exceeds the single-tile limit (4096 samples per side)", cfg.width, cfg.height))); + } + if cfg.bit_depth != 8 && cfg.bit_depth != 10 { + return Err(Error::InvalidConfig(format!("bit depth {} (8 or 10 supported)", cfg.bit_depth))); + } + if cfg.fps_num == 0 || cfg.fps_den == 0 { + return Err(Error::InvalidConfig("frame rate".into())); + } + let geom = FrameGeom::new(cfg.width as usize, cfg.height as usize); + if geom.sb_cols * geom.sb_rows > 2304 { + return Err(Error::InvalidConfig(format!("{}x{} exceeds the single-tile area limit (MAX_TILE_AREA)", cfg.width, cfg.height))); + } + if let RateControl::Bitrate { kbps } = cfg.rate + && kbps == 0 + { + return Err(Error::InvalidConfig("zero bitrate".into())); + } + let level = cfg.level_idx.unwrap_or_else(|| pick_level(&cfg)); + if level > 31 { + return Err(Error::InvalidConfig(format!("seq_level_idx {level}"))); + } + let rc = match cfg.rate { + RateControl::ConstantQ(_) => None, + RateControl::Bitrate { kbps } => { + let frame_bits = kbps as f64 * 1000.0 * cfg.fps_den as f64 / cfg.fps_num as f64; + // initial guess from the bits per sample + let bpp = frame_bits / (cfg.width as f64 * cfg.height as f64); + let q = (110.0 - 40.0 * (bpp / 0.1).log2()).clamp(20.0, 230.0); + Some(RateState { frame_bits, q, key_off: -24.0, slope: RC_SLOPE, prev_inter: None, debt: 0.0 }) + } + }; + let mut enc = Encoder { cfg, geom, level, seq_obu: Vec::new(), frame_num: 0, refr: None, rc }; + let payload = enc.sequence_header_payload(); + obu(OBU_SEQUENCE_HEADER, &payload, &mut enc.seq_obu); + Ok(enc) + } + + pub fn config(&self) -> &EncoderConfig { + &self.cfg + } + + /// The sequence header OBU (with size field). + pub fn sequence_header_obu(&self) -> Vec { + self.seq_obu.clone() + } + + /// AV1CodecConfigurationRecord (`av1C` payload, also the WebM CodecPrivate). + pub fn av1c(&self) -> Vec { + let high = (self.cfg.bit_depth > 8) as u8; + let mut v = vec![ + 0x81, // marker (1), version (1) + self.level & 0x1f, // seq_profile 0, seq_level_idx_0 + (high << 6) | (1 << 3) | (1 << 2), // tier 0, high_bitdepth, twelve_bit 0, mono 0, ss_x 1, ss_y 1, csp 0 + 0, // no initial_presentation_delay + ]; + v.extend_from_slice(&self.seq_obu); + v + } + + /// sequence_header_obu( ) payload (5.5) including trailing bits. + fn sequence_header_payload(&self) -> Vec { + let c = &self.cfg; + let mut w = BitWriter::new(); + w.f(3, 0); // seq_profile + w.f(1, 0); // still_picture + w.f(1, 0); // reduced_still_picture_header + w.f(1, 0); // timing_info_present_flag + w.f(1, 0); // initial_display_delay_present_flag + w.f(5, 0); // operating_points_cnt_minus_1 + w.f(12, 0); // operating_point_idc[ 0 ] + w.f(5, self.level as u32); // seq_level_idx[ 0 ] + if self.level > 7 { + w.f(1, 0); // seq_tier[ 0 ] + } + let wbits = 32 - (c.width - 1).max(1).leading_zeros(); + let hbits = 32 - (c.height - 1).max(1).leading_zeros(); + w.f(4, wbits - 1); + w.f(4, hbits - 1); + w.f(wbits, c.width - 1); + w.f(hbits, c.height - 1); + w.f(1, 0); // frame_id_numbers_present_flag + w.f(1, 0); // use_128x128_superblock + w.f(1, 0); // enable_filter_intra + w.f(1, 0); // enable_intra_edge_filter + w.f(1, 0); // enable_interintra_compound + w.f(1, 0); // enable_masked_compound + w.f(1, 0); // enable_warped_motion + w.f(1, 0); // enable_dual_filter + w.f(1, 0); // enable_order_hint + w.f(1, 0); // seq_choose_screen_content_tools + w.f(1, 0); // seq_force_screen_content_tools + w.f(1, 0); // enable_superres + w.f(1, 0); // enable_cdef + w.f(1, 0); // enable_restoration + // color_config( ) + w.f(1, (c.bit_depth > 8) as u32); // high_bitdepth + w.f(1, 0); // mono_chrome + w.f(1, 1); // color_description_present_flag + w.f(8, 1); // color_primaries: BT.709 + w.f(8, 1); // transfer_characteristics: BT.709 + w.f(8, 1); // matrix_coefficients: BT.709 + w.f(1, c.full_range as u32); // color_range + w.f(2, 0); // chroma_sample_position: unknown + w.f(1, 0); // separate_uv_delta_q + w.f(1, 0); // film_grain_params_present + w.trailing_bits(); + w.into_bytes() + } + + /// uncompressed_header( ) (5.9) for a shown KEY_FRAME or an INTER_FRAME referencing slot 0. + fn frame_header(&self, w: &mut BitWriter, key: bool, qidx: u8, lf_level: u32) { + w.f(1, 0); // show_existing_frame + w.f(2, if key { 0 } else { 1 }); // frame_type + w.f(1, 1); // show_frame + if !key { + w.f(1, 0); // error_resilient_mode + } + w.f(1, 0); // disable_cdf_update + w.f(1, 0); // frame_size_override_flag + if !key { + w.f(3, 0); // primary_ref_frame: LAST_FRAME's saved CDFs + w.f(8, 1); // refresh_frame_flags: slot 0 becomes LAST for the next frame + for _ in 0..7 { + w.f(3, 0); // ref_frame_idx[ i ] + } + w.f(1, 0); // render_and_frame_size_different + w.f(1, 0); // allow_high_precision_mv + w.f(1, 0); // is_filter_switchable + w.f(2, 0); // interpolation_filter = EIGHTTAP + w.f(1, 0); // is_motion_mode_switchable + } else { + w.f(1, 0); // render_and_frame_size_different + } + w.f(1, 0); // disable_frame_end_update_cdf + // tile_info( ): uniform spacing, one tile + let g = &self.geom; + let tile_log2 = |blk: usize, target: usize| { + let mut k = 0; + while (blk << k) < target { + k += 1; + } + k + }; + w.f(1, 1); // uniform_tile_spacing_flag + if tile_log2(1, g.sb_cols.min(64)) > 0 { + w.f(1, 0); // increment_tile_cols_log2 + } + if tile_log2(1, g.sb_rows.min(64)) > 0 { + w.f(1, 0); // increment_tile_rows_log2 + } + // quantization_params( ) + w.f(8, qidx as u32); + w.f(1, 0); // DeltaQYDc + w.f(1, 0); // DeltaQUDc + w.f(1, 0); // DeltaQUAc + w.f(1, 0); // using_qmatrix + w.f(1, 0); // segmentation_enabled + w.f(1, 0); // delta_q_present (base_q_idx > 0) + // loop_filter_params( ): the same level for every plane and direction + w.f(6, lf_level); + w.f(6, lf_level); + if lf_level != 0 { + w.f(6, lf_level); + w.f(6, lf_level); + } + w.f(3, 0); // loop_filter_sharpness + w.f(1, 0); // loop_filter_delta_enabled + w.f(1, 0); // tx_mode_select (TX_MODE_LARGEST) + if !key { + w.f(1, 0); // reference_select + } + w.f(1, 1); // reduced_tx_set + if !key { + for _ in 0..7 { + w.f(1, 0); // is_global + } + } + } + + fn check_frame(&self, frame: &Frame) -> Result<(), Error> { + for p in 0..3 { + let (vw, vh) = self.geom.visible(p); + let (data, stride) = match p { + 0 => (frame.y, frame.y_stride), + 1 => (frame.u, frame.uv_stride), + _ => (frame.v, frame.uv_stride), + }; + let need = stride.checked_mul(vh.saturating_sub(1)).and_then(|v| v.checked_add(vw)); + if stride < vw || need.is_none_or(|n| data.len() < n) { + return Err(Error::InvalidFrame(format!("plane {p} is too small for {vw}x{vh}"))); + } + } + Ok(()) + } + + /// Copies a picture into superblock-padded planes (edge replication). + fn load_source(&self, frame: &Frame) -> [Plane; 3] { + let maxv = (1u16 << self.cfg.bit_depth) - 1; + std::array::from_fn(|p| { + let (vw, vh) = self.geom.visible(p); + let (pw, ph) = self.geom.padded(p); + let (data, stride) = match p { + 0 => (frame.y, frame.y_stride), + 1 => (frame.u, frame.uv_stride), + _ => (frame.v, frame.uv_stride), + }; + let mut pl = Plane::new(pw, ph); + for y in 0..ph { + let sy = y.min(vh.saturating_sub(1)); + let src = &data[sy * stride..sy * stride + vw]; + let row = &mut pl.data[y * pw..y * pw + pw]; + for (x, d) in row.iter_mut().enumerate() { + *d = src[x.min(vw.saturating_sub(1))].min(maxv); + } + } + pl + }) + } + + /// Codes one frame at `qidx`; returns the frame OBU and the reconstruction. + fn encode_frame(&self, src: &[Plane; 3], key: bool, qidx: u8) -> (Vec, RefFrame) { + let g = &self.geom; + let refr = if key { None } else { self.refr.as_ref() }; + let mut dec = Decider::new(g, self.cfg.bit_depth as u32, qidx, key, src, refr); + let mut tw = TileWriter::new(g, key, qidx, refr.map(|r| &*r.cdfs)); + for sbr in 0..g.sb_rows { + tw.start_sb_row(); + for sbc in 0..g.sb_cols { + let node = dec.superblock(sbr * 16, sbc * 16); + tw.superblock(sbr * 16, sbc * 16, &node); + } + } + let lf_level = self.pick_filter_level(&dec.rec, &tw.lf_info(), src, qidx); + loop_filter(&mut dec.rec, &tw.lf_info(), [lf_level; 4], self.cfg.bit_depth as u32, false, 1); + let (tile, cdfs) = tw.finish(); + let mut w = BitWriter::new(); + self.frame_header(&mut w, key, qidx, lf_level); + w.byte_align(); + // tile_group_obu( ): a single tile, no tile_start_and_end_present_flag + let mut payload = w.into_bytes(); + payload.extend_from_slice(&tile); + let mut out = Vec::with_capacity(payload.len() + 8); + obu(OBU_FRAME, &payload, &mut out); + let rf = RefFrame { planes: dec.rec, mvs: dec.mvs, lf_level, cdfs }; + (out, rf) + } + + /// The loop filter level minimising the luma error (searched around a guess from the + /// quantizer, on the luma of a subset of the superblock rows). + fn pick_filter_level(&self, rec: &[Plane; 3], info: &LfInfo, src: &[Plane; 3], qidx: u8) -> u32 { + let (w, h) = (self.geom.width, self.geom.height); + let sb_row_step = if self.geom.sb_rows >= 9 { 3 } else { 1 }; + let sse = |pl: &Plane| -> u64 { + let mut s = 0u64; + for y in 0..h { + if !(y >> 6).is_multiple_of(sb_row_step) { + continue; + } + for (a, b) in pl.row(y)[..w].iter().zip(&src[0].row(y)[..w]) { + let d = *a as i64 - *b as i64; + s += (d * d) as u64; + } + } + s + }; + let bd = self.cfg.bit_depth as u32; + let mut cache: Vec<(u32, u64)> = vec![(0, sse(&rec[0]))]; + // one working copy of the luma plane for all candidate levels + let mut planes = [rec[0].clone(), Plane::new(0, 0), Plane::new(0, 0)]; + let mut eval = |lvl: u32| -> u64 { + if let Some(&(_, e)) = cache.iter().find(|(l, _)| *l == lvl) { + return e; + } + planes[0].data.copy_from_slice(&rec[0].data); + loop_filter(&mut planes, info, [lvl; 4], bd, true, sb_row_step); + let e = sse(&planes[0]); + cache.push((lvl, e)); + e + }; + let guess = ((qidx as u32 * 3) / 16).clamp(2, 50); + let mut best = (0u32, eval(0)); + let mut step = (guess / 2).max(1); + let mut center = guess; + let e = eval(center); + if e < best.1 { + best = (center, e); + } + while step >= 2 { + for lvl in [center.saturating_sub(step), (center + step).min(63)] { + let e = eval(lvl); + if e < best.1 { + best = (lvl, e); + } + } + center = best.0.max(1); + step /= 2; + } + best.0 + } + + /// Encodes one frame. + pub fn encode(&mut self, frame: &Frame) -> Result { + self.check_frame(frame)?; + let key = self.frame_num.is_multiple_of(self.cfg.keyint.max(1) as u64); + let src = self.load_source(frame); + let (frame_obu, rf) = match self.cfg.rate { + RateControl::ConstantQ(q) => self.encode_frame(&src, key, q.max(1)), + RateControl::Bitrate { .. } => match self.rc.take() { + Some(mut rc) => { + let out = self.encode_rate_controlled(&src, key, &mut rc); + self.rc = Some(rc); + out + } + // The state is created with the encoder for bitrate mode; without it, encode at a + // middle quantizer rather than fail. + None => self.encode_frame(&src, key, 128), + }, + }; + self.refr = Some(rf); + self.frame_num += 1; + let mut data = Vec::with_capacity(frame_obu.len() + self.seq_obu.len()); + if key { + data.extend_from_slice(&self.seq_obu); + } + data.extend_from_slice(&frame_obu); + Ok(Packet { data, keyframe: key }) + } + + fn encode_rate_controlled(&mut self, src: &[Plane; 3], key: bool, rc: &mut RateState) -> (Vec, RefFrame) { + let first = self.frame_num == 0; + let (budget, q_start) = rc.frame_budget(self.cfg.keyint, key); + // pay back the accumulated deviation over the next RC_PAYBACK frames + let target = (budget - rc.debt / RC_PAYBACK).clamp(budget * 0.35, budget * 2.0); + let mut q = q_start; + let mut best: Option<(Vec, RefFrame, f64, f64)> = None; + // The first frame is coded up to four times to find its quantizer (it fixes the + // quantizers of everything after it), a later key frame up to three times (it is a large + // part of the budget of its group). A frame is coded again only while its size is far from + // the target and an untried quantizer is left to try; other frames are coded once. + let (max_passes, tolerance) = if first { + (RC_FIRST_PASSES, RC_FIRST_TOLERANCE) + } else if key { + (RC_KEY_PASSES, RC_KEY_TOLERANCE) + } else { + (1, 0.0) + }; + let mut tried: Vec<(u8, f64)> = Vec::new(); + for _ in 0..max_passes { + let qi = q.round().clamp(1.0, 255.0) as u8; + if tried.iter().any(|t| t.0 == qi) { + break; + } + let (obu, rf) = self.encode_frame(src, key, qi); + let bits = obu.len() as f64 * 8.0; + let err = (bits / target).log2(); + tried.push((qi, err)); + if best.as_ref().is_none_or(|b| err.abs() < (b.2 / target).log2().abs()) { + best = Some((obu, rf, bits, qi as f64)); + } + if err.abs() <= tolerance { + break; + } + let next = if first && tried.len() == 1 { + Some((q + RC_FIRST_SLOPE * err).clamp(1.0, 255.0)) + } else { + next_probe_q(self.cfg.bit_depth, &tried).map(f64::from) + }; + match next { + Some(next) => q = next, + None => break, + } + } + // At least one pass ran, so `best` is set. + let Some((obu, rf, bits, q_used)) = best else { return self.encode_frame(src, key, q.round().clamp(1.0, 255.0) as u8) }; + rc.debt += bits - budget; + rc.update(key, first, q_used, bits, target); + (obu, rf) + } + + /// The loop filter level of the last encoded frame. For tests. + #[doc(hidden)] + pub fn last_loop_filter_level(&self) -> Option { + self.refr.as_ref().map(|r| r.lf_level) + } + + /// The reconstruction of the last encoded frame (visible area, planes Y, U, V), equal to + /// what a decoder outputs. For tests. + #[doc(hidden)] + pub fn last_reconstruction(&self) -> Option<[Vec; 3]> { + let rf = self.refr.as_ref()?; + Some(std::array::from_fn(|p| { + let (vw, vh) = self.geom.visible(p); + let pl = &rf.planes[p]; + let mut v = Vec::with_capacity(vw * vh); + for y in 0..vh { + v.extend_from_slice(&pl.row(y)[..vw]); + } + v + })) + } +} diff --git a/crates/av1enc/src/loopfilter.rs b/crates/av1enc/src/loopfilter.rs new file mode 100644 index 0000000000..37365fa0ae --- /dev/null +++ b/crates/av1enc/src/loopfilter.rs @@ -0,0 +1,217 @@ +//! Deblocking loop filter (spec 7.14), mirrored from the decoder process so that the encoder's +//! references equal a decoder's. Segmentation and loop filter deltas are off and the sharpness +//! is 0 in the frames this encoder writes. + +use crate::pred::Plane; +use crate::tables::{MAX_LOOP_FILTER, NUM_4X4_BLOCKS_HIGH, NUM_4X4_BLOCKS_WIDE, SUBSAMPLED_SIZE, TX_HEIGHT, TX_WIDTH}; + +/// Mode info the loop filter reads (per 4x4 luma unit, row stride `mi_cols`), and the +/// transform size per 4x4 unit of each plane (`LoopfilterTxSizes`, row stride `mi_cols + 32`). +pub(crate) struct LfInfo<'a> { + pub mi_cols: usize, + pub mi_rows: usize, + pub width: usize, + pub height: usize, + pub mi_size: &'a [u8], + pub skip: &'a [bool], + pub is_inter: &'a [bool], + pub tx: [&'a [u8]; 3], +} + +/// Applies the loop filter with `loop_filter_level[ 0..4 ]` to `planes`. For level search +/// (`luma_only`), only plane 0 and only every `sb_row_step`-th superblock row are filtered. +pub(crate) fn loop_filter(planes: &mut [Plane; 3], info: &LfInfo, level: [u32; 4], bit_depth: u32, luma_only: bool, sb_row_step: usize) { + if level[0] == 0 && level[1] == 0 { + return; + } + let nplanes = if luma_only { 1 } else { 3 }; + for (plane, pl) in planes.iter_mut().enumerate().take(nplanes) { + if plane > 0 && level[1 + plane] == 0 { + continue; + } + for pass in 0..2 { + let step = if plane == 0 { 1 } else { 2 }; + let mut row = 0; + while row < info.mi_rows { + if !(row >> 4).is_multiple_of(sb_row_step) { + row += step; + continue; + } + let mut col = 0; + while col < info.mi_cols { + edge(pl, info, level, bit_depth, plane, pass, (row, col)); + col += step; + } + row += step; + } + } + } +} + +fn edge(pl: &mut Plane, info: &LfInfo, level: [u32; 4], bd: u32, plane: usize, pass: usize, position: (usize, usize)) { + let (row, col) = position; + let s = (plane > 0) as usize; + let (dx, dy) = if pass == 0 { (1usize, 0usize) } else { (0, 1) }; + let x = col * 4; + let y = row * 4; + let row = row | s; + let col = col | s; + let on_screen = !(x >= info.width || y >= info.height || (pass == 0 && x == 0) || (pass == 1 && y == 0)); + if !on_screen { + return; + } + let xp = x >> s; + let yp = y >> s; + let prev_row = row - (dy << s); + let prev_col = col - (dx << s); + let i = row * info.mi_cols + col; + let mi_size = info.mi_size[i] as usize; + let tx_stride = info.mi_cols + 32; + let tx_sz = info.tx[plane][(row >> s) * tx_stride + (col >> s)] as usize; + let plane_size = SUBSAMPLED_SIZE[mi_size][s][s] as usize; + let skip = info.skip[i]; + let is_intra = !info.is_inter[i]; + let prev_tx_sz = info.tx[plane][(prev_row >> s) * tx_stride + (prev_col >> s)] as usize; + let is_block_edge = if pass == 0 { + xp.is_multiple_of(4 * NUM_4X4_BLOCKS_WIDE[plane_size] as usize) + } else { + yp.is_multiple_of(4 * NUM_4X4_BLOCKS_HIGH[plane_size] as usize) + }; + let is_tx_edge = if pass == 0 { xp.is_multiple_of(TX_WIDTH[tx_sz] as usize) } else { yp.is_multiple_of(TX_HEIGHT[tx_sz] as usize) }; + let apply_filter = is_tx_edge && (is_block_edge || !skip || is_intra); + let base_size = if pass == 0 { TX_WIDTH[prev_tx_sz].min(TX_WIDTH[tx_sz]) } else { TX_HEIGHT[prev_tx_sz].min(TX_HEIGHT[tx_sz]) } as usize; + let filter_size = if plane == 0 { 16.min(base_size) } else { 8.min(base_size) }; + // 7.14.4 with no deltas, segmentation or sharpness: the same strength for every block + let lvl = level[if plane == 0 { pass } else { plane + 1 }].min(MAX_LOOP_FILTER as u32) as i32; + if !apply_filter || lvl == 0 { + return; + } + let limit = lvl.max(1); + let blimit = 2 * (lvl + 2) + limit; + let thresh = lvl >> 4; + let strength = FilterStrength { limit, blimit, thresh }; + for k in 0..4 { + sample_filter(pl, (xp + dy * k, yp + dx * k), plane, &strength, (dx as isize, dy as isize), filter_size, bd); + } +} + +/// Thresholds derived from the loop-filter level (spec 7.14.4). +struct FilterStrength { + limit: i32, + blimit: i32, + thresh: i32, +} + +#[inline] +fn sample_filter( + pl: &mut Plane, + position: (usize, usize), + plane: usize, + strength: &FilterStrength, + direction: (isize, isize), + filter_size: usize, + bd: u32, +) { + let (x, y) = position; + let (dx, dy) = direction; + let FilterStrength { limit, blimit, thresh } = *strength; + let stride = pl.stride as isize; + let step = dy * stride + dx; + let base = y as isize * stride + x as isize; + let reach: isize = if filter_size >= 16 { 7 } else { 4 }; + let mut v = [0i32; 16]; + for k in -reach..reach { + v[(k + 8) as usize] = pl.data[(base + k * step) as usize] as i32; + } + let at = |k: isize| -> i32 { v[(k + 8) as usize] }; + let (q0, q1, q2, q3) = (at(0), at(1), at(2), at(3)); + let (p0, p1, p2, p3) = (at(-1), at(-2), at(-3), at(-4)); + let d = &mut pl.data; + let sh = bd - 8; + let thresh_bd = thresh << sh; + let hev = (p1 - p0).abs() > thresh_bd || (q1 - q0).abs() > thresh_bd; + let filter_len = if filter_size == 4 { + 4 + } else if plane != 0 { + 6 + } else if filter_size == 8 { + 8 + } else { + 16 + }; + let limit_bd = limit << sh; + let blimit_bd = blimit << sh; + let mut mask = (p1 - p0).abs() > limit_bd || (q1 - q0).abs() > limit_bd || (p0 - q0).abs() * 2 + (p1 - q1).abs() / 2 > blimit_bd; + if filter_len >= 6 { + mask |= (p2 - p1).abs() > limit_bd || (q2 - q1).abs() > limit_bd; + } + if filter_len >= 8 { + mask |= (p3 - p2).abs() > limit_bd || (q3 - q2).abs() > limit_bd; + } + if mask { + return; + } + let t_bd = 1 << sh; + let flat = if filter_size >= 8 { + let mut m = (p1 - p0).abs() > t_bd || (q1 - q0).abs() > t_bd || (p2 - p0).abs() > t_bd || (q2 - q0).abs() > t_bd; + if filter_len >= 8 { + m |= (p3 - p0).abs() > t_bd || (q3 - q0).abs() > t_bd; + } + !m + } else { + false + }; + let flat2 = if filter_size >= 16 { + let (q4, q5, q6, p4, p5, p6) = (at(4), at(5), at(6), at(-5), at(-6), at(-7)); + !((p6 - p0).abs() > t_bd + || (q6 - q0).abs() > t_bd + || (p5 - p0).abs() > t_bd + || (q5 - q0).abs() > t_bd + || (p4 - p0).abs() > t_bd + || (q4 - q0).abs() > t_bd) + } else { + false + }; + if filter_size == 4 || !flat { + // narrow filter (7.14.6.3) + let half = 1i32 << (bd - 1); + let c = |v: i32| v.clamp(-half, half - 1); + let off = 0x80 << sh; + let (ps1, ps0, qs0, qs1) = (p1 - off, p0 - off, q0 - off, q1 - off); + let mut filter = if hev { c(ps1 - qs1) } else { 0 }; + filter = c(filter + 3 * (qs0 - ps0)); + let filter1 = c(filter + 4) >> 3; + let filter2 = c(filter + 3) >> 3; + d[base as usize] = (c(qs0 - filter1) + off) as u16; + d[(base - step) as usize] = (c(ps0 + filter2) + off) as u16; + if !hev { + let f = (filter1 + 1) >> 1; + d[(base + step) as usize] = (c(qs1 - f) + off) as u16; + d[(base - 2 * step) as usize] = (c(ps1 + f) + off) as u16; + } + } else { + // wide filter (7.14.6.4) + let log2 = if filter_size == 8 || !flat2 { 3 } else { 4 }; + let n: isize = if log2 == 4 { + 6 + } else if plane == 0 { + 3 + } else { + 2 + }; + let n2: isize = if log2 == 3 && plane == 0 { 0 } else { 1 }; + let mut f = [0i32; 12]; + for i in -n..n { + let mut t = 0; + for j in -n..=n { + let p = (i + j).clamp(-(n + 1), n); + let tap = if j.abs() <= n2 { 2 } else { 1 }; + t += at(p) * tap; + } + f[(i + n) as usize] = (t + (1 << (log2 - 1))) >> log2; + } + for i in -n..n { + d[(base + i * step) as usize] = f[(i + n) as usize] as u16; + } + } +} diff --git a/crates/av1enc/src/pred.rs b/crates/av1enc/src/pred.rs new file mode 100644 index 0000000000..7384a1d39e --- /dev/null +++ b/crates/av1enc/src/pred.rs @@ -0,0 +1,454 @@ +//! Prediction processes, mirrored from the spec (7.11.2 intra prediction for the non-directional +//! modes plus V_PRED / H_PRED without edge filtering, 7.11.3 block inter prediction without +//! reference scaling) so that the encoder predicts exactly like a decoder. + +use crate::tables::{ + DC_PRED, EIGHTTAP, H_PRED, PAETH_PRED, SM_WEIGHTS_TX_4X4, SM_WEIGHTS_TX_8X8, SM_WEIGHTS_TX_16X16, SM_WEIGHTS_TX_32X32, SMOOTH_H_PRED, + SMOOTH_PRED, SMOOTH_V_PRED, SUBPEL_FILTERS, V_PRED, +}; +use crate::transform::round2; + +/// A plane buffer (padded to whole superblocks). +#[derive(Clone)] +pub(crate) struct Plane { + pub data: Vec, + pub stride: usize, +} + +impl Plane { + pub fn new(w: usize, h: usize) -> Plane { + Plane { data: vec![0; w * h], stride: w } + } + #[inline(always)] + pub fn at(&self, x: usize, y: usize) -> u16 { + self.data[y * self.stride + x] + } + #[inline(always)] + pub fn row(&self, y: usize) -> &[u16] { + &self.data[y * self.stride..(y + 1) * self.stride] + } +} + +/// The intra modes this encoder uses (values are the spec's y / uv mode numbers). +pub(crate) const INTRA_MODES_USED: [usize; 7] = [DC_PRED, V_PRED, H_PRED, PAETH_PRED, SMOOTH_PRED, SMOOTH_V_PRED, SMOOTH_H_PRED]; + +/// Inputs of the intra prediction process. +pub(crate) struct IntraEdge { + pub x: usize, + pub y: usize, + pub have_left: bool, + pub have_above: bool, + pub have_above_right: bool, + pub have_below_left: bool, + pub log2: u32, + /// ((MiCols * MI_SIZE) >> subX) - 1 and the same for rows. + pub max_x: i32, + pub max_y: i32, + pub bit_depth: u32, +} + +/// Edge samples: `above[0..2n]`, `left[0..2n]` and the top-left corner. +pub(crate) struct Edges { + above: [i32; 64], + left: [i32; 64], + corner: i32, +} + +pub(crate) fn edges(pl: &Plane, e: &IntraEdge) -> Edges { + let w = 1usize << e.log2; + let h = w; + let bd = e.bit_depth; + let (x, y) = (e.x, e.y); + let n = w + h; + let mut above = [0i32; 64]; + let mut left = [0i32; 64]; + if !e.have_above && e.have_left { + let v = pl.at(x - 1, y) as i32; + above[..n].iter_mut().for_each(|a| *a = v); + } else if !e.have_above && !e.have_left { + let v = (1 << (bd - 1)) - 1; + above[..n].iter_mut().for_each(|a| *a = v); + } else { + let above_limit = e.max_x.min(x as i32 + if e.have_above_right { 2 * w as i32 } else { w as i32 } - 1); + let row = pl.row(y - 1); + for (i, a) in above[..n].iter_mut().enumerate() { + *a = row[above_limit.min(x as i32 + i as i32) as usize] as i32; + } + } + if !e.have_left && e.have_above { + let v = pl.at(x, y - 1) as i32; + left[..n].iter_mut().for_each(|a| *a = v); + } else if !e.have_left && !e.have_above { + let v = (1 << (bd - 1)) + 1; + left[..n].iter_mut().for_each(|a| *a = v); + } else { + let left_limit = e.max_y.min(y as i32 + if e.have_below_left { 2 * h as i32 } else { h as i32 } - 1); + for (i, l) in left[..n].iter_mut().enumerate() { + *l = pl.at(x - 1, left_limit.min(y as i32 + i as i32) as usize) as i32; + } + } + let corner = if e.have_above && e.have_left { + pl.at(x - 1, y - 1) as i32 + } else if e.have_above { + pl.at(x, y - 1) as i32 + } else if e.have_left { + pl.at(x - 1, y) as i32 + } else { + 1 << (bd - 1) + }; + Edges { above, left, corner } +} + +fn sm_weights(log2: u32) -> &'static [u8] { + match log2 { + 2 => &SM_WEIGHTS_TX_4X4, + 3 => &SM_WEIGHTS_TX_8X8, + 4 => &SM_WEIGHTS_TX_16X16, + _ => &SM_WEIGHTS_TX_32X32, + } +} + +/// Predicts a square `2^log2` block with `mode` into `pred` (row stride = block width). +pub(crate) fn predict_intra(ed: &Edges, e: &IntraEdge, mode: usize, pred: &mut [u16]) { + let log2 = e.log2; + let w = 1usize << log2; + let h = w; + let bd = e.bit_depth; + let (above, left) = (&ed.above, &ed.left); + match mode { + DC_PRED => { + let v = if e.have_left && e.have_above { + let sum: i32 = left[..h].iter().sum::() + above[..w].iter().sum::() + ((w + h) >> 1) as i32; + sum / (w + h) as i32 + } else if e.have_left { + (left[..h].iter().sum::() + (h >> 1) as i32) >> log2 + } else if e.have_above { + (above[..w].iter().sum::() + (w >> 1) as i32) >> log2 + } else { + 1 << (bd - 1) + }; + let v = v.clamp(0, (1 << bd) - 1) as u16; + pred[..w * h].iter_mut().for_each(|p| *p = v); + } + V_PRED => { + for i in 0..h { + for j in 0..w { + pred[i * w + j] = above[j] as u16; + } + } + } + H_PRED => { + for i in 0..h { + pred[i * w..i * w + w].iter_mut().for_each(|p| *p = left[i] as u16); + } + } + PAETH_PRED => { + let tl = ed.corner; + for i in 0..h { + for j in 0..w { + let a = above[j]; + let l = left[i]; + let base = a + l - tl; + let p_left = (base - l).abs(); + let p_top = (base - a).abs(); + let p_tl = (base - tl).abs(); + pred[i * w + j] = if p_left <= p_top && p_left <= p_tl { + l as u16 + } else if p_top <= p_tl { + a as u16 + } else { + tl as u16 + }; + } + } + } + _ => { + let wx = sm_weights(log2); + let wy = wx; + for i in 0..h { + for j in 0..w { + let v = if mode == SMOOTH_PRED { + let s = wy[i] as i32 * above[j] + + (256 - wy[i] as i32) * left[h - 1] + + wx[j] as i32 * left[i] + + (256 - wx[j] as i32) * above[w - 1]; + round2(s, 9) + } else if mode == SMOOTH_V_PRED { + round2(wy[i] as i32 * above[j] + (256 - wy[i] as i32) * left[h - 1], 8) + } else { + round2(wx[j] as i32 * left[i] + (256 - wx[j] as i32) * above[w - 1], 8) + }; + pred[i * w + j] = v as u16; + } + } + } + } +} + +/// Block inter prediction (7.11.3.3 motion vector scaling without scaling, 7.11.3.4 block +/// inter prediction with the EIGHTTAP filter, single reference so InterRound1 = 11 and the +/// final rounding is the identity) of the `w x h` block at plane position (x, y) displaced by +/// `mv` (1/8 luma sample units, [row, col]). `last_x` / `last_y` are the largest sample +/// coordinates of the reference plane; the result goes to `pred` (row stride `w`). +pub(crate) struct InterBlock { + pub last_x: i32, + pub last_y: i32, + pub x: usize, + pub y: usize, + pub width: usize, + pub height: usize, + pub mv: [i32; 2], + pub subsampling: usize, + pub bit_depth: u32, +} + +pub(crate) fn predict_inter(refp: &Plane, block: &InterBlock, pred: &mut [u16]) { + let InterBlock { last_x, last_y, x, y, width: w, height: h, mv, subsampling: ss, bit_depth } = *block; + // positions in 1/16 sample units ((startX >> 6) & 15 is the filter phase) + let px = ((x as i32) << 4) + ((2 * mv[1]) >> ss); + let py = ((y as i32) << 4) + ((2 * mv[0]) >> ss); + let (fx, fy) = ((px & 15) as usize, (py & 15) as usize); + let (ix, iy) = (px >> 4, py >> 4); + let max = (1i32 << bit_depth) - 1; + if fx == 0 && fy == 0 { + // the filters' phase-0 kernel is a unit impulse: a clamped copy + for r in 0..h { + let ry = (iy + r as i32).clamp(0, last_y) as usize; + let row = refp.row(ry); + let out = &mut pred[r * w..r * w + w]; + let x0 = ix; + if x0 >= 0 && x0 + w as i32 - 1 <= last_x { + out.copy_from_slice(&row[x0 as usize..x0 as usize + w]); + } else { + for (c, o) in out.iter_mut().enumerate() { + *o = row[(x0 + c as i32).clamp(0, last_x) as usize]; + } + } + } + return; + } + let filt_h = if w <= 4 { 4 } else { EIGHTTAP }; + let filt_v = if h <= 4 { 4 } else { EIGHTTAP }; + let kh = &SUBPEL_FILTERS[filt_h][fx]; + let kv = &SUBPEL_FILTERS[filt_v][fy]; + let bx = ix - 3; + let x_inside = bx >= 0 && bx + w as i32 + 7 <= last_x + 1; + // horizontal pass of one reference row into `out` (Round2( sum, InterRound0 = 3 )) + let hfilter = |ry: usize, out: &mut [i32]| { + let row = refp.row(ry); + if x_inside { + let seg = &row[bx as usize..bx as usize + w + 7]; + for (c, o) in out.iter_mut().enumerate() { + let mut s = 0i32; + for t in 0..8 { + s += kh[t] as i32 * seg[c + t] as i32; + } + *o = round2(s, 3); + } + } else { + for (c, o) in out.iter_mut().enumerate() { + let mut s = 0i32; + for t in 0..8 { + s += kh[t] as i32 * row[(bx + c as i32 + t as i32).clamp(0, last_x) as usize] as i32; + } + *o = round2(s, 3); + } + } + }; + let mut inter = [0i32; (64 + 7) * 64]; + if fy == 0 { + // the vertical kernel is 128 at the centre: Round2( 128 * v, 11 ) = Round2( v, 4 ) + for r in 0..h { + let ry = (iy + r as i32).clamp(0, last_y) as usize; + hfilter(ry, &mut inter[..w]); + for (p, &v) in pred[r * w..r * w + w].iter_mut().zip(&inter[..w]) { + *p = round2(v, 4).clamp(0, max) as u16; + } + } + return; + } + if fx == 0 { + // the horizontal pass is exactly 16 * sample: Round2( 16 * sum, 11 ) = Round2( sum, 7 ) + for r in 0..h { + let rows: [&[u16]; 8] = std::array::from_fn(|t| refp.row((iy + r as i32 + t as i32 - 3).clamp(0, last_y) as usize)); + for c in 0..w { + let xx = if x_inside { (ix + c as i32) as usize } else { (ix + c as i32).clamp(0, last_x) as usize }; + let mut s = 0i32; + for t in 0..8 { + s += kv[t] as i32 * rows[t][xx] as i32; + } + pred[r * w + c] = round2(s, 7).clamp(0, max) as u16; + } + } + return; + } + let inter_h = h + 7; + for r in 0..inter_h { + let ry = (iy + r as i32 - 3).clamp(0, last_y) as usize; + hfilter(ry, &mut inter[r * w..r * w + w]); + } + for r in 0..h { + for c in 0..w { + let mut s = 0i32; + for t in 0..8 { + s += kv[t] as i32 * inter[(r + t) * w + c]; + } + pred[r * w + c] = round2(s, 11).clamp(0, max) as u16; + } + } +} + +/// The visible `w x h` area of a reference plane interpolated at the sub-sample phase +/// (`fx`, `fy`) (1/16 units) with the EIGHTTAP filter, as `predict_inter` computes it for +/// blocks wider and taller than 4. The reference for `half_planes`, which the encoder uses. +#[cfg(test)] +pub(crate) fn subpel_plane(refp: &Plane, w: usize, h: usize, fx: usize, fy: usize, bit_depth: u32) -> Vec { + let kh = &SUBPEL_FILTERS[EIGHTTAP][fx]; + let kv = &SUBPEL_FILTERS[EIGHTTAP][fy]; + let max = (1i32 << bit_depth) - 1; + let (lx, ly) = (w as i32 - 1, h as i32 - 1); + let mut inter = vec![0i32; w * h]; + for y in 0..h { + let row = refp.row(y); + for x in 0..w { + let mut s = 0i32; + for t in 0..8 { + s += kh[t] as i32 * row[(x as i32 + t as i32 - 3).clamp(0, lx) as usize] as i32; + } + inter[y * w + x] = round2(s, 3); + } + } + let mut out = vec![0u16; w * h]; + for y in 0..h { + for x in 0..w { + let mut s = 0i32; + for t in 0..8 { + s += kv[t] as i32 * inter[(y as i32 + t as i32 - 3).clamp(0, ly) as usize * w + x]; + } + out[y * w + x] = round2(s, 11).clamp(0, max) as u16; + } + } + out +} + +/// The three half-sample planes (8, 0), (0, 8), (8, 8) of `subpel_plane` in one pass: the +/// horizontal half-sample pass is shared by (8, 0) and (8, 8), and the phase-0 kernels reduce to +/// exact shifts (Round2( 128 * v, 11 ) = Round2( v, 4 ), Round2( 16 * s, 11 ) = Round2( s, 7 )), +/// so the result equals three `subpel_plane` calls bit for bit. +pub(crate) fn half_planes(refp: &Plane, w: usize, h: usize, bit_depth: u32) -> [Vec; 3] { + let k = &SUBPEL_FILTERS[EIGHTTAP][8]; + let max = (1i32 << bit_depth) - 1; + let (lx, ly) = (w as i32 - 1, h as i32 - 1); + // horizontal pass at phase 8: Round2( sum, 3 ) + let mut hp = vec![0i32; w * h]; + for y in 0..h { + let row = &refp.row(y)[..w]; + let out = &mut hp[y * w..y * w + w]; + let tap = |x: i32| -> i32 { + let mut s = 0i32; + for t in 0..8 { + s += k[t] as i32 * row[(x + t as i32 - 3).clamp(0, lx) as usize] as i32; + } + round2(s, 3) + }; + if w >= 8 { + for x in 0..3 { + out[x] = tap(x as i32); + } + for (x, o) in out.iter_mut().enumerate().take(w - 4).skip(3) { + let seg = &row[x - 3..x + 5]; + let mut s = 0i32; + for t in 0..8 { + s += k[t] as i32 * seg[t] as i32; + } + *o = round2(s, 3); + } + for x in w - 4..w { + out[x] = tap(x as i32); + } + } else { + for (x, o) in out.iter_mut().enumerate() { + *o = tap(x as i32); + } + } + } + let p80: Vec = hp.iter().map(|&v| round2(v, 4).clamp(0, max) as u16).collect(); + let mut p08 = vec![0u16; w * h]; + let mut p88 = vec![0u16; w * h]; + for y in 0..h { + let rows: [usize; 8] = std::array::from_fn(|t| (y as i32 + t as i32 - 3).clamp(0, ly) as usize); + let (o08, o88) = (&mut p08[y * w..y * w + w], &mut p88[y * w..y * w + w]); + for x in 0..w { + let (mut a, mut b) = (0i32, 0i32); + for t in 0..8 { + a += k[t] as i32 * refp.data[rows[t] * refp.stride + x] as i32; + b += k[t] as i32 * hp[rows[t] * w + x]; + } + o08[x] = round2(a, 7).clamp(0, max) as u16; + o88[x] = round2(b, 11).clamp(0, max) as u16; + } + } + [p80, p08, p88] +} + +#[cfg(test)] +mod tests { + use super::*; + + /// Whole-plane interpolation equals block inter prediction at the same phase. + #[test] + fn subpel_plane_matches_block_prediction() { + let (w, h) = (37, 29); + let mut p = Plane::new(64, 64); + for (i, v) in p.data.iter_mut().enumerate() { + *v = ((i * 7919) % 256) as u16; + } + for (fx, fy) in [(8, 0), (0, 8), (8, 8), (4, 12)] { + let plane = subpel_plane(&p, w, h, fx, fy, 8); + for (bx, by) in [(0i32, 0i32), (8, 8), (24, 16), (-5, 20), (30, -3)] { + let mut pred = vec![0u16; 64]; + let mv = [by * 8 + fy as i32 / 2, bx * 8 + fx as i32 / 2]; + predict_inter( + &p, + &InterBlock { last_x: w as i32 - 1, last_y: h as i32 - 1, x: 0, y: 0, width: 8, height: 8, mv, subsampling: 0, bit_depth: 8 }, + &mut pred, + ); + for r in 0..8 { + for c in 0..8 { + let (x, y) = ((bx + c).clamp(0, w as i32 - 1), (by + r).clamp(0, h as i32 - 1)); + if bx + c == x && by + r == y { + assert_eq!( + pred[(r * 8 + c) as usize], + plane[y as usize * w + x as usize], + "phase ({fx}, {fy}) at ({}, {})", + bx + c, + by + r + ); + } + } + } + } + } + } + + #[test] + fn half_planes_match_subpel_plane() { + for (w, h) in [(37usize, 29usize), (5, 3), (1, 1), (64, 64)] { + let mut p = Plane::new(64, 64); + for (i, v) in p.data.iter_mut().enumerate() { + *v = ((i * 7919) % 1024) as u16; + } + let hp = half_planes(&p, w, h, 10); + for (i, (fx, fy)) in [(8, 0), (0, 8), (8, 8)].into_iter().enumerate() { + assert_eq!(hp[i], subpel_plane(&p, w, h, fx, fy, 10), "{w}x{h} phase ({fx}, {fy})"); + } + } + } + + #[test] + fn phase_zero_filters_are_impulses() { + for f in SUBPEL_FILTERS.iter() { + assert_eq!(f[0], [0, 0, 0, 128, 0, 0, 0, 0]); + } + } +} diff --git a/crates/av1enc/src/symbol.rs b/crates/av1enc/src/symbol.rs new file mode 100644 index 0000000000..579a51e208 --- /dev/null +++ b/crates/av1enc/src/symbol.rs @@ -0,0 +1,267 @@ +//! Multi-symbol arithmetic encoder: the exact inverse of the symbol decoder of spec 8.2. +//! +//! The decoder keeps `SymbolValue`, the bitwise complement of a 15-bit window into the tile data +//! minus the lower bounds of the symbols decoded so far, and selects symbol `k` when +//! `cur(k) <= SymbolValue < cur(k - 1)` with +//! `cur(k) = ((SymbolRange >> 8) * ((32768 - cdf[k]) >> 6) >> 1) + 4 * (N - k - 1)` and +//! `cur(-1) = SymbolRange`. The encoder therefore runs an ordinary carry-propagating range +//! coder over the complemented bit stream: coding symbol `k` adds `cur(k)` to `low` and sets the +//! range to `cur(k - 1) - cur(k)`; both are renormalised exactly like the decoder's range. +//! +//! At the end (spec 8.2.4 exit process) the decoder has consumed `S` renormalisation bits plus a +//! 15-bit window, and the bits from position `S` on must be a one followed by zeros. In the +//! complemented domain that window is `0b011_1111_1111_1111`, so the encoder picks the `S`-bit +//! prefix `D` with `D * 2^15 + 2^14 - 1` inside `[low, low + range)`, i.e. +//! `D = (low + 2^14) >> 15`, and writes `!D`, a one bit and zero padding. + +pub(crate) struct SymbolWriter { + /// Low end of the interval, holding `15 + cnt` bits not yet moved to `out` (plus carries). + low: u64, + rng: u32, + cnt: u32, + out: Vec, + pub disable_update: bool, +} + +impl SymbolWriter { + pub fn new(disable_update: bool) -> Self { + SymbolWriter { low: 0, rng: 1 << 15, cnt: 0, out: Vec::with_capacity(1 << 16), disable_update } + } + + /// Adds `k` to the bytes already output (carry propagation). + fn carry(&mut self, mut k: u64) { + let mut i = self.out.len(); + while k != 0 { + i -= 1; + let t = self.out[i] as u64 + k; + self.out[i] = t as u8; + k = t >> 8; + } + } + + #[inline(always)] + fn encode(&mut self, cdf: &[u16], n: usize, s: usize) { + let r8 = self.rng >> 8; + let cur = |k: usize| -> u32 { ((r8 * ((32768 - cdf[k] as u32) >> 6)) >> 1) + 4 * (n - k - 1) as u32 }; + let hi = if s == 0 { self.rng } else { cur(s - 1) }; + let lo = cur(s); + debug_assert!(hi > lo, "symbol {s} has an empty interval"); + self.low += lo as u64; + self.rng = hi - lo; + let d = 15 - (31 - self.rng.leading_zeros()); + if d == 0 { + return; + } + self.rng <<= d; + self.low <<= d; + self.cnt += d; + while self.cnt >= 8 { + let sh = 15 + self.cnt - 8; + let c = self.low >> sh; + self.low &= (1u64 << sh) - 1; + self.cnt -= 8; + self.carry(c >> 8); + self.out.push((c & 0xff) as u8); + } + } + + /// Codes `s` with the adaptive CDF `cdf` (N values plus the adaptation counter). + #[inline] + pub fn symbol(&mut self, s: usize, cdf: &mut [u16]) { + let n = cdf.len() - 1; + debug_assert!(s < n); + self.encode(cdf, n, s); + if !self.disable_update { + update_cdf(cdf, n, s); + } + } + + /// Codes `s` with a CDF that is not adapted. + pub fn symbol_fixed(&mut self, s: usize, cdf: &[u16]) { + let n = cdf.len() - 1; + self.encode(cdf, n, s); + } + + /// `read_bool()` counterpart. + pub fn bool(&mut self, b: bool) { + self.encode(&[1 << 14, 1 << 15, 0], 2, b as usize); + } + + /// `L(n)` counterpart (most significant bit first). + #[cfg(test)] + pub fn literal(&mut self, n: u32, v: u32) { + for i in (0..n).rev() { + self.bool((v >> i) & 1 != 0); + } + } + + /// Finishes the tile: returns its bytes (with the trailing one bit and zero padding). + pub fn finish(mut self) -> Vec { + let low = self.low + (1 << 14); + let mut tail = low >> 15; + self.carry(tail >> self.cnt); + tail &= (1u64 << self.cnt) - 1; + let mut bytes: Vec = self.out.iter().map(|b| !b).collect(); + let n = self.cnt + 1; // 1..=8 bits: !tail then the trailing one bit + let v = (((!tail) & ((1u64 << self.cnt) - 1)) << 1) | 1; + bytes.push((v << (8 - n)) as u8); + bytes + } +} + +/// CDF adaptation (spec 8.2.6, as in the decoder). +#[inline(always)] +pub(crate) fn update_cdf(cdf: &mut [u16], n: usize, symbol: usize) { + let count = cdf[n]; + let rate = 3 + (count > 15) as u32 + (count > 31) as u32 + (31 - (n as u32).leading_zeros()).min(2); + let mut tmp = 0u32; + for i in 0..n - 1 { + if i == symbol { + tmp = 1 << 15; + } + let c = cdf[i] as u32; + if tmp < c { + cdf[i] = (c - ((c - tmp) >> rate)) as u16; + } else { + cdf[i] = (c + ((tmp - c) >> rate)) as u16; + } + } + cdf[n] += (count < 32) as u16; +} + +#[cfg(test)] +mod tests { + use super::*; + + /// The spec's symbol decoder (8.2.2 - 8.2.4), for checking the encoder. + struct Dec<'a> { + data: &'a [u8], + pos: usize, + value: u32, + range: u32, + max_bits: i64, + } + + impl<'a> Dec<'a> { + fn new(data: &'a [u8]) -> Self { + let mut d = Dec { data, pos: 0, value: 0, range: 1 << 15, max_bits: 8 * data.len() as i64 - 15 }; + let nb = (data.len() * 8).min(15) as u32; + let buf = d.bits(nb); + d.value = ((1 << 15) - 1) ^ (buf << (15 - nb)); + d + } + fn bits(&mut self, n: u32) -> u32 { + let mut x = 0; + for _ in 0..n { + let b = if self.pos < self.data.len() * 8 { (self.data[self.pos >> 3] >> (7 - (self.pos & 7))) & 1 } else { 0 }; + self.pos += 1; + x = (x << 1) | b as u32; + } + x + } + fn symbol(&mut self, cdf: &mut [u16], adapt: bool) -> usize { + let n = cdf.len() - 1; + let mut cur = self.range; + let mut s = 0; + let mut prev; + loop { + prev = cur; + let f = (1u32 << 15) - cdf[s] as u32; + cur = (((self.range >> 8) * (f >> 6)) >> 1) + 4 * (n - s - 1) as u32; + if self.value >= cur { + break; + } + s += 1; + } + self.range = prev - cur; + self.value -= cur; + let b = 15 - (31 - self.range.leading_zeros()); + if b > 0 { + self.range <<= b; + let nb = (b as i64).min(self.max_bits.max(0)) as u32; + let nd = self.bits(nb) << (b - nb); + self.value = nd ^ (((self.value + 1) << b) - 1); + self.max_bits -= b as i64; + } + if adapt { + update_cdf(cdf, n, s); + } + s + } + /// Position of the trailing one bit (8.2.4). + fn trailing_position(&self) -> i64 { + let consumed = (self.pos as i64).min(self.data.len() as i64 * 8); + consumed - (15i64).min(self.max_bits + 15) + } + } + + #[test] + fn bools_only() { + for len in 1..200 { + let bits: Vec = (0..len).map(|i| (i * 7 + len) % 3 == 0).collect(); + let mut w = SymbolWriter::new(false); + for &b in &bits { + w.bool(b); + } + let data = w.finish(); + let mut d = Dec::new(&data); + for (i, &b) in bits.iter().enumerate() { + assert_eq!(d.symbol(&mut [1 << 14, 1 << 15, 0], false) == 1, b, "len {len} bit {i} data {data:?}"); + } + } + } + + #[test] + fn round_trip_random_symbols() { + let mut seed = 0x1234_5678u32; + let mut rnd = || { + seed ^= seed << 13; + seed ^= seed >> 17; + seed ^= seed << 5; + seed + }; + for trial in 0..300 { + let count = 1 + (rnd() % 3000) as usize; + let mut cdfs_e = vec![[4096u16, 11264, 19328, 32768, 0]; 4]; + let mut cdfs_d = cdfs_e.clone(); + let mut syms = Vec::new(); + let mut w = SymbolWriter::new(false); + for _ in 0..count { + let kind = rnd() % 3; + let ctx = (rnd() % 4) as usize; + // skewed symbols so that the range also gets small + let s = if rnd() % 4 == 0 { (rnd() % 4) as usize } else { 0 }; + match kind { + 0 => w.symbol(s, &mut cdfs_e[ctx]), + 1 => w.bool(s & 1 == 1), + _ => w.literal(5, s as u32 * 7), + } + syms.push((kind, ctx, s)); + } + let data = w.finish(); + let mut d = Dec::new(&data); + for (idx, &(kind, ctx, s)) in syms.iter().enumerate() { + let got = match kind { + 0 => d.symbol(&mut cdfs_d[ctx], true), + 1 => d.symbol(&mut [1 << 14, 1 << 15, 0], false), + _ => { + let mut v = 0; + for _ in 0..5 { + v = (v << 1) | d.symbol(&mut [1 << 14, 1 << 15, 0], false); + } + v / 7 + } + }; + let want = if kind == 1 { s & 1 } else { s }; + assert_eq!(got, want, "trial {trial} idx {idx} kind {kind} of {}", syms.len()); + } + let tp = d.trailing_position(); + assert!(tp >= 0 && (tp as usize) < data.len() * 8, "trial {trial}: trailing bit position {tp}"); + let tp = tp as usize; + assert_eq!((data[tp >> 3] >> (7 - (tp & 7))) & 1, 1, "trial {trial}: trailing one bit"); + for p in tp + 1..data.len() * 8 { + assert_eq!((data[p >> 3] >> (7 - (p & 7))) & 1, 0, "trial {trial}: padding"); + } + } + } +} diff --git a/crates/av1enc/src/tables.rs b/crates/av1enc/src/tables.rs new file mode 100644 index 0000000000..d42890cc3e --- /dev/null +++ b/crates/av1enc/src/tables.rs @@ -0,0 +1,4836 @@ +// Tables normatively defined by the public AV1 Bitstream & Decoding Process Specification +// v1.0.0 (with Errata 1, Section 6.8.2 / Section 8). Only the tables this encoder uses are kept. +#![allow(dead_code, clippy::unreadable_literal)] + +pub const ADST_ADST: usize = 3; +pub const ADST_DCT: usize = 1; +pub const ADST_FLIPADST: usize = 7; +pub const AFFINE: usize = 3; +pub const ALTREF2_FRAME: usize = 6; +pub const ALTREF_FRAME: usize = 7; +pub const ANGLE_STEP: usize = 3; +pub const BIDIR_COMP_REFERENCE: usize = 1; +pub const BILINEAR: usize = 3; +pub const BLOCK_128X128: usize = 15; +pub const BLOCK_128X64: usize = 14; +pub const BLOCK_16X16: usize = 6; +pub const BLOCK_16X32: usize = 7; +pub const BLOCK_16X4: usize = 17; +pub const BLOCK_16X64: usize = 20; +pub const BLOCK_16X8: usize = 5; +pub const BLOCK_32X16: usize = 8; +pub const BLOCK_32X32: usize = 9; +pub const BLOCK_32X64: usize = 10; +pub const BLOCK_32X8: usize = 19; +pub const BLOCK_4X16: usize = 16; +pub const BLOCK_4X4: usize = 0; +pub const BLOCK_4X8: usize = 1; +pub const BLOCK_64X128: usize = 13; +pub const BLOCK_64X16: usize = 21; +pub const BLOCK_64X32: usize = 11; +pub const BLOCK_64X64: usize = 12; +pub const BLOCK_8X16: usize = 4; +pub const BLOCK_8X32: usize = 18; +pub const BLOCK_8X4: usize = 2; +pub const BLOCK_8X8: usize = 3; +pub const BLOCK_INVALID: usize = 22; +pub const BLOCK_SIZES: usize = 22; +pub const BLOCK_SIZE_GROUPS: usize = 4; +pub const BR_CDF_SIZE: usize = 4; +pub const BUFFER_POOL_MAX_SIZE: usize = 10; +pub const BWDREF_FRAME: usize = 5; +pub const BWD_REFS: usize = 3; +pub const CFL_ALPHABET_SIZE: usize = 16; +pub const CFL_ALPHA_CONTEXTS: usize = 6; +pub const CFL_JOINT_SIGNS: usize = 8; +pub const CFL_SIGN_NEG: usize = 1; +pub const CFL_SIGN_POS: usize = 2; +pub const CFL_SIGN_ZERO: usize = 0; +pub const CLASS0_SIZE: usize = 2; +pub const COEFF_BASE_RANGE: usize = 12; +pub const COEFF_CDF_Q_CTXS: usize = 4; +pub const COMPOUND_AVERAGE: usize = 2; +pub const COMPOUND_DIFFWTD: usize = 1; +pub const COMPOUND_DISTANCE: usize = 4; +pub const COMPOUND_IDX_CONTEXTS: usize = 6; +pub const COMPOUND_INTRA: usize = 3; +pub const COMPOUND_MODES: usize = 8; +pub const COMPOUND_MODE_CONTEXTS: usize = 8; +pub const COMPOUND_REFERENCE: usize = 1; +pub const COMPOUND_TYPES: usize = 2; +pub const COMPOUND_WEDGE: usize = 0; +pub const COMP_GROUP_IDX_CONTEXTS: usize = 6; +pub const COMP_INTER_CONTEXTS: usize = 5; +pub const COMP_NEWMV_CTXS: usize = 5; +pub const COMP_REF_TYPE_CONTEXTS: usize = 5; +pub const CP_BT_2020: usize = 9; +pub const CP_BT_470_B_G: usize = 5; +pub const CP_BT_470_M: usize = 4; +pub const CP_BT_601: usize = 6; +pub const CP_BT_709: usize = 1; +pub const CP_EBU_3213: usize = 22; +pub const CP_GENERIC_FILM: usize = 8; +pub const CP_SMPTE_240: usize = 7; +pub const CP_SMPTE_431: usize = 11; +pub const CP_SMPTE_432: usize = 12; +pub const CP_UNSPECIFIED: usize = 2; +pub const CP_XYZ: usize = 10; +pub const CSP_COLOCATED: usize = 2; +pub const CSP_RESERVED: usize = 3; +pub const CSP_UNKNOWN: usize = 0; +pub const CSP_VERTICAL: usize = 1; +pub const D113_PRED: usize = 5; +pub const D135_PRED: usize = 4; +pub const D157_PRED: usize = 6; +pub const D203_PRED: usize = 7; +pub const D45_PRED: usize = 3; +pub const D67_PRED: usize = 8; +pub const DCT_ADST: usize = 2; +pub const DCT_DCT: usize = 0; +pub const DCT_FLIPADST: usize = 5; +pub const DC_PRED: usize = 0; +pub const DC_SIGN_CONTEXTS: usize = 3; +pub const DELTA_LF_SMALL: usize = 3; +pub const DELTA_Q_SMALL: usize = 3; +pub const DIRECTIONAL_MODES: usize = 8; +pub const DIV_LUT_BITS: usize = 8; +pub const DIV_LUT_NUM: usize = 257; +pub const DIV_LUT_PREC_BITS: usize = 14; +pub const DRL_MODE_CONTEXTS: usize = 3; +pub const EC_MIN_PROB: usize = 4; +pub const EC_PROB_SHIFT: usize = 6; +pub const EIGHTTAP: usize = 0; +pub const EIGHTTAP_SHARP: usize = 2; +pub const EIGHTTAP_SMOOTH: usize = 1; +pub const EOB_COEF_CONTEXTS: usize = 9; +pub const FILTER_BITS: usize = 7; +pub const FILTER_D157_PRED: usize = 3; +pub const FILTER_DC_PRED: usize = 0; +pub const FILTER_H_PRED: usize = 2; +pub const FILTER_PAETH_PRED: usize = 4; +pub const FILTER_V_PRED: usize = 1; +pub const FLIPADST_ADST: usize = 8; +pub const FLIPADST_DCT: usize = 4; +pub const FLIPADST_FLIPADST: usize = 6; +pub const FRAME_LF_COUNT: usize = 4; +pub const FWD_REFS: usize = 4; +pub const GLOBALMV: usize = 16; +pub const GLOBAL_GLOBALMV: usize = 24; +pub const GM_ABS_ALPHA_BITS: usize = 12; +pub const GM_ABS_TRANS_BITS: usize = 12; +pub const GM_ABS_TRANS_ONLY_BITS: usize = 9; +pub const GM_ALPHA_PREC_BITS: usize = 15; +pub const GM_TRANS_ONLY_PREC_BITS: usize = 3; +pub const GM_TRANS_PREC_BITS: usize = 6; +pub const GOLDEN_FRAME: usize = 4; +pub const H_ADST: usize = 13; +pub const H_DCT: usize = 11; +pub const H_FLIPADST: usize = 15; +pub const H_PRED: usize = 2; +pub const IDENTITY: usize = 0; +pub const IDTX: usize = 9; +pub const II_DC_PRED: usize = 0; +pub const II_H_PRED: usize = 2; +pub const II_SMOOTH_PRED: usize = 3; +pub const II_V_PRED: usize = 1; +pub const INTERINTRA_MODES: usize = 4; +pub const INTERP_FILTERS: usize = 3; +pub const INTERP_FILTER_CONTEXTS: usize = 16; +pub const INTER_FRAME: usize = 1; +pub const INTRABC_DELAY_PIXELS: usize = 256; +pub const INTRABC_DELAY_SB64: usize = 4; +pub const INTRA_EDGE_KERNELS: usize = 3; +pub const INTRA_EDGE_TAPS: usize = 5; +pub const INTRA_FILTER_MODES: usize = 5; +pub const INTRA_FILTER_SCALE_BITS: usize = 4; +pub const INTRA_FRAME: usize = 0; +pub const INTRA_MODES: usize = 13; +pub const INTRA_MODE_CONTEXTS: usize = 5; +pub const INTRA_ONLY_FRAME: usize = 2; +pub const IS_INTER_CONTEXTS: usize = 4; +pub const KEY_FRAME: usize = 0; +pub const LAST2_FRAME: usize = 2; +pub const LAST3_FRAME: usize = 3; +pub const LAST_FRAME: usize = 1; +pub const LEAST_SQUARES_SAMPLES_MAX: usize = 8; +pub const LEVEL_CONTEXTS: usize = 21; +pub const LOCALWARP: usize = 2; +pub const LS_MV_MAX: usize = 256; +pub const MASK_MASTER_SIZE: usize = 64; +pub const MAX_ANGLE_DELTA: usize = 3; +pub const MAX_FRAME_DISTANCE: usize = 31; +pub const MAX_LOOP_FILTER: usize = 63; +pub const MAX_OFFSET_HEIGHT: usize = 0; +pub const MAX_OFFSET_WIDTH: usize = 8; +pub const MAX_REF_MV_STACK_SIZE: usize = 8; +pub const MAX_SB_SIZE: usize = 128; +pub const MAX_SEGMENTS: usize = 8; +pub const MAX_TILE_AREA: usize = 9437184; +pub const MAX_TILE_COLS: usize = 64; +pub const MAX_TILE_ROWS: usize = 64; +pub const MAX_TILE_WIDTH: usize = 4096; +pub const MAX_TX_DEPTH: usize = 2; +pub const MAX_VARTX_DEPTH: usize = 2; +pub const MB_MODE_COUNT: usize = 17; +pub const MC_BT_2020_CL: usize = 10; +pub const MC_BT_2020_NCL: usize = 9; +pub const MC_BT_470_B_G: usize = 5; +pub const MC_BT_601: usize = 6; +pub const MC_BT_709: usize = 1; +pub const MC_CHROMAT_CL: usize = 13; +pub const MC_CHROMAT_NCL: usize = 12; +pub const MC_FCC: usize = 4; +pub const MC_ICTCP: usize = 14; +pub const MC_IDENTITY: usize = 0; +pub const MC_RESERVED_3: usize = 3; +pub const MC_SMPTE_2085: usize = 11; +pub const MC_SMPTE_240: usize = 7; +pub const MC_SMPTE_YCGCO: usize = 8; +pub const MC_UNSPECIFIED: usize = 2; +pub const METADATA_TYPE_HDR_CLL: usize = 1; +pub const METADATA_TYPE_HDR_MDCV: usize = 2; +pub const METADATA_TYPE_ITUT_T35: usize = 4; +pub const METADATA_TYPE_SCALABILITY: usize = 3; +pub const METADATA_TYPE_TIMECODE: usize = 5; +pub const MFMV_STACK_SIZE: usize = 3; +pub const MI_SIZE: usize = 4; +pub const MI_SIZE_LOG2: usize = 2; +pub const MOTION_MODES: usize = 3; +pub const MV_BORDER: usize = 128; +pub const MV_CLASSES: usize = 11; +pub const MV_CLASS_0: usize = 0; +pub const MV_CLASS_1: usize = 1; +pub const MV_CLASS_10: usize = 10; +pub const MV_CLASS_2: usize = 2; +pub const MV_CLASS_3: usize = 3; +pub const MV_CLASS_4: usize = 4; +pub const MV_CLASS_5: usize = 5; +pub const MV_CLASS_6: usize = 6; +pub const MV_CLASS_7: usize = 7; +pub const MV_CLASS_8: usize = 8; +pub const MV_CLASS_9: usize = 9; +pub const MV_CONTEXTS: usize = 2; +pub const MV_INTRABC_CONTEXT: usize = 1; +pub const MV_JOINTS: usize = 4; +pub const MV_JOINT_HNZVNZ: usize = 3; +pub const MV_JOINT_HNZVZ: usize = 1; +pub const MV_JOINT_HZVNZ: usize = 2; +pub const MV_JOINT_ZERO: usize = 0; +pub const MV_OFFSET_BITS: usize = 10; +pub const NEARESTMV: usize = 14; +pub const NEAREST_NEARESTMV: usize = 18; +pub const NEAREST_NEWMV: usize = 20; +pub const NEARMV: usize = 15; +pub const NEAR_NEARMV: usize = 19; +pub const NEAR_NEWMV: usize = 22; +pub const NEWMV: usize = 17; +pub const NEW_MV_CONTEXTS: usize = 6; +pub const NEW_NEARESTMV: usize = 21; +pub const NEW_NEARMV: usize = 23; +pub const NEW_NEWMV: usize = 25; +pub const NUM_BASE_LEVELS: usize = 2; +pub const NUM_REF_FRAMES: usize = 8; +pub const OBMC: usize = 1; +pub const OBU_FRAME: usize = 6; +pub const OBU_FRAME_HEADER: usize = 3; +pub const OBU_METADATA: usize = 5; +pub const OBU_PADDING: usize = 15; +pub const OBU_REDUNDANT_FRAME_HEADER: usize = 7; +pub const OBU_SEQUENCE_HEADER: usize = 1; +pub const OBU_TEMPORAL_DELIMITER: usize = 2; +pub const OBU_TILE_GROUP: usize = 4; +pub const OBU_TILE_LIST: usize = 8; +pub const ONLY_4X4: usize = 0; +pub const PAETH_PRED: usize = 12; +pub const PALETTE_BLOCK_SIZE_CONTEXTS: usize = 7; +pub const PALETTE_COLORS: usize = 8; +pub const PALETTE_COLOR_CONTEXTS: usize = 5; +pub const PALETTE_MAX_COLOR_CONTEXT_HASH: usize = 8; +pub const PALETTE_NUM_NEIGHBORS: usize = 3; +pub const PALETTE_SIZES: usize = 7; +pub const PALETTE_UV_MODE_CONTEXTS: usize = 2; +pub const PALETTE_Y_MODE_CONTEXTS: usize = 3; +pub const PARTITION_CONTEXTS: usize = 4; +pub const PARTITION_HORZ: usize = 1; +pub const PARTITION_HORZ_4: usize = 8; +pub const PARTITION_HORZ_A: usize = 4; +pub const PARTITION_HORZ_B: usize = 5; +pub const PARTITION_NONE: usize = 0; +pub const PARTITION_SPLIT: usize = 3; +pub const PARTITION_VERT: usize = 2; +pub const PARTITION_VERT_4: usize = 9; +pub const PARTITION_VERT_A: usize = 6; +pub const PARTITION_VERT_B: usize = 7; +pub const PLANE_TYPES: usize = 2; +pub const PRIMARY_REF_NONE: usize = 7; +pub const QM_TOTAL_SIZE: usize = 3344; +pub const REFMVS_LIMIT: usize = 4095; +pub const REFS_PER_FRAME: usize = 7; +pub const REF_CAT_LEVEL: usize = 640; +pub const REF_CONTEXTS: usize = 3; +pub const REF_MV_CONTEXTS: usize = 6; +pub const REF_SCALE_SHIFT: usize = 14; +pub const RESTORATION_TILESIZE_MAX: usize = 256; +pub const RESTORE_NONE: usize = 0; +pub const RESTORE_SGRPROJ: usize = 2; +pub const RESTORE_SWITCHABLE: usize = 3; +pub const RESTORE_WIENER: usize = 1; +pub const ROTZOOM: usize = 2; +pub const SCALABILITY_L1T2: usize = 0; +pub const SCALABILITY_L1T3: usize = 1; +pub const SCALABILITY_L2T1: usize = 2; +pub const SCALABILITY_L2T2: usize = 3; +pub const SCALABILITY_L2T3: usize = 4; +pub const SCALABILITY_L3T1: usize = 15; +pub const SCALABILITY_L3T2: usize = 16; +pub const SCALABILITY_L3T2_KEY: usize = 21; +pub const SCALABILITY_L3T2_KEY_SHIFT: usize = 25; +pub const SCALABILITY_L3T3: usize = 17; +pub const SCALABILITY_L3T3_KEY: usize = 22; +pub const SCALABILITY_L3T3_KEY_SHIFT: usize = 26; +pub const SCALABILITY_L4T5_KEY: usize = 23; +pub const SCALABILITY_L4T5_KEY_SHIFT: usize = 27; +pub const SCALABILITY_L4T7_KEY: usize = 24; +pub const SCALABILITY_L4T7_KEY_SHIFT: usize = 28; +pub const SCALABILITY_S2T1: usize = 5; +pub const SCALABILITY_S2T2: usize = 6; +pub const SCALABILITY_S2T3: usize = 7; +pub const SCALABILITY_S3T1: usize = 18; +pub const SCALABILITY_S3T2: usize = 19; +pub const SCALABILITY_S3T3: usize = 20; +pub const SCALABILITY_SS: usize = 14; +pub const SCALE_SUBPEL_BITS: usize = 10; +pub const SEGMENT_ID_CONTEXTS: usize = 3; +pub const SEGMENT_ID_PREDICTED_CONTEXTS: usize = 3; +pub const SEG_LVL_ALT_LF_Y_V: usize = 1; +pub const SEG_LVL_ALT_Q: usize = 0; +pub const SEG_LVL_GLOBALMV: usize = 7; +pub const SEG_LVL_MAX: usize = 8; +pub const SEG_LVL_REF_FRAME: usize = 5; +pub const SEG_LVL_SKIP: usize = 6; +pub const SELECT_INTEGER_MV: usize = 2; +pub const SELECT_SCREEN_CONTENT_TOOLS: usize = 2; +pub const SGRPROJ_MTABLE_BITS: usize = 20; +pub const SGRPROJ_PARAMS_BITS: usize = 4; +pub const SGRPROJ_PRJ_BITS: usize = 7; +pub const SGRPROJ_PRJ_SUBEXP_K: usize = 4; +pub const SGRPROJ_RECIP_BITS: usize = 12; +pub const SGRPROJ_RST_BITS: usize = 4; +pub const SGRPROJ_SGR_BITS: usize = 8; +pub const SIG_COEF_CONTEXTS: usize = 42; +pub const SIG_COEF_CONTEXTS_2D: usize = 26; +pub const SIG_COEF_CONTEXTS_EOB: usize = 4; +pub const SIG_REF_DIFF_OFFSET_NUM: usize = 5; +pub const SIMPLE: usize = 0; +pub const SINGLE_REFERENCE: usize = 0; +pub const SINGLE_REFS: usize = 7; +pub const SINPI_1_9: usize = 1321; +pub const SINPI_2_9: usize = 2482; +pub const SINPI_3_9: usize = 3344; +pub const SINPI_4_9: usize = 3803; +pub const SKIP_CONTEXTS: usize = 3; +pub const SKIP_MODE_CONTEXTS: usize = 3; +pub const SMOOTH_H_PRED: usize = 11; +pub const SMOOTH_PRED: usize = 9; +pub const SMOOTH_V_PRED: usize = 10; +pub const SUBPEL_BITS: usize = 4; +pub const SUBPEL_MASK: usize = 15; +pub const SUPERRES_DENOM_BITS: usize = 3; +pub const SUPERRES_DENOM_MIN: usize = 9; +pub const SUPERRES_EXTRA_BITS: usize = 8; +pub const SUPERRES_FILTER_BITS: usize = 6; +pub const SUPERRES_FILTER_OFFSET: usize = 3; +pub const SUPERRES_FILTER_SHIFTS: usize = 64; +pub const SUPERRES_FILTER_TAPS: usize = 8; +pub const SUPERRES_NUM: usize = 8; +pub const SUPERRES_SCALE_BITS: usize = 14; +pub const SUPERRES_SCALE_MASK: usize = 16383; +pub const SWITCHABLE: usize = 4; +pub const SWITCH_FRAME: usize = 3; +pub const TC_BT_1361: usize = 12; +pub const TC_BT_2020_10_BIT: usize = 14; +pub const TC_BT_2020_12_BIT: usize = 15; +pub const TC_BT_470_B_G: usize = 5; +pub const TC_BT_470_M: usize = 4; +pub const TC_BT_601: usize = 6; +pub const TC_BT_709: usize = 1; +pub const TC_HLG: usize = 18; +pub const TC_IEC_61966: usize = 11; +pub const TC_LINEAR: usize = 8; +pub const TC_LOG_100: usize = 9; +pub const TC_LOG_100_SQRT10: usize = 10; +pub const TC_RESERVED_0: usize = 0; +pub const TC_RESERVED_3: usize = 3; +pub const TC_SMPTE_2084: usize = 16; +pub const TC_SMPTE_240: usize = 7; +pub const TC_SMPTE_428: usize = 17; +pub const TC_SRGB: usize = 13; +pub const TC_UNSPECIFIED: usize = 2; +pub const TOTAL_REFS_PER_FRAME: usize = 8; +pub const TRANSLATION: usize = 1; +pub const TXB_SKIP_CONTEXTS: usize = 13; +pub const TXFM_PARTITION_CONTEXTS: usize = 21; +pub const TX_16X16: usize = 2; +pub const TX_16X32: usize = 9; +pub const TX_16X4: usize = 14; +pub const TX_16X64: usize = 17; +pub const TX_16X8: usize = 8; +pub const TX_32X16: usize = 10; +pub const TX_32X32: usize = 3; +pub const TX_32X64: usize = 11; +pub const TX_32X8: usize = 16; +pub const TX_4X16: usize = 13; +pub const TX_4X4: usize = 0; +pub const TX_4X8: usize = 5; +pub const TX_64X16: usize = 18; +pub const TX_64X32: usize = 12; +pub const TX_64X64: usize = 4; +pub const TX_8X16: usize = 7; +pub const TX_8X32: usize = 15; +pub const TX_8X4: usize = 6; +pub const TX_8X8: usize = 1; +pub const TX_CLASS_2D: usize = 0; +pub const TX_CLASS_HORIZ: usize = 1; +pub const TX_CLASS_VERT: usize = 2; +pub const TX_MODES: usize = 3; +pub const TX_MODE_LARGEST: usize = 1; +pub const TX_MODE_SELECT: usize = 2; +pub const TX_SET_DCTONLY: usize = 0; +pub const TX_SET_INTER_1: usize = 1; +pub const TX_SET_INTER_2: usize = 2; +pub const TX_SET_INTER_3: usize = 3; +pub const TX_SET_INTRA_1: usize = 1; +pub const TX_SET_INTRA_2: usize = 2; +pub const TX_SET_TYPES_INTER: usize = 4; +pub const TX_SET_TYPES_INTRA: usize = 3; +pub const TX_SIZES: usize = 5; +pub const TX_SIZES_ALL: usize = 19; +pub const TX_SIZE_CONTEXTS: usize = 3; +pub const TX_TYPES: usize = 16; +pub const UNIDIR_COMP_REFERENCE: usize = 0; +pub const UNIDIR_COMP_REFS: usize = 4; +pub const UNIFORM_45: usize = 0; +pub const UNIFORM_45_INV: usize = 1; +pub const UV_CFL_PRED: usize = 13; +pub const UV_INTRA_MODES_CFL_ALLOWED: usize = 14; +pub const UV_INTRA_MODES_CFL_NOT_ALLOWED: usize = 13; +pub const V_ADST: usize = 12; +pub const V_DCT: usize = 10; +pub const V_FLIPADST: usize = 14; +pub const V_PRED: usize = 1; +pub const WARPEDDIFF_PREC_BITS: usize = 10; +pub const WARPEDMODEL_NONDIAGAFFINE_CLAMP: usize = 8192; +pub const WARPEDMODEL_PREC_BITS: usize = 16; +pub const WARPEDMODEL_TRANS_CLAMP: usize = 8388608; +pub const WARPEDPIXEL_PREC_SHIFTS: usize = 64; +pub const WARP_PARAM_REDUCE_BITS: usize = 6; +pub const WEDGE_HORIZONTAL: usize = 0; +pub const WEDGE_OBLIQUE117: usize = 4; +pub const WEDGE_OBLIQUE153: usize = 5; +pub const WEDGE_OBLIQUE27: usize = 2; +pub const WEDGE_OBLIQUE63: usize = 3; +pub const WEDGE_TYPES: usize = 16; +pub const WEDGE_VERTICAL: usize = 1; +pub const WIENER_COEFFS: usize = 3; +pub const ZERO_MV_CONTEXTS: usize = 2; + +pub static AC_QLOOKUP: [[u16; 256]; 3] = [ + [ + 4, 8, 9, 10, 11, 12, 13, 14, 15, 16, 17, 18, 19, 20, 21, 22, 23, 24, 25, 26, 27, 28, 29, 30, 31, 32, 33, 34, 35, 36, 37, 38, 39, 40, 41, 42, + 43, 44, 45, 46, 47, 48, 49, 50, 51, 52, 53, 54, 55, 56, 57, 58, 59, 60, 61, 62, 63, 64, 65, 66, 67, 68, 69, 70, 71, 72, 73, 74, 75, 76, 77, + 78, 79, 80, 81, 82, 83, 84, 85, 86, 87, 88, 89, 90, 91, 92, 93, 94, 95, 96, 97, 98, 99, 100, 101, 102, 104, 106, 108, 110, 112, 114, 116, + 118, 120, 122, 124, 126, 128, 130, 132, 134, 136, 138, 140, 142, 144, 146, 148, 150, 152, 155, 158, 161, 164, 167, 170, 173, 176, 179, 182, + 185, 188, 191, 194, 197, 200, 203, 207, 211, 215, 219, 223, 227, 231, 235, 239, 243, 247, 251, 255, 260, 265, 270, 275, 280, 285, 290, 295, + 300, 305, 311, 317, 323, 329, 335, 341, 347, 353, 359, 366, 373, 380, 387, 394, 401, 408, 416, 424, 432, 440, 448, 456, 465, 474, 483, 492, + 501, 510, 520, 530, 540, 550, 560, 571, 582, 593, 604, 615, 627, 639, 651, 663, 676, 689, 702, 715, 729, 743, 757, 771, 786, 801, 816, 832, + 848, 864, 881, 898, 915, 933, 951, 969, 988, 1007, 1026, 1046, 1066, 1087, 1108, 1129, 1151, 1173, 1196, 1219, 1243, 1267, 1292, 1317, 1343, + 1369, 1396, 1423, 1451, 1479, 1508, 1537, 1567, 1597, 1628, 1660, 1692, 1725, 1759, 1793, 1828, + ], + [ + 4, 9, 11, 13, 16, 18, 21, 24, 27, 30, 33, 37, 40, 44, 48, 51, 55, 59, 63, 67, 71, 75, 79, 83, 88, 92, 96, 100, 105, 109, 114, 118, 122, 127, + 131, 136, 140, 145, 149, 154, 158, 163, 168, 172, 177, 181, 186, 190, 195, 199, 204, 208, 213, 217, 222, 226, 231, 235, 240, 244, 249, 253, + 258, 262, 267, 271, 275, 280, 284, 289, 293, 297, 302, 306, 311, 315, 319, 324, 328, 332, 337, 341, 345, 349, 354, 358, 362, 367, 371, 375, + 379, 384, 388, 392, 396, 401, 409, 417, 425, 433, 441, 449, 458, 466, 474, 482, 490, 498, 506, 514, 523, 531, 539, 547, 555, 563, 571, 579, + 588, 596, 604, 616, 628, 640, 652, 664, 676, 688, 700, 713, 725, 737, 749, 761, 773, 785, 797, 809, 825, 841, 857, 873, 889, 905, 922, 938, + 954, 970, 986, 1002, 1018, 1038, 1058, 1078, 1098, 1118, 1138, 1158, 1178, 1198, 1218, 1242, 1266, 1290, 1314, 1338, 1362, 1386, 1411, 1435, + 1463, 1491, 1519, 1547, 1575, 1603, 1631, 1663, 1695, 1727, 1759, 1791, 1823, 1859, 1895, 1931, 1967, 2003, 2039, 2079, 2119, 2159, 2199, + 2239, 2283, 2327, 2371, 2415, 2459, 2507, 2555, 2603, 2651, 2703, 2755, 2807, 2859, 2915, 2971, 3027, 3083, 3143, 3203, 3263, 3327, 3391, + 3455, 3523, 3591, 3659, 3731, 3803, 3876, 3952, 4028, 4104, 4184, 4264, 4348, 4432, 4516, 4604, 4692, 4784, 4876, 4972, 5068, 5168, 5268, + 5372, 5476, 5584, 5692, 5804, 5916, 6032, 6148, 6268, 6388, 6512, 6640, 6768, 6900, 7036, 7172, 7312, + ], + [ + 4, 13, 19, 27, 35, 44, 54, 64, 75, 87, 99, 112, 126, 139, 154, 168, 183, 199, 214, 230, 247, 263, 280, 297, 314, 331, 349, 366, 384, 402, + 420, 438, 456, 475, 493, 511, 530, 548, 567, 586, 604, 623, 642, 660, 679, 698, 716, 735, 753, 772, 791, 809, 828, 846, 865, 884, 902, 920, + 939, 957, 976, 994, 1012, 1030, 1049, 1067, 1085, 1103, 1121, 1139, 1157, 1175, 1193, 1211, 1229, 1246, 1264, 1282, 1299, 1317, 1335, 1352, + 1370, 1387, 1405, 1422, 1440, 1457, 1474, 1491, 1509, 1526, 1543, 1560, 1577, 1595, 1627, 1660, 1693, 1725, 1758, 1791, 1824, 1856, 1889, + 1922, 1954, 1987, 2020, 2052, 2085, 2118, 2150, 2183, 2216, 2248, 2281, 2313, 2346, 2378, 2411, 2459, 2508, 2556, 2605, 2653, 2701, 2750, + 2798, 2847, 2895, 2943, 2992, 3040, 3088, 3137, 3185, 3234, 3298, 3362, 3426, 3491, 3555, 3619, 3684, 3748, 3812, 3876, 3941, 4005, 4069, + 4149, 4230, 4310, 4390, 4470, 4550, 4631, 4711, 4791, 4871, 4967, 5064, 5160, 5256, 5352, 5448, 5544, 5641, 5737, 5849, 5961, 6073, 6185, + 6297, 6410, 6522, 6650, 6778, 6906, 7034, 7162, 7290, 7435, 7579, 7723, 7867, 8011, 8155, 8315, 8475, 8635, 8795, 8956, 9132, 9308, 9484, + 9660, 9836, 10028, 10220, 10412, 10604, 10812, 11020, 11228, 11437, 11661, 11885, 12109, 12333, 12573, 12813, 13053, 13309, 13565, 13821, + 14093, 14365, 14637, 14925, 15213, 15502, 15806, 16110, 16414, 16734, 17054, 17390, 17726, 18062, 18414, 18766, 19134, 19502, 19886, 20270, + 20670, 21070, 21486, 21902, 22334, 22766, 23214, 23662, 24126, 24590, 25070, 25551, 26047, 26559, 27071, 27599, 28143, 28687, 29247, + ], +]; + +pub static ADJUSTED_TX_SIZE: [u8; 19] = [0, 1, 2, 3, 3, 5, 6, 7, 8, 9, 10, 3, 3, 13, 14, 15, 16, 9, 10]; + +pub static COEFF_BASE_CTX_OFFSET: [[[u8; 5]; 5]; 19] = [ + [[0, 1, 6, 6, 0], [1, 6, 6, 21, 0], [6, 6, 21, 21, 0], [6, 21, 21, 21, 0], [0, 0, 0, 0, 0]], + [[0, 1, 6, 6, 21], [1, 6, 6, 21, 21], [6, 6, 21, 21, 21], [6, 21, 21, 21, 21], [21, 21, 21, 21, 21]], + [[0, 1, 6, 6, 21], [1, 6, 6, 21, 21], [6, 6, 21, 21, 21], [6, 21, 21, 21, 21], [21, 21, 21, 21, 21]], + [[0, 1, 6, 6, 21], [1, 6, 6, 21, 21], [6, 6, 21, 21, 21], [6, 21, 21, 21, 21], [21, 21, 21, 21, 21]], + [[0, 1, 6, 6, 21], [1, 6, 6, 21, 21], [6, 6, 21, 21, 21], [6, 21, 21, 21, 21], [21, 21, 21, 21, 21]], + [[0, 11, 11, 11, 0], [11, 11, 11, 11, 0], [6, 6, 21, 21, 0], [6, 21, 21, 21, 0], [21, 21, 21, 21, 0]], + [[0, 16, 6, 6, 21], [16, 16, 6, 21, 21], [16, 16, 21, 21, 21], [16, 16, 21, 21, 21], [0, 0, 0, 0, 0]], + [[0, 11, 11, 11, 11], [11, 11, 11, 11, 11], [6, 6, 21, 21, 21], [6, 21, 21, 21, 21], [21, 21, 21, 21, 21]], + [[0, 16, 6, 6, 21], [16, 16, 6, 21, 21], [16, 16, 21, 21, 21], [16, 16, 21, 21, 21], [16, 16, 21, 21, 21]], + [[0, 11, 11, 11, 11], [11, 11, 11, 11, 11], [6, 6, 21, 21, 21], [6, 21, 21, 21, 21], [21, 21, 21, 21, 21]], + [[0, 16, 6, 6, 21], [16, 16, 6, 21, 21], [16, 16, 21, 21, 21], [16, 16, 21, 21, 21], [16, 16, 21, 21, 21]], + [[0, 11, 11, 11, 11], [11, 11, 11, 11, 11], [6, 6, 21, 21, 21], [6, 21, 21, 21, 21], [21, 21, 21, 21, 21]], + [[0, 16, 6, 6, 21], [16, 16, 6, 21, 21], [16, 16, 21, 21, 21], [16, 16, 21, 21, 21], [16, 16, 21, 21, 21]], + [[0, 11, 11, 11, 0], [11, 11, 11, 11, 0], [6, 6, 21, 21, 0], [6, 21, 21, 21, 0], [21, 21, 21, 21, 0]], + [[0, 16, 6, 6, 21], [16, 16, 6, 21, 21], [16, 16, 21, 21, 21], [16, 16, 21, 21, 21], [0, 0, 0, 0, 0]], + [[0, 11, 11, 11, 11], [11, 11, 11, 11, 11], [6, 6, 21, 21, 21], [6, 21, 21, 21, 21], [21, 21, 21, 21, 21]], + [[0, 16, 6, 6, 21], [16, 16, 6, 21, 21], [16, 16, 21, 21, 21], [16, 16, 21, 21, 21], [16, 16, 21, 21, 21]], + [[0, 11, 11, 11, 11], [11, 11, 11, 11, 11], [6, 6, 21, 21, 21], [6, 21, 21, 21, 21], [21, 21, 21, 21, 21]], + [[0, 16, 6, 6, 21], [16, 16, 6, 21, 21], [16, 16, 21, 21, 21], [16, 16, 21, 21, 21], [16, 16, 21, 21, 21]], +]; + +pub static COEFF_BASE_POS_CTX_OFFSET: [u8; 3] = [26, 31, 36]; + +pub static COS128_LOOKUP: [u16; 65] = [ + 4096, 4095, 4091, 4085, 4076, 4065, 4052, 4036, 4017, 3996, 3973, 3948, 3920, 3889, 3857, 3822, 3784, 3745, 3703, 3659, 3612, 3564, 3513, 3461, + 3406, 3349, 3290, 3229, 3166, 3102, 3035, 2967, 2896, 2824, 2751, 2675, 2598, 2520, 2440, 2359, 2276, 2191, 2106, 2019, 1931, 1842, 1751, 1660, + 1567, 1474, 1380, 1285, 1189, 1092, 995, 897, 799, 700, 601, 501, 401, 301, 201, 101, 0, +]; + +pub static DC_QLOOKUP: [[u16; 256]; 3] = [ + [ + 4, 8, 8, 9, 10, 11, 12, 12, 13, 14, 15, 16, 17, 18, 19, 19, 20, 21, 22, 23, 24, 25, 26, 26, 27, 28, 29, 30, 31, 32, 32, 33, 34, 35, 36, 37, + 38, 38, 39, 40, 41, 42, 43, 43, 44, 45, 46, 47, 48, 48, 49, 50, 51, 52, 53, 53, 54, 55, 56, 57, 57, 58, 59, 60, 61, 62, 62, 63, 64, 65, 66, + 66, 67, 68, 69, 70, 70, 71, 72, 73, 74, 74, 75, 76, 77, 78, 78, 79, 80, 81, 81, 82, 83, 84, 85, 85, 87, 88, 90, 92, 93, 95, 96, 98, 99, 101, + 102, 104, 105, 107, 108, 110, 111, 113, 114, 116, 117, 118, 120, 121, 123, 125, 127, 129, 131, 134, 136, 138, 140, 142, 144, 146, 148, 150, + 152, 154, 156, 158, 161, 164, 166, 169, 172, 174, 177, 180, 182, 185, 187, 190, 192, 195, 199, 202, 205, 208, 211, 214, 217, 220, 223, 226, + 230, 233, 237, 240, 243, 247, 250, 253, 257, 261, 265, 269, 272, 276, 280, 284, 288, 292, 296, 300, 304, 309, 313, 317, 322, 326, 330, 335, + 340, 344, 349, 354, 359, 364, 369, 374, 379, 384, 389, 395, 400, 406, 411, 417, 423, 429, 435, 441, 447, 454, 461, 467, 475, 482, 489, 497, + 505, 513, 522, 530, 539, 549, 559, 569, 579, 590, 602, 614, 626, 640, 654, 668, 684, 700, 717, 736, 755, 775, 796, 819, 843, 869, 896, 925, + 955, 988, 1022, 1058, 1098, 1139, 1184, 1232, 1282, 1336, + ], + [ + 4, 9, 10, 13, 15, 17, 20, 22, 25, 28, 31, 34, 37, 40, 43, 47, 50, 53, 57, 60, 64, 68, 71, 75, 78, 82, 86, 90, 93, 97, 101, 105, 109, 113, + 116, 120, 124, 128, 132, 136, 140, 143, 147, 151, 155, 159, 163, 166, 170, 174, 178, 182, 185, 189, 193, 197, 200, 204, 208, 212, 215, 219, + 223, 226, 230, 233, 237, 241, 244, 248, 251, 255, 259, 262, 266, 269, 273, 276, 280, 283, 287, 290, 293, 297, 300, 304, 307, 310, 314, 317, + 321, 324, 327, 331, 334, 337, 343, 350, 356, 362, 369, 375, 381, 387, 394, 400, 406, 412, 418, 424, 430, 436, 442, 448, 454, 460, 466, 472, + 478, 484, 490, 499, 507, 516, 525, 533, 542, 550, 559, 567, 576, 584, 592, 601, 609, 617, 625, 634, 644, 655, 666, 676, 687, 698, 708, 718, + 729, 739, 749, 759, 770, 782, 795, 807, 819, 831, 844, 856, 868, 880, 891, 906, 920, 933, 947, 961, 975, 988, 1001, 1015, 1030, 1045, 1061, + 1076, 1090, 1105, 1120, 1137, 1153, 1170, 1186, 1202, 1218, 1236, 1253, 1271, 1288, 1306, 1323, 1342, 1361, 1379, 1398, 1416, 1436, 1456, + 1476, 1496, 1516, 1537, 1559, 1580, 1601, 1624, 1647, 1670, 1692, 1717, 1741, 1766, 1791, 1817, 1844, 1871, 1900, 1929, 1958, 1990, 2021, + 2054, 2088, 2123, 2159, 2197, 2236, 2276, 2319, 2363, 2410, 2458, 2508, 2561, 2616, 2675, 2737, 2802, 2871, 2944, 3020, 3102, 3188, 3280, + 3375, 3478, 3586, 3702, 3823, 3953, 4089, 4236, 4394, 4559, 4737, 4929, 5130, 5347, + ], + [ + 4, 12, 18, 25, 33, 41, 50, 60, 70, 80, 91, 103, 115, 127, 140, 153, 166, 180, 194, 208, 222, 237, 251, 266, 281, 296, 312, 327, 343, 358, + 374, 390, 405, 421, 437, 453, 469, 484, 500, 516, 532, 548, 564, 580, 596, 611, 627, 643, 659, 674, 690, 706, 721, 737, 752, 768, 783, 798, + 814, 829, 844, 859, 874, 889, 904, 919, 934, 949, 964, 978, 993, 1008, 1022, 1037, 1051, 1065, 1080, 1094, 1108, 1122, 1136, 1151, 1165, + 1179, 1192, 1206, 1220, 1234, 1248, 1261, 1275, 1288, 1302, 1315, 1329, 1342, 1368, 1393, 1419, 1444, 1469, 1494, 1519, 1544, 1569, 1594, + 1618, 1643, 1668, 1692, 1717, 1741, 1765, 1789, 1814, 1838, 1862, 1885, 1909, 1933, 1957, 1992, 2027, 2061, 2096, 2130, 2165, 2199, 2233, + 2267, 2300, 2334, 2367, 2400, 2434, 2467, 2499, 2532, 2575, 2618, 2661, 2704, 2746, 2788, 2830, 2872, 2913, 2954, 2995, 3036, 3076, 3127, + 3177, 3226, 3275, 3324, 3373, 3421, 3469, 3517, 3565, 3621, 3677, 3733, 3788, 3843, 3897, 3951, 4005, 4058, 4119, 4181, 4241, 4301, 4361, + 4420, 4479, 4546, 4612, 4677, 4742, 4807, 4871, 4942, 5013, 5083, 5153, 5222, 5291, 5367, 5442, 5517, 5591, 5665, 5745, 5825, 5905, 5984, + 6063, 6149, 6234, 6319, 6404, 6495, 6587, 6678, 6769, 6867, 6966, 7064, 7163, 7269, 7376, 7483, 7599, 7715, 7832, 7958, 8085, 8214, 8352, + 8492, 8635, 8788, 8945, 9104, 9275, 9450, 9639, 9832, 10031, 10245, 10465, 10702, 10946, 11210, 11482, 11776, 12081, 12409, 12750, 13118, + 13501, 13913, 14343, 14807, 15290, 15812, 16356, 16943, 17575, 18237, 18949, 19718, 20521, 21387, + ], +]; + +pub static DEFAULT_ANGLE_DELTA_CDF: [[u16; 8]; 8] = [ + [2180, 5032, 7567, 22776, 26989, 30217, 32768, 0], + [2301, 5608, 8801, 23487, 26974, 30330, 32768, 0], + [3780, 11018, 13699, 19354, 23083, 31286, 32768, 0], + [4581, 11226, 15147, 17138, 21834, 28397, 32768, 0], + [1737, 10927, 14509, 19588, 22745, 28823, 32768, 0], + [2664, 10176, 12485, 17650, 21600, 30495, 32768, 0], + [2240, 11096, 15453, 20341, 22561, 28917, 32768, 0], + [3605, 10428, 12459, 17676, 21244, 30655, 32768, 0], +]; + +pub static DEFAULT_COEFF_BASE_CDF: [[[[[u16; 5]; 42]; 2]; 5]; 4] = [ + [ + [ + [ + [4034, 8930, 12727, 32768, 0], + [18082, 29741, 31877, 32768, 0], + [12596, 26124, 30493, 32768, 0], + [9446, 21118, 27005, 32768, 0], + [6308, 15141, 21279, 32768, 0], + [2463, 6357, 9783, 32768, 0], + [20667, 30546, 31929, 32768, 0], + [13043, 26123, 30134, 32768, 0], + [8151, 18757, 24778, 32768, 0], + [5255, 12839, 18632, 32768, 0], + [2820, 7206, 11161, 32768, 0], + [8192, 16384, 24576, 32768, 0], + [8192, 16384, 24576, 32768, 0], + [8192, 16384, 24576, 32768, 0], + [8192, 16384, 24576, 32768, 0], + [8192, 16384, 24576, 32768, 0], + [8192, 16384, 24576, 32768, 0], + [8192, 16384, 24576, 32768, 0], + [8192, 16384, 24576, 32768, 0], + [8192, 16384, 24576, 32768, 0], + [8192, 16384, 24576, 32768, 0], + [15736, 27553, 30604, 32768, 0], + [11210, 23794, 28787, 32768, 0], + [5947, 13874, 19701, 32768, 0], + [4215, 9323, 13891, 32768, 0], + [2833, 6462, 10059, 32768, 0], + [19605, 30393, 31582, 32768, 0], + [13523, 26252, 30248, 32768, 0], + [8446, 18622, 24512, 32768, 0], + [3818, 10343, 15974, 32768, 0], + [1481, 4117, 6796, 32768, 0], + [22649, 31302, 32190, 32768, 0], + [14829, 27127, 30449, 32768, 0], + [8313, 17702, 23304, 32768, 0], + [3022, 8301, 12786, 32768, 0], + [1536, 4412, 7184, 32768, 0], + [22354, 29774, 31372, 32768, 0], + [14723, 25472, 29214, 32768, 0], + [6673, 13745, 18662, 32768, 0], + [2068, 5766, 9322, 32768, 0], + [8192, 16384, 24576, 32768, 0], + [8192, 16384, 24576, 32768, 0], + ], + [ + [6302, 16444, 21761, 32768, 0], + [23040, 31538, 32475, 32768, 0], + [15196, 28452, 31496, 32768, 0], + [10020, 22946, 28514, 32768, 0], + [6533, 16862, 23501, 32768, 0], + [3538, 9816, 15076, 32768, 0], + [24444, 31875, 32525, 32768, 0], + [15881, 28924, 31635, 32768, 0], + [9922, 22873, 28466, 32768, 0], + [6527, 16966, 23691, 32768, 0], + [4114, 11303, 17220, 32768, 0], + [8192, 16384, 24576, 32768, 0], + [8192, 16384, 24576, 32768, 0], + [8192, 16384, 24576, 32768, 0], + [8192, 16384, 24576, 32768, 0], + [8192, 16384, 24576, 32768, 0], + [8192, 16384, 24576, 32768, 0], + [8192, 16384, 24576, 32768, 0], + [8192, 16384, 24576, 32768, 0], + [8192, 16384, 24576, 32768, 0], + [8192, 16384, 24576, 32768, 0], + [20201, 30770, 32209, 32768, 0], + [14754, 28071, 31258, 32768, 0], + [8378, 20186, 26517, 32768, 0], + [5916, 15299, 21978, 32768, 0], + [4268, 11583, 17901, 32768, 0], + [24361, 32025, 32581, 32768, 0], + [18673, 30105, 31943, 32768, 0], + [10196, 22244, 27576, 32768, 0], + [5495, 14349, 20417, 32768, 0], + [2676, 7415, 11498, 32768, 0], + [24678, 31958, 32585, 32768, 0], + [18629, 29906, 31831, 32768, 0], + [9364, 20724, 26315, 32768, 0], + [4641, 12318, 18094, 32768, 0], + [2758, 7387, 11579, 32768, 0], + [25433, 31842, 32469, 32768, 0], + [18795, 29289, 31411, 32768, 0], + [7644, 17584, 23592, 32768, 0], + [3408, 9014, 15047, 32768, 0], + [8192, 16384, 24576, 32768, 0], + [8192, 16384, 24576, 32768, 0], + ], + ], + [ + [ + [4536, 10072, 14001, 32768, 0], + [25459, 31416, 32206, 32768, 0], + [16605, 28048, 30818, 32768, 0], + [11008, 22857, 27719, 32768, 0], + [6915, 16268, 22315, 32768, 0], + [2625, 6812, 10537, 32768, 0], + [24257, 31788, 32499, 32768, 0], + [16880, 29454, 31879, 32768, 0], + [11958, 25054, 29778, 32768, 0], + [7916, 18718, 25084, 32768, 0], + [3383, 8777, 13446, 32768, 0], + [22720, 31603, 32393, 32768, 0], + [14960, 28125, 31335, 32768, 0], + [9731, 22210, 27928, 32768, 0], + [6304, 15832, 22277, 32768, 0], + [2910, 7818, 12166, 32768, 0], + [20375, 30627, 32131, 32768, 0], + [13904, 27284, 30887, 32768, 0], + [9368, 21558, 27144, 32768, 0], + [5937, 14966, 21119, 32768, 0], + [2667, 7225, 11319, 32768, 0], + [23970, 31470, 32378, 32768, 0], + [17173, 29734, 32018, 32768, 0], + [12795, 25441, 29965, 32768, 0], + [8981, 19680, 25893, 32768, 0], + [4728, 11372, 16902, 32768, 0], + [24287, 31797, 32439, 32768, 0], + [16703, 29145, 31696, 32768, 0], + [10833, 23554, 28725, 32768, 0], + [6468, 16566, 23057, 32768, 0], + [2415, 6562, 10278, 32768, 0], + [26610, 32395, 32659, 32768, 0], + [18590, 30498, 32117, 32768, 0], + [12420, 25756, 29950, 32768, 0], + [7639, 18746, 24710, 32768, 0], + [3001, 8086, 12347, 32768, 0], + [25076, 32064, 32580, 32768, 0], + [17946, 30128, 32028, 32768, 0], + [12024, 24985, 29378, 32768, 0], + [7517, 18390, 24304, 32768, 0], + [3243, 8781, 13331, 32768, 0], + [8192, 16384, 24576, 32768, 0], + ], + [ + [6037, 16771, 21957, 32768, 0], + [24774, 31704, 32426, 32768, 0], + [16830, 28589, 31056, 32768, 0], + [10602, 22828, 27760, 32768, 0], + [6733, 16829, 23071, 32768, 0], + [3250, 8914, 13556, 32768, 0], + [25582, 32220, 32668, 32768, 0], + [18659, 30342, 32223, 32768, 0], + [12546, 26149, 30515, 32768, 0], + [8420, 20451, 26801, 32768, 0], + [4636, 12420, 18344, 32768, 0], + [27581, 32362, 32639, 32768, 0], + [18987, 30083, 31978, 32768, 0], + [11327, 24248, 29084, 32768, 0], + [7264, 17719, 24120, 32768, 0], + [3995, 10768, 16169, 32768, 0], + [25893, 31831, 32487, 32768, 0], + [16577, 28587, 31379, 32768, 0], + [10189, 22748, 28182, 32768, 0], + [6832, 17094, 23556, 32768, 0], + [3708, 10110, 15334, 32768, 0], + [25904, 32282, 32656, 32768, 0], + [19721, 30792, 32276, 32768, 0], + [12819, 26243, 30411, 32768, 0], + [8572, 20614, 26891, 32768, 0], + [5364, 14059, 20467, 32768, 0], + [26580, 32438, 32677, 32768, 0], + [20852, 31225, 32340, 32768, 0], + [12435, 25700, 29967, 32768, 0], + [8691, 20825, 26976, 32768, 0], + [4446, 12209, 17269, 32768, 0], + [27350, 32429, 32696, 32768, 0], + [21372, 30977, 32272, 32768, 0], + [12673, 25270, 29853, 32768, 0], + [9208, 20925, 26640, 32768, 0], + [5018, 13351, 18732, 32768, 0], + [27351, 32479, 32713, 32768, 0], + [21398, 31209, 32387, 32768, 0], + [12162, 25047, 29842, 32768, 0], + [7896, 18691, 25319, 32768, 0], + [4670, 12882, 18881, 32768, 0], + [8192, 16384, 24576, 32768, 0], + ], + ], + [ + [ + [5487, 10460, 13708, 32768, 0], + [21597, 28303, 30674, 32768, 0], + [11037, 21953, 26476, 32768, 0], + [8147, 17962, 22952, 32768, 0], + [5242, 13061, 18532, 32768, 0], + [1889, 5208, 8182, 32768, 0], + [26774, 32133, 32590, 32768, 0], + [17844, 29564, 31767, 32768, 0], + [11690, 24438, 29171, 32768, 0], + [7542, 18215, 24459, 32768, 0], + [2993, 8050, 12319, 32768, 0], + [28023, 32328, 32591, 32768, 0], + [18651, 30126, 31954, 32768, 0], + [12164, 25146, 29589, 32768, 0], + [7762, 18530, 24771, 32768, 0], + [3492, 9183, 13920, 32768, 0], + [27591, 32008, 32491, 32768, 0], + [17149, 28853, 31510, 32768, 0], + [11485, 24003, 28860, 32768, 0], + [7697, 18086, 24210, 32768, 0], + [3075, 7999, 12218, 32768, 0], + [28268, 32482, 32654, 32768, 0], + [19631, 31051, 32404, 32768, 0], + [13860, 27260, 31020, 32768, 0], + [9605, 21613, 27594, 32768, 0], + [4876, 12162, 17908, 32768, 0], + [27248, 32316, 32576, 32768, 0], + [18955, 30457, 32075, 32768, 0], + [11824, 23997, 28795, 32768, 0], + [7346, 18196, 24647, 32768, 0], + [3403, 9247, 14111, 32768, 0], + [29711, 32655, 32735, 32768, 0], + [21169, 31394, 32417, 32768, 0], + [13487, 27198, 30957, 32768, 0], + [8828, 21683, 27614, 32768, 0], + [4270, 11451, 17038, 32768, 0], + [28708, 32578, 32731, 32768, 0], + [20120, 31241, 32482, 32768, 0], + [13692, 27550, 31321, 32768, 0], + [9418, 22514, 28439, 32768, 0], + [4999, 13283, 19462, 32768, 0], + [8192, 16384, 24576, 32768, 0], + ], + [ + [5673, 14302, 19711, 32768, 0], + [26251, 30701, 31834, 32768, 0], + [12782, 23783, 27803, 32768, 0], + [9127, 20657, 25808, 32768, 0], + [6368, 16208, 21462, 32768, 0], + [2465, 7177, 10822, 32768, 0], + [29961, 32563, 32719, 32768, 0], + [18318, 29891, 31949, 32768, 0], + [11361, 24514, 29357, 32768, 0], + [7900, 19603, 25607, 32768, 0], + [4002, 10590, 15546, 32768, 0], + [29637, 32310, 32595, 32768, 0], + [18296, 29913, 31809, 32768, 0], + [10144, 21515, 26871, 32768, 0], + [5358, 14322, 20394, 32768, 0], + [3067, 8362, 13346, 32768, 0], + [28652, 32470, 32676, 32768, 0], + [17538, 30771, 32209, 32768, 0], + [13924, 26882, 30494, 32768, 0], + [10496, 22837, 27869, 32768, 0], + [7236, 16396, 21621, 32768, 0], + [30743, 32687, 32746, 32768, 0], + [23006, 31676, 32489, 32768, 0], + [14494, 27828, 31120, 32768, 0], + [10174, 22801, 28352, 32768, 0], + [6242, 15281, 21043, 32768, 0], + [25817, 32243, 32720, 32768, 0], + [18618, 31367, 32325, 32768, 0], + [13997, 28318, 31878, 32768, 0], + [12255, 26534, 31383, 32768, 0], + [9561, 21588, 28450, 32768, 0], + [28188, 32635, 32724, 32768, 0], + [22060, 32365, 32728, 32768, 0], + [18102, 30690, 32528, 32768, 0], + [14196, 28864, 31999, 32768, 0], + [12262, 25792, 30865, 32768, 0], + [24176, 32109, 32628, 32768, 0], + [18280, 29681, 31963, 32768, 0], + [10205, 23703, 29664, 32768, 0], + [7889, 20025, 27676, 32768, 0], + [6060, 16743, 23970, 32768, 0], + [8192, 16384, 24576, 32768, 0], + ], + ], + [ + [ + [5141, 7096, 8260, 32768, 0], + [27186, 29022, 29789, 32768, 0], + [6668, 12568, 15682, 32768, 0], + [2172, 6181, 8638, 32768, 0], + [1126, 3379, 4531, 32768, 0], + [443, 1361, 2254, 32768, 0], + [26083, 31153, 32436, 32768, 0], + [13486, 24603, 28483, 32768, 0], + [6508, 14840, 19910, 32768, 0], + [3386, 8800, 13286, 32768, 0], + [1530, 4322, 7054, 32768, 0], + [29639, 32080, 32548, 32768, 0], + [15897, 27552, 30290, 32768, 0], + [8588, 20047, 25383, 32768, 0], + [4889, 13339, 19269, 32768, 0], + [2240, 6871, 10498, 32768, 0], + [28165, 32197, 32517, 32768, 0], + [20735, 30427, 31568, 32768, 0], + [14325, 24671, 27692, 32768, 0], + [5119, 12554, 17805, 32768, 0], + [1810, 5441, 8261, 32768, 0], + [31212, 32724, 32748, 32768, 0], + [23352, 31766, 32545, 32768, 0], + [14669, 27570, 31059, 32768, 0], + [8492, 20894, 27272, 32768, 0], + [3644, 10194, 15204, 32768, 0], + [8192, 16384, 24576, 32768, 0], + [8192, 16384, 24576, 32768, 0], + [8192, 16384, 24576, 32768, 0], + [8192, 16384, 24576, 32768, 0], + [8192, 16384, 24576, 32768, 0], + [8192, 16384, 24576, 32768, 0], + [8192, 16384, 24576, 32768, 0], + [8192, 16384, 24576, 32768, 0], + [8192, 16384, 24576, 32768, 0], + [8192, 16384, 24576, 32768, 0], + [8192, 16384, 24576, 32768, 0], + [8192, 16384, 24576, 32768, 0], + [8192, 16384, 24576, 32768, 0], + [8192, 16384, 24576, 32768, 0], + [8192, 16384, 24576, 32768, 0], + [8192, 16384, 24576, 32768, 0], + ], + [ + [2461, 7013, 9371, 32768, 0], + [24749, 29600, 30986, 32768, 0], + [9466, 19037, 22417, 32768, 0], + [3584, 9280, 14400, 32768, 0], + [1505, 3929, 5433, 32768, 0], + [677, 1500, 2736, 32768, 0], + [23987, 30702, 32117, 32768, 0], + [13554, 24571, 29263, 32768, 0], + [6211, 14556, 21155, 32768, 0], + [3135, 10972, 15625, 32768, 0], + [2435, 7127, 11427, 32768, 0], + [31300, 32532, 32550, 32768, 0], + [14757, 30365, 31954, 32768, 0], + [4405, 11612, 18553, 32768, 0], + [580, 4132, 7322, 32768, 0], + [1695, 10169, 14124, 32768, 0], + [30008, 32282, 32591, 32768, 0], + [19244, 30108, 31748, 32768, 0], + [11180, 24158, 29555, 32768, 0], + [5650, 14972, 19209, 32768, 0], + [2114, 5109, 8456, 32768, 0], + [31856, 32716, 32748, 32768, 0], + [23012, 31664, 32572, 32768, 0], + [13694, 26656, 30636, 32768, 0], + [8142, 19508, 26093, 32768, 0], + [4253, 10955, 16724, 32768, 0], + [8192, 16384, 24576, 32768, 0], + [8192, 16384, 24576, 32768, 0], + [8192, 16384, 24576, 32768, 0], + [8192, 16384, 24576, 32768, 0], + [8192, 16384, 24576, 32768, 0], + [8192, 16384, 24576, 32768, 0], + [8192, 16384, 24576, 32768, 0], + [8192, 16384, 24576, 32768, 0], + [8192, 16384, 24576, 32768, 0], + [8192, 16384, 24576, 32768, 0], + [8192, 16384, 24576, 32768, 0], + [8192, 16384, 24576, 32768, 0], + [8192, 16384, 24576, 32768, 0], + [8192, 16384, 24576, 32768, 0], + [8192, 16384, 24576, 32768, 0], + [8192, 16384, 24576, 32768, 0], + ], + ], + [ + [ + [601, 983, 1311, 32768, 0], + [18725, 23406, 28087, 32768, 0], + [5461, 8192, 10923, 32768, 0], + [3781, 15124, 21425, 32768, 0], + [2587, 7761, 12072, 32768, 0], + [106, 458, 810, 32768, 0], + [22282, 29710, 31894, 32768, 0], + [8508, 20926, 25984, 32768, 0], + [3726, 12713, 18083, 32768, 0], + [1620, 7112, 10893, 32768, 0], + [729, 2236, 3495, 32768, 0], + [30163, 32474, 32684, 32768, 0], + [18304, 30464, 32000, 32768, 0], + [11443, 26526, 29647, 32768, 0], + [6007, 15292, 21299, 32768, 0], + [2234, 6703, 8937, 32768, 0], + [30954, 32177, 32571, 32768, 0], + [17363, 29562, 31076, 32768, 0], + [9686, 22464, 27410, 32768, 0], + [8192, 16384, 21390, 32768, 0], + [1755, 8046, 11264, 32768, 0], + [31168, 32734, 32748, 32768, 0], + [22486, 31441, 32471, 32768, 0], + [12833, 25627, 29738, 32768, 0], + [6980, 17379, 23122, 32768, 0], + [3111, 8887, 13479, 32768, 0], + [8192, 16384, 24576, 32768, 0], + [8192, 16384, 24576, 32768, 0], + [8192, 16384, 24576, 32768, 0], + [8192, 16384, 24576, 32768, 0], + [8192, 16384, 24576, 32768, 0], + [8192, 16384, 24576, 32768, 0], + [8192, 16384, 24576, 32768, 0], + [8192, 16384, 24576, 32768, 0], + [8192, 16384, 24576, 32768, 0], + [8192, 16384, 24576, 32768, 0], + [8192, 16384, 24576, 32768, 0], + [8192, 16384, 24576, 32768, 0], + [8192, 16384, 24576, 32768, 0], + [8192, 16384, 24576, 32768, 0], + [8192, 16384, 24576, 32768, 0], + [8192, 16384, 24576, 32768, 0], + ], + [ + [8192, 16384, 24576, 32768, 0], + [8192, 16384, 24576, 32768, 0], + [8192, 16384, 24576, 32768, 0], + [8192, 16384, 24576, 32768, 0], + [8192, 16384, 24576, 32768, 0], + [8192, 16384, 24576, 32768, 0], + [8192, 16384, 24576, 32768, 0], + [8192, 16384, 24576, 32768, 0], + [8192, 16384, 24576, 32768, 0], + [8192, 16384, 24576, 32768, 0], + [8192, 16384, 24576, 32768, 0], + [8192, 16384, 24576, 32768, 0], + [8192, 16384, 24576, 32768, 0], + [8192, 16384, 24576, 32768, 0], + [8192, 16384, 24576, 32768, 0], + [8192, 16384, 24576, 32768, 0], + [8192, 16384, 24576, 32768, 0], + [8192, 16384, 24576, 32768, 0], + [8192, 16384, 24576, 32768, 0], + [8192, 16384, 24576, 32768, 0], + [8192, 16384, 24576, 32768, 0], + [8192, 16384, 24576, 32768, 0], + [8192, 16384, 24576, 32768, 0], + [8192, 16384, 24576, 32768, 0], + [8192, 16384, 24576, 32768, 0], + [8192, 16384, 24576, 32768, 0], + [8192, 16384, 24576, 32768, 0], + [8192, 16384, 24576, 32768, 0], + [8192, 16384, 24576, 32768, 0], + [8192, 16384, 24576, 32768, 0], + [8192, 16384, 24576, 32768, 0], + [8192, 16384, 24576, 32768, 0], + [8192, 16384, 24576, 32768, 0], + [8192, 16384, 24576, 32768, 0], + [8192, 16384, 24576, 32768, 0], + [8192, 16384, 24576, 32768, 0], + [8192, 16384, 24576, 32768, 0], + [8192, 16384, 24576, 32768, 0], + [8192, 16384, 24576, 32768, 0], + [8192, 16384, 24576, 32768, 0], + [8192, 16384, 24576, 32768, 0], + [8192, 16384, 24576, 32768, 0], + ], + ], + ], + [ + [ + [ + [6041, 11854, 15927, 32768, 0], + [20326, 30905, 32251, 32768, 0], + [14164, 26831, 30725, 32768, 0], + [9760, 20647, 26585, 32768, 0], + [6416, 14953, 21219, 32768, 0], + [2966, 7151, 10891, 32768, 0], + [23567, 31374, 32254, 32768, 0], + [14978, 27416, 30946, 32768, 0], + [9434, 20225, 26254, 32768, 0], + [6658, 14558, 20535, 32768, 0], + [3916, 8677, 12989, 32768, 0], + [8192, 16384, 24576, 32768, 0], + [8192, 16384, 24576, 32768, 0], + [8192, 16384, 24576, 32768, 0], + [8192, 16384, 24576, 32768, 0], + [8192, 16384, 24576, 32768, 0], + [8192, 16384, 24576, 32768, 0], + [8192, 16384, 24576, 32768, 0], + [8192, 16384, 24576, 32768, 0], + [8192, 16384, 24576, 32768, 0], + [8192, 16384, 24576, 32768, 0], + [18088, 29545, 31587, 32768, 0], + [13062, 25843, 30073, 32768, 0], + [8940, 16827, 22251, 32768, 0], + [7654, 13220, 17973, 32768, 0], + [5733, 10316, 14456, 32768, 0], + [22879, 31388, 32114, 32768, 0], + [15215, 27993, 30955, 32768, 0], + [9397, 19445, 24978, 32768, 0], + [3442, 9813, 15344, 32768, 0], + [1368, 3936, 6532, 32768, 0], + [25494, 32033, 32406, 32768, 0], + [16772, 27963, 30718, 32768, 0], + [9419, 18165, 23260, 32768, 0], + [2677, 7501, 11797, 32768, 0], + [1516, 4344, 7170, 32768, 0], + [26556, 31454, 32101, 32768, 0], + [17128, 27035, 30108, 32768, 0], + [8324, 15344, 20249, 32768, 0], + [1903, 5696, 9469, 32768, 0], + [8192, 16384, 24576, 32768, 0], + [8192, 16384, 24576, 32768, 0], + ], + [ + [8455, 19003, 24368, 32768, 0], + [23563, 32021, 32604, 32768, 0], + [16237, 29446, 31935, 32768, 0], + [10724, 23999, 29358, 32768, 0], + [6725, 17528, 24416, 32768, 0], + [3927, 10927, 16825, 32768, 0], + [26313, 32288, 32634, 32768, 0], + [17430, 30095, 32095, 32768, 0], + [11116, 24606, 29679, 32768, 0], + [7195, 18384, 25269, 32768, 0], + [4726, 12852, 19315, 32768, 0], + [8192, 16384, 24576, 32768, 0], + [8192, 16384, 24576, 32768, 0], + [8192, 16384, 24576, 32768, 0], + [8192, 16384, 24576, 32768, 0], + [8192, 16384, 24576, 32768, 0], + [8192, 16384, 24576, 32768, 0], + [8192, 16384, 24576, 32768, 0], + [8192, 16384, 24576, 32768, 0], + [8192, 16384, 24576, 32768, 0], + [8192, 16384, 24576, 32768, 0], + [22822, 31648, 32483, 32768, 0], + [16724, 29633, 31929, 32768, 0], + [10261, 23033, 28725, 32768, 0], + [7029, 17840, 24528, 32768, 0], + [4867, 13886, 21502, 32768, 0], + [25298, 31892, 32491, 32768, 0], + [17809, 29330, 31512, 32768, 0], + [9668, 21329, 26579, 32768, 0], + [4774, 12956, 18976, 32768, 0], + [2322, 7030, 11540, 32768, 0], + [25472, 31920, 32543, 32768, 0], + [17957, 29387, 31632, 32768, 0], + [9196, 20593, 26400, 32768, 0], + [4680, 12705, 19202, 32768, 0], + [2917, 8456, 13436, 32768, 0], + [26471, 32059, 32574, 32768, 0], + [18458, 29783, 31909, 32768, 0], + [8400, 19464, 25956, 32768, 0], + [3812, 10973, 17206, 32768, 0], + [8192, 16384, 24576, 32768, 0], + [8192, 16384, 24576, 32768, 0], + ], + ], + [ + [ + [6779, 13743, 17678, 32768, 0], + [24806, 31797, 32457, 32768, 0], + [17616, 29047, 31372, 32768, 0], + [11063, 23175, 28003, 32768, 0], + [6521, 16110, 22324, 32768, 0], + [2764, 7504, 11654, 32768, 0], + [25266, 32367, 32637, 32768, 0], + [19054, 30553, 32175, 32768, 0], + [12139, 25212, 29807, 32768, 0], + [7311, 18162, 24704, 32768, 0], + [3397, 9164, 14074, 32768, 0], + [25988, 32208, 32522, 32768, 0], + [16253, 28912, 31526, 32768, 0], + [9151, 21387, 27372, 32768, 0], + [5688, 14915, 21496, 32768, 0], + [2717, 7627, 12004, 32768, 0], + [23144, 31855, 32443, 32768, 0], + [16070, 28491, 31325, 32768, 0], + [8702, 20467, 26517, 32768, 0], + [5243, 13956, 20367, 32768, 0], + [2621, 7335, 11567, 32768, 0], + [26636, 32340, 32630, 32768, 0], + [19990, 31050, 32341, 32768, 0], + [13243, 26105, 30315, 32768, 0], + [8588, 19521, 25918, 32768, 0], + [4717, 11585, 17304, 32768, 0], + [25844, 32292, 32582, 32768, 0], + [19090, 30635, 32097, 32768, 0], + [11963, 24546, 28939, 32768, 0], + [6218, 16087, 22354, 32768, 0], + [2340, 6608, 10426, 32768, 0], + [28046, 32576, 32694, 32768, 0], + [21178, 31313, 32296, 32768, 0], + [13486, 26184, 29870, 32768, 0], + [7149, 17871, 23723, 32768, 0], + [2833, 7958, 12259, 32768, 0], + [27710, 32528, 32686, 32768, 0], + [20674, 31076, 32268, 32768, 0], + [12413, 24955, 29243, 32768, 0], + [6676, 16927, 23097, 32768, 0], + [2966, 8333, 12919, 32768, 0], + [8192, 16384, 24576, 32768, 0], + ], + [ + [8639, 19339, 24429, 32768, 0], + [24404, 31837, 32525, 32768, 0], + [16997, 29425, 31784, 32768, 0], + [11253, 24234, 29149, 32768, 0], + [6751, 17394, 24028, 32768, 0], + [3490, 9830, 15191, 32768, 0], + [26283, 32471, 32714, 32768, 0], + [19599, 31168, 32442, 32768, 0], + [13146, 26954, 30893, 32768, 0], + [8214, 20588, 26890, 32768, 0], + [4699, 13081, 19300, 32768, 0], + [28212, 32458, 32669, 32768, 0], + [18594, 30316, 32100, 32768, 0], + [11219, 24408, 29234, 32768, 0], + [6865, 17656, 24149, 32768, 0], + [3678, 10362, 16006, 32768, 0], + [25825, 32136, 32616, 32768, 0], + [17313, 29853, 32021, 32768, 0], + [11197, 24471, 29472, 32768, 0], + [6947, 17781, 24405, 32768, 0], + [3768, 10660, 16261, 32768, 0], + [27352, 32500, 32706, 32768, 0], + [20850, 31468, 32469, 32768, 0], + [14021, 27707, 31133, 32768, 0], + [8964, 21748, 27838, 32768, 0], + [5437, 14665, 21187, 32768, 0], + [26304, 32492, 32698, 32768, 0], + [20409, 31380, 32385, 32768, 0], + [13682, 27222, 30632, 32768, 0], + [8974, 21236, 26685, 32768, 0], + [4234, 11665, 16934, 32768, 0], + [26273, 32357, 32711, 32768, 0], + [20672, 31242, 32441, 32768, 0], + [14172, 27254, 30902, 32768, 0], + [9870, 21898, 27275, 32768, 0], + [5164, 13506, 19270, 32768, 0], + [26725, 32459, 32728, 32768, 0], + [20991, 31442, 32527, 32768, 0], + [13071, 26434, 30811, 32768, 0], + [8184, 20090, 26742, 32768, 0], + [4803, 13255, 19895, 32768, 0], + [8192, 16384, 24576, 32768, 0], + ], + ], + [ + [ + [7555, 14942, 18501, 32768, 0], + [24410, 31178, 32287, 32768, 0], + [14394, 26738, 30253, 32768, 0], + [8413, 19554, 25195, 32768, 0], + [4766, 12924, 18785, 32768, 0], + [2029, 5806, 9207, 32768, 0], + [26776, 32364, 32663, 32768, 0], + [18732, 29967, 31931, 32768, 0], + [11005, 23786, 28852, 32768, 0], + [6466, 16909, 23510, 32768, 0], + [3044, 8638, 13419, 32768, 0], + [29208, 32582, 32704, 32768, 0], + [20068, 30857, 32208, 32768, 0], + [12003, 25085, 29595, 32768, 0], + [6947, 17750, 24189, 32768, 0], + [3245, 9103, 14007, 32768, 0], + [27359, 32465, 32669, 32768, 0], + [19421, 30614, 32174, 32768, 0], + [11915, 25010, 29579, 32768, 0], + [6950, 17676, 24074, 32768, 0], + [3007, 8473, 13096, 32768, 0], + [29002, 32676, 32735, 32768, 0], + [22102, 31849, 32576, 32768, 0], + [14408, 28009, 31405, 32768, 0], + [9027, 21679, 27931, 32768, 0], + [4694, 12678, 18748, 32768, 0], + [28216, 32528, 32682, 32768, 0], + [20849, 31264, 32318, 32768, 0], + [12756, 25815, 29751, 32768, 0], + [7565, 18801, 24923, 32768, 0], + [3509, 9533, 14477, 32768, 0], + [30133, 32687, 32739, 32768, 0], + [23063, 31910, 32515, 32768, 0], + [14588, 28051, 31132, 32768, 0], + [9085, 21649, 27457, 32768, 0], + [4261, 11654, 17264, 32768, 0], + [29518, 32691, 32748, 32768, 0], + [22451, 31959, 32613, 32768, 0], + [14864, 28722, 31700, 32768, 0], + [9695, 22964, 28716, 32768, 0], + [4932, 13358, 19502, 32768, 0], + [8192, 16384, 24576, 32768, 0], + ], + [ + [6465, 16958, 21688, 32768, 0], + [25199, 31514, 32360, 32768, 0], + [14774, 27149, 30607, 32768, 0], + [9257, 21438, 26972, 32768, 0], + [5723, 15183, 21882, 32768, 0], + [3150, 8879, 13731, 32768, 0], + [26989, 32262, 32682, 32768, 0], + [17396, 29937, 32085, 32768, 0], + [11387, 24901, 29784, 32768, 0], + [7289, 18821, 25548, 32768, 0], + [3734, 10577, 16086, 32768, 0], + [29728, 32501, 32695, 32768, 0], + [17431, 29701, 31903, 32768, 0], + [9921, 22826, 28300, 32768, 0], + [5896, 15434, 22068, 32768, 0], + [3430, 9646, 14757, 32768, 0], + [28614, 32511, 32705, 32768, 0], + [19364, 30638, 32263, 32768, 0], + [13129, 26254, 30402, 32768, 0], + [8754, 20484, 26440, 32768, 0], + [4378, 11607, 17110, 32768, 0], + [30292, 32671, 32744, 32768, 0], + [21780, 31603, 32501, 32768, 0], + [14314, 27829, 31291, 32768, 0], + [9611, 22327, 28263, 32768, 0], + [4890, 13087, 19065, 32768, 0], + [25862, 32567, 32733, 32768, 0], + [20794, 32050, 32567, 32768, 0], + [17243, 30625, 32254, 32768, 0], + [13283, 27628, 31474, 32768, 0], + [9669, 22532, 28918, 32768, 0], + [27435, 32697, 32748, 32768, 0], + [24922, 32390, 32714, 32768, 0], + [21449, 31504, 32536, 32768, 0], + [16392, 29729, 31832, 32768, 0], + [11692, 24884, 29076, 32768, 0], + [24193, 32290, 32735, 32768, 0], + [18909, 31104, 32563, 32768, 0], + [12236, 26841, 31403, 32768, 0], + [8171, 21840, 29082, 32768, 0], + [7224, 17280, 25275, 32768, 0], + [8192, 16384, 24576, 32768, 0], + ], + ], + [ + [ + [3078, 6839, 9890, 32768, 0], + [13837, 20450, 24479, 32768, 0], + [5914, 14222, 19328, 32768, 0], + [3866, 10267, 14762, 32768, 0], + [2612, 7208, 11042, 32768, 0], + [1067, 2991, 4776, 32768, 0], + [25817, 31646, 32529, 32768, 0], + [13708, 26338, 30385, 32768, 0], + [7328, 18585, 24870, 32768, 0], + [4691, 13080, 19276, 32768, 0], + [1825, 5253, 8352, 32768, 0], + [29386, 32315, 32624, 32768, 0], + [17160, 29001, 31360, 32768, 0], + [9602, 21862, 27396, 32768, 0], + [5915, 15772, 22148, 32768, 0], + [2786, 7779, 12047, 32768, 0], + [29246, 32450, 32663, 32768, 0], + [18696, 29929, 31818, 32768, 0], + [10510, 23369, 28560, 32768, 0], + [6229, 16499, 23125, 32768, 0], + [2608, 7448, 11705, 32768, 0], + [30753, 32710, 32748, 32768, 0], + [21638, 31487, 32503, 32768, 0], + [12937, 26854, 30870, 32768, 0], + [8182, 20596, 26970, 32768, 0], + [3637, 10269, 15497, 32768, 0], + [8192, 16384, 24576, 32768, 0], + [8192, 16384, 24576, 32768, 0], + [8192, 16384, 24576, 32768, 0], + [8192, 16384, 24576, 32768, 0], + [8192, 16384, 24576, 32768, 0], + [8192, 16384, 24576, 32768, 0], + [8192, 16384, 24576, 32768, 0], + [8192, 16384, 24576, 32768, 0], + [8192, 16384, 24576, 32768, 0], + [8192, 16384, 24576, 32768, 0], + [8192, 16384, 24576, 32768, 0], + [8192, 16384, 24576, 32768, 0], + [8192, 16384, 24576, 32768, 0], + [8192, 16384, 24576, 32768, 0], + [8192, 16384, 24576, 32768, 0], + [8192, 16384, 24576, 32768, 0], + ], + [ + [5244, 12150, 16906, 32768, 0], + [20486, 26858, 29701, 32768, 0], + [7756, 18317, 23735, 32768, 0], + [3452, 9256, 13146, 32768, 0], + [2020, 5206, 8229, 32768, 0], + [1801, 4993, 7903, 32768, 0], + [27051, 31858, 32531, 32768, 0], + [15988, 27531, 30619, 32768, 0], + [9188, 21484, 26719, 32768, 0], + [6273, 17186, 23800, 32768, 0], + [3108, 9355, 14764, 32768, 0], + [31076, 32520, 32680, 32768, 0], + [18119, 30037, 31850, 32768, 0], + [10244, 22969, 27472, 32768, 0], + [4692, 14077, 19273, 32768, 0], + [3694, 11677, 17556, 32768, 0], + [30060, 32581, 32720, 32768, 0], + [21011, 30775, 32120, 32768, 0], + [11931, 24820, 29289, 32768, 0], + [7119, 17662, 24356, 32768, 0], + [3833, 10706, 16304, 32768, 0], + [31954, 32731, 32748, 32768, 0], + [23913, 31724, 32489, 32768, 0], + [15520, 28060, 31286, 32768, 0], + [11517, 23008, 28571, 32768, 0], + [6193, 14508, 20629, 32768, 0], + [8192, 16384, 24576, 32768, 0], + [8192, 16384, 24576, 32768, 0], + [8192, 16384, 24576, 32768, 0], + [8192, 16384, 24576, 32768, 0], + [8192, 16384, 24576, 32768, 0], + [8192, 16384, 24576, 32768, 0], + [8192, 16384, 24576, 32768, 0], + [8192, 16384, 24576, 32768, 0], + [8192, 16384, 24576, 32768, 0], + [8192, 16384, 24576, 32768, 0], + [8192, 16384, 24576, 32768, 0], + [8192, 16384, 24576, 32768, 0], + [8192, 16384, 24576, 32768, 0], + [8192, 16384, 24576, 32768, 0], + [8192, 16384, 24576, 32768, 0], + [8192, 16384, 24576, 32768, 0], + ], + ], + [ + [ + [1035, 2807, 4156, 32768, 0], + [13162, 18138, 20939, 32768, 0], + [2696, 6633, 8755, 32768, 0], + [1373, 4161, 6853, 32768, 0], + [1099, 2746, 4716, 32768, 0], + [340, 1021, 1599, 32768, 0], + [22826, 30419, 32135, 32768, 0], + [10395, 21762, 26942, 32768, 0], + [4726, 12407, 17361, 32768, 0], + [2447, 7080, 10593, 32768, 0], + [1227, 3717, 6011, 32768, 0], + [28156, 31424, 31934, 32768, 0], + [16915, 27754, 30373, 32768, 0], + [9148, 20990, 26431, 32768, 0], + [5950, 15515, 21148, 32768, 0], + [2492, 7327, 11526, 32768, 0], + [30602, 32477, 32670, 32768, 0], + [20026, 29955, 31568, 32768, 0], + [11220, 23628, 28105, 32768, 0], + [6652, 17019, 22973, 32768, 0], + [3064, 8536, 13043, 32768, 0], + [31769, 32724, 32748, 32768, 0], + [22230, 30887, 32373, 32768, 0], + [12234, 25079, 29731, 32768, 0], + [7326, 18816, 25353, 32768, 0], + [3933, 10907, 16616, 32768, 0], + [8192, 16384, 24576, 32768, 0], + [8192, 16384, 24576, 32768, 0], + [8192, 16384, 24576, 32768, 0], + [8192, 16384, 24576, 32768, 0], + [8192, 16384, 24576, 32768, 0], + [8192, 16384, 24576, 32768, 0], + [8192, 16384, 24576, 32768, 0], + [8192, 16384, 24576, 32768, 0], + [8192, 16384, 24576, 32768, 0], + [8192, 16384, 24576, 32768, 0], + [8192, 16384, 24576, 32768, 0], + [8192, 16384, 24576, 32768, 0], + [8192, 16384, 24576, 32768, 0], + [8192, 16384, 24576, 32768, 0], + [8192, 16384, 24576, 32768, 0], + [8192, 16384, 24576, 32768, 0], + ], + [ + [8192, 16384, 24576, 32768, 0], + [8192, 16384, 24576, 32768, 0], + [8192, 16384, 24576, 32768, 0], + [8192, 16384, 24576, 32768, 0], + [8192, 16384, 24576, 32768, 0], + [8192, 16384, 24576, 32768, 0], + [8192, 16384, 24576, 32768, 0], + [8192, 16384, 24576, 32768, 0], + [8192, 16384, 24576, 32768, 0], + [8192, 16384, 24576, 32768, 0], + [8192, 16384, 24576, 32768, 0], + [8192, 16384, 24576, 32768, 0], + [8192, 16384, 24576, 32768, 0], + [8192, 16384, 24576, 32768, 0], + [8192, 16384, 24576, 32768, 0], + [8192, 16384, 24576, 32768, 0], + [8192, 16384, 24576, 32768, 0], + [8192, 16384, 24576, 32768, 0], + [8192, 16384, 24576, 32768, 0], + [8192, 16384, 24576, 32768, 0], + [8192, 16384, 24576, 32768, 0], + [8192, 16384, 24576, 32768, 0], + [8192, 16384, 24576, 32768, 0], + [8192, 16384, 24576, 32768, 0], + [8192, 16384, 24576, 32768, 0], + [8192, 16384, 24576, 32768, 0], + [8192, 16384, 24576, 32768, 0], + [8192, 16384, 24576, 32768, 0], + [8192, 16384, 24576, 32768, 0], + [8192, 16384, 24576, 32768, 0], + [8192, 16384, 24576, 32768, 0], + [8192, 16384, 24576, 32768, 0], + [8192, 16384, 24576, 32768, 0], + [8192, 16384, 24576, 32768, 0], + [8192, 16384, 24576, 32768, 0], + [8192, 16384, 24576, 32768, 0], + [8192, 16384, 24576, 32768, 0], + [8192, 16384, 24576, 32768, 0], + [8192, 16384, 24576, 32768, 0], + [8192, 16384, 24576, 32768, 0], + [8192, 16384, 24576, 32768, 0], + [8192, 16384, 24576, 32768, 0], + ], + ], + ], + [ + [ + [ + [8896, 16227, 20630, 32768, 0], + [23629, 31782, 32527, 32768, 0], + [15173, 27755, 31321, 32768, 0], + [10158, 21233, 27382, 32768, 0], + [6420, 14857, 21558, 32768, 0], + [3269, 8155, 12646, 32768, 0], + [24835, 32009, 32496, 32768, 0], + [16509, 28421, 31579, 32768, 0], + [10957, 21514, 27418, 32768, 0], + [7881, 15930, 22096, 32768, 0], + [5388, 10960, 15918, 32768, 0], + [8192, 16384, 24576, 32768, 0], + [8192, 16384, 24576, 32768, 0], + [8192, 16384, 24576, 32768, 0], + [8192, 16384, 24576, 32768, 0], + [8192, 16384, 24576, 32768, 0], + [8192, 16384, 24576, 32768, 0], + [8192, 16384, 24576, 32768, 0], + [8192, 16384, 24576, 32768, 0], + [8192, 16384, 24576, 32768, 0], + [8192, 16384, 24576, 32768, 0], + [20745, 30773, 32093, 32768, 0], + [15200, 27221, 30861, 32768, 0], + [13032, 20873, 25667, 32768, 0], + [12285, 18663, 23494, 32768, 0], + [11563, 17481, 21489, 32768, 0], + [26260, 31982, 32320, 32768, 0], + [15397, 28083, 31100, 32768, 0], + [9742, 19217, 24824, 32768, 0], + [3261, 9629, 15362, 32768, 0], + [1480, 4322, 7499, 32768, 0], + [27599, 32256, 32460, 32768, 0], + [16857, 27659, 30774, 32768, 0], + [9551, 18290, 23748, 32768, 0], + [3052, 8933, 14103, 32768, 0], + [2021, 5910, 9787, 32768, 0], + [29005, 32015, 32392, 32768, 0], + [17677, 27694, 30863, 32768, 0], + [9204, 17356, 23219, 32768, 0], + [2403, 7516, 12814, 32768, 0], + [8192, 16384, 24576, 32768, 0], + [8192, 16384, 24576, 32768, 0], + ], + [ + [10808, 22056, 26896, 32768, 0], + [25739, 32313, 32676, 32768, 0], + [17288, 30203, 32221, 32768, 0], + [11359, 24878, 29896, 32768, 0], + [6949, 17767, 24893, 32768, 0], + [4287, 11796, 18071, 32768, 0], + [27880, 32521, 32705, 32768, 0], + [19038, 31004, 32414, 32768, 0], + [12564, 26345, 30768, 32768, 0], + [8269, 19947, 26779, 32768, 0], + [5674, 14657, 21674, 32768, 0], + [8192, 16384, 24576, 32768, 0], + [8192, 16384, 24576, 32768, 0], + [8192, 16384, 24576, 32768, 0], + [8192, 16384, 24576, 32768, 0], + [8192, 16384, 24576, 32768, 0], + [8192, 16384, 24576, 32768, 0], + [8192, 16384, 24576, 32768, 0], + [8192, 16384, 24576, 32768, 0], + [8192, 16384, 24576, 32768, 0], + [8192, 16384, 24576, 32768, 0], + [25742, 32319, 32671, 32768, 0], + [19557, 31164, 32454, 32768, 0], + [13381, 26381, 30755, 32768, 0], + [10101, 21466, 26722, 32768, 0], + [9209, 19650, 26825, 32768, 0], + [27107, 31917, 32432, 32768, 0], + [18056, 28893, 31203, 32768, 0], + [10200, 21434, 26764, 32768, 0], + [4660, 12913, 19502, 32768, 0], + [2368, 6930, 12504, 32768, 0], + [26960, 32158, 32613, 32768, 0], + [18628, 30005, 32031, 32768, 0], + [10233, 22442, 28232, 32768, 0], + [5471, 14630, 21516, 32768, 0], + [3235, 10767, 17109, 32768, 0], + [27696, 32440, 32692, 32768, 0], + [20032, 31167, 32438, 32768, 0], + [8700, 21341, 28442, 32768, 0], + [5662, 14831, 21795, 32768, 0], + [8192, 16384, 24576, 32768, 0], + [8192, 16384, 24576, 32768, 0], + ], + ], + [ + [ + [9704, 17294, 21132, 32768, 0], + [26762, 32278, 32633, 32768, 0], + [18382, 29620, 31819, 32768, 0], + [10891, 23475, 28723, 32768, 0], + [6358, 16583, 23309, 32768, 0], + [3248, 9118, 14141, 32768, 0], + [27204, 32573, 32699, 32768, 0], + [19818, 30824, 32329, 32768, 0], + [11772, 25120, 30041, 32768, 0], + [6995, 18033, 25039, 32768, 0], + [3752, 10442, 16098, 32768, 0], + [27222, 32256, 32559, 32768, 0], + [15356, 28399, 31475, 32768, 0], + [8821, 20635, 27057, 32768, 0], + [5511, 14404, 21239, 32768, 0], + [2935, 8222, 13051, 32768, 0], + [24875, 32120, 32529, 32768, 0], + [15233, 28265, 31445, 32768, 0], + [8605, 20570, 26932, 32768, 0], + [5431, 14413, 21196, 32768, 0], + [2994, 8341, 13223, 32768, 0], + [28201, 32604, 32700, 32768, 0], + [21041, 31446, 32456, 32768, 0], + [13221, 26213, 30475, 32768, 0], + [8255, 19385, 26037, 32768, 0], + [4930, 12585, 18830, 32768, 0], + [28768, 32448, 32627, 32768, 0], + [19705, 30561, 32021, 32768, 0], + [11572, 23589, 28220, 32768, 0], + [5532, 15034, 21446, 32768, 0], + [2460, 7150, 11456, 32768, 0], + [29874, 32619, 32699, 32768, 0], + [21621, 31071, 32201, 32768, 0], + [12511, 24747, 28992, 32768, 0], + [6281, 16395, 22748, 32768, 0], + [3246, 9278, 14497, 32768, 0], + [29715, 32625, 32712, 32768, 0], + [20958, 31011, 32283, 32768, 0], + [11233, 23671, 28806, 32768, 0], + [6012, 16128, 22868, 32768, 0], + [3427, 9851, 15414, 32768, 0], + [8192, 16384, 24576, 32768, 0], + ], + [ + [11016, 22111, 26794, 32768, 0], + [25946, 32357, 32677, 32768, 0], + [17890, 30452, 32252, 32768, 0], + [11678, 25142, 29816, 32768, 0], + [6720, 17534, 24584, 32768, 0], + [4230, 11665, 17820, 32768, 0], + [28400, 32623, 32747, 32768, 0], + [21164, 31668, 32575, 32768, 0], + [13572, 27388, 31182, 32768, 0], + [8234, 20750, 27358, 32768, 0], + [5065, 14055, 20897, 32768, 0], + [28981, 32547, 32705, 32768, 0], + [18681, 30543, 32239, 32768, 0], + [10919, 24075, 29286, 32768, 0], + [6431, 17199, 24077, 32768, 0], + [3819, 10464, 16618, 32768, 0], + [26870, 32467, 32693, 32768, 0], + [19041, 30831, 32347, 32768, 0], + [11794, 25211, 30016, 32768, 0], + [6888, 18019, 24970, 32768, 0], + [4370, 12363, 18992, 32768, 0], + [29578, 32670, 32744, 32768, 0], + [23159, 32007, 32613, 32768, 0], + [15315, 28669, 31676, 32768, 0], + [9298, 22607, 28782, 32768, 0], + [6144, 15913, 22968, 32768, 0], + [28110, 32499, 32669, 32768, 0], + [21574, 30937, 32015, 32768, 0], + [12759, 24818, 28727, 32768, 0], + [6545, 16761, 23042, 32768, 0], + [3649, 10597, 16833, 32768, 0], + [28163, 32552, 32728, 32768, 0], + [22101, 31469, 32464, 32768, 0], + [13160, 25472, 30143, 32768, 0], + [7303, 18684, 25468, 32768, 0], + [5241, 13975, 20955, 32768, 0], + [28400, 32631, 32744, 32768, 0], + [22104, 31793, 32603, 32768, 0], + [13557, 26571, 30846, 32768, 0], + [7749, 19861, 26675, 32768, 0], + [4873, 14030, 21234, 32768, 0], + [8192, 16384, 24576, 32768, 0], + ], + ], + [ + [ + [9800, 17635, 21073, 32768, 0], + [26153, 31885, 32527, 32768, 0], + [15038, 27852, 31006, 32768, 0], + [8718, 20564, 26486, 32768, 0], + [5128, 14076, 20514, 32768, 0], + [2636, 7566, 11925, 32768, 0], + [27551, 32504, 32701, 32768, 0], + [18310, 30054, 32100, 32768, 0], + [10211, 23420, 29082, 32768, 0], + [6222, 16876, 23916, 32768, 0], + [3462, 9954, 15498, 32768, 0], + [29991, 32633, 32721, 32768, 0], + [19883, 30751, 32201, 32768, 0], + [11141, 24184, 29285, 32768, 0], + [6420, 16940, 23774, 32768, 0], + [3392, 9753, 15118, 32768, 0], + [28465, 32616, 32712, 32768, 0], + [19850, 30702, 32244, 32768, 0], + [10983, 24024, 29223, 32768, 0], + [6294, 16770, 23582, 32768, 0], + [3244, 9283, 14509, 32768, 0], + [30023, 32717, 32748, 32768, 0], + [22940, 32032, 32626, 32768, 0], + [14282, 27928, 31473, 32768, 0], + [8562, 21327, 27914, 32768, 0], + [4846, 13393, 19919, 32768, 0], + [29981, 32590, 32695, 32768, 0], + [20465, 30963, 32166, 32768, 0], + [11479, 23579, 28195, 32768, 0], + [5916, 15648, 22073, 32768, 0], + [3031, 8605, 13398, 32768, 0], + [31146, 32691, 32739, 32768, 0], + [23106, 31724, 32444, 32768, 0], + [13783, 26738, 30439, 32768, 0], + [7852, 19468, 25807, 32768, 0], + [3860, 11124, 16853, 32768, 0], + [31014, 32724, 32748, 32768, 0], + [23629, 32109, 32628, 32768, 0], + [14747, 28115, 31403, 32768, 0], + [8545, 21242, 27478, 32768, 0], + [4574, 12781, 19067, 32768, 0], + [8192, 16384, 24576, 32768, 0], + ], + [ + [9185, 19694, 24688, 32768, 0], + [26081, 31985, 32621, 32768, 0], + [16015, 29000, 31787, 32768, 0], + [10542, 23690, 29206, 32768, 0], + [6732, 17945, 24677, 32768, 0], + [3916, 11039, 16722, 32768, 0], + [28224, 32566, 32744, 32768, 0], + [19100, 31138, 32485, 32768, 0], + [12528, 26620, 30879, 32768, 0], + [7741, 20277, 26885, 32768, 0], + [4566, 12845, 18990, 32768, 0], + [29933, 32593, 32718, 32768, 0], + [17670, 30333, 32155, 32768, 0], + [10385, 23600, 28909, 32768, 0], + [6243, 16236, 22407, 32768, 0], + [3976, 10389, 16017, 32768, 0], + [28377, 32561, 32738, 32768, 0], + [19366, 31175, 32482, 32768, 0], + [13327, 27175, 31094, 32768, 0], + [8258, 20769, 27143, 32768, 0], + [4703, 13198, 19527, 32768, 0], + [31086, 32706, 32748, 32768, 0], + [22853, 31902, 32583, 32768, 0], + [14759, 28186, 31419, 32768, 0], + [9284, 22382, 28348, 32768, 0], + [5585, 15192, 21868, 32768, 0], + [28291, 32652, 32746, 32768, 0], + [19849, 32107, 32571, 32768, 0], + [14834, 26818, 29214, 32768, 0], + [10306, 22594, 28672, 32768, 0], + [6615, 17384, 23384, 32768, 0], + [28947, 32604, 32745, 32768, 0], + [25625, 32289, 32646, 32768, 0], + [18758, 28672, 31403, 32768, 0], + [10017, 23430, 28523, 32768, 0], + [6862, 15269, 22131, 32768, 0], + [23933, 32509, 32739, 32768, 0], + [19927, 31495, 32631, 32768, 0], + [11903, 26023, 30621, 32768, 0], + [7026, 20094, 27252, 32768, 0], + [5998, 18106, 24437, 32768, 0], + [8192, 16384, 24576, 32768, 0], + ], + ], + [ + [ + [4456, 11274, 15533, 32768, 0], + [21219, 29079, 31616, 32768, 0], + [11173, 23774, 28567, 32768, 0], + [7282, 18293, 24263, 32768, 0], + [4890, 13286, 19115, 32768, 0], + [1890, 5508, 8659, 32768, 0], + [26651, 32136, 32647, 32768, 0], + [14630, 28254, 31455, 32768, 0], + [8716, 21287, 27395, 32768, 0], + [5615, 15331, 22008, 32768, 0], + [2675, 7700, 12150, 32768, 0], + [29954, 32526, 32690, 32768, 0], + [16126, 28982, 31633, 32768, 0], + [9030, 21361, 27352, 32768, 0], + [5411, 14793, 21271, 32768, 0], + [2943, 8422, 13163, 32768, 0], + [29539, 32601, 32730, 32768, 0], + [18125, 30385, 32201, 32768, 0], + [10422, 24090, 29468, 32768, 0], + [6468, 17487, 24438, 32768, 0], + [2970, 8653, 13531, 32768, 0], + [30912, 32715, 32748, 32768, 0], + [20666, 31373, 32497, 32768, 0], + [12509, 26640, 30917, 32768, 0], + [8058, 20629, 27290, 32768, 0], + [4231, 12006, 18052, 32768, 0], + [8192, 16384, 24576, 32768, 0], + [8192, 16384, 24576, 32768, 0], + [8192, 16384, 24576, 32768, 0], + [8192, 16384, 24576, 32768, 0], + [8192, 16384, 24576, 32768, 0], + [8192, 16384, 24576, 32768, 0], + [8192, 16384, 24576, 32768, 0], + [8192, 16384, 24576, 32768, 0], + [8192, 16384, 24576, 32768, 0], + [8192, 16384, 24576, 32768, 0], + [8192, 16384, 24576, 32768, 0], + [8192, 16384, 24576, 32768, 0], + [8192, 16384, 24576, 32768, 0], + [8192, 16384, 24576, 32768, 0], + [8192, 16384, 24576, 32768, 0], + [8192, 16384, 24576, 32768, 0], + ], + [ + [10202, 20633, 25484, 32768, 0], + [27336, 31445, 32352, 32768, 0], + [12420, 24384, 28552, 32768, 0], + [7648, 18115, 23856, 32768, 0], + [5662, 14341, 19902, 32768, 0], + [3611, 10328, 15390, 32768, 0], + [30945, 32616, 32736, 32768, 0], + [18682, 30505, 32253, 32768, 0], + [11513, 25336, 30203, 32768, 0], + [7449, 19452, 26148, 32768, 0], + [4482, 13051, 18886, 32768, 0], + [32022, 32690, 32747, 32768, 0], + [18578, 30501, 32146, 32768, 0], + [11249, 23368, 28631, 32768, 0], + [5645, 16958, 22158, 32768, 0], + [5009, 11444, 16637, 32768, 0], + [31357, 32710, 32748, 32768, 0], + [21552, 31494, 32504, 32768, 0], + [13891, 27677, 31340, 32768, 0], + [9051, 22098, 28172, 32768, 0], + [5190, 13377, 19486, 32768, 0], + [32364, 32740, 32748, 32768, 0], + [24839, 31907, 32551, 32768, 0], + [17160, 28779, 31696, 32768, 0], + [12452, 24137, 29602, 32768, 0], + [6165, 15389, 22477, 32768, 0], + [8192, 16384, 24576, 32768, 0], + [8192, 16384, 24576, 32768, 0], + [8192, 16384, 24576, 32768, 0], + [8192, 16384, 24576, 32768, 0], + [8192, 16384, 24576, 32768, 0], + [8192, 16384, 24576, 32768, 0], + [8192, 16384, 24576, 32768, 0], + [8192, 16384, 24576, 32768, 0], + [8192, 16384, 24576, 32768, 0], + [8192, 16384, 24576, 32768, 0], + [8192, 16384, 24576, 32768, 0], + [8192, 16384, 24576, 32768, 0], + [8192, 16384, 24576, 32768, 0], + [8192, 16384, 24576, 32768, 0], + [8192, 16384, 24576, 32768, 0], + [8192, 16384, 24576, 32768, 0], + ], + ], + [ + [ + [2575, 7281, 11077, 32768, 0], + [14002, 20866, 25402, 32768, 0], + [6343, 15056, 19658, 32768, 0], + [4474, 11858, 17041, 32768, 0], + [2865, 8299, 12534, 32768, 0], + [1344, 3949, 6391, 32768, 0], + [24720, 31239, 32459, 32768, 0], + [12585, 25356, 29968, 32768, 0], + [7181, 18246, 24444, 32768, 0], + [5025, 13667, 19885, 32768, 0], + [2521, 7304, 11605, 32768, 0], + [29908, 32252, 32584, 32768, 0], + [17421, 29156, 31575, 32768, 0], + [9889, 22188, 27782, 32768, 0], + [5878, 15647, 22123, 32768, 0], + [2814, 8665, 13323, 32768, 0], + [30183, 32568, 32713, 32768, 0], + [18528, 30195, 32049, 32768, 0], + [10982, 24606, 29657, 32768, 0], + [6957, 18165, 25231, 32768, 0], + [3508, 10118, 15468, 32768, 0], + [31761, 32736, 32748, 32768, 0], + [21041, 31328, 32546, 32768, 0], + [12568, 26732, 31166, 32768, 0], + [8052, 20720, 27733, 32768, 0], + [4336, 12192, 18396, 32768, 0], + [8192, 16384, 24576, 32768, 0], + [8192, 16384, 24576, 32768, 0], + [8192, 16384, 24576, 32768, 0], + [8192, 16384, 24576, 32768, 0], + [8192, 16384, 24576, 32768, 0], + [8192, 16384, 24576, 32768, 0], + [8192, 16384, 24576, 32768, 0], + [8192, 16384, 24576, 32768, 0], + [8192, 16384, 24576, 32768, 0], + [8192, 16384, 24576, 32768, 0], + [8192, 16384, 24576, 32768, 0], + [8192, 16384, 24576, 32768, 0], + [8192, 16384, 24576, 32768, 0], + [8192, 16384, 24576, 32768, 0], + [8192, 16384, 24576, 32768, 0], + [8192, 16384, 24576, 32768, 0], + ], + [ + [8192, 16384, 24576, 32768, 0], + [8192, 16384, 24576, 32768, 0], + [8192, 16384, 24576, 32768, 0], + [8192, 16384, 24576, 32768, 0], + [8192, 16384, 24576, 32768, 0], + [8192, 16384, 24576, 32768, 0], + [8192, 16384, 24576, 32768, 0], + [8192, 16384, 24576, 32768, 0], + [8192, 16384, 24576, 32768, 0], + [8192, 16384, 24576, 32768, 0], + [8192, 16384, 24576, 32768, 0], + [8192, 16384, 24576, 32768, 0], + [8192, 16384, 24576, 32768, 0], + [8192, 16384, 24576, 32768, 0], + [8192, 16384, 24576, 32768, 0], + [8192, 16384, 24576, 32768, 0], + [8192, 16384, 24576, 32768, 0], + [8192, 16384, 24576, 32768, 0], + [8192, 16384, 24576, 32768, 0], + [8192, 16384, 24576, 32768, 0], + [8192, 16384, 24576, 32768, 0], + [8192, 16384, 24576, 32768, 0], + [8192, 16384, 24576, 32768, 0], + [8192, 16384, 24576, 32768, 0], + [8192, 16384, 24576, 32768, 0], + [8192, 16384, 24576, 32768, 0], + [8192, 16384, 24576, 32768, 0], + [8192, 16384, 24576, 32768, 0], + [8192, 16384, 24576, 32768, 0], + [8192, 16384, 24576, 32768, 0], + [8192, 16384, 24576, 32768, 0], + [8192, 16384, 24576, 32768, 0], + [8192, 16384, 24576, 32768, 0], + [8192, 16384, 24576, 32768, 0], + [8192, 16384, 24576, 32768, 0], + [8192, 16384, 24576, 32768, 0], + [8192, 16384, 24576, 32768, 0], + [8192, 16384, 24576, 32768, 0], + [8192, 16384, 24576, 32768, 0], + [8192, 16384, 24576, 32768, 0], + [8192, 16384, 24576, 32768, 0], + [8192, 16384, 24576, 32768, 0], + ], + ], + ], + [ + [ + [ + [7062, 16472, 22319, 32768, 0], + [24538, 32261, 32674, 32768, 0], + [13675, 28041, 31779, 32768, 0], + [8590, 20674, 27631, 32768, 0], + [5685, 14675, 22013, 32768, 0], + [3655, 9898, 15731, 32768, 0], + [26493, 32418, 32658, 32768, 0], + [16376, 29342, 32090, 32768, 0], + [10594, 22649, 28970, 32768, 0], + [8176, 17170, 24303, 32768, 0], + [5605, 12694, 19139, 32768, 0], + [8192, 16384, 24576, 32768, 0], + [8192, 16384, 24576, 32768, 0], + [8192, 16384, 24576, 32768, 0], + [8192, 16384, 24576, 32768, 0], + [8192, 16384, 24576, 32768, 0], + [8192, 16384, 24576, 32768, 0], + [8192, 16384, 24576, 32768, 0], + [8192, 16384, 24576, 32768, 0], + [8192, 16384, 24576, 32768, 0], + [8192, 16384, 24576, 32768, 0], + [23888, 31902, 32542, 32768, 0], + [18612, 29687, 31987, 32768, 0], + [16245, 24852, 29249, 32768, 0], + [15765, 22608, 27559, 32768, 0], + [19895, 24699, 27510, 32768, 0], + [28401, 32212, 32457, 32768, 0], + [15274, 27825, 30980, 32768, 0], + [9364, 18128, 24332, 32768, 0], + [2283, 8193, 15082, 32768, 0], + [1228, 3972, 7881, 32768, 0], + [29455, 32469, 32620, 32768, 0], + [17981, 28245, 31388, 32768, 0], + [10921, 20098, 26240, 32768, 0], + [3743, 11829, 18657, 32768, 0], + [2374, 9593, 15715, 32768, 0], + [31068, 32466, 32635, 32768, 0], + [20321, 29572, 31971, 32768, 0], + [10771, 20255, 27119, 32768, 0], + [2795, 10410, 17361, 32768, 0], + [8192, 16384, 24576, 32768, 0], + [8192, 16384, 24576, 32768, 0], + ], + [ + [9320, 22102, 27840, 32768, 0], + [27057, 32464, 32724, 32768, 0], + [16331, 30268, 32309, 32768, 0], + [10319, 23935, 29720, 32768, 0], + [6189, 16448, 24106, 32768, 0], + [3589, 10884, 18808, 32768, 0], + [29026, 32624, 32748, 32768, 0], + [19226, 31507, 32587, 32768, 0], + [12692, 26921, 31203, 32768, 0], + [7049, 19532, 27635, 32768, 0], + [7727, 15669, 23252, 32768, 0], + [8192, 16384, 24576, 32768, 0], + [8192, 16384, 24576, 32768, 0], + [8192, 16384, 24576, 32768, 0], + [8192, 16384, 24576, 32768, 0], + [8192, 16384, 24576, 32768, 0], + [8192, 16384, 24576, 32768, 0], + [8192, 16384, 24576, 32768, 0], + [8192, 16384, 24576, 32768, 0], + [8192, 16384, 24576, 32768, 0], + [8192, 16384, 24576, 32768, 0], + [28056, 32625, 32748, 32768, 0], + [22383, 32075, 32669, 32768, 0], + [15417, 27098, 31749, 32768, 0], + [18127, 26493, 27190, 32768, 0], + [5461, 16384, 21845, 32768, 0], + [27982, 32091, 32584, 32768, 0], + [19045, 29868, 31972, 32768, 0], + [10397, 22266, 27932, 32768, 0], + [5990, 13697, 21500, 32768, 0], + [1792, 6912, 15104, 32768, 0], + [28198, 32501, 32718, 32768, 0], + [21534, 31521, 32569, 32768, 0], + [11109, 25217, 30017, 32768, 0], + [5671, 15124, 26151, 32768, 0], + [4681, 14043, 18725, 32768, 0], + [28688, 32580, 32741, 32768, 0], + [22576, 32079, 32661, 32768, 0], + [10627, 22141, 28340, 32768, 0], + [9362, 14043, 28087, 32768, 0], + [8192, 16384, 24576, 32768, 0], + [8192, 16384, 24576, 32768, 0], + ], + ], + [ + [ + [7754, 16948, 22142, 32768, 0], + [25670, 32330, 32691, 32768, 0], + [15663, 29225, 31994, 32768, 0], + [9878, 23288, 29158, 32768, 0], + [6419, 17088, 24336, 32768, 0], + [3859, 11003, 17039, 32768, 0], + [27562, 32595, 32725, 32768, 0], + [17575, 30588, 32399, 32768, 0], + [10819, 24838, 30309, 32768, 0], + [7124, 18686, 25916, 32768, 0], + [4479, 12688, 19340, 32768, 0], + [28385, 32476, 32673, 32768, 0], + [15306, 29005, 31938, 32768, 0], + [8937, 21615, 28322, 32768, 0], + [5982, 15603, 22786, 32768, 0], + [3620, 10267, 16136, 32768, 0], + [27280, 32464, 32667, 32768, 0], + [15607, 29160, 32004, 32768, 0], + [9091, 22135, 28740, 32768, 0], + [6232, 16632, 24020, 32768, 0], + [4047, 11377, 17672, 32768, 0], + [29220, 32630, 32718, 32768, 0], + [19650, 31220, 32462, 32768, 0], + [13050, 26312, 30827, 32768, 0], + [9228, 20870, 27468, 32768, 0], + [6146, 15149, 21971, 32768, 0], + [30169, 32481, 32623, 32768, 0], + [17212, 29311, 31554, 32768, 0], + [9911, 21311, 26882, 32768, 0], + [4487, 13314, 20372, 32768, 0], + [2570, 7772, 12889, 32768, 0], + [30924, 32613, 32708, 32768, 0], + [19490, 30206, 32107, 32768, 0], + [11232, 23998, 29276, 32768, 0], + [6769, 17955, 25035, 32768, 0], + [4398, 12623, 19214, 32768, 0], + [30609, 32627, 32722, 32768, 0], + [19370, 30582, 32287, 32768, 0], + [10457, 23619, 29409, 32768, 0], + [6443, 17637, 24834, 32768, 0], + [4645, 13236, 20106, 32768, 0], + [8192, 16384, 24576, 32768, 0], + ], + [ + [8626, 20271, 26216, 32768, 0], + [26707, 32406, 32711, 32768, 0], + [16999, 30329, 32286, 32768, 0], + [11445, 25123, 30286, 32768, 0], + [6411, 18828, 25601, 32768, 0], + [6801, 12458, 20248, 32768, 0], + [29918, 32682, 32748, 32768, 0], + [20649, 31739, 32618, 32768, 0], + [12879, 27773, 31581, 32768, 0], + [7896, 21751, 28244, 32768, 0], + [5260, 14870, 23698, 32768, 0], + [29252, 32593, 32731, 32768, 0], + [17072, 30460, 32294, 32768, 0], + [10653, 24143, 29365, 32768, 0], + [6536, 17490, 23983, 32768, 0], + [4929, 13170, 20085, 32768, 0], + [28137, 32518, 32715, 32768, 0], + [18171, 30784, 32407, 32768, 0], + [11437, 25436, 30459, 32768, 0], + [7252, 18534, 26176, 32768, 0], + [4126, 13353, 20978, 32768, 0], + [31162, 32726, 32748, 32768, 0], + [23017, 32222, 32701, 32768, 0], + [15629, 29233, 32046, 32768, 0], + [9387, 22621, 29480, 32768, 0], + [6922, 17616, 25010, 32768, 0], + [28838, 32265, 32614, 32768, 0], + [19701, 30206, 31920, 32768, 0], + [11214, 22410, 27933, 32768, 0], + [5320, 14177, 23034, 32768, 0], + [5049, 12881, 17827, 32768, 0], + [27484, 32471, 32734, 32768, 0], + [21076, 31526, 32561, 32768, 0], + [12707, 26303, 31211, 32768, 0], + [8169, 21722, 28219, 32768, 0], + [6045, 19406, 27042, 32768, 0], + [27753, 32572, 32745, 32768, 0], + [20832, 31878, 32653, 32768, 0], + [13250, 27356, 31674, 32768, 0], + [7718, 21508, 29858, 32768, 0], + [7209, 18350, 25559, 32768, 0], + [8192, 16384, 24576, 32768, 0], + ], + ], + [ + [ + [7876, 16901, 21741, 32768, 0], + [24001, 31898, 32625, 32768, 0], + [14529, 27959, 31451, 32768, 0], + [8273, 20818, 27258, 32768, 0], + [5278, 14673, 21510, 32768, 0], + [2983, 8843, 14039, 32768, 0], + [28016, 32574, 32732, 32768, 0], + [17471, 30306, 32301, 32768, 0], + [10224, 24063, 29728, 32768, 0], + [6602, 17954, 25052, 32768, 0], + [4002, 11585, 17759, 32768, 0], + [30190, 32634, 32739, 32768, 0], + [17497, 30282, 32270, 32768, 0], + [10229, 23729, 29538, 32768, 0], + [6344, 17211, 24440, 32768, 0], + [3849, 11189, 17108, 32768, 0], + [28570, 32583, 32726, 32768, 0], + [17521, 30161, 32238, 32768, 0], + [10153, 23565, 29378, 32768, 0], + [6455, 17341, 24443, 32768, 0], + [3907, 11042, 17024, 32768, 0], + [30689, 32715, 32748, 32768, 0], + [21546, 31840, 32610, 32768, 0], + [13547, 27581, 31459, 32768, 0], + [8912, 21757, 28309, 32768, 0], + [5548, 15080, 22046, 32768, 0], + [30783, 32540, 32685, 32768, 0], + [17540, 29528, 31668, 32768, 0], + [10160, 21468, 26783, 32768, 0], + [4724, 13393, 20054, 32768, 0], + [2702, 8174, 13102, 32768, 0], + [31648, 32686, 32742, 32768, 0], + [20954, 31094, 32337, 32768, 0], + [12420, 25698, 30179, 32768, 0], + [7304, 19320, 26248, 32768, 0], + [4366, 12261, 18864, 32768, 0], + [31581, 32723, 32748, 32768, 0], + [21373, 31586, 32525, 32768, 0], + [12744, 26625, 30885, 32768, 0], + [7431, 20322, 26950, 32768, 0], + [4692, 13323, 20111, 32768, 0], + [8192, 16384, 24576, 32768, 0], + ], + [ + [7833, 18369, 24095, 32768, 0], + [26650, 32273, 32702, 32768, 0], + [16371, 29961, 32191, 32768, 0], + [11055, 24082, 29629, 32768, 0], + [6892, 18644, 25400, 32768, 0], + [5006, 13057, 19240, 32768, 0], + [29834, 32666, 32748, 32768, 0], + [19577, 31335, 32570, 32768, 0], + [12253, 26509, 31122, 32768, 0], + [7991, 20772, 27711, 32768, 0], + [5677, 15910, 23059, 32768, 0], + [30109, 32532, 32720, 32768, 0], + [16747, 30166, 32252, 32768, 0], + [10134, 23542, 29184, 32768, 0], + [5791, 16176, 23556, 32768, 0], + [4362, 10414, 17284, 32768, 0], + [29492, 32626, 32748, 32768, 0], + [19894, 31402, 32525, 32768, 0], + [12942, 27071, 30869, 32768, 0], + [8346, 21216, 27405, 32768, 0], + [6572, 17087, 23859, 32768, 0], + [32035, 32735, 32748, 32768, 0], + [22957, 31838, 32618, 32768, 0], + [14724, 28572, 31772, 32768, 0], + [10364, 23999, 29553, 32768, 0], + [7004, 18433, 25655, 32768, 0], + [27528, 32277, 32681, 32768, 0], + [16959, 31171, 32096, 32768, 0], + [10486, 23593, 27962, 32768, 0], + [8192, 16384, 23211, 32768, 0], + [8937, 17873, 20852, 32768, 0], + [27715, 32002, 32615, 32768, 0], + [15073, 29491, 31676, 32768, 0], + [11264, 24576, 28672, 32768, 0], + [2341, 18725, 23406, 32768, 0], + [7282, 18204, 25486, 32768, 0], + [28547, 32213, 32657, 32768, 0], + [20788, 29773, 32239, 32768, 0], + [6780, 21469, 30508, 32768, 0], + [5958, 14895, 23831, 32768, 0], + [16384, 21845, 27307, 32768, 0], + [8192, 16384, 24576, 32768, 0], + ], + ], + [ + [ + [5992, 14304, 19765, 32768, 0], + [22612, 31238, 32456, 32768, 0], + [13456, 27162, 31087, 32768, 0], + [8001, 20062, 26504, 32768, 0], + [5168, 14105, 20764, 32768, 0], + [2632, 7771, 12385, 32768, 0], + [27034, 32344, 32709, 32768, 0], + [15850, 29415, 31997, 32768, 0], + [9494, 22776, 28841, 32768, 0], + [6151, 16830, 23969, 32768, 0], + [3461, 10039, 15722, 32768, 0], + [30134, 32569, 32731, 32768, 0], + [15638, 29422, 31945, 32768, 0], + [9150, 21865, 28218, 32768, 0], + [5647, 15719, 22676, 32768, 0], + [3402, 9772, 15477, 32768, 0], + [28530, 32586, 32735, 32768, 0], + [17139, 30298, 32292, 32768, 0], + [10200, 24039, 29685, 32768, 0], + [6419, 17674, 24786, 32768, 0], + [3544, 10225, 15824, 32768, 0], + [31333, 32726, 32748, 32768, 0], + [20618, 31487, 32544, 32768, 0], + [12901, 27217, 31232, 32768, 0], + [8624, 21734, 28171, 32768, 0], + [5104, 14191, 20748, 32768, 0], + [8192, 16384, 24576, 32768, 0], + [8192, 16384, 24576, 32768, 0], + [8192, 16384, 24576, 32768, 0], + [8192, 16384, 24576, 32768, 0], + [8192, 16384, 24576, 32768, 0], + [8192, 16384, 24576, 32768, 0], + [8192, 16384, 24576, 32768, 0], + [8192, 16384, 24576, 32768, 0], + [8192, 16384, 24576, 32768, 0], + [8192, 16384, 24576, 32768, 0], + [8192, 16384, 24576, 32768, 0], + [8192, 16384, 24576, 32768, 0], + [8192, 16384, 24576, 32768, 0], + [8192, 16384, 24576, 32768, 0], + [8192, 16384, 24576, 32768, 0], + [8192, 16384, 24576, 32768, 0], + ], + [ + [11206, 21090, 26561, 32768, 0], + [28759, 32279, 32671, 32768, 0], + [14171, 27952, 31569, 32768, 0], + [9743, 22907, 29141, 32768, 0], + [6871, 17886, 24868, 32768, 0], + [4960, 13152, 19315, 32768, 0], + [31077, 32661, 32748, 32768, 0], + [19400, 31195, 32515, 32768, 0], + [12752, 26858, 31040, 32768, 0], + [8370, 22098, 28591, 32768, 0], + [5457, 15373, 22298, 32768, 0], + [31697, 32706, 32748, 32768, 0], + [17860, 30657, 32333, 32768, 0], + [12510, 24812, 29261, 32768, 0], + [6180, 19124, 24722, 32768, 0], + [5041, 13548, 17959, 32768, 0], + [31552, 32716, 32748, 32768, 0], + [21908, 31769, 32623, 32768, 0], + [14470, 28201, 31565, 32768, 0], + [9493, 22982, 28608, 32768, 0], + [6858, 17240, 24137, 32768, 0], + [32543, 32752, 32756, 32768, 0], + [24286, 32097, 32666, 32768, 0], + [15958, 29217, 32024, 32768, 0], + [10207, 24234, 29958, 32768, 0], + [6929, 18305, 25652, 32768, 0], + [8192, 16384, 24576, 32768, 0], + [8192, 16384, 24576, 32768, 0], + [8192, 16384, 24576, 32768, 0], + [8192, 16384, 24576, 32768, 0], + [8192, 16384, 24576, 32768, 0], + [8192, 16384, 24576, 32768, 0], + [8192, 16384, 24576, 32768, 0], + [8192, 16384, 24576, 32768, 0], + [8192, 16384, 24576, 32768, 0], + [8192, 16384, 24576, 32768, 0], + [8192, 16384, 24576, 32768, 0], + [8192, 16384, 24576, 32768, 0], + [8192, 16384, 24576, 32768, 0], + [8192, 16384, 24576, 32768, 0], + [8192, 16384, 24576, 32768, 0], + [8192, 16384, 24576, 32768, 0], + ], + ], + [ + [ + [4137, 10847, 15682, 32768, 0], + [17824, 27001, 30058, 32768, 0], + [10204, 22796, 28291, 32768, 0], + [6076, 15935, 22125, 32768, 0], + [3852, 10937, 16816, 32768, 0], + [2252, 6324, 10131, 32768, 0], + [25840, 32016, 32662, 32768, 0], + [15109, 28268, 31531, 32768, 0], + [9385, 22231, 28340, 32768, 0], + [6082, 16672, 23479, 32768, 0], + [3318, 9427, 14681, 32768, 0], + [30594, 32574, 32718, 32768, 0], + [16836, 29552, 31859, 32768, 0], + [9556, 22542, 28356, 32768, 0], + [6305, 16725, 23540, 32768, 0], + [3376, 9895, 15184, 32768, 0], + [29383, 32617, 32745, 32768, 0], + [18891, 30809, 32401, 32768, 0], + [11688, 25942, 30687, 32768, 0], + [7468, 19469, 26651, 32768, 0], + [3909, 11358, 17012, 32768, 0], + [31564, 32736, 32748, 32768, 0], + [20906, 31611, 32600, 32768, 0], + [13191, 27621, 31537, 32768, 0], + [8768, 22029, 28676, 32768, 0], + [5079, 14109, 20906, 32768, 0], + [8192, 16384, 24576, 32768, 0], + [8192, 16384, 24576, 32768, 0], + [8192, 16384, 24576, 32768, 0], + [8192, 16384, 24576, 32768, 0], + [8192, 16384, 24576, 32768, 0], + [8192, 16384, 24576, 32768, 0], + [8192, 16384, 24576, 32768, 0], + [8192, 16384, 24576, 32768, 0], + [8192, 16384, 24576, 32768, 0], + [8192, 16384, 24576, 32768, 0], + [8192, 16384, 24576, 32768, 0], + [8192, 16384, 24576, 32768, 0], + [8192, 16384, 24576, 32768, 0], + [8192, 16384, 24576, 32768, 0], + [8192, 16384, 24576, 32768, 0], + [8192, 16384, 24576, 32768, 0], + ], + [ + [8192, 16384, 24576, 32768, 0], + [8192, 16384, 24576, 32768, 0], + [8192, 16384, 24576, 32768, 0], + [8192, 16384, 24576, 32768, 0], + [8192, 16384, 24576, 32768, 0], + [8192, 16384, 24576, 32768, 0], + [8192, 16384, 24576, 32768, 0], + [8192, 16384, 24576, 32768, 0], + [8192, 16384, 24576, 32768, 0], + [8192, 16384, 24576, 32768, 0], + [8192, 16384, 24576, 32768, 0], + [8192, 16384, 24576, 32768, 0], + [8192, 16384, 24576, 32768, 0], + [8192, 16384, 24576, 32768, 0], + [8192, 16384, 24576, 32768, 0], + [8192, 16384, 24576, 32768, 0], + [8192, 16384, 24576, 32768, 0], + [8192, 16384, 24576, 32768, 0], + [8192, 16384, 24576, 32768, 0], + [8192, 16384, 24576, 32768, 0], + [8192, 16384, 24576, 32768, 0], + [8192, 16384, 24576, 32768, 0], + [8192, 16384, 24576, 32768, 0], + [8192, 16384, 24576, 32768, 0], + [8192, 16384, 24576, 32768, 0], + [8192, 16384, 24576, 32768, 0], + [8192, 16384, 24576, 32768, 0], + [8192, 16384, 24576, 32768, 0], + [8192, 16384, 24576, 32768, 0], + [8192, 16384, 24576, 32768, 0], + [8192, 16384, 24576, 32768, 0], + [8192, 16384, 24576, 32768, 0], + [8192, 16384, 24576, 32768, 0], + [8192, 16384, 24576, 32768, 0], + [8192, 16384, 24576, 32768, 0], + [8192, 16384, 24576, 32768, 0], + [8192, 16384, 24576, 32768, 0], + [8192, 16384, 24576, 32768, 0], + [8192, 16384, 24576, 32768, 0], + [8192, 16384, 24576, 32768, 0], + [8192, 16384, 24576, 32768, 0], + [8192, 16384, 24576, 32768, 0], + ], + ], + ], +]; + +pub static DEFAULT_COEFF_BASE_EOB_CDF: [[[[[u16; 4]; 4]; 2]; 5]; 4] = [ + [ + [ + [[17837, 29055, 32768, 0], [29600, 31446, 32768, 0], [30844, 31878, 32768, 0], [24926, 28948, 32768, 0]], + [[21365, 30026, 32768, 0], [30512, 32423, 32768, 0], [31658, 32621, 32768, 0], [29630, 31881, 32768, 0]], + ], + [ + [[5717, 26477, 32768, 0], [30491, 31703, 32768, 0], [31550, 32158, 32768, 0], [29648, 31491, 32768, 0]], + [[12608, 27820, 32768, 0], [30680, 32225, 32768, 0], [30809, 32335, 32768, 0], [31299, 32423, 32768, 0]], + ], + [ + [[1786, 12612, 32768, 0], [30663, 31625, 32768, 0], [32339, 32468, 32768, 0], [31148, 31833, 32768, 0]], + [[18857, 23865, 32768, 0], [31428, 32428, 32768, 0], [31744, 32373, 32768, 0], [31775, 32526, 32768, 0]], + ], + [ + [[1787, 2532, 32768, 0], [30832, 31662, 32768, 0], [31824, 32682, 32768, 0], [32133, 32569, 32768, 0]], + [[13751, 22235, 32768, 0], [32089, 32409, 32768, 0], [27084, 27920, 32768, 0], [29291, 32594, 32768, 0]], + ], + [ + [[1725, 3449, 32768, 0], [31102, 31935, 32768, 0], [32457, 32613, 32768, 0], [32412, 32649, 32768, 0]], + [[10923, 21845, 32768, 0], [10923, 21845, 32768, 0], [10923, 21845, 32768, 0], [10923, 21845, 32768, 0]], + ], + ], + [ + [ + [[17560, 29888, 32768, 0], [29671, 31549, 32768, 0], [31007, 32056, 32768, 0], [27286, 30006, 32768, 0]], + [[26594, 31212, 32768, 0], [31208, 32582, 32768, 0], [31835, 32637, 32768, 0], [30595, 32206, 32768, 0]], + ], + [ + [[15239, 29932, 32768, 0], [31315, 32095, 32768, 0], [32130, 32434, 32768, 0], [30864, 31996, 32768, 0]], + [[26279, 30968, 32768, 0], [31142, 32495, 32768, 0], [31713, 32540, 32768, 0], [31929, 32594, 32768, 0]], + ], + [ + [[2644, 25198, 32768, 0], [32038, 32451, 32768, 0], [32639, 32695, 32768, 0], [32166, 32518, 32768, 0]], + [[17187, 27668, 32768, 0], [31714, 32550, 32768, 0], [32283, 32678, 32768, 0], [31930, 32563, 32768, 0]], + ], + [ + [[1044, 2257, 32768, 0], [30755, 31923, 32768, 0], [32208, 32693, 32768, 0], [32244, 32615, 32768, 0]], + [[21317, 26207, 32768, 0], [29133, 30868, 32768, 0], [29311, 31231, 32768, 0], [29657, 31087, 32768, 0]], + ], + [ + [[478, 1834, 32768, 0], [31005, 31987, 32768, 0], [32317, 32724, 32768, 0], [30865, 32648, 32768, 0]], + [[10923, 21845, 32768, 0], [10923, 21845, 32768, 0], [10923, 21845, 32768, 0], [10923, 21845, 32768, 0]], + ], + ], + [ + [ + [[20092, 30774, 32768, 0], [30695, 32020, 32768, 0], [31131, 32103, 32768, 0], [28666, 30870, 32768, 0]], + [[27258, 31095, 32768, 0], [31804, 32623, 32768, 0], [31763, 32528, 32768, 0], [31438, 32506, 32768, 0]], + ], + [ + [[18049, 30489, 32768, 0], [31706, 32286, 32768, 0], [32163, 32473, 32768, 0], [31550, 32184, 32768, 0]], + [[27116, 30842, 32768, 0], [31971, 32598, 32768, 0], [32088, 32576, 32768, 0], [32067, 32664, 32768, 0]], + ], + [ + [[12854, 29093, 32768, 0], [32272, 32558, 32768, 0], [32667, 32729, 32768, 0], [32306, 32585, 32768, 0]], + [[25476, 30366, 32768, 0], [32169, 32687, 32768, 0], [32479, 32689, 32768, 0], [31673, 32634, 32768, 0]], + ], + [ + [[2809, 19301, 32768, 0], [32205, 32622, 32768, 0], [32338, 32730, 32768, 0], [31786, 32616, 32768, 0]], + [[22737, 29105, 32768, 0], [30810, 32362, 32768, 0], [30014, 32627, 32768, 0], [30528, 32574, 32768, 0]], + ], + [ + [[935, 3382, 32768, 0], [30789, 31909, 32768, 0], [32466, 32756, 32768, 0], [30860, 32513, 32768, 0]], + [[10923, 21845, 32768, 0], [10923, 21845, 32768, 0], [10923, 21845, 32768, 0], [10923, 21845, 32768, 0]], + ], + ], + [ + [ + [[22497, 31198, 32768, 0], [31715, 32495, 32768, 0], [31606, 32337, 32768, 0], [30388, 31990, 32768, 0]], + [[27877, 31584, 32768, 0], [32170, 32728, 32768, 0], [32155, 32688, 32768, 0], [32219, 32702, 32768, 0]], + ], + [ + [[21457, 31043, 32768, 0], [31951, 32483, 32768, 0], [32153, 32562, 32768, 0], [31473, 32215, 32768, 0]], + [[27558, 31151, 32768, 0], [32020, 32640, 32768, 0], [32097, 32575, 32768, 0], [32242, 32719, 32768, 0]], + ], + [ + [[19980, 30591, 32768, 0], [32219, 32597, 32768, 0], [32581, 32706, 32768, 0], [31803, 32287, 32768, 0]], + [[26473, 30507, 32768, 0], [32431, 32723, 32768, 0], [32196, 32611, 32768, 0], [31588, 32528, 32768, 0]], + ], + [ + [[24647, 30463, 32768, 0], [32412, 32695, 32768, 0], [32468, 32720, 32768, 0], [31269, 32523, 32768, 0]], + [[28482, 31505, 32768, 0], [32152, 32701, 32768, 0], [31732, 32598, 32768, 0], [31767, 32712, 32768, 0]], + ], + [ + [[12358, 24977, 32768, 0], [31331, 32385, 32768, 0], [32634, 32756, 32768, 0], [30411, 32548, 32768, 0]], + [[10923, 21845, 32768, 0], [10923, 21845, 32768, 0], [10923, 21845, 32768, 0], [10923, 21845, 32768, 0]], + ], + ], +]; + +pub static DEFAULT_COEFF_BR_CDF: [[[[[u16; 5]; 21]; 2]; 5]; 4] = [ + [ + [ + [ + [14298, 20718, 24174, 32768, 0], + [12536, 19601, 23789, 32768, 0], + [8712, 15051, 19503, 32768, 0], + [6170, 11327, 15434, 32768, 0], + [4742, 8926, 12538, 32768, 0], + [3803, 7317, 10546, 32768, 0], + [1696, 3317, 4871, 32768, 0], + [14392, 19951, 22756, 32768, 0], + [15978, 23218, 26818, 32768, 0], + [12187, 19474, 23889, 32768, 0], + [9176, 15640, 20259, 32768, 0], + [7068, 12655, 17028, 32768, 0], + [5656, 10442, 14472, 32768, 0], + [2580, 4992, 7244, 32768, 0], + [12136, 18049, 21426, 32768, 0], + [13784, 20721, 24481, 32768, 0], + [10836, 17621, 21900, 32768, 0], + [8372, 14444, 18847, 32768, 0], + [6523, 11779, 16000, 32768, 0], + [5337, 9898, 13760, 32768, 0], + [3034, 5860, 8462, 32768, 0], + ], + [ + [15967, 22905, 26286, 32768, 0], + [13534, 20654, 24579, 32768, 0], + [9504, 16092, 20535, 32768, 0], + [6975, 12568, 16903, 32768, 0], + [5364, 10091, 14020, 32768, 0], + [4357, 8370, 11857, 32768, 0], + [2506, 4934, 7218, 32768, 0], + [23032, 28815, 30936, 32768, 0], + [19540, 26704, 29719, 32768, 0], + [15158, 22969, 27097, 32768, 0], + [11408, 18865, 23650, 32768, 0], + [8885, 15448, 20250, 32768, 0], + [7108, 12853, 17416, 32768, 0], + [4231, 8041, 11480, 32768, 0], + [19823, 26490, 29156, 32768, 0], + [18890, 25929, 28932, 32768, 0], + [15660, 23491, 27433, 32768, 0], + [12147, 19776, 24488, 32768, 0], + [9728, 16774, 21649, 32768, 0], + [7919, 14277, 19066, 32768, 0], + [5440, 10170, 14185, 32768, 0], + ], + ], + [ + [ + [14406, 20862, 24414, 32768, 0], + [11824, 18907, 23109, 32768, 0], + [8257, 14393, 18803, 32768, 0], + [5860, 10747, 14778, 32768, 0], + [4475, 8486, 11984, 32768, 0], + [3606, 6954, 10043, 32768, 0], + [1736, 3410, 5048, 32768, 0], + [14430, 20046, 22882, 32768, 0], + [15593, 22899, 26709, 32768, 0], + [12102, 19368, 23811, 32768, 0], + [9059, 15584, 20262, 32768, 0], + [6999, 12603, 17048, 32768, 0], + [5684, 10497, 14553, 32768, 0], + [2822, 5438, 7862, 32768, 0], + [15785, 21585, 24359, 32768, 0], + [18347, 25229, 28266, 32768, 0], + [14974, 22487, 26389, 32768, 0], + [11423, 18681, 23271, 32768, 0], + [8863, 15350, 20008, 32768, 0], + [7153, 12852, 17278, 32768, 0], + [3707, 7036, 9982, 32768, 0], + ], + [ + [15460, 21696, 25469, 32768, 0], + [12170, 19249, 23191, 32768, 0], + [8723, 15027, 19332, 32768, 0], + [6428, 11704, 15874, 32768, 0], + [4922, 9292, 13052, 32768, 0], + [4139, 7695, 11010, 32768, 0], + [2291, 4508, 6598, 32768, 0], + [19856, 26920, 29828, 32768, 0], + [17923, 25289, 28792, 32768, 0], + [14278, 21968, 26297, 32768, 0], + [10910, 18136, 22950, 32768, 0], + [8423, 14815, 19627, 32768, 0], + [6771, 12283, 16774, 32768, 0], + [4074, 7750, 11081, 32768, 0], + [19852, 26074, 28672, 32768, 0], + [19371, 26110, 28989, 32768, 0], + [16265, 23873, 27663, 32768, 0], + [12758, 20378, 24952, 32768, 0], + [10095, 17098, 21961, 32768, 0], + [8250, 14628, 19451, 32768, 0], + [5205, 9745, 13622, 32768, 0], + ], + ], + [ + [ + [10563, 16233, 19763, 32768, 0], + [9794, 16022, 19804, 32768, 0], + [6750, 11945, 15759, 32768, 0], + [4963, 9186, 12752, 32768, 0], + [3845, 7435, 10627, 32768, 0], + [3051, 6085, 8834, 32768, 0], + [1311, 2596, 3830, 32768, 0], + [11246, 16404, 19689, 32768, 0], + [12315, 18911, 22731, 32768, 0], + [10557, 17095, 21289, 32768, 0], + [8136, 14006, 18249, 32768, 0], + [6348, 11474, 15565, 32768, 0], + [5196, 9655, 13400, 32768, 0], + [2349, 4526, 6587, 32768, 0], + [13337, 18730, 21569, 32768, 0], + [19306, 26071, 28882, 32768, 0], + [15952, 23540, 27254, 32768, 0], + [12409, 19934, 24430, 32768, 0], + [9760, 16706, 21389, 32768, 0], + [8004, 14220, 18818, 32768, 0], + [4138, 7794, 10961, 32768, 0], + ], + [ + [10870, 16684, 20949, 32768, 0], + [9664, 15230, 18680, 32768, 0], + [6886, 12109, 15408, 32768, 0], + [4825, 8900, 12305, 32768, 0], + [3630, 7162, 10314, 32768, 0], + [3036, 6429, 9387, 32768, 0], + [1671, 3296, 4940, 32768, 0], + [13819, 19159, 23026, 32768, 0], + [11984, 19108, 23120, 32768, 0], + [10690, 17210, 21663, 32768, 0], + [7984, 14154, 18333, 32768, 0], + [6868, 12294, 16124, 32768, 0], + [5274, 8994, 12868, 32768, 0], + [2988, 5771, 8424, 32768, 0], + [19736, 26647, 29141, 32768, 0], + [18933, 26070, 28984, 32768, 0], + [15779, 23048, 27200, 32768, 0], + [12638, 20061, 24532, 32768, 0], + [10692, 17545, 22220, 32768, 0], + [9217, 15251, 20054, 32768, 0], + [5078, 9284, 12594, 32768, 0], + ], + ], + [ + [ + [2331, 3662, 5244, 32768, 0], + [2891, 4771, 6145, 32768, 0], + [4598, 7623, 9729, 32768, 0], + [3520, 6845, 9199, 32768, 0], + [3417, 6119, 9324, 32768, 0], + [2601, 5412, 7385, 32768, 0], + [600, 1173, 1744, 32768, 0], + [7672, 13286, 17469, 32768, 0], + [4232, 7792, 10793, 32768, 0], + [2915, 5317, 7397, 32768, 0], + [2318, 4356, 6152, 32768, 0], + [2127, 4000, 5554, 32768, 0], + [1850, 3478, 5275, 32768, 0], + [977, 1933, 2843, 32768, 0], + [18280, 24387, 27989, 32768, 0], + [15852, 22671, 26185, 32768, 0], + [13845, 20951, 24789, 32768, 0], + [11055, 17966, 22129, 32768, 0], + [9138, 15422, 19801, 32768, 0], + [7454, 13145, 17456, 32768, 0], + [3370, 6393, 9013, 32768, 0], + ], + [ + [5842, 9229, 10838, 32768, 0], + [2313, 3491, 4276, 32768, 0], + [2998, 6104, 7496, 32768, 0], + [2420, 7447, 9868, 32768, 0], + [3034, 8495, 10923, 32768, 0], + [4076, 8937, 10975, 32768, 0], + [1086, 2370, 3299, 32768, 0], + [9714, 17254, 20444, 32768, 0], + [8543, 13698, 17123, 32768, 0], + [4918, 9007, 11910, 32768, 0], + [4129, 7532, 10553, 32768, 0], + [2364, 5533, 8058, 32768, 0], + [1834, 3546, 5563, 32768, 0], + [1473, 2908, 4133, 32768, 0], + [15405, 21193, 25619, 32768, 0], + [15691, 21952, 26561, 32768, 0], + [12962, 19194, 24165, 32768, 0], + [10272, 17855, 22129, 32768, 0], + [8588, 15270, 20718, 32768, 0], + [8682, 14669, 19500, 32768, 0], + [4870, 9636, 13205, 32768, 0], + ], + ], + [ + [ + [8192, 16384, 24576, 32768, 0], + [8192, 16384, 24576, 32768, 0], + [8192, 16384, 24576, 32768, 0], + [8192, 16384, 24576, 32768, 0], + [8192, 16384, 24576, 32768, 0], + [8192, 16384, 24576, 32768, 0], + [8192, 16384, 24576, 32768, 0], + [8192, 16384, 24576, 32768, 0], + [8192, 16384, 24576, 32768, 0], + [8192, 16384, 24576, 32768, 0], + [8192, 16384, 24576, 32768, 0], + [8192, 16384, 24576, 32768, 0], + [8192, 16384, 24576, 32768, 0], + [8192, 16384, 24576, 32768, 0], + [8192, 16384, 24576, 32768, 0], + [8192, 16384, 24576, 32768, 0], + [8192, 16384, 24576, 32768, 0], + [8192, 16384, 24576, 32768, 0], + [8192, 16384, 24576, 32768, 0], + [8192, 16384, 24576, 32768, 0], + [8192, 16384, 24576, 32768, 0], + ], + [ + [8192, 16384, 24576, 32768, 0], + [8192, 16384, 24576, 32768, 0], + [8192, 16384, 24576, 32768, 0], + [8192, 16384, 24576, 32768, 0], + [8192, 16384, 24576, 32768, 0], + [8192, 16384, 24576, 32768, 0], + [8192, 16384, 24576, 32768, 0], + [8192, 16384, 24576, 32768, 0], + [8192, 16384, 24576, 32768, 0], + [8192, 16384, 24576, 32768, 0], + [8192, 16384, 24576, 32768, 0], + [8192, 16384, 24576, 32768, 0], + [8192, 16384, 24576, 32768, 0], + [8192, 16384, 24576, 32768, 0], + [8192, 16384, 24576, 32768, 0], + [8192, 16384, 24576, 32768, 0], + [8192, 16384, 24576, 32768, 0], + [8192, 16384, 24576, 32768, 0], + [8192, 16384, 24576, 32768, 0], + [8192, 16384, 24576, 32768, 0], + [8192, 16384, 24576, 32768, 0], + ], + ], + ], + [ + [ + [ + [14995, 21341, 24749, 32768, 0], + [13158, 20289, 24601, 32768, 0], + [8941, 15326, 19876, 32768, 0], + [6297, 11541, 15807, 32768, 0], + [4817, 9029, 12776, 32768, 0], + [3731, 7273, 10627, 32768, 0], + [1847, 3617, 5354, 32768, 0], + [14472, 19659, 22343, 32768, 0], + [16806, 24162, 27533, 32768, 0], + [12900, 20404, 24713, 32768, 0], + [9411, 16112, 20797, 32768, 0], + [7056, 12697, 17148, 32768, 0], + [5544, 10339, 14460, 32768, 0], + [2954, 5704, 8319, 32768, 0], + [12464, 18071, 21354, 32768, 0], + [15482, 22528, 26034, 32768, 0], + [12070, 19269, 23624, 32768, 0], + [8953, 15406, 20106, 32768, 0], + [7027, 12730, 17220, 32768, 0], + [5887, 10913, 15140, 32768, 0], + [3793, 7278, 10447, 32768, 0], + ], + [ + [15571, 22232, 25749, 32768, 0], + [14506, 21575, 25374, 32768, 0], + [10189, 17089, 21569, 32768, 0], + [7316, 13301, 17915, 32768, 0], + [5783, 10912, 15190, 32768, 0], + [4760, 9155, 13088, 32768, 0], + [2993, 5966, 8774, 32768, 0], + [23424, 28903, 30778, 32768, 0], + [20775, 27666, 30290, 32768, 0], + [16474, 24410, 28299, 32768, 0], + [12471, 20180, 24987, 32768, 0], + [9410, 16487, 21439, 32768, 0], + [7536, 13614, 18529, 32768, 0], + [5048, 9586, 13549, 32768, 0], + [21090, 27290, 29756, 32768, 0], + [20796, 27402, 30026, 32768, 0], + [17819, 25485, 28969, 32768, 0], + [13860, 21909, 26462, 32768, 0], + [11002, 18494, 23529, 32768, 0], + [8953, 15929, 20897, 32768, 0], + [6448, 11918, 16454, 32768, 0], + ], + ], + [ + [ + [15999, 22208, 25449, 32768, 0], + [13050, 19988, 24122, 32768, 0], + [8594, 14864, 19378, 32768, 0], + [6033, 11079, 15238, 32768, 0], + [4554, 8683, 12347, 32768, 0], + [3672, 7139, 10337, 32768, 0], + [1900, 3771, 5576, 32768, 0], + [15788, 21340, 23949, 32768, 0], + [16825, 24235, 27758, 32768, 0], + [12873, 20402, 24810, 32768, 0], + [9590, 16363, 21094, 32768, 0], + [7352, 13209, 17733, 32768, 0], + [5960, 10989, 15184, 32768, 0], + [3232, 6234, 9007, 32768, 0], + [15761, 20716, 23224, 32768, 0], + [19318, 25989, 28759, 32768, 0], + [15529, 23094, 26929, 32768, 0], + [11662, 18989, 23641, 32768, 0], + [8955, 15568, 20366, 32768, 0], + [7281, 13106, 17708, 32768, 0], + [4248, 8059, 11440, 32768, 0], + ], + [ + [14899, 21217, 24503, 32768, 0], + [13519, 20283, 24047, 32768, 0], + [9429, 15966, 20365, 32768, 0], + [6700, 12355, 16652, 32768, 0], + [5088, 9704, 13716, 32768, 0], + [4243, 8154, 11731, 32768, 0], + [2702, 5364, 7861, 32768, 0], + [22745, 28388, 30454, 32768, 0], + [20235, 27146, 29922, 32768, 0], + [15896, 23715, 27637, 32768, 0], + [11840, 19350, 24131, 32768, 0], + [9122, 15932, 20880, 32768, 0], + [7488, 13581, 18362, 32768, 0], + [5114, 9568, 13370, 32768, 0], + [20845, 26553, 28932, 32768, 0], + [20981, 27372, 29884, 32768, 0], + [17781, 25335, 28785, 32768, 0], + [13760, 21708, 26297, 32768, 0], + [10975, 18415, 23365, 32768, 0], + [9045, 15789, 20686, 32768, 0], + [6130, 11199, 15423, 32768, 0], + ], + ], + [ + [ + [13549, 19724, 23158, 32768, 0], + [11844, 18382, 22246, 32768, 0], + [7919, 13619, 17773, 32768, 0], + [5486, 10143, 13946, 32768, 0], + [4166, 7983, 11324, 32768, 0], + [3364, 6506, 9427, 32768, 0], + [1598, 3160, 4674, 32768, 0], + [15281, 20979, 23781, 32768, 0], + [14939, 22119, 25952, 32768, 0], + [11363, 18407, 22812, 32768, 0], + [8609, 14857, 19370, 32768, 0], + [6737, 12184, 16480, 32768, 0], + [5506, 10263, 14262, 32768, 0], + [2990, 5786, 8380, 32768, 0], + [20249, 25253, 27417, 32768, 0], + [21070, 27518, 30001, 32768, 0], + [16854, 24469, 28074, 32768, 0], + [12864, 20486, 25000, 32768, 0], + [9962, 16978, 21778, 32768, 0], + [8074, 14338, 19048, 32768, 0], + [4494, 8479, 11906, 32768, 0], + ], + [ + [13960, 19617, 22829, 32768, 0], + [11150, 17341, 21228, 32768, 0], + [7150, 12964, 17190, 32768, 0], + [5331, 10002, 13867, 32768, 0], + [4167, 7744, 11057, 32768, 0], + [3480, 6629, 9646, 32768, 0], + [1883, 3784, 5686, 32768, 0], + [18752, 25660, 28912, 32768, 0], + [16968, 24586, 28030, 32768, 0], + [13520, 21055, 25313, 32768, 0], + [10453, 17626, 22280, 32768, 0], + [8386, 14505, 19116, 32768, 0], + [6742, 12595, 17008, 32768, 0], + [4273, 8140, 11499, 32768, 0], + [22120, 27827, 30233, 32768, 0], + [20563, 27358, 29895, 32768, 0], + [17076, 24644, 28153, 32768, 0], + [13362, 20942, 25309, 32768, 0], + [10794, 17965, 22695, 32768, 0], + [9014, 15652, 20319, 32768, 0], + [5708, 10512, 14497, 32768, 0], + ], + ], + [ + [ + [5705, 10930, 15725, 32768, 0], + [7946, 12765, 16115, 32768, 0], + [6801, 12123, 16226, 32768, 0], + [5462, 10135, 14200, 32768, 0], + [4189, 8011, 11507, 32768, 0], + [3191, 6229, 9408, 32768, 0], + [1057, 2137, 3212, 32768, 0], + [10018, 17067, 21491, 32768, 0], + [7380, 12582, 16453, 32768, 0], + [6068, 10845, 14339, 32768, 0], + [5098, 9198, 12555, 32768, 0], + [4312, 8010, 11119, 32768, 0], + [3700, 6966, 9781, 32768, 0], + [1693, 3326, 4887, 32768, 0], + [18757, 24930, 27774, 32768, 0], + [17648, 24596, 27817, 32768, 0], + [14707, 22052, 26026, 32768, 0], + [11720, 18852, 23292, 32768, 0], + [9357, 15952, 20525, 32768, 0], + [7810, 13753, 18210, 32768, 0], + [3879, 7333, 10328, 32768, 0], + ], + [ + [8278, 13242, 15922, 32768, 0], + [10547, 15867, 18919, 32768, 0], + [9106, 15842, 20609, 32768, 0], + [6833, 13007, 17218, 32768, 0], + [4811, 9712, 13923, 32768, 0], + [3985, 7352, 11128, 32768, 0], + [1688, 3458, 5262, 32768, 0], + [12951, 21861, 26510, 32768, 0], + [9788, 16044, 20276, 32768, 0], + [6309, 11244, 14870, 32768, 0], + [5183, 9349, 12566, 32768, 0], + [4389, 8229, 11492, 32768, 0], + [3633, 6945, 10620, 32768, 0], + [3600, 6847, 9907, 32768, 0], + [21748, 28137, 30255, 32768, 0], + [19436, 26581, 29560, 32768, 0], + [16359, 24201, 27953, 32768, 0], + [13961, 21693, 25871, 32768, 0], + [11544, 18686, 23322, 32768, 0], + [9372, 16462, 20952, 32768, 0], + [6138, 11210, 15390, 32768, 0], + ], + ], + [ + [ + [8192, 16384, 24576, 32768, 0], + [8192, 16384, 24576, 32768, 0], + [8192, 16384, 24576, 32768, 0], + [8192, 16384, 24576, 32768, 0], + [8192, 16384, 24576, 32768, 0], + [8192, 16384, 24576, 32768, 0], + [8192, 16384, 24576, 32768, 0], + [8192, 16384, 24576, 32768, 0], + [8192, 16384, 24576, 32768, 0], + [8192, 16384, 24576, 32768, 0], + [8192, 16384, 24576, 32768, 0], + [8192, 16384, 24576, 32768, 0], + [8192, 16384, 24576, 32768, 0], + [8192, 16384, 24576, 32768, 0], + [8192, 16384, 24576, 32768, 0], + [8192, 16384, 24576, 32768, 0], + [8192, 16384, 24576, 32768, 0], + [8192, 16384, 24576, 32768, 0], + [8192, 16384, 24576, 32768, 0], + [8192, 16384, 24576, 32768, 0], + [8192, 16384, 24576, 32768, 0], + ], + [ + [8192, 16384, 24576, 32768, 0], + [8192, 16384, 24576, 32768, 0], + [8192, 16384, 24576, 32768, 0], + [8192, 16384, 24576, 32768, 0], + [8192, 16384, 24576, 32768, 0], + [8192, 16384, 24576, 32768, 0], + [8192, 16384, 24576, 32768, 0], + [8192, 16384, 24576, 32768, 0], + [8192, 16384, 24576, 32768, 0], + [8192, 16384, 24576, 32768, 0], + [8192, 16384, 24576, 32768, 0], + [8192, 16384, 24576, 32768, 0], + [8192, 16384, 24576, 32768, 0], + [8192, 16384, 24576, 32768, 0], + [8192, 16384, 24576, 32768, 0], + [8192, 16384, 24576, 32768, 0], + [8192, 16384, 24576, 32768, 0], + [8192, 16384, 24576, 32768, 0], + [8192, 16384, 24576, 32768, 0], + [8192, 16384, 24576, 32768, 0], + [8192, 16384, 24576, 32768, 0], + ], + ], + ], + [ + [ + [ + [16138, 22223, 25509, 32768, 0], + [15347, 22430, 26332, 32768, 0], + [9614, 16736, 21332, 32768, 0], + [6600, 12275, 16907, 32768, 0], + [4811, 9424, 13547, 32768, 0], + [3748, 7809, 11420, 32768, 0], + [2254, 4587, 6890, 32768, 0], + [15196, 20284, 23177, 32768, 0], + [18317, 25469, 28451, 32768, 0], + [13918, 21651, 25842, 32768, 0], + [10052, 17150, 21995, 32768, 0], + [7499, 13630, 18587, 32768, 0], + [6158, 11417, 16003, 32768, 0], + [4014, 7785, 11252, 32768, 0], + [15048, 21067, 24384, 32768, 0], + [18202, 25346, 28553, 32768, 0], + [14302, 22019, 26356, 32768, 0], + [10839, 18139, 23166, 32768, 0], + [8715, 15744, 20806, 32768, 0], + [7536, 13576, 18544, 32768, 0], + [5413, 10335, 14498, 32768, 0], + ], + [ + [17394, 24501, 27895, 32768, 0], + [15889, 23420, 27185, 32768, 0], + [11561, 19133, 23870, 32768, 0], + [8285, 14812, 19844, 32768, 0], + [6496, 12043, 16550, 32768, 0], + [4771, 9574, 13677, 32768, 0], + [3603, 6830, 10144, 32768, 0], + [21656, 27704, 30200, 32768, 0], + [21324, 27915, 30511, 32768, 0], + [17327, 25336, 28997, 32768, 0], + [13417, 21381, 26033, 32768, 0], + [10132, 17425, 22338, 32768, 0], + [8580, 15016, 19633, 32768, 0], + [5694, 11477, 16411, 32768, 0], + [24116, 29780, 31450, 32768, 0], + [23853, 29695, 31591, 32768, 0], + [20085, 27614, 30428, 32768, 0], + [15326, 24335, 28575, 32768, 0], + [11814, 19472, 24810, 32768, 0], + [10221, 18611, 24767, 32768, 0], + [7689, 14558, 20321, 32768, 0], + ], + ], + [ + [ + [16214, 22380, 25770, 32768, 0], + [14213, 21304, 25295, 32768, 0], + [9213, 15823, 20455, 32768, 0], + [6395, 11758, 16139, 32768, 0], + [4779, 9187, 13066, 32768, 0], + [3821, 7501, 10953, 32768, 0], + [2293, 4567, 6795, 32768, 0], + [15859, 21283, 23820, 32768, 0], + [18404, 25602, 28726, 32768, 0], + [14325, 21980, 26206, 32768, 0], + [10669, 17937, 22720, 32768, 0], + [8297, 14642, 19447, 32768, 0], + [6746, 12389, 16893, 32768, 0], + [4324, 8251, 11770, 32768, 0], + [16532, 21631, 24475, 32768, 0], + [20667, 27150, 29668, 32768, 0], + [16728, 24510, 28175, 32768, 0], + [12861, 20645, 25332, 32768, 0], + [10076, 17361, 22417, 32768, 0], + [8395, 14940, 19963, 32768, 0], + [5731, 10683, 14912, 32768, 0], + ], + [ + [14433, 21155, 24938, 32768, 0], + [14658, 21716, 25545, 32768, 0], + [9923, 16824, 21557, 32768, 0], + [6982, 13052, 17721, 32768, 0], + [5419, 10503, 15050, 32768, 0], + [4852, 9162, 13014, 32768, 0], + [3271, 6395, 9630, 32768, 0], + [22210, 27833, 30109, 32768, 0], + [20750, 27368, 29821, 32768, 0], + [16894, 24828, 28573, 32768, 0], + [13247, 21276, 25757, 32768, 0], + [10038, 17265, 22563, 32768, 0], + [8587, 14947, 20327, 32768, 0], + [5645, 11371, 15252, 32768, 0], + [22027, 27526, 29714, 32768, 0], + [23098, 29146, 31221, 32768, 0], + [19886, 27341, 30272, 32768, 0], + [15609, 23747, 28046, 32768, 0], + [11993, 20065, 24939, 32768, 0], + [9637, 18267, 23671, 32768, 0], + [7625, 13801, 19144, 32768, 0], + ], + ], + [ + [ + [14438, 20798, 24089, 32768, 0], + [12621, 19203, 23097, 32768, 0], + [8177, 14125, 18402, 32768, 0], + [5674, 10501, 14456, 32768, 0], + [4236, 8239, 11733, 32768, 0], + [3447, 6750, 9806, 32768, 0], + [1986, 3950, 5864, 32768, 0], + [16208, 22099, 24930, 32768, 0], + [16537, 24025, 27585, 32768, 0], + [12780, 20381, 24867, 32768, 0], + [9767, 16612, 21416, 32768, 0], + [7686, 13738, 18398, 32768, 0], + [6333, 11614, 15964, 32768, 0], + [3941, 7571, 10836, 32768, 0], + [22819, 27422, 29202, 32768, 0], + [22224, 28514, 30721, 32768, 0], + [17660, 25433, 28913, 32768, 0], + [13574, 21482, 26002, 32768, 0], + [10629, 17977, 22938, 32768, 0], + [8612, 15298, 20265, 32768, 0], + [5607, 10491, 14596, 32768, 0], + ], + [ + [13569, 19800, 23206, 32768, 0], + [13128, 19924, 23869, 32768, 0], + [8329, 14841, 19403, 32768, 0], + [6130, 10976, 15057, 32768, 0], + [4682, 8839, 12518, 32768, 0], + [3656, 7409, 10588, 32768, 0], + [2577, 5099, 7412, 32768, 0], + [22427, 28684, 30585, 32768, 0], + [20913, 27750, 30139, 32768, 0], + [15840, 24109, 27834, 32768, 0], + [12308, 20029, 24569, 32768, 0], + [10216, 16785, 21458, 32768, 0], + [8309, 14203, 19113, 32768, 0], + [6043, 11168, 15307, 32768, 0], + [23166, 28901, 30998, 32768, 0], + [21899, 28405, 30751, 32768, 0], + [18413, 26091, 29443, 32768, 0], + [15233, 23114, 27352, 32768, 0], + [12683, 20472, 25288, 32768, 0], + [10702, 18259, 23409, 32768, 0], + [8125, 14464, 19226, 32768, 0], + ], + ], + [ + [ + [9040, 14786, 18360, 32768, 0], + [9979, 15718, 19415, 32768, 0], + [7913, 13918, 18311, 32768, 0], + [5859, 10889, 15184, 32768, 0], + [4593, 8677, 12510, 32768, 0], + [3820, 7396, 10791, 32768, 0], + [1730, 3471, 5192, 32768, 0], + [11803, 18365, 22709, 32768, 0], + [11419, 18058, 22225, 32768, 0], + [9418, 15774, 20243, 32768, 0], + [7539, 13325, 17657, 32768, 0], + [6233, 11317, 15384, 32768, 0], + [5137, 9656, 13545, 32768, 0], + [2977, 5774, 8349, 32768, 0], + [21207, 27246, 29640, 32768, 0], + [19547, 26578, 29497, 32768, 0], + [16169, 23871, 27690, 32768, 0], + [12820, 20458, 25018, 32768, 0], + [10224, 17332, 22214, 32768, 0], + [8526, 15048, 19884, 32768, 0], + [5037, 9410, 13118, 32768, 0], + ], + [ + [12339, 17329, 20140, 32768, 0], + [13505, 19895, 23225, 32768, 0], + [9847, 16944, 21564, 32768, 0], + [7280, 13256, 18348, 32768, 0], + [4712, 10009, 14454, 32768, 0], + [4361, 7914, 12477, 32768, 0], + [2870, 5628, 7995, 32768, 0], + [20061, 25504, 28526, 32768, 0], + [15235, 22878, 26145, 32768, 0], + [12985, 19958, 24155, 32768, 0], + [9782, 16641, 21403, 32768, 0], + [9456, 16360, 20760, 32768, 0], + [6855, 12940, 18557, 32768, 0], + [5661, 10564, 15002, 32768, 0], + [25656, 30602, 31894, 32768, 0], + [22570, 29107, 31092, 32768, 0], + [18917, 26423, 29541, 32768, 0], + [15940, 23649, 27754, 32768, 0], + [12803, 20581, 25219, 32768, 0], + [11082, 18695, 23376, 32768, 0], + [7939, 14373, 19005, 32768, 0], + ], + ], + [ + [ + [8192, 16384, 24576, 32768, 0], + [8192, 16384, 24576, 32768, 0], + [8192, 16384, 24576, 32768, 0], + [8192, 16384, 24576, 32768, 0], + [8192, 16384, 24576, 32768, 0], + [8192, 16384, 24576, 32768, 0], + [8192, 16384, 24576, 32768, 0], + [8192, 16384, 24576, 32768, 0], + [8192, 16384, 24576, 32768, 0], + [8192, 16384, 24576, 32768, 0], + [8192, 16384, 24576, 32768, 0], + [8192, 16384, 24576, 32768, 0], + [8192, 16384, 24576, 32768, 0], + [8192, 16384, 24576, 32768, 0], + [8192, 16384, 24576, 32768, 0], + [8192, 16384, 24576, 32768, 0], + [8192, 16384, 24576, 32768, 0], + [8192, 16384, 24576, 32768, 0], + [8192, 16384, 24576, 32768, 0], + [8192, 16384, 24576, 32768, 0], + [8192, 16384, 24576, 32768, 0], + ], + [ + [8192, 16384, 24576, 32768, 0], + [8192, 16384, 24576, 32768, 0], + [8192, 16384, 24576, 32768, 0], + [8192, 16384, 24576, 32768, 0], + [8192, 16384, 24576, 32768, 0], + [8192, 16384, 24576, 32768, 0], + [8192, 16384, 24576, 32768, 0], + [8192, 16384, 24576, 32768, 0], + [8192, 16384, 24576, 32768, 0], + [8192, 16384, 24576, 32768, 0], + [8192, 16384, 24576, 32768, 0], + [8192, 16384, 24576, 32768, 0], + [8192, 16384, 24576, 32768, 0], + [8192, 16384, 24576, 32768, 0], + [8192, 16384, 24576, 32768, 0], + [8192, 16384, 24576, 32768, 0], + [8192, 16384, 24576, 32768, 0], + [8192, 16384, 24576, 32768, 0], + [8192, 16384, 24576, 32768, 0], + [8192, 16384, 24576, 32768, 0], + [8192, 16384, 24576, 32768, 0], + ], + ], + ], + [ + [ + [ + [18315, 24289, 27551, 32768, 0], + [16854, 24068, 27835, 32768, 0], + [10140, 17927, 23173, 32768, 0], + [6722, 12982, 18267, 32768, 0], + [4661, 9826, 14706, 32768, 0], + [3832, 8165, 12294, 32768, 0], + [2795, 6098, 9245, 32768, 0], + [17145, 23326, 26672, 32768, 0], + [20733, 27680, 30308, 32768, 0], + [16032, 24461, 28546, 32768, 0], + [11653, 20093, 25081, 32768, 0], + [9290, 16429, 22086, 32768, 0], + [7796, 14598, 19982, 32768, 0], + [6502, 12378, 17441, 32768, 0], + [21681, 27732, 30320, 32768, 0], + [22389, 29044, 31261, 32768, 0], + [19027, 26731, 30087, 32768, 0], + [14739, 23755, 28624, 32768, 0], + [11358, 20778, 25511, 32768, 0], + [10995, 18073, 24190, 32768, 0], + [9162, 14990, 20617, 32768, 0], + ], + [ + [21425, 27952, 30388, 32768, 0], + [18062, 25838, 29034, 32768, 0], + [11956, 19881, 24808, 32768, 0], + [7718, 15000, 20980, 32768, 0], + [5702, 11254, 16143, 32768, 0], + [4898, 9088, 16864, 32768, 0], + [3679, 6776, 11907, 32768, 0], + [23294, 30160, 31663, 32768, 0], + [24397, 29896, 31836, 32768, 0], + [19245, 27128, 30593, 32768, 0], + [13202, 19825, 26404, 32768, 0], + [11578, 19297, 23957, 32768, 0], + [8073, 13297, 21370, 32768, 0], + [5461, 10923, 19745, 32768, 0], + [27367, 30521, 31934, 32768, 0], + [24904, 30671, 31940, 32768, 0], + [23075, 28460, 31299, 32768, 0], + [14400, 23658, 30417, 32768, 0], + [13885, 23882, 28325, 32768, 0], + [14746, 22938, 27853, 32768, 0], + [5461, 16384, 27307, 32768, 0], + ], + ], + [ + [ + [18274, 24813, 27890, 32768, 0], + [15537, 23149, 27003, 32768, 0], + [9449, 16740, 21827, 32768, 0], + [6700, 12498, 17261, 32768, 0], + [4988, 9866, 14198, 32768, 0], + [4236, 8147, 11902, 32768, 0], + [2867, 5860, 8654, 32768, 0], + [17124, 23171, 26101, 32768, 0], + [20396, 27477, 30148, 32768, 0], + [16573, 24629, 28492, 32768, 0], + [12749, 20846, 25674, 32768, 0], + [10233, 17878, 22818, 32768, 0], + [8525, 15332, 20363, 32768, 0], + [6283, 11632, 16255, 32768, 0], + [20466, 26511, 29286, 32768, 0], + [23059, 29174, 31191, 32768, 0], + [19481, 27263, 30241, 32768, 0], + [15458, 23631, 28137, 32768, 0], + [12416, 20608, 25693, 32768, 0], + [10261, 18011, 23261, 32768, 0], + [8016, 14655, 19666, 32768, 0], + ], + [ + [17616, 24586, 28112, 32768, 0], + [15809, 23299, 27155, 32768, 0], + [10767, 18890, 23793, 32768, 0], + [7727, 14255, 18865, 32768, 0], + [6129, 11926, 16882, 32768, 0], + [4482, 9704, 14861, 32768, 0], + [3277, 7452, 11522, 32768, 0], + [22956, 28551, 30730, 32768, 0], + [22724, 28937, 30961, 32768, 0], + [18467, 26324, 29580, 32768, 0], + [13234, 20713, 25649, 32768, 0], + [11181, 17592, 22481, 32768, 0], + [8291, 18358, 24576, 32768, 0], + [7568, 11881, 14984, 32768, 0], + [24948, 29001, 31147, 32768, 0], + [25674, 30619, 32151, 32768, 0], + [20841, 26793, 29603, 32768, 0], + [14669, 24356, 28666, 32768, 0], + [11334, 23593, 28219, 32768, 0], + [8922, 14762, 22873, 32768, 0], + [8301, 13544, 20535, 32768, 0], + ], + ], + [ + [ + [17113, 23733, 27081, 32768, 0], + [14139, 21406, 25452, 32768, 0], + [8552, 15002, 19776, 32768, 0], + [5871, 11120, 15378, 32768, 0], + [4455, 8616, 12253, 32768, 0], + [3469, 6910, 10386, 32768, 0], + [2255, 4553, 6782, 32768, 0], + [18224, 24376, 27053, 32768, 0], + [19290, 26710, 29614, 32768, 0], + [14936, 22991, 27184, 32768, 0], + [11238, 18951, 23762, 32768, 0], + [8786, 15617, 20588, 32768, 0], + [7317, 13228, 18003, 32768, 0], + [5101, 9512, 13493, 32768, 0], + [22639, 28222, 30210, 32768, 0], + [23216, 29331, 31307, 32768, 0], + [19075, 26762, 29895, 32768, 0], + [15014, 23113, 27457, 32768, 0], + [11938, 19857, 24752, 32768, 0], + [9942, 17280, 22282, 32768, 0], + [7167, 13144, 17752, 32768, 0], + ], + [ + [15820, 22738, 26488, 32768, 0], + [13530, 20885, 25216, 32768, 0], + [8395, 15530, 20452, 32768, 0], + [6574, 12321, 16380, 32768, 0], + [5353, 10419, 14568, 32768, 0], + [4613, 8446, 12381, 32768, 0], + [3440, 7158, 9903, 32768, 0], + [24247, 29051, 31224, 32768, 0], + [22118, 28058, 30369, 32768, 0], + [16498, 24768, 28389, 32768, 0], + [12920, 21175, 26137, 32768, 0], + [10730, 18619, 25352, 32768, 0], + [10187, 16279, 22791, 32768, 0], + [9310, 14631, 22127, 32768, 0], + [24970, 30558, 32057, 32768, 0], + [24801, 29942, 31698, 32768, 0], + [22432, 28453, 30855, 32768, 0], + [19054, 25680, 29580, 32768, 0], + [14392, 23036, 28109, 32768, 0], + [12495, 20947, 26650, 32768, 0], + [12442, 20326, 26214, 32768, 0], + ], + ], + [ + [ + [12162, 18785, 22648, 32768, 0], + [12749, 19697, 23806, 32768, 0], + [8580, 15297, 20346, 32768, 0], + [6169, 11749, 16543, 32768, 0], + [4836, 9391, 13448, 32768, 0], + [3821, 7711, 11613, 32768, 0], + [2228, 4601, 7070, 32768, 0], + [16319, 24725, 28280, 32768, 0], + [15698, 23277, 27168, 32768, 0], + [12726, 20368, 25047, 32768, 0], + [9912, 17015, 21976, 32768, 0], + [7888, 14220, 19179, 32768, 0], + [6777, 12284, 17018, 32768, 0], + [4492, 8590, 12252, 32768, 0], + [23249, 28904, 30947, 32768, 0], + [21050, 27908, 30512, 32768, 0], + [17440, 25340, 28949, 32768, 0], + [14059, 22018, 26541, 32768, 0], + [11288, 18903, 23898, 32768, 0], + [9411, 16342, 21428, 32768, 0], + [6278, 11588, 15944, 32768, 0], + ], + [ + [13981, 20067, 23226, 32768, 0], + [16922, 23580, 26783, 32768, 0], + [11005, 19039, 24487, 32768, 0], + [7389, 14218, 19798, 32768, 0], + [5598, 11505, 17206, 32768, 0], + [6090, 11213, 15659, 32768, 0], + [3820, 7371, 10119, 32768, 0], + [21082, 26925, 29675, 32768, 0], + [21262, 28627, 31128, 32768, 0], + [18392, 26454, 30437, 32768, 0], + [14870, 22910, 27096, 32768, 0], + [12620, 19484, 24908, 32768, 0], + [9290, 16553, 22802, 32768, 0], + [6668, 14288, 20004, 32768, 0], + [27704, 31055, 31949, 32768, 0], + [24709, 29978, 31788, 32768, 0], + [21668, 29264, 31657, 32768, 0], + [18295, 26968, 30074, 32768, 0], + [16399, 24422, 29313, 32768, 0], + [14347, 23026, 28104, 32768, 0], + [12370, 19806, 24477, 32768, 0], + ], + ], + [ + [ + [8192, 16384, 24576, 32768, 0], + [8192, 16384, 24576, 32768, 0], + [8192, 16384, 24576, 32768, 0], + [8192, 16384, 24576, 32768, 0], + [8192, 16384, 24576, 32768, 0], + [8192, 16384, 24576, 32768, 0], + [8192, 16384, 24576, 32768, 0], + [8192, 16384, 24576, 32768, 0], + [8192, 16384, 24576, 32768, 0], + [8192, 16384, 24576, 32768, 0], + [8192, 16384, 24576, 32768, 0], + [8192, 16384, 24576, 32768, 0], + [8192, 16384, 24576, 32768, 0], + [8192, 16384, 24576, 32768, 0], + [8192, 16384, 24576, 32768, 0], + [8192, 16384, 24576, 32768, 0], + [8192, 16384, 24576, 32768, 0], + [8192, 16384, 24576, 32768, 0], + [8192, 16384, 24576, 32768, 0], + [8192, 16384, 24576, 32768, 0], + [8192, 16384, 24576, 32768, 0], + ], + [ + [8192, 16384, 24576, 32768, 0], + [8192, 16384, 24576, 32768, 0], + [8192, 16384, 24576, 32768, 0], + [8192, 16384, 24576, 32768, 0], + [8192, 16384, 24576, 32768, 0], + [8192, 16384, 24576, 32768, 0], + [8192, 16384, 24576, 32768, 0], + [8192, 16384, 24576, 32768, 0], + [8192, 16384, 24576, 32768, 0], + [8192, 16384, 24576, 32768, 0], + [8192, 16384, 24576, 32768, 0], + [8192, 16384, 24576, 32768, 0], + [8192, 16384, 24576, 32768, 0], + [8192, 16384, 24576, 32768, 0], + [8192, 16384, 24576, 32768, 0], + [8192, 16384, 24576, 32768, 0], + [8192, 16384, 24576, 32768, 0], + [8192, 16384, 24576, 32768, 0], + [8192, 16384, 24576, 32768, 0], + [8192, 16384, 24576, 32768, 0], + [8192, 16384, 24576, 32768, 0], + ], + ], + ], +]; + +pub static DEFAULT_DC_SIGN_CDF: [[[[u16; 3]; 3]; 2]; 4] = [ + [[[16000, 32768, 0], [13056, 32768, 0], [18816, 32768, 0]], [[15232, 32768, 0], [12928, 32768, 0], [17280, 32768, 0]]], + [[[16000, 32768, 0], [13056, 32768, 0], [18816, 32768, 0]], [[15232, 32768, 0], [12928, 32768, 0], [17280, 32768, 0]]], + [[[16000, 32768, 0], [13056, 32768, 0], [18816, 32768, 0]], [[15232, 32768, 0], [12928, 32768, 0], [17280, 32768, 0]]], + [[[16000, 32768, 0], [13056, 32768, 0], [18816, 32768, 0]], [[15232, 32768, 0], [12928, 32768, 0], [17280, 32768, 0]]], +]; + +pub static DEFAULT_DRL_MODE_CDF: [[u16; 3]; 3] = [[13104, 32768, 0], [24560, 32768, 0], [18945, 32768, 0]]; + +pub static DEFAULT_EOB_EXTRA_CDF: [[[[[u16; 3]; 9]; 2]; 5]; 4] = [ + [ + [ + [ + [16961, 32768, 0], + [17223, 32768, 0], + [7621, 32768, 0], + [16384, 32768, 0], + [16384, 32768, 0], + [16384, 32768, 0], + [16384, 32768, 0], + [16384, 32768, 0], + [16384, 32768, 0], + ], + [ + [19069, 32768, 0], + [22525, 32768, 0], + [13377, 32768, 0], + [16384, 32768, 0], + [16384, 32768, 0], + [16384, 32768, 0], + [16384, 32768, 0], + [16384, 32768, 0], + [16384, 32768, 0], + ], + ], + [ + [ + [20401, 32768, 0], + [17025, 32768, 0], + [12845, 32768, 0], + [12873, 32768, 0], + [14094, 32768, 0], + [16384, 32768, 0], + [16384, 32768, 0], + [16384, 32768, 0], + [16384, 32768, 0], + ], + [ + [20681, 32768, 0], + [20701, 32768, 0], + [15250, 32768, 0], + [15017, 32768, 0], + [14928, 32768, 0], + [16384, 32768, 0], + [16384, 32768, 0], + [16384, 32768, 0], + [16384, 32768, 0], + ], + ], + [ + [ + [23905, 32768, 0], + [17194, 32768, 0], + [16170, 32768, 0], + [17695, 32768, 0], + [13826, 32768, 0], + [15810, 32768, 0], + [12036, 32768, 0], + [16384, 32768, 0], + [16384, 32768, 0], + ], + [ + [23959, 32768, 0], + [20799, 32768, 0], + [19021, 32768, 0], + [16203, 32768, 0], + [17886, 32768, 0], + [14144, 32768, 0], + [12010, 32768, 0], + [16384, 32768, 0], + [16384, 32768, 0], + ], + ], + [ + [ + [27399, 32768, 0], + [16327, 32768, 0], + [18071, 32768, 0], + [19584, 32768, 0], + [20721, 32768, 0], + [18432, 32768, 0], + [19560, 32768, 0], + [10150, 32768, 0], + [8805, 32768, 0], + ], + [ + [24932, 32768, 0], + [20833, 32768, 0], + [12027, 32768, 0], + [16670, 32768, 0], + [19914, 32768, 0], + [15106, 32768, 0], + [17662, 32768, 0], + [13783, 32768, 0], + [28756, 32768, 0], + ], + ], + [ + [ + [23406, 32768, 0], + [21845, 32768, 0], + [18432, 32768, 0], + [16384, 32768, 0], + [17096, 32768, 0], + [12561, 32768, 0], + [17320, 32768, 0], + [22395, 32768, 0], + [21370, 32768, 0], + ], + [ + [16384, 32768, 0], + [16384, 32768, 0], + [16384, 32768, 0], + [16384, 32768, 0], + [16384, 32768, 0], + [16384, 32768, 0], + [16384, 32768, 0], + [16384, 32768, 0], + [16384, 32768, 0], + ], + ], + ], + [ + [ + [ + [17471, 32768, 0], + [20223, 32768, 0], + [11357, 32768, 0], + [16384, 32768, 0], + [16384, 32768, 0], + [16384, 32768, 0], + [16384, 32768, 0], + [16384, 32768, 0], + [16384, 32768, 0], + ], + [ + [20335, 32768, 0], + [21667, 32768, 0], + [14818, 32768, 0], + [16384, 32768, 0], + [16384, 32768, 0], + [16384, 32768, 0], + [16384, 32768, 0], + [16384, 32768, 0], + [16384, 32768, 0], + ], + ], + [ + [ + [20430, 32768, 0], + [20662, 32768, 0], + [15367, 32768, 0], + [16970, 32768, 0], + [14657, 32768, 0], + [16384, 32768, 0], + [16384, 32768, 0], + [16384, 32768, 0], + [16384, 32768, 0], + ], + [ + [22117, 32768, 0], + [22028, 32768, 0], + [18650, 32768, 0], + [16042, 32768, 0], + [15885, 32768, 0], + [16384, 32768, 0], + [16384, 32768, 0], + [16384, 32768, 0], + [16384, 32768, 0], + ], + ], + [ + [ + [22409, 32768, 0], + [21012, 32768, 0], + [15650, 32768, 0], + [17395, 32768, 0], + [15469, 32768, 0], + [20205, 32768, 0], + [19511, 32768, 0], + [16384, 32768, 0], + [16384, 32768, 0], + ], + [ + [24220, 32768, 0], + [22480, 32768, 0], + [17737, 32768, 0], + [18916, 32768, 0], + [19268, 32768, 0], + [18412, 32768, 0], + [18844, 32768, 0], + [16384, 32768, 0], + [16384, 32768, 0], + ], + ], + [ + [ + [25991, 32768, 0], + [20314, 32768, 0], + [17731, 32768, 0], + [19678, 32768, 0], + [18649, 32768, 0], + [17307, 32768, 0], + [21798, 32768, 0], + [17549, 32768, 0], + [15630, 32768, 0], + ], + [ + [26585, 32768, 0], + [21469, 32768, 0], + [20432, 32768, 0], + [17735, 32768, 0], + [19280, 32768, 0], + [15235, 32768, 0], + [20297, 32768, 0], + [22471, 32768, 0], + [28997, 32768, 0], + ], + ], + [ + [ + [26605, 32768, 0], + [11304, 32768, 0], + [16726, 32768, 0], + [16560, 32768, 0], + [20866, 32768, 0], + [23524, 32768, 0], + [19878, 32768, 0], + [13469, 32768, 0], + [23084, 32768, 0], + ], + [ + [16384, 32768, 0], + [16384, 32768, 0], + [16384, 32768, 0], + [16384, 32768, 0], + [16384, 32768, 0], + [16384, 32768, 0], + [16384, 32768, 0], + [16384, 32768, 0], + [16384, 32768, 0], + ], + ], + ], + [ + [ + [ + [18983, 32768, 0], + [20512, 32768, 0], + [14885, 32768, 0], + [16384, 32768, 0], + [16384, 32768, 0], + [16384, 32768, 0], + [16384, 32768, 0], + [16384, 32768, 0], + [16384, 32768, 0], + ], + [ + [20090, 32768, 0], + [19444, 32768, 0], + [17286, 32768, 0], + [16384, 32768, 0], + [16384, 32768, 0], + [16384, 32768, 0], + [16384, 32768, 0], + [16384, 32768, 0], + [16384, 32768, 0], + ], + ], + [ + [ + [19139, 32768, 0], + [21487, 32768, 0], + [18959, 32768, 0], + [20910, 32768, 0], + [19089, 32768, 0], + [16384, 32768, 0], + [16384, 32768, 0], + [16384, 32768, 0], + [16384, 32768, 0], + ], + [ + [20536, 32768, 0], + [20664, 32768, 0], + [20625, 32768, 0], + [19123, 32768, 0], + [14862, 32768, 0], + [16384, 32768, 0], + [16384, 32768, 0], + [16384, 32768, 0], + [16384, 32768, 0], + ], + ], + [ + [ + [19833, 32768, 0], + [21502, 32768, 0], + [17485, 32768, 0], + [20267, 32768, 0], + [18353, 32768, 0], + [23329, 32768, 0], + [21478, 32768, 0], + [16384, 32768, 0], + [16384, 32768, 0], + ], + [ + [22041, 32768, 0], + [23434, 32768, 0], + [20001, 32768, 0], + [20554, 32768, 0], + [20951, 32768, 0], + [20145, 32768, 0], + [15562, 32768, 0], + [16384, 32768, 0], + [16384, 32768, 0], + ], + ], + [ + [ + [23312, 32768, 0], + [21607, 32768, 0], + [16526, 32768, 0], + [18957, 32768, 0], + [18034, 32768, 0], + [18934, 32768, 0], + [24247, 32768, 0], + [16921, 32768, 0], + [17080, 32768, 0], + ], + [ + [26579, 32768, 0], + [24910, 32768, 0], + [18637, 32768, 0], + [19800, 32768, 0], + [20388, 32768, 0], + [9887, 32768, 0], + [15642, 32768, 0], + [30198, 32768, 0], + [24721, 32768, 0], + ], + ], + [ + [ + [26998, 32768, 0], + [16737, 32768, 0], + [17838, 32768, 0], + [18922, 32768, 0], + [19515, 32768, 0], + [18636, 32768, 0], + [17333, 32768, 0], + [15776, 32768, 0], + [22658, 32768, 0], + ], + [ + [16384, 32768, 0], + [16384, 32768, 0], + [16384, 32768, 0], + [16384, 32768, 0], + [16384, 32768, 0], + [16384, 32768, 0], + [16384, 32768, 0], + [16384, 32768, 0], + [16384, 32768, 0], + ], + ], + ], + [ + [ + [ + [20177, 32768, 0], + [20789, 32768, 0], + [20262, 32768, 0], + [16384, 32768, 0], + [16384, 32768, 0], + [16384, 32768, 0], + [16384, 32768, 0], + [16384, 32768, 0], + [16384, 32768, 0], + ], + [ + [21416, 32768, 0], + [20855, 32768, 0], + [23410, 32768, 0], + [16384, 32768, 0], + [16384, 32768, 0], + [16384, 32768, 0], + [16384, 32768, 0], + [16384, 32768, 0], + [16384, 32768, 0], + ], + ], + [ + [ + [20238, 32768, 0], + [21057, 32768, 0], + [19159, 32768, 0], + [22337, 32768, 0], + [20159, 32768, 0], + [16384, 32768, 0], + [16384, 32768, 0], + [16384, 32768, 0], + [16384, 32768, 0], + ], + [ + [20125, 32768, 0], + [20559, 32768, 0], + [21707, 32768, 0], + [22296, 32768, 0], + [17333, 32768, 0], + [16384, 32768, 0], + [16384, 32768, 0], + [16384, 32768, 0], + [16384, 32768, 0], + ], + ], + [ + [ + [19941, 32768, 0], + [20527, 32768, 0], + [21470, 32768, 0], + [22487, 32768, 0], + [19558, 32768, 0], + [22354, 32768, 0], + [20331, 32768, 0], + [16384, 32768, 0], + [16384, 32768, 0], + ], + [ + [22752, 32768, 0], + [25006, 32768, 0], + [22075, 32768, 0], + [21576, 32768, 0], + [17740, 32768, 0], + [21690, 32768, 0], + [19211, 32768, 0], + [16384, 32768, 0], + [16384, 32768, 0], + ], + ], + [ + [ + [21442, 32768, 0], + [22358, 32768, 0], + [18503, 32768, 0], + [20291, 32768, 0], + [19945, 32768, 0], + [21294, 32768, 0], + [21178, 32768, 0], + [19400, 32768, 0], + [10556, 32768, 0], + ], + [ + [24648, 32768, 0], + [24949, 32768, 0], + [20708, 32768, 0], + [23905, 32768, 0], + [20501, 32768, 0], + [9558, 32768, 0], + [9423, 32768, 0], + [30365, 32768, 0], + [19253, 32768, 0], + ], + ], + [ + [ + [26064, 32768, 0], + [22098, 32768, 0], + [19613, 32768, 0], + [20525, 32768, 0], + [17595, 32768, 0], + [16618, 32768, 0], + [20497, 32768, 0], + [18989, 32768, 0], + [15513, 32768, 0], + ], + [ + [16384, 32768, 0], + [16384, 32768, 0], + [16384, 32768, 0], + [16384, 32768, 0], + [16384, 32768, 0], + [16384, 32768, 0], + [16384, 32768, 0], + [16384, 32768, 0], + [16384, 32768, 0], + ], + ], + ], +]; + +pub static DEFAULT_EOB_PT_1024_CDF: [[[u16; 12]; 2]; 4] = [ + [ + [393, 421, 751, 1623, 3160, 6352, 13345, 18047, 22571, 25830, 32768, 0], + [1865, 1988, 2930, 4242, 10533, 16538, 21354, 27255, 28546, 31784, 32768, 0], + ], + [ + [696, 948, 3145, 5702, 9706, 13217, 17851, 21856, 25692, 28034, 32768, 0], + [2672, 3591, 9330, 17084, 22725, 24284, 26527, 28027, 28377, 30876, 32768, 0], + ], + [ + [2784, 3831, 7041, 10521, 14847, 18844, 23155, 26682, 29229, 31045, 32768, 0], + [9577, 12466, 17739, 20750, 22061, 23215, 24601, 25483, 25843, 32056, 32768, 0], + ], + [ + [6698, 8334, 11961, 15762, 20186, 23862, 27434, 29326, 31082, 32050, 32768, 0], + [20569, 22426, 25569, 26859, 28053, 28913, 29486, 29724, 29807, 32570, 32768, 0], + ], +]; + +pub static DEFAULT_EOB_PT_128_CDF: [[[[u16; 9]; 2]; 2]; 4] = [ + [ + [[219, 482, 1140, 2091, 3680, 6028, 12586, 32768, 0], [371, 699, 1254, 4830, 9479, 12562, 17497, 32768, 0]], + [[5245, 7456, 12880, 15852, 20033, 23932, 27608, 32768, 0], [2054, 3472, 5869, 14232, 18242, 20590, 26752, 32768, 0]], + ], + [ + [[685, 933, 1488, 2714, 4766, 8562, 19254, 32768, 0], [217, 352, 618, 2303, 5261, 9969, 17472, 32768, 0]], + [[8045, 11200, 15497, 19595, 23948, 27408, 30938, 32768, 0], [2310, 4160, 7471, 14997, 17931, 20768, 30240, 32768, 0]], + ], + [ + [[1366, 1738, 2527, 5016, 9355, 15797, 24643, 32768, 0], [354, 558, 944, 2760, 7287, 14037, 21779, 32768, 0]], + [[13627, 16246, 20173, 24429, 27948, 30415, 31863, 32768, 0], [6275, 9889, 14769, 23164, 27988, 30493, 32272, 32768, 0]], + ], + [ + [[3472, 4885, 7489, 12481, 18517, 24536, 29635, 32768, 0], [886, 1731, 3271, 8469, 15569, 22126, 28383, 32768, 0]], + [[24313, 26062, 28385, 30107, 31217, 31898, 32345, 32768, 0], [9165, 13282, 21150, 30286, 31894, 32571, 32712, 32768, 0]], + ], +]; + +pub static DEFAULT_EOB_PT_16_CDF: [[[[u16; 6]; 2]; 2]; 4] = [ + [ + [[840, 1039, 1980, 4895, 32768, 0], [370, 671, 1883, 4471, 32768, 0]], + [[3247, 4950, 9688, 14563, 32768, 0], [1904, 3354, 7763, 14647, 32768, 0]], + ], + [ + [[2125, 2551, 5165, 8946, 32768, 0], [513, 765, 1859, 6339, 32768, 0]], + [[7637, 9498, 14259, 19108, 32768, 0], [2497, 4096, 8866, 16993, 32768, 0]], + ], + [ + [[4016, 4897, 8881, 14968, 32768, 0], [716, 1105, 2646, 10056, 32768, 0]], + [[11139, 13270, 18241, 23566, 32768, 0], [3192, 5032, 10297, 19755, 32768, 0]], + ], + [ + [[6708, 8958, 14746, 22133, 32768, 0], [1222, 2074, 4783, 15410, 32768, 0]], + [[19575, 21766, 26044, 29709, 32768, 0], [7297, 10767, 19273, 28194, 32768, 0]], + ], +]; + +pub static DEFAULT_EOB_PT_256_CDF: [[[[u16; 10]; 2]; 2]; 4] = [ + [ + [[310, 584, 1887, 3589, 6168, 8611, 11352, 15652, 32768, 0], [998, 1850, 2998, 5604, 17341, 19888, 22899, 25583, 32768, 0]], + [[2520, 3240, 5952, 8870, 12577, 17558, 19954, 24168, 32768, 0], [2203, 4130, 7435, 10739, 20652, 23681, 25609, 27261, 32768, 0]], + ], + [ + [[1448, 2109, 4151, 6263, 9329, 13260, 17944, 23300, 32768, 0], [399, 1019, 1749, 3038, 10444, 15546, 22739, 27294, 32768, 0]], + [[6402, 8148, 12623, 15072, 18728, 22847, 26447, 29377, 32768, 0], [1674, 3252, 5734, 10159, 22397, 23802, 24821, 30940, 32768, 0]], + ], + [ + [[3089, 3920, 6038, 9460, 14266, 19881, 25766, 29176, 32768, 0], [1084, 2358, 3488, 5122, 11483, 18103, 26023, 29799, 32768, 0]], + [[11514, 13794, 17480, 20754, 24361, 27378, 29492, 31277, 32768, 0], [6571, 9610, 15516, 21826, 29092, 30829, 31842, 32708, 32768, 0]], + ], + [ + [[5348, 7113, 11820, 15924, 22106, 26777, 30334, 31757, 32768, 0], [2453, 4474, 6307, 8777, 16474, 22975, 29000, 31547, 32768, 0]], + [[23110, 24597, 27140, 28894, 30167, 30927, 31392, 32094, 32768, 0], [9998, 17661, 25178, 28097, 31308, 32038, 32403, 32695, 32768, 0]], + ], +]; + +pub static DEFAULT_EOB_PT_32_CDF: [[[[u16; 7]; 2]; 2]; 4] = [ + [ + [[400, 520, 977, 2102, 6542, 32768, 0], [210, 405, 1315, 3326, 7537, 32768, 0]], + [[2636, 4273, 7588, 11794, 20401, 32768, 0], [1786, 3179, 6902, 11357, 19054, 32768, 0]], + ], + [ + [[989, 1249, 2019, 4151, 10785, 32768, 0], [313, 441, 1099, 2917, 8562, 32768, 0]], + [[8394, 10352, 13932, 18855, 26014, 32768, 0], [2578, 4124, 8181, 13670, 24234, 32768, 0]], + ], + [ + [[2515, 3003, 4452, 8162, 16041, 32768, 0], [574, 821, 1836, 5089, 13128, 32768, 0]], + [[13468, 16303, 20361, 25105, 29281, 32768, 0], [3542, 5502, 10415, 16760, 25644, 32768, 0]], + ], + [ + [[4617, 5709, 8446, 13584, 23135, 32768, 0], [1156, 1702, 3675, 9274, 20539, 32768, 0]], + [[22086, 24282, 27010, 29770, 31743, 32768, 0], [7699, 10897, 20891, 26926, 31628, 32768, 0]], + ], +]; + +pub static DEFAULT_EOB_PT_512_CDF: [[[u16; 11]; 2]; 4] = [ + [[641, 983, 3707, 5430, 10234, 14958, 18788, 23412, 26061, 32768, 0], [5095, 6446, 9996, 13354, 16017, 17986, 20919, 26129, 29140, 32768, 0]], + [[1230, 2278, 5035, 7776, 11871, 15346, 19590, 24584, 28749, 32768, 0], [7265, 9979, 15819, 19250, 21780, 23846, 26478, 28396, 31811, 32768, 0]], + [ + [2624, 3936, 6480, 9686, 13979, 17726, 23267, 28410, 31078, 32768, 0], + [12015, 14769, 19588, 22052, 24222, 25812, 27300, 29219, 32114, 32768, 0], + ], + [ + [5927, 7809, 10923, 14597, 19439, 24135, 28456, 31142, 32060, 32768, 0], + [21093, 23043, 25742, 27658, 29097, 29716, 30073, 30820, 31956, 32768, 0], + ], +]; + +pub static DEFAULT_EOB_PT_64_CDF: [[[[u16; 8]; 2]; 2]; 4] = [ + [ + [[329, 498, 1101, 1784, 3265, 7758, 32768, 0], [335, 730, 1459, 5494, 8755, 12997, 32768, 0]], + [[3505, 5304, 10086, 13814, 17684, 23370, 32768, 0], [1563, 2700, 4876, 10911, 14706, 22480, 32768, 0]], + ], + [ + [[1260, 1446, 2253, 3712, 6652, 13369, 32768, 0], [401, 605, 1029, 2563, 5845, 12626, 32768, 0]], + [[8609, 10612, 14624, 18714, 22614, 29024, 32768, 0], [1923, 3127, 5867, 9703, 14277, 27100, 32768, 0]], + ], + [ + [[2374, 2772, 4583, 7276, 12288, 19706, 32768, 0], [497, 810, 1315, 3000, 7004, 15641, 32768, 0]], + [[15050, 17126, 21410, 24886, 28156, 30726, 32768, 0], [4034, 6290, 10235, 14982, 21214, 28491, 32768, 0]], + ], + [ + [[6307, 7541, 12060, 16358, 22553, 27865, 32768, 0], [1289, 2320, 3971, 7926, 14153, 24291, 32768, 0]], + [[24212, 25708, 28268, 30035, 31307, 32049, 32768, 0], [8726, 12378, 19409, 26450, 30038, 32462, 32768, 0]], + ], +]; + +pub static DEFAULT_INTER_TX_TYPE_SET3_CDF: [[u16; 3]; 4] = [[16384, 32768, 0], [4167, 32768, 0], [1998, 32768, 0], [748, 32768, 0]]; + +pub static DEFAULT_INTRA_FRAME_Y_MODE_CDF: [[[u16; 14]; 5]; 5] = [ + [ + [15588, 17027, 19338, 20218, 20682, 21110, 21825, 23244, 24189, 28165, 29093, 30466, 32768, 0], + [12016, 18066, 19516, 20303, 20719, 21444, 21888, 23032, 24434, 28658, 30172, 31409, 32768, 0], + [10052, 10771, 22296, 22788, 23055, 23239, 24133, 25620, 26160, 29336, 29929, 31567, 32768, 0], + [14091, 15406, 16442, 18808, 19136, 19546, 19998, 22096, 24746, 29585, 30958, 32462, 32768, 0], + [12122, 13265, 15603, 16501, 18609, 20033, 22391, 25583, 26437, 30261, 31073, 32475, 32768, 0], + ], + [ + [10023, 19585, 20848, 21440, 21832, 22760, 23089, 24023, 25381, 29014, 30482, 31436, 32768, 0], + [5983, 24099, 24560, 24886, 25066, 25795, 25913, 26423, 27610, 29905, 31276, 31794, 32768, 0], + [7444, 12781, 20177, 20728, 21077, 21607, 22170, 23405, 24469, 27915, 29090, 30492, 32768, 0], + [8537, 14689, 15432, 17087, 17408, 18172, 18408, 19825, 24649, 29153, 31096, 32210, 32768, 0], + [7543, 14231, 15496, 16195, 17905, 20717, 21984, 24516, 26001, 29675, 30981, 31994, 32768, 0], + ], + [ + [12613, 13591, 21383, 22004, 22312, 22577, 23401, 25055, 25729, 29538, 30305, 32077, 32768, 0], + [9687, 13470, 18506, 19230, 19604, 20147, 20695, 22062, 23219, 27743, 29211, 30907, 32768, 0], + [6183, 6505, 26024, 26252, 26366, 26434, 27082, 28354, 28555, 30467, 30794, 32086, 32768, 0], + [10718, 11734, 14954, 17224, 17565, 17924, 18561, 21523, 23878, 28975, 30287, 32252, 32768, 0], + [9194, 9858, 16501, 17263, 18424, 19171, 21563, 25961, 26561, 30072, 30737, 32463, 32768, 0], + ], + [ + [12602, 14399, 15488, 18381, 18778, 19315, 19724, 21419, 25060, 29696, 30917, 32409, 32768, 0], + [8203, 13821, 14524, 17105, 17439, 18131, 18404, 19468, 25225, 29485, 31158, 32342, 32768, 0], + [8451, 9731, 15004, 17643, 18012, 18425, 19070, 21538, 24605, 29118, 30078, 32018, 32768, 0], + [7714, 9048, 9516, 16667, 16817, 16994, 17153, 18767, 26743, 30389, 31536, 32528, 32768, 0], + [8843, 10280, 11496, 15317, 16652, 17943, 19108, 22718, 25769, 29953, 30983, 32485, 32768, 0], + ], + [ + [12578, 13671, 15979, 16834, 19075, 20913, 22989, 25449, 26219, 30214, 31150, 32477, 32768, 0], + [9563, 13626, 15080, 15892, 17756, 20863, 22207, 24236, 25380, 29653, 31143, 32277, 32768, 0], + [8356, 8901, 17616, 18256, 19350, 20106, 22598, 25947, 26466, 29900, 30523, 32261, 32768, 0], + [10835, 11815, 13124, 16042, 17018, 18039, 18947, 22753, 24615, 29489, 30883, 32482, 32768, 0], + [7618, 8288, 9859, 10509, 15386, 18657, 22903, 28776, 29180, 31355, 31802, 32593, 32768, 0], + ], +]; + +pub static DEFAULT_INTRA_TX_TYPE_SET2_CDF: [[[u16; 6]; 13]; 3] = [ + [ + [6554, 13107, 19661, 26214, 32768, 0], + [6554, 13107, 19661, 26214, 32768, 0], + [6554, 13107, 19661, 26214, 32768, 0], + [6554, 13107, 19661, 26214, 32768, 0], + [6554, 13107, 19661, 26214, 32768, 0], + [6554, 13107, 19661, 26214, 32768, 0], + [6554, 13107, 19661, 26214, 32768, 0], + [6554, 13107, 19661, 26214, 32768, 0], + [6554, 13107, 19661, 26214, 32768, 0], + [6554, 13107, 19661, 26214, 32768, 0], + [6554, 13107, 19661, 26214, 32768, 0], + [6554, 13107, 19661, 26214, 32768, 0], + [6554, 13107, 19661, 26214, 32768, 0], + ], + [ + [6554, 13107, 19661, 26214, 32768, 0], + [6554, 13107, 19661, 26214, 32768, 0], + [6554, 13107, 19661, 26214, 32768, 0], + [6554, 13107, 19661, 26214, 32768, 0], + [6554, 13107, 19661, 26214, 32768, 0], + [6554, 13107, 19661, 26214, 32768, 0], + [6554, 13107, 19661, 26214, 32768, 0], + [6554, 13107, 19661, 26214, 32768, 0], + [6554, 13107, 19661, 26214, 32768, 0], + [6554, 13107, 19661, 26214, 32768, 0], + [6554, 13107, 19661, 26214, 32768, 0], + [6554, 13107, 19661, 26214, 32768, 0], + [6554, 13107, 19661, 26214, 32768, 0], + ], + [ + [1127, 12814, 22772, 27483, 32768, 0], + [145, 6761, 11980, 26667, 32768, 0], + [362, 5887, 11678, 16725, 32768, 0], + [385, 15213, 18587, 30693, 32768, 0], + [25, 2914, 23134, 27903, 32768, 0], + [60, 4470, 11749, 23991, 32768, 0], + [37, 3332, 14511, 21448, 32768, 0], + [157, 6320, 13036, 17439, 32768, 0], + [119, 6719, 12906, 29396, 32768, 0], + [47, 5537, 12576, 21499, 32768, 0], + [269, 6076, 11258, 23115, 32768, 0], + [83, 5615, 12001, 17228, 32768, 0], + [1968, 5556, 12023, 18547, 32768, 0], + ], +]; + +pub static DEFAULT_IS_INTER_CDF: [[u16; 3]; 4] = [[806, 32768, 0], [16662, 32768, 0], [20186, 32768, 0], [26538, 32768, 0]]; + +pub static DEFAULT_MV_BIT_CDF: [[u16; 3]; 10] = [ + [17408, 32768, 0], + [17920, 32768, 0], + [18944, 32768, 0], + [20480, 32768, 0], + [22528, 32768, 0], + [24576, 32768, 0], + [28672, 32768, 0], + [29952, 32768, 0], + [29952, 32768, 0], + [30720, 32768, 0], +]; + +pub static DEFAULT_MV_CLASS0_BIT_CDF: [u16; 3] = [27648, 32768, 0]; + +pub static DEFAULT_MV_CLASS0_FR_CDF: [[[u16; 5]; 2]; 2] = + [[[16384, 24576, 26624, 32768, 0], [12288, 21248, 24128, 32768, 0]], [[16384, 24576, 26624, 32768, 0], [12288, 21248, 24128, 32768, 0]]]; + +pub static DEFAULT_MV_CLASS0_HP_CDF: [u16; 3] = [20480, 32768, 0]; + +pub static DEFAULT_MV_CLASS_CDF: [[u16; 12]; 2] = [ + [28672, 30976, 31858, 32320, 32551, 32656, 32740, 32757, 32762, 32767, 32768, 0], + [28672, 30976, 31858, 32320, 32551, 32656, 32740, 32757, 32762, 32767, 32768, 0], +]; + +pub static DEFAULT_MV_FR_CDF: [[u16; 5]; 2] = [[8192, 17408, 21248, 32768, 0], [8192, 17408, 21248, 32768, 0]]; + +pub static DEFAULT_MV_HP_CDF: [u16; 3] = [16384, 32768, 0]; + +pub static DEFAULT_MV_JOINT_CDF: [u16; 5] = [4096, 11264, 19328, 32768, 0]; + +pub static DEFAULT_MV_SIGN_CDF: [u16; 3] = [16384, 32768, 0]; + +pub static DEFAULT_NEW_MV_CDF: [[u16; 3]; 6] = + [[24035, 32768, 0], [16630, 32768, 0], [15339, 32768, 0], [8386, 32768, 0], [12222, 32768, 0], [4676, 32768, 0]]; + +pub static DEFAULT_PARTITION_W16_CDF: [[u16; 11]; 4] = [ + [15597, 20929, 24571, 26706, 27664, 28821, 29601, 30571, 31902, 32768, 0], + [7925, 11043, 16785, 22470, 23971, 25043, 26651, 28701, 29834, 32768, 0], + [5414, 13269, 15111, 20488, 22360, 24500, 25537, 26336, 32117, 32768, 0], + [2662, 6362, 8614, 20860, 23053, 24778, 26436, 27829, 31171, 32768, 0], +]; + +pub static DEFAULT_PARTITION_W32_CDF: [[u16; 11]; 4] = [ + [18462, 20920, 23124, 27647, 28227, 29049, 29519, 30178, 31544, 32768, 0], + [7689, 9060, 12056, 24992, 25660, 26182, 26951, 28041, 29052, 32768, 0], + [6015, 9009, 10062, 24544, 25409, 26545, 27071, 27526, 32047, 32768, 0], + [1394, 2208, 2796, 28614, 29061, 29466, 29840, 30185, 31899, 32768, 0], +]; + +pub static DEFAULT_PARTITION_W64_CDF: [[u16; 11]; 4] = [ + [20137, 21547, 23078, 29566, 29837, 30261, 30524, 30892, 31724, 32768, 0], + [6732, 7490, 9497, 27944, 28250, 28515, 28969, 29630, 30104, 32768, 0], + [5945, 7663, 8348, 28683, 29117, 29749, 30064, 30298, 32238, 32768, 0], + [870, 1212, 1487, 31198, 31394, 31574, 31743, 31881, 32332, 32768, 0], +]; + +pub static DEFAULT_PARTITION_W8_CDF: [[u16; 5]; 4] = + [[19132, 25510, 30392, 32768, 0], [13928, 19855, 28540, 32768, 0], [12522, 23679, 28629, 32768, 0], [9896, 18783, 25853, 32768, 0]]; + +pub static DEFAULT_REF_MV_CDF: [[u16; 3]; 6] = + [[23974, 32768, 0], [24188, 32768, 0], [17848, 32768, 0], [28622, 32768, 0], [24312, 32768, 0], [19923, 32768, 0]]; + +pub static DEFAULT_SCAN_16X16: [u16; 256] = [ + 0, 1, 16, 32, 17, 2, 3, 18, 33, 48, 64, 49, 34, 19, 4, 5, 20, 35, 50, 65, 80, 96, 81, 66, 51, 36, 21, 6, 7, 22, 37, 52, 67, 82, 97, 112, 128, + 113, 98, 83, 68, 53, 38, 23, 8, 9, 24, 39, 54, 69, 84, 99, 114, 129, 144, 160, 145, 130, 115, 100, 85, 70, 55, 40, 25, 10, 11, 26, 41, 56, 71, + 86, 101, 116, 131, 146, 161, 176, 192, 177, 162, 147, 132, 117, 102, 87, 72, 57, 42, 27, 12, 13, 28, 43, 58, 73, 88, 103, 118, 133, 148, 163, + 178, 193, 208, 224, 209, 194, 179, 164, 149, 134, 119, 104, 89, 74, 59, 44, 29, 14, 15, 30, 45, 60, 75, 90, 105, 120, 135, 150, 165, 180, 195, + 210, 225, 240, 241, 226, 211, 196, 181, 166, 151, 136, 121, 106, 91, 76, 61, 46, 31, 47, 62, 77, 92, 107, 122, 137, 152, 167, 182, 197, 212, 227, + 242, 243, 228, 213, 198, 183, 168, 153, 138, 123, 108, 93, 78, 63, 79, 94, 109, 124, 139, 154, 169, 184, 199, 214, 229, 244, 245, 230, 215, 200, + 185, 170, 155, 140, 125, 110, 95, 111, 126, 141, 156, 171, 186, 201, 216, 231, 246, 247, 232, 217, 202, 187, 172, 157, 142, 127, 143, 158, 173, + 188, 203, 218, 233, 248, 249, 234, 219, 204, 189, 174, 159, 175, 190, 205, 220, 235, 250, 251, 236, 221, 206, 191, 207, 222, 237, 252, 253, 238, + 223, 239, 254, 255, +]; + +pub static DEFAULT_SCAN_32X32: [u16; 1024] = [ + 0, 1, 32, 64, 33, 2, 3, 34, 65, 96, 128, 97, 66, 35, 4, 5, 36, 67, 98, 129, 160, 192, 161, 130, 99, 68, 37, 6, 7, 38, 69, 100, 131, 162, 193, + 224, 256, 225, 194, 163, 132, 101, 70, 39, 8, 9, 40, 71, 102, 133, 164, 195, 226, 257, 288, 320, 289, 258, 227, 196, 165, 134, 103, 72, 41, 10, + 11, 42, 73, 104, 135, 166, 197, 228, 259, 290, 321, 352, 384, 353, 322, 291, 260, 229, 198, 167, 136, 105, 74, 43, 12, 13, 44, 75, 106, 137, 168, + 199, 230, 261, 292, 323, 354, 385, 416, 448, 417, 386, 355, 324, 293, 262, 231, 200, 169, 138, 107, 76, 45, 14, 15, 46, 77, 108, 139, 170, 201, + 232, 263, 294, 325, 356, 387, 418, 449, 480, 512, 481, 450, 419, 388, 357, 326, 295, 264, 233, 202, 171, 140, 109, 78, 47, 16, 17, 48, 79, 110, + 141, 172, 203, 234, 265, 296, 327, 358, 389, 420, 451, 482, 513, 544, 576, 545, 514, 483, 452, 421, 390, 359, 328, 297, 266, 235, 204, 173, 142, + 111, 80, 49, 18, 19, 50, 81, 112, 143, 174, 205, 236, 267, 298, 329, 360, 391, 422, 453, 484, 515, 546, 577, 608, 640, 609, 578, 547, 516, 485, + 454, 423, 392, 361, 330, 299, 268, 237, 206, 175, 144, 113, 82, 51, 20, 21, 52, 83, 114, 145, 176, 207, 238, 269, 300, 331, 362, 393, 424, 455, + 486, 517, 548, 579, 610, 641, 672, 704, 673, 642, 611, 580, 549, 518, 487, 456, 425, 394, 363, 332, 301, 270, 239, 208, 177, 146, 115, 84, 53, + 22, 23, 54, 85, 116, 147, 178, 209, 240, 271, 302, 333, 364, 395, 426, 457, 488, 519, 550, 581, 612, 643, 674, 705, 736, 768, 737, 706, 675, 644, + 613, 582, 551, 520, 489, 458, 427, 396, 365, 334, 303, 272, 241, 210, 179, 148, 117, 86, 55, 24, 25, 56, 87, 118, 149, 180, 211, 242, 273, 304, + 335, 366, 397, 428, 459, 490, 521, 552, 583, 614, 645, 676, 707, 738, 769, 800, 832, 801, 770, 739, 708, 677, 646, 615, 584, 553, 522, 491, 460, + 429, 398, 367, 336, 305, 274, 243, 212, 181, 150, 119, 88, 57, 26, 27, 58, 89, 120, 151, 182, 213, 244, 275, 306, 337, 368, 399, 430, 461, 492, + 523, 554, 585, 616, 647, 678, 709, 740, 771, 802, 833, 864, 896, 865, 834, 803, 772, 741, 710, 679, 648, 617, 586, 555, 524, 493, 462, 431, 400, + 369, 338, 307, 276, 245, 214, 183, 152, 121, 90, 59, 28, 29, 60, 91, 122, 153, 184, 215, 246, 277, 308, 339, 370, 401, 432, 463, 494, 525, 556, + 587, 618, 649, 680, 711, 742, 773, 804, 835, 866, 897, 928, 960, 929, 898, 867, 836, 805, 774, 743, 712, 681, 650, 619, 588, 557, 526, 495, 464, + 433, 402, 371, 340, 309, 278, 247, 216, 185, 154, 123, 92, 61, 30, 31, 62, 93, 124, 155, 186, 217, 248, 279, 310, 341, 372, 403, 434, 465, 496, + 527, 558, 589, 620, 651, 682, 713, 744, 775, 806, 837, 868, 899, 930, 961, 992, 993, 962, 931, 900, 869, 838, 807, 776, 745, 714, 683, 652, 621, + 590, 559, 528, 497, 466, 435, 404, 373, 342, 311, 280, 249, 218, 187, 156, 125, 94, 63, 95, 126, 157, 188, 219, 250, 281, 312, 343, 374, 405, + 436, 467, 498, 529, 560, 591, 622, 653, 684, 715, 746, 777, 808, 839, 870, 901, 932, 963, 994, 995, 964, 933, 902, 871, 840, 809, 778, 747, 716, + 685, 654, 623, 592, 561, 530, 499, 468, 437, 406, 375, 344, 313, 282, 251, 220, 189, 158, 127, 159, 190, 221, 252, 283, 314, 345, 376, 407, 438, + 469, 500, 531, 562, 593, 624, 655, 686, 717, 748, 779, 810, 841, 872, 903, 934, 965, 996, 997, 966, 935, 904, 873, 842, 811, 780, 749, 718, 687, + 656, 625, 594, 563, 532, 501, 470, 439, 408, 377, 346, 315, 284, 253, 222, 191, 223, 254, 285, 316, 347, 378, 409, 440, 471, 502, 533, 564, 595, + 626, 657, 688, 719, 750, 781, 812, 843, 874, 905, 936, 967, 998, 999, 968, 937, 906, 875, 844, 813, 782, 751, 720, 689, 658, 627, 596, 565, 534, + 503, 472, 441, 410, 379, 348, 317, 286, 255, 287, 318, 349, 380, 411, 442, 473, 504, 535, 566, 597, 628, 659, 690, 721, 752, 783, 814, 845, 876, + 907, 938, 969, 1000, 1001, 970, 939, 908, 877, 846, 815, 784, 753, 722, 691, 660, 629, 598, 567, 536, 505, 474, 443, 412, 381, 350, 319, 351, + 382, 413, 444, 475, 506, 537, 568, 599, 630, 661, 692, 723, 754, 785, 816, 847, 878, 909, 940, 971, 1002, 1003, 972, 941, 910, 879, 848, 817, + 786, 755, 724, 693, 662, 631, 600, 569, 538, 507, 476, 445, 414, 383, 415, 446, 477, 508, 539, 570, 601, 632, 663, 694, 725, 756, 787, 818, 849, + 880, 911, 942, 973, 1004, 1005, 974, 943, 912, 881, 850, 819, 788, 757, 726, 695, 664, 633, 602, 571, 540, 509, 478, 447, 479, 510, 541, 572, + 603, 634, 665, 696, 727, 758, 789, 820, 851, 882, 913, 944, 975, 1006, 1007, 976, 945, 914, 883, 852, 821, 790, 759, 728, 697, 666, 635, 604, + 573, 542, 511, 543, 574, 605, 636, 667, 698, 729, 760, 791, 822, 853, 884, 915, 946, 977, 1008, 1009, 978, 947, 916, 885, 854, 823, 792, 761, + 730, 699, 668, 637, 606, 575, 607, 638, 669, 700, 731, 762, 793, 824, 855, 886, 917, 948, 979, 1010, 1011, 980, 949, 918, 887, 856, 825, 794, + 763, 732, 701, 670, 639, 671, 702, 733, 764, 795, 826, 857, 888, 919, 950, 981, 1012, 1013, 982, 951, 920, 889, 858, 827, 796, 765, 734, 703, + 735, 766, 797, 828, 859, 890, 921, 952, 983, 1014, 1015, 984, 953, 922, 891, 860, 829, 798, 767, 799, 830, 861, 892, 923, 954, 985, 1016, 1017, + 986, 955, 924, 893, 862, 831, 863, 894, 925, 956, 987, 1018, 1019, 988, 957, 926, 895, 927, 958, 989, 1020, 1021, 990, 959, 991, 1022, 1023, +]; + +pub static DEFAULT_SCAN_4X4: [u16; 16] = [0, 1, 4, 8, 5, 2, 3, 6, 9, 12, 13, 10, 7, 11, 14, 15]; + +pub static DEFAULT_SCAN_8X8: [u16; 64] = [ + 0, 1, 8, 16, 9, 2, 3, 10, 17, 24, 32, 25, 18, 11, 4, 5, 12, 19, 26, 33, 40, 48, 41, 34, 27, 20, 13, 6, 7, 14, 21, 28, 35, 42, 49, 56, 57, 50, 43, + 36, 29, 22, 15, 23, 30, 37, 44, 51, 58, 59, 52, 45, 38, 31, 39, 46, 53, 60, 61, 54, 47, 55, 62, 63, +]; + +pub static DEFAULT_SINGLE_REF_CDF: [[[u16; 3]; 6]; 3] = [ + [[4897, 32768, 0], [1555, 32768, 0], [4236, 32768, 0], [8650, 32768, 0], [904, 32768, 0], [1444, 32768, 0]], + [[16973, 32768, 0], [16751, 32768, 0], [19647, 32768, 0], [24773, 32768, 0], [11014, 32768, 0], [15087, 32768, 0]], + [[29744, 32768, 0], [30279, 32768, 0], [31194, 32768, 0], [31895, 32768, 0], [26875, 32768, 0], [30304, 32768, 0]], +]; + +pub static DEFAULT_SKIP_CDF: [[u16; 3]; 3] = [[31671, 32768, 0], [16515, 32768, 0], [4576, 32768, 0]]; + +pub static DEFAULT_TXB_SKIP_CDF: [[[[u16; 3]; 13]; 5]; 4] = [ + [ + [ + [31849, 32768, 0], + [5892, 32768, 0], + [12112, 32768, 0], + [21935, 32768, 0], + [20289, 32768, 0], + [27473, 32768, 0], + [32487, 32768, 0], + [7654, 32768, 0], + [19473, 32768, 0], + [29984, 32768, 0], + [9961, 32768, 0], + [30242, 32768, 0], + [32117, 32768, 0], + ], + [ + [31548, 32768, 0], + [1549, 32768, 0], + [10130, 32768, 0], + [16656, 32768, 0], + [18591, 32768, 0], + [26308, 32768, 0], + [32537, 32768, 0], + [5403, 32768, 0], + [18096, 32768, 0], + [30003, 32768, 0], + [16384, 32768, 0], + [16384, 32768, 0], + [16384, 32768, 0], + ], + [ + [29957, 32768, 0], + [5391, 32768, 0], + [18039, 32768, 0], + [23566, 32768, 0], + [22431, 32768, 0], + [25822, 32768, 0], + [32197, 32768, 0], + [3778, 32768, 0], + [15336, 32768, 0], + [28981, 32768, 0], + [16384, 32768, 0], + [16384, 32768, 0], + [16384, 32768, 0], + ], + [ + [17920, 32768, 0], + [1818, 32768, 0], + [7282, 32768, 0], + [25273, 32768, 0], + [10923, 32768, 0], + [31554, 32768, 0], + [32624, 32768, 0], + [1366, 32768, 0], + [15628, 32768, 0], + [30462, 32768, 0], + [146, 32768, 0], + [5132, 32768, 0], + [31657, 32768, 0], + ], + [ + [6308, 32768, 0], + [117, 32768, 0], + [1638, 32768, 0], + [2161, 32768, 0], + [16384, 32768, 0], + [10923, 32768, 0], + [30247, 32768, 0], + [16384, 32768, 0], + [16384, 32768, 0], + [16384, 32768, 0], + [16384, 32768, 0], + [16384, 32768, 0], + [16384, 32768, 0], + ], + ], + [ + [ + [30371, 32768, 0], + [7570, 32768, 0], + [13155, 32768, 0], + [20751, 32768, 0], + [20969, 32768, 0], + [27067, 32768, 0], + [32013, 32768, 0], + [5495, 32768, 0], + [17942, 32768, 0], + [28280, 32768, 0], + [16384, 32768, 0], + [16384, 32768, 0], + [16384, 32768, 0], + ], + [ + [31782, 32768, 0], + [1836, 32768, 0], + [10689, 32768, 0], + [17604, 32768, 0], + [21622, 32768, 0], + [27518, 32768, 0], + [32399, 32768, 0], + [4419, 32768, 0], + [16294, 32768, 0], + [28345, 32768, 0], + [16384, 32768, 0], + [16384, 32768, 0], + [16384, 32768, 0], + ], + [ + [31901, 32768, 0], + [10311, 32768, 0], + [18047, 32768, 0], + [24806, 32768, 0], + [23288, 32768, 0], + [27914, 32768, 0], + [32296, 32768, 0], + [4215, 32768, 0], + [15756, 32768, 0], + [28341, 32768, 0], + [16384, 32768, 0], + [16384, 32768, 0], + [16384, 32768, 0], + ], + [ + [26726, 32768, 0], + [1045, 32768, 0], + [11703, 32768, 0], + [20590, 32768, 0], + [18554, 32768, 0], + [25970, 32768, 0], + [31938, 32768, 0], + [5583, 32768, 0], + [21313, 32768, 0], + [29390, 32768, 0], + [641, 32768, 0], + [22265, 32768, 0], + [31452, 32768, 0], + ], + [ + [26584, 32768, 0], + [188, 32768, 0], + [8847, 32768, 0], + [24519, 32768, 0], + [22938, 32768, 0], + [30583, 32768, 0], + [32608, 32768, 0], + [16384, 32768, 0], + [16384, 32768, 0], + [16384, 32768, 0], + [16384, 32768, 0], + [16384, 32768, 0], + [16384, 32768, 0], + ], + ], + [ + [ + [29614, 32768, 0], + [9068, 32768, 0], + [12924, 32768, 0], + [19538, 32768, 0], + [17737, 32768, 0], + [24619, 32768, 0], + [30642, 32768, 0], + [4119, 32768, 0], + [16026, 32768, 0], + [25657, 32768, 0], + [16384, 32768, 0], + [16384, 32768, 0], + [16384, 32768, 0], + ], + [ + [31957, 32768, 0], + [3230, 32768, 0], + [11153, 32768, 0], + [18123, 32768, 0], + [20143, 32768, 0], + [26536, 32768, 0], + [31986, 32768, 0], + [3050, 32768, 0], + [14603, 32768, 0], + [25155, 32768, 0], + [16384, 32768, 0], + [16384, 32768, 0], + [16384, 32768, 0], + ], + [ + [32363, 32768, 0], + [10692, 32768, 0], + [19090, 32768, 0], + [24357, 32768, 0], + [24442, 32768, 0], + [28312, 32768, 0], + [32169, 32768, 0], + [3648, 32768, 0], + [15690, 32768, 0], + [26815, 32768, 0], + [16384, 32768, 0], + [16384, 32768, 0], + [16384, 32768, 0], + ], + [ + [30669, 32768, 0], + [3832, 32768, 0], + [11663, 32768, 0], + [18889, 32768, 0], + [19782, 32768, 0], + [23313, 32768, 0], + [31330, 32768, 0], + [5124, 32768, 0], + [18719, 32768, 0], + [28468, 32768, 0], + [3082, 32768, 0], + [20982, 32768, 0], + [29443, 32768, 0], + ], + [ + [28573, 32768, 0], + [3183, 32768, 0], + [17802, 32768, 0], + [25977, 32768, 0], + [26677, 32768, 0], + [27832, 32768, 0], + [32387, 32768, 0], + [16384, 32768, 0], + [16384, 32768, 0], + [16384, 32768, 0], + [16384, 32768, 0], + [16384, 32768, 0], + [16384, 32768, 0], + ], + ], + [ + [ + [26887, 32768, 0], + [6729, 32768, 0], + [10361, 32768, 0], + [17442, 32768, 0], + [15045, 32768, 0], + [22478, 32768, 0], + [29072, 32768, 0], + [2713, 32768, 0], + [11861, 32768, 0], + [20773, 32768, 0], + [16384, 32768, 0], + [16384, 32768, 0], + [16384, 32768, 0], + ], + [ + [31903, 32768, 0], + [2044, 32768, 0], + [7528, 32768, 0], + [14618, 32768, 0], + [16182, 32768, 0], + [24168, 32768, 0], + [31037, 32768, 0], + [2786, 32768, 0], + [11194, 32768, 0], + [20155, 32768, 0], + [16384, 32768, 0], + [16384, 32768, 0], + [16384, 32768, 0], + ], + [ + [32510, 32768, 0], + [8430, 32768, 0], + [17318, 32768, 0], + [24154, 32768, 0], + [23674, 32768, 0], + [28789, 32768, 0], + [32139, 32768, 0], + [3440, 32768, 0], + [13117, 32768, 0], + [22702, 32768, 0], + [16384, 32768, 0], + [16384, 32768, 0], + [16384, 32768, 0], + ], + [ + [31671, 32768, 0], + [2056, 32768, 0], + [11746, 32768, 0], + [16852, 32768, 0], + [18635, 32768, 0], + [24715, 32768, 0], + [31484, 32768, 0], + [4656, 32768, 0], + [16074, 32768, 0], + [24704, 32768, 0], + [1806, 32768, 0], + [14645, 32768, 0], + [25336, 32768, 0], + ], + [ + [31539, 32768, 0], + [8433, 32768, 0], + [20576, 32768, 0], + [27904, 32768, 0], + [27852, 32768, 0], + [30026, 32768, 0], + [32441, 32768, 0], + [16384, 32768, 0], + [16384, 32768, 0], + [16384, 32768, 0], + [16384, 32768, 0], + [16384, 32768, 0], + [16384, 32768, 0], + ], + ], +]; + +pub static DEFAULT_UV_MODE_CFL_ALLOWED_CDF: [[u16; 15]; 13] = [ + [10407, 11208, 12900, 13181, 13823, 14175, 14899, 15656, 15986, 20086, 20995, 22455, 24212, 32768, 0], + [4532, 19780, 20057, 20215, 20428, 21071, 21199, 21451, 22099, 24228, 24693, 27032, 29472, 32768, 0], + [5273, 5379, 20177, 20270, 20385, 20439, 20949, 21695, 21774, 23138, 24256, 24703, 26679, 32768, 0], + [6740, 7167, 7662, 14152, 14536, 14785, 15034, 16741, 18371, 21520, 22206, 23389, 24182, 32768, 0], + [4987, 5368, 5928, 6068, 19114, 20315, 21857, 22253, 22411, 24911, 25380, 26027, 26376, 32768, 0], + [5370, 6889, 7247, 7393, 9498, 21114, 21402, 21753, 21981, 24780, 25386, 26517, 27176, 32768, 0], + [4816, 4961, 7204, 7326, 8765, 8930, 20169, 20682, 20803, 23188, 23763, 24455, 24940, 32768, 0], + [6608, 6740, 8529, 9049, 9257, 9356, 9735, 18827, 19059, 22336, 23204, 23964, 24793, 32768, 0], + [5998, 7419, 7781, 8933, 9255, 9549, 9753, 10417, 18898, 22494, 23139, 24764, 25989, 32768, 0], + [10660, 11298, 12550, 12957, 13322, 13624, 14040, 15004, 15534, 20714, 21789, 23443, 24861, 32768, 0], + [10522, 11530, 12552, 12963, 13378, 13779, 14245, 15235, 15902, 20102, 22696, 23774, 25838, 32768, 0], + [10099, 10691, 12639, 13049, 13386, 13665, 14125, 15163, 15636, 19676, 20474, 23519, 25208, 32768, 0], + [3144, 5087, 7382, 7504, 7593, 7690, 7801, 8064, 8232, 9248, 9875, 10521, 29048, 32768, 0], +]; + +pub static DEFAULT_UV_MODE_CFL_NOT_ALLOWED_CDF: [[u16; 14]; 13] = [ + [22631, 24152, 25378, 25661, 25986, 26520, 27055, 27923, 28244, 30059, 30941, 31961, 32768, 0], + [9513, 26881, 26973, 27046, 27118, 27664, 27739, 27824, 28359, 29505, 29800, 31796, 32768, 0], + [9845, 9915, 28663, 28704, 28757, 28780, 29198, 29822, 29854, 30764, 31777, 32029, 32768, 0], + [13639, 13897, 14171, 25331, 25606, 25727, 25953, 27148, 28577, 30612, 31355, 32493, 32768, 0], + [9764, 9835, 9930, 9954, 25386, 27053, 27958, 28148, 28243, 31101, 31744, 32363, 32768, 0], + [11825, 13589, 13677, 13720, 15048, 29213, 29301, 29458, 29711, 31161, 31441, 32550, 32768, 0], + [14175, 14399, 16608, 16821, 17718, 17775, 28551, 30200, 30245, 31837, 32342, 32667, 32768, 0], + [12885, 13038, 14978, 15590, 15673, 15748, 16176, 29128, 29267, 30643, 31961, 32461, 32768, 0], + [12026, 13661, 13874, 15305, 15490, 15726, 15995, 16273, 28443, 30388, 30767, 32416, 32768, 0], + [19052, 19840, 20579, 20916, 21150, 21467, 21885, 22719, 23174, 28861, 30379, 32175, 32768, 0], + [18627, 19649, 20974, 21219, 21492, 21816, 22199, 23119, 23527, 27053, 31397, 32148, 32768, 0], + [17026, 19004, 19997, 20339, 20586, 21103, 21349, 21907, 22482, 25896, 26541, 31819, 32768, 0], + [12124, 13759, 14959, 14992, 15007, 15051, 15078, 15166, 15255, 15753, 16039, 16606, 32768, 0], +]; + +pub static DEFAULT_Y_MODE_CDF: [[u16; 14]; 4] = [ + [22801, 23489, 24293, 24756, 25601, 26123, 26606, 27418, 27945, 29228, 29685, 30349, 32768, 0], + [18673, 19845, 22631, 23318, 23950, 24649, 25527, 27364, 28152, 29701, 29984, 30852, 32768, 0], + [19770, 20979, 23396, 23939, 24241, 24654, 25136, 27073, 27830, 29360, 29730, 30659, 32768, 0], + [20155, 21301, 22838, 23178, 23261, 23533, 23703, 24804, 25352, 26575, 27016, 28049, 32768, 0], +]; + +pub static DEFAULT_ZERO_MV_CDF: [[u16; 3]; 2] = [[2175, 32768, 0], [1054, 32768, 0]]; + +pub static INTRA_MODE_CONTEXT: [u8; 13] = [0, 1, 2, 3, 4, 4, 4, 4, 3, 0, 1, 2, 0]; + +pub static MAG_REF_OFFSET_WITH_TX_CLASS: [[[u8; 2]; 3]; 3] = [[[0, 1], [1, 0], [1, 1]], [[0, 1], [1, 0], [0, 2]], [[0, 1], [1, 0], [2, 0]]]; + +pub static MAX_TX_SIZE_RECT: [u8; 22] = [0, 5, 6, 1, 7, 8, 2, 9, 10, 3, 11, 12, 4, 4, 4, 4, 13, 14, 15, 16, 17, 18]; + +pub static MI_HEIGHT_LOG2: [u8; 22] = [0, 1, 0, 1, 2, 1, 2, 3, 2, 3, 4, 3, 4, 5, 4, 5, 2, 0, 3, 1, 4, 2]; + +pub static MI_WIDTH_LOG2: [u8; 22] = [0, 0, 1, 1, 1, 2, 2, 2, 3, 3, 3, 4, 4, 4, 5, 5, 0, 2, 1, 3, 2, 4]; + +pub static MODE_TO_TXFM: [u8; 14] = [0, 1, 2, 0, 3, 1, 2, 2, 1, 3, 1, 2, 3, 0]; + +pub static NUM_4X4_BLOCKS_HIGH: [u8; 22] = [1, 2, 1, 2, 4, 2, 4, 8, 4, 8, 16, 8, 16, 32, 16, 32, 4, 1, 8, 2, 16, 4]; + +pub static NUM_4X4_BLOCKS_WIDE: [u8; 22] = [1, 1, 2, 2, 2, 4, 4, 4, 8, 8, 8, 16, 16, 16, 32, 32, 1, 4, 2, 8, 4, 16]; + +pub static PARTITION_SUBSIZE: [[u8; 22]; 10] = [ + [0, 22, 22, 3, 22, 22, 6, 22, 22, 9, 22, 22, 12, 22, 22, 15, 22, 22, 22, 22, 22, 22], + [22, 22, 22, 2, 22, 22, 5, 22, 22, 8, 22, 22, 11, 22, 22, 14, 22, 22, 22, 22, 22, 22], + [22, 22, 22, 1, 22, 22, 4, 22, 22, 7, 22, 22, 10, 22, 22, 13, 22, 22, 22, 22, 22, 22], + [22, 22, 22, 0, 22, 22, 3, 22, 22, 6, 22, 22, 9, 22, 22, 12, 22, 22, 22, 22, 22, 22], + [22, 22, 22, 2, 22, 22, 5, 22, 22, 8, 22, 22, 11, 22, 22, 14, 22, 22, 22, 22, 22, 22], + [22, 22, 22, 2, 22, 22, 5, 22, 22, 8, 22, 22, 11, 22, 22, 14, 22, 22, 22, 22, 22, 22], + [22, 22, 22, 1, 22, 22, 4, 22, 22, 7, 22, 22, 10, 22, 22, 13, 22, 22, 22, 22, 22, 22], + [22, 22, 22, 1, 22, 22, 4, 22, 22, 7, 22, 22, 10, 22, 22, 13, 22, 22, 22, 22, 22, 22], + [22, 22, 22, 22, 22, 22, 17, 22, 22, 19, 22, 22, 21, 22, 22, 22, 22, 22, 22, 22, 22, 22], + [22, 22, 22, 22, 22, 22, 16, 22, 22, 18, 22, 22, 20, 22, 22, 22, 22, 22, 22, 22, 22, 22], +]; + +pub static SIG_REF_DIFF_OFFSET: [[[u8; 2]; 5]; 3] = + [[[0, 1], [1, 0], [1, 1], [0, 2], [2, 0]], [[0, 1], [1, 0], [0, 2], [0, 3], [0, 4]], [[0, 1], [1, 0], [2, 0], [3, 0], [4, 0]]]; + +pub static SIZE_GROUP: [u8; 22] = [0, 0, 0, 1, 1, 1, 2, 2, 2, 3, 3, 3, 3, 3, 3, 3, 0, 0, 1, 1, 2, 2]; + +pub static SM_WEIGHTS_TX_16X16: [u8; 16] = [255, 225, 196, 170, 145, 123, 102, 84, 68, 54, 43, 33, 26, 20, 17, 16]; + +pub static SM_WEIGHTS_TX_32X32: [u8; 32] = + [255, 240, 225, 210, 196, 182, 169, 157, 145, 133, 122, 111, 101, 92, 83, 74, 66, 59, 52, 45, 39, 34, 29, 25, 21, 17, 14, 12, 10, 9, 8, 8]; + +pub static SM_WEIGHTS_TX_4X4: [u8; 4] = [255, 149, 85, 64]; + +pub static SM_WEIGHTS_TX_8X8: [u8; 8] = [255, 197, 146, 105, 73, 50, 37, 32]; + +pub static SUBPEL_FILTERS: [[[i16; 8]; 16]; 6] = [ + [ + [0, 0, 0, 128, 0, 0, 0, 0], + [0, 2, -6, 126, 8, -2, 0, 0], + [0, 2, -10, 122, 18, -4, 0, 0], + [0, 2, -12, 116, 28, -8, 2, 0], + [0, 2, -14, 110, 38, -10, 2, 0], + [0, 2, -14, 102, 48, -12, 2, 0], + [0, 2, -16, 94, 58, -12, 2, 0], + [0, 2, -14, 84, 66, -12, 2, 0], + [0, 2, -14, 76, 76, -14, 2, 0], + [0, 2, -12, 66, 84, -14, 2, 0], + [0, 2, -12, 58, 94, -16, 2, 0], + [0, 2, -12, 48, 102, -14, 2, 0], + [0, 2, -10, 38, 110, -14, 2, 0], + [0, 2, -8, 28, 116, -12, 2, 0], + [0, 0, -4, 18, 122, -10, 2, 0], + [0, 0, -2, 8, 126, -6, 2, 0], + ], + [ + [0, 0, 0, 128, 0, 0, 0, 0], + [0, 2, 28, 62, 34, 2, 0, 0], + [0, 0, 26, 62, 36, 4, 0, 0], + [0, 0, 22, 62, 40, 4, 0, 0], + [0, 0, 20, 60, 42, 6, 0, 0], + [0, 0, 18, 58, 44, 8, 0, 0], + [0, 0, 16, 56, 46, 10, 0, 0], + [0, -2, 16, 54, 48, 12, 0, 0], + [0, -2, 14, 52, 52, 14, -2, 0], + [0, 0, 12, 48, 54, 16, -2, 0], + [0, 0, 10, 46, 56, 16, 0, 0], + [0, 0, 8, 44, 58, 18, 0, 0], + [0, 0, 6, 42, 60, 20, 0, 0], + [0, 0, 4, 40, 62, 22, 0, 0], + [0, 0, 4, 36, 62, 26, 0, 0], + [0, 0, 2, 34, 62, 28, 2, 0], + ], + [ + [0, 0, 0, 128, 0, 0, 0, 0], + [-2, 2, -6, 126, 8, -2, 2, 0], + [-2, 6, -12, 124, 16, -6, 4, -2], + [-2, 8, -18, 120, 26, -10, 6, -2], + [-4, 10, -22, 116, 38, -14, 6, -2], + [-4, 10, -22, 108, 48, -18, 8, -2], + [-4, 10, -24, 100, 60, -20, 8, -2], + [-4, 10, -24, 90, 70, -22, 10, -2], + [-4, 12, -24, 80, 80, -24, 12, -4], + [-2, 10, -22, 70, 90, -24, 10, -4], + [-2, 8, -20, 60, 100, -24, 10, -4], + [-2, 8, -18, 48, 108, -22, 10, -4], + [-2, 6, -14, 38, 116, -22, 10, -4], + [-2, 6, -10, 26, 120, -18, 8, -2], + [-2, 4, -6, 16, 124, -12, 6, -2], + [0, 2, -2, 8, 126, -6, 2, -2], + ], + [ + [0, 0, 0, 128, 0, 0, 0, 0], + [0, 0, 0, 120, 8, 0, 0, 0], + [0, 0, 0, 112, 16, 0, 0, 0], + [0, 0, 0, 104, 24, 0, 0, 0], + [0, 0, 0, 96, 32, 0, 0, 0], + [0, 0, 0, 88, 40, 0, 0, 0], + [0, 0, 0, 80, 48, 0, 0, 0], + [0, 0, 0, 72, 56, 0, 0, 0], + [0, 0, 0, 64, 64, 0, 0, 0], + [0, 0, 0, 56, 72, 0, 0, 0], + [0, 0, 0, 48, 80, 0, 0, 0], + [0, 0, 0, 40, 88, 0, 0, 0], + [0, 0, 0, 32, 96, 0, 0, 0], + [0, 0, 0, 24, 104, 0, 0, 0], + [0, 0, 0, 16, 112, 0, 0, 0], + [0, 0, 0, 8, 120, 0, 0, 0], + ], + [ + [0, 0, 0, 128, 0, 0, 0, 0], + [0, 0, -4, 126, 8, -2, 0, 0], + [0, 0, -8, 122, 18, -4, 0, 0], + [0, 0, -10, 116, 28, -6, 0, 0], + [0, 0, -12, 110, 38, -8, 0, 0], + [0, 0, -12, 102, 48, -10, 0, 0], + [0, 0, -14, 94, 58, -10, 0, 0], + [0, 0, -12, 84, 66, -10, 0, 0], + [0, 0, -12, 76, 76, -12, 0, 0], + [0, 0, -10, 66, 84, -12, 0, 0], + [0, 0, -10, 58, 94, -14, 0, 0], + [0, 0, -10, 48, 102, -12, 0, 0], + [0, 0, -8, 38, 110, -12, 0, 0], + [0, 0, -6, 28, 116, -10, 0, 0], + [0, 0, -4, 18, 122, -8, 0, 0], + [0, 0, -2, 8, 126, -4, 0, 0], + ], + [ + [0, 0, 0, 128, 0, 0, 0, 0], + [0, 0, 30, 62, 34, 2, 0, 0], + [0, 0, 26, 62, 36, 4, 0, 0], + [0, 0, 22, 62, 40, 4, 0, 0], + [0, 0, 20, 60, 42, 6, 0, 0], + [0, 0, 18, 58, 44, 8, 0, 0], + [0, 0, 16, 56, 46, 10, 0, 0], + [0, 0, 14, 54, 48, 12, 0, 0], + [0, 0, 12, 52, 52, 12, 0, 0], + [0, 0, 12, 48, 54, 14, 0, 0], + [0, 0, 10, 46, 56, 16, 0, 0], + [0, 0, 8, 44, 58, 18, 0, 0], + [0, 0, 6, 42, 60, 20, 0, 0], + [0, 0, 4, 40, 62, 22, 0, 0], + [0, 0, 4, 36, 62, 26, 0, 0], + [0, 0, 2, 34, 62, 30, 0, 0], + ], +]; + +pub static SUBSAMPLED_SIZE: [[[u8; 2]; 2]; 22] = [ + [[0, 0], [0, 0]], + [[1, 0], [22, 0]], + [[2, 22], [0, 0]], + [[3, 2], [1, 0]], + [[4, 3], [22, 1]], + [[5, 22], [3, 2]], + [[6, 5], [4, 3]], + [[7, 6], [22, 4]], + [[8, 22], [6, 5]], + [[9, 8], [7, 6]], + [[10, 9], [22, 7]], + [[11, 22], [9, 8]], + [[12, 11], [10, 9]], + [[13, 12], [22, 10]], + [[14, 22], [12, 11]], + [[15, 14], [13, 12]], + [[16, 1], [22, 1]], + [[17, 22], [2, 2]], + [[18, 4], [22, 16]], + [[19, 22], [5, 17]], + [[20, 7], [22, 18]], + [[21, 22], [8, 19]], +]; + +pub static TRANSFORM_ROW_SHIFT: [u8; 19] = [0, 1, 2, 2, 2, 0, 0, 1, 1, 1, 1, 1, 1, 1, 1, 2, 2, 2, 2]; + +pub static TX_HEIGHT: [u8; 19] = [4, 8, 16, 32, 64, 8, 4, 16, 8, 32, 16, 64, 32, 16, 4, 32, 8, 64, 16]; + +pub static TX_HEIGHT_LOG2: [u8; 19] = [2, 3, 4, 5, 6, 3, 2, 4, 3, 5, 4, 6, 5, 4, 2, 5, 3, 6, 4]; + +pub static TX_SIZE_SQR: [u8; 19] = [0, 1, 2, 3, 4, 0, 0, 1, 1, 2, 2, 3, 3, 0, 0, 1, 1, 2, 2]; + +pub static TX_SIZE_SQR_UP: [u8; 19] = [0, 1, 2, 3, 4, 1, 1, 2, 2, 3, 3, 4, 4, 2, 2, 3, 3, 4, 4]; + +pub static TX_TYPE_IN_SET_INTER: [[u8; 16]; 4] = [ + [1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0], + [1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1], + [1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 0, 0, 0, 0], + [1, 0, 0, 0, 0, 0, 0, 0, 0, 1, 0, 0, 0, 0, 0, 0], +]; + +pub static TX_TYPE_IN_SET_INTRA: [[u8; 16]; 3] = [ + [1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0], + [1, 1, 1, 1, 0, 0, 0, 0, 0, 1, 1, 1, 0, 0, 0, 0], + [1, 1, 1, 1, 0, 0, 0, 0, 0, 1, 0, 0, 0, 0, 0, 0], +]; + +pub static TX_TYPE_INTER_INV_SET3: [u8; 2] = [9, 0]; + +pub static TX_TYPE_INTRA_INV_SET2: [u8; 5] = [9, 0, 3, 1, 2]; + +pub static TX_WIDTH: [u8; 19] = [4, 8, 16, 32, 64, 4, 8, 8, 16, 16, 32, 32, 64, 4, 16, 8, 32, 16, 64]; + +pub static TX_WIDTH_LOG2: [u8; 19] = [2, 3, 4, 5, 6, 2, 3, 3, 4, 4, 5, 5, 6, 2, 4, 3, 5, 4, 6]; diff --git a/crates/av1enc/src/transform.rs b/crates/av1enc/src/transform.rs new file mode 100644 index 0000000000..9d81ead0bb --- /dev/null +++ b/crates/av1enc/src/transform.rs @@ -0,0 +1,575 @@ +//! Transforms. The inverse transforms are the normative ones of spec 7.13 (mirrored from +//! filmcraft-av1, so that the encoder reconstructs exactly like a decoder); the forward +//! transforms are floating-point matrices scaled to match them (not normative). + +use std::sync::OnceLock; + +use crate::tables::{ADST_ADST, ADST_DCT, COS128_LOOKUP, DCT_ADST, DCT_DCT, TRANSFORM_ROW_SHIFT, TX_WIDTH_LOG2}; + +#[inline(always)] +fn round2_64(x: i64, n: u32) -> i32 { + if n == 0 { x as i32 } else { ((x + (1i64 << (n - 1))) >> n) as i32 } +} + +#[inline(always)] +pub(crate) fn round2(x: i32, n: u32) -> i32 { + if n == 0 { x } else { (x + (1 << (n - 1))) >> n } +} + +#[inline(always)] +fn cos128(angle: i32) -> i64 { + let a = (angle & 255) as usize; + (match a { + 0..=64 => COS128_LOOKUP[a] as i32, + 65..=128 => -(COS128_LOOKUP[128 - a] as i32), + 129..=192 => -(COS128_LOOKUP[a - 128] as i32), + _ => COS128_LOOKUP[256 - a] as i32, + }) as i64 +} + +#[inline(always)] +fn sin128(angle: i32) -> i64 { + cos128(angle - 64) +} + +#[inline(always)] +fn brev(num_bits: u32, x: usize) -> usize { + let mut t = 0; + for i in 0..num_bits { + let bit = (x >> i) & 1; + t += bit << (num_bits - 1 - i); + } + t +} + +#[inline(always)] +fn bf(t: &mut [i32], a: usize, b: usize, angle: i32, flip: bool) { + let x = t[a] as i64 * cos128(angle) - t[b] as i64 * sin128(angle); + let y = t[a] as i64 * sin128(angle) + t[b] as i64 * cos128(angle); + t[a] = round2_64(x, 12); + t[b] = round2_64(y, 12); + if flip { + t.swap(a, b); + } +} + +#[inline(always)] +fn hd(t: &mut [i32], a: usize, b: usize, flip: bool, r: u32) { + let (a, b) = if flip { (b, a) } else { (a, b) }; + let x = t[a]; + let y = t[b]; + let lo = -(1i32 << (r - 1)); + let hi = (1i32 << (r - 1)) - 1; + t[a] = (x + y).clamp(lo, hi); + t[b] = (x - y).clamp(lo, hi); +} + +/// Inverse DCT of length 2^N, N = 2..=5 (7.13.2.3), in place. `N` is a const generic so that the +/// bit-reversal permutations and loop bounds fold at compile time. +#[inline(always)] +fn inverse_dct_n(t: &mut [i32], r: u32) { + let n = N; + let len = 1usize << n; + let mut copy = [0i32; 32]; + copy[..len].copy_from_slice(&t[..len]); + for i in 0..len { + t[i] = copy[brev(n, i)]; + } + inverse_dct_initial_stages(t, n, r); + inverse_dct_final_stages(t, n, r); +} + +/// Rotate the high-frequency branches before the four-point core. +#[inline(always)] +fn inverse_dct_initial_stages(t: &mut [i32], n: u32, r: u32) { + if n >= 5 { + for i in 0..8 { + bf(t, 16 + i, 31 - i, 6 + ((brev(3, 7 - i) as i32) << 3), false); + } + } + if n >= 4 { + for i in 0..4 { + bf(t, 8 + i, 15 - i, 12 + ((brev(2, 3 - i) as i32) << 4), false); + } + } + if n >= 5 { + for i in 0..8 { + hd(t, 16 + 2 * i, 17 + 2 * i, i & 1 == 1, r); + } + } + if n >= 3 { + for i in 0..2 { + bf(t, 4 + i, 7 - i, 56 - 32 * i as i32, false); + } + } + if n >= 4 { + for i in 0..4 { + hd(t, 8 + 2 * i, 9 + 2 * i, i & 1 == 1, r); + } + } + if n >= 5 { + for i in 0..2 { + for j in 0..2 { + bf(t, 30 - 4 * i - j, 17 + 4 * i + j, 24 + ((j as i32) << 6) + (((1 - i) as i32) << 5), true); + } + } + } +} + +/// Evaluate the four-point core and merge progressively larger branches. +#[inline(always)] +fn inverse_dct_final_stages(t: &mut [i32], n: u32, r: u32) { + for i in 0..2 { + bf(t, 2 * i, 2 * i + 1, 32 + 16 * i as i32, i == 0); + } + if n >= 3 { + for i in 0..2 { + hd(t, 4 + 2 * i, 5 + 2 * i, i == 1, r); + } + } + if n >= 4 { + for i in 0..2 { + bf(t, 14 - i, 9 + i, 48 + 64 * i as i32, true); + } + } + if n >= 5 { + for i in 0..4 { + for j in 0..2 { + hd(t, 16 + 4 * i + j, 19 + 4 * i - j, i & 1 == 1, r); + } + } + } + for i in 0..2 { + hd(t, i, 3 - i, false, r); + } + if n >= 3 { + bf(t, 6, 5, 32, true); + } + if n >= 4 { + for i in 0..2 { + for j in 0..2 { + hd(t, 8 + 4 * i + j, 11 + 4 * i - j, i == 1, r); + } + } + } + if n >= 5 { + for i in 0..4 { + bf(t, 29 - i, 18 + i, 48 + (i as i32 >> 1) * 64, true); + } + } + if n >= 3 { + for i in 0..4 { + hd(t, i, 7 - i, false, r); + } + } + if n >= 4 { + for i in 0..2 { + bf(t, 13 - i, 10 + i, 32, true); + } + } + if n >= 5 { + for i in 0..2 { + for j in 0..4 { + hd(t, 16 + i * 8 + j, 23 + i * 8 - j, i == 1, r); + } + } + } + if n >= 4 { + for i in 0..8 { + hd(t, i, 15 - i, false, r); + } + } + if n >= 5 { + for i in 0..4 { + bf(t, 27 - i, 20 + i, 32, true); + } + } + if n >= 5 { + for i in 0..16 { + hd(t, i, 31 - i, false, r); + } + } +} + +const SINPI_1_9: i64 = 1321; +const SINPI_2_9: i64 = 2482; +const SINPI_3_9: i64 = 3344; +const SINPI_4_9: i64 = 3803; + +#[inline(always)] +fn inverse_adst4(t: &mut [i32]) { + let (t0, t1, t2, t3) = (t[0] as i64, t[1] as i64, t[2] as i64, t[3] as i64); + let mut s = [SINPI_1_9 * t0, SINPI_2_9 * t0, SINPI_3_9 * t1, SINPI_4_9 * t2, SINPI_1_9 * t2, SINPI_2_9 * t3, SINPI_4_9 * t3]; + let a7 = t0 - t2; + let b7 = a7 + t3; + s[0] += s[3]; + s[1] -= s[4]; + s[3] = s[2]; + s[2] = SINPI_3_9 * b7; + s[0] += s[5]; + s[1] -= s[6]; + let x = [s[0] + s[3], s[1] + s[3], s[2], s[0] + s[1] - s[3]]; + for i in 0..4 { + t[i] = round2_64(x[i], 12); + } +} + +#[inline(always)] +fn adst_in_permute(t: &mut [i32]) { + let n = N; + let n0 = 1usize << n; + let mut copy = [0i32; 16]; + copy[..n0].copy_from_slice(&t[..n0]); + for i in 0..n0 { + let idx = if i & 1 == 1 { i - 1 } else { n0 - i - 1 }; + t[i] = copy[idx]; + } +} + +#[inline(always)] +fn adst_out_permute(t: &mut [i32]) { + let n = N; + let n0 = 1usize << n; + let mut copy = [0i32; 16]; + copy[..n0].copy_from_slice(&t[..n0]); + for i in 0..n0 { + let a = (i >> 3) & 1; + let b = ((i >> 2) & 1) ^ ((i >> 3) & 1); + let c = ((i >> 1) & 1) ^ ((i >> 2) & 1); + let d = (i & 1) ^ ((i >> 1) & 1); + let idx = ((d << 3) | (c << 2) | (b << 1) | a) >> (4 - n); + t[i] = if i & 1 == 1 { -copy[idx] } else { copy[idx] }; + } +} + +#[inline(always)] +fn inverse_adst8(t: &mut [i32], r: u32) { + adst_in_permute::<3>(t); + for i in 0..4 { + bf(t, 2 * i, 2 * i + 1, 60 - 16 * i as i32, true); + } + for i in 0..4 { + hd(t, i, 4 + i, false, r); + } + for i in 0..2 { + bf(t, 4 + 3 * i, 5 + i, 48 - 32 * i as i32, true); + } + for i in 0..2 { + for j in 0..2 { + hd(t, 4 * j + i, 2 + 4 * j + i, false, r); + } + } + for i in 0..2 { + bf(t, 2 + 4 * i, 3 + 4 * i, 32, true); + } + adst_out_permute::<3>(t); +} + +#[inline(always)] +fn inverse_adst16(t: &mut [i32], r: u32) { + adst_in_permute::<4>(t); + for i in 0..8 { + bf(t, 2 * i, 2 * i + 1, 62 - 8 * i as i32, true); + } + for i in 0..8 { + hd(t, i, 8 + i, false, r); + } + for i in 0..2 { + bf(t, 8 + 2 * i, 9 + 2 * i, 56 - 32 * i as i32, true); + bf(t, 13 + 2 * i, 12 + 2 * i, 8 + 32 * i as i32, true); + } + for i in 0..4 { + for j in 0..2 { + hd(t, 8 * j + i, 4 + 8 * j + i, false, r); + } + } + for i in 0..2 { + for j in 0..2 { + bf(t, 4 + 8 * j + 3 * i, 5 + 8 * j + i, 48 - 32 * i as i32, true); + } + } + for i in 0..2 { + for j in 0..4 { + hd(t, 4 * j + i, 2 + 4 * j + i, false, r); + } + } + for i in 0..4 { + bf(t, 2 + 4 * i, 3 + 4 * i, 32, true); + } + adst_out_permute::<4>(t); +} + +/// 1-D transform kind. +#[derive(Clone, Copy, PartialEq, Eq, Debug)] +pub(crate) enum Kind { + Dct, + Adst, +} + +/// (vertical / column kind, horizontal / row kind) of a transform type. +pub(crate) fn kinds(tx_type: usize) -> (Kind, Kind) { + match tx_type { + DCT_DCT => (Kind::Dct, Kind::Dct), + ADST_DCT => (Kind::Adst, Kind::Dct), + DCT_ADST => (Kind::Dct, Kind::Adst), + ADST_ADST => (Kind::Adst, Kind::Adst), + // The encoder only chooses the four types above. + _ => { + debug_assert!(false, "unsupported transform type {tx_type}"); + (Kind::Dct, Kind::Dct) + } + } +} + +#[inline(always)] +fn inverse_dct(t: &mut [i32], n: u32, r: u32) { + match n { + 2 => inverse_dct_n::<2>(t, r), + 3 => inverse_dct_n::<3>(t, r), + 4 => inverse_dct_n::<4>(t, r), + _ => inverse_dct_n::<5>(t, r), + } +} + +#[inline(always)] +fn inverse_1d(t: &mut [i32], kind: Kind, n: u32, r: u32) { + match kind { + Kind::Dct => inverse_dct(t, n, r), + Kind::Adst => match n { + 2 => inverse_adst4(t), + 3 => inverse_adst8(t, r), + _ => inverse_adst16(t, r), + }, + } +} + +/// 2-D inverse transform (7.13.3) of a square `n x n` block (n = 4..=32) of dequantised +/// coefficients (row stride `n`) into `residual` (row stride `n`). +pub(crate) fn inverse_2d(dequant: &[i32], residual: &mut [i32], tx_sz: usize, tx_type: usize, bit_depth: u32) { + match TX_WIDTH_LOG2[tx_sz] { + 2 => inverse_2d_n::<2>(dequant, residual, tx_sz, tx_type, bit_depth), + 3 => inverse_2d_n::<3>(dequant, residual, tx_sz, tx_type, bit_depth), + 4 => inverse_2d_n::<4>(dequant, residual, tx_sz, tx_type, bit_depth), + _ => inverse_2d_n::<5>(dequant, residual, tx_sz, tx_type, bit_depth), + } +} + +/// `inverse_2d` for a block of width `1 << LOG2`. +fn inverse_2d_n(dequant: &[i32], residual: &mut [i32], tx_sz: usize, tx_type: usize, bit_depth: u32) { + let log2 = LOG2; + let n = 1usize << log2; + let row_shift = TRANSFORM_ROW_SHIFT[tx_sz] as u32; + let row_clamp = bit_depth + 8; + let col_clamp = (bit_depth + 6).max(16); + let (ck, rk) = kinds(tx_type); + let lo = -(1i32 << (col_clamp - 1)); + let hi = (1i32 << (col_clamp - 1)) - 1; + let mut t = [0i32; 32]; + for i in 0..n { + let row = &dequant[i * n..i * n + n]; + if row.iter().all(|&v| v == 0) { + residual[i * n..i * n + n].iter_mut().for_each(|v| *v = 0); + continue; + } + t[..n].copy_from_slice(row); + inverse_1d(&mut t, rk, log2, row_clamp); + for j in 0..n { + residual[i * n + j] = round2(t[j], row_shift).clamp(lo, hi); + } + } + for j in 0..n { + for i in 0..n { + t[i] = residual[i * n + j]; + } + inverse_1d(&mut t, ck, log2, col_clamp); + for i in 0..n { + residual[i * n + j] = round2(t[i], 4); + } + } +} + +/// Orthonormal forward basis matrices `[k][i]` (row k = basis function k), sizes 4..=32. +struct Bases { + dct: [Vec; 4], + adst: [Vec; 3], + /// The same matrices transposed (`[i][k]`). + dct_t: [Vec; 4], + adst_t: [Vec; 3], +} + +fn transpose(m: &[f32]) -> Vec { + let n = (m.len() as f64).sqrt() as usize; + let mut t = vec![0f32; n * n]; + for k in 0..n { + for i in 0..n { + t[i * n + k] = m[k * n + i]; + } + } + t +} + +fn bases() -> &'static Bases { + static B: OnceLock = OnceLock::new(); + B.get_or_init(|| { + let pi = std::f64::consts::PI; + let dct = std::array::from_fn(|l| { + let n = 4usize << l; + let mut m = vec![0f32; n * n]; + for k in 0..n { + let c = if k == 0 { std::f64::consts::FRAC_1_SQRT_2 } else { 1.0 }; + for i in 0..n { + m[k * n + i] = (c * (2.0 / n as f64).sqrt() * (pi * (2 * i + 1) as f64 * k as f64 / (2 * n) as f64).cos()) as f32; + } + } + m + }); + let adst = std::array::from_fn(|l| { + let n = 4usize << l; + let mut m = vec![0f32; n * n]; + for k in 0..n { + for i in 0..n { + m[k * n + i] = if n == 4 { + // the AV1 4-point ADST (sin(pi (i + 1)(2k + 1) / 9) basis) + (2.0 / 3.0 * (pi * ((i + 1) * (2 * k + 1)) as f64 / 9.0).sin()) as f32 + } else { + ((2.0 / n as f64).sqrt() * (pi * ((2 * i + 1) * (2 * k + 1)) as f64 / (4 * n) as f64).sin()) as f32 + }; + } + } + m + }); + let dct_t = std::array::from_fn(|l| transpose(&dct[l])); + let adst_t = std::array::from_fn(|l| transpose(&adst[l])); + Bases { dct, adst, dct_t, adst_t } + }) +} + +fn basis_t(kind: Kind, n: usize) -> &'static [f32] { + let l = n.trailing_zeros() as usize - 2; + match kind { + Kind::Dct => &bases().dct_t[l], + Kind::Adst => &bases().adst_t[l], + } +} + +fn basis(kind: Kind, n: usize) -> &'static [f32] { + let l = n.trailing_zeros() as usize - 2; + match kind { + Kind::Dct => &bases().dct[l], + Kind::Adst => &bases().adst[l], + } +} + +/// Forward 2-D transform of the `n x n` residual (row stride `n`) into orthonormal-domain +/// coefficients scaled by 8 (so that `level = out / q` with the spec's dequantiser). +pub(crate) fn forward_2d(residual: &[i32], out: &mut [f32], n: usize, tx_type: usize) { + match n { + 4 => forward_2d_n::<4, 16>(residual, out, tx_type), + 8 => forward_2d_n::<8, 64>(residual, out, tx_type), + 16 => forward_2d_n::<16, 256>(residual, out, tx_type), + _ => forward_2d_dyn(residual, out, n, tx_type), + } +} + +/// `forward_2d` for a fixed size, so `tmp` is exactly `NN = N * N` floats (no 4 KiB zeroing). +#[inline(always)] +fn forward_2d_n(residual: &[i32], out: &mut [f32], tx_type: usize) { + let n = N; + let (ck, rk) = kinds(tx_type); + let bc = &basis(ck, n)[..NN]; + let brt = &basis_t(rk, n)[..NN]; + let residual = &residual[..NN]; + let out = &mut out[..NN]; + let mut tmp = [0f32; NN]; + for i in 0..n { + let t = &mut tmp[i * n..i * n + n]; + for j in 0..n { + let r = residual[i * n + j]; + if r == 0 { + continue; + } + let r = r as f32; + for (o, &b) in t.iter_mut().zip(&brt[j * n..j * n + n]) { + *o += r * b; + } + } + } + out.iter_mut().for_each(|v| *v = 0.0); + for k in 0..n { + let o = &mut out[k * n..k * n + n]; + for i in 0..n { + let b = 8.0 * bc[k * n + i]; + for (v, &t) in o.iter_mut().zip(&tmp[i * n..i * n + n]) { + *v += b * t; + } + } + } +} + +/// 32x32 (and any other size): a const-generic 32 version measured slower. +fn forward_2d_dyn(residual: &[i32], out: &mut [f32], n: usize, tx_type: usize) { + let (ck, rk) = kinds(tx_type); + let bc = basis(ck, n); + let brt = basis_t(rk, n); + // tmp[i][k] = sum_j residual[i][j] * br[k][j] (horizontal; written as row updates so that + // the inner loops vectorise) + let mut tmp = [0f32; 32 * 32]; + for i in 0..n { + let t = &mut tmp[i * n..i * n + n]; + for j in 0..n { + let r = residual[i * n + j]; + if r == 0 { + continue; + } + let r = r as f32; + for (o, &b) in t.iter_mut().zip(&brt[j * n..j * n + n]) { + *o += r * b; + } + } + } + // out[k][j] = 8 * sum_i bc[k][i] * tmp[i][j] (vertical) + out[..n * n].iter_mut().for_each(|v| *v = 0.0); + for k in 0..n { + let o = &mut out[k * n..k * n + n]; + for i in 0..n { + let b = 8.0 * bc[k * n + i]; + for (v, &t) in o.iter_mut().zip(&tmp[i * n..i * n + n]) { + *v += b * t; + } + } + } +} + +#[cfg(test)] +mod tests { + use super::*; + use crate::tables::{TX_4X4, TX_8X8, TX_16X16, TX_32X32}; + + /// forward (float) then the normative inverse reproduces the residual: the forward basis + /// and scale match the decoder's inverse transforms for every supported size and type. + #[test] + fn forward_matches_inverse() { + let mut seed = 99u32; + let mut rnd = || { + seed = seed.wrapping_mul(1_103_515_245).wrapping_add(12345); + ((seed >> 16) % 201) as i32 - 100 + }; + for (tx, n) in [(TX_4X4, 4), (TX_8X8, 8), (TX_16X16, 16), (TX_32X32, 32)] { + let denom = if tx == TX_32X32 { 2.0 } else { 1.0 }; + for tx_type in [DCT_DCT, ADST_DCT, DCT_ADST, ADST_ADST] { + if n == 32 && tx_type != DCT_DCT { + continue; + } + let res: Vec = (0..n * n).map(|_| rnd()).collect(); + let mut coef = vec![0f32; n * n]; + forward_2d(&res, &mut coef, n, tx_type); + // q = 8 / denom keeps full precision: dequant = level * q / denom = coef + let deq: Vec = coef.iter().map(|&c| (c / denom).round() as i32).collect(); + let mut out = vec![0i32; n * n]; + inverse_2d(&deq, &mut out, tx, tx_type, 8); + let err: i32 = res.iter().zip(&out).map(|(a, b)| (a - b).abs()).max().unwrap(); + assert!(err <= 1, "tx {tx} type {tx_type}: max error {err}"); + } + } + } +} diff --git a/crates/av1enc/src/writer.rs b/crates/av1enc/src/writer.rs new file mode 100644 index 0000000000..fbed84bc31 --- /dev/null +++ b/crates/av1enc/src/writer.rs @@ -0,0 +1,1040 @@ +//! Tile syntax writer (spec 5.11 with the 8.3 CDF selection). It keeps the same per-4x4 mode +//! info, coefficient contexts and adaptive CDFs as a decoder and writes the decisions taken by +//! `decide.rs` as symbols. The context derivations and the motion vector prediction process +//! (7.10.2) mirror the decoder exactly, since symbols and coded motion vector differences +//! depend on them. + +use crate::cdf::Cdfs; +use crate::decide::{FrameGeom, Leaf, Node, intra_uv_tx_type, scan_for, tx_in_set, tx_set, tx_size_for}; +use crate::loopfilter::LfInfo; +use crate::symbol::SymbolWriter; +use crate::tables::{ + ADST_ADST, ADST_DCT, ALTREF_FRAME, ALTREF2_FRAME, BLOCK_8X8, BLOCK_64X64, BR_CDF_SIZE, BWDREF_FRAME, CLASS0_SIZE, COEFF_BASE_CTX_OFFSET, + COEFF_BASE_RANGE, D67_PRED, DC_PRED, DCT_ADST, DCT_DCT, DEFAULT_INTRA_FRAME_Y_MODE_CDF, GLOBALMV, GOLDEN_FRAME, INTRA_FRAME, INTRA_MODE_CONTEXT, + LAST_FRAME, LAST2_FRAME, LAST3_FRAME, MAG_REF_OFFSET_WITH_TX_CLASS, MAX_ANGLE_DELTA, MAX_REF_MV_STACK_SIZE, MI_HEIGHT_LOG2, MI_WIDTH_LOG2, + MV_BORDER, MV_CLASS_0, NEARESTMV, NEARMV, NEWMV, NUM_4X4_BLOCKS_HIGH, NUM_4X4_BLOCKS_WIDE, NUM_BASE_LEVELS, PARTITION_HORZ, PARTITION_HORZ_4, + PARTITION_HORZ_A, PARTITION_HORZ_B, PARTITION_NONE, PARTITION_SPLIT, PARTITION_SUBSIZE, PARTITION_VERT, PARTITION_VERT_4, PARTITION_VERT_A, + PARTITION_VERT_B, REF_CAT_LEVEL, SIG_REF_DIFF_OFFSET, SIZE_GROUP, SUBSAMPLED_SIZE, TX_32X32, TX_CLASS_2D, TX_HEIGHT, TX_HEIGHT_LOG2, TX_SIZE_SQR, + TX_SIZE_SQR_UP, TX_TYPE_INTER_INV_SET3, TX_TYPE_INTRA_INV_SET2, TX_WIDTH, TX_WIDTH_LOG2, V_PRED, +}; + +type MvP = [i32; 2]; + +/// Mode info per 4x4 (MiSizes, YModes, RefFrames, Mvs, ...). +struct Mi { + cols: usize, + mi_size: Vec, + y_mode: Vec, + ref_frame: Vec<[i8; 2]>, + mv: Vec, + skip: Vec, + is_inter: Vec, + tx_type: Vec, + written: Vec, +} + +impl Mi { + #[inline(always)] + fn idx(&self, r: usize, c: usize) -> usize { + r * self.cols + c + } +} + +/// Output of find_mv_stack. +#[derive(Default)] +struct MvStack { + ref_stack_mv: [MvP; MAX_REF_MV_STACK_SIZE + 1], + weight_stack: [u32; MAX_REF_MV_STACK_SIZE + 1], + num_mv_found: usize, + new_mv_count: usize, + found_match: bool, + close_matches: usize, + total_matches: usize, + new_mv_context: usize, + ref_mv_context: usize, + zero_mv_context: usize, + drl_ctx_stack: [usize; MAX_REF_MV_STACK_SIZE + 1], +} + +pub(crate) struct TileWriter<'a> { + g: &'a FrameGeom, + intra_only: bool, + qidx: u8, + sw: SymbolWriter, + cdf: Box, + kf_y_mode: [[[u16; 14]; 5]; 5], + mi: Mi, + above_level: [Vec; 3], + above_dc: [Vec; 3], + left_level: [Vec; 3], + left_dc: [Vec; 3], + /// LoopfilterTxSizes per plane (row stride mi_cols + 32). + lf_tx: [Vec; 3], + /// Scratch for the levels coded so far in `coeffs`. + quant: Vec, + // current block + r: usize, + c: usize, + bsize: usize, + avail_u: bool, + avail_l: bool, + mvs: MvStack, +} + +fn block_size_px(bs: usize) -> usize { + 4 * NUM_4X4_BLOCKS_WIDE[bs] as usize +} + +fn is_directional(mode: usize) -> bool { + (V_PRED..=D67_PRED).contains(&mode) +} + +impl<'a> TileWriter<'a> { + /// `init`: the CDFs saved by the primary reference frame (load_cdfs), or None for the + /// defaults (init_non_coeff_cdfs / init_coeff_cdfs). + pub fn new(g: &'a FrameGeom, intra_only: bool, qidx: u8, init: Option<&Cdfs>) -> Self { + let n = g.mi_cols * g.mi_rows; + TileWriter { + g, + intra_only, + qidx, + sw: SymbolWriter::new(false), + cdf: match init { + Some(c) => { + let mut c = Box::new(c.clone()); + c.clear_counts(); + c + } + None => Cdfs::new(qidx as u32), + }, + kf_y_mode: DEFAULT_INTRA_FRAME_Y_MODE_CDF, + mi: Mi { + cols: g.mi_cols, + mi_size: vec![0; n], + y_mode: vec![0; n], + ref_frame: vec![[0, -1]; n], + mv: vec![[0, 0]; n], + skip: vec![false; n], + is_inter: vec![false; n], + tx_type: vec![0; n], + written: vec![false; n], + }, + above_level: std::array::from_fn(|_| vec![0; g.mi_cols + 64]), + above_dc: std::array::from_fn(|_| vec![0; g.mi_cols + 64]), + left_level: std::array::from_fn(|_| vec![0; g.mi_rows + 64]), + left_dc: std::array::from_fn(|_| vec![0; g.mi_rows + 64]), + lf_tx: std::array::from_fn(|_| vec![0; (g.mi_cols + 32) * (g.mi_rows + 32)]), + quant: Vec::new(), + r: 0, + c: 0, + bsize: 0, + avail_u: false, + avail_l: false, + mvs: MvStack::default(), + } + } + + /// The tile data and the final CDFs (saved for the frame-end CDF update). + pub fn finish(self) -> (Vec, Box) { + (self.sw.finish(), self.cdf) + } + + /// The mode info the loop filter needs. + pub fn lf_info(&self) -> LfInfo<'_> { + LfInfo { + mi_cols: self.g.mi_cols, + mi_rows: self.g.mi_rows, + width: self.g.width, + height: self.g.height, + mi_size: &self.mi.mi_size, + skip: &self.mi.skip, + is_inter: &self.mi.is_inter, + tx: [&self.lf_tx[0], &self.lf_tx[1], &self.lf_tx[2]], + } + } + + /// Start of a superblock row (clear_left_context). + pub fn start_sb_row(&mut self) { + for p in 0..3 { + self.left_level[p].iter_mut().for_each(|v| *v = 0); + self.left_dc[p].iter_mut().for_each(|v| *v = 0); + } + } + + pub fn superblock(&mut self, r: usize, c: usize, node: &Node) { + self.partition(r, c, BLOCK_64X64, node); + } + + fn partition(&mut self, r: usize, c: usize, bsize: usize, node: &Node) { + let (mi_rows, mi_cols) = (self.g.mi_rows, self.g.mi_cols); + if r >= mi_rows || c >= mi_cols { + return; + } + let avail_u = r > 0; + let avail_l = c > 0; + let n4 = NUM_4X4_BLOCKS_WIDE[bsize] as usize; + let half = n4 >> 1; + let has_rows = r + half < mi_rows; + let has_cols = c + half < mi_cols; + let split = matches!(node, Node::Split(_)); + let bsl = MI_WIDTH_LOG2[bsize] as usize; + let above = avail_u && (MI_WIDTH_LOG2[self.mi.mi_size[self.mi.idx(r - 1, c)] as usize] as usize) < bsl; + let left = avail_l && (MI_HEIGHT_LOG2[self.mi.mi_size[self.mi.idx(r, c - 1)] as usize] as usize) < bsl; + let ctx = left as usize * 2 + above as usize; + let partition = if split { PARTITION_SPLIT } else { PARTITION_NONE }; + if has_rows && has_cols { + let cdf: &mut [u16] = match bsl { + 1 => &mut self.cdf.partition_w8[ctx], + 2 => &mut self.cdf.partition_w16[ctx], + 3 => &mut self.cdf.partition_w32[ctx], + _ => &mut self.cdf.partition_w64[ctx], + }; + self.sw.symbol(partition, cdf); + } else if has_cols || has_rows { + debug_assert!(split, "blocks crossing the frame edge are split"); + let pcdf: &[u16] = match bsl { + 2 => &self.cdf.partition_w16[ctx], + 3 => &self.cdf.partition_w32[ctx], + _ => &self.cdf.partition_w64[ctx], + }; + let pr = |k: usize| pcdf[k] as i32 - if k == 0 { 0 } else { pcdf[k - 1] as i32 }; + let psum = if has_cols { + // split_or_horz + pr(PARTITION_VERT) + pr(PARTITION_SPLIT) + pr(PARTITION_HORZ_A) + pr(PARTITION_VERT_A) + pr(PARTITION_VERT_B) + pr(PARTITION_VERT_4) + } else { + // split_or_vert + pr(PARTITION_HORZ) + pr(PARTITION_SPLIT) + pr(PARTITION_HORZ_A) + pr(PARTITION_HORZ_B) + pr(PARTITION_VERT_A) + pr(PARTITION_HORZ_4) + }; + let cdf = [((1i32 << 15) - psum) as u16, 1 << 15, 0]; + self.sw.symbol_fixed(1, &cdf); + } else { + debug_assert!(split); + } + match node { + Node::Split(ch) => { + let sub = PARTITION_SUBSIZE[PARTITION_SPLIT][bsize] as usize; + self.partition(r, c, sub, &ch[0]); + self.partition(r, c + half, sub, &ch[1]); + self.partition(r + half, c, sub, &ch[2]); + self.partition(r + half, c + half, sub, &ch[3]); + } + Node::Leaf(leaf) => { + debug_assert_eq!(leaf.bsize, bsize); + self.block(r, c, leaf); + } + // Blocks inside the frame are always decided; nothing is coded outside it. + Node::Out => debug_assert!(false, "undecided block inside the frame"), + } + } + + fn block(&mut self, r: usize, c: usize, b: &Leaf) { + self.r = r; + self.c = c; + self.bsize = b.bsize; + self.avail_u = r > 0; + self.avail_l = c > 0; + let bsize = b.bsize; + let n4 = NUM_4X4_BLOCKS_WIDE[bsize] as usize; + // skip + let mut ctx = 0; + if self.avail_u { + ctx += self.mi.skip[self.mi.idx(r - 1, c)] as usize; + } + if self.avail_l { + ctx += self.mi.skip[self.mi.idx(r, c - 1)] as usize; + } + let mut y_mode = b.y_mode; + if self.intra_only { + self.sw.symbol(b.skip as usize, &mut self.cdf.skip[ctx]); + let above = if self.avail_u { self.mi.y_mode[self.mi.idx(r - 1, c)] as usize } else { DC_PRED }; + let left = if self.avail_l { self.mi.y_mode[self.mi.idx(r, c - 1)] as usize } else { DC_PRED }; + let actx = INTRA_MODE_CONTEXT[above] as usize; + let lctx = INTRA_MODE_CONTEXT[left] as usize; + self.sw.symbol(b.y_mode, &mut self.kf_y_mode[actx][lctx]); + self.intra_angle_and_uv(b); + } else { + self.sw.symbol(b.skip as usize, &mut self.cdf.skip[ctx]); + // is_inter + let left = if self.avail_l { self.mi.ref_frame[self.mi.idx(r, c - 1)] } else { [INTRA_FRAME as i8, -1] }; + let above = if self.avail_u { self.mi.ref_frame[self.mi.idx(r - 1, c)] } else { [INTRA_FRAME as i8, -1] }; + let left_intra = left[0] <= INTRA_FRAME as i8; + let above_intra = above[0] <= INTRA_FRAME as i8; + let ctx = if self.avail_u && self.avail_l { + if left_intra && above_intra { 3 } else { (left_intra || above_intra) as usize } + } else if self.avail_u || self.avail_l { + 2 * (if self.avail_u { above_intra } else { left_intra }) as usize + } else { + 0 + }; + self.sw.symbol(b.is_inter as usize, &mut self.cdf.is_inter[ctx]); + if b.is_inter { + y_mode = self.inter_block_mode_info(b, left, above); + } else { + let ctx = SIZE_GROUP[bsize] as usize; + self.sw.symbol(b.y_mode, &mut self.cdf.y_mode[ctx]); + self.intra_angle_and_uv(b); + } + } + // mode info stored before the residual + let (rows, cols) = (n4.min(self.g.mi_rows - r), n4.min(self.g.mi_cols - c)); + for y in 0..rows { + for x in 0..cols { + let i = self.mi.idx(r + y, c + x); + self.mi.y_mode[i] = y_mode as u8; + self.mi.ref_frame[i] = if b.is_inter { [LAST_FRAME as i8, -1] } else { [INTRA_FRAME as i8, -1] }; + self.mi.written[i] = true; + if b.is_inter { + self.mi.mv[i] = b.mv; + } + } + } + if b.skip { + // reset_block_context( ) + for plane in 0..3 { + let s = (plane > 0) as usize; + for i in (c >> s)..((c + n4) >> s) { + self.above_level[plane][i] = 0; + self.above_dc[plane][i] = 0; + } + for i in (r >> s)..((r + n4) >> s) { + self.left_level[plane][i] = 0; + self.left_dc[plane][i] = 0; + } + } + } else { + let log2 = (n4 * 4).trailing_zeros(); + debug_assert!(log2 <= 5, "64x64 blocks are coded as skip blocks"); + for plane in 0..3 { + let s = (plane > 0) as u32; + self.coeffs(plane, (c * 4) >> s, (r * 4) >> s, tx_size_for(log2 - s), b, &b.coefs[plane]); + } + } + for y in 0..rows { + for x in 0..cols { + let i = self.mi.idx(r + y, c + x); + self.mi.is_inter[i] = b.is_inter; + self.mi.skip[i] = b.skip; + self.mi.mi_size[i] = bsize as u8; + } + } + // LoopfilterTxSizes: one transform block per plane + let log2 = (n4 * 4).trailing_zeros(); + let stride = self.g.mi_cols + 32; + for plane in 0..3 { + let s = (plane > 0) as usize; + let tx = tx_size_for(log2 - s as u32) as u8; + let (r0, c0, m) = (r >> s, c >> s, n4 >> s); + for y in r0..r0 + m { + self.lf_tx[plane][y * stride + c0..y * stride + c0 + m].iter_mut().for_each(|v| *v = tx); + } + } + } + + fn intra_angle_and_uv(&mut self, b: &Leaf) { + if self.bsize >= BLOCK_8X8 && is_directional(b.y_mode) { + self.sw.symbol(MAX_ANGLE_DELTA, &mut self.cdf.angle_delta[b.y_mode - V_PRED]); + } + let bs = block_size_px(self.bsize); + if bs <= 32 { + self.sw.symbol(b.uv_mode, &mut self.cdf.uv_mode_cfl_allowed[b.y_mode]); + } else { + self.sw.symbol(b.uv_mode, &mut self.cdf.uv_mode_cfl_not_allowed[b.y_mode]); + } + if self.bsize >= BLOCK_8X8 && is_directional(b.uv_mode) { + self.sw.symbol(MAX_ANGLE_DELTA, &mut self.cdf.angle_delta[b.uv_mode - V_PRED]); + } + } + + /// read_ref_frames (LAST_FRAME), find_mv_stack, the inter mode, DRL and motion vector. + /// Returns the inter mode (YMode). + fn inter_block_mode_info(&mut self, b: &Leaf, left: [i8; 2], above: [i8; 2]) -> usize { + let count = |ft: i8| -> usize { + let mut n = 0; + if self.avail_u { + n += (above[0] == ft) as usize + (above[1] == ft) as usize; + } + if self.avail_l { + n += (left[0] == ft) as usize + (left[1] == ft) as usize; + } + n + }; + let cmp = |a: usize, b: usize| match a.cmp(&b) { + std::cmp::Ordering::Less => 0, + std::cmp::Ordering::Equal => 1, + std::cmp::Ordering::Greater => 2, + }; + let (last, last2, last3, gold) = (count(LAST_FRAME as i8), count(LAST2_FRAME as i8), count(LAST3_FRAME as i8), count(GOLDEN_FRAME as i8)); + let (bwd, alt2, alt) = (count(BWDREF_FRAME as i8), count(ALTREF2_FRAME as i8), count(ALTREF_FRAME as i8)); + let ctx_p1 = cmp(last + last2 + last3 + gold, bwd + alt2 + alt); + let ctx_comp_ref = cmp(last + last2, last3 + gold); + let ctx_comp_ref_p1 = cmp(last, last2); + self.sw.symbol(0, &mut self.cdf.single_ref[ctx_p1][0]); + self.sw.symbol(0, &mut self.cdf.single_ref[ctx_comp_ref][2]); + self.sw.symbol(0, &mut self.cdf.single_ref[ctx_comp_ref_p1][3]); + self.find_mv_stack(); + let mv = b.mv; + let s = &self.mvs; + let mode = if mv == [0, 0] { + GLOBALMV + } else if mv == s.ref_stack_mv[0] { + NEARESTMV + } else if mv == s.ref_stack_mv[1] { + NEARMV + } else { + NEWMV + }; + let (nctx, zctx, rctx) = (s.new_mv_context, s.zero_mv_context, s.ref_mv_context); + self.sw.symbol((mode != NEWMV) as usize, &mut self.cdf.new_mv[nctx]); + if mode != NEWMV { + self.sw.symbol((mode != GLOBALMV) as usize, &mut self.cdf.zero_mv[zctx]); + if mode != GLOBALMV { + self.sw.symbol((mode == NEARMV) as usize, &mut self.cdf.ref_mv[rctx]); + } + } + // DRL: ref_mv_idx 0 for NEWMV, 1 for NEARMV + if mode == NEWMV { + if self.mvs.num_mv_found > 1 { + let ctx = self.mvs.drl_ctx_stack[0]; + self.sw.symbol(0, &mut self.cdf.drl_mode[ctx]); + } + } else if mode == NEARMV && self.mvs.num_mv_found > 2 { + let ctx = self.mvs.drl_ctx_stack[1]; + self.sw.symbol(0, &mut self.cdf.drl_mode[ctx]); + } + if mode == NEWMV { + let pred = self.mvs.ref_stack_mv[0]; + self.write_mv([mv[0] - pred[0], mv[1] - pred[1]]); + } + mode + } + + fn write_mv(&mut self, diff: MvP) { + let joint = ((diff[0] != 0) as usize) << 1 | (diff[1] != 0) as usize; + // MV_JOINT_ZERO, MV_JOINT_HNZVZ (col only), MV_JOINT_HZVNZ (row only), MV_JOINT_HNZVNZ + self.sw.symbol(joint, &mut self.cdf.mv_joint); + for (comp, &d) in diff.iter().enumerate() { + if d != 0 { + self.write_mv_component(comp, d); + } + } + } + + fn write_mv_component(&mut self, comp: usize, v: i32) { + debug_assert!(v % 2 == 0, "quarter-sample precision"); + self.sw.symbol((v < 0) as usize, &mut self.cdf.mv_sign[comp]); + let m = v.unsigned_abs() - 1; // odd: the implied hp bit is 1 + let fr = ((m >> 1) & 3) as usize; + let int = m >> 3; + if int < CLASS0_SIZE as u32 { + self.sw.symbol(MV_CLASS_0, &mut self.cdf.mv_class[comp]); + self.sw.symbol(int as usize, &mut self.cdf.mv_class0_bit[comp]); + self.sw.symbol(fr, &mut self.cdf.mv_class0_fr[comp][int as usize]); + } else { + let class = 31 - int.leading_zeros(); + let d = int - (1 << class); + self.sw.symbol(class as usize, &mut self.cdf.mv_class[comp]); + for i in 0..class as usize { + self.sw.symbol(((d >> i) & 1) as usize, &mut self.cdf.mv_bit[comp][i]); + } + self.sw.symbol(fr, &mut self.cdf.mv_fr[comp]); + } + } + + // ---- coefficients ---------------------------------------------------------------------- + + fn coeffs(&mut self, plane: usize, start_x: usize, start_y: usize, tx_sz: usize, b: &Leaf, levels: &[i32]) { + let x4 = start_x >> 2; + let y4 = start_y >> 2; + let w4 = TX_WIDTH[tx_sz] as usize >> 2; + let h4 = TX_HEIGHT[tx_sz] as usize >> 2; + let tx_sz_ctx = (TX_SIZE_SQR[tx_sz] as usize + TX_SIZE_SQR_UP[tx_sz] as usize + 1) >> 1; + let ptype = (plane > 0) as usize; + let scan = scan_for(tx_sz); + let mut eob = 0; + for (i, &pos) in scan.iter().enumerate() { + if levels[pos as usize] != 0 { + eob = i + 1; + } + } + let ctx = self.all_zero_ctx(plane, tx_sz, x4, y4, w4, h4); + self.sw.symbol((eob == 0) as usize, &mut self.cdf.txb_skip[tx_sz_ctx][ctx]); + let mut cul_level = 0u32; + let mut dc_category = 0u8; + if eob == 0 { + if plane == 0 { + self.set_tx_types(x4, y4, tx_sz, DCT_DCT); + } + } else { + if plane == 0 { + self.write_transform_type(x4, y4, tx_sz, b); + } + let plane_tx_type = self.compute_tx_type(plane, tx_sz, x4, y4, b); + debug_assert!(plane_tx_type == DCT_DCT || matches!(plane_tx_type, ADST_DCT | DCT_ADST | ADST_ADST)); + let eob_multisize = (TX_WIDTH_LOG2[tx_sz] as usize).min(5) + (TX_HEIGHT_LOG2[tx_sz] as usize).min(5) - 4; + let eob_pt = if eob < 2 { eob } else { (32 - ((eob - 1) as u32).leading_zeros()) as usize + 1 }; + let ectx = 0; // TX_CLASS_2D + let s = eob_pt - 1; + match eob_multisize { + 0 => self.sw.symbol(s, &mut self.cdf.eob_pt_16[ptype][ectx]), + 1 => self.sw.symbol(s, &mut self.cdf.eob_pt_32[ptype][ectx]), + 2 => self.sw.symbol(s, &mut self.cdf.eob_pt_64[ptype][ectx]), + 3 => self.sw.symbol(s, &mut self.cdf.eob_pt_128[ptype][ectx]), + 4 => self.sw.symbol(s, &mut self.cdf.eob_pt_256[ptype][ectx]), + 5 => self.sw.symbol(s, &mut self.cdf.eob_pt_512[ptype]), + _ => self.sw.symbol(s, &mut self.cdf.eob_pt_1024[ptype]), + } + if eob_pt >= 3 { + let eob_shift = eob_pt - 3; + let offset = eob - ((1 << (eob_pt - 2)) + 1); + self.sw.symbol((offset >> eob_shift) & 1, &mut self.cdf.eob_extra[tx_sz_ctx][ptype][eob_pt - 3]); + for i in 1..eob_pt - 2 { + let sh = eob_pt - 2 - 1 - i; + self.sw.bool((offset >> sh) & 1 == 1); + } + } + let bwl = TX_WIDTH_LOG2[tx_sz] as usize; + let height = TX_HEIGHT[tx_sz] as usize; + let width = 1usize << bwl; + // Quant[] as the decoder sees it while parsing: the levels coded so far (capped) + let mut quant = std::mem::take(&mut self.quant); + quant.clear(); + quant.resize(width * height, 0); + for c in (0..eob).rev() { + let pos = scan[c] as usize; + let a = levels[pos].unsigned_abs().min(15) as i32; + if c == eob - 1 { + let ctx = coeff_base_eob_ctx(c, bwl, height); + self.sw.symbol((a.min(3) - 1) as usize, &mut self.cdf.coeff_base_eob[tx_sz_ctx][ptype][ctx]); + } else { + let ctx = coeff_base_ctx(&quant, tx_sz, bwl, width, height, pos); + self.sw.symbol(a.min(3) as usize, &mut self.cdf.coeff_base[tx_sz_ctx][ptype][ctx]); + } + if a > NUM_BASE_LEVELS as i32 { + let ctx = coeff_br_ctx(&quant, bwl, height, pos); + let mut rem = a - 3; + for _ in 0..(COEFF_BASE_RANGE / (BR_CDF_SIZE - 1)) { + let br = rem.min(3); + self.sw.symbol(br as usize, &mut self.cdf.coeff_br[tx_sz_ctx.min(TX_32X32)][ptype][ctx]); + rem -= br; + if br < 3 { + break; + } + } + } + quant[pos] = a; + } + self.quant = quant; + for c in 0..eob { + let pos = scan[c] as usize; + let v = levels[pos]; + if v != 0 { + if c == 0 { + let ctx = self.dc_sign_ctx(plane, x4, y4, w4, h4); + self.sw.symbol((v < 0) as usize, &mut self.cdf.dc_sign[ptype][ctx]); + } else { + self.sw.bool(v < 0); + } + } + let a = v.unsigned_abs(); + if a > (NUM_BASE_LEVELS + COEFF_BASE_RANGE) as u32 { + // Golomb code of a - 15 + let x = a - 15 + 1; + let length = 32 - x.leading_zeros(); + for _ in 0..length - 1 { + self.sw.bool(false); + } + self.sw.bool(true); + for i in (0..length - 1).rev() { + self.sw.bool((x >> i) & 1 == 1); + } + } + if pos == 0 && a > 0 { + dc_category = if v < 0 { 1 } else { 2 }; + } + cul_level += a & 0xFFFFF; + } + cul_level = cul_level.min(63); + } + for i in 0..w4 { + if x4 + i < self.above_level[plane].len() { + self.above_level[plane][x4 + i] = cul_level as u8; + self.above_dc[plane][x4 + i] = dc_category; + } + } + for i in 0..h4 { + if y4 + i < self.left_level[plane].len() { + self.left_level[plane][y4 + i] = cul_level as u8; + self.left_dc[plane][y4 + i] = dc_category; + } + } + } + + fn write_transform_type(&mut self, x4: usize, y4: usize, tx_sz: usize, b: &Leaf) { + // transform_type( ) + let set = tx_set(tx_sz, b.is_inter); + let t = b.tx_type_y; + if set > 0 && self.qidx > 0 { + let sqr = TX_SIZE_SQR[tx_sz] as usize; + if b.is_inter { + // The decision only picks types of this set (DCT_DCT otherwise). + let s = TX_TYPE_INTER_INV_SET3.iter().position(|&v| v as usize == t).unwrap_or_default(); + self.sw.symbol(s, &mut self.cdf.inter_tx_type_set3[sqr]); + } else { + let s = TX_TYPE_INTRA_INV_SET2.iter().position(|&v| v as usize == t).unwrap_or_default(); + self.sw.symbol(s, &mut self.cdf.intra_tx_type_set2[sqr][b.y_mode]); + } + } else { + debug_assert_eq!(t, DCT_DCT); + } + self.set_tx_types(x4, y4, tx_sz, t); + } + + fn set_tx_types(&mut self, x4: usize, y4: usize, tx_sz: usize, t: usize) { + for i in 0..(TX_WIDTH[tx_sz] as usize >> 2) { + for j in 0..(TX_HEIGHT[tx_sz] as usize >> 2) { + if y4 + j < self.g.mi_rows && x4 + i < self.g.mi_cols { + let k = self.mi.idx(y4 + j, x4 + i); + self.mi.tx_type[k] = t as u8; + } + } + } + } + + fn compute_tx_type(&self, plane: usize, tx_sz: usize, x4: usize, y4: usize, b: &Leaf) -> usize { + if TX_SIZE_SQR_UP[tx_sz] as usize > TX_32X32 { + return DCT_DCT; + } + if plane == 0 { + return self.mi.tx_type[self.mi.idx(y4, x4)] as usize; + } + if b.is_inter { + let set = tx_set(tx_sz, true); + let xx = self.c.max(x4 << 1); + let yy = self.r.max(y4 << 1); + let t = self.mi.tx_type[self.mi.idx(yy, xx)] as usize; + return if tx_in_set(set, true, t) { t } else { DCT_DCT }; + } + intra_uv_tx_type(b.uv_mode, tx_sz) + } + + fn all_zero_ctx(&self, plane: usize, tx_sz: usize, x4: usize, y4: usize, w4: usize, h4: usize) -> usize { + let s = (plane > 0) as usize; + let max_x4 = self.g.mi_cols >> s; + let max_y4 = self.g.mi_rows >> s; + let w = TX_WIDTH[tx_sz] as usize; + let h = TX_HEIGHT[tx_sz] as usize; + let bsize = SUBSAMPLED_SIZE[self.bsize][s][s] as usize; + let bw = block_size_px(bsize); + let bh = 4 * NUM_4X4_BLOCKS_HIGH[bsize] as usize; + let al = &self.above_level[plane]; + let ll = &self.left_level[plane]; + if plane == 0 { + let mut top = 0u32; + let mut left = 0u32; + for k in 0..w4 { + if x4 + k < max_x4 { + top = top.max(al[x4 + k] as u32); + } + } + for k in 0..h4 { + if y4 + k < max_y4 { + left = left.max(ll[y4 + k] as u32); + } + } + top = top.min(255); + left = left.min(255); + if bw == w && bh == h { + 0 + } else if top == 0 && left == 0 { + 1 + } else if top == 0 || left == 0 { + 2 + (top.max(left) > 3) as usize + } else if top.max(left) <= 3 { + 4 + } else if top.min(left) <= 3 { + 5 + } else { + 6 + } + } else { + let ad = &self.above_dc[plane]; + let ld = &self.left_dc[plane]; + let mut above = 0u8; + let mut left = 0u8; + for i in 0..w4 { + if x4 + i < max_x4 { + above |= al[x4 + i] | ad[x4 + i]; + } + } + for i in 0..h4 { + if y4 + i < max_y4 { + left |= ll[y4 + i] | ld[y4 + i]; + } + } + let mut ctx = (above != 0) as usize + (left != 0) as usize + 7; + if bw * bh > w * h { + ctx += 3; + } + ctx + } + } + + fn dc_sign_ctx(&self, plane: usize, x4: usize, y4: usize, w4: usize, h4: usize) -> usize { + let s = (plane > 0) as usize; + let max_x4 = self.g.mi_cols >> s; + let max_y4 = self.g.mi_rows >> s; + let mut dc_sign = 0i32; + for k in 0..w4 { + if x4 + k < max_x4 { + match self.above_dc[plane][x4 + k] { + 1 => dc_sign -= 1, + 2 => dc_sign += 1, + _ => {} + } + } + } + for k in 0..h4 { + if y4 + k < max_y4 { + match self.left_dc[plane][y4 + k] { + 1 => dc_sign -= 1, + 2 => dc_sign += 1, + _ => {} + } + } + } + match dc_sign.signum() { + -1 => 1, + 1 => 2, + _ => 0, + } + } + + // ---- motion vector prediction (7.10.2), single reference LAST_FRAME -------------------- + + fn inside(&self, r: isize, c: isize) -> bool { + c >= 0 && c < self.g.mi_cols as isize && r >= 0 && r < self.g.mi_rows as isize + } + + fn find_mv_stack(&mut self) { + let n4 = NUM_4X4_BLOCKS_WIDE[self.bsize] as isize; + let (bw4, bh4) = (n4, n4); + self.mvs = MvStack::default(); + self.scan_row(-1); + let mut found_above = self.mvs.found_match; + self.mvs.found_match = false; + self.scan_col(-1); + let mut found_left = self.mvs.found_match; + self.mvs.found_match = false; + if bw4.max(bh4) <= 16 { + self.scan_point(-1, bw4); + } + if self.mvs.found_match { + found_above = true; + } + self.mvs.close_matches = found_above as usize + found_left as usize; + let num_nearest = self.mvs.num_mv_found; + let num_new = self.mvs.new_mv_count; + for idx in 0..num_nearest { + self.mvs.weight_stack[idx] += REF_CAT_LEVEL as u32; + } + self.mvs.zero_mv_context = 0; + // use_ref_frame_mvs = 0: no temporal candidates + self.scan_point(-1, -1); + if self.mvs.found_match { + found_above = true; + } + self.mvs.found_match = false; + self.scan_row(-3); + if self.mvs.found_match { + found_above = true; + } + self.mvs.found_match = false; + self.scan_col(-3); + if self.mvs.found_match { + found_left = true; + } + self.mvs.found_match = false; + if bh4 > 1 { + self.scan_row(-5); + } + if self.mvs.found_match { + found_above = true; + } + self.mvs.found_match = false; + if bw4 > 1 { + self.scan_col(-5); + } + if self.mvs.found_match { + found_left = true; + } + self.mvs.total_matches = found_above as usize + found_left as usize; + let n = self.mvs.num_mv_found; + self.sort_stack(0, num_nearest); + self.sort_stack(num_nearest, n); + if self.mvs.num_mv_found < 2 { + self.extra_search(); + } + self.context_and_clamping(num_new); + } + + fn scan_row(&mut self, delta_row: isize) { + let bw4 = NUM_4X4_BLOCKS_WIDE[self.bsize] as isize; + let (mi_row, mi_col) = (self.r as isize, self.c as isize); + let end4 = bw4.min(self.g.mi_cols as isize - mi_col).min(16); + let mut delta_col = 0; + let use_step16 = bw4 >= 16; + let mut delta_row = delta_row; + if delta_row.abs() > 1 { + delta_row += mi_row & 1; + delta_col = 1 - (mi_col & 1); + } + let mut i = 0; + while i < end4 { + let mv_row = mi_row + delta_row; + let mv_col = mi_col + delta_col + i; + if !self.inside(mv_row, mv_col) { + break; + } + let k = self.mi.idx(mv_row as usize, mv_col as usize); + let mut len = bw4.min(NUM_4X4_BLOCKS_WIDE[self.mi.mi_size[k] as usize] as isize); + if delta_row.abs() > 1 { + len = len.max(2); + } + if use_step16 { + len = len.max(4); + } + self.add_ref_mv_candidate(mv_row as usize, mv_col as usize, len as u32 * 2); + i += len; + } + } + + fn scan_col(&mut self, delta_col: isize) { + let bh4 = NUM_4X4_BLOCKS_HIGH[self.bsize] as isize; + let (mi_row, mi_col) = (self.r as isize, self.c as isize); + let end4 = bh4.min(self.g.mi_rows as isize - mi_row).min(16); + let mut delta_row = 0; + let use_step16 = bh4 >= 16; + let mut delta_col = delta_col; + if delta_col.abs() > 1 { + delta_row = 1 - (mi_row & 1); + delta_col += mi_col & 1; + } + let mut i = 0; + while i < end4 { + let mv_row = mi_row + delta_row + i; + let mv_col = mi_col + delta_col; + if !self.inside(mv_row, mv_col) { + break; + } + let k = self.mi.idx(mv_row as usize, mv_col as usize); + let mut len = bh4.min(NUM_4X4_BLOCKS_HIGH[self.mi.mi_size[k] as usize] as isize); + if delta_col.abs() > 1 { + len = len.max(2); + } + if use_step16 { + len = len.max(4); + } + self.add_ref_mv_candidate(mv_row as usize, mv_col as usize, len as u32 * 2); + i += len; + } + } + + fn scan_point(&mut self, delta_row: isize, delta_col: isize) { + let mv_row = self.r as isize + delta_row; + let mv_col = self.c as isize + delta_col; + if self.inside(mv_row, mv_col) && self.mi.written[self.mi.idx(mv_row as usize, mv_col as usize)] { + self.add_ref_mv_candidate(mv_row as usize, mv_col as usize, 4); + } + } + + fn add_ref_mv_candidate(&mut self, mv_row: usize, mv_col: usize, weight: u32) { + let i = self.mi.idx(mv_row, mv_col); + if !self.mi.is_inter[i] { + return; + } + let rf = self.mi.ref_frame[i]; + for cand_list in 0..2 { + if rf[cand_list] == LAST_FRAME as i8 { + self.search_stack(i, cand_list, weight); + } + } + } + + fn search_stack(&mut self, i: usize, cand_list: usize, weight: u32) { + debug_assert_eq!(cand_list, 0); + let cand_mode = self.mi.y_mode[i] as usize; + // gm_type is IDENTITY: the stored motion vector is used + let mut cand = self.mi.mv[i]; + lower_mv_precision(&mut cand); + if cand_mode == NEWMV { + self.mvs.new_mv_count += 1; + } + self.mvs.found_match = true; + let n = self.mvs.num_mv_found; + if let Some(idx) = (0..n).find(|&k| self.mvs.ref_stack_mv[k] == cand) { + self.mvs.weight_stack[idx] += weight; + } else if n < MAX_REF_MV_STACK_SIZE { + self.mvs.ref_stack_mv[n] = cand; + self.mvs.weight_stack[n] = weight; + self.mvs.num_mv_found += 1; + } + } + + fn sort_stack(&mut self, start: usize, end: usize) { + let mut end = end; + let s = &mut self.mvs; + while end > start { + let mut new_end = start; + for idx in start + 1..end { + if s.weight_stack[idx - 1] < s.weight_stack[idx] { + s.weight_stack.swap(idx - 1, idx); + s.ref_stack_mv.swap(idx - 1, idx); + new_end = idx; + } + } + end = new_end; + } + } + + fn extra_search(&mut self) { + let (mi_row, mi_col) = (self.r as isize, self.c as isize); + let n4 = NUM_4X4_BLOCKS_WIDE[self.bsize] as isize; + let w4 = n4.min(16).min(self.g.mi_cols as isize - mi_col); + let h4 = n4.min(16).min(self.g.mi_rows as isize - mi_row); + let num4x4 = w4.min(h4); + for pass in 0..2 { + let mut idx = 0; + while idx < num4x4 && self.mvs.num_mv_found < 2 { + let (mv_row, mv_col) = if pass == 0 { (mi_row - 1, mi_col + idx) } else { (mi_row + idx, mi_col - 1) }; + if !self.inside(mv_row, mv_col) { + break; + } + let k = self.mi.idx(mv_row as usize, mv_col as usize); + // add_extra_mv_candidate( ) (no sign bias: a single forward reference) + let rfs = self.mi.ref_frame[k]; + for &cand_ref in rfs.iter().take(2) { + if cand_ref > INTRA_FRAME as i8 { + let cand = self.mi.mv[k]; + let n = self.mvs.num_mv_found; + if !(0..n).any(|j| self.mvs.ref_stack_mv[j] == cand) { + self.mvs.ref_stack_mv[n] = cand; + self.mvs.weight_stack[n] = 2; + self.mvs.num_mv_found += 1; + } + } + } + let sz = self.mi.mi_size[k] as usize; + idx += if pass == 0 { NUM_4X4_BLOCKS_WIDE[sz] } else { NUM_4X4_BLOCKS_HIGH[sz] } as isize; + } + } + for idx in self.mvs.num_mv_found..2 { + self.mvs.ref_stack_mv[idx] = [0, 0]; // GlobalMvs[ 0 ] + } + } + + fn context_and_clamping(&mut self, num_new: usize) { + let bs = block_size_px(self.bsize) as i32; + let n = self.mvs.num_mv_found; + for idx in 0..n { + let mut z = 0; + if idx + 1 < n { + let w0 = self.mvs.weight_stack[idx]; + let w1 = self.mvs.weight_stack[idx + 1]; + if w0 >= REF_CAT_LEVEL as u32 { + if w1 < REF_CAT_LEVEL as u32 { + z = 1; + } + } else { + z = 2; + } + } + self.mvs.drl_ctx_stack[idx] = z; + } + let n4 = NUM_4X4_BLOCKS_WIDE[self.bsize] as i32; + for idx in 0..n { + let mut mv = self.mvs.ref_stack_mv[idx]; + let border_r = MV_BORDER as i32 + bs * 8; + let to_top = -((self.r as i32 * 4) * 8); + let to_bottom = ((self.g.mi_rows as i32 - n4 - self.r as i32) * 4) * 8; + mv[0] = mv[0].clamp(to_top - border_r, to_bottom + border_r); + let to_left = -((self.c as i32 * 4) * 8); + let to_right = ((self.g.mi_cols as i32 - n4 - self.c as i32) * 4) * 8; + mv[1] = mv[1].clamp(to_left - border_r, to_right + border_r); + self.mvs.ref_stack_mv[idx] = mv; + } + let (close, total) = (self.mvs.close_matches, self.mvs.total_matches); + if close == 0 { + self.mvs.new_mv_context = total.min(1); + self.mvs.ref_mv_context = total; + } else if close == 1 { + self.mvs.new_mv_context = 3 - num_new.min(1); + self.mvs.ref_mv_context = 2 + total; + } else { + self.mvs.new_mv_context = 5 - num_new.min(1); + self.mvs.ref_mv_context = 5; + } + } +} + +/// lower_mv_precision( ) with allow_high_precision_mv = 0 and force_integer_mv = 0. +fn lower_mv_precision(mv: &mut MvP) { + for c in mv.iter_mut() { + if *c & 1 != 0 { + *c += if *c > 0 { -1 } else { 1 }; + } + } +} + +fn coeff_base_eob_ctx(c: usize, bwl: usize, height: usize) -> usize { + if c == 0 { + 0 + } else if c <= (height << bwl) / 8 { + 1 + } else if c <= (height << bwl) / 4 { + 2 + } else { + 3 + } +} + +/// Context of coeff_base for TX_CLASS_2D (8.3.2). +fn coeff_base_ctx(quant: &[i32], tx_sz: usize, bwl: usize, width: usize, height: usize, pos: usize) -> usize { + let row = pos >> bwl; + let col = pos - (row << bwl); + let mut mag = 0i32; + for off in SIG_REF_DIFF_OFFSET[TX_CLASS_2D].iter() { + let ref_row = row + off[0] as usize; + let ref_col = col + off[1] as usize; + if ref_row < height && ref_col < width { + mag += quant[(ref_row << bwl) + ref_col].abs().min(3); + } + } + let ctx = ((mag + 1) >> 1).min(4) as usize; + if row == 0 && col == 0 { + return 0; + } + ctx + COEFF_BASE_CTX_OFFSET[tx_sz][row.min(4)][col.min(4)] as usize +} + +/// Context of coeff_br for TX_CLASS_2D. +fn coeff_br_ctx(quant: &[i32], bwl: usize, txh: usize, pos: usize) -> usize { + let txw = 1usize << bwl; + let row = pos >> bwl; + let col = pos - (row << bwl); + let mut mag = 0i32; + for off in MAG_REF_OFFSET_WITH_TX_CLASS[TX_CLASS_2D].iter() { + let ref_row = row + off[0] as usize; + let ref_col = col + off[1] as usize; + if ref_row < txh && ref_col < txw { + mag += quant[ref_row * txw + ref_col].min((COEFF_BASE_RANGE + NUM_BASE_LEVELS + 1) as i32); + } + } + let mag = ((mag + 1) >> 1).min(6) as usize; + if pos == 0 { + mag + } else if row < 2 && col < 2 { + mag + 7 + } else { + mag + 14 + } +} diff --git a/crates/codecs/Cargo.toml b/crates/codecs/Cargo.toml new file mode 100644 index 0000000000..737d097b03 --- /dev/null +++ b/crates/codecs/Cargo.toml @@ -0,0 +1,31 @@ +[package] +name = "craft-codecs" +description = "Umbrella crate re-exporting pure-Rust video codecs (H.264, ProRes, DNx, HEVC, AV1, VP9, VP8)" +version.workspace = true +edition.workspace = true +license.workspace = true +rust-version.workspace = true +repository.workspace = true + +[lints] +workspace = true + +[features] +default = ["all"] +all = ["h264", "prores", "dnx", "hevc", "av1", "vp9", "vp8"] +h264 = ["dep:craft-h264enc"] +prores = ["dep:craft-prores"] +dnx = ["dep:craft-dnx"] +hevc = ["dep:craft-hevcenc"] +av1 = ["dep:craft-av1enc"] +vp9 = ["dep:craft-vp9enc"] +vp8 = ["dep:craft-vp8"] + +[dependencies] +craft-h264enc = { workspace = true, optional = true } +craft-prores = { workspace = true, optional = true } +craft-dnx = { workspace = true, optional = true } +craft-hevcenc = { workspace = true, optional = true } +craft-av1enc = { workspace = true, optional = true } +craft-vp9enc = { workspace = true, optional = true } +craft-vp8 = { workspace = true, optional = true } diff --git a/crates/codecs/README.md b/crates/codecs/README.md new file mode 100644 index 0000000000..64e1559d86 --- /dev/null +++ b/crates/codecs/README.md @@ -0,0 +1,22 @@ +# craft-codecs + +Umbrella crate centralizing pure-Rust video codecs for the Storytold Craft Apps ecosystem. + +## Canonical Ownership & Architecture + +`craft-libs` is the single canonical source of truth for the shared video codecs: +- `craft-h264enc`: Clean-room H.264/AVC baseline, main, and high profile encoder with CABAC/CAVLC rate control. +- `craft-prores`: Clean-room Apple ProRes 422 (Proxy, LT, Standard, HQ) and 4444 encoder and decoder (SMPTE RDD 36). +- `craft-dnx`: Avid DNxHD / DNxHR encoder and decoder (SMPTE ST 2019-1:2016). +- `craft-vp8`: RFC 6386 VP8 frame decoder. +- `craft-hevcenc`: Pure-Rust HEVC (H.265) video encoder with CABAC and rate control. +- `craft-vp9enc`: Pure-Rust VP9 Profile 0 video encoder. +- `craft-av1enc`: Pure-Rust AV1 video encoder with normative spec tables and OBU bitstream generation. + +## Application Migration & De-duplication Plan + +Previously, several applications (specifically `storytold/filmcraft`) maintained in-tree copies of codec implementations. With the centralization into `craft-libs`: +1. `craft-libs` owns and maintains all shared video codecs going forward. +2. In `storytold/filmcraft`, a planned follow-up will migrate workspace dependencies to `craft-libs` crates (`craft-h264enc`, `craft-prores`, etc.) and deprecate or remove the in-tree duplicate crates (`crates/h264enc`, `crates/prores`, `crates/dnx`, `crates/vp8`, `crates/hevc`, `crates/vp9`, `crates/av1`). +3. Audio codecs (AAC, Opus, WAV/AIFF/FLAC I/O) are owned by `craft-audio` (#441–#448), keeping video and audio concerns cleanly separated. + diff --git a/crates/codecs/src/lib.rs b/crates/codecs/src/lib.rs new file mode 100644 index 0000000000..2fcf60aef9 --- /dev/null +++ b/crates/codecs/src/lib.rs @@ -0,0 +1,33 @@ +#![deny(clippy::unwrap_used, clippy::expect_used, clippy::panic, clippy::unimplemented, clippy::todo, clippy::unreachable)] + +//! Storytold ArtCraft shared video codecs library. +//! +//! Centralizes high-performance pure-Rust video codecs: +//! - H.264 / AVC video encoding (`craft-h264enc`) +//! - Apple ProRes video encoding and decoding (`craft-prores`) +//! - Avid DNxHD / DNxHR SMPTE ST 2019-1 encoding and decoding (`craft-dnx`) +//! - HEVC / H.265 video encoding (`craft-hevcenc`) +//! - AV1 video encoding (`craft-av1enc`) +//! - VP9 video encoding (`craft-vp9enc`) +//! - VP8 video decoding (`craft-vp8`) + +#[cfg(feature = "h264")] +pub use craft_h264enc as h264; + +#[cfg(feature = "prores")] +pub use craft_prores as prores; + +#[cfg(feature = "dnx")] +pub use craft_dnx as dnx; + +#[cfg(feature = "hevc")] +pub use craft_hevcenc as hevc; + +#[cfg(feature = "av1")] +pub use craft_av1enc as av1; + +#[cfg(feature = "vp9")] +pub use craft_vp9enc as vp9; + +#[cfg(feature = "vp8")] +pub use craft_vp8 as vp8; diff --git a/crates/dnx/Cargo.toml b/crates/dnx/Cargo.toml new file mode 100644 index 0000000000..b5c8d1a8a5 --- /dev/null +++ b/crates/dnx/Cargo.toml @@ -0,0 +1,22 @@ +[package] +name = "craft-dnx" +description = "Clean-room Avid DNxHD / DNxHR (SMPTE ST 2019-1 VC-3) decoder and encoder for the Craft ecosystem" +version.workspace = true +edition.workspace = true +license.workspace = true +rust-version.workspace = true +repository.workspace = true + +[lints] +workspace = true + +[features] +default = ["threads"] +threads = ["dep:rayon"] + +[dependencies] +thiserror = { workspace = true } +rayon = { workspace = true, optional = true } + +[dev-dependencies] +craft-testkit = { workspace = true } diff --git a/crates/dnx/README.md b/crates/dnx/README.md new file mode 100644 index 0000000000..acbe13ad21 --- /dev/null +++ b/crates/dnx/README.md @@ -0,0 +1,141 @@ +# craft-dnx + +Clean-room Avid DNxHD / DNxHR (VC-3) decoder and encoder in pure Rust, written from the public +**SMPTE ST 2019-1:2016** *VC-3 Picture Compression and Data Stream Format* and its +**Amendment 1:2023** (both free from `pub.smpte.org`). Layer L0: depends only on +`thiserror` and (optional, feature `threads`, default on) `rayon`. Builds +for `wasm32-unknown-unknown`; no `unsafe`. + +The constant tables (Annex D quantisation weights, Annex E VLC tables) were extracted from the +standard's text by [`tools/extract_tables.py`](tools/extract_tables.py) into +`src/spec_tables.rs`; the script checks every table's size and that every VLC table is a +complete prefix code. No code was taken from FFmpeg or any other implementation; ffmpeg is used +only as an external fixture generator and test oracle. + +## Status + +| Area | Status | +|---|---| +| Header (§7.2): all fields, HVN 1–4, RI header growth above 1088 lines, time code | done | +| DNxHD (HD profile) CIDs 1235, 1237, 1238, 1241–1244, 1250–1253, 1258, 1259 | done, oracle-tested | +| CID 1256 (HD RGB 4:4:4) and 1260 (macroblock-adaptive field/frame, 1440×1080i) | done, not oracle-tested (ffmpeg can't encode them) | +| DNxHR (RI profile) 1270–1274: LB, SQ, HQ, HQX, 444; any raster 1…16384 | done, oracle-tested | +| 8-, 10-, 12-bit; 4:2:2, 4:4:4; Y′CbCr and RGB (per-macroblock ACF) | done (12-bit: our encoder's streams, compared with ffmpeg's decode) | +| 4:2:0 (RI only) | done, not oracle-tested (ffmpeg can't encode it) | +| Interlaced: field-coded (two coding units) and frame-coded MACF | done | +| Alpha: DCT-coded and lossless differential RLE (Amendment 1 for HD CIDs) | done, not oracle-tested (no encoder produces it) | +| CBR padding, VBR, scan-line indices, EOF signature / CRC flag | done | +| Encoder: DNxHR LB, SQ, HQ (8-bit), HQX (10/12-bit), 444 (10/12-bit); CBR | done | +| Encoder: DNxHD (HD CIDs), interlaced, alpha, VBR | not yet | + +## API + +```rust +use craft_dnx::*; + +let frame: Frame = decode_frame(&sample)?; // one or two coding units +let f = decode_frame_with(&sample, &DecodeOptions { threads: false, ..Default::default() })?; +let hdr: FrameHeader = probe(&sample)?; // cid, raster, depth, chroma… +let name = cid_name(hdr.cid); // "DNxHR HQX", "DNxHD 1080i 8-bit (CID 1242)" + +let mut enc = Encoder::new(Profile::Hq, 1920, 1080)?; // or Encoder::with_config(EncoderConfig { .. }) +let sample: Vec = enc.encode(&frame)?; // exactly enc.frame_size() bytes (CBR) +``` + +`Frame { width, height, chroma, rgb, bit_depth, y, cb, cr: Vec, alpha, interlace, +color_volume, cid, par }` holds planar samples without row padding. Interlaced frames are woven +(field 1 = top field on even lines). RGB frames hold G, B, R in `y`, `cb`, `cr`. In MOV the +sample entry is `AVdn` (DNxHD) or `AVdh` (DNxHR); `craft-codecs` reads the CID from the +first frame header to label the stream. + +## Decoder + +- Every coding unit's scan-line indices give each macroblock row's byte offset, so rows decode + independently and in parallel, writing straight into their 16-line bands. +- VLCs are decoded with two-level lookup tables (11-bit primary) built from the Annex E lists. +- Inverse quantisation with the CID's Annex D weights; IDCT: the exact orthonormal 8×8 inverse DCT + of equation 8.3, evaluated separably in `f32` (shared design with `craft-prores`). + Output: `floor(x·s + 2^(b−1) + ½)` clipped to `[0, 2^b − 1]`, where `s = 4` at 12 bits (the + 12-bit coefficient domain is ¼ of the orthonormal DCT, as Table 14's 13-bit IDCT input range + for 12-bit samples implies) and 1 otherwise. +- Robustness: every offset and size is validated, run lengths past coefficient 63 are rejected, + and corrupted or truncated streams return `Err` without panicking (`tests/it/roundtrip.rs`). + +### Where we follow ffmpeg rather than the letter of the standard + +ffmpeg's decoder is the reference everyone interoperates with (and the oracle of our tests). In +three places it differs from the text of ST 2019-1:2016; black-box probes (`src/ffmpeg_probe.rs`: +streams with a single coefficient, decoded by ffmpeg, measured with a forward DCT) pinned down +its behaviour, and we match it by default: + +1. **Reconstruction offset.** Equation 8.1 reconstructs a quantised amplitude `A` at + ≈ `(A + ½)·W·qsf/p`. ffmpeg reconstructs at `(A + ¾)·W·qsf/p` (fitted intercept 0.750 ± 0.01 + steps for every CID family and step size). The decoder does the same; + `DecodeOptions::spec_dequant` selects equation 8.1 instead. The encoder picks the amplitude + nearest to the `(A + ¾)` reconstruction. +2. **CID 1271 (DNxHR HQX) weights.** Table C.2 assigns Table D.1 to CID 1271. ffmpeg's decoder and + encoder use Table D.4 (the CID 1241 weights) — fitted RMS error 0.65 against D.4, > 2.6 + against every other table. We use D.4. +3. **4:4:4 signalling.** ffmpeg writes RGB DNxHR 444 with `CLF = 0`, and Y′CbCr 444 with `CLF = 1` + plus `ACF = 1` on every macroblock; its channel order is G, B, R (Ch1 = G). The decoder treats + every 4:4:4 stream as RGB except macroblocks flagged `ACF = 1` (a stream that is entirely + ACF = 1 is returned as Y′CbCr; mixed streams are converted to RGB macroblock by macroblock as + §8.1.1 describes), and the encoder writes the same signalling. + +Also noted: the RLE alpha pseudo code (Figure 50) tests the header bit the opposite way from +the prose of §7.3.1.2; we follow the prose (normative text takes precedence). + +## Encoder + +- DNxHR LB/SQ/HQ (8-bit 4:2:2), HQX (10- or 12-bit 4:2:2) and 444 (10- or 12-bit 4:4:4, + Y′CbCr or RGB), progressive, CBR at exactly the equation 7.1 frame size (e.g. 917 504 bytes + for 1080p HQ, 606 208 for SQ). Input at any depth 8–16 is rescaled to the coded depth. +- Every macroblock is transformed once (orthonormal FDCT) and stored pre-divided by its weights. + Rate control binary-searches the smallest uniform `qsf` whose frame fits the payload + (including the 4-byte scan-line alignment), starting from the previous frame's value, then + gives macroblocks one finer step, spread evenly over the frame, while the budget allows. +- `export::Format::DnxHr` (`file.exportMedia {"format":"dnxhr","dnxProfile":"lb|sq|hq|hqx"}`) + writes QuickTime `AVdh` + PCM. The Avid-specific `ACLR`/`ADHR` sample-entry atoms are not + written (ffmpeg and our reader don't need them). + +## Accuracy (vs ffmpeg as an external oracle) + +`tests/it/oracle_decode.rs` decodes 22 fixtures made by ffmpeg's VC-3 encoder: CIDs 1235, 1237, +1238, 1241, 1242, 1243, 1244, 1250, 1251, 1252, 1253, 1258, 1259 and DNxHR LB/SQ/HQ/HQX/444 +(Y′CbCr and RGB), with testsrc2, mandelbrot and heavy noise, at 1920×1080, 1280×720, 960×720, +1440×1080 (thin raster, interlaced), 1000×562, 640×360, 256×120 and 3840×2160. Compared with +ffmpeg's decode at the native depth, **every fixture is within ±2 LSB, with at most ~50 samples +per million at ±2** (typically 1–30 per fixture); 87–99% of samples are bit-identical and the +mean absolute error is 0.01–0.13 LSB. The remaining differences are ffmpeg's integer IDCT; this +decoder evaluates the exact IDCT of equation 8.3, so decode is **not bit-exact, but within IDCT +precision** of ffmpeg. (ffmpeg wraps rather than clips 10-bit reconstructions that overflow +1023, so the noise fixtures keep their samples inside 24…231 before encoding.) + +`tests/it/oracle_encode.rs` encodes 1920×1080 frames with every profile and writes them into a +minimal test-only MOV. ffmpeg decodes them with `-xerror`, without errors, and agrees with our +decoder within ±2 (90–99% identical). Luma PSNR against the source: HQ 58.8 dB (mandelbrot) and +66.3 dB (testsrc2), SQ 57.5 dB, LB 56.1 dB, HQX 10-bit 67.5 dB, HQX 12-bit 80.2 dB, 444 10-bit +79.7 dB — all above the 45 dB HQ target. + +## Performance + +`cargo test --release -p craft-dnx --test it perf:: -- --ignored --nocapture` on an M4 Pro +(14 cores) while the machine was **heavily overloaded** by other jobs (load average ≈ 190). +Under the same load `craft-prores`' perf test measured 30 fps threaded / 11.8 fps single +for a 1080p HQ frame, against 318 / 53 fps in its README on a less loaded machine, so these are +lower bounds by a factor of roughly 4–10: + +| Frame (busy synthetic content) | Decode, threaded | Decode, 1 thread | Encode | +|---|---|---|---| +| DNxHR HQ 1080p (896 KB) | 41 fps | 13.2 fps | 4.1 fps | +| DNxHR HQX 10-bit 1080p (896 KB) | 39 fps | 8.9 fps | 3.6 fps | +| DNxHR SQ 2160p (2352 KB) | 21 fps | 15.6 fps | 1.1 fps | +| DNxHR HQX 10-bit 2160p (3556 KB) | 16 fps | 12.2 fps | 1.1 fps | + +## Gaps + +- Decode is within IDCT precision of ffmpeg, not bit-exact (see above). +- Encoder: no DNxHD (HD CIDs), interlaced, alpha or VBR output; no RDO/trellis. +- CIDs 1256/1260 and alpha decoding are implemented from the standard but have no oracle + fixtures, because no available encoder produces them. +- No MXF; DNx in MOV only (via `craft-media` / `craft-codecs`). diff --git a/crates/dnx/src/bits.rs b/crates/dnx/src/bits.rs new file mode 100644 index 0000000000..9c3ae8fdf9 --- /dev/null +++ b/crates/dnx/src/bits.rs @@ -0,0 +1,127 @@ +//! Fast MSB-first bit reader (64-bit cache) and writer for the macroblock data. + +use crate::tables::{LINK, Lut}; + +/// Bit reader over one macroblock scan line. Reads past the end yield zero bits; callers check +/// [`Reader::overrun`]. +pub(crate) struct Reader<'a> { + data: &'a [u8], + next: usize, + cache: u64, + avail: u32, +} + +impl<'a> Reader<'a> { + #[inline] + pub fn new(data: &'a [u8]) -> Self { + let mut r = Self { data, next: 0, cache: 0, avail: 0 }; + r.refill(); + r + } + + /// Ensure at least 56 valid bits are cached. + #[inline(always)] + fn refill(&mut self) { + if self.avail >= 56 { + return; + } + if let Some(s) = self.data.get(self.next..self.next + 8) { + let w = u64::from_be_bytes([s[0], s[1], s[2], s[3], s[4], s[5], s[6], s[7]]); + self.cache |= w >> self.avail; + self.next += ((63 - self.avail) >> 3) as usize; + self.avail |= 56; + } else { + while self.avail <= 56 { + let b = self.data.get(self.next).copied().unwrap_or(0) as u64; + self.cache |= b << (56 - self.avail); + self.avail += 8; + self.next += 1; + } + } + } + + #[inline(always)] + fn consume(&mut self, n: u32) { + self.cache <<= n; + self.avail -= n; + } + + #[inline] + pub fn consumed(&self) -> usize { + self.next * 8 - self.avail as usize + } + + #[inline] + pub fn overrun(&self) -> bool { + self.consumed() > self.data.len() * 8 + } + + /// Read `n` ≤ 32 bits. + #[inline(always)] + pub fn read(&mut self, n: u32) -> u32 { + if n == 0 { + return 0; + } + self.refill(); + let v = (self.cache >> (64 - n)) as u32; + self.consume(n); + v + } + + /// Decode one VLC symbol (value bits of the LUT entry). + #[inline(always)] + pub fn vlc(&mut self, t: &Lut) -> u32 { + self.refill(); + let w = self.cache; + let mut e = t.primary[(w >> (64 - t.bits)) as usize]; + if e & LINK != 0 { + let sb = (e >> 24) & 0x7f; + let off = (e & 0xff_ffff) as usize; + e = t.secondary[off + ((w << t.bits) >> (64 - sb)) as usize]; + } + // entries are always filled for complete prefix codes + self.consume(e >> 16); + e & 0xffff + } +} + +/// MSB-first bit writer. +#[derive(Default)] +pub(crate) struct Writer { + pub buf: Vec, + acc: u64, + n: u32, +} + +impl Writer { + pub fn new() -> Self { + Self::default() + } + + #[inline(always)] + pub fn put(&mut self, val: u32, bits: u32) { + if bits == 0 { + return; + } + let v = (val as u64) & ((1u64 << bits) - 1); + self.acc = (self.acc << bits) | v; + self.n += bits; + while self.n >= 8 { + self.n -= 8; + self.buf.push((self.acc >> self.n) as u8); + } + self.acc &= (1u64 << self.n) - 1; + } + + /// Pad with zero bits to a multiple of `align` bytes and return the bytes. + pub fn finish_aligned(mut self, align: usize) -> Vec { + if self.n > 0 { + let pad = 8 - self.n; + self.put(0, pad); + } + while !self.buf.len().is_multiple_of(align) { + self.buf.push(0); + } + self.buf + } +} diff --git a/crates/dnx/src/dct.rs b/crates/dnx/src/dct.rs new file mode 100644 index 0000000000..53248290e0 --- /dev/null +++ b/crates/dnx/src/dct.rs @@ -0,0 +1,160 @@ +//! 8×8 orthonormal DCT-II / DCT-III (IDCT) in single precision. +//! +//! The reconstruction IDCT is defined as the exact real-valued 2-D inverse DCT +//! `f(x,y) = Σ_u Σ_v C(u)C(v)/4 · F(v,u) · cos((2x+1)uπ/16) · cos((2y+1)vπ/16)` +//! evaluated separably (rows then columns) in `f32`. For VC-3 coefficient ranges the `f32` +//! evaluation error is below 2⁻⁸ in the 12-bit sample domain, so the rounded output equals the +//! rounded exact IDCT except when the exact value lies within that distance of a rounding tie. + +use crate::tables::BASIS; + +/// In-place inverse DCT. `block` holds dequantised coefficients in raster order +/// (`block[v*8 + u]`, `v` = vertical frequency) and receives samples (`block[y*8 + x]`). +/// +/// Each 1-D pass uses the even/odd symmetry of the basis (`B[u][7-x] = ±B[u][x]` for even/odd +/// `u`) and skips all-zero rows; DC-only blocks are filled directly. +#[inline] +pub(crate) fn idct8x8(block: &mut [f32; 64]) { + let mut t = [[0f32; 8]; 8]; + let mut nz = [false; 8]; + let mut dc_only = true; + // Horizontal pass: t[r][x] = Σ_u F[r][u] · B[u][x] + for (r, c) in block.as_chunks::<8>().0.iter().enumerate() { + if c[1..].iter().all(|&v| v == 0.0) { + if c[0] != 0.0 { + t[r] = [c[0] * BASIS[0][0]; 8]; + nz[r] = true; + dc_only &= r == 0; + } + continue; + } + dc_only = false; + nz[r] = true; + let mut out = [0f32; 8]; + for x in 0..4 { + let e = c[0] * BASIS[0][x] + c[2] * BASIS[2][x] + c[4] * BASIS[4][x] + c[6] * BASIS[6][x]; + let o = c[1] * BASIS[1][x] + c[3] * BASIS[3][x] + c[5] * BASIS[5][x] + c[7] * BASIS[7][x]; + out[x] = e + o; + out[7 - x] = e - o; + } + t[r] = out; + } + if dc_only { + *block = [t[0][0] * BASIS[0][0]; 64]; + return; + } + // Vertical pass: f[y][x] = Σ_v t[v][x] · B[v][y] + for y in 0..4 { + let mut e = [0f32; 8]; + let mut o = [0f32; 8]; + for v in [0, 2, 4, 6] { + if nz[v] { + let b = BASIS[v][y]; + for x in 0..8 { + e[x] += t[v][x] * b; + } + } + } + for v in [1, 3, 5, 7] { + if nz[v] { + let b = BASIS[v][y]; + for x in 0..8 { + o[x] += t[v][x] * b; + } + } + } + for x in 0..8 { + block[y * 8 + x] = e[x] + o[x]; + block[(7 - y) * 8 + x] = e[x] - o[x]; + } + } +} + +/// In-place forward DCT (samples in, coefficients out; same layout as [`idct8x8`]). +pub(crate) fn fdct8x8(block: &mut [f32; 64]) { + let mut t = [[0f32; 8]; 8]; + // Horizontal: t[y][u] = Σ_x f[y][x] · B[u][x] + for y in 0..8 { + for u in 0..8 { + let mut s = 0f32; + for x in 0..8 { + s += block[y * 8 + x] * BASIS[u][x]; + } + t[y][u] = s; + } + } + // Vertical: F[v][u] = Σ_y t[y][u] · B[v][y] + for v in 0..8 { + let mut acc = [0f32; 8]; + for y in 0..8 { + let b = BASIS[v][y]; + for u in 0..8 { + acc[u] += t[y][u] * b; + } + } + block[v * 8..v * 8 + 8].copy_from_slice(&acc); + } +} + +#[cfg(test)] +mod tests { + use super::*; + + fn reference_idct(c: &[f32; 64]) -> [f64; 64] { + let mut out = [0f64; 64]; + for y in 0..8 { + for x in 0..8 { + let mut s = 0f64; + for v in 0..8 { + for u in 0..8 { + let cu = if u == 0 { std::f64::consts::FRAC_1_SQRT_2 } else { 1.0 }; + let cv = if v == 0 { std::f64::consts::FRAC_1_SQRT_2 } else { 1.0 }; + s += cu * cv / 4.0 + * c[v * 8 + u] as f64 + * (((2 * x + 1) * u) as f64 * std::f64::consts::PI / 16.0).cos() + * (((2 * y + 1) * v) as f64 * std::f64::consts::PI / 16.0).cos(); + } + } + out[y * 8 + x] = s; + } + } + out + } + + #[test] + fn idct_matches_double_reference() { + let mut seed = 12345u32; + let mut rnd = || { + seed = seed.wrapping_mul(1664525).wrapping_add(1013904223); + seed >> 8 + }; + for _ in 0..200 { + let mut c = [0f32; 64]; + for v in c.iter_mut() { + if rnd() % 3 == 0 { + *v = ((rnd() % 8192) as i32 - 4096) as f32; + } + } + let r = reference_idct(&c); + let mut b = c; + idct8x8(&mut b); + for i in 0..64 { + assert!((b[i] as f64 - r[i]).abs() < 4e-3, "{} vs {}", b[i], r[i]); + } + } + } + + #[test] + fn fdct_inverts_idct() { + let mut b = [0f32; 64]; + for (i, v) in b.iter_mut().enumerate() { + *v = ((i * 37) % 200) as f32 - 100.0; + } + let orig = b; + fdct8x8(&mut b); + idct8x8(&mut b); + for i in 0..64 { + assert!((b[i] - orig[i]).abs() < 1e-3); + } + } +} diff --git a/crates/dnx/src/decode.rs b/crates/dnx/src/decode.rs new file mode 100644 index 0000000000..d47e1c5a66 --- /dev/null +++ b/crates/dnx/src/decode.rs @@ -0,0 +1,517 @@ +//! VC-3 decoding (SMPTE ST 2019-1 §8). + +use crate::bits::Reader; +use crate::dct::idct8x8; +use crate::header::{self, FieldCode, FrameHeader, SCAN_INDEX_OFFSET}; +use crate::tables::{AMP_EOB, AMP_FINDEX, AMP_FRUN, CidInfo, VlcSet, ZIGZAG, cid_info, vlc}; +use crate::{ChromaFormat, Error, Frame, Interlace, Result}; + +/// Decoder options. +#[derive(Debug, Clone, Copy)] +pub struct DecodeOptions { + /// Decode macroblock scan lines in parallel (needs the `threads` feature). + pub threads: bool, + /// Use the standard's integer inverse quantisation (equation 8.1) instead of the + /// ffmpeg-compatible reconstruction (default false; see the README). + pub spec_dequant: bool, +} + +impl Default for DecodeOptions { + fn default() -> Self { + DecodeOptions { threads: true, spec_dequant: false } + } +} + +/// Parse the header of the first coding unit of a frame. +pub fn probe(data: &[u8]) -> Result { + header::parse(data) +} + +/// Decode one compressed frame (one or two coding units). +pub fn decode_frame(data: &[u8]) -> Result { + decode_frame_with(data, &DecodeOptions::default()) +} + +/// Decode one compressed frame with options. +pub fn decode_frame_with(data: &[u8], opts: &DecodeOptions) -> Result { + let h1 = header::parse(data)?; + let field_coded = h1.interlaced && !h1.frame_encoding; + if !field_coded { + let planes = decode_unit(&h1, data, opts)?; + let mut f = Frame::new_like(&h1, h1.width, h1.lines); + f.interlace = if h1.interlaced { Interlace::TopFieldFirst } else { Interlace::Progressive }; + planes.copy_into(&mut f, 0, 1); + return Ok(f); + } + // field encoding: two coding units, field 1 (top) then field 2 + let off = second_unit_offset(&h1, data)?; + let h2 = header::parse(&data[off..])?; + if h2.cid != h1.cid || h2.width != h1.width || h2.lines != h1.lines || h2.chroma != h1.chroma || h2.alpha != h1.alpha { + return Err(Error::Invalid("field coding units differ")); + } + let (p1, p2) = if opts.threads { + join(|| decode_unit(&h1, &data[..off], opts), || decode_unit(&h2, &data[off..], opts)) + } else { + (decode_unit(&h1, &data[..off], opts), decode_unit(&h2, &data[off..], opts)) + }; + let (top, bottom) = if h1.field == FieldCode::Field2 { (p2?, p1?) } else { (p1?, p2?) }; + // Both field payloads must be valid before committing the full-frame + // allocation; a damaged header alone must not allocate an output picture. + let mut f = Frame::new_like(&h1, h1.width, h1.lines * 2); + f.interlace = Interlace::TopFieldFirst; + top.copy_into(&mut f, 0, 2); + bottom.copy_into(&mut f, 1, 2); + Ok(f) +} + +#[cfg(feature = "threads")] +fn join(a: impl FnOnce() -> A + Send, b: impl FnOnce() -> B + Send) -> (A, B) { + rayon::join(a, b) +} + +#[cfg(not(feature = "threads"))] +fn join(a: impl FnOnce() -> A, b: impl FnOnce() -> B) -> (A, B) { + (a(), b()) +} + +/// Byte offset of the second coding unit of a field-coded frame. +fn second_unit_offset(h: &FrameHeader, data: &[u8]) -> Result { + if let Some(sz) = h.coding_unit_size() { + let sz = sz as usize; + if data.len() >= sz + 0x280 && data[sz..sz + 4] == data[0..4] && data[sz + 4] == data[4] { + return Ok(sz); + } + } + // VBR (or unexpected size): find the next header prefix after the first unit's scan lines + let prefix = &data[0..5]; + let start = h.header_size as usize; + data[start..] + .windows(5) + .position(|w| w == prefix) + .map(|p| p + start) + .filter(|&p| header::parse(&data[p..]).is_ok()) + .ok_or(Error::Invalid("second field coding unit not found")) +} + +/// Decoded planes of one coding unit at the coded (macroblock-aligned) size. +struct CodedPlanes { + /// Y / Cb / Cr / A (or R / G / B / A). + planes: [Vec; 4], + strides: [usize; 4], + /// An RGB-signalled stream whose macroblocks are all coded as Y'CbCr. + ycbcr: bool, +} + +impl CodedPlanes { + /// Crop into `f`, writing coded line `i` to frame line `first + i·step`. + fn copy_into(&self, f: &mut Frame, first: usize, step: usize) { + if self.ycbcr { + f.rgb = false; + } + let cw = f.chroma_width() as usize; + let w = f.width as usize; + let ch = f.chroma_height() as usize; + let h = f.height as usize; + let cfirst = first; + let mut targets: Vec<(&mut Vec, usize, usize, usize, usize)> = + vec![(&mut f.y, w, h, first, 0), (&mut f.cb, cw, ch, cfirst, 1), (&mut f.cr, cw, ch, cfirst, 2)]; + if let Some(a) = f.alpha.as_mut() { + targets.push((a, w, h, first, 3)); + } + for (dst, pw, ph, first, pi) in targets { + let src = &self.planes[pi]; + let ss = self.strides[pi]; + let mut line = 0; + let mut y = first; + while y < ph { + let s = &src[line * ss..line * ss + pw]; + dst[y * pw..y * pw + pw].copy_from_slice(s); + line += 1; + y += step; + } + } + } +} + +/// Block layout entry: plane, x offset and y offset (0 or 8) inside the macroblock. +type BlockPos = (u8, u8, u8); + +const LAYOUT_422: [BlockPos; 8] = [(0, 0, 0), (0, 8, 0), (1, 0, 0), (2, 0, 0), (0, 0, 8), (0, 8, 8), (1, 0, 8), (2, 0, 8)]; +const LAYOUT_420: [BlockPos; 6] = [(0, 0, 0), (0, 8, 0), (1, 0, 0), (2, 0, 0), (0, 0, 8), (0, 8, 8)]; +const LAYOUT_444: [BlockPos; 12] = + [(0, 0, 0), (0, 8, 0), (1, 0, 0), (1, 8, 0), (2, 0, 0), (2, 8, 0), (0, 0, 8), (0, 8, 8), (1, 0, 8), (1, 8, 8), (2, 0, 8), (2, 8, 8)]; +const LAYOUT_ALPHA: [BlockPos; 4] = [(3, 0, 0), (3, 8, 0), (3, 0, 8), (3, 8, 8)]; + +/// Per-coding-unit decoding context. +struct Ctx<'a> { + h: &'a FrameHeader, + info: &'static CidInfo, + vlc: &'static VlcSet, + nw: usize, + layout: &'static [BlockPos], + /// Plane macroblock sizes (width, height) for Y/Cb/Cr/A. + mb_size: [(usize, usize); 4], + strides: [usize; 4], + p_bits: u32, + p_shift: u32, + p_half: u32, + spec_dequant: bool, + /// `1 / (4p)` + inv_4p: f32, + /// IDCT output scale (4 at 12 bits, see README) and level offset. + scale: f32, + mid: f32, + max: f32, +} + +fn decode_unit(h: &FrameHeader, data: &[u8], opts: &DecodeOptions) -> Result { + let info = cid_info(h.cid).ok_or(Error::UnknownCid(h.cid))?; + let nw = h.mb_width() as usize; + let ns = h.mb_rows as usize; + let (layout, cmb): (&'static [BlockPos], (usize, usize)) = match h.chroma { + ChromaFormat::Yuv422 => (&LAYOUT_422, (8, 16)), + ChromaFormat::Yuv420 => (&LAYOUT_420, (8, 8)), + ChromaFormat::Yuv444 => (&LAYOUT_444, (16, 16)), + }; + let mb_size = [(16, 16), cmb, cmb, (16, 16)]; + let strides = [nw * 16, nw * cmb.0, nw * cmb.0, nw * 16]; + let ctx = Ctx { + h, + info, + vlc: vlc(info.vlc), + nw, + layout, + mb_size, + strides, + p_bits: if h.bit_depth == 8 { 4 } else { 6 }, + p_shift: info.p.trailing_zeros(), + p_half: info.p / 2, + spec_dequant: opts.spec_dequant, + inv_4p: 1.0 / (4 * info.p) as f32, + scale: if h.bit_depth == 12 { 4.0 } else { 1.0 }, + mid: (1u32 << (h.bit_depth - 1)) as f32, + max: ((1u32 << h.bit_depth) - 1) as f32, + }; + // scan line byte ranges + let hs = h.header_size as usize; + let mut starts = Vec::with_capacity(ns); + for s in 0..ns { + let o = SCAN_INDEX_OFFSET + 4 * s; + let idx = u32::from_be_bytes([data[o], data[o + 1], data[o + 2], data[o + 3]]) as usize; + let st = hs.checked_add(idx).filter(|&v| v < data.len()).ok_or(Error::Truncated)?; + starts.push(st); + } + let ranges: Vec<(usize, usize)> = (0..ns) + .map(|s| { + let st = starts[s]; + let end = starts.get(s + 1).copied().filter(|&e| e > st).unwrap_or(data.len()); + (st, end) + }) + .collect(); + + let alpha = h.alpha; + let mut planes: [Vec; 4] = [ + vec![0; strides[0] * 16 * ns], + vec![0; strides[1] * cmb.1 * ns], + vec![0; strides[2] * cmb.1 * ns], + if alpha { vec![0; strides[3] * 16 * ns] } else { Vec::new() }, + ]; + let [py, pcb, pcr, pa] = &mut planes; + let band = [strides[0] * 16, strides[1] * cmb.1, strides[2] * cmb.1, strides[3] * 16]; + let mut alpha_bands: Vec<&mut [u16]> = if alpha { pa.chunks_mut(band[3]).collect() } else { (0..ns).map(|_| &mut [][..]).collect() }; + let rows: Vec<(usize, &mut [u16], &mut [u16], &mut [u16], &mut [u16])> = py + .chunks_mut(band[0]) + .zip(pcb.chunks_mut(band[1])) + .zip(pcr.chunks_mut(band[2])) + .zip(alpha_bands.drain(..)) + .enumerate() + .map(|(i, (((y, cb), cr), a))| (i, y, cb, cr, a)) + .collect(); + let run = |(i, y, cb, cr, a): (usize, &mut [u16], &mut [u16], &mut [u16], &mut [u16])| -> Result> { + let (st, end) = ranges[i]; + decode_row(&ctx, &data[st..end], [y, cb, cr, a]).map_err(|e| match e { + Error::Row(_) => Error::Row(i), + e => e, + }) + }; + #[cfg(feature = "threads")] + let acf: Vec> = if opts.threads { + use rayon::prelude::*; + rows.into_par_iter().map(run).collect::>()? + } else { + rows.into_iter().map(run).collect::>()? + }; + #[cfg(not(feature = "threads"))] + let acf: Vec> = { + let _ = opts; + rows.into_iter().map(run).collect::>()? + }; + // RGB streams (CLF = 1) choose RGB or Y'CbCr per macroblock (ACF). A stream whose + // macroblocks are all Y'CbCr is returned as Y'CbCr 4:4:4; mixed streams are converted to RGB + // macroblock by macroblock (ConvertMacroblockColor, 8.1.1). + let n_acf: usize = acf.iter().map(|r| r.iter().filter(|&&a| a).count()).sum(); + let ycbcr = h.rgb_planes() && n_acf == nw * ns; + if h.rgb_planes() && n_acf > 0 && !ycbcr && h.color_volume != crate::ColorVolume::OutOfBand { + let [py, pcb, pcr, _] = &mut planes; + for (s, row) in acf.iter().enumerate() { + for (mb, &a) in row.iter().enumerate() { + if a { + let o = s * band[0]; + convert_mb_to_rgb(&ctx, [&mut py[o..o + band[0]], &mut pcb[o..o + band[0]], &mut pcr[o..o + band[0]]], mb); + } + } + } + } + Ok(CodedPlanes { planes, strides, ycbcr }) +} + +/// Decode one macroblock scan line into the 16-line bands of each plane. +fn decode_row(ctx: &Ctx, data: &[u8], bands: [&mut [u16]; 4]) -> Result> { + let h = ctx.h; + let mut r = Reader::new(data); + // DC predictors: Y/Ch1, Cb, Cr, R, G, B, A + let mut pred = [0i32; 7]; + let rgb = h.rgb_planes(); + let mff_cid = ctx.info.cid == 1260; + let mut block = [0f32; 64]; + let mut acfs = Vec::new(); + for mb in 0..ctx.nw { + let (qsf, flag, mff) = if mff_cid { + let mff = r.read(1) == 1; + (r.read(10), r.read(1) == 1, mff) + } else { + (r.read(11), r.read(1) == 1, false) + }; + let acf = rgb && flag; + let wq = scaled_weights(ctx.info.weights, qsf); + for &(plane, xo, yo) in ctx.layout { + let comp = plane as usize; + let slot = if rgb && !acf { 3 + comp } else { comp }; + let w = &wq[(comp != 0) as usize]; + decode_block(ctx, &mut r, &mut pred[slot], w, &mut block)?; + store( + ctx, + bands[comp], + BlockPosition { plane: comp, macroblock: mb, x_offset: xo as usize, y_offset: yo as usize, field: mff }, + &mut block, + ); + } + if rgb { + acfs.push(acf); + } + if r.overrun() { + return Err(Error::Row(0)); + } + } + if h.alpha { + if h.lossless_alpha { + for mb in 0..ctx.nw { + decode_rle_alpha(ctx, &mut r, bands[3], mb)?; + } + } else { + for mb in 0..ctx.nw { + let qsf = if mff_cid { + r.read(1); + let q = r.read(10); + r.read(1); + q + } else { + let q = r.read(11); + r.read(1); + q + }; + let wq = scaled_weights(ctx.info.weights, qsf); + for &(plane, xo, yo) in &LAYOUT_ALPHA { + decode_block(ctx, &mut r, &mut pred[6], &wq[0], &mut block)?; + store( + ctx, + bands[3], + BlockPosition { plane: plane as usize, macroblock: mb, x_offset: xo as usize, y_offset: yo as usize, field: false }, + &mut block, + ); + } + } + } + if r.overrun() { + return Err(Error::Row(0)); + } + } + Ok(acfs) +} + +/// `W(u,v)·qsf` for every AC raster position (index 0 unused), luma and chroma. +#[inline] +fn scaled_weights(w: &[[u8; 63]; 2], qsf: u32) -> [[(u32, u32); 64]; 2] { + let mut out = [[(0u32, 0u32); 64]; 2]; + for c in 0..2 { + for i in 0..63 { + let wi = w[c][i] as u32; + out[c][i + 1] = (wi * qsf, wi); + } + } + out +} + +/// Decode one DCT block into dequantised coefficients (raster order). +#[inline] +fn decode_block(ctx: &Ctx, r: &mut Reader, pred: &mut i32, wq: &[(u32, u32); 64], block: &mut [f32; 64]) -> Result<()> { + let v = ctx.vlc; + *block = [0.0; 64]; + // DC (8.2.4) + let eta = r.vlc(&v.dc); + let eps = if eta == 0 { + 0 + } else { + let rho = r.read(eta) as i32; + if rho >= 1 << (eta - 1) { rho } else { rho + 1 - (1 << eta) } + }; + let dc = pred.wrapping_add(eps); + *pred = dc; + block[0] = dc as f32; + // AC (8.2.5) + let mut pos = 0usize; + loop { + let s = r.vlc(&v.amp); + if s & AMP_EOB != 0 { + break; + } + let neg = r.read(1) == 1; + let mut a = s & 0xff; + if s & AMP_FINDEX != 0 { + a += r.read(ctx.p_bits) * 64; + } + pos += 1; + if s & AMP_FRUN != 0 { + pos += r.vlc(&v.run) as usize; + } + if pos > 63 { + return Err(Error::Row(0)); + } + let z = ZIGZAG[pos] as usize; + let (wqs, w) = wq[z]; + // inverse quantisation (8.2.7) + // Reconstruction at (|X̂| + 3/4)·W·qsf/p, as ffmpeg's decoder does (see README); the + // standard's equation 8.1 rounds to an integer near (|X̂| + 1/2)·W·qsf/p. + let mag = if ctx.spec_dequant { + let add = if w != ctx.info.p { ctx.p_half } else { 0 }; + ((a as u64 * wqs as u64 + (wqs >> 1) as u64 + add as u64) >> ctx.p_shift) as f32 + } else { + ((4 * a as u64 + 3) * wqs as u64) as f32 * ctx.inv_4p + }; + block[z] = if neg { -mag } else { mag }; + } + Ok(()) +} + +/// IDCT a block and store it (with level adjustment and clipping) into a plane band. +struct BlockPosition { + plane: usize, + macroblock: usize, + x_offset: usize, + y_offset: usize, + field: bool, +} + +#[inline] +fn store(ctx: &Ctx, band: &mut [u16], position: BlockPosition, block: &mut [f32; 64]) { + idct8x8(block); + let BlockPosition { plane, macroblock, x_offset, y_offset, field } = position; + let stride = ctx.strides[plane]; + let x0 = macroblock * ctx.mb_size[plane].0 + x_offset; + let (first, step) = if field { (y_offset / 8, 2) } else { (y_offset, 1) }; + let (scale, mid, max) = (ctx.scale, ctx.mid + 0.5, ctx.max); + for j in 0..8 { + let row = &mut band[(first + j * step) * stride + x0..][..8]; + let src = &block[j * 8..j * 8 + 8]; + for i in 0..8 { + row[i] = (src[i] * scale + mid).floor().clamp(0.0, max) as u16; + } + } +} + +/// `ConvertMacroblockColor` (8.1.1): BT.709 Y′CbCr → R′G′B′ on the quantised (video-range) +/// signals, in place over the macroblock's three planes. +fn convert_mb_to_rgb(ctx: &Ctx, bands: [&mut [u16]; 3], mb: usize) { + let stride = ctx.strides[0]; + let s = (1u32 << (ctx.h.bit_depth - 8)) as f32; + let (kr, kb) = match ctx.h.color_volume { + crate::ColorVolume::Bt709 => (0.2126f32, 0.0722f32), + _ => (0.2627, 0.0593), + }; + let kg = 1.0 - kr - kb; + let c = 219.0 / 224.0; + for y in 0..16 { + for x in 0..16 { + let i = y * stride + mb * 16 + x; + let yy = bands[0][i] as f32; + let cb = bands[1][i] as f32 - 128.0 * s; + let cr = bands[2][i] as f32 - 128.0 * s; + let r = yy + 2.0 * (1.0 - kr) * c * cr; + let b = yy + 2.0 * (1.0 - kb) * c * cb; + let g = (yy - kr * r - kb * b) / kg; + // RGB planes are stored in coded channel order G, B, R + bands[0][i] = (g + 0.5).floor().clamp(0.0, ctx.max) as u16; + bands[1][i] = (b + 0.5).floor().clamp(0.0, ctx.max) as u16; + bands[2][i] = (r + 0.5).floor().clamp(0.0, ctx.max) as u16; + } + } +} + +/// Lossless (differential RLE) alpha macroblock (§7.3.1.2, §8.3). +fn decode_rle_alpha(ctx: &Ctx, r: &mut Reader, band: &mut [u16], mb: usize) -> Result<()> { + let d = ctx.h.bit_depth as u32; + let mask = (1u32 << d) - 1; + let stride = ctx.strides[3]; + let pos = |count: usize| { + let y = count / 16; + let x = if y.is_multiple_of(2) { count % 16 } else { 15 - count % 16 }; + y * stride + mb * 16 + x + }; + // RL = 1: RLE coded; RL = 0: 256 raw samples (§7.3.1.2 prose; see README) + let rl = r.read(4) & 1; + if rl == 0 { + for count in 0..256 { + band[pos(count)] = r.read(d) as u16; + } + return Ok(()); + } + let rice = d / 2 - 2; + let (mut d1, mut d2) = (mask >> 1, mask >> 1); + let mut count = 0usize; + while count < 256 { + let p = r.read(1); + // Elias gamma + let mut n = 0; + while r.read(1) == 0 { + n += 1; + if n > 8 || r.overrun() { + return Err(Error::Row(0)); + } + } + let nrl = (1usize << n) + r.read(n) as usize; + let dcw = if r.read(1) == 1 { + let mut q = 0u32; + while r.read(1) == 1 { + q += 1; + if q > 1 << d || r.overrun() { + return Err(Error::Row(0)); + } + } + (q << rice) + r.read(rice) + } else { + r.read(d) + }; + let diff = if dcw & 1 == 1 { mask.wrapping_sub(dcw >> 1) } else { dcw >> 1 }; + if count + nrl > 256 { + return Err(Error::Row(0)); + } + for _ in 0..nrl { + let v = if p == 1 { (2 * d1).wrapping_sub(d2).wrapping_add(diff) & mask } else { d1.wrapping_add(diff) & mask }; + band[pos(count)] = v as u16; + d2 = d1; + d1 = v; + count += 1; + } + } + Ok(()) +} diff --git a/crates/dnx/src/encode.rs b/crates/dnx/src/encode.rs new file mode 100644 index 0000000000..914191f1a3 --- /dev/null +++ b/crates/dnx/src/encode.rs @@ -0,0 +1,438 @@ +//! DNxHR encoder (RI profile, CBR, progressive). +//! +//! Every macroblock is transformed once (orthonormal 8×8 FDCT). Quantisation follows equation +//! 8.2 exactly (`X̂ = ⌊p·|X| / (qsf·W)⌋`, which the decoder reconstructs at the interval +//! midpoint). Rate control: a binary search finds the smallest uniform quantisation scale factor +//! whose frame fits the CBR payload, then macroblocks get one finer step in scan order while the +//! budget allows. + +use crate::bits::Writer; +use crate::dct::fdct8x8; +use crate::header::{self, EOF_SIGNATURE, FieldCode, FrameHeader, SCAN_INDEX_OFFSET}; +use crate::tables::{CidInfo, VlcSet, ZIGZAG, cid_info, vlc}; +use crate::{ChromaFormat, ColorVolume, Error, Frame, Profile, Result}; + +/// Encoder configuration. +#[derive(Debug, Clone)] +pub struct EncoderConfig { + pub profile: Profile, + pub width: u32, + pub height: u32, + /// Coded sample depth: 8 for LB/SQ/HQ; 10 or 12 for HQX and 444. + pub bit_depth: u8, + pub color_volume: ColorVolume, + /// Pixel aspect ratio to signal (0/0 = not signalled). + pub par: (u16, u16), +} + +impl EncoderConfig { + pub fn new(profile: Profile, width: u32, height: u32) -> EncoderConfig { + let bit_depth = match profile { + Profile::Hqx | Profile::R444 => 10, + _ => 8, + }; + EncoderConfig { profile, width, height, bit_depth, color_volume: ColorVolume::Bt709, par: (0, 0) } + } + + pub fn chroma(&self) -> ChromaFormat { + if self.profile == Profile::R444 { ChromaFormat::Yuv444 } else { ChromaFormat::Yuv422 } + } +} + +/// DNxHR encoder. +pub struct Encoder { + cfg: EncoderConfig, + info: &'static CidInfo, + frame_size: u32, + header_size: u32, + last_q: u32, +} + +/// Transformed macroblock data: per block, the DC value and the AC magnitudes pre-divided by +/// the weights (`p·|X|/W`, sign in the float's sign) in bitstream (zig-zag) order. +struct Prepared { + nw: usize, + ns: usize, + nblk: usize, + /// `[mb * nblk + k][r]`; `[..][0]` is the DC value. + t: Vec<[f32; 64]>, + /// DC bits of each macroblock (independent of the quantiser). + dc_bits: Vec, +} + +impl Encoder { + /// An encoder with the default configuration; fails on rasters the profile can't code. + pub fn new(profile: Profile, width: u32, height: u32) -> Result { + Encoder::with_config(EncoderConfig::new(profile, width, height)) + } + + pub fn with_config(cfg: EncoderConfig) -> Result { + if cfg.width == 0 || cfg.height == 0 || cfg.width > 16384 || cfg.height > 16384 { + return Err(Error::Input(format!("unsupported raster {}x{}", cfg.width, cfg.height))); + } + if cfg.chroma() == ChromaFormat::Yuv422 && !cfg.width.is_multiple_of(2) { + return Err(Error::Input("4:2:2 needs an even width".into())); + } + let ok_depth = match cfg.profile { + Profile::Hqx | Profile::R444 => matches!(cfg.bit_depth, 10 | 12), + _ => cfg.bit_depth == 8, + }; + if !ok_depth { + return Err(Error::Input(format!("{} does not support {}-bit", cfg.profile.name(), cfg.bit_depth))); + } + let info = cid_info(cfg.profile.cid()).ok_or_else(|| Error::Input(format!("no compression table for {}", cfg.profile.name())))?; + let frame_size = cfg.profile.frame_size(cfg.width, cfg.height); + let header_size = header::header_size_for(cfg.height); + Ok(Encoder { cfg, info, frame_size, header_size, last_q: 8 }) + } + + pub fn config(&self) -> &EncoderConfig { + &self.cfg + } + + /// Size of every encoded frame in bytes (CBR). + pub fn frame_size(&self) -> u32 { + self.frame_size + } + + /// Encode one frame. Its geometry and chroma format must match the configuration; samples + /// at another depth are rescaled to the coded depth. + pub fn encode(&mut self, f: &Frame) -> Result> { + let cfg = &self.cfg; + if f.width != cfg.width || f.height != cfg.height { + return Err(Error::Input(format!("frame is {}x{}, encoder is {}x{}", f.width, f.height, cfg.width, cfg.height))); + } + if f.chroma != cfg.chroma() { + return Err(Error::Input(format!("{:?} input for {}", f.chroma, cfg.profile.name()))); + } + if !(8..=16).contains(&f.bit_depth) { + return Err(Error::Input(format!("unsupported input depth {}", f.bit_depth))); + } + let n = f.width as usize * f.height as usize; + let cn = f.chroma_width() as usize * f.chroma_height() as usize; + if f.y.len() != n || f.cb.len() != cn || f.cr.len() != cn { + return Err(Error::Input("plane sizes do not match the frame geometry".into())); + } + let prep = self.prepare(f); + let vlc = vlc(self.info.vlc); + let budget_bits = (self.frame_size as u64 - self.header_size as u64 - 4) * 8; + let q = self.rate_control(&prep, vlc, budget_bits); + self.write(&prep, vlc, &q, f.rgb) + } + + fn mb_layout(&self) -> &'static [(u8, u8, u8)] { + match self.cfg.chroma() { + ChromaFormat::Yuv444 => { + &[(0, 0, 0), (0, 8, 0), (1, 0, 0), (1, 8, 0), (2, 0, 0), (2, 8, 0), (0, 0, 8), (0, 8, 8), (1, 0, 8), (1, 8, 8), (2, 0, 8), (2, 8, 8)] + } + _ => &[(0, 0, 0), (0, 8, 0), (1, 0, 0), (2, 0, 0), (0, 0, 8), (0, 8, 8), (1, 0, 8), (2, 0, 8)], + } + } + + fn prepare(&self, f: &Frame) -> Prepared { + let cfg = &self.cfg; + let nw = cfg.width.div_ceil(16) as usize; + let ns = cfg.height.div_ceil(16) as usize; + let layout = self.mb_layout(); + let nblk = layout.len(); + let depth = cfg.bit_depth as i32; + let shift = depth - f.bit_depth as i32; + let mid = (1i32 << (depth - 1)) as f32; + let inv_scale = if depth == 12 { 0.25f32 } else { 1.0 }; + let p = self.info.p as f32; + let w = self.info.weights; + let cmbw = if cfg.chroma() == ChromaFormat::Yuv444 { 16 } else { 8 }; + let planes: [(&[u16], usize, usize, usize); 3] = [ + (&f.y, f.width as usize, f.height as usize, 16), + (&f.cb, f.chroma_width() as usize, f.chroma_height() as usize, cmbw), + (&f.cr, f.chroma_width() as usize, f.chroma_height() as usize, cmbw), + ]; + let sample = |pl: usize, x: usize, y: usize| -> f32 { + let (d, pw, ph, _) = planes[pl]; + let v = d[y.min(ph - 1) * pw + x.min(pw - 1)] as i32; + let v = if shift >= 0 { v << shift } else { (v + (1 << (-shift - 1))) >> -shift }; + v.min((1 << depth) - 1) as f32 - mid + }; + let do_row = |s: usize| -> Vec<[f32; 64]> { + let mut out = Vec::with_capacity(nw * nblk); + for mb in 0..nw { + for &(pl, xo, yo) in layout { + let pl = pl as usize; + let x0 = mb * planes[pl].3 + xo as usize; + let y0 = s * 16 + yo as usize; + let mut b = [0f32; 64]; + for j in 0..8 { + for i in 0..8 { + b[j * 8 + i] = sample(pl, x0 + i, y0 + j); + } + } + fdct8x8(&mut b); + let wt = &w[(pl != 0) as usize]; + let mut t = [0f32; 64]; + let dc_max = if depth == 8 { 1023.0 } else { 4095.0 }; + t[0] = (b[0] * inv_scale).round().clamp(-dc_max - 1.0, dc_max); + for r in 1..64 { + let z = ZIGZAG[r] as usize; + t[r] = b[z] * inv_scale * p / wt[z - 1] as f32; + } + out.push(t); + } + } + out + }; + #[cfg(feature = "threads")] + let rows: Vec> = { + use rayon::prelude::*; + (0..ns).into_par_iter().map(do_row).collect() + }; + #[cfg(not(feature = "threads"))] + let rows: Vec> = (0..ns).map(do_row).collect(); + let t: Vec<[f32; 64]> = rows.into_iter().flatten().collect(); + // DC bits with per-row, per-component prediction + let v = vlc(self.info.vlc); + let mut dc_bits = vec![0u32; nw * ns]; + for s in 0..ns { + let mut pred = [0i32; 3]; + for mb in 0..nw { + let mut bits = 0; + for (k, &(pl, _, _)) in layout.iter().enumerate() { + let dc = t[(s * nw + mb) * nblk + k][0] as i32; + let e = dc - pred[pl as usize]; + pred[pl as usize] = dc; + let eta = dc_eta(e); + bits += v.dc_code[eta as usize].1 as u32 + eta; + } + dc_bits[s * nw + mb] = bits; + } + } + Prepared { nw, ns, nblk, t, dc_bits } + } + + /// Per-macroblock qsf values whose coded frame fits `budget_bits`. + fn rate_control(&mut self, prep: &Prepared, vlc: &VlcSet, budget_bits: u64) -> Vec { + let p_bits = if self.cfg.bit_depth == 8 { 4 } else { 6 }; + let max_amp = if p_bits == 4 { 1024 } else { 4096 }; + let nmb = prep.nw * prep.ns; + let mb_bits = |mb: usize, q: u32| -> u32 { + let mut bits = 12 + prep.dc_bits[mb]; + for k in 0..prep.nblk { + bits += ac_bits(&prep.t[mb * prep.nblk + k], q as f32, vlc, p_bits, max_amp); + } + bits + }; + let frame_bits = |q: u32| -> u64 { + let row = |s: usize| -> u64 { row_bytes((0..prep.nw).map(|m| mb_bits(s * prep.nw + m, q) as u64).sum()) * 8 }; + #[cfg(feature = "threads")] + { + use rayon::prelude::*; + (0..prep.ns).into_par_iter().map(row).sum() + } + #[cfg(not(feature = "threads"))] + { + (0..prep.ns).map(row).sum() + } + }; + // smallest feasible uniform q (bits are non-increasing in q) + let (mut lo, mut hi) = (1u32, 2047u32); + if frame_bits(1) <= budget_bits { + hi = 1; + } else { + // bracket around the previous frame's choice first + let g = self.last_q.clamp(2, 2047); + if frame_bits(g) <= budget_bits { + hi = g; + let g2 = (g / 2).max(1); + if g2 > 1 && frame_bits(g2) > budget_bits { + lo = g2; + } + } else { + lo = g; + } + while hi - lo > 1 { + let m = (lo + hi) / 2; + if frame_bits(m) <= budget_bits { hi = m } else { lo = m } + } + } + let q = hi; + self.last_q = q; + let mut qs = vec![q; nmb]; + if q > 1 { + // refine: one finer step per macroblock while the budget allows + let mut row_bits: Vec = (0..prep.ns).map(|s| (0..prep.nw).map(|m| mb_bits(s * prep.nw + m, q) as u64).sum()).collect(); + let mut total: u64 = row_bits.iter().map(|&b| row_bytes(b) * 8).sum(); + let cur: Vec = (0..nmb).map(|mb| mb_bits(mb, q)).collect(); + let finer: Vec = (0..nmb).map(|mb| mb_bits(mb, q - 1)).collect(); + // spread the refinement evenly over the frame: visit macroblocks in a strided order + let stride = 7919usize; + let mut idx = 0usize; + for _ in 0..nmb { + idx = (idx + stride) % nmb; + let s = idx / prep.nw; + let extra = finer[idx] as u64 - cur[idx].min(finer[idx]) as u64; + let nb = row_bits[s] + extra; + let nt = total - row_bytes(row_bits[s]) * 8 + row_bytes(nb) * 8; + if nt <= budget_bits { + row_bits[s] = nb; + total = nt; + qs[idx] = q - 1; + } + } + } + qs + } + + fn write(&self, prep: &Prepared, vlc: &VlcSet, qs: &[u32], rgb: bool) -> Result> { + let cfg = &self.cfg; + let p_bits = if cfg.bit_depth == 8 { 4 } else { 6 }; + let max_amp = if p_bits == 4 { 1024 } else { 4096 }; + let layout = self.mb_layout(); + // 4:4:4 signalling as ffmpeg writes and reads it (see `FrameHeader::rgb_planes`): RGB is + // CLF = 0 with ACF = 0; Y'CbCr is CLF = 1 with ACF = 1 on every macroblock. + let ycbcr444 = cfg.chroma() == ChromaFormat::Yuv444 && !rgb; + let write_row = |s: usize| -> Vec { + let mut w = Writer::new(); + let mut pred = [0i32; 3]; + for mb in 0..prep.nw { + let m = s * prep.nw + mb; + let q = qs[m]; + w.put(q, 11); + w.put(ycbcr444 as u32, 1); + for (k, &(pl, _, _)) in layout.iter().enumerate() { + let t = &prep.t[m * prep.nblk + k]; + let dc = t[0] as i32; + let e = dc - pred[pl as usize]; + pred[pl as usize] = dc; + let eta = dc_eta(e); + let (c, l) = vlc.dc_code[eta as usize]; + w.put(c as u32, l as u32); + if eta > 0 { + let rho = if e > 0 { e } else { e + (1 << eta) - 1 }; + w.put(rho as u32, eta); + } + write_ac(&mut w, t, q as f32, vlc, p_bits, max_amp); + } + } + w.finish_aligned(4) + }; + #[cfg(feature = "threads")] + let rows: Vec> = { + use rayon::prelude::*; + (0..prep.ns).into_par_iter().map(write_row).collect() + }; + #[cfg(not(feature = "threads"))] + let rows: Vec> = (0..prep.ns).map(write_row).collect(); + + let h = FrameHeader { + header_size: self.header_size, + version: 3, + cid: self.info.cid, + vbr: false, + field: FieldCode::Frame, + macf: false, + crc: false, + alpha: false, + lossless_alpha: false, + premultiplied_alpha: false, + width: cfg.width, + lines: cfg.height, + par: cfg.par, + bit_depth: cfg.bit_depth, + interlaced: false, + frame_encoding: true, + chroma: cfg.chroma(), + rgb: ycbcr444, + color_volume: cfg.color_volume, + timecode: None, + mb_rows: prep.ns as u32, + }; + let mut out = header::write(&h); + out.reserve(self.frame_size as usize - out.len()); + let mut off = 0u32; + for (s, r) in rows.iter().enumerate() { + let o = SCAN_INDEX_OFFSET + 4 * s; + out[o..o + 4].copy_from_slice(&off.to_be_bytes()); + off += r.len() as u32; + } + for r in rows { + out.extend_from_slice(&r); + } + let end = self.frame_size as usize - 4; + if out.len() > end { + return Err(Error::Input("rate control overflow".into())); + } + out.resize(end, 0); + out.extend_from_slice(&EOF_SIGNATURE); + Ok(out) + } +} + +#[inline] +fn row_bytes(bits: u64) -> u64 { + bits.div_ceil(32) * 4 +} + +#[inline] +fn dc_eta(e: i32) -> u32 { + if e == 0 { 0 } else { 32 - e.unsigned_abs().leading_zeros() } +} + +/// Quantised amplitude (≥ 1) of a pre-divided magnitude `|t| ≥ q`: the nearest reconstruction +/// level for decoders that reconstruct at `(A + 3/4)·step` (ffmpeg and ours by default). +#[inline(always)] +fn quant(t: f32, q: f32, max_amp: u32) -> u32 { + ((t.abs() / q - 0.25) as u32).clamp(1, max_amp) +} + +/// Bits of a block's AC coefficients plus EOB at quantiser `q`. +#[inline] +fn ac_bits(t: &[f32; 64], q: f32, v: &VlcSet, p_bits: u32, max_amp: u32) -> u32 { + let mut bits = v.eob.1 as u32; + let mut run = 0usize; + for &x in &t[1..] { + if x.abs() < q { + run += 1; + continue; + } + let a = quant(x, q, max_amp); + let p = (a - 1) >> 6; + let base = ((a - 1) & 63) as usize; + let frun = (run > 0) as usize; + let findex = (p > 0) as usize; + bits += v.amp_code[frun][findex][base].1 as u32 + 1; + if findex == 1 { + bits += p_bits; + } + if frun == 1 { + bits += v.run_code[run].1 as u32; + } + run = 0; + } + bits +} + +fn write_ac(w: &mut Writer, t: &[f32; 64], q: f32, v: &VlcSet, p_bits: u32, max_amp: u32) { + let mut run = 0usize; + for &x in &t[1..] { + if x.abs() < q { + run += 1; + continue; + } + let a = quant(x, q, max_amp); + let p = (a - 1) >> 6; + let base = ((a - 1) & 63) as usize; + let frun = (run > 0) as usize; + let findex = (p > 0) as usize; + let (c, l) = v.amp_code[frun][findex][base]; + w.put(c as u32, l as u32); + w.put((x < 0.0) as u32, 1); + if findex == 1 { + w.put(p, p_bits); + } + if frun == 1 { + let (c, l) = v.run_code[run]; + w.put(c as u32, l as u32); + } + run = 0; + } + w.put(v.eob.0 as u32, v.eob.1 as u32); +} diff --git a/crates/dnx/src/ffmpeg_probe.rs b/crates/dnx/src/ffmpeg_probe.rs new file mode 100644 index 0000000000..b7fc70b546 --- /dev/null +++ b/crates/dnx/src/ffmpeg_probe.rs @@ -0,0 +1,284 @@ +//! Black-box probes of ffmpeg's VC-3 decoder (ignored tests, run by hand). +//! +//! Each probe writes a stream whose first DCT block holds one AC coefficient (all other blocks +//! empty), decodes it with ffmpeg as an external process, and measures the reconstructed +//! coefficient with a forward DCT of the output pixels. They established the two places where +//! ffmpeg's decoder differs from the text of SMPTE ST 2019-1 (see the README): the quantisation +//! weights it uses for CID 1271, and its reconstruction offset. +//! +//! `DNX_CID=1271 cargo test -p craft-dnx --lib ffmpeg_probe -- --ignored --nocapture` +use crate::bits::Writer; +use crate::header::{self, EOF_SIGNATURE, FieldCode, FrameHeader, SCAN_INDEX_OFFSET}; +use crate::tables::{ZIGZAG, cid_info, vlc}; +use crate::{ChromaFormat, ColorVolume}; + +struct ProbeStream { + cid: u32, + width: u32, + height: u32, + depth: u8, + quantizer: u32, + coefficient_position: usize, + amplitude: u32, + negative: bool, + block: usize, +} + +fn stream_cid(input: ProbeStream) -> Vec { + let ProbeStream { cid, width: w, height: h, depth, quantizer: q, coefficient_position: pos, amplitude: amp, negative: neg, block: blk } = input; + let info = cid_info(cid).unwrap(); + let v = vlc(info.vlc); + let p_bits = if depth == 8 { 4 } else { 6 }; + let nw = w.div_ceil(16) as usize; + let ns = h.div_ceil(16) as usize; + let mut rows = Vec::new(); + for s in 0..ns { + let mut wr = Writer::new(); + for mb in 0..nw { + wr.put(q, 11); + wr.put(0, 1); + for k in 0..8 { + let (c, l) = v.dc_code[0]; + wr.put(c as u32, l as u32); + if s == 0 && mb == 0 && k == blk { + let p = (amp - 1) >> 6; + let base = ((amp - 1) & 63) as usize; + let frun = (pos > 1) as usize; + let (c, l) = v.amp_code[frun][(p > 0) as usize][base]; + wr.put(c as u32, l as u32); + wr.put(neg as u32, 1); + if p > 0 { + wr.put(p, p_bits); + } + if frun == 1 { + let (c, l) = v.run_code[pos - 1]; + wr.put(c as u32, l as u32); + } + } + wr.put(v.eob.0 as u32, v.eob.1 as u32); + } + } + rows.push(wr.finish_aligned(4)); + } + let hdr = FrameHeader { + header_size: 0x280, + version: if cid >= 1270 { 3 } else { 1 }, + cid, + vbr: false, + field: FieldCode::Frame, + macf: false, + crc: false, + alpha: false, + lossless_alpha: false, + premultiplied_alpha: false, + width: w, + lines: h, + par: (0, 0), + bit_depth: depth, + interlaced: false, + frame_encoding: true, + chroma: ChromaFormat::Yuv422, + rgb: false, + color_volume: ColorVolume::Bt709, + timecode: None, + mb_rows: ns as u32, + }; + let mut out = header::write(&hdr); + let mut off = 0u32; + for (s, r) in rows.iter().enumerate() { + out[SCAN_INDEX_OFFSET + 4 * s..SCAN_INDEX_OFFSET + 4 * s + 4].copy_from_slice(&off.to_be_bytes()); + off += r.len() as u32; + } + for r in rows { + out.extend_from_slice(&r); + } + let size = match info.kind { + crate::tables::Kind::Hd { frame_size, .. } => frame_size as usize, + crate::tables::Kind::Ri { c0 } => crate::ri_frame_size(w, h, c0, false) as usize, + }; + out.resize(size - 4, 0); + out.extend_from_slice(&EOF_SIGNATURE); + out +} + +#[test] +#[ignore] +fn probe_weights() { + let ff = craft_testkit::ffmpeg().unwrap(); + let dir = craft_testkit::fixtures_dir("dnx"); + let cid: u32 = std::env::var("DNX_CID").unwrap().parse().unwrap(); + let (w, h, depth): (u32, u32, u8) = match cid { + 1250 => (1280, 720, 10), + 1251 | 1252 => (1280, 720, 8), + 1235 => (1920, 1080, 10), + 1271 => (256, 128, 10), + 1272..=1274 => (256, 128, 8), + c => { + let i = cid_info(c).unwrap(); + match i.kind { + crate::tables::Kind::Hd { width, height, depth, .. } => (width as u32, height as u32, depth), + _ => panic!(), + } + } + }; + let pix = if depth == 10 { "yuv422p10le" } else { "yuv422p" }; + let prof = cid; + let amp = 20u32; + let q = 4u32; + for blk in [0usize, 2] { + let mut grid = [[0f64; 8]; 8]; + for pos in 1..64 { + let s = stream_cid(ProbeStream { + cid, + width: w, + height: h, + depth, + quantizer: q, + coefficient_position: pos, + amplitude: amp, + negative: false, + block: blk, + }); + let path = dir.join("explore.dnxhd"); + std::fs::write(&path, &s).unwrap(); + let out = std::process::Command::new(&ff) + .args(["-hide_banner", "-loglevel", "error", "-f", "dnxhd", "-i"]) + .arg(&path) + .args(["-f", "rawvideo", "-pix_fmt", pix, "-"]) + .output() + .unwrap(); + let px: Vec = if depth == 8 { + out.stdout.iter().map(|&b| b as f64).collect() + } else { + out.stdout.as_chunks::<2>().0.iter().map(|c| u16::from_le_bytes([c[0], c[1]]) as f64).collect() + }; + let (off, stride) = if blk == 0 { (0, w as usize) } else { ((w * h) as usize, w as usize / 2) }; + let mid = (1 << (depth - 1)) as f64; + let z = ZIGZAG[pos] as usize; + let (u, vv) = (z % 8, z / 8); + let mut sum = 0f64; + for j in 0..8 { + for i in 0..8 { + let cu = if u == 0 { 0.5f64.sqrt() } else { 1.0 }; + let cv = if vv == 0 { 0.5f64.sqrt() } else { 1.0 }; + sum += cu * cv / 4.0 + * (px[off + j * stride + i] - mid) + * (((2 * i + 1) * u) as f64 * std::f64::consts::PI / 16.0).cos() + * (((2 * j + 1) * vv) as f64 * std::f64::consts::PI / 16.0).cos(); + } + } + grid[vv][u] = sum * cid_info(cid).unwrap().p as f64 / ((amp as f64 + 0.5) * q as f64); + } + { + use crate::spec_tables::*; + let all: [(&str, &[[u8; 63]; 2]); 11] = [ + ("D1", &W_D_1), + ("D2", &W_D_2), + ("D3", &W_D_3), + ("D4", &W_D_4), + ("D5", &W_D_5), + ("D6", &W_D_6), + ("D7", &W_D_7), + ("D8", &W_D_8), + ("D9", &W_D_9), + ("D10", &W_D_10), + ("D11", &W_D_11), + ]; + let mut res = vec![]; + for (n, t) in all { + for c in 0..2 { + let mut e = 0f64; + for i in 1..64 { + e += (grid[i / 8][i % 8] - t[c][i - 1] as f64).powi(2); + } + res.push(((e / 63.0).sqrt(), format!("{n}{}", if c == 0 { "L" } else { "C" }))); + } + } + res.sort_by(|a, b| a.0.partial_cmp(&b.0).unwrap()); + println!("best matches: {:?}", &res[..4]); + } + println!("{prof:?} block {blk} estimated W (spec table below):"); + let w = &cid_info(cid).unwrap().weights[(blk != 0) as usize]; + for v in 0..8 { + let mut l = String::new(); + for u in 0..8 { + l += &format!("{:6.1}", grid[v][u]); + } + l += " |"; + for u in 0..8 { + if v + u > 0 { + l += &format!("{:4}", w[v * 8 + u - 1]); + } else { + l += " -"; + } + } + println!("{l}"); + } + } +} + +#[test] +#[ignore] +fn probe_reconstruction() { + let ff = craft_testkit::ffmpeg().unwrap(); + let dir = craft_testkit::fixtures_dir("dnx"); + let cid: u32 = std::env::var("DNX_CID").unwrap().parse().unwrap(); + let (w, h, depth): (u32, u32, u8) = match cid { + 1250 => (1280, 720, 10), + 1251 | 1252 => (1280, 720, 8), + 1235 => (1920, 1080, 10), + 1271 => (256, 128, 10), + _ => (256, 128, 8), + }; + let pix = if depth == 10 { "yuv422p10le" } else { "yuv422p" }; + let p = cid_info(cid).unwrap().p as f64; + for q in [1u32, 2, 3, 4, 8, 16] { + let mut xs = vec![]; + for amp in 1..=40u32 { + let s = stream_cid(ProbeStream { + cid, + width: w, + height: h, + depth, + quantizer: q, + coefficient_position: 1, + amplitude: amp, + negative: false, + block: 0, + }); + let path = dir.join("explore.dnxhd"); + std::fs::write(&path, &s).unwrap(); + let out = std::process::Command::new(&ff) + .args(["-hide_banner", "-loglevel", "error", "-f", "dnxhd", "-i"]) + .arg(&path) + .args(["-f", "rawvideo", "-pix_fmt", pix, "-"]) + .output() + .unwrap(); + let px: Vec = if depth == 8 { + out.stdout.iter().map(|&b| b as f64).collect() + } else { + out.stdout.as_chunks::<2>().0.iter().map(|c| u16::from_le_bytes([c[0], c[1]]) as f64).collect() + }; + let mid = (1 << (depth - 1)) as f64; + let mut sum = 0f64; + for j in 0..8 { + for i in 0..8 { + sum += 0.25 + * (px[j * w as usize + i] - mid) + * ((2 * i + 1) as f64 * std::f64::consts::PI / 16.0).cos() + * std::f64::consts::FRAC_1_SQRT_2; + } + } + xs.push((amp as f64, sum)); + } + let n = xs.len() as f64; + let sx: f64 = xs.iter().map(|x| x.0).sum(); + let sy: f64 = xs.iter().map(|x| x.1).sum(); + let sxx: f64 = xs.iter().map(|x| x.0 * x.0).sum(); + let sxy: f64 = xs.iter().map(|x| x.0 * x.1).sum(); + let a = (n * sxy - sx * sy) / (n * sxx - sx * sx); + let b = (sy - a * sx) / n; + let step = 32.0 * q as f64 / p; + println!("cid {cid} q {q}: step {step} slope {a:.4} ({:.4} steps) intercept {b:.3} ({:.4} steps)", a / step, b / step); + } +} diff --git a/crates/dnx/src/header.rs b/crates/dnx/src/header.rs new file mode 100644 index 0000000000..2459538095 --- /dev/null +++ b/crates/dnx/src/header.rs @@ -0,0 +1,259 @@ +//! Coding-unit header (SMPTE ST 2019-1 §7.2): parse and write. + +use crate::tables::{Kind, cid_info, ri_frame_size}; +use crate::{ChromaFormat, ColorVolume, Error, Result}; + +/// Offset of the macroblock scan indices payload. +pub const SCAN_INDEX_OFFSET: usize = 0x170; +/// EOF signature when the CRC flag is clear (§7.4, Figure 39). +pub const EOF_SIGNATURE: [u8; 4] = [0x60, 0x0D, 0xC0, 0xDE]; + +/// Field/frame code of a coding unit (`FFC`, §7.2.2). +#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)] +pub enum FieldCode { + /// A progressive frame, or an interlaced frame coded as one unit. + Frame, + /// Field 1 (the top field) of a field-coded interlaced frame. + Field1, + /// Field 2 (the bottom field). + Field2, +} + +/// A parsed coding-unit header. +#[derive(Debug, Clone, PartialEq, Eq)] +pub struct FrameHeader { + /// Header size `HS` in bytes (the compressed payload starts here). + pub header_size: u32, + /// Header version number `HVN` (1, 2 = HD profile; 3 = RI profile; 4 = HD with alpha). + pub version: u8, + pub cid: u32, + /// Variable bitrate (no payload padding). + pub vbr: bool, + pub field: FieldCode, + /// Macroblock-adaptive field/frame coding (CID 1260). + pub macf: bool, + /// The EOF signature holds a CRC-32 instead of `0x600DC0DE`. + pub crc: bool, + pub alpha: bool, + /// Alpha is coded losslessly (differential RLE) instead of with the DCT. + pub lossless_alpha: bool, + pub premultiplied_alpha: bool, + /// Samples per line (`SPL`). + pub width: u32, + /// Active lines (`ALPF`) of this coding unit: lines per frame, or per field for a field + /// coding unit. + pub lines: u32, + /// Pixel aspect ratio `PARC / PARN` (0/0 = not signalled). + pub par: (u16, u16), + pub bit_depth: u8, + /// Source scan type is interlaced (`SST`). + pub interlaced: bool, + /// Frame encoding (`FFE`); false = each field is its own coding unit. + pub frame_encoding: bool, + pub chroma: ChromaFormat, + /// The bitstream codes RGB (`CLF`). + pub rgb: bool, + pub color_volume: ColorVolume, + /// SMPTE ST 12-1 time code bytes, if present. + pub timecode: Option<[u8; 8]>, + /// Number of macroblock scan lines `NS`. + pub mb_rows: u32, +} + +impl FrameHeader { + /// Whether the 4:4:4 planes carry R′G′B′ (macroblocks with ACF = 1 carry Y′CbCr). + /// + /// The standard ties RGB to `CLF = 1`. ffmpeg's encoder, the most common source of DNxHR 444 + /// files, writes RGB with `CLF = 0` (and Y′CbCr with `CLF = 1`, `ACF = 1` everywhere), and + /// its decoder reads them that way; we follow that interpretation: every 4:4:4 stream is RGB + /// except macroblocks flagged ACF = 1. + pub fn rgb_planes(&self) -> bool { + self.rgb || self.chroma == ChromaFormat::Yuv444 + } + + /// Macroblocks per scan line `NW`. + pub fn mb_width(&self) -> u32 { + self.width.div_ceil(16) + } + + /// Size of this coding unit for CBR streams (`None` for VBR streams), from Table C.1 or + /// equation 7.1. + pub fn coding_unit_size(&self) -> Option { + if self.vbr { + return None; + } + let info = cid_info(self.cid)?; + Some(match info.kind { + Kind::Hd { frame_size, interlaced, .. } => { + let f = if self.alpha { frame_size + frame_size / 2 } else { frame_size }; + if interlaced && !self.frame_encoding { f / 2 } else { f } + } + Kind::Ri { c0 } => ri_frame_size(self.width, self.lines, c0, self.alpha), + }) + } +} + +fn be16(d: &[u8], o: usize) -> u32 { + u32::from(d[o]) << 8 | u32::from(d[o + 1]) +} + +fn be32(d: &[u8], o: usize) -> u32 { + u32::from_be_bytes([d[o], d[o + 1], d[o + 2], d[o + 3]]) +} + +/// Parse a coding-unit header at the start of `d`. +pub fn parse(d: &[u8]) -> Result { + if d.len() < 0x280 { + return Err(Error::Truncated); + } + let header_size = be32(d, 0); + let version = d[4]; + if !(1..=4).contains(&version) { + return Err(Error::Invalid("unknown header version")); + } + if header_size < 0x280 || header_size as usize > d.len() { + return Err(if header_size as usize > d.len() { Error::Truncated } else { Error::Invalid("header size") }); + } + let cid = be32(d, 0x28); + let info = cid_info(cid).ok_or(Error::UnknownCid(cid))?; + let vbr = d[5] & 0x10 != 0; + let field = match d[5] & 3 { + 2 => FieldCode::Field1, + 3 => FieldCode::Field2, + _ => FieldCode::Frame, + }; + let macf = d[6] & 0x20 != 0; + let crc = d[6] & 0x10 != 0; + let alpha = d[7] & 1 != 0; + let lossless_alpha = d[7] & 2 != 0; + let premultiplied_alpha = d[7] & 4 != 0; + let lines = be16(d, 0x18); + let width = be16(d, 0x1A); + let parc = (u32::from(d[0x1C] >> 2 & 3) << 8 | u32::from(d[0x1F])) as u16; + let parn = (u32::from(d[0x1C] & 3) << 8 | u32::from(d[0x20])) as u16; + let bit_depth = match d[0x21] >> 5 { + 1 => 8, + 2 => 10, + 3 => 12, + _ => return Err(Error::Invalid("sample bit depth")), + }; + let interlaced = d[0x22] & 4 != 0; + let frame_encoding = d[0x2C] & 0x80 != 0; + let chroma = match d[0x2C] >> 5 & 3 { + 0 => ChromaFormat::Yuv422, + 1 => ChromaFormat::Yuv420, + 2 => ChromaFormat::Yuv444, + _ => return Err(Error::Invalid("sub sampling")), + }; + let rgb = d[0x2C] & 1 != 0; + let color_volume = match d[0x2C] >> 1 & 3 { + 0 => ColorVolume::Bt709, + 1 => ColorVolume::Bt2020Ncl, + 2 => ColorVolume::Bt2020Cl, + _ => ColorVolume::OutOfBand, + }; + let timecode = if d[0x30] & 0x80 != 0 { d.get(0x31..).and_then(|t| t.first_chunk::<8>()).copied() } else { None }; + let mb_rows = be16(d, 0x16C); + if width == 0 || lines == 0 || width > 16384 || lines > 16384 { + return Err(Error::Invalid("raster size")); + } + if let Kind::Hd { width: w, depth, .. } = info.kind + && (w as u32 != width || depth != bit_depth) + { + return Err(Error::Invalid("raster does not match the compression ID")); + } + if rgb && chroma != ChromaFormat::Yuv444 { + return Err(Error::Invalid("RGB requires 4:4:4")); + } + if chroma == ChromaFormat::Yuv444 && !matches!(cid, 1256 | 1270) { + return Err(Error::Invalid("4:4:4 requires CID 1256 or 1270")); + } + // macroblock scan lines must cover the coded raster, and their indices must fit the header + if mb_rows == 0 || SCAN_INDEX_OFFSET + 4 * mb_rows as usize > header_size as usize { + return Err(Error::Invalid("macroblock scan line count")); + } + if mb_rows != lines.div_ceil(16) { + return Err(Error::Invalid("macroblock scan lines do not match the raster")); + } + Ok(FrameHeader { + header_size, + version, + cid, + vbr, + field, + macf, + crc, + alpha, + lossless_alpha, + premultiplied_alpha, + width, + lines, + par: (parc, parn), + bit_depth, + interlaced, + frame_encoding, + chroma, + rgb, + color_volume, + timecode, + mb_rows, + }) +} + +/// Header size for a raster of `lines` lines (§7.2: 640 bytes up to 1088 lines, plus 4 bytes +/// per further 16 lines). +pub fn header_size_for(lines: u32) -> u32 { + if lines <= 1088 { 0x280 } else { 0x280 + 4 * (lines - 1088).div_ceil(16) } +} + +/// Write a header (without scan indices) for `h` into a zeroed buffer of `h.header_size` bytes. +pub fn write(h: &FrameHeader) -> Vec { + let mut d = vec![0u8; h.header_size as usize]; + d[0..4].copy_from_slice(&h.header_size.to_be_bytes()); + d[4] = h.version; + d[5] = (h.vbr as u8) << 4 + | match h.field { + FieldCode::Frame => 1, + FieldCode::Field1 => 2, + FieldCode::Field2 => 3, + }; + d[6] = 0x80 | (h.macf as u8) << 5 | (h.crc as u8) << 4; + d[7] = 0xA0 | (h.premultiplied_alpha as u8) << 2 | (h.lossless_alpha as u8) << 1 | h.alpha as u8; + d[0x18..0x1A].copy_from_slice(&(h.lines as u16).to_be_bytes()); + d[0x1A..0x1C].copy_from_slice(&(h.width as u16).to_be_bytes()); + d[0x1C] = ((h.par.0 >> 8) as u8 & 3) << 2 | ((h.par.1 >> 8) as u8 & 3); + d[0x1D..0x1F].copy_from_slice(&(h.lines as u16).to_be_bytes()); + d[0x1F] = h.par.0 as u8; + d[0x20] = h.par.1 as u8; + let sbd = match h.bit_depth { + 8 => 1, + 10 => 2, + _ => 3, + }; + d[0x21] = sbd << 5 | 0x18; + d[0x22] = 0x88 | (h.interlaced as u8) << 2; + d[0x28..0x2C].copy_from_slice(&h.cid.to_be_bytes()); + let ssc = match h.chroma { + ChromaFormat::Yuv422 => 0, + ChromaFormat::Yuv420 => 1, + ChromaFormat::Yuv444 => 2, + }; + let clv = match h.color_volume { + ColorVolume::Bt709 => 0, + ColorVolume::Bt2020Ncl => 1, + ColorVolume::Bt2020Cl => 2, + ColorVolume::OutOfBand => 3, + }; + d[0x2C] = (h.frame_encoding as u8) << 7 | ssc << 5 | clv << 1 | h.rgb as u8; + if let Some(tc) = h.timecode { + d[0x30] = 0x80; + d[0x31..0x39].copy_from_slice(&tc); + } + d[0x5F] = 0x01; + d[0x167] = 0x02; + let msips = (4 * h.mb_rows + 4) as u16; + d[0x16A..0x16C].copy_from_slice(&msips.to_be_bytes()); + d[0x16C..0x16E].copy_from_slice(&(h.mb_rows as u16).to_be_bytes()); + d[0x16F] = 0x10; + d +} diff --git a/crates/dnx/src/lib.rs b/crates/dnx/src/lib.rs new file mode 100644 index 0000000000..2b44489c8e --- /dev/null +++ b/crates/dnx/src/lib.rs @@ -0,0 +1,236 @@ +//! Clean-room Avid DNxHD / DNxHR codec (VC-3, implemented from SMPTE ST 2019-1:2016 and its +//! Amendment 1:2023). +//! +//! - **Decoder** ([`decode_frame`]): every compression ID of the standard — the DNxHD (HD +//! profile) IDs 1235–1260 including interlaced field and macroblock-adaptive coding, thin +//! rasters and 4:4:4 RGB, and the resolution-independent DNxHR IDs 1270–1274 (LB, SQ, HQ, HQX, +//! 444) at 8, 10 and 12 bits in 4:2:0, 4:2:2 and 4:4:4, with DCT or lossless alpha. Macroblock +//! scan lines decode in parallel with the `threads` feature (rayon). +//! - **Encoder** ([`Encoder`]): DNxHR SQ / HQ (8-bit 4:2:2) and HQX (10- or 12-bit 4:2:2), CBR +//! at the standard's frame sizes, progressive. +//! +//! See the crate README for accuracy, performance and limitations. + +#![deny(clippy::unwrap_used, clippy::expect_used, clippy::panic, clippy::unimplemented, clippy::todo, clippy::unreachable)] +#![allow(clippy::needless_range_loop, clippy::type_complexity)] + +mod bits; +mod dct; +mod decode; +mod encode; +pub mod header; +mod spec_tables; +mod tables; + +pub use decode::{DecodeOptions, decode_frame, decode_frame_with, probe}; +pub use encode::{Encoder, EncoderConfig}; +pub use header::{FieldCode, FrameHeader}; +pub use tables::ri_frame_size; + +/// Codec errors. +#[derive(Debug, Clone, PartialEq, Eq, thiserror::Error)] +pub enum Error { + #[error("truncated VC-3 data")] + Truncated, + #[error("invalid VC-3 data: {0}")] + Invalid(&'static str), + #[error("unknown VC-3 compression ID {0}")] + UnknownCid(u32), + #[error("corrupt macroblock scan line {0}")] + Row(usize), + #[error("invalid encoder input: {0}")] + Input(String), +} + +pub type Result = std::result::Result; + +/// Chroma subsampling (`SSC`). +#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)] +pub enum ChromaFormat { + /// Half width, half height. + Yuv420, + /// Half width, full height. + Yuv422, + /// Full size (also RGB). + Yuv444, +} + +impl ChromaFormat { + pub fn chroma_width(self, width: u32) -> u32 { + match self { + ChromaFormat::Yuv444 => width, + _ => width.div_ceil(2), + } + } + pub fn chroma_height(self, height: u32) -> u32 { + match self { + ChromaFormat::Yuv420 => height.div_ceil(2), + _ => height, + } + } +} + +/// Colour volume (`CLV`). +#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash, Default)] +pub enum ColorVolume { + #[default] + Bt709, + /// BT.2020 non-constant luminance. + Bt2020Ncl, + /// BT.2020 constant luminance. + Bt2020Cl, + /// Described out of band. + OutOfBand, +} + +/// Frame scan structure. +#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash, Default)] +pub enum Interlace { + #[default] + Progressive, + /// Interlaced; field 1 (the top field, even lines) comes first. + TopFieldFirst, +} + +/// DNxHR profiles (resolution-independent compression IDs). +#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)] +pub enum Profile { + /// DNxHR LB (CID 1274), 8-bit. + Lb, + /// DNxHR SQ (CID 1273), 8-bit. + Sq, + /// DNxHR HQ (CID 1272), 8-bit. + Hq, + /// DNxHR HQX (CID 1271), 10- or 12-bit. + Hqx, + /// DNxHR 444 (CID 1270), 10- or 12-bit 4:4:4. + R444, +} + +impl Profile { + pub fn cid(self) -> u32 { + match self { + Profile::Lb => 1274, + Profile::Sq => 1273, + Profile::Hq => 1272, + Profile::Hqx => 1271, + Profile::R444 => 1270, + } + } + pub fn from_cid(cid: u32) -> Option { + [Profile::Lb, Profile::Sq, Profile::Hq, Profile::Hqx, Profile::R444].into_iter().find(|p| p.cid() == cid) + } + pub fn name(self) -> &'static str { + match self { + Profile::Lb => "DNxHR LB", + Profile::Sq => "DNxHR SQ", + Profile::Hq => "DNxHR HQ", + Profile::Hqx => "DNxHR HQX", + Profile::R444 => "DNxHR 444", + } + } + /// CBR frame size for a raster (equation 7.1). + pub fn frame_size(self, width: u32, height: u32) -> u32 { + let c0 = match self { + Profile::Lb => 188416, + Profile::Sq => 606208, + Profile::Hq | Profile::Hqx => 917504, + Profile::R444 => 1835008, + }; + ri_frame_size(width, height, c0, false) + } +} + +/// A human-readable name for a compression ID ("DNxHD 1080p 145/120 8-bit", "DNxHR HQ"…). +pub fn cid_name(cid: u32) -> String { + if let Some(p) = Profile::from_cid(cid) { + return p.name().into(); + } + match cid { + 1235 => "DNxHD 1080p 10-bit (CID 1235)".into(), + 1237 => "DNxHD 1080p 8-bit (CID 1237)".into(), + 1238 => "DNxHD 1080p 8-bit (CID 1238)".into(), + 1241 => "DNxHD 1080i 10-bit (CID 1241)".into(), + 1242 => "DNxHD 1080i 8-bit (CID 1242)".into(), + 1243 => "DNxHD 1080i 8-bit (CID 1243)".into(), + 1244 => "DNxHD 1080i thin raster (CID 1244)".into(), + 1250 => "DNxHD 720p 10-bit (CID 1250)".into(), + 1251 => "DNxHD 720p 8-bit (CID 1251)".into(), + 1252 => "DNxHD 720p 8-bit (CID 1252)".into(), + 1253 => "DNxHD 1080p 36 (CID 1253)".into(), + 1256 => "DNxHD 444 10-bit (CID 1256)".into(), + 1258 => "DNxHD 720p thin raster (CID 1258)".into(), + 1259 => "DNxHD 1080p thin raster (CID 1259)".into(), + 1260 => "DNxHD 1080i thin raster (CID 1260)".into(), + _ => format!("VC-3 (CID {cid})"), + } +} + +/// A decoded (or to-be-encoded) picture: planar `u16` samples, rows packed without padding. +/// +/// For RGB streams (`rgb == true`) the planes `y`, `cb`, `cr` hold G, B and R: the coded channel +/// order Ch1/Ch2/Ch3 as written by ffmpeg and read by its decoder (the standard's text names Ch1 +/// red; see the README). +#[derive(Debug, Clone, PartialEq, Eq)] +pub struct Frame { + pub width: u32, + pub height: u32, + pub chroma: ChromaFormat, + pub rgb: bool, + /// Significant bits per sample of every plane (8, 10 or 12). + pub bit_depth: u8, + pub y: Vec, + pub cb: Vec, + pub cr: Vec, + /// Full-range alpha (0 = transparent). + pub alpha: Option>, + pub interlace: Interlace, + pub color_volume: ColorVolume, + /// Compression ID of the stream. + pub cid: u32, + /// Pixel aspect ratio (0/0 = not signalled). + pub par: (u16, u16), +} + +impl Frame { + /// A black (video-range) frame. + pub fn new(width: u32, height: u32, chroma: ChromaFormat, bit_depth: u8, with_alpha: bool) -> Frame { + let n = width as usize * height as usize; + let cn = chroma.chroma_width(width) as usize * chroma.chroma_height(height) as usize; + let s = bit_depth.saturating_sub(8) as u32; + Frame { + width, + height, + chroma, + rgb: false, + bit_depth, + y: vec![(16u32 << s) as u16; n], + cb: vec![(128u32 << s) as u16; cn], + cr: vec![(128u32 << s) as u16; cn], + alpha: with_alpha.then(|| vec![((1u32 << bit_depth) - 1) as u16; n]), + interlace: Interlace::Progressive, + color_volume: ColorVolume::Bt709, + cid: 0, + par: (0, 0), + } + } + + pub(crate) fn new_like(h: &FrameHeader, width: u32, height: u32) -> Frame { + let mut f = Frame::new(width, height, h.chroma, h.bit_depth, h.alpha); + f.rgb = h.rgb_planes(); + f.color_volume = h.color_volume; + f.cid = h.cid; + f.par = h.par; + f + } + + pub fn chroma_width(&self) -> u32 { + self.chroma.chroma_width(self.width) + } + + pub fn chroma_height(&self) -> u32 { + self.chroma.chroma_height(self.height) + } +} +#[cfg(test)] +mod ffmpeg_probe; diff --git a/crates/dnx/src/spec_tables.rs b/crates/dnx/src/spec_tables.rs new file mode 100644 index 0000000000..c29c6a53f0 --- /dev/null +++ b/crates/dnx/src/spec_tables.rs @@ -0,0 +1,2163 @@ +// Generated by tools/extract_tables.py from SMPTE ST 2019-1:2016 (VC-3), Annexes D and E. +// Do not edit by hand. + +pub const W_D_1: [[u8; 63]; 2] = [ + [ + 32, 32, 32, 33, 35, 38, 39, 32, 33, 32, 33, 36, 36, 39, 42, 32, 32, 33, 36, 35, 37, 41, 43, 31, 33, 34, 36, 36, 40, 42, 48, 32, 34, 36, 37, + 39, 42, 46, 51, 36, 37, 37, 39, 41, 46, 51, 55, 37, 39, 41, 41, 47, 50, 55, 56, 41, 42, 41, 44, 50, 53, 60, 60, + ], + [ + 32, 33, 34, 39, 41, 54, 59, 33, 34, 35, 38, 43, 49, 58, 84, 34, 37, 39, 44, 46, 55, 74, 87, 40, 42, 47, 48, 58, 70, 87, 86, 43, 50, 56, 63, + 72, 94, 91, 82, 55, 63, 65, 75, 93, 89, 85, 73, 61, 67, 82, 81, 83, 90, 79, 73, 74, 84, 75, 78, 90, 85, 73, 73, + ], +]; +pub const W_D_2: [[u8; 63]; 2] = [ + [ + 32, 36, 37, 41, 44, 54, 60, 33, 34, 36, 39, 43, 51, 62, 78, 34, 36, 38, 41, 49, 59, 73, 79, 37, 38, 40, 47, 55, 66, 80, 95, 38, 41, 46, 54, + 63, 79, 93, 96, 46, 47, 56, 64, 78, 90, 97, 98, 49, 58, 66, 78, 89, 97, 102, 98, 61, 65, 82, 87, 100, 104, 99, 99, + ], + [ + 32, 38, 39, 47, 51, 77, 83, 36, 39, 41, 48, 55, 74, 85, 95, 39, 45, 53, 58, 72, 83, 105, 89, 51, 58, 66, 73, 82, 109, 92, 95, 57, 75, 78, 89, + 105, 95, 93, 96, 81, 82, 99, 99, 94, 90, 97, 98, 83, 96, 97, 93, 89, 97, 102, 98, 90, 94, 92, 88, 100, 104, 99, 99, + ], +]; +pub const W_D_3: [[u8; 63]; 2] = [ + [ + 32, 33, 33, 37, 37, 41, 42, 32, 34, 33, 35, 36, 38, 41, 43, 33, 33, 33, 36, 37, 39, 41, 44, 33, 33, 35, 36, 38, 40, 44, 50, 33, 36, 35, 38, + 39, 44, 49, 51, 38, 36, 38, 39, 44, 47, 51, 58, 38, 39, 41, 44, 47, 51, 57, 59, 41, 42, 45, 47, 53, 55, 57, 57, + ], + [ + 32, 34, 34, 40, 41, 55, 61, 35, 35, 35, 39, 42, 52, 62, 75, 35, 39, 41, 44, 52, 58, 68, 77, 43, 45, 48, 53, 58, 65, 74, 80, 45, 55, 59, 63, + 66, 74, 76, 89, 59, 65, 66, 70, 77, 73, 90, 84, 63, 66, 74, 76, 73, 82, 86, 82, 65, 70, 73, 73, 77, 80, 82, 82, + ], +]; +pub const W_D_4: [[u8; 63]; 2] = [ + [ + 32, 35, 36, 39, 40, 45, 45, 33, 34, 37, 39, 40, 43, 46, 46, 34, 36, 38, 38, 42, 47, 46, 46, 37, 38, 38, 41, 46, 45, 47, 49, 38, 39, 41, 45, + 45, 47, 48, 50, 38, 39, 43, 44, 46, 47, 49, 48, 37, 39, 40, 44, 45, 48, 48, 49, 37, 37, 42, 43, 46, 47, 49, 49, + ], + [ + 32, 37, 40, 41, 41, 45, 45, 36, 37, 41, 41, 42, 45, 46, 48, 38, 40, 41, 43, 45, 47, 48, 46, 40, 42, 44, 44, 46, 48, 47, 49, 42, 44, 45, 45, + 47, 47, 48, 50, 45, 46, 44, 45, 46, 47, 49, 48, 46, 42, 45, 47, 45, 48, 49, 49, 41, 43, 46, 45, 46, 48, 49, 49, + ], +]; +pub const W_D_5: [[u8; 63]; 2] = [ + [ + 32, 35, 36, 38, 37, 40, 42, 33, 34, 35, 37, 37, 37, 45, 49, 33, 33, 37, 37, 36, 46, 48, 50, 33, 36, 36, 37, 44, 46, 52, 51, 35, 37, 39, 41, + 48, 52, 51, 50, 37, 38, 42, 50, 50, 50, 49, 49, 37, 44, 49, 49, 50, 49, 51, 47, 45, 47, 47, 48, 51, 52, 47, 47, + ], + [ + 32, 45, 45, 51, 47, 45, 47, 37, 45, 44, 49, 41, 47, 50, 51, 42, 38, 44, 40, 47, 51, 49, 51, 37, 42, 43, 51, 49, 48, 52, 54, 40, 44, 54, 46, + 51, 52, 54, 53, 46, 51, 47, 55, 54, 53, 53, 60, 48, 49, 55, 54, 52, 55, 62, 60, 47, 50, 49, 49, 59, 63, 60, 60, + ], +]; +pub const W_D_6: [[u8; 63]; 2] = [ + [ + 32, 35, 35, 38, 40, 44, 45, 32, 33, 35, 35, 39, 42, 44, 45, 33, 35, 34, 37, 41, 42, 45, 45, 35, 35, 37, 40, 41, 44, 45, 48, 35, 37, 38, 40, + 43, 45, 47, 48, 36, 36, 38, 40, 45, 47, 48, 47, 35, 36, 38, 41, 45, 46, 47, 48, 36, 37, 39, 41, 44, 45, 47, 47, + ], + [ + 32, 37, 39, 41, 42, 45, 45, 36, 36, 39, 41, 43, 45, 46, 45, 37, 41, 41, 43, 45, 44, 45, 46, 43, 42, 43, 46, 44, 45, 46, 48, 43, 44, 47, 45, + 44, 46, 47, 49, 44, 46, 44, 45, 45, 47, 48, 47, 44, 42, 46, 44, 45, 46, 47, 48, 41, 43, 45, 44, 45, 46, 47, 47, + ], +]; +pub const W_D_7: [[u8; 63]; 2] = [ + [ + 32, 37, 37, 40, 41, 52, 53, 33, 36, 36, 38, 40, 48, 49, 52, 34, 34, 37, 39, 44, 47, 49, 54, 33, 35, 38, 40, 45, 46, 54, 51, 34, 37, 37, 42, + 44, 49, 52, 48, 34, 34, 38, 43, 44, 51, 50, 50, 33, 36, 41, 44, 51, 52, 50, 54, 36, 38, 44, 47, 53, 53, 54, 54, + ], + [ + 32, 40, 38, 42, 40, 45, 45, 34, 42, 36, 43, 38, 46, 46, 49, 38, 35, 43, 39, 44, 47, 47, 49, 35, 42, 43, 42, 46, 47, 49, 52, 38, 43, 43, 44, + 50, 49, 56, 50, 42, 43, 44, 50, 51, 57, 52, 53, 41, 45, 46, 53, 53, 56, 53, 54, 46, 46, 51, 49, 56, 53, 58, 58, + ], +]; +pub const W_D_8: [[u8; 63]; 2] = [ + [ + 32, 35, 35, 36, 36, 41, 43, 32, 34, 35, 36, 37, 39, 43, 47, 33, 34, 36, 38, 38, 42, 42, 50, 34, 36, 38, 38, 41, 40, 47, 54, 35, 38, 39, 40, + 39, 45, 49, 58, 38, 39, 40, 39, 46, 47, 54, 60, 38, 39, 41, 46, 46, 48, 57, 62, 40, 41, 44, 45, 49, 54, 63, 63, + ], + [ + 32, 35, 36, 40, 42, 51, 51, 35, 36, 39, 39, 43, 51, 52, 55, 36, 41, 41, 43, 51, 53, 54, 56, 43, 44, 45, 50, 54, 54, 55, 57, 45, 48, 50, 51, + 55, 58, 59, 58, 49, 52, 49, 57, 58, 62, 58, 60, 51, 51, 56, 58, 62, 61, 59, 62, 52, 52, 60, 61, 59, 59, 63, 63, + ], +]; +pub const W_D_9: [[u8; 63]; 2] = [ + [ + 32, 34, 34, 35, 36, 41, 44, 32, 34, 35, 36, 38, 39, 43, 46, 34, 35, 36, 38, 38, 41, 43, 46, 35, 37, 38, 38, 40, 40, 44, 55, 36, 38, 38, 40, + 39, 44, 50, 58, 38, 38, 40, 40, 44, 48, 53, 61, 38, 40, 41, 42, 46, 48, 58, 62, 39, 40, 41, 43, 50, 55, 62, 62, + ], + [ + 32, 35, 36, 40, 42, 51, 51, 35, 36, 39, 39, 43, 51, 52, 55, 36, 41, 41, 43, 51, 53, 54, 56, 43, 44, 45, 50, 54, 54, 55, 57, 45, 48, 50, 51, + 55, 58, 59, 58, 48, 50, 49, 57, 58, 62, 58, 61, 48, 51, 56, 58, 62, 61, 59, 62, 52, 52, 60, 61, 59, 59, 62, 62, + ], +]; +pub const W_D_10: [[u8; 63]; 2] = [ + [ + 32, 36, 36, 40, 40, 55, 60, 34, 36, 37, 40, 41, 48, 57, 82, 35, 36, 41, 41, 46, 52, 73, 82, 37, 40, 42, 45, 50, 65, 80, 87, 39, 41, 44, 49, + 62, 78, 88, 90, 41, 44, 49, 58, 73, 90, 95, 95, 43, 52, 55, 68, 90, 100, 97, 93, 52, 53, 71, 82, 107, 103, 99, 99, + ], + [ + 32, 37, 38, 49, 53, 65, 66, 35, 37, 40, 49, 56, 64, 65, 82, 36, 42, 50, 56, 64, 67, 73, 85, 46, 50, 57, 63, 71, 72, 89, 87, 49, 58, 65, 72, + 78, 88, 88, 90, 60, 64, 74, 81, 84, 90, 95, 134, 62, 74, 77, 80, 90, 114, 129, 125, 74, 74, 90, 100, 128, 125, 116, 116, + ], +]; +pub const W_D_11: [[u8; 63]; 2] = [ + [ + 32, 32, 32, 33, 35, 38, 39, 32, 33, 32, 33, 36, 36, 39, 42, 32, 32, 33, 36, 35, 37, 41, 43, 31, 33, 34, 36, 36, 40, 42, 48, 32, 34, 36, 37, + 39, 42, 46, 51, 36, 37, 37, 39, 41, 46, 51, 55, 37, 39, 41, 41, 47, 50, 55, 56, 41, 42, 41, 44, 50, 53, 60, 60, + ], + [ + 32, 32, 32, 33, 35, 38, 39, 32, 33, 32, 33, 36, 36, 39, 42, 32, 32, 33, 36, 35, 37, 41, 43, 31, 33, 34, 36, 36, 40, 42, 48, 32, 34, 36, 37, + 39, 42, 46, 51, 36, 37, 37, 39, 41, 46, 51, 55, 37, 39, 41, 41, 47, 50, 55, 56, 41, 42, 41, 44, 50, 53, 60, 60, + ], +]; + +/// Table E.1: (codeword, length, amplitude (0 = EOB), Frun, Findex). +pub const AMP_E_1: [(u16, u8, u8, u8, u8); 257] = [ + (0b00, 2, 1, 0, 0), + (0b11111100111, 11, 37, 0, 0), + (0b01, 2, 1, 1, 0), + (0b11111101000, 11, 38, 0, 0), + (0b100, 3, 2, 0, 0), + (0b11111101001, 11, 10, 1, 0), + (0b1010, 4, 3, 0, 0), + (0b11111101010, 11, 11, 1, 0), + (0b1011, 4, 0, 0, 0), + (0b111111010110, 12, 39, 0, 0), + (0b11000, 5, 4, 0, 0), + (0b111111010111, 12, 40, 0, 0), + (0b11001, 5, 5, 0, 0), + (0b111111011000, 12, 41, 0, 0), + (0b11010, 5, 2, 1, 0), + (0b111111011001, 12, 42, 0, 0), + (0b110110, 6, 6, 0, 0), + (0b111111011010, 12, 43, 0, 0), + (0b110111, 6, 7, 0, 0), + (0b111111011011, 12, 44, 0, 0), + (0b111000, 6, 8, 0, 0), + (0b111111011100, 12, 45, 0, 0), + (0b111001, 6, 3, 1, 0), + (0b111111011101, 12, 46, 0, 0), + (0b1110100, 7, 9, 0, 0), + (0b111111011110, 12, 47, 0, 0), + (0b1110101, 7, 10, 0, 0), + (0b111111011111, 12, 48, 0, 0), + (0b1110110, 7, 11, 0, 0), + (0b111111100000, 12, 49, 0, 0), + (0b1110111, 7, 4, 1, 0), + (0b111111100001, 12, 50, 0, 0), + (0b11110000, 8, 12, 0, 0), + (0b111111100010, 12, 12, 1, 0), + (0b11110001, 8, 13, 0, 0), + (0b111111100011, 12, 13, 1, 0), + (0b11110010, 8, 14, 0, 0), + (0b111111100100, 12, 14, 1, 0), + (0b11110011, 8, 15, 0, 0), + (0b111111100101, 12, 15, 1, 0), + (0b11110100, 8, 16, 0, 0), + (0b1111111001100, 13, 51, 0, 0), + (0b11110101, 8, 5, 1, 0), + (0b1111111001101, 13, 52, 0, 0), + (0b111101100, 9, 17, 0, 0), + (0b1111111001110, 13, 53, 0, 0), + (0b111101101, 9, 18, 0, 0), + (0b1111111001111, 13, 54, 0, 0), + (0b111101110, 9, 19, 0, 0), + (0b1111111010000, 13, 55, 0, 0), + (0b111101111, 9, 20, 0, 0), + (0b1111111010001, 13, 56, 0, 0), + (0b111110000, 9, 21, 0, 0), + (0b1111111010010, 13, 57, 0, 0), + (0b111110001, 9, 6, 1, 0), + (0b1111111010011, 13, 58, 0, 0), + (0b111110010, 9, 7, 1, 0), + (0b1111111010100, 13, 59, 0, 0), + (0b1111100110, 10, 22, 0, 0), + (0b1111111010101, 13, 60, 0, 0), + (0b1111100111, 10, 23, 0, 0), + (0b1111111010110, 13, 61, 0, 0), + (0b1111101000, 10, 24, 0, 0), + (0b1111111010111, 13, 62, 0, 0), + (0b1111101001, 10, 25, 0, 0), + (0b1111111011000, 13, 63, 0, 0), + (0b1111101010, 10, 26, 0, 0), + (0b1111111011001, 13, 1, 0, 1), + (0b1111101011, 10, 27, 0, 0), + (0b1111111011010, 13, 16, 1, 0), + (0b1111101100, 10, 28, 0, 0), + (0b1111111011011, 13, 17, 1, 0), + (0b1111101101, 10, 29, 0, 0), + (0b1111111011100, 13, 18, 1, 0), + (0b1111101110, 10, 8, 1, 0), + (0b1111111011101, 13, 19, 1, 0), + (0b1111101111, 10, 9, 1, 0), + (0b11111110111100, 14, 64, 0, 0), + (0b11111100000, 11, 30, 0, 0), + (0b11111110111101, 14, 2, 0, 1), + (0b11111100001, 11, 31, 0, 0), + (0b11111110111110, 14, 3, 0, 1), + (0b11111100010, 11, 32, 0, 0), + (0b11111110111111, 14, 4, 0, 1), + (0b11111100011, 11, 33, 0, 0), + (0b11111111000000, 14, 5, 0, 1), + (0b11111100100, 11, 34, 0, 0), + (0b11111111000001, 14, 6, 0, 1), + (0b11111100101, 11, 35, 0, 0), + (0b11111111000010, 14, 7, 0, 1), + (0b11111100110, 11, 36, 0, 0), + (0b11111111000011, 14, 8, 0, 1), + (0b11111111000100, 14, 9, 0, 1), + (0b1111111110010101, 16, 54, 0, 1), + (0b11111111000101, 14, 10, 0, 1), + (0b1111111110010110, 16, 55, 0, 1), + (0b11111111000110, 14, 11, 0, 1), + (0b1111111110010111, 16, 56, 0, 1), + (0b11111111000111, 14, 12, 0, 1), + (0b1111111110011000, 16, 57, 0, 1), + (0b11111111001000, 14, 13, 0, 1), + (0b1111111110011001, 16, 58, 0, 1), + (0b11111111001001, 14, 14, 0, 1), + (0b1111111110011010, 16, 59, 0, 1), + (0b11111111001010, 14, 15, 0, 1), + (0b1111111110011011, 16, 60, 0, 1), + (0b11111111001011, 14, 16, 0, 1), + (0b1111111110011100, 16, 61, 0, 1), + (0b11111111001100, 14, 17, 0, 1), + (0b1111111110011101, 16, 62, 0, 1), + (0b11111111001101, 14, 20, 1, 0), + (0b1111111110011110, 16, 63, 0, 1), + (0b11111111001110, 14, 21, 1, 0), + (0b1111111110011111, 16, 64, 0, 1), + (0b11111111001111, 14, 22, 1, 0), + (0b1111111110100000, 16, 33, 1, 0), + (0b11111111010000, 14, 23, 1, 0), + (0b1111111110100001, 16, 34, 1, 0), + (0b11111111010001, 14, 24, 1, 0), + (0b1111111110100010, 16, 35, 1, 0), + (0b111111110100100, 15, 18, 0, 1), + (0b1111111110100011, 16, 36, 1, 0), + (0b111111110100101, 15, 19, 0, 1), + (0b1111111110100100, 16, 37, 1, 0), + (0b111111110100110, 15, 20, 0, 1), + (0b1111111110100101, 16, 38, 1, 0), + (0b111111110100111, 15, 21, 0, 1), + (0b1111111110100110, 16, 39, 1, 0), + (0b111111110101000, 15, 22, 0, 1), + (0b1111111110100111, 16, 40, 1, 0), + (0b111111110101001, 15, 23, 0, 1), + (0b1111111110101000, 16, 41, 1, 0), + (0b111111110101010, 15, 24, 0, 1), + (0b1111111110101001, 16, 42, 1, 0), + (0b111111110101011, 15, 25, 0, 1), + (0b1111111110101010, 16, 43, 1, 0), + (0b111111110101100, 15, 26, 0, 1), + (0b1111111110101011, 16, 44, 1, 0), + (0b111111110101101, 15, 27, 0, 1), + (0b1111111110101100, 16, 45, 1, 0), + (0b111111110101110, 15, 28, 0, 1), + (0b1111111110101101, 16, 46, 1, 0), + (0b111111110101111, 15, 29, 0, 1), + (0b1111111110101110, 16, 47, 1, 0), + (0b111111110110000, 15, 30, 0, 1), + (0b1111111110101111, 16, 48, 1, 0), + (0b111111110110001, 15, 31, 0, 1), + (0b1111111110110000, 16, 49, 1, 0), + (0b111111110110010, 15, 32, 0, 1), + (0b1111111110110001, 16, 50, 1, 0), + (0b111111110110011, 15, 33, 0, 1), + (0b1111111110110010, 16, 51, 1, 0), + (0b111111110110100, 15, 34, 0, 1), + (0b1111111110110011, 16, 52, 1, 0), + (0b111111110110101, 15, 35, 0, 1), + (0b1111111110110100, 16, 53, 1, 0), + (0b111111110110110, 15, 36, 0, 1), + (0b1111111110110101, 16, 54, 1, 0), + (0b111111110110111, 15, 37, 0, 1), + (0b1111111110110110, 16, 55, 1, 0), + (0b111111110111000, 15, 38, 0, 1), + (0b1111111110110111, 16, 56, 1, 0), + (0b111111110111001, 15, 39, 0, 1), + (0b1111111110111000, 16, 57, 1, 0), + (0b111111110111010, 15, 40, 0, 1), + (0b1111111110111001, 16, 58, 1, 0), + (0b111111110111011, 15, 41, 0, 1), + (0b1111111110111010, 16, 59, 1, 0), + (0b111111110111100, 15, 42, 0, 1), + (0b1111111110111011, 16, 60, 1, 0), + (0b111111110111101, 15, 25, 1, 0), + (0b1111111110111100, 16, 61, 1, 0), + (0b111111110111110, 15, 26, 1, 0), + (0b1111111110111101, 16, 62, 1, 0), + (0b111111110111111, 15, 27, 1, 0), + (0b1111111110111110, 16, 63, 1, 0), + (0b111111111000000, 15, 28, 1, 0), + (0b1111111110111111, 16, 64, 1, 0), + (0b111111111000001, 15, 29, 1, 0), + (0b1111111111000000, 16, 1, 1, 1), + (0b111111111000010, 15, 30, 1, 0), + (0b1111111111000001, 16, 2, 1, 1), + (0b111111111000011, 15, 31, 1, 0), + (0b1111111111000010, 16, 3, 1, 1), + (0b111111111000100, 15, 32, 1, 0), + (0b1111111111000011, 16, 4, 1, 1), + (0b1111111110001010, 16, 43, 0, 1), + (0b1111111111000100, 16, 5, 1, 1), + (0b1111111110001011, 16, 44, 0, 1), + (0b1111111111000101, 16, 6, 1, 1), + (0b1111111110001100, 16, 45, 0, 1), + (0b1111111111000110, 16, 7, 1, 1), + (0b1111111110001101, 16, 46, 0, 1), + (0b1111111111000111, 16, 8, 1, 1), + (0b1111111110001110, 16, 47, 0, 1), + (0b1111111111001000, 16, 9, 1, 1), + (0b1111111110001111, 16, 48, 0, 1), + (0b1111111111001001, 16, 10, 1, 1), + (0b1111111110010000, 16, 49, 0, 1), + (0b1111111111001010, 16, 11, 1, 1), + (0b1111111110010001, 16, 50, 0, 1), + (0b1111111111001011, 16, 12, 1, 1), + (0b1111111110010010, 16, 51, 0, 1), + (0b1111111111001100, 16, 13, 1, 1), + (0b1111111110010011, 16, 52, 0, 1), + (0b1111111111001101, 16, 14, 1, 1), + (0b1111111110010100, 16, 53, 0, 1), + (0b1111111111001110, 16, 15, 1, 1), + (0b1111111111001111, 16, 16, 1, 1), + (0b1111111111101001, 16, 42, 1, 1), + (0b1111111111010000, 16, 17, 1, 1), + (0b1111111111101010, 16, 43, 1, 1), + (0b1111111111010001, 16, 18, 1, 1), + (0b1111111111101011, 16, 44, 1, 1), + (0b1111111111010010, 16, 19, 1, 1), + (0b1111111111101100, 16, 45, 1, 1), + (0b1111111111010011, 16, 20, 1, 1), + (0b1111111111101101, 16, 46, 1, 1), + (0b1111111111010100, 16, 21, 1, 1), + (0b1111111111101110, 16, 47, 1, 1), + (0b1111111111010101, 16, 22, 1, 1), + (0b1111111111101111, 16, 48, 1, 1), + (0b1111111111010110, 16, 23, 1, 1), + (0b1111111111110000, 16, 49, 1, 1), + (0b1111111111010111, 16, 24, 1, 1), + (0b1111111111110001, 16, 50, 1, 1), + (0b1111111111011000, 16, 25, 1, 1), + (0b1111111111110010, 16, 51, 1, 1), + (0b1111111111011001, 16, 26, 1, 1), + (0b1111111111110011, 16, 52, 1, 1), + (0b1111111111011010, 16, 27, 1, 1), + (0b1111111111110100, 16, 53, 1, 1), + (0b1111111111011011, 16, 28, 1, 1), + (0b1111111111110101, 16, 54, 1, 1), + (0b1111111111011100, 16, 29, 1, 1), + (0b1111111111110110, 16, 55, 1, 1), + (0b1111111111011101, 16, 30, 1, 1), + (0b1111111111110111, 16, 56, 1, 1), + (0b1111111111011110, 16, 31, 1, 1), + (0b1111111111111000, 16, 57, 1, 1), + (0b1111111111011111, 16, 32, 1, 1), + (0b1111111111111001, 16, 58, 1, 1), + (0b1111111111100000, 16, 33, 1, 1), + (0b1111111111111010, 16, 59, 1, 1), + (0b1111111111100001, 16, 34, 1, 1), + (0b1111111111111011, 16, 60, 1, 1), + (0b1111111111100010, 16, 35, 1, 1), + (0b1111111111111100, 16, 61, 1, 1), + (0b1111111111100011, 16, 36, 1, 1), + (0b1111111111111101, 16, 62, 1, 1), + (0b1111111111100100, 16, 37, 1, 1), + (0b1111111111111110, 16, 63, 1, 1), + (0b1111111111100101, 16, 38, 1, 1), + (0b1111111111111111, 16, 64, 1, 1), + (0b1111111111100110, 16, 39, 1, 1), + (0b1111111111100111, 16, 40, 1, 1), + (0b1111111111101000, 16, 41, 1, 1), +]; +/// Table E.2: (codeword, length, run). +pub const RUN_E_1: [(u16, u8, u8); 62] = [ + (0b0, 1, 1), + (0b1111100001, 10, 32), + (0b100, 3, 2), + (0b1111100010, 10, 33), + (0b1010, 4, 3), + (0b1111100011, 10, 34), + (0b1011, 4, 4), + (0b1111100100, 10, 35), + (0b11000, 5, 5), + (0b1111100101, 10, 36), + (0b11001, 5, 6), + (0b1111100110, 10, 37), + (0b11010, 5, 7), + (0b1111100111, 10, 38), + (0b11011, 5, 8), + (0b1111101000, 10, 39), + (0b111000, 6, 9), + (0b1111101001, 10, 40), + (0b111001, 6, 10), + (0b1111101010, 10, 41), + (0b111010, 6, 11), + (0b1111101011, 10, 42), + (0b111011, 6, 12), + (0b1111101100, 10, 43), + (0b1111000, 7, 13), + (0b1111101101, 10, 44), + (0b11110010, 8, 14), + (0b1111101110, 10, 45), + (0b111100110, 9, 15), + (0b1111101111, 10, 46), + (0b111100111, 9, 16), + (0b1111110000, 10, 47), + (0b111101000, 9, 18), + (0b1111110001, 10, 48), + (0b111101001, 9, 20), + (0b1111110010, 10, 49), + (0b1111010100, 10, 17), + (0b1111110011, 10, 50), + (0b1111010101, 10, 19), + (0b1111110100, 10, 51), + (0b1111010110, 10, 21), + (0b1111110101, 10, 52), + (0b1111010111, 10, 22), + (0b1111110110, 10, 53), + (0b1111011000, 10, 23), + (0b1111110111, 10, 54), + (0b1111011001, 10, 24), + (0b1111111000, 10, 55), + (0b1111011010, 10, 25), + (0b1111111001, 10, 56), + (0b1111011011, 10, 26), + (0b1111111010, 10, 57), + (0b1111011100, 10, 27), + (0b1111111011, 10, 58), + (0b1111011101, 10, 28), + (0b1111111100, 10, 59), + (0b1111011110, 10, 29), + (0b1111111101, 10, 60), + (0b1111011111, 10, 30), + (0b1111111110, 10, 61), + (0b1111100000, 10, 31), + (0b1111111111, 10, 62), +]; +/// Table E.3: (codeword, length) indexed by the DC size (eta). +pub const DC_E_1: [(u16, u8); 14] = [ + (0b1010, 4), + (0b111110, 6), + (0b1011, 4), + (0b1100, 4), + (0b1101, 4), + (0b000, 3), + (0b001, 3), + (0b010, 3), + (0b011, 3), + (0b100, 3), + (0b1110, 4), + (0b11110, 5), + (0b1111110, 7), + (0b1111111, 7), +]; + +/// Table E.4: (codeword, length, amplitude (0 = EOB), Frun, Findex). +pub const AMP_E_4: [(u16, u8, u8, u8, u8); 257] = [ + (0b00, 2, 1, 0, 0), + (0b1111111011101, 13, 15, 1, 0), + (0b01, 2, 1, 1, 0), + (0b1111111011110, 13, 16, 1, 0), + (0b100, 3, 2, 0, 0), + (0b11111110111110, 14, 42, 0, 0), + (0b101, 3, 0, 0, 0), + (0b11111110111111, 14, 43, 0, 0), + (0b1100, 4, 3, 0, 0), + (0b11111111000000, 14, 44, 0, 0), + (0b11010, 5, 4, 0, 0), + (0b11111111000001, 14, 45, 0, 0), + (0b11011, 5, 2, 1, 0), + (0b11111111000010, 14, 46, 0, 0), + (0b111000, 6, 5, 0, 0), + (0b11111111000011, 14, 47, 0, 0), + (0b111001, 6, 6, 0, 0), + (0b11111111000100, 14, 48, 0, 0), + (0b111010, 6, 7, 0, 0), + (0b11111111000101, 14, 49, 0, 0), + (0b111011, 6, 3, 1, 0), + (0b11111111000110, 14, 50, 0, 0), + (0b1111000, 7, 8, 0, 0), + (0b11111111000111, 14, 51, 0, 0), + (0b1111001, 7, 9, 0, 0), + (0b11111111001000, 14, 52, 0, 0), + (0b11110100, 8, 10, 0, 0), + (0b11111111001001, 14, 17, 1, 0), + (0b11110101, 8, 11, 0, 0), + (0b11111111001010, 14, 18, 1, 0), + (0b11110110, 8, 12, 0, 0), + (0b11111111001011, 14, 19, 1, 0), + (0b11110111, 8, 4, 1, 0), + (0b11111111001100, 14, 20, 1, 0), + (0b11111000, 8, 5, 1, 0), + (0b11111111001101, 14, 21, 1, 0), + (0b111110010, 9, 13, 0, 0), + (0b111111110011100, 15, 53, 0, 0), + (0b111110011, 9, 14, 0, 0), + (0b111111110011101, 15, 54, 0, 0), + (0b111110100, 9, 15, 0, 0), + (0b111111110011110, 15, 55, 0, 0), + (0b111110101, 9, 16, 0, 0), + (0b111111110011111, 15, 56, 0, 0), + (0b111110110, 9, 6, 1, 0), + (0b111111110100000, 15, 57, 0, 0), + (0b1111101110, 10, 17, 0, 0), + (0b111111110100001, 15, 58, 0, 0), + (0b1111101111, 10, 18, 0, 0), + (0b111111110100010, 15, 59, 0, 0), + (0b1111110000, 10, 19, 0, 0), + (0b111111110100011, 15, 60, 0, 0), + (0b1111110001, 10, 20, 0, 0), + (0b111111110100100, 15, 61, 0, 0), + (0b1111110010, 10, 21, 0, 0), + (0b111111110100101, 15, 64, 0, 0), + (0b1111110011, 10, 7, 1, 0), + (0b111111110100110, 15, 1, 0, 1), + (0b11111101000, 11, 22, 0, 0), + (0b111111110100111, 15, 22, 1, 0), + (0b11111101001, 11, 23, 0, 0), + (0b111111110101000, 15, 23, 1, 0), + (0b11111101010, 11, 24, 0, 0), + (0b111111110101001, 15, 24, 1, 0), + (0b11111101011, 11, 25, 0, 0), + (0b111111110101010, 15, 25, 1, 0), + (0b11111101100, 11, 26, 0, 0), + (0b111111110101011, 15, 26, 1, 0), + (0b11111101101, 11, 27, 0, 0), + (0b111111110101100, 15, 27, 1, 0), + (0b11111101110, 11, 8, 1, 0), + (0b1111111101011010, 16, 62, 0, 0), + (0b11111101111, 11, 9, 1, 0), + (0b1111111101011011, 16, 63, 0, 0), + (0b111111100000, 12, 28, 0, 0), + (0b1111111101011100, 16, 2, 0, 1), + (0b111111100001, 12, 29, 0, 0), + (0b1111111101011101, 16, 3, 0, 1), + (0b111111100010, 12, 30, 0, 0), + (0b1111111101011110, 16, 4, 0, 1), + (0b111111100011, 12, 31, 0, 0), + (0b1111111101011111, 16, 5, 0, 1), + (0b111111100100, 12, 32, 0, 0), + (0b1111111101100000, 16, 6, 0, 1), + (0b111111100101, 12, 33, 0, 0), + (0b1111111101100001, 16, 7, 0, 1), + (0b111111100110, 12, 34, 0, 0), + (0b1111111101100010, 16, 8, 0, 1), + (0b111111100111, 12, 10, 1, 0), + (0b1111111101100011, 16, 9, 0, 1), + (0b111111101000, 12, 11, 1, 0), + (0b1111111101100100, 16, 10, 0, 1), + (0b111111101001, 12, 12, 1, 0), + (0b1111111101100101, 16, 11, 0, 1), + (0b1111111010100, 13, 35, 0, 0), + (0b1111111101100110, 16, 12, 0, 1), + (0b1111111010101, 13, 36, 0, 0), + (0b1111111101100111, 16, 13, 0, 1), + (0b1111111010110, 13, 37, 0, 0), + (0b1111111101101000, 16, 14, 0, 1), + (0b1111111010111, 13, 38, 0, 0), + (0b1111111101101001, 16, 15, 0, 1), + (0b1111111011000, 13, 39, 0, 0), + (0b1111111101101010, 16, 16, 0, 1), + (0b1111111011001, 13, 40, 0, 0), + (0b1111111101101011, 16, 17, 0, 1), + (0b1111111011010, 13, 41, 0, 0), + (0b1111111101101100, 16, 18, 0, 1), + (0b1111111011011, 13, 13, 1, 0), + (0b1111111101101101, 16, 19, 0, 1), + (0b1111111011100, 13, 14, 1, 0), + (0b1111111101101110, 16, 20, 0, 1), + (0b1111111101101111, 16, 21, 0, 1), + (0b1111111110101101, 16, 46, 1, 0), + (0b1111111101110000, 16, 22, 0, 1), + (0b1111111110101110, 16, 47, 1, 0), + (0b1111111101110001, 16, 23, 0, 1), + (0b1111111110101111, 16, 48, 1, 0), + (0b1111111101110010, 16, 24, 0, 1), + (0b1111111110110000, 16, 49, 1, 0), + (0b1111111101110011, 16, 25, 0, 1), + (0b1111111110110001, 16, 50, 1, 0), + (0b1111111101110100, 16, 26, 0, 1), + (0b1111111110110010, 16, 51, 1, 0), + (0b1111111101110101, 16, 27, 0, 1), + (0b1111111110110011, 16, 52, 1, 0), + (0b1111111101110110, 16, 28, 0, 1), + (0b1111111110110100, 16, 53, 1, 0), + (0b1111111101110111, 16, 29, 0, 1), + (0b1111111110110101, 16, 54, 1, 0), + (0b1111111101111000, 16, 30, 0, 1), + (0b1111111110110110, 16, 55, 1, 0), + (0b1111111101111001, 16, 31, 0, 1), + (0b1111111110110111, 16, 56, 1, 0), + (0b1111111101111010, 16, 32, 0, 1), + (0b1111111110111000, 16, 57, 1, 0), + (0b1111111101111011, 16, 33, 0, 1), + (0b1111111110111001, 16, 58, 1, 0), + (0b1111111101111100, 16, 34, 0, 1), + (0b1111111110111010, 16, 59, 1, 0), + (0b1111111101111101, 16, 35, 0, 1), + (0b1111111110111011, 16, 60, 1, 0), + (0b1111111101111110, 16, 36, 0, 1), + (0b1111111110111100, 16, 61, 1, 0), + (0b1111111101111111, 16, 37, 0, 1), + (0b1111111110111101, 16, 62, 1, 0), + (0b1111111110000000, 16, 38, 0, 1), + (0b1111111110111110, 16, 63, 1, 0), + (0b1111111110000001, 16, 39, 0, 1), + (0b1111111110111111, 16, 64, 1, 0), + (0b1111111110000010, 16, 40, 0, 1), + (0b1111111111000000, 16, 1, 1, 1), + (0b1111111110000011, 16, 41, 0, 1), + (0b1111111111000001, 16, 2, 1, 1), + (0b1111111110000100, 16, 42, 0, 1), + (0b1111111111000010, 16, 3, 1, 1), + (0b1111111110000101, 16, 43, 0, 1), + (0b1111111111000011, 16, 4, 1, 1), + (0b1111111110000110, 16, 44, 0, 1), + (0b1111111111000100, 16, 5, 1, 1), + (0b1111111110000111, 16, 45, 0, 1), + (0b1111111111000101, 16, 6, 1, 1), + (0b1111111110001000, 16, 46, 0, 1), + (0b1111111111000110, 16, 7, 1, 1), + (0b1111111110001001, 16, 47, 0, 1), + (0b1111111111000111, 16, 8, 1, 1), + (0b1111111110001010, 16, 48, 0, 1), + (0b1111111111001000, 16, 9, 1, 1), + (0b1111111110001011, 16, 49, 0, 1), + (0b1111111111001001, 16, 10, 1, 1), + (0b1111111110001100, 16, 50, 0, 1), + (0b1111111111001010, 16, 11, 1, 1), + (0b1111111110001101, 16, 51, 0, 1), + (0b1111111111001011, 16, 12, 1, 1), + (0b1111111110001110, 16, 52, 0, 1), + (0b1111111111001100, 16, 13, 1, 1), + (0b1111111110001111, 16, 53, 0, 1), + (0b1111111111001101, 16, 14, 1, 1), + (0b1111111110010000, 16, 54, 0, 1), + (0b1111111111001110, 16, 15, 1, 1), + (0b1111111110010001, 16, 55, 0, 1), + (0b1111111111001111, 16, 16, 1, 1), + (0b1111111110010010, 16, 56, 0, 1), + (0b1111111111010000, 16, 17, 1, 1), + (0b1111111110010011, 16, 57, 0, 1), + (0b1111111111010001, 16, 18, 1, 1), + (0b1111111110010100, 16, 58, 0, 1), + (0b1111111111010010, 16, 19, 1, 1), + (0b1111111110010101, 16, 59, 0, 1), + (0b1111111111010011, 16, 20, 1, 1), + (0b1111111110010110, 16, 60, 0, 1), + (0b1111111111010100, 16, 21, 1, 1), + (0b1111111110010111, 16, 61, 0, 1), + (0b1111111111010101, 16, 22, 1, 1), + (0b1111111110011000, 16, 62, 0, 1), + (0b1111111111010110, 16, 23, 1, 1), + (0b1111111110011001, 16, 63, 0, 1), + (0b1111111111010111, 16, 24, 1, 1), + (0b1111111110011010, 16, 64, 0, 1), + (0b1111111111011000, 16, 25, 1, 1), + (0b1111111110011011, 16, 28, 1, 0), + (0b1111111111011001, 16, 26, 1, 1), + (0b1111111110011100, 16, 29, 1, 0), + (0b1111111111011010, 16, 27, 1, 1), + (0b1111111110011101, 16, 30, 1, 0), + (0b1111111111011011, 16, 28, 1, 1), + (0b1111111110011110, 16, 31, 1, 0), + (0b1111111111011100, 16, 29, 1, 1), + (0b1111111110011111, 16, 32, 1, 0), + (0b1111111111011101, 16, 30, 1, 1), + (0b1111111110100000, 16, 33, 1, 0), + (0b1111111111011110, 16, 31, 1, 1), + (0b1111111110100001, 16, 34, 1, 0), + (0b1111111111011111, 16, 32, 1, 1), + (0b1111111110100010, 16, 35, 1, 0), + (0b1111111111100000, 16, 33, 1, 1), + (0b1111111110100011, 16, 36, 1, 0), + (0b1111111111100001, 16, 34, 1, 1), + (0b1111111110100100, 16, 37, 1, 0), + (0b1111111111100010, 16, 35, 1, 1), + (0b1111111110100101, 16, 38, 1, 0), + (0b1111111111100011, 16, 36, 1, 1), + (0b1111111110100110, 16, 39, 1, 0), + (0b1111111111100100, 16, 37, 1, 1), + (0b1111111110100111, 16, 40, 1, 0), + (0b1111111111100101, 16, 38, 1, 1), + (0b1111111110101000, 16, 41, 1, 0), + (0b1111111111100110, 16, 39, 1, 1), + (0b1111111110101001, 16, 42, 1, 0), + (0b1111111111100111, 16, 40, 1, 1), + (0b1111111110101010, 16, 43, 1, 0), + (0b1111111111101000, 16, 41, 1, 1), + (0b1111111110101011, 16, 44, 1, 0), + (0b1111111111101001, 16, 42, 1, 1), + (0b1111111110101100, 16, 45, 1, 0), + (0b1111111111101010, 16, 43, 1, 1), + (0b1111111111101011, 16, 44, 1, 1), + (0b1111111111110110, 16, 55, 1, 1), + (0b1111111111101100, 16, 45, 1, 1), + (0b1111111111110111, 16, 56, 1, 1), + (0b1111111111101101, 16, 46, 1, 1), + (0b1111111111111000, 16, 57, 1, 1), + (0b1111111111101110, 16, 47, 1, 1), + (0b1111111111111001, 16, 58, 1, 1), + (0b1111111111101111, 16, 48, 1, 1), + (0b1111111111111010, 16, 59, 1, 1), + (0b1111111111110000, 16, 49, 1, 1), + (0b1111111111111011, 16, 60, 1, 1), + (0b1111111111110001, 16, 50, 1, 1), + (0b1111111111111100, 16, 61, 1, 1), + (0b1111111111110010, 16, 51, 1, 1), + (0b1111111111111101, 16, 62, 1, 1), + (0b1111111111110011, 16, 52, 1, 1), + (0b1111111111111110, 16, 63, 1, 1), + (0b1111111111110100, 16, 53, 1, 1), + (0b1111111111111111, 16, 64, 1, 1), + (0b1111111111110101, 16, 54, 1, 1), +]; +/// Table E.5: (codeword, length, run). +pub const RUN_E_4: [(u16, u8, u8); 62] = [ + (0b0, 1, 1), + (0b1111100001, 10, 25), + (0b100, 3, 2), + (0b1111100010, 10, 26), + (0b1010, 4, 3), + (0b1111100011, 10, 27), + (0b1011, 4, 4), + (0b1111100100, 10, 28), + (0b11000, 5, 5), + (0b1111100101, 10, 29), + (0b11001, 5, 6), + (0b1111100110, 10, 30), + (0b11010, 5, 7), + (0b1111100111, 10, 31), + (0b110110, 6, 8), + (0b1111101000, 10, 32), + (0b110111, 6, 9), + (0b1111101001, 10, 33), + (0b111000, 6, 10), + (0b1111101010, 10, 34), + (0b111001, 6, 11), + (0b1111101011, 10, 35), + (0b111010, 6, 12), + (0b1111101100, 10, 36), + (0b1110110, 7, 13), + (0b1111101101, 10, 37), + (0b1110111, 7, 14), + (0b1111101110, 10, 38), + (0b11110000, 8, 15), + (0b1111101111, 10, 39), + (0b111100010, 9, 16), + (0b1111110000, 10, 40), + (0b111100011, 9, 17), + (0b1111110001, 10, 41), + (0b111100100, 9, 18), + (0b1111110010, 10, 42), + (0b111100101, 9, 19), + (0b1111110011, 10, 43), + (0b111100110, 9, 20), + (0b1111110100, 10, 44), + (0b111100111, 9, 21), + (0b1111110101, 10, 45), + (0b111101000, 9, 53), + (0b1111110110, 10, 46), + (0b111101001, 9, 57), + (0b1111110111, 10, 47), + (0b111101010, 9, 58), + (0b1111111000, 10, 48), + (0b111101011, 9, 59), + (0b1111111001, 10, 49), + (0b111101100, 9, 60), + (0b1111111010, 10, 50), + (0b111101101, 9, 61), + (0b1111111011, 10, 51), + (0b111101110, 9, 62), + (0b1111111100, 10, 52), + (0b1111011110, 10, 22), + (0b1111111101, 10, 54), + (0b1111011111, 10, 23), + (0b1111111110, 10, 55), + (0b1111100000, 10, 24), + (0b1111111111, 10, 56), +]; +/// Table E.6: (codeword, length) indexed by the DC size (eta). +pub const DC_E_4: [(u16, u8); 12] = [ + (0b000, 3), + (0b1100, 4), + (0b1101, 4), + (0b001, 3), + (0b010, 3), + (0b011, 3), + (0b100, 3), + (0b101, 3), + (0b1110, 4), + (0b11110, 5), + (0b111110, 6), + (0b111111, 6), +]; + +/// Table E.7: (codeword, length, amplitude (0 = EOB), Frun, Findex). +pub const AMP_E_7: [(u16, u8, u8, u8, u8); 257] = [ + (0b00, 2, 1, 0, 0), + (0b111111011111, 12, 45, 0, 0), + (0b01, 2, 1, 1, 0), + (0b111111100000, 12, 46, 0, 0), + (0b100, 3, 2, 0, 0), + (0b111111100001, 12, 47, 0, 0), + (0b1010, 4, 3, 0, 0), + (0b111111100010, 12, 48, 0, 0), + (0b1011, 4, 0, 0, 0), + (0b111111100011, 12, 12, 1, 0), + (0b11000, 5, 4, 0, 0), + (0b111111100100, 12, 13, 1, 0), + (0b11001, 5, 5, 0, 0), + (0b111111100101, 12, 14, 1, 0), + (0b11010, 5, 2, 1, 0), + (0b1111111001100, 13, 49, 0, 0), + (0b110110, 6, 6, 0, 0), + (0b1111111001101, 13, 50, 0, 0), + (0b110111, 6, 7, 0, 0), + (0b1111111001110, 13, 51, 0, 0), + (0b111000, 6, 8, 0, 0), + (0b1111111001111, 13, 52, 0, 0), + (0b111001, 6, 3, 1, 0), + (0b1111111010000, 13, 53, 0, 0), + (0b1110100, 7, 9, 0, 0), + (0b1111111010001, 13, 54, 0, 0), + (0b1110101, 7, 10, 0, 0), + (0b1111111010010, 13, 55, 0, 0), + (0b1110110, 7, 11, 0, 0), + (0b1111111010011, 13, 56, 0, 0), + (0b1110111, 7, 4, 1, 0), + (0b1111111010100, 13, 57, 0, 0), + (0b11110000, 8, 12, 0, 0), + (0b1111111010101, 13, 58, 0, 0), + (0b11110001, 8, 13, 0, 0), + (0b1111111010110, 13, 59, 0, 0), + (0b11110010, 8, 14, 0, 0), + (0b1111111010111, 13, 60, 0, 0), + (0b11110011, 8, 15, 0, 0), + (0b1111111011000, 13, 61, 0, 0), + (0b11110100, 8, 16, 0, 0), + (0b1111111011001, 13, 15, 1, 0), + (0b11110101, 8, 5, 1, 0), + (0b1111111011010, 13, 16, 1, 0), + (0b111101100, 9, 17, 0, 0), + (0b1111111011011, 13, 17, 1, 0), + (0b111101101, 9, 18, 0, 0), + (0b1111111011100, 13, 18, 1, 0), + (0b111101110, 9, 19, 0, 0), + (0b11111110111010, 14, 62, 0, 0), + (0b111101111, 9, 20, 0, 0), + (0b11111110111011, 14, 63, 0, 0), + (0b111110000, 9, 21, 0, 0), + (0b11111110111100, 14, 64, 0, 0), + (0b111110001, 9, 22, 0, 0), + (0b11111110111101, 14, 1, 0, 1), + (0b111110010, 9, 6, 1, 0), + (0b11111110111110, 14, 2, 0, 1), + (0b111110011, 9, 7, 1, 0), + (0b11111110111111, 14, 3, 0, 1), + (0b1111101000, 10, 23, 0, 0), + (0b11111111000000, 14, 4, 0, 1), + (0b1111101001, 10, 24, 0, 0), + (0b11111111000001, 14, 5, 0, 1), + (0b1111101010, 10, 25, 0, 0), + (0b11111111000010, 14, 6, 0, 1), + (0b1111101011, 10, 26, 0, 0), + (0b11111111000011, 14, 7, 0, 1), + (0b1111101100, 10, 27, 0, 0), + (0b11111111000100, 14, 8, 0, 1), + (0b1111101101, 10, 28, 0, 0), + (0b11111111000101, 14, 9, 0, 1), + (0b1111101110, 10, 29, 0, 0), + (0b11111111000110, 14, 10, 0, 1), + (0b1111101111, 10, 8, 1, 0), + (0b11111111000111, 14, 11, 0, 1), + (0b1111110000, 10, 9, 1, 0), + (0b11111111001000, 14, 12, 0, 1), + (0b11111100010, 11, 30, 0, 0), + (0b11111111001001, 14, 13, 0, 1), + (0b11111100011, 11, 31, 0, 0), + (0b11111111001010, 14, 14, 0, 1), + (0b11111100100, 11, 32, 0, 0), + (0b11111111001011, 14, 15, 0, 1), + (0b11111100101, 11, 33, 0, 0), + (0b11111111001100, 14, 16, 0, 1), + (0b11111100110, 11, 34, 0, 0), + (0b11111111001101, 14, 19, 1, 0), + (0b11111100111, 11, 35, 0, 0), + (0b11111111001110, 14, 20, 1, 0), + (0b11111101000, 11, 36, 0, 0), + (0b11111111001111, 14, 21, 1, 0), + (0b11111101001, 11, 37, 0, 0), + (0b11111111010000, 14, 22, 1, 0), + (0b11111101010, 11, 10, 1, 0), + (0b11111111010001, 14, 23, 1, 0), + (0b11111101011, 11, 11, 1, 0), + (0b11111111010010, 14, 24, 1, 0), + (0b111111011000, 12, 38, 0, 0), + (0b111111110100110, 15, 17, 0, 1), + (0b111111011001, 12, 39, 0, 0), + (0b111111110100111, 15, 18, 0, 1), + (0b111111011010, 12, 40, 0, 0), + (0b111111110101000, 15, 19, 0, 1), + (0b111111011011, 12, 41, 0, 0), + (0b111111110101001, 15, 20, 0, 1), + (0b111111011100, 12, 42, 0, 0), + (0b111111110101010, 15, 21, 0, 1), + (0b111111011101, 12, 43, 0, 0), + (0b111111110101011, 15, 22, 0, 1), + (0b111111011110, 12, 44, 0, 0), + (0b111111110101100, 15, 23, 0, 1), + (0b111111110101101, 15, 24, 0, 1), + (0b1111111110101101, 16, 46, 1, 0), + (0b111111110101110, 15, 25, 0, 1), + (0b1111111110101110, 16, 47, 1, 0), + (0b111111110101111, 15, 26, 0, 1), + (0b1111111110101111, 16, 48, 1, 0), + (0b111111110110000, 15, 27, 0, 1), + (0b1111111110110000, 16, 49, 1, 0), + (0b111111110110001, 15, 28, 0, 1), + (0b1111111110110001, 16, 50, 1, 0), + (0b111111110110010, 15, 29, 0, 1), + (0b1111111110110010, 16, 51, 1, 0), + (0b111111110110011, 15, 30, 0, 1), + (0b1111111110110011, 16, 52, 1, 0), + (0b111111110110100, 15, 31, 0, 1), + (0b1111111110110100, 16, 53, 1, 0), + (0b111111110110101, 15, 32, 0, 1), + (0b1111111110110101, 16, 54, 1, 0), + (0b111111110110110, 15, 33, 0, 1), + (0b1111111110110110, 16, 55, 1, 0), + (0b111111110110111, 15, 34, 0, 1), + (0b1111111110110111, 16, 56, 1, 0), + (0b111111110111000, 15, 35, 0, 1), + (0b1111111110111000, 16, 57, 1, 0), + (0b111111110111001, 15, 36, 0, 1), + (0b1111111110111001, 16, 58, 1, 0), + (0b111111110111010, 15, 37, 0, 1), + (0b1111111110111010, 16, 59, 1, 0), + (0b111111110111011, 15, 40, 0, 1), + (0b1111111110111011, 16, 60, 1, 0), + (0b111111110111100, 15, 25, 1, 0), + (0b1111111110111100, 16, 61, 1, 0), + (0b111111110111101, 15, 26, 1, 0), + (0b1111111110111101, 16, 62, 1, 0), + (0b111111110111110, 15, 27, 1, 0), + (0b1111111110111110, 16, 63, 1, 0), + (0b111111110111111, 15, 28, 1, 0), + (0b1111111110111111, 16, 64, 1, 0), + (0b111111111000000, 15, 29, 1, 0), + (0b1111111111000000, 16, 1, 1, 1), + (0b111111111000001, 15, 30, 1, 0), + (0b1111111111000001, 16, 2, 1, 1), + (0b1111111110000100, 16, 38, 0, 1), + (0b1111111111000010, 16, 3, 1, 1), + (0b1111111110000101, 16, 39, 0, 1), + (0b1111111111000011, 16, 4, 1, 1), + (0b1111111110000110, 16, 41, 0, 1), + (0b1111111111000100, 16, 5, 1, 1), + (0b1111111110000111, 16, 42, 0, 1), + (0b1111111111000101, 16, 6, 1, 1), + (0b1111111110001000, 16, 43, 0, 1), + (0b1111111111000110, 16, 7, 1, 1), + (0b1111111110001001, 16, 44, 0, 1), + (0b1111111111000111, 16, 8, 1, 1), + (0b1111111110001010, 16, 45, 0, 1), + (0b1111111111001000, 16, 9, 1, 1), + (0b1111111110001011, 16, 46, 0, 1), + (0b1111111111001001, 16, 10, 1, 1), + (0b1111111110001100, 16, 47, 0, 1), + (0b1111111111001010, 16, 11, 1, 1), + (0b1111111110001101, 16, 48, 0, 1), + (0b1111111111001011, 16, 12, 1, 1), + (0b1111111110001110, 16, 49, 0, 1), + (0b1111111111001100, 16, 13, 1, 1), + (0b1111111110001111, 16, 50, 0, 1), + (0b1111111111001101, 16, 14, 1, 1), + (0b1111111110010000, 16, 51, 0, 1), + (0b1111111111001110, 16, 15, 1, 1), + (0b1111111110010001, 16, 52, 0, 1), + (0b1111111111001111, 16, 16, 1, 1), + (0b1111111110010010, 16, 53, 0, 1), + (0b1111111111010000, 16, 17, 1, 1), + (0b1111111110010011, 16, 54, 0, 1), + (0b1111111111010001, 16, 18, 1, 1), + (0b1111111110010100, 16, 55, 0, 1), + (0b1111111111010010, 16, 19, 1, 1), + (0b1111111110010101, 16, 56, 0, 1), + (0b1111111111010011, 16, 20, 1, 1), + (0b1111111110010110, 16, 57, 0, 1), + (0b1111111111010100, 16, 21, 1, 1), + (0b1111111110010111, 16, 58, 0, 1), + (0b1111111111010101, 16, 22, 1, 1), + (0b1111111110011000, 16, 59, 0, 1), + (0b1111111111010110, 16, 23, 1, 1), + (0b1111111110011001, 16, 60, 0, 1), + (0b1111111111010111, 16, 24, 1, 1), + (0b1111111110011010, 16, 61, 0, 1), + (0b1111111111011000, 16, 25, 1, 1), + (0b1111111110011011, 16, 62, 0, 1), + (0b1111111111011001, 16, 26, 1, 1), + (0b1111111110011100, 16, 63, 0, 1), + (0b1111111111011010, 16, 27, 1, 1), + (0b1111111110011101, 16, 64, 0, 1), + (0b1111111111011011, 16, 28, 1, 1), + (0b1111111110011110, 16, 31, 1, 0), + (0b1111111111011100, 16, 29, 1, 1), + (0b1111111110011111, 16, 32, 1, 0), + (0b1111111111011101, 16, 30, 1, 1), + (0b1111111110100000, 16, 33, 1, 0), + (0b1111111111011110, 16, 31, 1, 1), + (0b1111111110100001, 16, 34, 1, 0), + (0b1111111111011111, 16, 32, 1, 1), + (0b1111111110100010, 16, 35, 1, 0), + (0b1111111111100000, 16, 33, 1, 1), + (0b1111111110100011, 16, 36, 1, 0), + (0b1111111111100001, 16, 34, 1, 1), + (0b1111111110100100, 16, 37, 1, 0), + (0b1111111111100010, 16, 35, 1, 1), + (0b1111111110100101, 16, 38, 1, 0), + (0b1111111111100011, 16, 36, 1, 1), + (0b1111111110100110, 16, 39, 1, 0), + (0b1111111111100100, 16, 37, 1, 1), + (0b1111111110100111, 16, 40, 1, 0), + (0b1111111111100101, 16, 38, 1, 1), + (0b1111111110101000, 16, 41, 1, 0), + (0b1111111111100110, 16, 39, 1, 1), + (0b1111111110101001, 16, 42, 1, 0), + (0b1111111111100111, 16, 40, 1, 1), + (0b1111111110101010, 16, 43, 1, 0), + (0b1111111111101000, 16, 41, 1, 1), + (0b1111111110101011, 16, 44, 1, 0), + (0b1111111111101001, 16, 42, 1, 1), + (0b1111111110101100, 16, 45, 1, 0), + (0b1111111111101010, 16, 43, 1, 1), + (0b1111111111101011, 16, 44, 1, 1), + (0b1111111111110110, 16, 55, 1, 1), + (0b1111111111101100, 16, 45, 1, 1), + (0b1111111111110111, 16, 56, 1, 1), + (0b1111111111101101, 16, 46, 1, 1), + (0b1111111111111000, 16, 57, 1, 1), + (0b1111111111101110, 16, 47, 1, 1), + (0b1111111111111001, 16, 58, 1, 1), + (0b1111111111101111, 16, 48, 1, 1), + (0b1111111111111010, 16, 59, 1, 1), + (0b1111111111110000, 16, 49, 1, 1), + (0b1111111111111011, 16, 60, 1, 1), + (0b1111111111110001, 16, 50, 1, 1), + (0b1111111111111100, 16, 61, 1, 1), + (0b1111111111110010, 16, 51, 1, 1), + (0b1111111111111101, 16, 62, 1, 1), + (0b1111111111110011, 16, 52, 1, 1), + (0b1111111111111110, 16, 63, 1, 1), + (0b1111111111110100, 16, 53, 1, 1), + (0b1111111111111111, 16, 64, 1, 1), + (0b1111111111110101, 16, 54, 1, 1), +]; +/// Table E.8: (codeword, length, run). +pub const RUN_E_7: [(u16, u8, u8); 62] = [ + (0b0, 1, 1), + (0b1111100001, 10, 32), + (0b100, 3, 2), + (0b1111100010, 10, 33), + (0b1010, 4, 3), + (0b1111100011, 10, 34), + (0b1011, 4, 4), + (0b1111100100, 10, 35), + (0b11000, 5, 5), + (0b1111100101, 10, 36), + (0b11001, 5, 6), + (0b1111100110, 10, 37), + (0b11010, 5, 7), + (0b1111100111, 10, 38), + (0b11011, 5, 8), + (0b1111101000, 10, 39), + (0b111000, 6, 9), + (0b1111101001, 10, 40), + (0b111001, 6, 10), + (0b1111101010, 10, 41), + (0b111010, 6, 11), + (0b1111101011, 10, 42), + (0b111011, 6, 12), + (0b1111101100, 10, 43), + (0b1111000, 7, 13), + (0b1111101101, 10, 44), + (0b11110010, 8, 14), + (0b1111101110, 10, 45), + (0b111100110, 9, 15), + (0b1111101111, 10, 46), + (0b111100111, 9, 16), + (0b1111110000, 10, 47), + (0b111101000, 9, 20), + (0b1111110001, 10, 48), + (0b111101001, 9, 21), + (0b1111110010, 10, 49), + (0b1111010100, 10, 17), + (0b1111110011, 10, 50), + (0b1111010101, 10, 18), + (0b1111110100, 10, 51), + (0b1111010110, 10, 19), + (0b1111110101, 10, 52), + (0b1111010111, 10, 22), + (0b1111110110, 10, 53), + (0b1111011000, 10, 23), + (0b1111110111, 10, 54), + (0b1111011001, 10, 24), + (0b1111111000, 10, 55), + (0b1111011010, 10, 25), + (0b1111111001, 10, 56), + (0b1111011011, 10, 26), + (0b1111111010, 10, 57), + (0b1111011100, 10, 27), + (0b1111111011, 10, 58), + (0b1111011101, 10, 28), + (0b1111111100, 10, 59), + (0b1111011110, 10, 29), + (0b1111111101, 10, 60), + (0b1111011111, 10, 30), + (0b1111111110, 10, 61), + (0b1111100000, 10, 31), + (0b1111111111, 10, 62), +]; +/// Table E.9: (codeword, length) indexed by the DC size (eta). +pub const DC_E_7: [(u16, u8); 12] = [ + (0b000, 3), + (0b1100, 4), + (0b1101, 4), + (0b001, 3), + (0b010, 3), + (0b011, 3), + (0b100, 3), + (0b101, 3), + (0b1110, 4), + (0b11110, 5), + (0b111110, 6), + (0b111111, 6), +]; + +/// Table E.10: (codeword, length, amplitude (0 = EOB), Frun, Findex). +pub const AMP_E_10: [(u16, u8, u8, u8, u8); 257] = [ + (0b00, 2, 1, 0, 0), + (0b111111011011, 12, 47, 0, 0), + (0b01, 2, 1, 1, 0), + (0b111111011100, 12, 48, 0, 0), + (0b100, 3, 2, 0, 0), + (0b111111011101, 12, 49, 0, 0), + (0b1010, 4, 3, 0, 0), + (0b111111011110, 12, 50, 0, 0), + (0b1011, 4, 0, 0, 0), + (0b111111011111, 12, 51, 0, 0), + (0b11000, 5, 4, 0, 0), + (0b111111100000, 12, 52, 0, 0), + (0b11001, 5, 5, 0, 0), + (0b111111100001, 12, 11, 1, 0), + (0b11010, 5, 2, 1, 0), + (0b111111100010, 12, 12, 1, 0), + (0b110110, 6, 6, 0, 0), + (0b111111100011, 12, 13, 1, 0), + (0b110111, 6, 7, 0, 0), + (0b1111111001000, 13, 53, 0, 0), + (0b111000, 6, 8, 0, 0), + (0b1111111001001, 13, 54, 0, 0), + (0b111001, 6, 3, 1, 0), + (0b1111111001010, 13, 55, 0, 0), + (0b1110100, 7, 9, 0, 0), + (0b1111111001011, 13, 56, 0, 0), + (0b1110101, 7, 10, 0, 0), + (0b1111111001100, 13, 57, 0, 0), + (0b1110110, 7, 11, 0, 0), + (0b1111111001101, 13, 58, 0, 0), + (0b1110111, 7, 4, 1, 0), + (0b1111111001110, 13, 59, 0, 0), + (0b11110000, 8, 12, 0, 0), + (0b1111111001111, 13, 60, 0, 0), + (0b11110001, 8, 13, 0, 0), + (0b1111111010000, 13, 61, 0, 0), + (0b11110010, 8, 14, 0, 0), + (0b1111111010001, 13, 62, 0, 0), + (0b11110011, 8, 15, 0, 0), + (0b1111111010010, 13, 63, 0, 0), + (0b11110100, 8, 16, 0, 0), + (0b1111111010011, 13, 64, 0, 0), + (0b11110101, 8, 5, 1, 0), + (0b1111111010100, 13, 1, 0, 1), + (0b111101100, 9, 17, 0, 0), + (0b1111111010101, 13, 2, 0, 1), + (0b111101101, 9, 18, 0, 0), + (0b1111111010110, 13, 3, 0, 1), + (0b111101110, 9, 19, 0, 0), + (0b1111111010111, 13, 4, 0, 1), + (0b111101111, 9, 20, 0, 0), + (0b1111111011000, 13, 5, 0, 1), + (0b111110000, 9, 21, 0, 0), + (0b1111111011001, 13, 14, 1, 0), + (0b111110001, 9, 22, 0, 0), + (0b1111111011010, 13, 15, 1, 0), + (0b111110010, 9, 6, 1, 0), + (0b1111111011011, 13, 16, 1, 0), + (0b1111100110, 10, 23, 0, 0), + (0b1111111011100, 13, 17, 1, 0), + (0b1111100111, 10, 24, 0, 0), + (0b11111110111010, 14, 6, 0, 1), + (0b1111101000, 10, 25, 0, 0), + (0b11111110111011, 14, 7, 0, 1), + (0b1111101001, 10, 26, 0, 0), + (0b11111110111100, 14, 8, 0, 1), + (0b1111101010, 10, 27, 0, 0), + (0b11111110111101, 14, 9, 0, 1), + (0b1111101011, 10, 28, 0, 0), + (0b11111110111110, 14, 10, 0, 1), + (0b1111101100, 10, 29, 0, 0), + (0b11111110111111, 14, 11, 0, 1), + (0b1111101101, 10, 7, 1, 0), + (0b11111111000000, 14, 12, 0, 1), + (0b1111101110, 10, 8, 1, 0), + (0b11111111000001, 14, 13, 0, 1), + (0b11111011110, 11, 30, 0, 0), + (0b11111111000010, 14, 14, 0, 1), + (0b11111011111, 11, 31, 0, 0), + (0b11111111000011, 14, 15, 0, 1), + (0b11111100000, 11, 32, 0, 0), + (0b11111111000100, 14, 16, 0, 1), + (0b11111100001, 11, 33, 0, 0), + (0b11111111000101, 14, 17, 0, 1), + (0b11111100010, 11, 34, 0, 0), + (0b11111111000110, 14, 18, 0, 1), + (0b11111100011, 11, 35, 0, 0), + (0b11111111000111, 14, 19, 0, 1), + (0b11111100100, 11, 36, 0, 0), + (0b11111111001000, 14, 20, 0, 1), + (0b11111100101, 11, 37, 0, 0), + (0b11111111001001, 14, 21, 0, 1), + (0b11111100110, 11, 38, 0, 0), + (0b11111111001010, 14, 22, 0, 1), + (0b11111100111, 11, 39, 0, 0), + (0b11111111001011, 14, 23, 0, 1), + (0b11111101000, 11, 9, 1, 0), + (0b11111111001100, 14, 24, 0, 1), + (0b11111101001, 11, 10, 1, 0), + (0b11111111001101, 14, 25, 0, 1), + (0b111111010100, 12, 40, 0, 0), + (0b11111111001110, 14, 26, 0, 1), + (0b111111010101, 12, 41, 0, 0), + (0b11111111001111, 14, 18, 1, 0), + (0b111111010110, 12, 42, 0, 0), + (0b11111111010000, 14, 19, 1, 0), + (0b111111010111, 12, 43, 0, 0), + (0b11111111010001, 14, 20, 1, 0), + (0b111111011000, 12, 44, 0, 0), + (0b11111111010010, 14, 21, 1, 0), + (0b111111011001, 12, 45, 0, 0), + (0b111111110100110, 15, 27, 0, 1), + (0b111111011010, 12, 46, 0, 0), + (0b111111110100111, 15, 28, 0, 1), + (0b111111110101000, 15, 29, 0, 1), + (0b1111111110101111, 16, 48, 1, 0), + (0b111111110101001, 15, 30, 0, 1), + (0b1111111110110000, 16, 49, 1, 0), + (0b111111110101010, 15, 31, 0, 1), + (0b1111111110110001, 16, 50, 1, 0), + (0b111111110101011, 15, 32, 0, 1), + (0b1111111110110010, 16, 51, 1, 0), + (0b111111110101100, 15, 33, 0, 1), + (0b1111111110110011, 16, 52, 1, 0), + (0b111111110101101, 15, 34, 0, 1), + (0b1111111110110100, 16, 53, 1, 0), + (0b111111110101110, 15, 35, 0, 1), + (0b1111111110110101, 16, 54, 1, 0), + (0b111111110101111, 15, 36, 0, 1), + (0b1111111110110110, 16, 55, 1, 0), + (0b111111110110000, 15, 37, 0, 1), + (0b1111111110110111, 16, 56, 1, 0), + (0b111111110110001, 15, 38, 0, 1), + (0b1111111110111000, 16, 57, 1, 0), + (0b111111110110010, 15, 39, 0, 1), + (0b1111111110111001, 16, 58, 1, 0), + (0b111111110110011, 15, 40, 0, 1), + (0b1111111110111010, 16, 59, 1, 0), + (0b111111110110100, 15, 41, 0, 1), + (0b1111111110111011, 16, 60, 1, 0), + (0b111111110110101, 15, 42, 0, 1), + (0b1111111110111100, 16, 61, 1, 0), + (0b111111110110110, 15, 43, 0, 1), + (0b1111111110111101, 16, 62, 1, 0), + (0b111111110110111, 15, 44, 0, 1), + (0b1111111110111110, 16, 63, 1, 0), + (0b111111110111000, 15, 45, 0, 1), + (0b1111111110111111, 16, 64, 1, 0), + (0b111111110111001, 15, 46, 0, 1), + (0b1111111111000000, 16, 1, 1, 1), + (0b111111110111010, 15, 47, 0, 1), + (0b1111111111000001, 16, 2, 1, 1), + (0b111111110111011, 15, 48, 0, 1), + (0b1111111111000010, 16, 3, 1, 1), + (0b111111110111100, 15, 49, 0, 1), + (0b1111111111000011, 16, 4, 1, 1), + (0b111111110111101, 15, 50, 0, 1), + (0b1111111111000100, 16, 5, 1, 1), + (0b111111110111110, 15, 51, 0, 1), + (0b1111111111000101, 16, 6, 1, 1), + (0b111111110111111, 15, 52, 0, 1), + (0b1111111111000110, 16, 7, 1, 1), + (0b111111111000000, 15, 53, 0, 1), + (0b1111111111000111, 16, 8, 1, 1), + (0b111111111000001, 15, 55, 0, 1), + (0b1111111111001000, 16, 9, 1, 1), + (0b111111111000010, 15, 56, 0, 1), + (0b1111111111001001, 16, 10, 1, 1), + (0b111111111000011, 15, 22, 1, 0), + (0b1111111111001010, 16, 11, 1, 1), + (0b111111111000100, 15, 23, 1, 0), + (0b1111111111001011, 16, 12, 1, 1), + (0b111111111000101, 15, 24, 1, 0), + (0b1111111111001100, 16, 13, 1, 1), + (0b111111111000110, 15, 25, 1, 0), + (0b1111111111001101, 16, 14, 1, 1), + (0b111111111000111, 15, 26, 1, 0), + (0b1111111111001110, 16, 15, 1, 1), + (0b111111111001000, 15, 27, 1, 0), + (0b1111111111001111, 16, 16, 1, 1), + (0b1111111110010010, 16, 54, 0, 1), + (0b1111111111010000, 16, 17, 1, 1), + (0b1111111110010011, 16, 57, 0, 1), + (0b1111111111010001, 16, 18, 1, 1), + (0b1111111110010100, 16, 58, 0, 1), + (0b1111111111010010, 16, 19, 1, 1), + (0b1111111110010101, 16, 59, 0, 1), + (0b1111111111010011, 16, 20, 1, 1), + (0b1111111110010110, 16, 60, 0, 1), + (0b1111111111010100, 16, 21, 1, 1), + (0b1111111110010111, 16, 61, 0, 1), + (0b1111111111010101, 16, 22, 1, 1), + (0b1111111110011000, 16, 62, 0, 1), + (0b1111111111010110, 16, 23, 1, 1), + (0b1111111110011001, 16, 63, 0, 1), + (0b1111111111010111, 16, 24, 1, 1), + (0b1111111110011010, 16, 64, 0, 1), + (0b1111111111011000, 16, 25, 1, 1), + (0b1111111110011011, 16, 28, 1, 0), + (0b1111111111011001, 16, 26, 1, 1), + (0b1111111110011100, 16, 29, 1, 0), + (0b1111111111011010, 16, 27, 1, 1), + (0b1111111110011101, 16, 30, 1, 0), + (0b1111111111011011, 16, 28, 1, 1), + (0b1111111110011110, 16, 31, 1, 0), + (0b1111111111011100, 16, 29, 1, 1), + (0b1111111110011111, 16, 32, 1, 0), + (0b1111111111011101, 16, 30, 1, 1), + (0b1111111110100000, 16, 33, 1, 0), + (0b1111111111011110, 16, 31, 1, 1), + (0b1111111110100001, 16, 34, 1, 0), + (0b1111111111011111, 16, 32, 1, 1), + (0b1111111110100010, 16, 35, 1, 0), + (0b1111111111100000, 16, 33, 1, 1), + (0b1111111110100011, 16, 36, 1, 0), + (0b1111111111100001, 16, 34, 1, 1), + (0b1111111110100100, 16, 37, 1, 0), + (0b1111111111100010, 16, 35, 1, 1), + (0b1111111110100101, 16, 38, 1, 0), + (0b1111111111100011, 16, 36, 1, 1), + (0b1111111110100110, 16, 39, 1, 0), + (0b1111111111100100, 16, 37, 1, 1), + (0b1111111110100111, 16, 40, 1, 0), + (0b1111111111100101, 16, 38, 1, 1), + (0b1111111110101000, 16, 41, 1, 0), + (0b1111111111100110, 16, 39, 1, 1), + (0b1111111110101001, 16, 42, 1, 0), + (0b1111111111100111, 16, 40, 1, 1), + (0b1111111110101010, 16, 43, 1, 0), + (0b1111111111101000, 16, 41, 1, 1), + (0b1111111110101011, 16, 44, 1, 0), + (0b1111111111101001, 16, 42, 1, 1), + (0b1111111110101100, 16, 45, 1, 0), + (0b1111111111101010, 16, 43, 1, 1), + (0b1111111110101101, 16, 46, 1, 0), + (0b1111111111101011, 16, 44, 1, 1), + (0b1111111110101110, 16, 47, 1, 0), + (0b1111111111101100, 16, 45, 1, 1), + (0b1111111111101101, 16, 46, 1, 1), + (0b1111111111110111, 16, 56, 1, 1), + (0b1111111111101110, 16, 47, 1, 1), + (0b1111111111111000, 16, 57, 1, 1), + (0b1111111111101111, 16, 48, 1, 1), + (0b1111111111111001, 16, 58, 1, 1), + (0b1111111111110000, 16, 49, 1, 1), + (0b1111111111111010, 16, 59, 1, 1), + (0b1111111111110001, 16, 50, 1, 1), + (0b1111111111111011, 16, 60, 1, 1), + (0b1111111111110010, 16, 51, 1, 1), + (0b1111111111111100, 16, 61, 1, 1), + (0b1111111111110011, 16, 52, 1, 1), + (0b1111111111111101, 16, 62, 1, 1), + (0b1111111111110100, 16, 53, 1, 1), + (0b1111111111111110, 16, 63, 1, 1), + (0b1111111111110101, 16, 54, 1, 1), + (0b1111111111111111, 16, 64, 1, 1), + (0b1111111111110110, 16, 55, 1, 1), +]; +/// Table E.11: (codeword, length, run). +pub const RUN_E_10: [(u16, u8, u8); 62] = [ + (0b0, 1, 1), + (0b1111100001, 10, 32), + (0b100, 3, 2), + (0b1111100010, 10, 33), + (0b101, 3, 3), + (0b1111100011, 10, 34), + (0b1100, 4, 4), + (0b1111100100, 10, 35), + (0b11010, 5, 5), + (0b1111100101, 10, 36), + (0b11011, 5, 6), + (0b1111100110, 10, 37), + (0b11100, 5, 7), + (0b1111100111, 10, 38), + (0b111010, 6, 8), + (0b1111101000, 10, 39), + (0b1110110, 7, 9), + (0b1111101001, 10, 40), + (0b1110111, 7, 10), + (0b1111101010, 10, 41), + (0b1111000, 7, 11), + (0b1111101011, 10, 42), + (0b11110010, 8, 12), + (0b1111101100, 10, 43), + (0b111100110, 9, 13), + (0b1111101101, 10, 44), + (0b111100111, 9, 14), + (0b1111101110, 10, 45), + (0b1111010000, 10, 15), + (0b1111101111, 10, 46), + (0b1111010001, 10, 16), + (0b1111110000, 10, 47), + (0b1111010010, 10, 17), + (0b1111110001, 10, 48), + (0b1111010011, 10, 18), + (0b1111110010, 10, 49), + (0b1111010100, 10, 19), + (0b1111110011, 10, 50), + (0b1111010101, 10, 20), + (0b1111110100, 10, 51), + (0b1111010110, 10, 21), + (0b1111110101, 10, 52), + (0b1111010111, 10, 22), + (0b1111110110, 10, 53), + (0b1111011000, 10, 23), + (0b1111110111, 10, 54), + (0b1111011001, 10, 24), + (0b1111111000, 10, 55), + (0b1111011010, 10, 25), + (0b1111111001, 10, 56), + (0b1111011011, 10, 26), + (0b1111111010, 10, 57), + (0b1111011100, 10, 27), + (0b1111111011, 10, 58), + (0b1111011101, 10, 28), + (0b1111111100, 10, 59), + (0b1111011110, 10, 29), + (0b1111111101, 10, 60), + (0b1111011111, 10, 30), + (0b1111111110, 10, 61), + (0b1111100000, 10, 31), + (0b1111111111, 10, 62), +]; +/// Table E.12: (codeword, length) indexed by the DC size (eta). +pub const DC_E_10: [(u16, u8); 14] = [ + (0b1010, 4), + (0b111110, 6), + (0b1011, 4), + (0b1100, 4), + (0b1101, 4), + (0b000, 3), + (0b001, 3), + (0b010, 3), + (0b011, 3), + (0b100, 3), + (0b1110, 4), + (0b11110, 5), + (0b1111110, 7), + (0b1111111, 7), +]; + +/// Table E.13: (codeword, length, amplitude (0 = EOB), Frun, Findex). +pub const AMP_E_13: [(u16, u8, u8, u8, u8); 257] = [ + (0b00, 2, 1, 0, 0), + (0b111111011000, 12, 45, 0, 0), + (0b01, 2, 1, 1, 0), + (0b111111011001, 12, 46, 0, 0), + (0b100, 3, 2, 0, 0), + (0b111111011010, 12, 47, 0, 0), + (0b1010, 4, 3, 0, 0), + (0b111111011011, 12, 48, 0, 0), + (0b1011, 4, 0, 0, 0), + (0b111111011100, 12, 49, 0, 0), + (0b11000, 5, 4, 0, 0), + (0b111111011101, 12, 50, 0, 0), + (0b11001, 5, 5, 0, 0), + (0b111111011110, 12, 51, 0, 0), + (0b11010, 5, 2, 1, 0), + (0b111111011111, 12, 52, 0, 0), + (0b110110, 6, 6, 0, 0), + (0b111111100000, 12, 12, 1, 0), + (0b110111, 6, 7, 0, 0), + (0b111111100001, 12, 13, 1, 0), + (0b111000, 6, 8, 0, 0), + (0b111111100010, 12, 14, 1, 0), + (0b111001, 6, 3, 1, 0), + (0b1111111000110, 13, 53, 0, 0), + (0b1110100, 7, 9, 0, 0), + (0b1111111000111, 13, 54, 0, 0), + (0b1110101, 7, 10, 0, 0), + (0b1111111001000, 13, 55, 0, 0), + (0b1110110, 7, 11, 0, 0), + (0b1111111001001, 13, 56, 0, 0), + (0b1110111, 7, 4, 1, 0), + (0b1111111001010, 13, 57, 0, 0), + (0b11110000, 8, 12, 0, 0), + (0b1111111001011, 13, 58, 0, 0), + (0b11110001, 8, 13, 0, 0), + (0b1111111001100, 13, 59, 0, 0), + (0b11110010, 8, 14, 0, 0), + (0b1111111001101, 13, 60, 0, 0), + (0b11110011, 8, 15, 0, 0), + (0b1111111001110, 13, 61, 0, 0), + (0b11110100, 8, 16, 0, 0), + (0b1111111001111, 13, 62, 0, 0), + (0b11110101, 8, 5, 1, 0), + (0b1111111010000, 13, 63, 0, 0), + (0b111101100, 9, 17, 0, 0), + (0b1111111010001, 13, 64, 0, 0), + (0b111101101, 9, 18, 0, 0), + (0b1111111010010, 13, 1, 0, 1), + (0b111101110, 9, 19, 0, 0), + (0b1111111010011, 13, 2, 0, 1), + (0b111101111, 9, 20, 0, 0), + (0b1111111010100, 13, 3, 0, 1), + (0b111110000, 9, 21, 0, 0), + (0b1111111010101, 13, 4, 0, 1), + (0b111110001, 9, 6, 1, 0), + (0b1111111010110, 13, 5, 0, 1), + (0b1111100100, 10, 22, 0, 0), + (0b1111111010111, 13, 6, 0, 1), + (0b1111100101, 10, 23, 0, 0), + (0b1111111011000, 13, 7, 0, 1), + (0b1111100110, 10, 24, 0, 0), + (0b1111111011001, 13, 8, 0, 1), + (0b1111100111, 10, 25, 0, 0), + (0b1111111011010, 13, 15, 1, 0), + (0b1111101000, 10, 26, 0, 0), + (0b1111111011011, 13, 16, 1, 0), + (0b1111101001, 10, 27, 0, 0), + (0b1111111011100, 13, 17, 1, 0), + (0b1111101010, 10, 28, 0, 0), + (0b11111110111010, 14, 9, 0, 1), + (0b1111101011, 10, 29, 0, 0), + (0b11111110111011, 14, 10, 0, 1), + (0b1111101100, 10, 7, 1, 0), + (0b11111110111100, 14, 11, 0, 1), + (0b1111101101, 10, 8, 1, 0), + (0b11111110111101, 14, 12, 0, 1), + (0b11111011100, 11, 30, 0, 0), + (0b11111110111110, 14, 13, 0, 1), + (0b11111011101, 11, 31, 0, 0), + (0b11111110111111, 14, 14, 0, 1), + (0b11111011110, 11, 32, 0, 0), + (0b11111111000000, 14, 15, 0, 1), + (0b11111011111, 11, 33, 0, 0), + (0b11111111000001, 14, 16, 0, 1), + (0b11111100000, 11, 34, 0, 0), + (0b11111111000010, 14, 17, 0, 1), + (0b11111100001, 11, 35, 0, 0), + (0b11111111000011, 14, 18, 0, 1), + (0b11111100010, 11, 36, 0, 0), + (0b11111111000100, 14, 19, 0, 1), + (0b11111100011, 11, 37, 0, 0), + (0b11111111000101, 14, 20, 0, 1), + (0b11111100100, 11, 38, 0, 0), + (0b11111111000110, 14, 21, 0, 1), + (0b11111100101, 11, 39, 0, 0), + (0b11111111000111, 14, 22, 0, 1), + (0b11111100110, 11, 40, 0, 0), + (0b11111111001000, 14, 23, 0, 1), + (0b11111100111, 11, 9, 1, 0), + (0b11111111001001, 14, 24, 0, 1), + (0b11111101000, 11, 10, 1, 0), + (0b11111111001010, 14, 25, 0, 1), + (0b11111101001, 11, 11, 1, 0), + (0b11111111001011, 14, 26, 0, 1), + (0b111111010100, 12, 41, 0, 0), + (0b11111111001100, 14, 27, 0, 1), + (0b111111010101, 12, 42, 0, 0), + (0b11111111001101, 14, 28, 0, 1), + (0b111111010110, 12, 43, 0, 0), + (0b11111111001110, 14, 29, 0, 1), + (0b111111010111, 12, 44, 0, 0), + (0b11111111001111, 14, 18, 1, 0), + (0b11111111010000, 14, 19, 1, 0), + (0b1111111110101101, 16, 46, 1, 0), + (0b11111111010001, 14, 20, 1, 0), + (0b1111111110101110, 16, 47, 1, 0), + (0b11111111010010, 14, 21, 1, 0), + (0b1111111110101111, 16, 48, 1, 0), + (0b11111111010011, 14, 22, 1, 0), + (0b1111111110110000, 16, 49, 1, 0), + (0b111111110101000, 15, 30, 0, 1), + (0b1111111110110001, 16, 50, 1, 0), + (0b111111110101001, 15, 31, 0, 1), + (0b1111111110110010, 16, 51, 1, 0), + (0b111111110101010, 15, 32, 0, 1), + (0b1111111110110011, 16, 52, 1, 0), + (0b111111110101011, 15, 33, 0, 1), + (0b1111111110110100, 16, 53, 1, 0), + (0b111111110101100, 15, 34, 0, 1), + (0b1111111110110101, 16, 54, 1, 0), + (0b111111110101101, 15, 35, 0, 1), + (0b1111111110110110, 16, 55, 1, 0), + (0b111111110101110, 15, 36, 0, 1), + (0b1111111110110111, 16, 56, 1, 0), + (0b111111110101111, 15, 37, 0, 1), + (0b1111111110111000, 16, 57, 1, 0), + (0b111111110110000, 15, 38, 0, 1), + (0b1111111110111001, 16, 58, 1, 0), + (0b111111110110001, 15, 39, 0, 1), + (0b1111111110111010, 16, 59, 1, 0), + (0b111111110110010, 15, 40, 0, 1), + (0b1111111110111011, 16, 60, 1, 0), + (0b111111110110011, 15, 41, 0, 1), + (0b1111111110111100, 16, 61, 1, 0), + (0b111111110110100, 15, 42, 0, 1), + (0b1111111110111101, 16, 62, 1, 0), + (0b111111110110101, 15, 43, 0, 1), + (0b1111111110111110, 16, 63, 1, 0), + (0b111111110110110, 15, 44, 0, 1), + (0b1111111110111111, 16, 64, 1, 0), + (0b111111110110111, 15, 45, 0, 1), + (0b1111111111000000, 16, 1, 1, 1), + (0b111111110111000, 15, 46, 0, 1), + (0b1111111111000001, 16, 2, 1, 1), + (0b111111110111001, 15, 47, 0, 1), + (0b1111111111000010, 16, 3, 1, 1), + (0b111111110111010, 15, 48, 0, 1), + (0b1111111111000011, 16, 4, 1, 1), + (0b111111110111011, 15, 49, 0, 1), + (0b1111111111000100, 16, 5, 1, 1), + (0b111111110111100, 15, 50, 0, 1), + (0b1111111111000101, 16, 6, 1, 1), + (0b111111110111101, 15, 51, 0, 1), + (0b1111111111000110, 16, 7, 1, 1), + (0b111111110111110, 15, 52, 0, 1), + (0b1111111111000111, 16, 8, 1, 1), + (0b111111110111111, 15, 53, 0, 1), + (0b1111111111001000, 16, 9, 1, 1), + (0b111111111000000, 15, 54, 0, 1), + (0b1111111111001001, 16, 10, 1, 1), + (0b111111111000001, 15, 55, 0, 1), + (0b1111111111001010, 16, 11, 1, 1), + (0b111111111000010, 15, 56, 0, 1), + (0b1111111111001011, 16, 12, 1, 1), + (0b111111111000011, 15, 57, 0, 1), + (0b1111111111001100, 16, 13, 1, 1), + (0b111111111000100, 15, 58, 0, 1), + (0b1111111111001101, 16, 14, 1, 1), + (0b111111111000101, 15, 23, 1, 0), + (0b1111111111001110, 16, 15, 1, 1), + (0b111111111000110, 15, 24, 1, 0), + (0b1111111111001111, 16, 16, 1, 1), + (0b111111111000111, 15, 25, 1, 0), + (0b1111111111010000, 16, 17, 1, 1), + (0b111111111001000, 15, 26, 1, 0), + (0b1111111111010001, 16, 18, 1, 1), + (0b111111111001001, 15, 27, 1, 0), + (0b1111111111010010, 16, 19, 1, 1), + (0b111111111001010, 15, 28, 1, 0), + (0b1111111111010011, 16, 20, 1, 1), + (0b1111111110010110, 16, 59, 0, 1), + (0b1111111111010100, 16, 21, 1, 1), + (0b1111111110010111, 16, 60, 0, 1), + (0b1111111111010101, 16, 22, 1, 1), + (0b1111111110011000, 16, 61, 0, 1), + (0b1111111111010110, 16, 23, 1, 1), + (0b1111111110011001, 16, 62, 0, 1), + (0b1111111111010111, 16, 24, 1, 1), + (0b1111111110011010, 16, 63, 0, 1), + (0b1111111111011000, 16, 25, 1, 1), + (0b1111111110011011, 16, 64, 0, 1), + (0b1111111111011001, 16, 26, 1, 1), + (0b1111111110011100, 16, 29, 1, 0), + (0b1111111111011010, 16, 27, 1, 1), + (0b1111111110011101, 16, 30, 1, 0), + (0b1111111111011011, 16, 28, 1, 1), + (0b1111111110011110, 16, 31, 1, 0), + (0b1111111111011100, 16, 29, 1, 1), + (0b1111111110011111, 16, 32, 1, 0), + (0b1111111111011101, 16, 30, 1, 1), + (0b1111111110100000, 16, 33, 1, 0), + (0b1111111111011110, 16, 31, 1, 1), + (0b1111111110100001, 16, 34, 1, 0), + (0b1111111111011111, 16, 32, 1, 1), + (0b1111111110100010, 16, 35, 1, 0), + (0b1111111111100000, 16, 33, 1, 1), + (0b1111111110100011, 16, 36, 1, 0), + (0b1111111111100001, 16, 34, 1, 1), + (0b1111111110100100, 16, 37, 1, 0), + (0b1111111111100010, 16, 35, 1, 1), + (0b1111111110100101, 16, 38, 1, 0), + (0b1111111111100011, 16, 36, 1, 1), + (0b1111111110100110, 16, 39, 1, 0), + (0b1111111111100100, 16, 37, 1, 1), + (0b1111111110100111, 16, 40, 1, 0), + (0b1111111111100101, 16, 38, 1, 1), + (0b1111111110101000, 16, 41, 1, 0), + (0b1111111111100110, 16, 39, 1, 1), + (0b1111111110101001, 16, 42, 1, 0), + (0b1111111111100111, 16, 40, 1, 1), + (0b1111111110101010, 16, 43, 1, 0), + (0b1111111111101000, 16, 41, 1, 1), + (0b1111111110101011, 16, 44, 1, 0), + (0b1111111111101001, 16, 42, 1, 1), + (0b1111111110101100, 16, 45, 1, 0), + (0b1111111111101010, 16, 43, 1, 1), + (0b1111111111101011, 16, 44, 1, 1), + (0b1111111111110110, 16, 55, 1, 1), + (0b1111111111101100, 16, 45, 1, 1), + (0b1111111111110111, 16, 56, 1, 1), + (0b1111111111101101, 16, 46, 1, 1), + (0b1111111111111000, 16, 57, 1, 1), + (0b1111111111101110, 16, 47, 1, 1), + (0b1111111111111001, 16, 58, 1, 1), + (0b1111111111101111, 16, 48, 1, 1), + (0b1111111111111010, 16, 59, 1, 1), + (0b1111111111110000, 16, 49, 1, 1), + (0b1111111111111011, 16, 60, 1, 1), + (0b1111111111110001, 16, 50, 1, 1), + (0b1111111111111100, 16, 61, 1, 1), + (0b1111111111110010, 16, 51, 1, 1), + (0b1111111111111101, 16, 62, 1, 1), + (0b1111111111110011, 16, 52, 1, 1), + (0b1111111111111110, 16, 63, 1, 1), + (0b1111111111110100, 16, 53, 1, 1), + (0b1111111111111111, 16, 64, 1, 1), + (0b1111111111110101, 16, 54, 1, 1), +]; +/// Table E.14: (codeword, length, run). +pub const RUN_E_13: [(u16, u8, u8); 62] = [ + (0b0, 1, 1), + (0b1111100001, 10, 32), + (0b100, 3, 2), + (0b1111100010, 10, 33), + (0b101, 3, 3), + (0b1111100011, 10, 34), + (0b1100, 4, 4), + (0b1111100100, 10, 35), + (0b11010, 5, 5), + (0b1111100101, 10, 36), + (0b11011, 5, 6), + (0b1111100110, 10, 37), + (0b11100, 5, 7), + (0b1111100111, 10, 38), + (0b111010, 6, 8), + (0b1111101000, 10, 39), + (0b1110110, 7, 9), + (0b1111101001, 10, 40), + (0b1110111, 7, 10), + (0b1111101010, 10, 41), + (0b1111000, 7, 11), + (0b1111101011, 10, 42), + (0b11110010, 8, 12), + (0b1111101100, 10, 43), + (0b111100110, 9, 13), + (0b1111101101, 10, 44), + (0b111100111, 9, 14), + (0b1111101110, 10, 45), + (0b1111010000, 10, 15), + (0b1111101111, 10, 46), + (0b1111010001, 10, 16), + (0b1111110000, 10, 47), + (0b1111010010, 10, 17), + (0b1111110001, 10, 48), + (0b1111010011, 10, 18), + (0b1111110010, 10, 49), + (0b1111010100, 10, 19), + (0b1111110011, 10, 50), + (0b1111010101, 10, 20), + (0b1111110100, 10, 51), + (0b1111010110, 10, 21), + (0b1111110101, 10, 52), + (0b1111010111, 10, 22), + (0b1111110110, 10, 53), + (0b1111011000, 10, 23), + (0b1111110111, 10, 54), + (0b1111011001, 10, 24), + (0b1111111000, 10, 55), + (0b1111011010, 10, 25), + (0b1111111001, 10, 56), + (0b1111011011, 10, 26), + (0b1111111010, 10, 57), + (0b1111011100, 10, 27), + (0b1111111011, 10, 58), + (0b1111011101, 10, 28), + (0b1111111100, 10, 59), + (0b1111011110, 10, 29), + (0b1111111101, 10, 60), + (0b1111011111, 10, 30), + (0b1111111110, 10, 61), + (0b1111100000, 10, 31), + (0b1111111111, 10, 62), +]; +/// Table E.15: (codeword, length) indexed by the DC size (eta). +pub const DC_E_13: [(u16, u8); 12] = [ + (0b000, 3), + (0b1100, 4), + (0b1101, 4), + (0b001, 3), + (0b010, 3), + (0b011, 3), + (0b100, 3), + (0b101, 3), + (0b1110, 4), + (0b11110, 5), + (0b111110, 6), + (0b111111, 6), +]; + +/// Table E.16: (codeword, length, amplitude (0 = EOB), Frun, Findex). +pub const AMP_E_16: [(u16, u8, u8, u8, u8); 257] = [ + (0b00, 2, 1, 0, 0), + (0b1111111010001, 13, 43, 0, 0), + (0b01, 2, 1, 1, 0), + (0b1111111010010, 13, 44, 0, 0), + (0b100, 3, 2, 0, 0), + (0b1111111010011, 13, 45, 0, 0), + (0b1010, 4, 3, 0, 0), + (0b1111111010100, 13, 46, 0, 0), + (0b1011, 4, 2, 1, 0), + (0b1111111010101, 13, 47, 0, 0), + (0b1100, 4, 0, 0, 0), + (0b1111111010110, 13, 48, 0, 0), + (0b11010, 5, 4, 0, 0), + (0b1111111010111, 13, 49, 0, 0), + (0b11011, 5, 5, 0, 0), + (0b1111111011000, 13, 50, 0, 0), + (0b111000, 6, 6, 0, 0), + (0b1111111011001, 13, 51, 0, 0), + (0b111001, 6, 7, 0, 0), + (0b1111111011010, 13, 52, 0, 0), + (0b111010, 6, 3, 1, 0), + (0b1111111011011, 13, 53, 0, 0), + (0b1110110, 7, 8, 0, 0), + (0b1111111011100, 13, 14, 1, 0), + (0b1110111, 7, 9, 0, 0), + (0b1111111011101, 13, 15, 1, 0), + (0b1111000, 7, 10, 0, 0), + (0b1111111011110, 13, 16, 1, 0), + (0b11110010, 8, 11, 0, 0), + (0b11111110111110, 14, 54, 0, 0), + (0b11110011, 8, 12, 0, 0), + (0b11111110111111, 14, 55, 0, 0), + (0b11110100, 8, 13, 0, 0), + (0b11111111000000, 14, 56, 0, 0), + (0b11110101, 8, 14, 0, 0), + (0b11111111000001, 14, 57, 0, 0), + (0b11110110, 8, 4, 1, 0), + (0b11111111000010, 14, 58, 0, 0), + (0b11110111, 8, 5, 1, 0), + (0b11111111000011, 14, 59, 0, 0), + (0b111110000, 9, 15, 0, 0), + (0b11111111000100, 14, 60, 0, 0), + (0b111110001, 9, 16, 0, 0), + (0b11111111000101, 14, 61, 0, 0), + (0b111110010, 9, 17, 0, 0), + (0b11111111000110, 14, 62, 0, 0), + (0b111110011, 9, 18, 0, 0), + (0b11111111000111, 14, 63, 0, 0), + (0b111110100, 9, 6, 1, 0), + (0b11111111001000, 14, 64, 0, 0), + (0b1111101010, 10, 19, 0, 0), + (0b11111111001001, 14, 1, 0, 1), + (0b1111101011, 10, 20, 0, 0), + (0b11111111001010, 14, 2, 0, 1), + (0b1111101100, 10, 21, 0, 0), + (0b11111111001011, 14, 3, 0, 1), + (0b1111101101, 10, 22, 0, 0), + (0b11111111001100, 14, 17, 1, 0), + (0b1111101110, 10, 23, 0, 0), + (0b11111111001101, 14, 18, 1, 0), + (0b1111101111, 10, 24, 0, 0), + (0b11111111001110, 14, 19, 1, 0), + (0b1111110000, 10, 7, 1, 0), + (0b11111111001111, 14, 20, 1, 0), + (0b1111110001, 10, 8, 1, 0), + (0b111111110100000, 15, 4, 0, 1), + (0b11111100100, 11, 25, 0, 0), + (0b111111110100001, 15, 5, 0, 1), + (0b11111100101, 11, 26, 0, 0), + (0b111111110100010, 15, 6, 0, 1), + (0b11111100110, 11, 27, 0, 0), + (0b111111110100011, 15, 7, 0, 1), + (0b11111100111, 11, 28, 0, 0), + (0b111111110100100, 15, 8, 0, 1), + (0b11111101000, 11, 29, 0, 0), + (0b111111110100101, 15, 9, 0, 1), + (0b11111101001, 11, 30, 0, 0), + (0b111111110100110, 15, 10, 0, 1), + (0b11111101010, 11, 31, 0, 0), + (0b111111110100111, 15, 11, 0, 1), + (0b11111101011, 11, 32, 0, 0), + (0b111111110101000, 15, 12, 0, 1), + (0b11111101100, 11, 9, 1, 0), + (0b111111110101001, 15, 13, 0, 1), + (0b11111101101, 11, 10, 1, 0), + (0b111111110101010, 15, 14, 0, 1), + (0b111111011100, 12, 33, 0, 0), + (0b111111110101011, 15, 15, 0, 1), + (0b111111011101, 12, 34, 0, 0), + (0b111111110101100, 15, 16, 0, 1), + (0b111111011110, 12, 35, 0, 0), + (0b111111110101101, 15, 17, 0, 1), + (0b111111011111, 12, 36, 0, 0), + (0b111111110101110, 15, 18, 0, 1), + (0b111111100000, 12, 37, 0, 0), + (0b111111110101111, 15, 19, 0, 1), + (0b111111100001, 12, 38, 0, 0), + (0b111111110110000, 15, 20, 0, 1), + (0b111111100010, 12, 39, 0, 0), + (0b111111110110001, 15, 21, 0, 1), + (0b111111100011, 12, 40, 0, 0), + (0b111111110110010, 15, 21, 1, 0), + (0b111111100100, 12, 41, 0, 0), + (0b111111110110011, 15, 22, 1, 0), + (0b111111100101, 12, 11, 1, 0), + (0b111111110110100, 15, 23, 1, 0), + (0b111111100110, 12, 12, 1, 0), + (0b111111110110101, 15, 24, 1, 0), + (0b111111100111, 12, 13, 1, 0), + (0b111111110110110, 15, 25, 1, 0), + (0b1111111010000, 13, 42, 0, 0), + (0b1111111101101110, 16, 22, 0, 1), + (0b1111111101101111, 16, 23, 0, 1), + (0b1111111110101101, 16, 46, 1, 0), + (0b1111111101110000, 16, 24, 0, 1), + (0b1111111110101110, 16, 47, 1, 0), + (0b1111111101110001, 16, 25, 0, 1), + (0b1111111110101111, 16, 48, 1, 0), + (0b1111111101110010, 16, 26, 0, 1), + (0b1111111110110000, 16, 49, 1, 0), + (0b1111111101110011, 16, 27, 0, 1), + (0b1111111110110001, 16, 50, 1, 0), + (0b1111111101110100, 16, 28, 0, 1), + (0b1111111110110010, 16, 51, 1, 0), + (0b1111111101110101, 16, 29, 0, 1), + (0b1111111110110011, 16, 52, 1, 0), + (0b1111111101110110, 16, 30, 0, 1), + (0b1111111110110100, 16, 53, 1, 0), + (0b1111111101110111, 16, 31, 0, 1), + (0b1111111110110101, 16, 54, 1, 0), + (0b1111111101111000, 16, 32, 0, 1), + (0b1111111110110110, 16, 55, 1, 0), + (0b1111111101111001, 16, 33, 0, 1), + (0b1111111110110111, 16, 56, 1, 0), + (0b1111111101111010, 16, 34, 0, 1), + (0b1111111110111000, 16, 57, 1, 0), + (0b1111111101111011, 16, 35, 0, 1), + (0b1111111110111001, 16, 58, 1, 0), + (0b1111111101111100, 16, 36, 0, 1), + (0b1111111110111010, 16, 59, 1, 0), + (0b1111111101111101, 16, 37, 0, 1), + (0b1111111110111011, 16, 60, 1, 0), + (0b1111111101111110, 16, 38, 0, 1), + (0b1111111110111100, 16, 61, 1, 0), + (0b1111111101111111, 16, 39, 0, 1), + (0b1111111110111101, 16, 62, 1, 0), + (0b1111111110000000, 16, 40, 0, 1), + (0b1111111110111110, 16, 63, 1, 0), + (0b1111111110000001, 16, 41, 0, 1), + (0b1111111110111111, 16, 64, 1, 0), + (0b1111111110000010, 16, 42, 0, 1), + (0b1111111111000000, 16, 1, 1, 1), + (0b1111111110000011, 16, 43, 0, 1), + (0b1111111111000001, 16, 2, 1, 1), + (0b1111111110000100, 16, 44, 0, 1), + (0b1111111111000010, 16, 3, 1, 1), + (0b1111111110000101, 16, 45, 0, 1), + (0b1111111111000011, 16, 4, 1, 1), + (0b1111111110000110, 16, 46, 0, 1), + (0b1111111111000100, 16, 5, 1, 1), + (0b1111111110000111, 16, 47, 0, 1), + (0b1111111111000101, 16, 6, 1, 1), + (0b1111111110001000, 16, 48, 0, 1), + (0b1111111111000110, 16, 7, 1, 1), + (0b1111111110001001, 16, 49, 0, 1), + (0b1111111111000111, 16, 8, 1, 1), + (0b1111111110001010, 16, 50, 0, 1), + (0b1111111111001000, 16, 9, 1, 1), + (0b1111111110001011, 16, 51, 0, 1), + (0b1111111111001001, 16, 10, 1, 1), + (0b1111111110001100, 16, 52, 0, 1), + (0b1111111111001010, 16, 11, 1, 1), + (0b1111111110001101, 16, 53, 0, 1), + (0b1111111111001011, 16, 12, 1, 1), + (0b1111111110001110, 16, 54, 0, 1), + (0b1111111111001100, 16, 13, 1, 1), + (0b1111111110001111, 16, 55, 0, 1), + (0b1111111111001101, 16, 14, 1, 1), + (0b1111111110010000, 16, 56, 0, 1), + (0b1111111111001110, 16, 15, 1, 1), + (0b1111111110010001, 16, 57, 0, 1), + (0b1111111111001111, 16, 16, 1, 1), + (0b1111111110010010, 16, 58, 0, 1), + (0b1111111111010000, 16, 17, 1, 1), + (0b1111111110010011, 16, 59, 0, 1), + (0b1111111111010001, 16, 18, 1, 1), + (0b1111111110010100, 16, 60, 0, 1), + (0b1111111111010010, 16, 19, 1, 1), + (0b1111111110010101, 16, 61, 0, 1), + (0b1111111111010011, 16, 20, 1, 1), + (0b1111111110010110, 16, 62, 0, 1), + (0b1111111111010100, 16, 21, 1, 1), + (0b1111111110010111, 16, 63, 0, 1), + (0b1111111111010101, 16, 22, 1, 1), + (0b1111111110011000, 16, 64, 0, 1), + (0b1111111111010110, 16, 23, 1, 1), + (0b1111111110011001, 16, 26, 1, 0), + (0b1111111111010111, 16, 24, 1, 1), + (0b1111111110011010, 16, 27, 1, 0), + (0b1111111111011000, 16, 25, 1, 1), + (0b1111111110011011, 16, 28, 1, 0), + (0b1111111111011001, 16, 26, 1, 1), + (0b1111111110011100, 16, 29, 1, 0), + (0b1111111111011010, 16, 27, 1, 1), + (0b1111111110011101, 16, 30, 1, 0), + (0b1111111111011011, 16, 28, 1, 1), + (0b1111111110011110, 16, 31, 1, 0), + (0b1111111111011100, 16, 29, 1, 1), + (0b1111111110011111, 16, 32, 1, 0), + (0b1111111111011101, 16, 30, 1, 1), + (0b1111111110100000, 16, 33, 1, 0), + (0b1111111111011110, 16, 31, 1, 1), + (0b1111111110100001, 16, 34, 1, 0), + (0b1111111111011111, 16, 32, 1, 1), + (0b1111111110100010, 16, 35, 1, 0), + (0b1111111111100000, 16, 33, 1, 1), + (0b1111111110100011, 16, 36, 1, 0), + (0b1111111111100001, 16, 34, 1, 1), + (0b1111111110100100, 16, 37, 1, 0), + (0b1111111111100010, 16, 35, 1, 1), + (0b1111111110100101, 16, 38, 1, 0), + (0b1111111111100011, 16, 36, 1, 1), + (0b1111111110100110, 16, 39, 1, 0), + (0b1111111111100100, 16, 37, 1, 1), + (0b1111111110100111, 16, 40, 1, 0), + (0b1111111111100101, 16, 38, 1, 1), + (0b1111111110101000, 16, 41, 1, 0), + (0b1111111111100110, 16, 39, 1, 1), + (0b1111111110101001, 16, 42, 1, 0), + (0b1111111111100111, 16, 40, 1, 1), + (0b1111111110101010, 16, 43, 1, 0), + (0b1111111111101000, 16, 41, 1, 1), + (0b1111111110101011, 16, 44, 1, 0), + (0b1111111111101001, 16, 42, 1, 1), + (0b1111111110101100, 16, 45, 1, 0), + (0b1111111111101010, 16, 43, 1, 1), + (0b1111111111101011, 16, 44, 1, 1), + (0b1111111111110110, 16, 55, 1, 1), + (0b1111111111101100, 16, 45, 1, 1), + (0b1111111111110111, 16, 56, 1, 1), + (0b1111111111101101, 16, 46, 1, 1), + (0b1111111111111000, 16, 57, 1, 1), + (0b1111111111101110, 16, 47, 1, 1), + (0b1111111111111001, 16, 58, 1, 1), + (0b1111111111101111, 16, 48, 1, 1), + (0b1111111111111010, 16, 59, 1, 1), + (0b1111111111110000, 16, 49, 1, 1), + (0b1111111111111011, 16, 60, 1, 1), + (0b1111111111110001, 16, 50, 1, 1), + (0b1111111111111100, 16, 61, 1, 1), + (0b1111111111110010, 16, 51, 1, 1), + (0b1111111111111101, 16, 62, 1, 1), + (0b1111111111110011, 16, 52, 1, 1), + (0b1111111111111110, 16, 63, 1, 1), + (0b1111111111110100, 16, 53, 1, 1), + (0b1111111111111111, 16, 64, 1, 1), + (0b1111111111110101, 16, 54, 1, 1), +]; +/// Table E.17: (codeword, length, run). +pub const RUN_E_16: [(u16, u8, u8); 62] = [ + (0b0, 1, 1), + (0b1111100001, 10, 32), + (0b100, 3, 2), + (0b1111100010, 10, 33), + (0b101, 3, 3), + (0b1111100011, 10, 34), + (0b1100, 4, 4), + (0b1111100100, 10, 35), + (0b11010, 5, 5), + (0b1111100101, 10, 36), + (0b11011, 5, 6), + (0b1111100110, 10, 37), + (0b11100, 5, 7), + (0b1111100111, 10, 38), + (0b111010, 6, 8), + (0b1111101000, 10, 39), + (0b1110110, 7, 9), + (0b1111101001, 10, 40), + (0b1110111, 7, 10), + (0b1111101010, 10, 41), + (0b1111000, 7, 11), + (0b1111101011, 10, 42), + (0b11110010, 8, 12), + (0b1111101100, 10, 43), + (0b111100110, 9, 13), + (0b1111101101, 10, 44), + (0b111100111, 9, 14), + (0b1111101110, 10, 45), + (0b1111010000, 10, 15), + (0b1111101111, 10, 46), + (0b1111010001, 10, 16), + (0b1111110000, 10, 47), + (0b1111010010, 10, 17), + (0b1111110001, 10, 48), + (0b1111010011, 10, 18), + (0b1111110010, 10, 49), + (0b1111010100, 10, 19), + (0b1111110011, 10, 50), + (0b1111010101, 10, 20), + (0b1111110100, 10, 51), + (0b1111010110, 10, 21), + (0b1111110101, 10, 52), + (0b1111010111, 10, 22), + (0b1111110110, 10, 53), + (0b1111011000, 10, 23), + (0b1111110111, 10, 54), + (0b1111011001, 10, 24), + (0b1111111000, 10, 55), + (0b1111011010, 10, 25), + (0b1111111001, 10, 56), + (0b1111011011, 10, 26), + (0b1111111010, 10, 57), + (0b1111011100, 10, 27), + (0b1111111011, 10, 58), + (0b1111011101, 10, 28), + (0b1111111100, 10, 59), + (0b1111011110, 10, 29), + (0b1111111101, 10, 60), + (0b1111011111, 10, 30), + (0b1111111110, 10, 61), + (0b1111100000, 10, 31), + (0b1111111111, 10, 62), +]; +/// Table E.18: (codeword, length) indexed by the DC size (eta). +pub const DC_E_16: [(u16, u8); 12] = [ + (0b000, 3), + (0b1100, 4), + (0b1101, 4), + (0b001, 3), + (0b010, 3), + (0b011, 3), + (0b100, 3), + (0b101, 3), + (0b1110, 4), + (0b11110, 5), + (0b111110, 6), + (0b111111, 6), +]; diff --git a/crates/dnx/src/tables.rs b/crates/dnx/src/tables.rs new file mode 100644 index 0000000000..67284e024e --- /dev/null +++ b/crates/dnx/src/tables.rs @@ -0,0 +1,301 @@ +//! Compression-ID parameters (Annex C), zig-zag order (Figure 48) and VLC lookup tables built from +//! the Annex E codeword lists in [`crate::spec_tables`]. + +use crate::spec_tables::{ + AMP_E_1, AMP_E_4, AMP_E_7, AMP_E_10, AMP_E_13, AMP_E_16, DC_E_1, DC_E_4, DC_E_7, DC_E_10, DC_E_13, DC_E_16, RUN_E_1, RUN_E_4, RUN_E_7, RUN_E_10, + RUN_E_13, RUN_E_16, W_D_1, W_D_2, W_D_3, W_D_4, W_D_5, W_D_6, W_D_7, W_D_8, W_D_9, W_D_10, W_D_11, +}; +use std::sync::OnceLock; + +/// Bitstream coefficient index `r` → raster position `v*8 + u` (Figure 48). +pub(crate) const ZIGZAG: [u8; 64] = { + // Figure 48 printed as a grid: GRID[v][u] = r + const GRID: [[u8; 8]; 8] = [ + [0, 1, 5, 6, 14, 15, 27, 28], + [2, 4, 7, 13, 16, 26, 29, 42], + [3, 8, 12, 17, 25, 30, 41, 43], + [9, 11, 18, 24, 31, 40, 44, 53], + [10, 19, 23, 32, 39, 45, 52, 54], + [20, 22, 33, 38, 46, 51, 55, 60], + [21, 34, 37, 47, 50, 56, 59, 61], + [35, 36, 48, 49, 57, 58, 62, 63], + ]; + let mut z = [0u8; 64]; + let mut v = 0; + while v < 8 { + let mut u = 0; + while u < 8 { + z[GRID[v][u] as usize] = (v * 8 + u) as u8; + u += 1; + } + v += 1; + } + z +}; + +/// Raster profile of a compression ID. +#[derive(Debug, Clone, Copy, PartialEq, Eq)] +pub(crate) enum Kind { + /// HD profile: fixed raster and frame size (Table C.1). + Hd { width: u16, height: u16, interlaced: bool, depth: u8, frame_size: u32 }, + /// Resolution-independent profile (Table C.2): reference frame size C0 for 8160 macroblocks. + Ri { c0: u32 }, +} + +/// Parameters of one compression ID. +#[derive(Debug, Clone, Copy)] +pub(crate) struct CidInfo { + pub cid: u32, + pub kind: Kind, + /// Quantisation weights (Annex D), `[luma, chroma]`, raster order without the DC entry. + pub weights: &'static [[u8; 63]; 2], + /// VLC table set (index into [`vlc`]): 0 = E.1–E.3, 1 = E.4–E.6, 2 = E.7–E.9, 3 = E.10–E.12, + /// 4 = E.13–E.15, 5 = E.16–E.18. + pub vlc: usize, + /// Inverse quantisation parameter `p` (8.2.7): 8 or 32. + pub p: u32, +} + +const fn hd(cid: u32, kind: Kind, weights: &'static [[u8; 63]; 2], vlc: usize) -> CidInfo { + let p = if cid == 1235 || cid == 1241 || cid == 1250 { 8 } else { 32 }; + CidInfo { cid, kind, weights, vlc, p } +} + +const fn ri(cid: u32, c0: u32, weights: &'static [[u8; 63]; 2], vlc: usize) -> CidInfo { + CidInfo { cid, kind: Kind::Ri { c0 }, weights, vlc, p: 32 } +} + +/// Every compression ID of SMPTE ST 2019-1:2016 (Tables C.1 and C.2). +pub(crate) const CIDS: [CidInfo; 20] = [ + hd(1235, Kind::Hd { width: 1920, height: 1080, interlaced: false, depth: 10, frame_size: 917504 }, &W_D_1, 0), + hd(1237, Kind::Hd { width: 1920, height: 1080, interlaced: false, depth: 8, frame_size: 606208 }, &W_D_2, 1), + hd(1238, Kind::Hd { width: 1920, height: 1080, interlaced: false, depth: 8, frame_size: 917504 }, &W_D_3, 2), + hd(1241, Kind::Hd { width: 1920, height: 1080, interlaced: true, depth: 10, frame_size: 917504 }, &W_D_4, 0), + hd(1242, Kind::Hd { width: 1920, height: 1080, interlaced: true, depth: 8, frame_size: 606208 }, &W_D_5, 1), + hd(1243, Kind::Hd { width: 1920, height: 1080, interlaced: true, depth: 8, frame_size: 917504 }, &W_D_6, 2), + hd(1244, Kind::Hd { width: 1440, height: 1080, interlaced: true, depth: 8, frame_size: 606208 }, &W_D_7, 1), + hd(1250, Kind::Hd { width: 1280, height: 720, interlaced: false, depth: 10, frame_size: 458752 }, &W_D_8, 3), + hd(1251, Kind::Hd { width: 1280, height: 720, interlaced: false, depth: 8, frame_size: 458752 }, &W_D_9, 4), + hd(1252, Kind::Hd { width: 1280, height: 720, interlaced: false, depth: 8, frame_size: 303104 }, &W_D_10, 5), + hd(1253, Kind::Hd { width: 1920, height: 1080, interlaced: false, depth: 8, frame_size: 188416 }, &W_D_2, 1), + hd(1256, Kind::Hd { width: 1920, height: 1080, interlaced: false, depth: 10, frame_size: 1835008 }, &W_D_11, 0), + hd(1258, Kind::Hd { width: 960, height: 720, interlaced: false, depth: 8, frame_size: 212992 }, &W_D_10, 5), + hd(1259, Kind::Hd { width: 1440, height: 1080, interlaced: false, depth: 8, frame_size: 417792 }, &W_D_2, 1), + hd(1260, Kind::Hd { width: 1440, height: 1080, interlaced: true, depth: 8, frame_size: 417792 }, &W_D_7, 1), + ri(1270, 1835008, &W_D_11, 0), + ri(1271, 917504, &W_D_4, 0), + ri(1272, 917504, &W_D_3, 2), + ri(1273, 606208, &W_D_2, 1), + ri(1274, 188416, &W_D_2, 1), +]; + +pub(crate) fn cid_info(cid: u32) -> Option<&'static CidInfo> { + CIDS.iter().find(|c| c.cid == cid) +} + +/// Compressed frame size of an RI raster (equation 7.1 / the `RIsize` pseudo code), without the +/// alpha factor when `alpha` is false. +pub fn ri_frame_size(width: u32, height: u32, c0: u32, alpha: bool) -> u32 { + let nw = width.div_ceil(16) as u64; + let ns = height.div_ceil(16) as u64; + let mut size = c0 as u64 * nw * ns / 8160; + if alpha { + size += size / 2; + } + let rem = size % 4096; + if rem >= 2048 { + size += 4096 - rem; + } else { + size -= rem; + } + size.max(8192) as u32 +} + +/// Two-level VLC lookup table. `primary` is indexed by the next `PRIMARY_BITS` bits; an entry is +/// either a decoded symbol (`len << 16 | value`, len ≤ PRIMARY_BITS) or a link to a secondary +/// table (`LINK | sub_bits << 24 | offset`), indexed by the following `sub_bits` bits. +pub(crate) struct Lut { + pub primary: Vec, + pub secondary: Vec, + pub bits: u32, +} + +pub(crate) const LINK: u32 = 1 << 31; + +impl Lut { + /// `codes`: (codeword, length, value ≤ 0xFFFF). + fn build(codes: &[(u32, u32, u32)], bits: u32) -> Lut { + let max_len = codes.iter().map(|c| c.1).max().unwrap_or(1); + let bits = bits.min(max_len); + let mut primary = vec![0u32; 1 << bits]; + // longest code under each long prefix + let mut sub_len = vec![0u32; 1 << bits]; + for &(code, len, _) in codes { + if len > bits { + let pre = (code >> (len - bits)) as usize; + sub_len[pre] = sub_len[pre].max(len - bits); + } + } + let mut secondary = Vec::new(); + for pre in 0..1usize << bits { + if sub_len[pre] > 0 { + let off = secondary.len() as u32; + secondary.resize(secondary.len() + (1 << sub_len[pre]), 0); + primary[pre] = LINK | sub_len[pre] << 24 | off; + } + } + for &(code, len, value) in codes { + if len <= bits { + let shift = bits - len; + let base = (code << shift) as usize; + for e in &mut primary[base..base + (1 << shift)] { + *e = len << 16 | value; + } + } else { + let pre = (code >> (len - bits)) as usize; + let link = primary[pre]; + let sb = (link >> 24) & 0x7f; + let off = (link & 0xff_ffff) as usize; + let rest_len = len - bits; + let rest = code & ((1 << rest_len) - 1); + let shift = sb - rest_len; + let base = off + ((rest << shift) as usize); + for e in &mut secondary[base..base + (1 << shift)] { + *e = len << 16 | value; + } + } + } + Lut { primary, secondary, bits } + } +} + +/// Decoded amplitude-table symbol value: `amp | frun << 8 | findex << 9 | eob << 10`. +pub(crate) const AMP_FRUN: u32 = 1 << 8; +pub(crate) const AMP_FINDEX: u32 = 1 << 9; +pub(crate) const AMP_EOB: u32 = 1 << 10; + +/// One VLC table set (amplitude, run and DC tables) with decode LUTs and encode maps. +pub(crate) struct VlcSet { + pub amp: Lut, + pub run: Lut, + pub dc: Lut, + /// Encoder: `amp_code[frun][findex][amp-1]` = (codeword, length). + pub amp_code: [[[(u16, u8); 64]; 2]; 2], + pub eob: (u16, u8), + /// Encoder: `run_code[run]` (run 1..=62; index 0 unused). + pub run_code: [(u16, u8); 63], + pub dc_code: Vec<(u16, u8)>, +} + +fn build_set(amp: &[(u16, u8, u8, u8, u8)], run: &[(u16, u8, u8); 62], dc: &[(u16, u8)]) -> VlcSet { + let ac: Vec<(u32, u32, u32)> = amp + .iter() + .map(|&(c, l, a, fr, fi)| { + let v = if a == 0 { AMP_EOB } else { a as u32 | (fr as u32) << 8 | (fi as u32) << 9 }; + (c as u32, l as u32, v) + }) + .collect(); + let rc: Vec<(u32, u32, u32)> = run.iter().map(|&(c, l, r)| (c as u32, l as u32, r as u32)).collect(); + let dcc: Vec<(u32, u32, u32)> = dc.iter().enumerate().map(|(i, &(c, l))| (c as u32, l as u32, i as u32)).collect(); + let mut amp_code = [[[(0u16, 0u8); 64]; 2]; 2]; + let mut eob = (0, 0); + for &(c, l, a, fr, fi) in amp { + if a == 0 { + eob = (c, l); + } else { + amp_code[fr as usize][fi as usize][a as usize - 1] = (c, l); + } + } + let mut run_code = [(0u16, 0u8); 63]; + for &(c, l, r) in run { + run_code[r as usize] = (c, l); + } + VlcSet { amp: Lut::build(&ac, 11), run: Lut::build(&rc, 10), dc: Lut::build(&dcc, 8), amp_code, eob, run_code, dc_code: dc.to_vec() } +} + +/// The VLC table set `i` (see [`CidInfo::vlc`]), built on first use. +pub(crate) fn vlc(i: usize) -> &'static VlcSet { + static SETS: [OnceLock; 6] = [const { OnceLock::new() }; 6]; + SETS[i].get_or_init(|| match i { + 0 => build_set(&_E_1, &RUN_E_1, &DC_E_1), + 1 => build_set(&_E_4, &RUN_E_4, &DC_E_4), + 2 => build_set(&_E_7, &RUN_E_7, &DC_E_7), + 3 => build_set(&_E_10, &RUN_E_10, &DC_E_10), + 4 => build_set(&_E_13, &RUN_E_13, &DC_E_13), + _ => build_set(&_E_16, &RUN_E_16, &DC_E_16), + }) +} + +/// Orthonormal 1-D DCT-II basis: `BASIS[u][x] = C(u)/2 · cos((2x+1)uπ/16)`, `C(0) = 1/√2`. +pub(crate) const BASIS: [[f32; 8]; 8] = [ + [0.353_553_39, 0.353_553_39, 0.353_553_39, 0.353_553_39, 0.353_553_39, 0.353_553_39, 0.353_553_39, 0.353_553_39], + [0.49039264, 0.415_734_8, 0.277_785_12, 0.097_545_16, -0.097_545_16, -0.277_785_12, -0.415_734_8, -0.49039264], + [0.461_939_77, 0.191_341_72, -0.191_341_72, -0.461_939_77, -0.461_939_77, -0.191_341_72, 0.191_341_72, 0.461_939_77], + [0.415_734_8, -0.097_545_16, -0.49039264, -0.277_785_12, 0.277_785_12, 0.49039264, 0.097_545_16, -0.415_734_8], + [0.353_553_39, -0.353_553_39, -0.353_553_39, 0.353_553_39, 0.353_553_39, -0.353_553_39, -0.353_553_39, 0.353_553_39], + [0.277_785_12, -0.49039264, 0.097_545_16, 0.415_734_8, -0.415_734_8, -0.097_545_16, 0.49039264, -0.277_785_12], + [0.191_341_72, -0.461_939_77, 0.461_939_77, -0.191_341_72, -0.191_341_72, 0.461_939_77, -0.461_939_77, 0.191_341_72], + [0.097_545_16, -0.277_785_12, 0.415_734_8, -0.49039264, 0.49039264, -0.415_734_8, 0.277_785_12, -0.097_545_16], +]; + +#[cfg(test)] +mod tests { + use super::*; + + #[test] + fn zigzag_is_permutation() { + let mut seen = [false; 64]; + for &z in &ZIGZAG { + assert!(!seen[z as usize]); + seen[z as usize] = true; + } + assert_eq!(ZIGZAG[1], 1); + assert_eq!(ZIGZAG[2], 8); + assert_eq!(ZIGZAG[63], 63); + } + + #[test] + fn ri_size_matches_examples() { + // 1080p HQ (1272) has 8160 macroblocks: exactly C0 + assert_eq!(ri_frame_size(1920, 1080, 917504, false), 917504); + // tiny rasters clamp to 8192 + assert_eq!(ri_frame_size(16, 16, 188416, false), 8192); + } + + #[test] + fn luts_decode_every_code() { + for i in 0..6 { + let s = vlc(i); + for f in 0..2 { + for x in 0..2 { + for a in 0..64 { + let (c, l) = s.amp_code[f][x][a]; + assert!(l > 0); + let v = lookup(&s.amp, c as u32, l as u32); + assert_eq!(v, (a as u32 + 1) | (f as u32) << 8 | (x as u32) << 9); + } + } + } + assert_eq!(lookup(&s.amp, s.eob.0 as u32, s.eob.1 as u32), AMP_EOB); + for r in 1..63 { + let (c, l) = s.run_code[r]; + assert_eq!(lookup(&s.run, c as u32, l as u32), r as u32); + } + } + } + + fn lookup(t: &Lut, code: u32, len: u32) -> u32 { + // left-align the code in a 32-bit window + let w = code << (32 - len); + let e = t.primary[(w >> (32 - t.bits)) as usize]; + let e = if e & LINK != 0 { + let sb = (e >> 24) & 0x7f; + let off = (e & 0xff_ffff) as usize; + t.secondary[off + ((w << t.bits) >> (32 - sb)) as usize] + } else { + e + }; + assert_eq!(e >> 16, len); + e & 0xffff + } +} diff --git a/crates/dnx/tests/it/common/mod.rs b/crates/dnx/tests/it/common/mod.rs new file mode 100644 index 0000000000..043223fb39 --- /dev/null +++ b/crates/dnx/tests/it/common/mod.rs @@ -0,0 +1,303 @@ +//! Shared helpers for oracle tests. ffmpeg is used only as an external fixture generator and +//! reference decoder (never linked). +#![allow(dead_code)] + +use craft_dnx::{Frame, probe}; +use std::path::{Path, PathBuf}; +use std::process::Command; + +/// ffmpeg, or `None` after printing `SKIPPED` (see `craft_testkit::oracle`). +pub fn ffmpeg() -> Option { + craft_testkit::ffmpeg_or_skip("dnx oracle") +} + +pub fn fixture_dir() -> PathBuf { + craft_testkit::fixtures_dir("dnx") +} + +pub fn run(ff: &Path, args: &[&str]) { + let out = Command::new(ff).args(["-hide_banner", "-loglevel", "error", "-y"]).args(args).output().expect("run ffmpeg"); + assert!(out.status.success(), "ffmpeg {:?} failed: {}", args, String::from_utf8_lossy(&out.stderr)); +} + +/// A DNxHD/DNxHR fixture: a lavfi source encoded by ffmpeg's VC-3 encoder into a MOV. +pub struct Spec { + pub name: &'static str, + pub lavfi: String, + pub frames: u32, + /// Encoder arguments (bitrate / profile, pixel format, flags). + pub enc: Vec<&'static str>, + /// Raw pixel format of the reference decode. + pub pix_fmt: &'static str, +} + +impl Spec { + pub fn new(name: &'static str, lavfi: impl Into, frames: u32, enc: &[&'static str], pix_fmt: &'static str) -> Spec { + Spec { name, lavfi: lavfi.into(), frames, enc: enc.to_vec(), pix_fmt } + } +} + +/// Generate (or reuse) the fixture MOV. +pub fn make(ff: &Path, spec: &Spec) -> PathBuf { + let path = fixture_dir().join(format!("{}.mov", spec.name)); + if !path.exists() { + let tmp = craft_testkit::temp_path(&path); + let frames = spec.frames.to_string(); + let mut args = vec!["-f", "lavfi", "-i", spec.lavfi.as_str(), "-frames:v", frames.as_str(), "-c:v", "dnxhd"]; + args.extend(spec.enc.iter().copied()); + args.extend(["-f", "mov"]); + let t = tmp.to_str().unwrap().to_string(); + args.push(&t); + run(ff, &args); + craft_testkit::fixtures::publish(&tmp, &path).unwrap(); + } + path +} + +/// The coded frames of a MOV (ffmpeg's `data` muxer concatenates packets; split them with the +/// coding-unit sizes from the headers). +pub fn packets(ff: &Path, mov: &Path) -> Vec> { + let out = Command::new(ff) + .args(["-hide_banner", "-loglevel", "error", "-i"]) + .arg(mov) + .args(["-map", "0:v", "-c", "copy", "-f", "data", "-"]) + .output() + .expect("run ffmpeg"); + assert!(out.status.success(), "{}", String::from_utf8_lossy(&out.stderr)); + split_frames(&out.stdout) +} + +pub fn split_frames(mut d: &[u8]) -> Vec> { + let mut v = Vec::new(); + while d.len() >= 0x280 { + let h = probe(d).expect("header"); + let mut n = h.coding_unit_size().expect("CBR") as usize; + if h.interlaced && !h.frame_encoding { + n += probe(&d[n..]).expect("second field").coding_unit_size().unwrap() as usize; + } + v.push(d[..n].to_vec()); + d = &d[n..]; + } + v +} + +/// Reference-decode a MOV with ffmpeg; samples as u16 (8-bit formats widened). +pub fn reference(ff: &Path, mov: &Path, pix_fmt: &str) -> Vec { + let out = Command::new(ff) + .args(["-hide_banner", "-loglevel", "error", "-i"]) + .arg(mov) + .args(["-f", "rawvideo", "-pix_fmt", pix_fmt, "-"]) + .output() + .expect("run ffmpeg"); + assert!(out.status.success(), "{}", String::from_utf8_lossy(&out.stderr)); + if pix_fmt.ends_with("le") { + out.stdout.as_chunks::<2>().0.iter().map(|c| u16::from_le_bytes([c[0], c[1]])).collect() + } else { + out.stdout.iter().map(|&b| b as u16).collect() + } +} + +#[derive(Default, Clone, Copy, Debug)] +pub struct Stats { + pub max: u32, + pub sum: u64, + pub n: u64, + pub exact: u64, + /// Samples off by more than 1. + pub over1: u64, +} + +impl Stats { + pub fn add(&mut self, a: &[u16], b: &[u16]) { + assert_eq!(a.len(), b.len()); + for (&x, &y) in a.iter().zip(b) { + let d = (x as i32 - y as i32).unsigned_abs(); + self.max = self.max.max(d); + self.sum += d as u64; + self.n += 1; + self.exact += (d == 0) as u64; + self.over1 += (d > 1) as u64; + } + } + pub fn merge(&mut self, o: &Stats) { + self.max = self.max.max(o.max); + self.sum += o.sum; + self.n += o.n; + self.exact += o.exact; + self.over1 += o.over1; + } + pub fn mean(&self) -> f64 { + self.sum as f64 / self.n.max(1) as f64 + } + pub fn exact_pct(&self) -> f64 { + 100.0 * self.exact as f64 / self.n.max(1) as f64 + } +} + +/// Planes in the order of ffmpeg's planar raw output. +pub fn planes(f: &Frame, pix_fmt: &str) -> Vec { + // RGB frames hold G, B, R in y, cb, cr: the same order as `gbrp` + let _ = pix_fmt; + let order: Vec<&Vec> = vec![&f.y, &f.cb, &f.cr]; + order.into_iter().chain(f.alpha.as_ref()).flat_map(|p| p.iter().copied()).collect() +} + +/// Samples per frame of a raw reference image. +pub fn frame_samples(f: &Frame) -> usize { + let n = f.width as usize * f.height as usize; + let c = f.chroma_width() as usize * f.chroma_height() as usize; + n + 2 * c + if f.alpha.is_some() { n } else { 0 } +} + +pub fn psnr(a: &[u16], b: &[u16], peak: f64) -> f64 { + let mse: f64 = a.iter().zip(b).map(|(&x, &y)| (x as f64 - y as f64).powi(2)).sum::() / a.len() as f64; + if mse == 0.0 { 99.0 } else { 10.0 * (peak * peak / mse).log10() } +} + +fn bx(kind: &[u8; 4], payload: &[u8]) -> Vec { + let mut v = Vec::with_capacity(payload.len() + 8); + v.extend_from_slice(&((payload.len() + 8) as u32).to_be_bytes()); + v.extend_from_slice(kind); + v.extend_from_slice(payload); + v +} +const MATRIX: [u32; 9] = [0x10000, 0, 0, 0, 0x10000, 0, 0, 0, 0x4000_0000]; +/// Minimal QuickTime writer (test-only): one video track, all samples in one chunk. +pub fn write_mov(path: &Path, fourcc: &[u8; 4], width: u16, height: u16, depth: u16, frames: &[Vec]) { + let n = frames.len() as u32; + let be32 = |v: u32| v.to_be_bytes(); + let ftyp = bx(b"ftyp", &[b"qt ".as_slice(), &be32(0x200), b"qt "].concat()); + let mdat_payload: Vec = frames.concat(); + let data_off = ftyp.len() as u32 + 8; + let mdat = bx(b"mdat", &mdat_payload); + let matrix: Vec = MATRIX.iter().flat_map(|v| v.to_be_bytes()).collect(); + + let mut mvhd = vec![0u8; 4 + 8]; + mvhd.extend_from_slice(&be32(25)); + mvhd.extend_from_slice(&be32(n)); + mvhd.extend_from_slice(&be32(0x10000)); + mvhd.extend_from_slice(&[1, 0]); + mvhd.extend_from_slice(&[0; 10]); + mvhd.extend_from_slice(&matrix); + mvhd.extend_from_slice(&[0; 24]); + mvhd.extend_from_slice(&be32(2)); + + let mut tkhd = vec![0, 0, 0, 0xf]; + tkhd.extend_from_slice(&[0; 8]); + tkhd.extend_from_slice(&be32(1)); + tkhd.extend_from_slice(&[0; 4]); + tkhd.extend_from_slice(&be32(n)); + tkhd.extend_from_slice(&[0; 8]); + tkhd.extend_from_slice(&[0; 8]); // layer, alternate group, volume, reserved + tkhd.extend_from_slice(&matrix); + tkhd.extend_from_slice(&be32((width as u32) << 16)); + tkhd.extend_from_slice(&be32((height as u32) << 16)); + + let mut mdhd = vec![0u8; 4 + 8]; + mdhd.extend_from_slice(&be32(25)); + mdhd.extend_from_slice(&be32(n)); + mdhd.extend_from_slice(&[0; 4]); + let hdlr = [&[0u8; 4][..], b"mhlr", b"vide", &[0; 12], &[0]].concat(); + let vmhd = [0u8, 0, 0, 1, 0, 0, 0, 0, 0, 0, 0, 0]; + let dref = bx(b"dref", &[&[0u8; 4][..], &be32(1), &bx(b"alis", &[0, 0, 0, 1])].concat()); + let dinf = bx(b"dinf", &dref); + + let mut entry = vec![0u8; 6]; + entry.extend_from_slice(&[0, 1]); + entry.extend_from_slice(&[0; 4]); // version, revision + entry.extend_from_slice(b"fmcr"); + entry.extend_from_slice(&be32(0)); + entry.extend_from_slice(&be32(0x400)); + entry.extend_from_slice(&width.to_be_bytes()); + entry.extend_from_slice(&height.to_be_bytes()); + entry.extend_from_slice(&be32(72 << 16)); + entry.extend_from_slice(&be32(72 << 16)); + entry.extend_from_slice(&be32(0)); + entry.extend_from_slice(&[0, 1]); + entry.extend_from_slice(&[0; 32]); + entry.extend_from_slice(&depth.to_be_bytes()); + entry.extend_from_slice(&[0xff, 0xff]); + let stsd = bx(b"stsd", &[&[0u8; 4][..], &be32(1), &bx(fourcc, &entry)].concat()); + let stts = bx(b"stts", &[&[0u8; 4][..], &be32(1), &be32(n), &be32(1)].concat()); + let stsc = bx(b"stsc", &[&[0u8; 4][..], &be32(1), &be32(1), &be32(n), &be32(1)].concat()); + let mut stsz = vec![0u8; 4]; + stsz.extend_from_slice(&be32(0)); + stsz.extend_from_slice(&be32(n)); + for f in frames { + stsz.extend_from_slice(&be32(f.len() as u32)); + } + let stsz = bx(b"stsz", &stsz); + let stco = bx(b"stco", &[&[0u8; 4][..], &be32(1), &be32(data_off)].concat()); + let stbl = bx(b"stbl", &[stsd, stts, stsc, stsz, stco].concat()); + let minf = bx(b"minf", &[bx(b"vmhd", &vmhd), dinf, stbl].concat()); + let mdia = bx(b"mdia", &[bx(b"mdhd", &mdhd), bx(b"hdlr", &hdlr), minf].concat()); + let trak = bx(b"trak", &[bx(b"tkhd", &tkhd), mdia].concat()); + let moov = bx(b"moov", &[bx(b"mvhd", &mvhd), trak].concat()); + std::fs::write(path, [ftyp, mdat, moov].concat()).unwrap(); +} + +/// Decode a MOV with ffmpeg (failing on any decode error); raw samples as u16. +pub fn ffmpeg_decode(ff: &Path, mov: &Path, pix_fmt: &str) -> Vec { + let out = Command::new(ff) + .args(["-hide_banner", "-loglevel", "error", "-xerror", "-i"]) + .arg(mov) + .args(["-f", "rawvideo", "-pix_fmt", pix_fmt, "-"]) + .output() + .expect("run ffmpeg"); + let err = String::from_utf8_lossy(&out.stderr).to_string(); + assert!(out.status.success() && err.trim().is_empty(), "ffmpeg failed: {err}"); + if pix_fmt.ends_with("le") { + out.stdout.as_chunks::<2>().0.iter().map(|c| u16::from_le_bytes([c[0], c[1]])).collect() + } else { + out.stdout.iter().map(|&b| b as u16).collect() + } +} + +/// Render a lavfi source to raw planar frames with ffmpeg and load them as [`Frame`]s. +pub struct SourceFrameSpec<'a> { + pub name: &'a str, + pub lavfi: &'a str, + pub frames: u32, + pub width: u32, + pub height: u32, + pub chroma: craft_dnx::ChromaFormat, + pub depth: u8, +} + +pub fn source_frames(ff: &Path, spec: SourceFrameSpec<'_>) -> Vec { + let SourceFrameSpec { name, lavfi, frames, width: w, height: h, chroma, depth } = spec; + let pix = match (chroma, depth) { + (craft_dnx::ChromaFormat::Yuv422, 8) => "yuv422p", + (craft_dnx::ChromaFormat::Yuv422, 10) => "yuv422p10le", + (craft_dnx::ChromaFormat::Yuv422, _) => "yuv422p12le", + (_, 10) => "yuv444p10le", + _ => "yuv444p12le", + }; + let path = fixture_dir().join(format!("{name}.{pix}.raw")); + if !path.exists() { + let tmp = craft_testkit::temp_path(&path); + let f = frames.to_string(); + run(ff, &["-f", "lavfi", "-i", lavfi, "-frames:v", &f, "-f", "rawvideo", "-pix_fmt", pix, tmp.to_str().unwrap()]); + craft_testkit::fixtures::publish(&tmp, &path).unwrap(); + } + let bytes = std::fs::read(&path).unwrap(); + let s: Vec = if depth == 8 { + bytes.iter().map(|&b| b as u16).collect() + } else { + bytes.as_chunks::<2>().0.iter().map(|c| u16::from_le_bytes([c[0], c[1]])).collect() + }; + let proto = Frame::new(w, h, chroma, depth, false); + let per = frame_samples(&proto); + assert_eq!(s.len(), per * frames as usize); + s.chunks_exact(per) + .map(|p| { + let mut f = proto.clone(); + let n = f.y.len(); + let c = f.cb.len(); + f.y.copy_from_slice(&p[..n]); + f.cb.copy_from_slice(&p[n..n + c]); + f.cr.copy_from_slice(&p[n + c..n + 2 * c]); + f + }) + .collect() +} diff --git a/crates/dnx/tests/it/fuzz_hostile.rs b/crates/dnx/tests/it/fuzz_hostile.rs new file mode 100644 index 0000000000..3559a4ad14 --- /dev/null +++ b/crates/dnx/tests/it/fuzz_hostile.rs @@ -0,0 +1,183 @@ +//! Hostile-input robustness: every header byte set to extreme values, every bit flipped, +//! truncation at (nearly) every length and random mutation of encoder-made frames must never +//! panic or run away (errors are fine). No ffmpeg needed. + +use std::panic::{AssertUnwindSafe, catch_unwind}; + +use craft_dnx::{ChromaFormat, DecodeOptions, Encoder, EncoderConfig, Frame, Profile, decode_frame_with, probe}; + +struct Rng(u64); +impl Rng { + fn next(&mut self) -> u64 { + self.0 ^= self.0 << 13; + self.0 ^= self.0 >> 7; + self.0 ^= self.0 << 17; + self.0 + } + fn below(&mut self, n: usize) -> usize { + (self.next() % n.max(1) as u64) as usize + } +} + +fn seeds() -> Vec> { + let mut v = Vec::new(); + for (profile, depth, w, h) in [ + (Profile::Lb, 8, 64u32, 48u32), + (Profile::Hq, 8, 48, 32), + (Profile::Hqx, 10, 64, 32), + (Profile::Hqx, 12, 48, 32), + (Profile::R444, 10, 48, 32), + ] { + let mut cfg = EncoderConfig::new(profile, w, h); + cfg.bit_depth = depth; + let chroma = cfg.chroma(); + let mut f = Frame::new(w, h, chroma, depth, false); + for (i, s) in f.y.iter_mut().enumerate() { + *s = ((16 + (i * 37) % 200) as u16) << (depth - 8); + } + if chroma != ChromaFormat::Yuv444 { + f.cb.fill(128 << (depth - 8)); + f.cr.fill(100 << (depth - 8)); + } + if let Ok(mut enc) = Encoder::with_config(cfg) + && let Ok(d) = enc.encode(&f) + { + v.push(d); + } + } + v +} + +fn run(d: &[u8], label: &str) { + craft_testkit::set_progress(label); + let res = catch_unwind(AssertUnwindSafe(|| { + let _ = probe(d); + for opts in [DecodeOptions::default(), DecodeOptions { threads: true, spec_dequant: true }] { + let _ = decode_frame_with(d, &opts); + } + })); + assert!(res.is_ok(), "panic: {label}"); +} + +/// Seconds the whole sweep may take before it counts as a hang (override: FILMCRAFT_HOSTILE_TIMEOUT_SECS). +const DEADLINE_SECS: u64 = 600; + +/// CI runs a rotating share of every part of the sweep; `FILMCRAFT_FUZZ_ITERS=1500` runs all of it (see [`Depth`]). +#[test] +fn hostile_frames_never_panic() { + craft_testkit::with_deadline(DEADLINE_SECS, sweep); +} + +fn sweep() { + let depth = Depth::from_env(); + let seeds = seeds(); + assert!(!seeds.is_empty()); + for (si, seed) in seeds.iter().enumerate() { + for n in depth.truncations(seed.len()) { + run(&seed[..n], &format!("seed {si} truncate {n}")); + } + header_flips(&depth, seed, si); + big_fields(seed, si); + random_rounds(&depth, seed, si); + } +} + +/// The header (and the start of the scan-line index): every byte to extreme values, every bit. +fn header_flips(depth: &Depth, seed: &[u8], si: usize) { + for p in 0..seed.len().min(0x300) { + for (i, v) in VALUES.into_iter().enumerate() { + if !depth.value(p, i) { + continue; + } + let mut d = seed.to_vec(); + d[p] = v; + run(&d, &format!("seed {si} byte {p:#x}={v:#x}")); + } + for b in 0..8 { + if !depth.bit(p, b) { + continue; + } + let mut d = seed.to_vec(); + d[p] ^= 1 << b; + run(&d, &format!("seed {si} flip {p:#x}.{b}")); + } + } +} + +/// Big dimension fields with the rest intact (lines 0x18, width 0x1A, macroblock rows 0x16C). +fn big_fields(seed: &[u8], si: usize) { + for (off, v) in [(0x18usize, 0xffffu16), (0x1a, 0xffff), (0x18, 0x4000), (0x1a, 0x4000), (0x16c, 0xffff)] { + let mut d = seed.to_vec(); + if let Some(s) = d.get_mut(off..off + 2) { + s.copy_from_slice(&v.to_be_bytes()); + } + run(&d, &format!("seed {si} u16 {off:#x}={v:#x}")); + } +} + +fn random_rounds(depth: &Depth, seed: &[u8], si: usize) { + let mut rng = Rng(0x9e3779b97f4a7c15 ^ si as u64); + // Skipped rounds still draw their mutations, so round i matches round i of a full run. + for i in 0..depth.rounds { + let mut d = seed.to_vec(); + for _ in 0..1 + rng.below(8) { + let p = rng.below(d.len()); + if rng.below(2) == 0 { + d[p] ^= 1 << rng.below(8); + } else { + d[p] = rng.next() as u8; + } + } + if depth.round(i) { + run(&d, &format!("seed {si} random {i}")); + } + } +} + +const VALUES: [u8; 5] = [0, 0xff, 0x80, 0x01, 0x7f]; + +/// How much of the sweep runs. `FILMCRAFT_FUZZ_ITERS=N` runs all of it (about 400 truncation +/// points spread over each seed, every header byte, overwrite value and bit flip) plus N random +/// rounds per seed: 1500 is the full depth, more hunts longer. Unset, CI runs a third of the +/// truncation points and of the full depth's random rounds and, for each header byte, one value +/// and two bits. The subset is reproducible; set `FILMCRAFT_FUZZ_SEED` to choose another share. +/// A random round's mutations match the same round of a full run. +struct Depth { + full: bool, + truncations: usize, + rounds: usize, + rotation: usize, +} + +impl Depth { + fn from_env() -> Self { + match std::env::var("FILMCRAFT_FUZZ_ITERS").ok().and_then(|s| s.parse().ok()) { + Some(rounds) => Depth { full: true, truncations: 400, rounds, rotation: 0 }, + None => { + let rotation = rotation(); + eprintln!("fuzz subset rotation {rotation} (FILMCRAFT_FUZZ_SEED={rotation} replays it; FILMCRAFT_FUZZ_ITERS=1500 runs everything)"); + Depth { full: false, truncations: 400, rounds: 1500, rotation } + } + } + } + /// Truncation lengths for a `len`-byte input: every point of the full sweep, or every third one. + fn truncations(&self, len: usize) -> std::iter::StepBy> { + let stride = (len / self.truncations).max(1); + let (start, step) = if self.full { (0, stride) } else { ((self.rotation % 3) * stride, 3 * stride) }; + (start..len).step_by(step) + } + fn value(&self, p: usize, i: usize) -> bool { + self.full || i == (p + self.rotation % VALUES.len()) % VALUES.len() + } + fn bit(&self, p: usize, b: usize) -> bool { + self.full || (b + p + self.rotation % 4).is_multiple_of(4) + } + fn round(&self, i: usize) -> bool { + self.full || i % 3 == self.rotation % 3 + } +} + +/// The default subset's rotation offset: `FILMCRAFT_FUZZ_SEED`, or zero for reproducible CI runs. +fn rotation() -> usize { + std::env::var("FILMCRAFT_FUZZ_SEED").ok().and_then(|s| s.parse().ok()).unwrap_or(0) +} diff --git a/crates/dnx/tests/it/main.rs b/crates/dnx/tests/it/main.rs new file mode 100644 index 0000000000..c12ebd0994 --- /dev/null +++ b/crates/dnx/tests/it/main.rs @@ -0,0 +1,6 @@ +mod common; +mod fuzz_hostile; +mod oracle_decode; +mod oracle_encode; +mod perf; +mod roundtrip; diff --git a/crates/dnx/tests/it/oracle_decode.rs b/crates/dnx/tests/it/oracle_decode.rs new file mode 100644 index 0000000000..0610036297 --- /dev/null +++ b/crates/dnx/tests/it/oracle_decode.rs @@ -0,0 +1,85 @@ +//! Decode ffmpeg-made DNxHD / DNxHR fixtures and compare with ffmpeg's own decode. + +use crate::common; +use common::*; +use craft_dnx::{DecodeOptions, decode_frame_with}; + +fn specs() -> Vec { + let t = |s: &str, r: u32| format!("testsrc2=s={s}:r={r}"); + let m = |s: &str, r: u32| format!("mandelbrot=s={s}:r={r}"); + let n = |s: &str, r: u32| { + format!("testsrc2=s={s}:r={r},noise=alls=60:allf=t,lutyuv=y=clip(val\\,24\\,231):u=clip(val\\,24\\,231):v=clip(val\\,24\\,231)") + }; + vec![ + // DNxHD (HD profile) + Spec::new("hd1237_testsrc", t("1920x1080", 25), 2, &["-b:v", "120M", "-pix_fmt", "yuv422p"], "yuv422p"), + Spec::new("hd1235_mandel", m("1920x1080", 25), 2, &["-b:v", "185M", "-pix_fmt", "yuv422p10le"], "yuv422p10le"), + Spec::new("hd1238_noise", n("1920x1080", 25), 2, &["-b:v", "185M", "-pix_fmt", "yuv422p"], "yuv422p"), + Spec::new("hd1253_testsrc", t("1920x1080", 25), 2, &["-b:v", "36M", "-pix_fmt", "yuv422p"], "yuv422p"), + Spec::new("hd1250_noise", n("1280x720", 50), 2, &["-b:v", "90M", "-pix_fmt", "yuv422p10le"], "yuv422p10le"), + Spec::new("hd1251_testsrc", t("1280x720", 50), 2, &["-b:v", "90M", "-pix_fmt", "yuv422p"], "yuv422p"), + Spec::new("hd1252_mandel", m("1280x720", 50), 2, &["-b:v", "60M", "-pix_fmt", "yuv422p"], "yuv422p"), + Spec::new("hd1258_testsrc", t("960x720", 50), 2, &["-b:v", "60M", "-pix_fmt", "yuv422p"], "yuv422p"), + Spec::new("hd1259_testsrc", t("1440x1080", 25), 2, &["-b:v", "84M", "-pix_fmt", "yuv422p"], "yuv422p"), + Spec::new("hd1242_interlaced", t("1920x1080", 25), 2, &["-flags", "+ildct", "-b:v", "120M", "-pix_fmt", "yuv422p"], "yuv422p"), + Spec::new("hd1241_interlaced", n("1920x1080", 25), 2, &["-flags", "+ildct", "-b:v", "185M", "-pix_fmt", "yuv422p10le"], "yuv422p10le"), + Spec::new("hd1243_interlaced", m("1920x1080", 25), 2, &["-flags", "+ildct", "-b:v", "220M", "-pix_fmt", "yuv422p"], "yuv422p"), + Spec::new("hd1244_interlaced", t("1440x1080", 25), 2, &["-flags", "+ildct", "-b:v", "120M", "-pix_fmt", "yuv422p"], "yuv422p"), + // DNxHR (RI profile) + Spec::new("hr_lb_testsrc", t("1920x1080", 25), 2, &["-profile:v", "dnxhr_lb", "-pix_fmt", "yuv422p"], "yuv422p"), + Spec::new("hr_sq_mandel", m("1920x1080", 25), 2, &["-profile:v", "dnxhr_sq", "-pix_fmt", "yuv422p"], "yuv422p"), + Spec::new("hr_hq_noise", n("1920x1080", 25), 2, &["-profile:v", "dnxhr_hq", "-pix_fmt", "yuv422p"], "yuv422p"), + Spec::new("hr_hqx_mandel", m("1920x1080", 25), 2, &["-profile:v", "dnxhr_hqx", "-pix_fmt", "yuv422p10le"], "yuv422p10le"), + Spec::new("hr_hqx_noise_odd", n("1000x562", 25), 2, &["-profile:v", "dnxhr_hqx", "-pix_fmt", "yuv422p10le"], "yuv422p10le"), + Spec::new("hr_444_testsrc", t("1280x720", 25), 2, &["-profile:v", "dnxhr_444", "-pix_fmt", "yuv444p10le"], "yuv444p10le"), + Spec::new("hr_444_rgb_mandel", m("640x360", 25), 2, &["-profile:v", "dnxhr_444", "-pix_fmt", "gbrp10le"], "gbrp10le"), + Spec::new("hr_hq_small", t("256x120", 25), 2, &["-profile:v", "dnxhr_hq", "-pix_fmt", "yuv422p"], "yuv422p"), + Spec::new("hr_sq_uhd", t("3840x2160", 25), 1, &["-profile:v", "dnxhr_sq", "-pix_fmt", "yuv422p"], "yuv422p"), + ] +} + +/// Generate every fixture (used by `cargo xtask fixtures dnx`). +#[test] +#[ignore] +fn generate_fixtures() { + let Some(ff) = ffmpeg() else { return }; + for s in specs() { + make(&ff, &s); + } +} + +#[test] +fn decode_matches_ffmpeg() { + let Some(ff) = ffmpeg() else { return }; + let mut worst = 0; + for spec in specs() { + let mov = make(&ff, &spec); + let pk = packets(&ff, &mov); + assert_eq!(pk.len(), spec.frames as usize, "{}", spec.name); + let raw = reference(&ff, &mov, spec.pix_fmt); + let mut st = Stats::default(); + let mut off = 0; + for (i, p) in pk.iter().enumerate() { + let f = decode_frame_with(p, &DecodeOptions { threads: i % 2 == 0, ..Default::default() }) + .unwrap_or_else(|e| panic!("{} frame {i}: {e}", spec.name)); + let ours = planes(&f, spec.pix_fmt); + let n = frame_samples(&f); + st.add(&ours, &raw[off..off + n]); + off += n; + } + assert_eq!(off, raw.len(), "{}: frame size mismatch", spec.name); + println!( + "{:<22} cid {} max {} mean {:.5} exact {:.3}% beyond ±1: {}", + spec.name, + craft_dnx::probe(&pk[0]).unwrap().cid, + st.max, + st.mean(), + st.exact_pct(), + st.over1 + ); + worst = worst.max(st.max); + // the remaining differences are ffmpeg's integer IDCT: ±1, at most ~50 samples per million at ±2 + assert!(st.over1 * 10_000 <= st.n, "{}: {} samples beyond ±1", spec.name, st.over1); + } + assert!(worst <= 2, "max error {worst}"); +} diff --git a/crates/dnx/tests/it/oracle_encode.rs b/crates/dnx/tests/it/oracle_encode.rs new file mode 100644 index 0000000000..ced23b4ea6 --- /dev/null +++ b/crates/dnx/tests/it/oracle_encode.rs @@ -0,0 +1,76 @@ +//! Encode DNxHR with our encoder, decode with ffmpeg (`-xerror`) and with our decoder. + +use crate::common; +use common::*; +use craft_dnx::{ChromaFormat, Encoder, EncoderConfig, Profile, decode_frame}; + +struct Case { + name: &'static str, + profile: Profile, + depth: u8, + lavfi: &'static str, + min_psnr: f64, +} + +#[test] +fn encode_decodes_in_ffmpeg() { + let Some(ff) = ffmpeg() else { return }; + let (w, h) = (1920u32, 1080u32); + let cases = [ + Case { name: "enc_sq_mandel", profile: Profile::Sq, depth: 8, lavfi: "mandelbrot=s=1920x1080:r=25", min_psnr: 40.0 }, + Case { name: "enc_hq_mandel", profile: Profile::Hq, depth: 8, lavfi: "mandelbrot=s=1920x1080:r=25", min_psnr: 45.0 }, + Case { name: "enc_hq_testsrc", profile: Profile::Hq, depth: 8, lavfi: "testsrc2=s=1920x1080:r=25", min_psnr: 45.0 }, + Case { name: "enc_hqx10_mandel", profile: Profile::Hqx, depth: 10, lavfi: "mandelbrot=s=1920x1080:r=25", min_psnr: 45.0 }, + Case { name: "enc_hqx12_testsrc", profile: Profile::Hqx, depth: 12, lavfi: "testsrc2=s=1920x1080:r=25", min_psnr: 45.0 }, + Case { name: "enc_lb_testsrc", profile: Profile::Lb, depth: 8, lavfi: "testsrc2=s=1920x1080:r=25", min_psnr: 30.0 }, + Case { name: "enc_444_testsrc", profile: Profile::R444, depth: 10, lavfi: "testsrc2=s=1920x1080:r=25", min_psnr: 45.0 }, + ]; + for c in cases { + let mut cfg = EncoderConfig::new(c.profile, w, h); + cfg.bit_depth = c.depth; + let chroma = cfg.chroma(); + let src = source_frames(&ff, SourceFrameSpec { name: c.name, lavfi: c.lavfi, frames: 2, width: w, height: h, chroma, depth: c.depth }); + let mut enc = Encoder::with_config(cfg).unwrap(); + let t = std::time::Instant::now(); + let coded: Vec> = src.iter().map(|f| enc.encode(f).unwrap()).collect(); + let ms = t.elapsed().as_secs_f64() * 1000.0 / src.len() as f64; + for p in &coded { + assert_eq!(p.len() as u32, enc.frame_size(), "{}: CBR frame size", c.name); + } + let mov = fixture_dir().join(format!("{}.out.mov", c.name)); + write_mov(&mov, b"AVdh", w as u16, h as u16, 24, &coded); + let pix = match (chroma, c.depth) { + (ChromaFormat::Yuv444, 10) => "yuv444p10le", + (_, 8) => "yuv422p", + (_, 10) => "yuv422p10le", + _ => "yuv422p12le", + }; + // ffmpeg reads 4:4:4 Y'CbCr DNxHR as gbrp unless every macroblock is flagged + let ff_raw = ffmpeg_decode(&ff, &mov, pix); + let mut agree = Stats::default(); + let mut psnr_min = f64::MAX; + let per = frame_samples(&src[0]); + assert_eq!(ff_raw.len(), per * coded.len(), "{}: ffmpeg frame count", c.name); + for (i, p) in coded.iter().enumerate() { + let f = decode_frame(p).unwrap(); + let ours = planes(&f, pix); + agree.add(&ours, &ff_raw[i * per..(i + 1) * per]); + let peak = ((1u32 << c.depth) - 1) as f64; + let srcp = planes(&src[i], pix); + psnr_min = psnr_min.min(psnr(&srcp[..f.y.len()], &ours[..f.y.len()], peak)); + } + println!( + "{:<18} {} {}-bit: {} bytes/frame, {:.1} ms/frame, Y PSNR {:.2} dB, vs ffmpeg max {} exact {:.2}%", + c.name, + c.profile.name(), + c.depth, + enc.frame_size(), + ms, + psnr_min, + agree.max, + agree.exact_pct() + ); + assert!(agree.max <= 2, "{}: ffmpeg disagrees by {}", c.name, agree.max); + assert!(psnr_min >= c.min_psnr, "{}: PSNR {psnr_min:.2}", c.name); + } +} diff --git a/crates/dnx/tests/it/perf.rs b/crates/dnx/tests/it/perf.rs new file mode 100644 index 0000000000..8b31c6cc4e --- /dev/null +++ b/crates/dnx/tests/it/perf.rs @@ -0,0 +1,61 @@ +//! Throughput: `cargo test --release -p craft-dnx --test it perf:: -- --ignored --nocapture`. + +use craft_dnx::{ChromaFormat, DecodeOptions, Encoder, EncoderConfig, Frame, Profile, decode_frame_with}; +use std::time::Instant; + +fn busy(w: u32, h: u32, chroma: ChromaFormat, depth: u8) -> Frame { + let mut f = Frame::new(w, h, chroma, depth, false); + let max = (1u32 << depth) - 1; + let mut s = 1u32; + for v in f.y.iter_mut().chain(f.cb.iter_mut()).chain(f.cr.iter_mut()) { + s ^= s << 13; + s ^= s >> 17; + s ^= s << 5; + *v = (((*v as u32) + (s % (24 << (depth - 8)))).min(max)) as u16; + } + for y in 0..h { + for x in 0..w { + let i = (y * w + x) as usize; + f.y[i] = (f.y[i] as u32 + (((x ^ y) % 160) << (depth - 8))).min(max) as u16; + } + } + f +} + +#[test] +#[ignore] +fn throughput() { + for (profile, depth, w, h) in + [(Profile::Hq, 8, 1920, 1080), (Profile::Hqx, 10, 1920, 1080), (Profile::Sq, 8, 3840, 2160), (Profile::Hqx, 10, 3840, 2160)] + { + let mut cfg = EncoderConfig::new(profile, w, h); + cfg.bit_depth = depth; + let src = busy(w, h, cfg.chroma(), depth); + let mut enc = Encoder::with_config(cfg).unwrap(); + let data = enc.encode(&src).unwrap(); + let n = if w > 1920 { 5 } else { 20 }; + let t = Instant::now(); + for _ in 0..n { + enc.encode(&src).unwrap(); + } + let enc_fps = n as f64 / t.elapsed().as_secs_f64(); + let mut fps = [0f64; 2]; + for (k, threads) in [true, false].into_iter().enumerate() { + let opts = DecodeOptions { threads, ..Default::default() }; + let m = if threads { n * 3 } else { n / 2 }; + let t = Instant::now(); + for _ in 0..m { + decode_frame_with(&data, &opts).unwrap(); + } + fps[k] = m as f64 / t.elapsed().as_secs_f64(); + } + println!( + "{} {depth}-bit {w}x{h} ({} KB/frame): decode {:.0} fps threaded, {:.1} fps single-thread; encode {:.1} fps", + profile.name(), + data.len() / 1024, + fps[0], + fps[1], + enc_fps + ); + } +} diff --git a/crates/dnx/tests/it/roundtrip.rs b/crates/dnx/tests/it/roundtrip.rs new file mode 100644 index 0000000000..1dd6d9e0b6 --- /dev/null +++ b/crates/dnx/tests/it/roundtrip.rs @@ -0,0 +1,106 @@ +//! Encoder → decoder round trips and robustness against corrupt data (no ffmpeg needed). + +use craft_dnx::{ChromaFormat, DecodeOptions, Encoder, EncoderConfig, Frame, Profile, decode_frame, decode_frame_with, probe}; + +/// A synthetic picture: gradients, a ring pattern and some hashed texture. +fn picture(w: u32, h: u32, chroma: ChromaFormat, depth: u8, seed: u32) -> Frame { + let mut f = Frame::new(w, h, chroma, depth, false); + let max = (1u32 << depth) - 1; + let s = depth as u32 - 8; + let hash = |x: u32, y: u32| (x.wrapping_mul(73856093) ^ y.wrapping_mul(19349663) ^ seed.wrapping_mul(83492791)) % 17; + for y in 0..h { + for x in 0..w { + let r = (((x as f32 - w as f32 / 2.0).powi(2) + (y as f32 - h as f32 / 2.0).powi(2)).sqrt() / 6.0).sin(); + let v = (16 << s) as f32 + (219 << s) as f32 * (0.5 + 0.3 * r + 0.2 * (x as f32 / w as f32 - 0.5)) + (hash(x, y) << s) as f32; + f.y[(y * w + x) as usize] = (v as u32).min(max) as u16; + } + } + let cw = f.chroma_width(); + for y in 0..f.chroma_height() { + for x in 0..cw { + let i = (y * cw + x) as usize; + f.cb[i] = (((128 + (x * 90 / cw) as i32 - 45) as u32) << s) as u16; + f.cr[i] = (((128 + (y * 80 / f.chroma_height()) as i32 - 40) as u32) << s) as u16; + } + } + f +} + +fn psnr(a: &[u16], b: &[u16], peak: f64) -> f64 { + let mse: f64 = a.iter().zip(b).map(|(&x, &y)| (x as f64 - y as f64).powi(2)).sum::() / a.len() as f64; + if mse == 0.0 { 99.0 } else { 10.0 * (peak * peak / mse).log10() } +} + +#[test] +fn roundtrip_all_profiles() { + for (profile, depth, w, h) in [ + (Profile::Lb, 8, 640, 360), + (Profile::Sq, 8, 1280, 720), + (Profile::Hq, 8, 720, 486), + (Profile::Hqx, 10, 1000, 562), + (Profile::Hqx, 12, 512, 288), + (Profile::R444, 10, 400, 300), + (Profile::R444, 12, 64, 48), + ] { + let mut cfg = EncoderConfig::new(profile, w, h); + cfg.bit_depth = depth; + let chroma = cfg.chroma(); + let mut enc = Encoder::with_config(cfg).unwrap(); + for seed in 0..2 { + let src = picture(w, h, chroma, depth, seed); + let data = enc.encode(&src).unwrap(); + assert_eq!(data.len() as u32, enc.frame_size()); + assert_eq!(&data[data.len() - 4..], &[0x60, 0x0D, 0xC0, 0xDE]); + let hdr = probe(&data).unwrap(); + assert_eq!((hdr.cid, hdr.width, hdr.lines, hdr.bit_depth), (profile.cid(), w, h, depth)); + let dec = decode_frame(&data).unwrap(); + let single = decode_frame_with(&data, &DecodeOptions { threads: false, ..Default::default() }).unwrap(); + assert_eq!(dec, single, "threaded and single-threaded decodes differ"); + assert_eq!((dec.width, dec.height, dec.chroma, dec.bit_depth, dec.rgb), (w, h, chroma, depth, false)); + let peak = ((1u32 << depth) - 1) as f64; + let p = psnr(&src.y, &dec.y, peak); + let pc = psnr(&src.cb, &dec.cb, peak); + let min = if profile == Profile::Lb { 30.0 } else { 38.0 }; + assert!(p > min && pc > min, "{profile:?} {depth}-bit {w}x{h}: PSNR {p:.1}/{pc:.1}"); + } + } +} + +#[test] +fn rejects_bad_input() { + let mut enc = Encoder::new(Profile::Hq, 64, 64).unwrap(); + assert!(enc.encode(&Frame::new(32, 64, ChromaFormat::Yuv422, 8, false)).is_err()); + assert!(enc.encode(&Frame::new(64, 64, ChromaFormat::Yuv444, 8, false)).is_err()); + assert!(Encoder::with_config(EncoderConfig { bit_depth: 10, ..EncoderConfig::new(Profile::Hq, 64, 64) }).is_err()); + assert!(decode_frame(&[0u8; 100]).is_err()); + assert!(decode_frame(&[0u8; 1000]).is_err()); + // An unsupported raster is an error, not a panic. + assert!(Encoder::new(Profile::Hq, 0, 0).is_err()); + assert!(Encoder::new(Profile::Hq, 20_000, 64).is_err()); +} + +#[test] +fn corrupt_streams_never_panic() { + let mut enc = Encoder::new(Profile::Hq, 256, 128).unwrap(); + let good = enc.encode(&picture(256, 128, ChromaFormat::Yuv422, 8, 1)).unwrap(); + let mut seed = 0x1234_5678u32; + let mut rnd = || { + seed ^= seed << 13; + seed ^= seed >> 17; + seed ^= seed << 5; + seed + }; + for i in 0..400 { + let mut d = good.clone(); + let n = 1 + rnd() % 8; + for _ in 0..n { + // mostly the payload, sometimes the header + let pos = if i % 4 == 0 { rnd() as usize % 0x280 } else { 0x280 + rnd() as usize % (d.len() - 0x280) }; + d[pos] ^= 1 << (rnd() % 8); + } + if i % 5 == 0 { + d.truncate(rnd() as usize % d.len()); + } + let _ = decode_frame(&d); + } +} diff --git a/crates/dnx/tools/extract_tables.py b/crates/dnx/tools/extract_tables.py new file mode 100644 index 0000000000..2e120cc93e --- /dev/null +++ b/crates/dnx/tools/extract_tables.py @@ -0,0 +1,133 @@ +#!/usr/bin/env python3 +"""Extract the VC-3 constant tables (Annex D quantization weights, Annex E VLC tables) from the +text of the public SMPTE ST 2019-1:2016 standard. + +Usage: + curl -LO https://pub.smpte.org/doc/st2019-1/20160518-pub/st2019-1-20160518-pub.zip + unzip st2019-1-20160518-pub.zip + pdftotext -layout st2019-1-2016.pdf vc3.txt + python3 extract_tables.py vc3.txt > ../src/spec_tables.rs + +Only the standard's text is read. Every table's size is checked (63 AC weights per matrix, 4*64+1 +amplitude codewords including EOB, 62 run codewords) and every VLC table must be a complete +prefix code. +""" +import re +import sys +from pathlib import Path + + +def _spec_path(arg): + """Resolve a CLI path argument and require it to be an existing regular file.""" + path = Path(arg).resolve() + if not path.is_file(): + sys.exit(f"not a readable file: {arg}") + return path + + +text = open(_spec_path(sys.argv[1]), encoding="utf-8").read() +lines = text.split("\n") + + +def section(start_pat, end_pat): + s = next(i for i, l in enumerate(lines) if re.search(start_pat, l)) + e = next(i for i in range(s + 1, len(lines)) if re.search(end_pat, lines[i])) + return lines[s + 1:e] + + +def weights(sec): + """The 63 AC weights of an 8x8 table printed in raster order with '-' for the DC entry.""" + nums = [] + for l in sec: + if "Page" in l or "SMPTE" in l or "Compression" in l: + continue + nums += [int(x) for x in re.findall(r"\b\d+\b", l)] + return nums + + +def check_prefix(codes): + """Kraft sum of a prefix code (1.0 when complete).""" + for a in codes: + for b in codes: + if a is not b and b.startswith(a): + raise SystemExit("not a prefix code: %s %s" % (a, b)) + return sum(2.0 ** -len(c) for c in codes) + + +out = ["// Generated by tools/extract_tables.py from SMPTE ST 2019-1:2016 (VC-3), Annexes D and E.", + "// Do not edit by hand.", ""] + +# ---- Annex D: weights in raster order (v*8+u), DC entry omitted ---- +dtables = [("D_1", r"Table D\.1 – Compression ID 1235"), ("D_2", r"Table D\.2 – Compression ID 1237"), + ("D_3", r"^Table D\.3 – Compression"), ("D_4", r"Table D\.4 – Compression"), + ("D_5", r"Table D\.5 – Compression"), ("D_6", r"Table D\.6 – Compression"), + ("D_7", r"^Table D\.7 – Compression"), ("D_8", r"Table D\.8 – Compression"), + ("D_9", r"Table D\.9 – Compression"), ("D_10", r"Table D\.10 – Compression"), + ("D_11", r"^Table D\.11 – Compression")] +for i, (name, pat) in enumerate(dtables): + end = dtables[i + 1][1] if i + 1 < len(dtables) else r"^Annex E VLC Tables" + sec = section(pat, end) + if name == "D_11": + sec = [l for l in sec if "Compression ID" not in l] + nums = weights(sec) + assert len(nums) == 63, (name, len(nums)) + out.append("pub const W_%s: [[u8; 63]; 2] = [%s, %s];" % (name, nums, nums)) + continue + li = next(j for j, l in enumerate(sec) if "Luma Quantization" in l) + ci = next(j for j, l in enumerate(sec) if "Chroma Quantization" in l) + luma = weights(sec[li + 1:ci]) + chroma = weights(sec[ci + 1:]) + assert len(luma) == 63 and len(chroma) == 63, (name, len(luma), len(chroma)) + out.append("pub const W_%s: [[u8; 63]; 2] = [%s, %s];" % (name, luma, chroma)) +out.append("") + +# ---- Annex E ---- +etables = ["E.1", "E.4", "E.7", "E.10", "E.13", "E.16"] +for n, t in enumerate(etables): + a = int(t[2:]) + amp = section(r"^\s*Table E\.%d – Amplitude" % a, r"Table E\.%d – Run" % (a + 1)) + run = section(r"Table E\.%d – Run" % (a + 1), r"Table E\.%d – DC" % (a + 2)) + nxt = r"Table E\.%d – Amplitude" % (a + 3) if n + 1 < len(etables) else r"^Annex F System" + dc = section(r"Table E\.%d – DC" % (a + 2), nxt) + ent = [] + for l in amp: + for m in re.finditer(r"\b([01]+)\s+(\d+)\s+(EOB1?|\d+)\s+([01])\s+([01])\b", l): + code, ln, am, fr, fi = m.groups() + assert len(code) == int(ln), m.group(0) + ent.append((code, 0 if am.startswith("EOB") else int(am), int(fr), int(fi), am.startswith("EOB"))) + k = check_prefix([e[0] for e in ent]) + assert abs(k - 1.0) < 1e-12, (t, k, len(ent)) + assert sum(e[4] for e in ent) == 1 and len(ent) == 4 * 64 + 1, (t, len(ent)) + rent = [] + for l in run: + for m in re.finditer(r"\b([01]+)\s+(\d+)\s+(\d+)\b", l): + code, ln, r = m.groups() + if len(code) != int(ln): + continue + rent.append((code, int(r))) + assert len(rent) == 62 and sorted(r for _, r in rent) == list(range(1, 63)), (t, len(rent)) + assert abs(check_prefix([c for c, _ in rent]) - 1.0) < 1e-12 + dent = [] + for l in dc: + m = re.match(r"^\s+([01]+)\s+(\d+)\s*$", l) + if m: + dent.append((m.group(1), int(m.group(2)))) + assert [e for _, e in dent] == list(range(len(dent))), (t, dent) + assert abs(check_prefix([c for c, _ in dent]) - 1.0) < 1e-12, (t, dent) + name = t.replace(".", "_") + out.append("/// Table %s: (codeword, length, amplitude (0 = EOB), Frun, Findex)." % t) + out.append("pub const AMP_%s: [(u16, u8, u8, u8, u8); %d] = [" % (name, len(ent))) + for c, a_, fr, fi, _ in ent: + out.append(" (0b%s, %d, %d, %d, %d)," % (c, len(c), a_, fr, fi)) + out.append("];") + out.append("/// Table E.%d: (codeword, length, run)." % (a + 1)) + out.append("pub const RUN_%s: [(u16, u8, u8); 62] = [" % name) + for c, r in rent: + out.append(" (0b%s, %d, %d)," % (c, len(c), r)) + out.append("];") + out.append("/// Table E.%d: (codeword, length) indexed by the DC size.", ) + out[-1] = "/// Table E.%d: (codeword, length) indexed by the DC size (eta)." % (a + 2) + out.append("pub const DC_%s: [(u16, u8); %d] = [%s];" % (name, len(dent), ", ".join("(0b%s, %d)" % (c, len(c)) for c, _ in dent))) + out.append("") + +print("\n".join(out)) diff --git a/crates/h264enc/Cargo.toml b/crates/h264enc/Cargo.toml new file mode 100644 index 0000000000..283499f966 --- /dev/null +++ b/crates/h264enc/Cargo.toml @@ -0,0 +1,20 @@ +[package] +name = "craft-h264enc" +description = "Clean-room pure-Rust H.264/AVC encoder for the Craft ecosystem" +version.workspace = true +edition.workspace = true +license.workspace = true +rust-version.workspace = true +repository.workspace = true + +[lints] +workspace = true + +[features] +default = ["threads"] +threads = ["dep:rayon"] + +[dependencies] +craft-media = { workspace = true, default-features = false, features = ["bitstream"] } +thiserror = { workspace = true } +rayon = { workspace = true, optional = true } diff --git a/crates/h264enc/README.md b/crates/h264enc/README.md new file mode 100644 index 0000000000..55932d667f --- /dev/null +++ b/crates/h264enc/README.md @@ -0,0 +1,84 @@ +# craft-h264enc + +A clean-room, pure-Rust H.264/AVC encoder for export pipelines, written from the public +ITU-T H.264 specification (04/2013 edition) and general encoder literature. Layer L0: it depends only on +`craft-media` (with `bitstream` feature), `thiserror` and (optionally, feature `threads`, on by default) `rayon`. No `unsafe`, +builds for `wasm32-unknown-unknown` with or without `threads`. + +## Usage + +```rust +use craft_h264enc::{Encoder, EncoderConfig, PacketFormat, Preset, Profile, RateControl, YuvFrame}; + +let mut cfg = EncoderConfig::new(1920, 1080, 30000, 1001); +cfg.profile = Profile::High; +cfg.preset = Preset::Balanced; // Speed / Balanced / Quality +cfg.rate = RateControl::Crf(20.0); // Crf(f32) | Cbr { kbps } | Vbr { target_kbps, max_kbps } | Qp(u8) +cfg.bframes = 2; // 0..=3 +cfg.keyint = 250; +cfg.format = PacketFormat::LengthPrefixed; // MP4 samples; or AnnexB (default) for .h264 +let mut enc = Encoder::new(cfg)?; +let avcc = enc.avcc(); // AVCDecoderConfigurationRecord for the MP4 `avcC` box +for (i, pic) in pictures.iter().enumerate() { + let frame = YuvFrame { y: &pic.y, u: &pic.u, v: &pic.v, y_stride: w, uv_stride: w / 2 }; + for p in enc.encode(&frame, i as i64)? { /* p.data, p.pts, p.dts, p.keyframe, p.frame_type */ } +} +for p in enc.flush() { /* ... */ } +``` + +- Input: planar 4:2:0 8-bit (`YuvFrame` with strides); any even size, non-multiple-of-16 sizes are + cropped via the SPS frame cropping. `encode` returns an error (`Error::BadFrame`) instead of panicking on short planes. +- Output: one `Packet` per picture in decoding order with `pts` (as given), `dts` (reorder-delayed, always + `<= pts` and strictly increasing), `keyframe` (IDR) and the frame type/QP. Annex-B packets carry an AUD, + SPS/PPS on every IDR and an identification SEI on the first picture. +- Two-pass: encode once with `cfg.pass = Pass::First`, take `enc.pass_stats()` (serialisable with + `PassStats::to_text/from_text`), then encode again with `Pass::Second(stats)`. +- `set_recon_capture(true)` + `take_recon()` expose the encoder's reconstructed pictures (used by the tests to + prove encoder/decoder match). + +## Features + +| Area | Implemented | +|---|---| +| Profiles | Constrained Baseline (CAVLC, I/P), Main (CABAC, I/P/B), High (CABAC, 8x8 transform, I/P/B); 4:2:0 8-bit, frame coding | +| Parameter sets | SPS (level auto-selected from size/rate/DPB/bitrate, cropping, VUI: SAR, colour description + range, timing, bitstream restriction/reorder), PPS, AUD, user-data SEI, avcC | +| Intra | 16x16 (4 modes, SATD), 4x4 (9 modes, SATD + mode-cost, sequential reconstruction), 8x8 (9 modes with reference filtering, High), chroma (4 modes) | +| Inter P | hexagon integer search from predictors, half- then quarter-sample refinement (SATD) on precomputed 6-tap half-sample planes; 16x16/16x8/8x16/8x8 partitions (Balanced/Quality); P_Skip detection with the normative skip MV | +| Inter B | 1–3 non-reference B-frames, spatial direct / B_Skip, L0/L1/Bi 16x16 and (Balanced/Quality) 16x8/8x16 with a per-partition direction; all B-frames of a mini-GOP are encoded concurrently | +| Residual | 4x4 / 8x8 integer transforms, Intra16x16 and chroma DC Hadamards, deadzone quantisation (intra 1/3, inter 1/6), adaptive 4x4/8x8 inter transform choice, coefficient decimation for inter blocks | +| Entropy | CABAC (all I and cabac_init_idc 0 context tables, verified against the spec text) and CAVLC (all coeff_token/total_zeros/run_before tables, level escapes) | +| Loop filter | normative deblocking (bS 0–4 incl. bi-predicted rules, 8x8-transform edges, chroma QP mapping) in the reconstruction loop | +| Mode decision | SATD + λ·bits for Speed/Balanced; Quality adds RD-lite (SSD + λ·CABAC-estimated bits) between the best intra/inter/skip candidates | +| Rate control | `Qp`, `Crf` (constant quality with I/P/B offsets), 1-pass ABR with complexity model + VBV (`Cbr`, 1-pass `Vbr`), closed-loop 2-pass VBR; variance adaptive quantisation (`aq_strength`), lookahead scene-cut IDR insertion | +| Threading | slice-parallel encoding (MB-row slices, `disable_deblocking_filter_idc = 2` with >1 slice so each slice is also deblocked in parallel), parallel half-sample interpolation and lookahead | + +## Verification + +`cargo test --release -p craft-h264enc` (ffmpeg/ffprobe used only as external oracles; tests skip when +absent). Every stream is decoded with `ffmpeg -v warning -err_detect +crccheck+bitstream+buffer+explode -f rawvideo` +and must print nothing (not even a `corrupt decoded frame` warning), produce exactly the number of frames encoded, +and be **bit-identical to the encoder's reconstruction** — this covers Baseline/Main/High, all presets, 0–3 +B-frames, 1–4 slices and automatic slicing at 1080p, odd sizes, `testsrc2` and `mandelbrot` sources, and +length-prefixed output re-wrapped with the avcC parameter sets. B-frame display order is checked with ffprobe. A +negative control drops and truncates slices and checks that the oracle reports them. + +The oracle leaves ffmpeg's error concealment on. With `-ec 0`, ffmpeg 7.1 and 8.1 print `corrupt decoded frame` +for every picture that has more than one slice, libx264's multi-slice streams included, while the decoded pixels +are unchanged (#74). The bit-exact comparison is what rules out concealment. + +Results on the synthetic content (moving gradient, grain noise, text-like edges, sub-pixel panning): + +| Test | Result | +|---|---| +| 1080p, High, Speed, CRF 20, 2 B | mean luma PSNR 43.0 dB (IDR 39.1, P/B 43.2–44.5) | +| 640x360 CBR 1500 kbit/s | 1518 kbit/s (+1.2%) | +| 640x360 1-pass VBR 1000 kbit/s | 981 kbit/s (−1.9%) | +| 640x360 2-pass VBR 800 kbit/s | 840 kbit/s (+5.0%) | + +## Limitations / not yet implemented + +- Single reference frame per list; no B-pyramid, no weighted prediction, no interlaced/MBAFF, no 4:2:2/4:4:4 or + high bit depth, no custom scaling matrices, no sub-8x8 partitions, no B_8x8. +- CAVLC is used only for Baseline (High/Main always use CABAC); 8x8 transform is therefore CABAC-only. +- No trellis quantisation, no psy-RD; no HRD/buffering-period SEI (VBV is enforced internally only). +- Threading is slice-based only (no frame-level pipelining); with many slices, slice edges are not deblocked. diff --git a/crates/h264enc/src/cabac.rs b/crates/h264enc/src/cabac.rs new file mode 100644 index 0000000000..11a3fd76ec --- /dev/null +++ b/crates/h264enc/src/cabac.rs @@ -0,0 +1,344 @@ +//! CABAC arithmetic encoder (ITU-T H.264 §9.3.4.2) and a matching bit-cost estimator. + +use crate::tables::{NUM_CTX, RANGE_LPS, TRANS_LPS}; + +/// A sink for CABAC bins. Implemented by the real arithmetic coder and by the rate estimator, so the same +/// syntax-element code drives both bitstream writing and RD cost estimation. +pub trait BinSink { + fn decision(&mut self, ctx: usize, bin: u32); + fn bypass(&mut self, bin: u32); + fn terminate(&mut self, bin: u32); +} + +/// Context states: `pStateIdx << 1 | valMPS`. +#[derive(Clone)] +pub struct Contexts(pub [u8; NUM_CTX]); + +impl Contexts { + pub fn init(table: &[(i8, i8); NUM_CTX], slice_qp: i32) -> Self { + let qp = slice_qp.clamp(0, 51); + let mut s = [0u8; NUM_CTX]; + for (i, &(m, n)) in table.iter().enumerate() { + let pre = (((m as i32) * qp) >> 4) + n as i32; + let pre = pre.clamp(1, 126); + s[i] = if pre <= 63 { ((63 - pre) as u8) << 1 } else { (((pre - 64) as u8) << 1) | 1 }; + } + Contexts(s) + } +} + +/// The arithmetic encoder writing into a byte buffer. +/// +/// `low` holds the 10-bit coding window plus `queue + 8` pending bits above it; whole bytes are emitted +/// as soon as they are complete. A pending byte of 0xff may still receive a carry, so runs of them are +/// counted in `outstanding` until a byte that is not 0xff resolves the carry. +#[derive(Clone)] +pub struct CabacEncoder { + pub ctx: Contexts, + low: u32, + range: u32, + queue: i32, + outstanding: usize, + buf: Vec, +} + +impl CabacEncoder { + /// Start encoding after a slice header; `header` must be byte aligned (cabac_alignment_one_bits written). + pub fn new(ctx: Contexts, header: Vec) -> Self { + // queue = -9: the first bit out of the window is the spec's discarded first bit, which is always 0. + CabacEncoder { ctx, low: 0, range: 510, queue: -9, outstanding: 0, buf: header } + } + + #[inline] + fn put_byte(&mut self) { + if self.queue >= 0 { + let out = self.low >> (self.queue + 10); + self.low &= (0x400 << self.queue) - 1; + self.queue -= 8; + if out & 0xff == 0xff { + self.outstanding += 1; + } else { + let carry = out >> 8; + if let Some(last) = self.buf.last_mut() { + *last = last.wrapping_add(carry as u8); + } + let fill = (carry as u8).wrapping_sub(1); + self.buf.extend(std::iter::repeat_n(fill, self.outstanding)); + self.buf.push(out as u8); + self.outstanding = 0; + } + } + } + + #[inline] + fn renorm(&mut self) { + let shift = self.range.leading_zeros() - 23; + self.range <<= shift; + self.low <<= shift; + self.queue += shift as i32; + self.put_byte(); + } + + /// Terminate with end_of_slice_flag = 1 and flush; returns the RBSP bytes (with rbsp trailing bits). + pub fn finish(mut self) -> Vec { + self.terminate(1); + // EncodeFlush: the window's top two bits, then the stop bit, then zeros to byte alignment. + let bits = self.queue + 11; + let pad = (8 - bits % 8) % 8; + self.low = ((self.low >> 7) | 1) << (pad + 10); + self.queue = bits + pad - 8; + while self.queue >= 0 { + self.put_byte(); + } + self.buf.extend(std::iter::repeat_n(0xff, self.outstanding)); + self.buf + } +} + +impl BinSink for CabacEncoder { + #[inline] + fn decision(&mut self, ctx: usize, bin: u32) { + let s = self.ctx.0[ctx]; + let state = (s >> 1) as usize; + let mps = (s & 1) as u32; + let lps = RANGE_LPS[state][((self.range >> 6) & 3) as usize] as u32; + self.range -= lps; + if bin != mps { + self.low += self.range; + self.range = lps; + let new_mps = if state == 0 { 1 - mps } else { mps }; + self.ctx.0[ctx] = (TRANS_LPS[state] << 1) | new_mps as u8; + } else if state < 62 { + self.ctx.0[ctx] = (((state + 1) as u8) << 1) | mps as u8; + } + self.renorm(); + } + + #[inline] + fn bypass(&mut self, bin: u32) { + self.low <<= 1; + if bin != 0 { + self.low += self.range; + } + self.queue += 1; + self.put_byte(); + } + + fn terminate(&mut self, bin: u32) { + self.range -= 2; + if bin != 0 { + self.low += self.range; + self.range = 2; + } + self.renorm(); + } +} + +/// Fractional-bit cost table: `COST[state][is_mps]` in 1/256 bit units. +pub struct CostTable { + pub cost: [[u32; 2]; 64], +} + +impl CostTable { + pub fn new() -> Self { + let mut cost = [[0u32; 2]; 64]; + // Probability of the LPS for each state, estimated from rangeTabLPS relative to a mid-range interval. + for (s, c) in cost.iter_mut().enumerate() { + let avg: f64 = RANGE_LPS[s].iter().map(|&v| v as f64).sum::() / 4.0; + let p_lps = (avg / 384.0).clamp(1e-4, 0.5); + c[0] = (-(p_lps.log2()) * 256.0).round() as u32; + c[1] = (-((1.0 - p_lps).log2()) * 256.0).round() as u32; + } + CostTable { cost } + } +} + +impl Default for CostTable { + fn default() -> Self { + Self::new() + } +} + +/// Rate estimator: tracks context states like the encoder and accumulates cost in 1/256 bits. +#[derive(Clone)] +pub struct CabacEstimator<'a> { + pub ctx: Contexts, + pub cost: u64, + table: &'a CostTable, +} + +impl<'a> CabacEstimator<'a> { + pub fn new(ctx: Contexts, table: &'a CostTable) -> Self { + CabacEstimator { ctx, cost: 0, table } + } +} + +impl BinSink for CabacEstimator<'_> { + #[inline] + fn decision(&mut self, ctx: usize, bin: u32) { + let s = self.ctx.0[ctx]; + let state = (s >> 1) as usize; + let mps = (s & 1) as u32; + if bin != mps { + self.cost += self.table.cost[state][0] as u64; + let new_mps = if state == 0 { 1 - mps } else { mps }; + self.ctx.0[ctx] = (TRANS_LPS[state] << 1) | new_mps as u8; + } else { + self.cost += self.table.cost[state][1] as u64; + if state < 62 { + self.ctx.0[ctx] = (((state + 1) as u8) << 1) | mps as u8; + } + } + } + #[inline] + fn bypass(&mut self, _bin: u32) { + self.cost += 256; + } + fn terminate(&mut self, bin: u32) { + self.cost += if bin != 0 { 7 * 256 } else { 2 }; + } +} + +#[cfg(test)] +mod tests { + use super::*; + + /// A reference CABAC decoder (§9.3.1.2 / 9.3.3.2) used only to round-trip-test the encoder. + struct Dec<'a> { + data: &'a [u8], + pos: usize, + range: u32, + offset: u32, + ctx: Contexts, + } + impl<'a> Dec<'a> { + fn bit(&mut self) -> u32 { + let b = if self.pos / 8 < self.data.len() { (self.data[self.pos / 8] >> (7 - self.pos % 8)) & 1 } else { 0 }; + self.pos += 1; + b as u32 + } + fn new(data: &'a [u8], ctx: Contexts) -> Self { + let mut d = Dec { data, pos: 0, range: 510, offset: 0, ctx }; + for _ in 0..9 { + d.offset = (d.offset << 1) | d.bit(); + } + d + } + fn decision(&mut self, c: usize) -> u32 { + let s = self.ctx.0[c]; + let state = (s >> 1) as usize; + let mps = (s & 1) as u32; + let lps = RANGE_LPS[state][((self.range >> 6) & 3) as usize] as u32; + self.range -= lps; + let bin; + if self.offset >= self.range { + bin = 1 - mps; + self.offset -= self.range; + self.range = lps; + let new_mps = if state == 0 { 1 - mps } else { mps }; + self.ctx.0[c] = (TRANS_LPS[state] << 1) | new_mps as u8; + } else { + bin = mps; + if state < 62 { + self.ctx.0[c] = (((state + 1) as u8) << 1) | mps as u8; + } + } + while self.range < 256 { + self.range <<= 1; + self.offset = (self.offset << 1) | self.bit(); + } + bin + } + fn bypass(&mut self) -> u32 { + self.offset = (self.offset << 1) | self.bit(); + if self.offset >= self.range { + self.offset -= self.range; + 1 + } else { + 0 + } + } + fn terminate(&mut self) -> u32 { + self.range -= 2; + if self.offset >= self.range { + 1 + } else { + while self.range < 256 { + self.range <<= 1; + self.offset = (self.offset << 1) | self.bit(); + } + 0 + } + } + } + + fn roundtrip(n: usize, seed: u32) { + let table = crate::tables::cabac_init_pb0(); + let ctx = Contexts::init(&table, 26); + let mut enc = CabacEncoder::new(ctx.clone(), vec![0xa5]); + let mut seed = seed; + let mut rnd = || { + seed = seed.wrapping_mul(1664525).wrapping_add(1013904223); + seed >> 8 + }; + let mut ops = Vec::new(); + for _ in 0..n { + let kind = rnd() % 10; + let c = (rnd() % 400) as usize; + // skewed bins so contexts adapt + let bin = if rnd() % 100 < 80 { (c % 2) as u32 } else { 1 - (c % 2) as u32 }; + if kind < 7 { + enc.decision(c, bin); + ops.push((0, c, bin)); + } else if kind < 9 { + enc.bypass(bin); + ops.push((1, 0, bin)); + } else { + enc.terminate(0); + ops.push((2, 0, 0)); + } + } + let data = enc.finish(); + assert_eq!(data[0], 0xa5, "header byte changed"); + let mut dec = Dec::new(&data[1..], ctx); + for (i, &(k, c, bin)) in ops.iter().enumerate() { + let got = match k { + 0 => dec.decision(c), + 1 => dec.bypass(), + _ => dec.terminate(), + }; + assert_eq!(got, bin, "n {n} op {i}"); + } + assert_eq!(dec.terminate(), 1); + assert!(dec.pos.div_ceil(8) < data.len(), "n {n}: decoder read past the end"); + } + + #[test] + fn roundtrip_random_bins() { + roundtrip(20000, 12345); + } + + #[test] + fn roundtrip_short_streams() { + for n in 0..300 { + roundtrip(n, n as u32 * 7919 + 1); + } + } + + #[test] + fn long_bypass_runs_and_carries() { + let ctx = Contexts::init(&crate::tables::cabac_init_pb0(), 26); + for bin in 0..2 { + let mut enc = CabacEncoder::new(ctx.clone(), Vec::new()); + for _ in 0..5000 { + enc.bypass(bin); + } + let data = enc.finish(); + let mut dec = Dec::new(&data, ctx.clone()); + for i in 0..5000 { + assert_eq!(dec.bypass(), bin, "bin {bin} op {i}"); + } + assert_eq!(dec.terminate(), 1); + } + } +} diff --git a/crates/h264enc/src/cabac_mb.rs b/crates/h264enc/src/cabac_mb.rs new file mode 100644 index 0000000000..a4995573c1 --- /dev/null +++ b/crates/h264enc/src/cabac_mb.rs @@ -0,0 +1,446 @@ +//! CABAC binarisation and context selection for macroblock-layer syntax elements (§9.3.2, §9.3.3.1). + +use crate::cabac::BinSink; +use crate::mbinfo::{MbInfo, MbKind, Part}; +use crate::nal::SliceType; +use crate::syntax::{MbCode, NbCtx}; +use crate::tables::{BLK4_XY, LAST8X8, SIG8X8_FRAME}; + +pub fn write_skip_flag(s: &mut S, nb: &NbCtx, skip: bool) { + let cond = |m: Option<&MbInfo>| m.is_some_and(|m| !m.kind.is_skip()) as usize; + let base = if nb.slice_type == SliceType::B { 24 } else { 11 }; + s.decision(base + cond(nb.left) + cond(nb.top), skip as u32); +} + +fn eg_bypass(s: &mut S, mut v: u32, mut k: u32) { + loop { + if v >= (1 << k) { + s.bypass(1); + v -= 1 << k; + k += 1; + } else { + s.bypass(0); + while k > 0 { + k -= 1; + s.bypass((v >> k) & 1); + } + break; + } + } +} + +/// I16x16 / I_NxN mb_type bins. `off` = ctxIdxOffset of the (suffix) table; `i_slice` selects I-slice contexts. +fn write_intra_type(s: &mut S, nb: &NbCtx, c: &MbCode) { + let i_slice = nb.slice_type == SliceType::I; + let (off, b0ctx) = match nb.slice_type { + SliceType::I => { + let cond = |m: Option<&MbInfo>| m.is_some_and(|m| !m.kind.is_inxn()) as usize; + (3, 3 + cond(nb.left) + cond(nb.top)) + } + SliceType::P => { + s.decision(14, 1); // prefix: intra + (17, 17) + } + SliceType::B => { + // prefix 111101 + write_b_bins(s, nb, &[1, 1, 1, 1, 0, 1]); + (32, 32) + } + }; + if c.kind != MbKind::I16x16 { + s.decision(b0ctx, 0); + return; + } + s.decision(b0ctx, 1); + s.terminate(0); + let luma = (c.cbp & 15 != 0) as u32; + let chroma = (c.cbp >> 4) as u32; + let pred = c.i16_mode as u32; + if i_slice { + s.decision(off + 3, luma); + s.decision(off + 4, (chroma != 0) as u32); + if chroma != 0 { + s.decision(off + 5, (chroma == 2) as u32); + s.decision(off + 6, pred >> 1); + s.decision(off + 7, pred & 1); + } else { + s.decision(off + 6, pred >> 1); + s.decision(off + 7, pred & 1); + } + } else { + s.decision(off + 1, luma); + s.decision(off + 2, (chroma != 0) as u32); + if chroma != 0 { + s.decision(off + 2, (chroma == 2) as u32); + } + s.decision(off + 3, pred >> 1); + s.decision(off + 3, pred & 1); + } +} + +fn write_b_bins(s: &mut S, nb: &NbCtx, bins: &[u32]) { + for (i, &b) in bins.iter().enumerate() { + let ctx = match i { + 0 => { + let cond = |m: Option<&MbInfo>| m.is_some_and(|m| !matches!(m.kind, MbKind::BSkip | MbKind::BDirect)) as usize; + 27 + cond(nb.left) + cond(nb.top) + } + 1 => 27 + 3, + 2 => 27 + if bins[1] != 0 { 4 } else { 5 }, + _ => 27 + 5, + }; + s.decision(ctx, b); + } +} + +const B_BINS: [&[u32]; 23] = [ + &[0], + &[1, 0, 0], + &[1, 0, 1], + &[1, 1, 0, 0, 0, 0], + &[1, 1, 0, 0, 0, 1], + &[1, 1, 0, 0, 1, 0], + &[1, 1, 0, 0, 1, 1], + &[1, 1, 0, 1, 0, 0], + &[1, 1, 0, 1, 0, 1], + &[1, 1, 0, 1, 1, 0], + &[1, 1, 0, 1, 1, 1], + &[1, 1, 1, 1, 1, 0], + &[1, 1, 1, 0, 0, 0, 0], + &[1, 1, 1, 0, 0, 0, 1], + &[1, 1, 1, 0, 0, 1, 0], + &[1, 1, 1, 0, 0, 1, 1], + &[1, 1, 1, 0, 1, 0, 0], + &[1, 1, 1, 0, 1, 0, 1], + &[1, 1, 1, 0, 1, 1, 0], + &[1, 1, 1, 0, 1, 1, 1], + &[1, 1, 1, 1, 0, 0, 0], + &[1, 1, 1, 1, 0, 0, 1], + &[1, 1, 1, 1, 1, 1], +]; + +fn write_mvd(s: &mut S, comp: usize, v: i32, sum: u32) { + let base = if comp == 0 { 40 } else { 47 }; + let a = v.unsigned_abs(); + let inc = if sum < 3 { + 0 + } else if sum > 32 { + 2 + } else { + 1 + }; + let u = a.min(9); + s.decision(base + inc, (u > 0) as u32); + if u > 0 { + for k in 1..9u32 { + let ctx = base + [3, 4, 5, 6, 6, 6, 6, 6][(k - 1) as usize]; + if k < u { + s.decision(ctx, 1); + } else { + s.decision(ctx, 0); + break; + } + } + if a >= 9 { + eg_bypass(s, a - 9, 3); + } + s.bypass((v < 0) as u32); + } +} + +/// Sum of |mvd| of neighbours A and B for the 4x4 block at raster `r` (current MB info already holds earlier partitions). +fn mvd_sum(nb: &NbCtx, r: usize, list: usize, comp: usize) -> u32 { + let (x, y) = (r & 3, r >> 2); + let a = if x > 0 { nb.cur.mvd[list][r - 1][comp] } else { nb.left.map_or(0, |m| m.mvd[list][y * 4 + 3][comp]) }; + let b = if y > 0 { nb.cur.mvd[list][r - 4][comp] } else { nb.top.map_or(0, |m| m.mvd[list][12 + x][comp]) }; + a as u32 + b as u32 +} + +fn write_cbp(s: &mut S, nb: &NbCtx, cbp: u8) { + let luma_bit = |m: &MbInfo, b8: usize| (m.cbp >> b8) & 1; + for b8 in 0..4usize { + // (A, B) neighbour 8x8 blocks: (Some(mb), idx) where None = current MB + let cond = |m: Option<&MbInfo>, idx: usize, in_cur: bool| -> usize { + if in_cur { + ((cbp >> idx) & 1 == 0) as usize + } else { + match m { + None => 0, + Some(m) => (luma_bit(m, idx) == 0) as usize, + } + } + }; + let (ca, cb) = match b8 { + 0 => (cond(nb.left, 1, false), cond(nb.top, 2, false)), + 1 => (cond(None, 0, true), cond(nb.top, 3, false)), + 2 => (cond(nb.left, 3, false), cond(None, 0, true)), + _ => (cond(None, 2, true), cond(None, 1, true)), + }; + s.decision(73 + ca + 2 * cb, ((cbp >> b8) & 1) as u32); + } + let chroma = cbp >> 4; + let cc = |m: Option<&MbInfo>, two: bool| -> usize { + m.is_some_and(|m| { + let c = m.cbp >> 4; + !m.kind.is_skip() && if two { c == 2 } else { c != 0 } + }) as usize + }; + s.decision(77 + cc(nb.left, false) + 2 * cc(nb.top, false), (chroma != 0) as u32); + if chroma != 0 { + s.decision(77 + 4 + cc(nb.left, true) + 2 * cc(nb.top, true), (chroma == 2) as u32); + } +} + +#[derive(Clone, Copy, PartialEq, Eq)] +pub enum Cat { + LumaDc = 0, + LumaAc = 1, + Luma4x4 = 2, + ChromaDc = 3, + ChromaAc = 4, + Luma8x8 = 5, +} + +/// Encode one residual block; `coeffs` are the block's coefficients in scan order (length = maxNumCoeff). +pub fn write_residual(s: &mut S, cat: Cat, coeffs: &[i16], cbf_inc: u32) { + const CBF_OFF: [usize; 5] = [0, 4, 8, 12, 16]; + const SIG_OFF: [usize; 5] = [0, 15, 29, 44, 47]; + const ABS_OFF: [usize; 5] = [0, 10, 20, 30, 39]; + let n = coeffs.len(); + let last = coeffs.iter().rposition(|&c| c != 0); + let ci = cat as usize; + if cat != Cat::Luma8x8 { + s.decision(85 + CBF_OFF[ci] + cbf_inc as usize, last.is_some() as u32); + } + let Some(last) = last else { return }; + let (sig_base, last_base, abs_base) = + if cat == Cat::Luma8x8 { (402, 417, 426) } else { (105 + SIG_OFF[ci], 166 + SIG_OFF[ci], 227 + ABS_OFF[ci]) }; + for i in 0..n - 1 { + let (si, li) = match cat { + Cat::ChromaDc => (i.min(2), i.min(2)), + Cat::Luma8x8 => (SIG8X8_FRAME[i] as usize, LAST8X8[i] as usize), + _ => (i, i), + }; + let sig = coeffs[i] != 0; + s.decision(sig_base + si, sig as u32); + if sig { + let is_last = i == last; + s.decision(last_base + li, is_last as u32); + if is_last { + break; + } + } + } + let mut gt1 = 0usize; + let mut eq1 = 0usize; + let max_gt1 = if cat == Cat::ChromaDc { 3 } else { 4 }; + for i in (0..=last).rev() { + let c = coeffs[i]; + if c == 0 { + continue; + } + let a = c.unsigned_abs() as u32; + let v = a - 1; + let inc0 = if gt1 != 0 { 0 } else { (1 + eq1).min(4) }; + s.decision(abs_base + inc0, (v > 0) as u32); + if v > 0 { + let ctx = abs_base + 5 + gt1.min(max_gt1); + let u = v.min(14); + for _ in 1..u { + s.decision(ctx, 1); + } + if u < 14 { + s.decision(ctx, 0); + } else { + eg_bypass(s, v - 14, 0); + } + } + s.bypass((c < 0) as u32); + if a == 1 { + eq1 += 1; + } else { + gt1 += 1; + } + } +} + +fn cbf_luma(nb: &NbCtx, raster: usize) -> u32 { + let f = |v: Option| v.map_or(nb.unavail_cbf(), |n| (n != 0) as u32); + f(nb.luma_nnz(raster, true)) + 2 * f(nb.luma_nnz(raster, false)) +} + +fn cbf_chroma_ac(nb: &NbCtx, c: usize, b: usize) -> u32 { + let f = |v: Option| v.map_or(nb.unavail_cbf(), |n| (n != 0) as u32); + f(nb.chroma_nnz(c, b, true)) + 2 * f(nb.chroma_nnz(c, b, false)) +} + +fn cbf_dc(nb: &NbCtx, bit: u8) -> u32 { + let f = |m: Option<&MbInfo>| m.map_or(nb.unavail_cbf(), |m| ((m.dc_cbf >> bit) & 1) as u32); + f(nb.left) + 2 * f(nb.top) +} + +/// mb_type (and the P_L0_8x8 sub_mb_types of a P_8x8 macroblock). +fn write_mb_type(s: &mut S, nb: &NbCtx, c: &MbCode) { + match c.kind { + MbKind::I4x4 | MbKind::I8x8 | MbKind::I16x16 => write_intra_type(s, nb, c), + MbKind::PInter => { + let bins: [u32; 3] = match c.part { + Part::P16x16 => [0, 0, 0], + Part::P16x8 => [0, 1, 1], + Part::P8x16 => [0, 1, 0], + Part::P8x8 => [0, 0, 1], + }; + s.decision(14, bins[0]); + s.decision(15, bins[1]); + s.decision(if bins[1] != 1 { 16 } else { 17 }, bins[2]); + } + MbKind::BDirect | MbKind::BInter => write_b_bins(s, nb, B_BINS[c.b_type_number() as usize]), + // Skip / none macroblocks are not written with write_mb. + _ => debug_assert!(false, "write_mb on a skipped macroblock"), + } + if c.kind == MbKind::PInter && c.part == Part::P8x8 { + for _ in 0..4 { + s.decision(21, 1); // P_L0_8x8 + } + } +} + +/// transform_size_8x8_flag. +fn write_transform_size_flag(s: &mut S, nb: &NbCtx, c: &MbCode) { + let cond = |m: Option<&MbInfo>| m.is_some_and(|m| m.t8x8) as usize; + s.decision(399 + cond(nb.left) + cond(nb.top), c.t8x8 as u32); +} + +/// prev_intra_NxN_pred_mode flags / rem_intra_NxN_pred_mode values. +fn write_intra_pred_modes(s: &mut S, c: &MbCode) { + let n = if c.t8x8 { 4 } else { 16 }; + for i in 0..n { + let p = c.ipred[i]; + if p < 0 { + s.decision(68, 1); + } else { + s.decision(68, 0); + s.decision(69, (p & 1) as u32); + s.decision(69, ((p >> 1) & 1) as u32); + s.decision(69, ((p >> 2) & 1) as u32); + } + } +} + +/// intra_chroma_pred_mode. +fn write_chroma_pred_mode(s: &mut S, nb: &NbCtx, c: &MbCode) { + let cond = |m: Option<&MbInfo>| m.is_some_and(|m| m.kind.is_intra() && m.chroma_mode != 0) as usize; + let m = c.chroma_mode as u32; + s.decision(64 + cond(nb.left) + cond(nb.top), (m > 0) as u32); + if m > 0 { + s.decision(64 + 3, (m > 1) as u32); + if m > 1 { + s.decision(64 + 3, (m > 2) as u32); + } + } +} + +/// mvd_lX components of every partition that uses each list. +fn write_mvds(s: &mut S, nb: &NbCtx, c: &MbCode) { + for list in 0..2 { + for p in 0..c.part.count() { + if !c.uses_list(p, list) { + continue; + } + let (px, py, _, _) = c.part.rect(p); + let r = (py / 4) * 4 + px / 4; + let mvd = c.mvd[list][p]; + write_mvd(s, 0, mvd.x as i32, mvd_sum(nb, r, list, 0)); + write_mvd(s, 1, mvd.y as i32, mvd_sum(nb, r, list, 1)); + } + } +} + +/// mb_qp_delta. +fn write_qp_delta(s: &mut S, nb: &NbCtx, v: i32) { + let m = if v > 0 { 2 * v as u32 - 1 } else { (-2 * v) as u32 }; + s.decision(60 + nb.prev_dqp_nz as usize, (m > 0) as u32); + if m > 0 { + for k in 1..=m { + s.decision(if k == 1 { 62 } else { 63 }, (k < m) as u32); + } + } +} + +/// Luma residual blocks of the macroblock. +fn write_luma_residual(s: &mut S, nb: &NbCtx, c: &MbCode) { + if c.kind == MbKind::I16x16 { + write_residual(s, Cat::LumaDc, &c.luma_dc, cbf_dc(nb, 0)); + if c.cbp & 15 != 0 { + for blk in 0..16 { + let (x, y) = BLK4_XY[blk]; + write_residual(s, Cat::LumaAc, &c.luma4[blk][1..16], cbf_luma(nb, y * 4 + x)); + } + } + return; + } + for b8 in 0..4 { + if (c.cbp >> b8) & 1 == 0 { + continue; + } + if c.t8x8 { + write_residual(s, Cat::Luma8x8, &c.luma8[b8], 0); + } else { + for i in 0..4 { + let blk = b8 * 4 + i; + let (x, y) = BLK4_XY[blk]; + write_residual(s, Cat::Luma4x4, &c.luma4[blk], cbf_luma(nb, y * 4 + x)); + } + } + } +} + +/// Chroma DC / AC residual blocks of the macroblock. +fn write_chroma_residual(s: &mut S, nb: &NbCtx, c: &MbCode) { + let chroma = c.cbp >> 4; + if chroma != 0 { + for comp in 0..2 { + write_residual(s, Cat::ChromaDc, &c.cdc[comp], cbf_dc(nb, 1 + comp as u8)); + } + } + if chroma == 2 { + for comp in 0..2 { + for b in 0..4 { + write_residual(s, Cat::ChromaAc, &c.cac[comp * 4 + b][1..16], cbf_chroma_ac(nb, comp, b)); + } + } + } +} + +/// Write the whole macroblock layer (after mb_skip_flag = 0 in P/B slices). +pub fn write_mb(s: &mut S, nb: &NbCtx, c: &MbCode) { + write_mb_type(s, nb, c); + let is_inxn = c.kind.is_inxn(); + // ---- mb_pred + if is_inxn { + if nb.t8x8_mode { + write_transform_size_flag(s, nb, c); + } + write_intra_pred_modes(s, c); + } + if c.kind.is_intra() { + write_chroma_pred_mode(s, nb, c); + } + if matches!(c.kind, MbKind::PInter | MbKind::BInter) { + write_mvds(s, nb, c); + } + // ---- cbp, transform size, qp delta + if c.kind != MbKind::I16x16 { + write_cbp(s, nb, c.cbp); + if c.cbp & 15 != 0 && nb.t8x8_mode && !is_inxn { + write_transform_size_flag(s, nb, c); + } + } + if c.cbp == 0 && c.kind != MbKind::I16x16 { + return; + } + write_qp_delta(s, nb, c.qp_delta); + // ---- residual + write_luma_residual(s, nb, c); + write_chroma_residual(s, nb, c); +} diff --git a/crates/h264enc/src/cavlc.rs b/crates/h264enc/src/cavlc.rs new file mode 100644 index 0000000000..2557d084ee --- /dev/null +++ b/crates/h264enc/src/cavlc.rs @@ -0,0 +1,288 @@ +//! CAVLC residual coding (§9.2) and macroblock-layer syntax for Baseline streams. + +use craft_media::bitstream::BitWriter; + +use crate::mbinfo::{MbKind, Part}; +use crate::nal::SliceType; +use crate::syntax::{MbCode, NbCtx}; +use crate::tables::{ + BLK4_XY, CBP_INTER_TO_CODE, CBP_INTRA_TO_CODE, COEFF_TOKEN_CDC_CODE, COEFF_TOKEN_CDC_LEN, COEFF_TOKEN_CODE, COEFF_TOKEN_LEN, RUN_BEFORE, + TOTAL_ZEROS, TOTAL_ZEROS_CDC, +}; + +/// Largest level magnitude representable with level_prefix <= 15 in every suffixLength state. +pub const MAX_LEVEL: i32 = 2047; + +/// Write one CAVLC residual block. `nc` = -1 for chroma DC. Returns TotalCoeff. +pub fn write_residual(w: &mut BitWriter, coeffs: &[i16], nc: i32) -> u8 { + let max = coeffs.len(); + // levels from highest frequency to lowest + let mut levels = [0i32; 16]; + let mut pos = [0usize; 16]; + let mut total = 0usize; + for i in (0..max).rev() { + if coeffs[i] != 0 { + levels[total] = coeffs[i] as i32; + pos[total] = i; + total += 1; + } + } + let mut t1 = 0usize; + while t1 < total && t1 < 3 && levels[t1].abs() == 1 { + t1 += 1; + } + // coeff_token + if nc == -1 { + w.write_bits(COEFF_TOKEN_CDC_CODE[total][t1] as u32, COEFF_TOKEN_CDC_LEN[total][t1] as u32); + } else if nc >= 8 { + if total == 0 { + w.write_bits(3, 6); + } else { + w.write_bits((((total - 1) << 2) | t1) as u32, 6); + } + } else { + let t = if nc < 2 { + 0 + } else if nc < 4 { + 1 + } else { + 2 + }; + w.write_bits(COEFF_TOKEN_CODE[t][total][t1] as u32, COEFF_TOKEN_LEN[t][total][t1] as u32); + } + if total == 0 { + return 0; + } + let mut suffix_len: u32 = if total > 10 && t1 < 3 { 1 } else { 0 }; + for i in 0..total { + let l = levels[i]; + if i < t1 { + w.write_bit(l < 0); + continue; + } + let mut code = if l > 0 { 2 * l - 2 } else { -2 * l - 1 }; + if i == t1 && t1 < 3 { + code -= 2; + } + let code = code as u32; + if suffix_len == 0 { + if code < 14 { + w.write_bits(1, code + 1); + } else if code < 30 { + w.write_bits(1, 15); + w.write_bits(code - 14, 4); + } else { + write_escape(w, code - 30); + } + } else if code < (15 << suffix_len) { + w.write_bits(1, (code >> suffix_len) + 1); + w.write_bits(code & ((1 << suffix_len) - 1), suffix_len); + } else { + write_escape(w, code - (15 << suffix_len)); + } + if suffix_len == 0 { + suffix_len = 1; + } + if l.unsigned_abs() > (3 << (suffix_len - 1)) && suffix_len < 6 { + suffix_len += 1; + } + } + let total_zeros = pos[0] + 1 - total; + if total < max { + let (len, code) = if nc == -1 { TOTAL_ZEROS_CDC[total - 1][total_zeros] } else { TOTAL_ZEROS[total - 1][total_zeros] }; + w.write_bits(code as u32, len as u32); + } + let mut zeros_left = total_zeros; + for i in 0..total - 1 { + if zeros_left == 0 { + break; + } + let run = pos[i] - pos[i + 1] - 1; + let (len, code) = RUN_BEFORE[zeros_left.min(7) - 1][run]; + w.write_bits(code as u32, len as u32); + zeros_left -= run; + } + total as u8 +} + +/// level_prefix >= 15 escape; `rem` = levelCode minus the prefix-15 base. +fn write_escape(w: &mut BitWriter, rem: u32) { + if rem < 4096 { + w.write_bits(1, 16); + w.write_bits(rem, 12); + } else { + // level_prefix p >= 16 (High profiles): suffix of p-3 bits, value rem + 4096 - 2^(p-3) + let mut p = 16; + while rem + 4096 >= (1 << (p - 3)) * 2 { + p += 1; + } + w.write_bits(0, p); + w.write_bit(true); + w.write_bits(rem + 4096 - (1 << (p - 3)), p - 3); + } +} + +fn nc_of(a: Option, b: Option) -> i32 { + match (a, b) { + (Some(a), Some(b)) => (a as i32 + b as i32 + 1) >> 1, + (Some(a), None) => a as i32, + (None, Some(b)) => b as i32, + _ => 0, + } +} + +/// mb_type (and the P_8x8 sub_mb_types). +fn write_mb_type(w: &mut BitWriter, nb: &NbCtx, c: &MbCode) { + let intra_base = match nb.slice_type { + SliceType::I => 0, + SliceType::P => 5, + SliceType::B => 23, + }; + match c.kind { + MbKind::I4x4 | MbKind::I8x8 => w.write_ue(intra_base), + MbKind::I16x16 => { + let t = 1 + c.i16_mode as u32 + 4 * (c.cbp >> 4) as u32 + if c.cbp & 15 != 0 { 12 } else { 0 }; + w.write_ue(intra_base + t); + } + MbKind::PInter => w.write_ue(match c.part { + Part::P16x16 => 0, + Part::P16x8 => 1, + Part::P8x16 => 2, + Part::P8x8 => 3, + }), + MbKind::BDirect | MbKind::BInter => w.write_ue(c.b_type_number()), + // Skip / none macroblocks are not written with write_mb. + _ => debug_assert!(false, "write_mb on a skipped macroblock"), + } + if c.kind == MbKind::PInter && c.part == Part::P8x8 { + for _ in 0..4 { + w.write_ue(0); + } + } +} + +/// prev_intra_NxN_pred_mode flags / rem_intra_NxN_pred_mode values. +fn write_intra_pred_modes(w: &mut BitWriter, c: &MbCode) { + let n = if c.t8x8 { 4 } else { 16 }; + for i in 0..n { + let p = c.ipred[i]; + if p < 0 { + w.write_bit(true); + } else { + w.write_bit(false); + w.write_bits(p as u32, 3); + } + } +} + +/// coded_block_pattern. +fn write_cbp(w: &mut BitWriter, c: &MbCode) { + let code = if c.kind.is_intra() { CBP_INTRA_TO_CODE[c.cbp as usize] } else { CBP_INTER_TO_CODE[c.cbp as usize] }; + let cbp_idx = (c.cbp & 15) | ((c.cbp >> 4) << 4); + debug_assert_eq!(cbp_idx, c.cbp); + w.write_ue(code as u32); +} + +/// Luma residual blocks of the macroblock. +fn write_luma_residual(w: &mut BitWriter, nb: &NbCtx, c: &MbCode) { + let luma_nc = |r: usize| nc_of(nb.luma_nnz(r, true), nb.luma_nnz(r, false)); + if c.kind == MbKind::I16x16 { + write_residual(w, &c.luma_dc, luma_nc(0)); + if c.cbp & 15 != 0 { + for blk in 0..16 { + let (x, y) = BLK4_XY[blk]; + write_residual(w, &c.luma4[blk][1..16], luma_nc(y * 4 + x)); + } + } + return; + } + for b8 in 0..4 { + if (c.cbp >> b8) & 1 == 0 { + continue; + } + for i in 0..4 { + let blk = b8 * 4 + i; + let (x, y) = BLK4_XY[blk]; + if c.t8x8 { + let mut sub = [0i16; 16]; + for k in 0..16 { + sub[k] = c.luma8[b8][4 * k + i]; + } + write_residual(w, &sub, luma_nc(y * 4 + x)); + } else { + write_residual(w, &c.luma4[blk], luma_nc(y * 4 + x)); + } + } + } +} + +/// Chroma DC / AC residual blocks of the macroblock. +fn write_chroma_residual(w: &mut BitWriter, nb: &NbCtx, c: &MbCode) { + let chroma = c.cbp >> 4; + if chroma != 0 { + for comp in 0..2 { + write_residual(w, &c.cdc[comp], -1); + } + } + if chroma == 2 { + for comp in 0..2 { + for b in 0..4 { + let ncv = nc_of(nb.chroma_nnz(comp, b, true), nb.chroma_nnz(comp, b, false)); + write_residual(w, &c.cac[comp * 4 + b][1..16], ncv); + } + } + } +} + +/// Write the macroblock layer in CAVLC. For P/B slices the caller writes mb_skip_run first. +pub fn write_mb(w: &mut BitWriter, nb: &NbCtx, c: &MbCode) { + write_mb_type(w, nb, c); + if c.kind.is_inxn() { + if nb.t8x8_mode { + w.write_bit(c.t8x8); + } + write_intra_pred_modes(w, c); + } + if c.kind.is_intra() { + w.write_ue(c.chroma_mode as u32); + } + if matches!(c.kind, MbKind::PInter | MbKind::BInter) { + for list in 0..2 { + for p in 0..c.part.count() { + if c.uses_list(p, list) { + w.write_se(c.mvd[list][p].x as i32); + w.write_se(c.mvd[list][p].y as i32); + } + } + } + } + if c.kind != MbKind::I16x16 { + write_cbp(w, c); + if c.cbp & 15 != 0 && nb.t8x8_mode && !c.kind.is_inxn() { + w.write_bit(c.t8x8); + } + } + if c.cbp == 0 && c.kind != MbKind::I16x16 { + return; + } + w.write_se(c.qp_delta); + write_luma_residual(w, nb, c); + write_chroma_residual(w, nb, c); +} + +#[cfg(test)] +mod tests { + use super::*; + + #[test] + fn simple_block_matches_spec_example() { + // scan: 0,3,-1,0,0,-1,1,0,1 -> TotalCoeff 5, T1s 3. + let coeffs: [i16; 16] = [0, 3, -1, 0, 0, -1, 1, 0, 1, 0, 0, 0, 0, 0, 0, 0]; + let mut w = BitWriter::new(); + let t = write_residual(&mut w, &coeffs, 0); + assert_eq!(t, 5); + // coeff_token 0000100 | signs 001 | level -1: 01 | level 3: 001 0 | total_zeros(4) 110 | runs 10 11 01 1 + let bits: String = w.finish().iter().map(|b| format!("{b:08b}")).collect(); + assert!(bits.starts_with("00001000010100101101011011"), "{bits}"); + } +} diff --git a/crates/h264enc/src/deblock.rs b/crates/h264enc/src/deblock.rs new file mode 100644 index 0000000000..2e18995844 --- /dev/null +++ b/crates/h264enc/src/deblock.rs @@ -0,0 +1,263 @@ +//! In-loop deblocking filter (§8.7) for frame-coded 4:2:0 pictures without MBAFF. +//! +//! Operates on a band of macroblock rows that begins at a slice boundary; the top edge of the band is +//! never filtered (slices are MB-row aligned and either there is one slice, or +//! `disable_deblocking_filter_idc = 2` is used). + +use crate::mbinfo::{MbInfo, Mv}; +use crate::tables::{ALPHA, BETA, TC0, chroma_qp}; + +pub struct DeblockParams { + pub alpha_offset: i32, + pub beta_offset: i32, + pub chroma_qp_offset: i32, + /// Picture identity for (list, ref_idx 0). + pub ref_ids: [i32; 2], +} + +/// A mutable band of rows of a padded plane: sample (x, y) with y relative to the band top. +pub struct BandPlane<'a> { + pub data: &'a mut [u8], + pub stride: usize, + pub pad: usize, +} + +impl BandPlane<'_> { + #[inline] + fn idx(&self, x: usize, y: usize) -> usize { + y * self.stride + self.pad + x + } +} + +#[inline] +fn nz8(m: &MbInfo, blk: usize) -> bool { + if m.t8x8 { + let (x, y) = ((blk & 3) >> 1, (blk >> 2) >> 1); + let b = y * 8 + x * 2; + m.nnz[b] | m.nnz[b + 1] | m.nnz[b + 4] | m.nnz[b + 5] != 0 + } else { + m.nnz[blk] != 0 + } +} + +#[inline] +fn mvdiff(a: Mv, b: Mv) -> bool { + (a.x as i32 - b.x as i32).abs() >= 4 || (a.y as i32 - b.y as i32).abs() >= 4 +} + +fn bs_inter(p: &MbInfo, ip: usize, q: &MbInfo, iq: usize, ids: &[i32; 2]) -> u8 { + if nz8(p, ip) || nz8(q, iq) { + return 2; + } + let rp = [p.ref_at(0, ip), p.ref_at(1, ip)]; + let rq = [q.ref_at(0, iq), q.ref_at(1, iq)]; + // Only ref_idx 0 is ever used per list, so the list index identifies the picture. + let pic = |r: i8, l: usize| if r >= 0 { ids[l] } else { -1 }; + let pp = [pic(rp[0], 0), pic(rp[1], 1)]; + let pq = [pic(rq[0], 0), pic(rq[1], 1)]; + let np = (pp[0] >= 0) as u8 + (pp[1] >= 0) as u8; + let nq = (pq[0] >= 0) as u8 + (pq[1] >= 0) as u8; + if np != nq { + return 1; + } + if np == 1 { + let (a, ma) = if pp[0] >= 0 { (pp[0], p.mv[0][ip]) } else { (pp[1], p.mv[1][ip]) }; + let (b, mb) = if pq[0] >= 0 { (pq[0], q.mv[0][iq]) } else { (pq[1], q.mv[1][iq]) }; + if a != b { + return 1; + } + return mvdiff(ma, mb) as u8; + } + if np == 0 { + return 0; + } + // two motion vectors each + let (mp0, mp1, mq0, mq1) = (p.mv[0][ip], p.mv[1][ip], q.mv[0][iq], q.mv[1][iq]); + let same_set = (pp[0] == pq[0] && pp[1] == pq[1]) || (pp[0] == pq[1] && pp[1] == pq[0]); + if !same_set { + return 1; + } + if pp[0] != pp[1] { + // two different pictures: compare mvs referring to the same picture + if pp[0] == pq[0] { (mvdiff(mp0, mq0) || mvdiff(mp1, mq1)) as u8 } else { (mvdiff(mp0, mq1) || mvdiff(mp1, mq0)) as u8 } + } else { + ((mvdiff(mp0, mq0) || mvdiff(mp1, mq1)) && (mvdiff(mp0, mq1) || mvdiff(mp1, mq0))) as u8 + } +} + +#[inline] +fn bs_of(p: &MbInfo, ip: usize, q: &MbInfo, iq: usize, mb_edge: bool, ids: &[i32; 2]) -> u8 { + if p.kind.is_intra() || q.kind.is_intra() { + return if mb_edge { 4 } else { 3 }; + } + bs_inter(p, ip, q, iq, ids) +} + +#[inline] +fn filter_luma(d: &mut [u8], q0i: usize, step: usize, bs: u8, alpha: i32, beta: i32, tc0: i32) { + let p0 = d[q0i - step] as i32; + let q0 = d[q0i] as i32; + let p1 = d[q0i - 2 * step] as i32; + let q1 = d[q0i + step] as i32; + if (p0 - q0).abs() >= alpha || (p1 - p0).abs() >= beta || (q1 - q0).abs() >= beta { + return; + } + let p2 = d[q0i - 3 * step] as i32; + let q2 = d[q0i + 2 * step] as i32; + let ap = (p2 - p0).abs(); + let aq = (q2 - q0).abs(); + if bs < 4 { + let tc = tc0 + (ap < beta) as i32 + (aq < beta) as i32; + let delta = ((((q0 - p0) << 2) + (p1 - q1) + 4) >> 3).clamp(-tc, tc); + d[q0i - step] = (p0 + delta).clamp(0, 255) as u8; + d[q0i] = (q0 - delta).clamp(0, 255) as u8; + if ap < beta { + d[q0i - 2 * step] = (p1 + ((p2 + ((p0 + q0 + 1) >> 1) - (p1 << 1)) >> 1).clamp(-tc0, tc0)) as u8; + } + if aq < beta { + d[q0i + step] = (q1 + ((q2 + ((p0 + q0 + 1) >> 1) - (q1 << 1)) >> 1).clamp(-tc0, tc0)) as u8; + } + } else { + let p3 = d[q0i - 4 * step] as i32; + let q3 = d[q0i + 3 * step] as i32; + let strong = (p0 - q0).abs() < ((alpha >> 2) + 2); + if ap < beta && strong { + d[q0i - step] = ((p2 + 2 * p1 + 2 * p0 + 2 * q0 + q1 + 4) >> 3) as u8; + d[q0i - 2 * step] = ((p2 + p1 + p0 + q0 + 2) >> 2) as u8; + d[q0i - 3 * step] = ((2 * p3 + 3 * p2 + p1 + p0 + q0 + 4) >> 3) as u8; + } else { + d[q0i - step] = ((2 * p1 + p0 + q1 + 2) >> 2) as u8; + } + if aq < beta && strong { + d[q0i] = ((p1 + 2 * p0 + 2 * q0 + 2 * q1 + q2 + 4) >> 3) as u8; + d[q0i + step] = ((p0 + q0 + q1 + q2 + 2) >> 2) as u8; + d[q0i + 2 * step] = ((2 * q3 + 3 * q2 + q1 + q0 + p0 + 4) >> 3) as u8; + } else { + d[q0i] = ((2 * q1 + q0 + p1 + 2) >> 2) as u8; + } + } +} + +#[inline] +fn filter_chroma(d: &mut [u8], q0i: usize, step: usize, bs: u8, alpha: i32, beta: i32, tc0: i32) { + let p0 = d[q0i - step] as i32; + let q0 = d[q0i] as i32; + let p1 = d[q0i - 2 * step] as i32; + let q1 = d[q0i + step] as i32; + if (p0 - q0).abs() >= alpha || (p1 - p0).abs() >= beta || (q1 - q0).abs() >= beta { + return; + } + if bs < 4 { + let tc = tc0 + 1; + let delta = ((((q0 - p0) << 2) + (p1 - q1) + 4) >> 3).clamp(-tc, tc); + d[q0i - step] = (p0 + delta).clamp(0, 255) as u8; + d[q0i] = (q0 - delta).clamp(0, 255) as u8; + } else { + d[q0i - step] = ((2 * p1 + p0 + q1 + 2) >> 2) as u8; + d[q0i] = ((2 * q1 + q0 + p1 + 2) >> 2) as u8; + } +} + +struct EdgeQ { + alpha: i32, + beta: i32, + idx_a: usize, +} + +fn edge_q(qp_p: i32, qp_q: i32, dp: &DeblockParams) -> EdgeQ { + let qav = (qp_p + qp_q + 1) >> 1; + let ia = (qav + dp.alpha_offset).clamp(0, 51) as usize; + let ib = (qav + dp.beta_offset).clamp(0, 51) as usize; + EdgeQ { alpha: ALPHA[ia] as i32, beta: BETA[ib] as i32, idx_a: ia } +} + +/// Deblock `rows` macroblock rows of a band. `mbs` holds the band's MbInfo (row-major, `mbw` wide). +pub fn deblock_band<'a>( + y: &mut BandPlane<'a>, + u: &mut BandPlane<'a>, + v: &mut BandPlane<'a>, + mbs: &[MbInfo], + mbw: usize, + rows: usize, + dp: &DeblockParams, +) { + let ids = &dp.ref_ids; + for my in 0..rows { + for mx in 0..mbw { + let q = &mbs[my * mbw + mx]; + let cqp = |m: &MbInfo| chroma_qp(m.qp as i32 + dp.chroma_qp_offset) as i32; + // ---- vertical edges (dir 0) then horizontal (dir 1) + for dir in 0..2 { + let mut bs_all = [[0u8; 4]; 4]; + for e in 0..4 { + if e == 0 && ((dir == 0 && mx == 0) || (dir == 1 && my == 0)) { + continue; + } + if e & 1 == 1 && q.t8x8 { + continue; + } + let p_mb = if e == 0 { if dir == 0 { &mbs[my * mbw + mx - 1] } else { &mbs[(my - 1) * mbw + mx] } } else { q }; + let mut bs = [0u8; 4]; + for s in 0..4 { + let (iq, ip) = if dir == 0 { + let iq = s * 4 + e; + (iq, if e == 0 { s * 4 + 3 } else { iq - 1 }) + } else { + let iq = e * 4 + s; + (iq, if e == 0 { 12 + s } else { iq - 4 }) + }; + bs[s] = bs_of(p_mb, ip, q, iq, e == 0, ids); + } + bs_all[e] = bs; + if bs == [0; 4] { + continue; + } + let eq = edge_q(p_mb.qp as i32, q.qp as i32, dp); + if eq.alpha == 0 || eq.beta == 0 { + continue; + } + for k in 0..16 { + let b = bs[k >> 2]; + if b == 0 { + continue; + } + let tc0 = if b < 4 { TC0[eq.idx_a][b as usize - 1] as i32 } else { 0 }; + let (qi, step) = + if dir == 0 { (y.idx(mx * 16 + e * 4, my * 16 + k), 1) } else { (y.idx(mx * 16 + k, my * 16 + e * 4), y.stride) }; + filter_luma(y.data, qi, step, b, eq.alpha, eq.beta, tc0); + } + } + // chroma edges 0 and 2 (chroma sample offsets 0 and 4) + for e in [0usize, 2] { + if e == 0 && ((dir == 0 && mx == 0) || (dir == 1 && my == 0)) { + continue; + } + let bs = bs_all[e]; + if bs == [0; 4] { + continue; + } + let p_mb = if e == 0 { if dir == 0 { &mbs[my * mbw + mx - 1] } else { &mbs[(my - 1) * mbw + mx] } } else { q }; + let eq = edge_q(cqp(p_mb), cqp(q), dp); + if eq.alpha == 0 || eq.beta == 0 { + continue; + } + for plane in [&mut *u, &mut *v] { + for k in 0..8 { + let b = bs[k >> 1]; + if b == 0 { + continue; + } + let tc0 = if b < 4 { TC0[eq.idx_a][b as usize - 1] as i32 } else { 0 }; + let (qi, step) = if dir == 0 { + (plane.idx(mx * 8 + e * 2, my * 8 + k), 1) + } else { + (plane.idx(mx * 8 + k, my * 8 + e * 2), plane.stride) + }; + filter_chroma(plane.data, qi, step, b, eq.alpha, eq.beta, tc0); + } + } + } + } + } + } +} diff --git a/crates/h264enc/src/dsp.rs b/crates/h264enc/src/dsp.rs new file mode 100644 index 0000000000..3baecc91dd --- /dev/null +++ b/crates/h264enc/src/dsp.rs @@ -0,0 +1,255 @@ +//! Distortion metrics: SAD, SATD (4x4 Hadamard), SSD. + +#[inline] +pub fn sad(a: &[u8], sa: usize, b: &[u8], sb: usize, w: usize, h: usize) -> u32 { + let mut s = 0u32; + for r in 0..h { + let ra = &a[r * sa..r * sa + w]; + let rb = &b[r * sb..r * sb + w]; + s += ra.iter().zip(rb).map(|(&x, &y)| x.abs_diff(y) as u32).sum::(); + } + s +} + +/// SAD of a 16-wide block (fast path). +#[inline] +pub fn sad16(a: &[u8], sa: usize, b: &[u8], sb: usize, h: usize) -> u32 { + sad_n::<16>(a, sa, b, sb, h) +} + +/// SAD of an 8-wide block (fast path). +#[inline] +pub fn sad8(a: &[u8], sa: usize, b: &[u8], sb: usize, h: usize) -> u32 { + sad_n::<8>(a, sa, b, sb, h) +} + +#[inline(always)] +fn sad_n(a: &[u8], sa: usize, b: &[u8], sb: usize, h: usize) -> u32 { + let mut s = 0u32; + for r in 0..h { + let (Some(ra), Some(rb)) = (a.get(r * sa..).and_then(|x| x.first_chunk::()), b.get(r * sb..).and_then(|x| x.first_chunk::())) else { + break; + }; + let mut acc = 0u16; + for i in 0..N { + acc += ra[i].abs_diff(rb[i]) as u16; + } + s += acc as u32; + } + s +} + +#[cfg(test)] +fn hadamard4x4_sum(d: &[i32; 16]) -> u32 { + let mut t = [0i32; 16]; + for i in 0..4 { + let a0 = d[i * 4] + d[i * 4 + 1]; + let a1 = d[i * 4] - d[i * 4 + 1]; + let a2 = d[i * 4 + 2] + d[i * 4 + 3]; + let a3 = d[i * 4 + 2] - d[i * 4 + 3]; + t[i * 4] = a0 + a2; + t[i * 4 + 1] = a1 + a3; + t[i * 4 + 2] = a0 - a2; + t[i * 4 + 3] = a1 - a3; + } + let mut s = 0u32; + for i in 0..4 { + let a0 = t[i] + t[4 + i]; + let a1 = t[i] - t[4 + i]; + let a2 = t[8 + i] + t[12 + i]; + let a3 = t[8 + i] - t[12 + i]; + s += (a0 + a2).unsigned_abs() + (a1 + a3).unsigned_abs() + (a0 - a2).unsigned_abs() + (a1 - a3).unsigned_abs(); + } + s +} + +/// SATD of a strip of `N` columns (N/4 side-by-side 4x4 blocks) and 4 rows, computed lane-wise so the compiler +/// can vectorise it. Uses |p+q| + |p-q| = 2 max(|p|, |q|) for the last butterfly, which also absorbs the /2. +#[inline(always)] +fn satd_strip(a: &[u8], sa: usize, b: &[u8], sb: usize) -> u32 { + let mut d = [[0i16; N]; 4]; + for r in 0..4 { + let ra = &a[r * sa..r * sa + N]; + let rb = &b[r * sb..r * sb + N]; + for i in 0..N { + d[r][i] = ra[i] as i16 - rb[i] as i16; + } + } + let mut v = [[0i16; N]; 4]; + for i in 0..N { + let s01 = d[0][i] + d[1][i]; + let d01 = d[0][i] - d[1][i]; + let s23 = d[2][i] + d[3][i]; + let d23 = d[2][i] - d[3][i]; + v[0][i] = s01 + s23; + v[1][i] = s01 - s23; + v[2][i] = d01 + d23; + v[3][i] = d01 - d23; + } + let mut total = 0u32; + for row in &v { + let mut hs = [0i16; H]; + let mut hd = [0i16; H]; + for k in 0..H { + hs[k] = row[2 * k] + row[2 * k + 1]; + hd[k] = row[2 * k] - row[2 * k + 1]; + } + for k in 0..H / 2 { + total += hs[2 * k].unsigned_abs().max(hs[2 * k + 1].unsigned_abs()) as u32; + total += hd[2 * k].unsigned_abs().max(hd[2 * k + 1].unsigned_abs()) as u32; + } + } + total +} + +/// [`satd_strip`] for 8 and 4 columns, where its pairwise sums across neighbouring lanes do not +/// vectorise: the same sums are formed lane by lane against copies of the rows shifted by one and +/// two lanes, and only every fourth lane (the first column of a 4x4 block) is counted. +#[inline(always)] +fn satd_strip_narrow(a: &[u8], sa: usize, b: &[u8], sb: usize) -> u32 { + let mut d = [[0i16; N]; 4]; + for r in 0..4 { + let ra = &a[r * sa..r * sa + N]; + let rb = &b[r * sb..r * sb + N]; + for i in 0..N { + d[r][i] = ra[i] as i16 - rb[i] as i16; + } + } + let mut v = [[0i16; N]; 4]; + for i in 0..N { + let s01 = d[0][i] + d[1][i]; + let d01 = d[0][i] - d[1][i]; + let s23 = d[2][i] + d[3][i]; + let d23 = d[2][i] - d[3][i]; + v[0][i] = s01 + s23; + v[1][i] = s01 - s23; + v[2][i] = d01 + d23; + v[3][i] = d01 - d23; + } + // per lane at most 4 rows x 2 x 2040: fits u16 + let mut acc = [0u16; N]; + for row in &v { + let mut p = [0i16; N]; + let mut m = [0i16; N]; + p[..N - 1].copy_from_slice(&row[1..]); + m[..N - 1].copy_from_slice(&row[1..]); + for i in 0..N { + p[i] += row[i]; + m[i] = row[i] - m[i]; + } + let mut p2 = [0i16; N]; + let mut m2 = [0i16; N]; + p2[..N - 2].copy_from_slice(&p[2..]); + m2[..N - 2].copy_from_slice(&m[2..]); + for i in 0..N { + let t = p[i].unsigned_abs().max(p2[i].unsigned_abs()) + m[i].unsigned_abs().max(m2[i].unsigned_abs()); + acc[i] += if i % 4 == 0 { t } else { 0 }; + } + } + acc.iter().map(|&x| x as u32).sum() +} + +/// Sum of absolute 4x4 Hadamard-transformed differences (halved), for w, h multiples of 4. +pub fn satd(a: &[u8], sa: usize, b: &[u8], sb: usize, w: usize, h: usize) -> u32 { + let mut total = 0; + for by in (0..h).step_by(4) { + let (ao, bo) = (by * sa, by * sb); + match w { + 16 => total += satd_strip::<16, 8>(&a[ao..], sa, &b[bo..], sb), + 8 => total += satd_strip_narrow::<8>(&a[ao..], sa, &b[bo..], sb), + 4 => total += satd_strip_narrow::<4>(&a[ao..], sa, &b[bo..], sb), + _ => { + for bx in (0..w).step_by(4) { + total += satd_strip_narrow::<4>(&a[ao + bx..], sa, &b[bo + bx..], sb); + } + } + } + } + total +} + +pub fn ssd(a: &[u8], sa: usize, b: &[u8], sb: usize, w: usize, h: usize) -> u64 { + let mut s = 0u64; + for r in 0..h { + let ra = &a[r * sa..r * sa + w]; + let rb = &b[r * sb..r * sb + w]; + s += ra.iter().zip(rb).map(|(&x, &y)| (x as i32 - y as i32).pow(2) as u32).sum::() as u64; + } + s +} + +/// Variance*N (sum of squares minus square of sum / N) of a block. +pub fn block_var(a: &[u8], sa: usize, w: usize, h: usize) -> u32 { + let mut s = 0u32; + let mut ss = 0u32; + for r in 0..h { + for &v in &a[r * sa..r * sa + w] { + s += v as u32; + ss += (v as u32) * (v as u32); + } + } + ss - ((s as u64 * s as u64) / (w * h) as u64) as u32 +} + +#[cfg(test)] +mod tests { + use super::*; + #[test] + fn satd_of_dc_difference() { + let a = [10u8; 16]; + let b = [0u8; 16]; + // DC-only difference of 10: hadamard DC = 160, halved = 80. + assert_eq!(satd(&a, 4, &b, 4, 4, 4), 80); + assert_eq!(sad(&a, 4, &b, 4, 4, 4), 160); + } + + #[test] + fn satd_matches_reference() { + let mut seed = 7u32; + let mut rnd = || { + seed = seed.wrapping_mul(1103515245).wrapping_add(12345); + (seed >> 16) as u8 + }; + let a: Vec = (0..16 * 16).map(|_| rnd()).collect(); + let b: Vec = (0..16 * 16).map(|_| rnd()).collect(); + for (w, h) in [(16, 16), (8, 8), (4, 4), (16, 8), (8, 16), (12, 4)] { + let mut want = 0; + for by in (0..h).step_by(4) { + for bx in (0..w).step_by(4) { + let mut d = [0i32; 16]; + for r in 0..4 { + for c in 0..4 { + d[r * 4 + c] = a[(by + r) * 16 + bx + c] as i32 - b[(by + r) * 16 + bx + c] as i32; + } + } + want += hadamard4x4_sum(&d); + } + } + assert_eq!(satd(&a, 16, &b, 16, w, h), want / 2, "{w}x{h}"); + } + } + + #[test] + fn satd_matches_reference_at_the_extremes() { + // the largest differences (every lane at +-255) are where 16-bit sums could overflow + let (zero, full) = ([0u8; 16 * 16], [255u8; 16 * 16]); + let stripes: Vec = (0..16 * 16).map(|i| if (i / 16 + i % 16) % 2 == 0 { 255 } else { 0 }).collect(); + for (a, b) in [(&zero[..], &full[..]), (&full[..], &zero[..]), (&stripes[..], &zero[..]), (&zero[..], &stripes[..])] { + for (w, h) in [(16, 16), (8, 8), (4, 4), (8, 16), (12, 4)] { + let mut want = 0; + for by in (0..h).step_by(4) { + for bx in (0..w).step_by(4) { + let mut d = [0i32; 16]; + for r in 0..4 { + for c in 0..4 { + d[r * 4 + c] = a[(by + r) * 16 + bx + c] as i32 - b[(by + r) * 16 + bx + c] as i32; + } + } + want += hadamard4x4_sum(&d); + } + } + assert_eq!(satd(a, 16, b, 16, w, h), want / 2, "{w}x{h}"); + } + } + } +} diff --git a/crates/h264enc/src/intra.rs b/crates/h264enc/src/intra.rs new file mode 100644 index 0000000000..cae6fe8102 --- /dev/null +++ b/crates/h264enc/src/intra.rs @@ -0,0 +1,303 @@ +//! Intra prediction (§8.3): 4x4, 8x8 (with reference filtering), 16x16 and chroma. + +/// Neighbouring samples of a block. `top` holds p[x,-1] for x = 0..2N (top then top-right), `left` p[-1,y]. +#[derive(Clone, Copy)] +pub struct Edge { + pub top: [u8; N2], + pub left: [u8; N], + pub tl: u8, + pub has_top: bool, + pub has_left: bool, + pub has_tl: bool, +} + +pub type Edge4 = Edge<8, 4>; +pub type Edge8 = Edge<16, 8>; + +/// Which modes are usable given neighbour availability. +pub fn mode_available(mode: u8, has_top: bool, has_left: bool, has_tl: bool) -> bool { + match mode { + 0 | 3 | 7 => has_top, + 1 | 8 => has_left, + 2 => true, + _ => has_top && has_left && has_tl, + } +} + +/// Generic directional prediction for an NxN block given (already filtered, for 8x8) neighbours. +/// `t(x)` = p[x,-1] for x in -1..2N, `l(y)` = p[-1,y] for y in -1..N. +fn pred_dir(mode: u8, t: &dyn Fn(i32) -> i32, l: &dyn Fn(i32) -> i32, dc: i32, out: &mut [u8]) { + let n = N as i32; + for y in 0..n { + for x in 0..n { + let v = match mode { + 0 => t(x), + 1 => l(y), + 2 => dc, + 3 => { + if x == n - 1 && y == n - 1 { + (t(2 * n - 2) + 3 * t(2 * n - 1) + 2) >> 2 + } else { + (t(x + y) + 2 * t(x + y + 1) + t(x + y + 2) + 2) >> 2 + } + } + 4 => { + if x > y { + (t(x - y - 2) + 2 * t(x - y - 1) + t(x - y) + 2) >> 2 + } else if x < y { + (l(y - x - 2) + 2 * l(y - x - 1) + l(y - x) + 2) >> 2 + } else { + (t(0) + 2 * t(-1) + l(0) + 2) >> 2 + } + } + 5 => { + let z = 2 * x - y; + if z >= 0 && z & 1 == 0 { + (t(x - (y >> 1) - 1) + t(x - (y >> 1)) + 1) >> 1 + } else if z >= 0 { + (t(x - (y >> 1) - 2) + 2 * t(x - (y >> 1) - 1) + t(x - (y >> 1)) + 2) >> 2 + } else if z == -1 { + (l(0) + 2 * l(-1) + t(0) + 2) >> 2 + } else { + (l(y - 2 * x - 1) + 2 * l(y - 2 * x - 2) + l(y - 2 * x - 3) + 2) >> 2 + } + } + 6 => { + let z = 2 * y - x; + if z >= 0 && z & 1 == 0 { + (l(y - (x >> 1) - 1) + l(y - (x >> 1)) + 1) >> 1 + } else if z >= 0 { + (l(y - (x >> 1) - 2) + 2 * l(y - (x >> 1) - 1) + l(y - (x >> 1)) + 2) >> 2 + } else if z == -1 { + (l(0) + 2 * l(-1) + t(0) + 2) >> 2 + } else { + (t(x - 2 * y - 1) + 2 * t(x - 2 * y - 2) + t(x - 2 * y - 3) + 2) >> 2 + } + } + 7 => { + if y & 1 == 0 { + (t(x + (y >> 1)) + t(x + (y >> 1) + 1) + 1) >> 1 + } else { + (t(x + (y >> 1)) + 2 * t(x + (y >> 1) + 1) + t(x + (y >> 1) + 2) + 2) >> 2 + } + } + _ => { + let z = x + 2 * y; + let lim = 2 * n - 3; + if z < lim && z & 1 == 0 { + (l(y + (x >> 1)) + l(y + (x >> 1) + 1) + 1) >> 1 + } else if z < lim { + (l(y + (x >> 1)) + 2 * l(y + (x >> 1) + 1) + l(y + (x >> 1) + 2) + 2) >> 2 + } else if z == lim { + (l(n - 2) + 3 * l(n - 1) + 2) >> 2 + } else { + l(n - 1) + } + } + }; + out[(y * n + x) as usize] = v as u8; + } + } +} + +/// Intra 4x4 prediction. `e.top[4..8]` must already be substituted with top[3] when top-right is unavailable. +pub fn pred4x4(mode: u8, e: &Edge4, out: &mut [u8; 16]) { + let dc = match (e.has_top, e.has_left) { + (true, true) => (e.top[..4].iter().map(|&v| v as i32).sum::() + e.left.iter().map(|&v| v as i32).sum::() + 4) >> 3, + (true, false) => (e.top[..4].iter().map(|&v| v as i32).sum::() + 2) >> 2, + (false, true) => (e.left.iter().map(|&v| v as i32).sum::() + 2) >> 2, + _ => 128, + }; + if mode == 2 { + out.fill(dc as u8); + return; + } + let t = |x: i32| if x < 0 { e.tl as i32 } else { e.top[x as usize] as i32 }; + let l = |y: i32| if y < 0 { e.tl as i32 } else { e.left[y as usize] as i32 }; + pred_dir::<4>(mode, &t, &l, dc, out); +} + +/// Reference sample filtering for Intra 8x8 (§8.3.2.2.1). `e.top[8..16]` must be substituted if unavailable. +pub fn filter8(e: &Edge8) -> Edge8 { + let mut f = *e; + if e.has_top { + let p = |x: usize| e.top[x] as u32; + f.top[0] = if e.has_tl { ((e.tl as u32 + 2 * p(0) + p(1) + 2) >> 2) as u8 } else { ((3 * p(0) + p(1) + 2) >> 2) as u8 }; + for x in 1..15 { + f.top[x] = ((p(x - 1) + 2 * p(x) + p(x + 1) + 2) >> 2) as u8; + } + f.top[15] = ((p(14) + 3 * p(15) + 2) >> 2) as u8; + } + if e.has_tl { + let tl = e.tl as u32; + f.tl = if !e.has_top || !e.has_left { + if e.has_top { + ((3 * tl + e.top[0] as u32 + 2) >> 2) as u8 + } else if e.has_left { + ((3 * tl + e.left[0] as u32 + 2) >> 2) as u8 + } else { + e.tl + } + } else { + ((e.top[0] as u32 + 2 * tl + e.left[0] as u32 + 2) >> 2) as u8 + }; + } + if e.has_left { + let p = |y: usize| e.left[y] as u32; + f.left[0] = if e.has_tl { ((e.tl as u32 + 2 * p(0) + p(1) + 2) >> 2) as u8 } else { ((3 * p(0) + p(1) + 2) >> 2) as u8 }; + for y in 1..7 { + f.left[y] = ((p(y - 1) + 2 * p(y) + p(y + 1) + 2) >> 2) as u8; + } + f.left[7] = ((p(6) + 3 * p(7) + 2) >> 2) as u8; + } + f +} + +/// Intra 8x8 prediction from *filtered* neighbours. +pub fn pred8x8(mode: u8, f: &Edge8, out: &mut [u8; 64]) { + let dc = match (f.has_top, f.has_left) { + (true, true) => (f.top[..8].iter().map(|&v| v as i32).sum::() + f.left.iter().map(|&v| v as i32).sum::() + 8) >> 4, + (true, false) => (f.top[..8].iter().map(|&v| v as i32).sum::() + 4) >> 3, + (false, true) => (f.left.iter().map(|&v| v as i32).sum::() + 4) >> 3, + _ => 128, + }; + if mode == 2 { + out.fill(dc as u8); + return; + } + let t = |x: i32| if x < 0 { f.tl as i32 } else { f.top[x as usize] as i32 }; + let l = |y: i32| if y < 0 { f.tl as i32 } else { f.left[y as usize] as i32 }; + pred_dir::<8>(mode, &t, &l, dc, out); +} + +/// Intra 16x16 prediction. Modes: 0 V, 1 H, 2 DC, 3 plane. +pub fn pred16x16(mode: u8, top: &[u8; 16], left: &[u8; 16], tl: u8, has_top: bool, has_left: bool, out: &mut [u8; 256]) { + match mode { + 0 => { + for y in 0..16 { + out[y * 16..y * 16 + 16].copy_from_slice(top); + } + } + 1 => { + for y in 0..16 { + out[y * 16..y * 16 + 16].fill(left[y]); + } + } + 2 => { + let st: u32 = top.iter().map(|&v| v as u32).sum(); + let sl: u32 = left.iter().map(|&v| v as u32).sum(); + let dc = match (has_top, has_left) { + (true, true) => (st + sl + 16) >> 5, + (true, false) => (st + 8) >> 4, + (false, true) => (sl + 8) >> 4, + _ => 128, + }; + out.fill(dc as u8); + } + _ => { + let p_t = |x: i32| if x < 0 { tl as i32 } else { top[x as usize] as i32 }; + let p_l = |y: i32| if y < 0 { tl as i32 } else { left[y as usize] as i32 }; + let mut h = 0; + let mut v = 0; + for i in 0..8 { + h += (i + 1) * (p_t(8 + i) - p_t(6 - i)); + v += (i + 1) * (p_l(8 + i) - p_l(6 - i)); + } + let a = 16 * (p_l(15) + p_t(15)); + let b = (5 * h + 32) >> 6; + let c = (5 * v + 32) >> 6; + for y in 0..16 { + for x in 0..16 { + out[y * 16 + x] = ((a + b * (x as i32 - 7) + c * (y as i32 - 7) + 16) >> 5).clamp(0, 255) as u8; + } + } + } + } +} + +/// Chroma 8x8 prediction (4:2:0). Modes: 0 DC, 1 H, 2 V, 3 plane. +pub fn pred_chroma(mode: u8, top: &[u8; 8], left: &[u8; 8], tl: u8, has_top: bool, has_left: bool, out: &mut [u8; 64]) { + match mode { + 0 => { + for by in 0..2 { + for bx in 0..2 { + let st: u32 = top[bx * 4..bx * 4 + 4].iter().map(|&v| v as u32).sum(); + let sl: u32 = left[by * 4..by * 4 + 4].iter().map(|&v| v as u32).sum(); + let dc = if (bx == 0 && by == 0) || (bx == 1 && by == 1) { + match (has_top, has_left) { + (true, true) => (st + sl + 4) >> 3, + (true, false) => (st + 2) >> 2, + (false, true) => (sl + 2) >> 2, + _ => 128, + } + } else if bx == 1 && by == 0 { + if has_top { + (st + 2) >> 2 + } else if has_left { + (sl + 2) >> 2 + } else { + 128 + } + } else if has_left { + (sl + 2) >> 2 + } else if has_top { + (st + 2) >> 2 + } else { + 128 + }; + for y in 0..4 { + for x in 0..4 { + out[(by * 4 + y) * 8 + bx * 4 + x] = dc as u8; + } + } + } + } + } + 1 => { + for y in 0..8 { + out[y * 8..y * 8 + 8].fill(left[y]); + } + } + 2 => { + for y in 0..8 { + out[y * 8..y * 8 + 8].copy_from_slice(top); + } + } + _ => { + let p_t = |x: i32| if x < 0 { tl as i32 } else { top[x as usize] as i32 }; + let p_l = |y: i32| if y < 0 { tl as i32 } else { left[y as usize] as i32 }; + let mut h = 0; + let mut v = 0; + for i in 0..4 { + h += (i + 1) * (p_t(4 + i) - p_t(2 - i)); + v += (i + 1) * (p_l(4 + i) - p_l(2 - i)); + } + let a = 16 * (p_l(7) + p_t(7)); + let b = (34 * h + 32) >> 6; + let c = (34 * v + 32) >> 6; + for y in 0..8 { + for x in 0..8 { + out[y * 8 + x] = ((a + b * (x as i32 - 3) + c * (y as i32 - 3) + 16) >> 5).clamp(0, 255) as u8; + } + } + } + } +} + +#[cfg(test)] +mod tests { + use super::*; + + #[test] + fn dc_and_vertical() { + let e = Edge4 { top: [10, 20, 30, 40, 40, 40, 40, 40], left: [1, 2, 3, 4], tl: 5, has_top: true, has_left: true, has_tl: true }; + let mut o = [0u8; 16]; + pred4x4(0, &e, &mut o); + assert_eq!(&o[12..16], &[10, 20, 30, 40]); + pred4x4(2, &e, &mut o); + assert_eq!(o[0], ((100 + 10 + 4) >> 3) as u8); + // horizontal up saturates at bottom-left + pred4x4(8, &e, &mut o); + assert_eq!(o[15], 4); + } +} diff --git a/crates/h264enc/src/lib.rs b/crates/h264enc/src/lib.rs new file mode 100644 index 0000000000..5adcb71eca --- /dev/null +++ b/crates/h264enc/src/lib.rs @@ -0,0 +1,1031 @@ +//! Clean-room, pure-Rust H.264/AVC encoder (ITU-T H.264), written from the public specification. +//! +//! - Profiles: Constrained Baseline (CAVLC, I/P), Main (CABAC, I/P/B) and High (CABAC, 8x8 transform, I/P/B), 4:2:0 8-bit. +//! - Intra 16x16 / 4x4 / 8x8 mode decision, hexagon motion search with quarter-sample refinement, +//! 16x16/16x8/8x16/8x8 partitions, P_Skip, B-frames with spatial direct/B_Skip, in-loop deblocking. +//! - Rate control: constant QP, constant quality (CRF-like), 1-pass ABR with VBV, 2-pass VBR. +//! - Slice-parallel encoding with rayon (feature `threads`, on by default). +//! +//! ```no_run +//! use craft_h264enc::{Encoder, EncoderConfig, RateControl, YuvFrame}; +//! let mut cfg = EncoderConfig::new(1280, 720, 30, 1); +//! cfg.rate = RateControl::Crf(20.0); +//! let mut enc = Encoder::new(cfg).unwrap(); +//! let (y, u, v) = (vec![128u8; 1280 * 720], vec![128u8; 640 * 360], vec![128u8; 640 * 360]); +//! let frame = YuvFrame { y: &y, u: &u, v: &v, y_stride: 1280, uv_stride: 640 }; +//! let mut packets = enc.encode(&frame, 0).unwrap(); +//! packets.extend(enc.flush()); +//! ``` + +#![deny(clippy::unwrap_used, clippy::expect_used, clippy::panic, clippy::unimplemented, clippy::todo, clippy::unreachable)] +#![allow(clippy::needless_range_loop)] + +mod cabac; +mod cabac_mb; +mod cavlc; +mod deblock; +mod dsp; +mod intra; +mod lookahead; +mod mbinfo; +mod nal; +mod picture; +mod ratecontrol; +mod slice; +mod syntax; +mod tables; +mod transform; + +use std::collections::VecDeque; + +pub use ratecontrol::{FrameStat, PassStats}; + +use cabac::CostTable; +use deblock::{BandPlane, DeblockParams, deblock_band}; +use lookahead::{Complexity, LowRes}; +use mbinfo::MbInfo; +use nal::{NAL_AUD, NAL_IDR, NAL_PPS, NAL_SEI, NAL_SLICE, NAL_SPS, Pps, SliceHeader, SliceType, Sps, Vui}; +use picture::{CHROMA_PAD, Frame, LUMA_PAD, RefPic, build_hpel}; +use ratecontrol::RcKind; +use slice::{Band, EncParams, FrameEnc, Lambdas, SliceEnc, SliceOut}; +use tables::NUM_CTX; +use transform::QuantTables; + +/// Encoder errors. +#[derive(Debug, thiserror::Error)] +pub enum Error { + #[error("invalid dimensions {0}x{1} (must be even and non-zero)")] + InvalidDimensions(u32, u32), + #[error("invalid frame rate {0}/{1}")] + InvalidFrameRate(u32, u32), + #[error("invalid configuration: {0}")] + InvalidConfig(String), + #[error("input frame planes too small for the configured size")] + BadFrame, +} + +#[derive(Clone, Copy, Debug, PartialEq, Eq, Default)] +pub enum Profile { + /// Constrained Baseline: CAVLC, no B-frames. + Baseline, + /// Main: CABAC, B-frames. + Main, + /// High: CABAC, B-frames, 8x8 transform. + #[default] + High, +} + +#[derive(Clone, Copy, Debug, PartialEq, Eq, Default)] +pub enum Preset { + Speed, + #[default] + Balanced, + Quality, +} + +#[derive(Clone, Copy, Debug, PartialEq)] +pub enum RateControl { + /// Constant quality; roughly the QP of P frames (0–51, lower is better). + Crf(f32), + /// Constant bitrate target (1-pass ABR with a 1-second VBV at `kbps`). + Cbr { kbps: u32 }, + /// Variable bitrate: average `target_kbps`, VBV-limited to `max_kbps`. Use [`Pass`] for 2-pass. + Vbr { target_kbps: u32, max_kbps: u32 }, + /// Constant QP (B frames use QP + 2). + Qp(u8), +} + +/// Multi-pass mode for bitrate-targeted rate control. +#[derive(Clone, Debug, Default, PartialEq)] +pub enum Pass { + #[default] + Single, + /// First pass: encode and collect [`PassStats`] via [`Encoder::pass_stats`]. + First, + /// Second pass using the statistics of the first. + Second(PassStats), +} + +/// Colour description written to the VUI (ITU-T H.273 code points). +#[derive(Clone, Copy, Debug, PartialEq, Eq)] +pub struct ColorConfig { + pub primaries: u8, + pub transfer: u8, + pub matrix: u8, + pub full_range: bool, +} + +impl Default for ColorConfig { + /// BT.709, limited range. + fn default() -> Self { + ColorConfig { primaries: 1, transfer: 1, matrix: 1, full_range: false } + } +} + +#[derive(Clone, Copy, Debug, PartialEq, Eq, Default)] +pub enum PacketFormat { + /// Start-code prefixed NAL units (`00 00 00 01`), with AUD and in-band SPS/PPS on keyframes. + #[default] + AnnexB, + /// 4-byte big-endian length-prefixed NAL units (MP4 samples); SPS/PPS live in [`Encoder::avcc`]. + LengthPrefixed, +} + +#[derive(Clone, Debug)] +pub struct EncoderConfig { + pub width: u32, + pub height: u32, + pub fps_num: u32, + pub fps_den: u32, + pub profile: Profile, + pub preset: Preset, + pub rate: RateControl, + /// Maximum distance between IDR frames. + pub keyint: u32, + /// Consecutive B-frames (0–3). Ignored for Baseline. + pub bframes: u8, + pub color: ColorConfig, + /// Worker threads (0 = all available cores). Also bounds the number of slices. + pub threads: usize, + pub pass: Pass, + pub format: PacketFormat, + /// Variance adaptive quantisation strength (0 = off). + pub aq_strength: f32, + /// Sample aspect ratio (1:1 by default). + pub sar: (u16, u16), + /// Insert IDR frames at detected scene cuts. + pub scenecut: bool, + /// Number of slices per picture (0 = derived from `threads`). + pub slices: usize, + /// Access unit delimiters in Annex-B output. + pub aud: bool, + /// HDR static metadata sent in an SEI with the first access unit: the 24-byte SMPTE ST 2086 + /// mastering display payload (payloadType 137) … + pub mastering_display: Option<[u8; 24]>, + /// … and (MaxCLL, MaxFALL) content light level (payloadType 144). + pub content_light: Option<(u16, u16)>, + /// Requested level_idc (41 = level 4.1); None = the lowest level that fits. A level too low + /// for the stream is raised to the one it needs. + pub level: Option, +} + +impl EncoderConfig { + pub fn new(width: u32, height: u32, fps_num: u32, fps_den: u32) -> Self { + EncoderConfig { + width, + height, + fps_num, + fps_den, + profile: Profile::High, + preset: Preset::Balanced, + rate: RateControl::Crf(23.0), + keyint: 250, + bframes: 2, + color: ColorConfig::default(), + threads: 0, + pass: Pass::Single, + format: PacketFormat::AnnexB, + aq_strength: 0.6, + sar: (1, 1), + scenecut: true, + slices: 0, + mastering_display: None, + content_light: None, + aud: true, + level: None, + } + } +} + +/// A planar 4:2:0 8-bit input picture of the configured size. +#[derive(Clone, Copy, Debug)] +pub struct YuvFrame<'a> { + pub y: &'a [u8], + pub u: &'a [u8], + pub v: &'a [u8], + pub y_stride: usize, + pub uv_stride: usize, +} + +#[derive(Clone, Copy, Debug, PartialEq, Eq)] +pub enum FrameType { + Idr, + I, + P, + B, +} + +/// One coded picture (access unit). +#[derive(Clone, Debug)] +pub struct Packet { + pub data: Vec, + pub pts: i64, + pub dts: i64, + pub keyframe: bool, + pub frame_type: FrameType, + /// QP used for the picture (slice QP). + pub qp: u8, +} + +/// A reconstructed (decoded) picture, available when [`Encoder::set_recon_capture`] is on. +#[derive(Clone, Debug)] +pub struct ReconFrame { + pub pts: i64, + pub y: Vec, + pub u: Vec, + pub v: Vec, +} + +struct Input { + frame: Frame, + pts: i64, + display: u64, + cplx: Complexity, + aq: Vec, +} + +struct Pool { + #[cfg(feature = "threads")] + pool: Option, +} + +impl Pool { + fn install(&self, f: impl FnOnce() -> R + Send) -> R { + #[cfg(feature = "threads")] + if let Some(p) = &self.pool { + return p.install(f); + } + f() + } +} + +pub struct Encoder { + cfg: EncoderConfig, + params: EncParams, + sps: Sps, + sps_nal: Vec, + pps_nal: Vec, + qt: QuantTables, + cost_tab: CostTable, + lam: Lambdas, + cabac_i: Box<[(i8, i8); NUM_CTX]>, + cabac_pb: Box<[(i8, i8); NUM_CTX]>, + rc: ratecontrol::RateControl, + record_pass1: bool, + pending: VecDeque, + prev_lowres: Option, + display_count: u64, + last_idr: u64, + idr_count: u32, + next_frame_num: u32, + dpb: Vec, + coded_count: u64, + display_pts: VecDeque, + dts_offset: Option, + /// First two presentation times (to derive the frame interval). + all_pts: Vec, + held: Vec, + frame_pool: Vec, + hpel_pool: Vec<[picture::Plane; 3]>, + sei_sent: bool, + slices: usize, + pool: Pool, + recon: Option>, + ref_uid: u32, + max_refs: usize, +} + +const LOG2_MAX_FRAME_NUM: u32 = 8; +const LOG2_MAX_POC_LSB: u32 = 8; + +/// The bitrate a level must allow for constant quality `crf`, which has no bitrate cap. It +/// assumes the worst footage: about 3 bits per pixel at CRF 0 (FilmCraft's CRF 0 exports of real +/// camera footage measured 1.7 on average and 2.4 in their busiest second), halving every 6 steps +/// (one doubling of the quantiser step). The default CRF 23 keeps 1080p30 at level 4. +fn crf_level_kbps(crf: f32, mbw: usize, mbh: usize, fps: f64) -> u32 { + let crf = if crf.is_finite() { f64::from(crf.clamp(0.0, 51.0)) } else { 23.0 }; + let bpp = 3.0 * (-crf / 6.0).exp2(); + let pixels = (mbw as f64) * (mbh as f64) * 256.0; + // a float-to-int `as` saturates, and NaN (a hostile fps) becomes 0 + (pixels * fps * bpp / 1000.0).ceil() as u32 +} + +impl Encoder { + /// The settings that shaped the stream, x264-style (`key=value` separated by spaces), for the + /// user_data_unregistered SEI: a file says how it was made (`strings`, `ffprobe -show_data`). + fn options(&self) -> String { + let c = &self.cfg; + let rate = match c.rate { + RateControl::Qp(q) => format!("rc=cqp qp={q}"), + RateControl::Crf(f) => format!("rc=crf crf={:.1}", f.clamp(0.0, 51.0)), + RateControl::Cbr { kbps } => format!("rc=cbr bitrate={kbps}"), + RateControl::Vbr { target_kbps, max_kbps } => format!("rc=vbr bitrate={target_kbps} vbv_maxrate={max_kbps}"), + }; + let pass = match c.pass { + Pass::Single => "", + Pass::First => " pass=1", + Pass::Second(_) => " pass=2", + }; + let profile = match c.profile { + Profile::Baseline => "baseline", + Profile::Main => "main", + Profile::High => "high", + }; + let preset = match c.preset { + Preset::Speed => "speed", + Preset::Balanced => "balanced", + Preset::Quality => "quality", + }; + let level = self.sps.level_idc; + format!( + "{rate}{pass} profile={profile} level={}.{} preset={preset} keyint={} bframes={} refs={} cabac={} 8x8dct={} slices={} aq={:.2} scenecut={}", + level / 10, + level % 10, + c.keyint, + c.bframes, + self.max_refs, + u8::from(c.profile != Profile::Baseline), + u8::from(c.profile == Profile::High), + self.slices, + c.aq_strength, + u8::from(c.scenecut), + ) + } + + pub fn new(mut cfg: EncoderConfig) -> Result { + if cfg.width == 0 + || cfg.height == 0 + || !cfg.width.is_multiple_of(2) + || !cfg.height.is_multiple_of(2) + || cfg.width > 16384 + || cfg.height > 16384 + { + return Err(Error::InvalidDimensions(cfg.width, cfg.height)); + } + if cfg.fps_num == 0 || cfg.fps_den == 0 { + return Err(Error::InvalidFrameRate(cfg.fps_num, cfg.fps_den)); + } + if cfg.bframes > 3 { + return Err(Error::InvalidConfig("bframes must be 0..=3".into())); + } + if cfg.profile == Profile::Baseline { + cfg.bframes = 0; + } + cfg.keyint = cfg.keyint.max(1); + let mbw = cfg.width.div_ceil(16) as usize; + let mbh = cfg.height.div_ceil(16) as usize; + let fps = cfg.fps_num as f64 / cfg.fps_den as f64; + let cabac = cfg.profile != Profile::Baseline; + let high = cfg.profile == Profile::High; + let params = match cfg.preset { + Preset::Speed => EncParams { + mbw, + mbh, + cabac, + t8x8: high, + i4x4: true, + i8x8: false, + partitions: false, + me_range: 8, + subpel: 2, + subpel_rounds: 0, + rd: false, + chroma_qp_offset: 0, + decimate: true, + adaptive_t8: false, + always_intra: false, + cavlc_clamp: !cabac, + }, + Preset::Balanced => EncParams { + mbw, + mbh, + cabac, + t8x8: high, + i4x4: true, + i8x8: high, + partitions: true, + me_range: 16, + subpel: 2, + subpel_rounds: 1, + rd: false, + chroma_qp_offset: 0, + decimate: true, + adaptive_t8: high, + always_intra: false, + cavlc_clamp: !cabac, + }, + Preset::Quality => EncParams { + mbw, + mbh, + cabac, + t8x8: high, + i4x4: true, + i8x8: high, + partitions: true, + me_range: 24, + subpel: 2, + subpel_rounds: 2, + rd: true, + chroma_qp_offset: 0, + decimate: true, + adaptive_t8: high, + always_intra: true, + cavlc_clamp: !cabac, + }, + }; + let max_refs = if cfg.bframes > 0 { 2 } else { 1 }; + if let Pass::Second(st) = &cfg.pass { + let invalid = + st.frames.is_empty() || !st.fps.is_finite() || st.fps <= 0.0 || st.frames.iter().any(|f| !f.qscale.is_finite() || f.qscale <= 0.0); + if invalid { + return Err(Error::InvalidConfig( + "second pass needs non-empty first-pass statistics with a positive fps and valid frame qscales".into(), + )); + } + } + let (kbps_for_level, rc_kind, vbv) = match (&cfg.rate, &cfg.pass) { + (RateControl::Qp(q), _) => (None, RcKind::Qp((*q).min(51)), (None, None)), + (RateControl::Crf(c), _) => (Some(crf_level_kbps(*c, mbw, mbh, fps)), RcKind::Crf(c.clamp(0.0, 51.0)), (None, None)), + (RateControl::Cbr { kbps }, Pass::Second(st)) => { + (Some(*kbps), ratecontrol::RateControl::plan_two_pass(st, *kbps), (Some(*kbps), Some(*kbps))) + } + (RateControl::Cbr { kbps }, Pass::First) => (Some(*kbps), RcKind::Abr { kbps: *kbps }, (None, None)), + (RateControl::Cbr { kbps }, Pass::Single) => (Some(*kbps), RcKind::Abr { kbps: *kbps }, (Some(*kbps), Some(*kbps))), + (RateControl::Vbr { target_kbps, max_kbps }, Pass::Second(st)) => { + let buffer = + max_kbps.checked_mul(2).ok_or_else(|| Error::InvalidConfig("VBR buffer rate exceeds the supported integer range".into()))?; + (Some(*max_kbps), ratecontrol::RateControl::plan_two_pass(st, *target_kbps), (Some(*max_kbps), Some(buffer))) + } + (RateControl::Vbr { target_kbps, max_kbps }, Pass::First) => (Some(*max_kbps), RcKind::Abr { kbps: *target_kbps }, (None, None)), + (RateControl::Vbr { target_kbps, max_kbps }, Pass::Single) => { + let buffer = + max_kbps.checked_mul(2).ok_or_else(|| Error::InvalidConfig("VBR buffer rate exceeds the supported integer range".into()))?; + (Some(*max_kbps), RcKind::Abr { kbps: *target_kbps }, (Some(*max_kbps), Some(buffer))) + } + }; + if matches!(cfg.rate, RateControl::Cbr { kbps: 0 } | RateControl::Vbr { target_kbps: 0, .. }) { + return Err(Error::InvalidConfig("bitrate must be non-zero".into())); + } + let auto = nal::pick_level(mbw as u32, mbh as u32, fps, max_refs as u32, kbps_for_level, high) + .ok_or_else(|| Error::InvalidConfig("configuration exceeds H.264 level 6.2 limits".into()))?; + let level = cfg.level.map_or(auto, |l| nal::level_at_least(l, auto)); + let (profile_idc, constraint) = match cfg.profile { + Profile::Baseline => (66u8, 0xC0u8), // constraint_set0 + set1 => Constrained Baseline + Profile::Main => (77, 0x40), + Profile::High => (100, 0x00), + }; + let reorder = if cfg.bframes > 0 { 1 } else { 0 }; + let sps = Sps { + profile_idc, + constraint_flags: constraint, + level_idc: level, + width_mbs: mbw as u32, + height_mbs: mbh as u32, + crop_right: mbw as u32 * 16 - cfg.width, + crop_bottom: mbh as u32 * 16 - cfg.height, + log2_max_frame_num: LOG2_MAX_FRAME_NUM, + log2_max_poc_lsb: LOG2_MAX_POC_LSB, + max_num_ref_frames: max_refs as u32, + vui: Vui { + sar: cfg.sar, + video_full_range: cfg.color.full_range, + colour_primaries: cfg.color.primaries, + transfer: cfg.color.transfer, + matrix: cfg.color.matrix, + num_units_in_tick: cfg.fps_den, + time_scale: cfg.fps_num.saturating_mul(2), + max_num_reorder_frames: reorder, + max_dec_frame_buffering: max_refs as u32, + }, + }; + let pps = Pps { cabac, pic_init_qp: 26, chroma_qp_offset: params.chroma_qp_offset, transform_8x8: params.t8x8, high }; + let sps_nal = nal::nal(3, NAL_SPS, &sps.rbsp()); + let pps_nal = nal::nal(3, NAL_PPS, &pps.rbsp()); + let threads = if cfg.threads == 0 { std::thread::available_parallelism().map_or(1, |n| n.get()) } else { cfg.threads }; + let slices = if cfg.slices > 0 { cfg.slices.min(mbh) } else { threads.min(mbh.div_ceil(4)).max(1) }; + #[cfg(feature = "threads")] + let pool = Pool { pool: if threads > 1 { rayon::ThreadPoolBuilder::new().num_threads(threads).build().ok() } else { None } }; + #[cfg(not(feature = "threads"))] + let pool = Pool {}; + let record_pass1 = cfg.pass == Pass::First; + let rc = ratecontrol::RateControl::new(rc_kind, fps, vbv.0, vbv.1); + Ok(Encoder { + params, + sps, + sps_nal, + pps_nal, + qt: QuantTables::new(), + cost_tab: CostTable::new(), + lam: Lambdas::new(), + cabac_i: Box::new(tables::cabac_init_i()), + cabac_pb: Box::new(tables::cabac_init_pb0()), + rc, + record_pass1, + pending: VecDeque::new(), + prev_lowres: None, + display_count: 0, + last_idr: 0, + idr_count: 0, + next_frame_num: 0, + dpb: Vec::new(), + coded_count: 0, + display_pts: VecDeque::new(), + dts_offset: None, + all_pts: Vec::new(), + held: Vec::new(), + frame_pool: Vec::new(), + hpel_pool: Vec::new(), + sei_sent: false, + slices, + pool, + recon: None, + ref_uid: 0, + max_refs, + cfg, + }) + } + + pub fn config(&self) -> &EncoderConfig { + &self.cfg + } + + /// Run `f` on the encoder's worker threads (the calling thread without the `threads` + /// feature). The pool is idle between pictures, so input preparation such as colour + /// conversion that runs here does not queue behind work on the global rayon pool. + pub fn install(&self, f: impl FnOnce() -> R + Send) -> R { + self.pool.install(f) + } + + /// The SPS and PPS NAL units (without start codes). + pub fn sps_pps(&self) -> (Vec, Vec) { + (self.sps_nal.clone(), self.pps_nal.clone()) + } + + /// AVCDecoderConfigurationRecord (`avcC` box payload) for MP4 muxing (4-byte NAL lengths). + pub fn avcc(&self) -> Vec { + nal::avcc(&self.sps_nal, &self.pps_nal, self.sps.profile_idc, self.sps.constraint_flags, self.sps.level_idc) + } + + /// Number of frames the output is delayed by (B-frame reordering). + pub fn delay(&self) -> usize { + self.cfg.bframes as usize + } + + /// Enable capture of reconstructed pictures (for verification). + pub fn set_recon_capture(&mut self, on: bool) { + self.recon = if on { Some(Vec::new()) } else { None }; + } + + /// Take the reconstructed pictures captured so far (coding order). + pub fn take_recon(&mut self) -> Vec { + self.recon.as_mut().map(std::mem::take).unwrap_or_default() + } + + /// First-pass statistics (only when configured with [`Pass::First`]). + pub fn pass_stats(&self) -> Option { + if !self.record_pass1 { + return None; + } + Some(PassStats { frames: self.rc.pass1.clone(), fps: self.cfg.fps_num as f64 / self.cfg.fps_den as f64 }) + } + + /// Encode one picture. Returns zero or more packets in decoding order, or + /// [`Error::BadFrame`] if the planes are smaller than the configured size (same as + /// [`Encoder::try_encode`]). + pub fn encode(&mut self, frame: &YuvFrame, pts: i64) -> Result, Error> { + self.try_encode(frame, pts) + } + + pub fn try_encode(&mut self, frame: &YuvFrame, pts: i64) -> Result, Error> { + let src = self.import(frame)?; + let use_aq = self.cfg.aq_strength > 0.0 && !matches!(self.cfg.rate, RateControl::Qp(_)); + let (mbw, mbh, strength) = (self.params.mbw, self.params.mbh, self.cfg.aq_strength); + let prev = self.prev_lowres.take(); + let analyse = || { + let lowres = LowRes::new(&src); + let cplx = lookahead::complexity(&lowres, prev.as_ref()); + (lowres, cplx) + }; + let aq_of = || if use_aq { lookahead::aq_offsets(&src, mbw, mbh, strength) } else { Vec::new() }; + // the two analyses are independent: side by side, the pool's threads are not left idle + // at the end of each + #[cfg(feature = "threads")] + let ((lowres, cplx), aq) = self.pool.install(|| rayon::join(analyse, aq_of)); + #[cfg(not(feature = "threads"))] + let ((lowres, cplx), aq) = (analyse(), aq_of()); + self.prev_lowres = Some(lowres); + let display = self.display_count; + self.display_count += 1; + self.display_pts.push_back(pts); + if self.all_pts.len() < 2 { + self.all_pts.push(pts); + } + let input = Input { frame: src, pts, display, cplx, aq }; + let since = display - self.last_idr; + let fps = self.cfg.fps_num as f64 / self.cfg.fps_den as f64; + let min_gap = (fps / 2.0).max(1.0) as u64; + let scenecut = self.cfg.scenecut && display > 0 && since >= min_gap && cplx.inter as f64 > 0.75 * cplx.intra as f64; + let idr = display == 0 || since >= self.cfg.keyint as u64 || scenecut; + let mut out = Vec::new(); + if idr { + self.flush_minigop(&mut out); + self.last_idr = display; + self.encode_batch(vec![(input, SliceType::I, true)], &mut out); + } else { + self.pending.push_back(input); + if self.pending.len() > self.cfg.bframes as usize { + self.flush_minigop(&mut out); + } + } + Ok(self.release(out, false)) + } + + /// Encode all buffered pictures. + pub fn flush(&mut self) -> Vec { + let mut out = Vec::new(); + self.flush_minigop(&mut out); + self.release(out, true) + } + + /// Assign decoding timestamps. The k-th coded picture gets the k-th smallest presentation time minus the + /// reordering delay (one frame interval when B-frames are enabled); packets are held back until that interval is known. + fn release(&mut self, out: Vec, flushing: bool) -> Vec { + self.held.extend(out); + if self.dts_offset.is_none() { + if self.cfg.bframes == 0 { + self.dts_offset = Some(0); + } else if self.all_pts.len() >= 2 { + let mut v = self.all_pts.clone(); + v.sort_unstable(); + self.dts_offset = Some(v[1] - v[0]); + } else if flushing { + self.dts_offset = Some(0); + } else { + return Vec::new(); + } + } + let off = self.dts_offset.unwrap_or(0); + let mut res = std::mem::take(&mut self.held); + for p in res.iter_mut() { + p.dts -= off; + } + res + } + + fn flush_minigop(&mut self, out: &mut Vec) { + let Some(p) = self.pending.pop_back() else { return }; + let bs: Vec<(Input, SliceType, bool)> = self.pending.drain(..).map(|b| (b, SliceType::B, false)).collect(); + self.encode_batch(vec![(p, SliceType::P, false)], out); + if !bs.is_empty() { + // B-frames are non-reference pictures: encode them all concurrently. + self.encode_batch(bs, out); + } + } + + fn import(&mut self, f: &YuvFrame) -> Result { + let (w, h) = (self.cfg.width as usize, self.cfg.height as usize); + let (cw, ch) = (w / 2, h / 2); + if f.y_stride < w + || f.uv_stride < cw + || f.y.len() < f.y_stride * (h - 1) + w + || f.u.len() < f.uv_stride * (ch - 1) + cw + || f.v.len() < f.uv_stride * (ch - 1) + cw + { + return Err(Error::BadFrame); + } + let mut fr = self.new_frame(); + for (pl, src, stride, pw, ph) in + [(&mut fr.y, f.y, f.y_stride, w, h), (&mut fr.u, f.u, f.uv_stride, cw, ch), (&mut fr.v, f.v, f.uv_stride, cw, ch)] + { + let aw = pl.w; + for y in 0..pl.h { + let sy = y.min(ph - 1); + let o = pl.idx(0, y as isize); + pl.data[o..o + pw].copy_from_slice(&src[sy * stride..sy * stride + pw]); + let last = pl.data[o + pw - 1]; + pl.data[o + pw..o + aw].fill(last); + } + } + Ok(fr) + } + + fn new_frame(&mut self) -> Frame { + self.frame_pool.pop().unwrap_or_else(|| Frame::new(self.params.mbw * 16, self.params.mbh * 16)) + } + + fn recycle(&mut self, f: Frame) { + if self.frame_pool.len() < 8 { + self.frame_pool.push(f); + } + } + + /// Encode a batch of pictures that do not reference each other (a single I/P picture, or the B-frames of a + /// mini-GOP). Slices of all pictures in the batch are encoded in parallel. + fn encode_batch(&mut self, pics: Vec<(Input, SliceType, bool)>, out: &mut Vec) { + let mut jobs: Vec = Vec::with_capacity(pics.len()); + for (input, st, idr) in pics { + let job = self.plan_job(input, st, idr, jobs.len()); + jobs.push(job); + } + self.encode_slices(&mut jobs); + // ---- finalise each picture in coding order + for job in jobs { + self.finalise_job(job, out); + } + } + + /// Choose the final slice type and references of one picture, derive its quantiser and slice header, + /// and allocate its reconstruction buffers. `queued` is the number of pictures already planned in the batch. + fn plan_job(&mut self, input: Input, mut st: SliceType, idr: bool, queued: usize) -> Job { + if idr { + let old: Vec = std::mem::take(&mut self.dpb); + for r in old { + self.recycle_ref(r); + } + self.next_frame_num = 0; + } + let poc = 2 * (input.display as i64 - self.last_idr as i64) as i32; + let by_id = self.dpb.iter().enumerate().max_by_key(|(_, r)| r.id).map(|(i, _)| i); + let (mut l0, mut l1) = match st { + SliceType::I => (None, None), + SliceType::P => (by_id, None), + SliceType::B => ( + self.dpb.iter().enumerate().filter(|(_, r)| r.poc < poc).max_by_key(|(_, r)| r.poc).map(|(i, _)| i), + self.dpb.iter().enumerate().filter(|(_, r)| r.poc > poc).min_by_key(|(_, r)| r.poc).map(|(i, _)| i), + ), + }; + if st == SliceType::B && (l0.is_none() || l1.is_none()) { + st = SliceType::P; + l0 = by_id; + l1 = None; + } + if st == SliceType::P && l0.is_none() { + st = SliceType::I; + } + let is_ref = st != SliceType::B; + let cplx = match st { + SliceType::I => input.cplx.intra, + _ => input.cplx.inter, + } as f64; + let qpf = self.rc.frame_qp(st, cplx, self.coded_count as usize + queued); + let qp = qpf.round().clamp(0.0, 51.0) as u8; + let aq: Vec = if input.aq.is_empty() { + Vec::new() + } else { + input.aq.iter().map(|&o| ((qpf + o as f64).round().clamp(0.0, 51.0) as i32 - qp as i32) as i8).collect() + }; + let nal_ref_idc = if idr { + 3 + } else if is_ref { + 2 + } else { + 0 + }; + let hdr = SliceHeader { + first_mb: 0, + slice_type: st, + nal_ref_idc, + idr, + idr_pic_id: self.idr_count % 2, + frame_num: self.next_frame_num % (1 << LOG2_MAX_FRAME_NUM), + log2_max_frame_num: LOG2_MAX_FRAME_NUM, + poc_lsb: (poc as u32) % (1 << LOG2_MAX_POC_LSB), + log2_max_poc_lsb: LOG2_MAX_POC_LSB, + l0_modification: None, + cabac: self.params.cabac, + slice_qp: qp as i32, + pic_init_qp: 26, + disable_deblocking: if self.slices > 1 { 2 } else { 0 }, + alpha_offset_div2: 0, + beta_offset_div2: 0, + }; + let rec = self.new_frame(); + let mbs = vec![MbInfo::default(); self.params.mbw * self.params.mbh]; + Job { input, st, idr, qpf, qp, cplx, aq, hdr, nal_ref_idc, l0, l1, poc, rec: Some(rec), mbs, results: Vec::new() } + } + + /// Encode all slices of the batch's pictures in parallel (with deblocking of each slice band), storing + /// the results and reconstructions back into `jobs`. + fn encode_slices(&self, jobs: &mut [Job]) { + // ---- parallel slice encoding (+ deblocking of each slice band) + let (mbw, mbh) = (self.params.mbw, self.params.mbh); + let n = self.slices; + let bounds: Vec<(usize, usize)> = (0..n).map(|s| (s * mbh / n, (s + 1) * mbh / n)).filter(|(a, b)| b > a).collect(); + let capture = self.recon.is_some(); + let mut slice_results: Vec> = (0..jobs.len()).map(|_| Vec::new()).collect(); + // Every job carries its reconstruction buffer (`rec: Some(..)` above). + let mut bufs: Vec<(Frame, Vec)> = jobs.iter_mut().filter_map(|j| Some((j.rec.take()?, std::mem::take(&mut j.mbs)))).collect(); + { + let this = self; + let fencs: Vec = jobs + .iter() + .map(|j| FrameEnc { + p: &this.params, + qt: &this.qt, + cost_tab: &this.cost_tab, + st: j.hdr.slice_type, + src: &j.input.frame, + l0: j.l0.map(|i| &this.dpb[i]), + l1: j.l1.map(|i| &this.dpb[i]), + qp: j.qp, + aq: &j.aq, + hdr: j.hdr.clone(), + nal_ref_idc: j.nal_ref_idc, + nal_type: if j.idr { NAL_IDR } else { NAL_SLICE }, + cabac_table: if j.hdr.slice_type == SliceType::I { &this.cabac_i } else { &this.cabac_pb }, + lam: &this.lam, + }) + .collect(); + let dps: Vec = jobs + .iter() + .map(|j| DeblockParams { + alpha_offset: 0, + beta_offset: 0, + chroma_qp_offset: this.params.chroma_qp_offset, + ref_ids: [j.l0.map_or(-1, |i| this.dpb[i].id as i32), j.l1.map_or(-1, |i| this.dpb[i].id as i32)], + }) + .collect(); + let deblock: Vec = jobs.iter().map(|j| j.hdr.slice_type != SliceType::B || capture).collect(); + type Task<'t> = (usize, usize, usize, usize, &'t mut [u8], &'t mut [u8], &'t mut [u8], &'t mut [MbInfo]); + let mut tasks: Vec = Vec::new(); + for (pi, (rec, mbs)) in bufs.iter_mut().enumerate() { + let ys = rec.y.stride; + let cs = rec.u.stride; + let mut yrest = &mut rec.y.data[LUMA_PAD * ys..]; + let mut urest = &mut rec.u.data[CHROMA_PAD * cs..]; + let mut vrest = &mut rec.v.data[CHROMA_PAD * cs..]; + let mut mrest = &mut mbs[..]; + for (k, &(r0, r1)) in bounds.iter().enumerate() { + let rows = r1 - r0; + let (a, b) = std::mem::take(&mut yrest).split_at_mut(rows * 16 * ys); + yrest = b; + let (ua, ub) = std::mem::take(&mut urest).split_at_mut(rows * 8 * cs); + urest = ub; + let (va, vb) = std::mem::take(&mut vrest).split_at_mut(rows * 8 * cs); + vrest = vb; + let (ma, mb) = std::mem::take(&mut mrest).split_at_mut(rows * mbw); + mrest = mb; + tasks.push((pi, k, r0, r1, a, ua, va, ma)); + } + } + let ys = jobs_stride_y(mbw); + let cs = jobs_stride_c(mbw); + let run = |(pi, k, r0, r1, y, u, v, m): Task| -> (usize, usize, SliceOut) { + let out = SliceEnc::new(&fencs[pi], Band { y: &mut *y, u: &mut *u, v: &mut *v, ys, cs }, &mut *m, r0, r1, k as u16).encode(); + if deblock[pi] { + let mut by = BandPlane { data: y, stride: ys, pad: LUMA_PAD }; + let mut bu = BandPlane { data: u, stride: cs, pad: CHROMA_PAD }; + let mut bv = BandPlane { data: v, stride: cs, pad: CHROMA_PAD }; + deblock_band(&mut by, &mut bu, &mut bv, m, mbw, r1 - r0, &dps[pi]); + } + (pi, k, out) + }; + #[cfg(feature = "threads")] + let results: Vec<(usize, usize, SliceOut)> = { + use rayon::prelude::*; + if tasks.len() > 1 { this.pool.install(|| tasks.into_par_iter().map(run).collect()) } else { tasks.into_iter().map(run).collect() } + }; + #[cfg(not(feature = "threads"))] + let results: Vec<(usize, usize, SliceOut)> = tasks.into_iter().map(run).collect(); + let mut results = results; + results.sort_by_key(|r| (r.0, r.1)); + for (pi, _, r) in results { + slice_results[pi].push(r); + } + } + for ((j, (rec, mbs)), res) in jobs.iter_mut().zip(bufs).zip(slice_results) { + j.rec = Some(rec); + j.mbs = mbs; + j.results = res; + } + } + + /// Assemble the packet of one coded picture, update rate control and the reference state. + fn finalise_job(&mut self, job: Job, out: &mut Vec) { + let Job { input, st, idr, qpf, qp, cplx, hdr: _, nal_ref_idc: _, l0: _, l1: _, poc, rec, mbs, results, aq: _ } = job; + let Some(mut rec) = rec else { return }; + let is_ref = st != SliceType::B; + let annexb = self.cfg.format == PacketFormat::AnnexB; + let mut nals: Vec> = Vec::new(); + if annexb && self.cfg.aud { + let t = match st { + SliceType::I => 0, + SliceType::P => 1, + SliceType::B => 2, + }; + nals.push(nal::nal(0, NAL_AUD, &nal::aud_rbsp(t))); + } + if idr && annexb { + nals.push(self.sps_nal.clone()); + nals.push(self.pps_nal.clone()); + } + if !self.sei_sent { + self.sei_sent = true; + let text = format!("craft-h264enc {} - clean-room H.264 encoder - options: {}", env!("CARGO_PKG_VERSION"), self.options()); + nals.push(nal::nal(0, NAL_SEI, &nal::sei_user_data_rbsp(&text))); + if self.cfg.mastering_display.is_some() || self.cfg.content_light.is_some() { + nals.push(nal::nal(0, NAL_SEI, &nal::sei_hdr_rbsp(self.cfg.mastering_display.as_ref(), self.cfg.content_light))); + } + } + for r in results { + nals.push(r.nal); + } + let mut data = Vec::with_capacity(nals.iter().map(|n| n.len() + 4).sum()); + for n in &nals { + if annexb { + data.extend_from_slice(&[0, 0, 0, 1]); + } else { + data.extend_from_slice(&(n.len() as u32).to_be_bytes()); + } + data.extend_from_slice(n); + } + let total_bits = data.len() as u64 * 8; + self.rc.update(st, qpf, cplx, total_bits); + if self.record_pass1 { + self.rc.record_pass1(st, qpf, cplx, total_bits); + } + if let Some(rv) = self.recon.as_mut() { + let (w, h) = (self.cfg.width as usize, self.cfg.height as usize); + let mut y = Vec::with_capacity(w * h); + let mut u = Vec::with_capacity(w * h / 4); + let mut v = Vec::with_capacity(w * h / 4); + rec.y.copy_out(w, h, &mut y); + rec.u.copy_out(w / 2, h / 2, &mut u); + rec.v.copy_out(w / 2, h / 2, &mut v); + rv.push(ReconFrame { pts: input.pts, y, u, v }); + } + if is_ref { + let hp = self.hpel_pool.pop(); + let hpel = self.pool.install(|| { + rec.extend_edges(); + build_hpel(&rec.y, hp) + }); + self.ref_uid += 1; + let rp = RefPic { frame: rec, hpel, mbs, poc, id: self.ref_uid }; + if self.dpb.len() >= self.max_refs { + // sliding window: drop the oldest + if let Some(oldest) = self.dpb.iter().enumerate().min_by_key(|(_, r)| r.id).map(|(i, _)| i) { + let r = self.dpb.remove(oldest); + self.recycle_ref(r); + } + } + self.dpb.push(rp); + self.next_frame_num = (self.next_frame_num + 1) % (1 << LOG2_MAX_FRAME_NUM); + } else { + self.recycle(rec); + } + if idr { + self.idr_count += 1; + } + // decoding timestamp base: the k-th smallest presentation time (offset applied in `release`) + // One display time is queued per input picture, so the queue is never empty here. + let pos = self.display_pts.iter().enumerate().min_by_key(|(_, p)| **p).map(|(i, _)| i); + let dts_base = pos.and_then(|p| self.display_pts.remove(p)).unwrap_or(input.pts); + self.coded_count += 1; + let frame_type = if idr { + FrameType::Idr + } else { + match st { + SliceType::I => FrameType::I, + SliceType::P => FrameType::P, + SliceType::B => FrameType::B, + } + }; + self.recycle(input.frame); + out.push(Packet { data, pts: input.pts, dts: dts_base, keyframe: idr, frame_type, qp }); + } + + fn recycle_ref(&mut self, r: RefPic) { + self.recycle(r.frame); + if self.hpel_pool.len() < 4 { + self.hpel_pool.push(r.hpel); + } + } +} + +/// One picture of a batch while it is planned, sliced and finalised. +struct Job { + input: Input, + st: SliceType, + idr: bool, + qpf: f64, + qp: u8, + cplx: f64, + aq: Vec, + hdr: SliceHeader, + nal_ref_idc: u8, + l0: Option, + l1: Option, + poc: i32, + rec: Option, + mbs: Vec, + results: Vec, +} + +fn jobs_stride_y(mbw: usize) -> usize { + mbw * 16 + 2 * LUMA_PAD +} + +fn jobs_stride_c(mbw: usize) -> usize { + mbw * 8 + 2 * CHROMA_PAD +} diff --git a/crates/h264enc/src/lookahead.rs b/crates/h264enc/src/lookahead.rs new file mode 100644 index 0000000000..c2140b9ac9 --- /dev/null +++ b/crates/h264enc/src/lookahead.rs @@ -0,0 +1,156 @@ +//! Cheap half-resolution analysis used for rate control, scene-cut detection and adaptive quantisation. + +use crate::dsp::{block_var, sad, satd}; +use crate::picture::Frame; + +/// Half-resolution luma (2x2 box filtered) of the macroblock-aligned picture. +pub struct LowRes { + pub w: usize, + pub h: usize, + pub data: Vec, +} + +impl LowRes { + pub fn new(f: &Frame) -> Self { + let (w, h) = (f.y.w / 2, f.y.h / 2); + let mut data = vec![0u8; w * h]; + let row = |y: usize, out: &mut [u8]| { + let r0 = f.y.idx(0, (2 * y) as isize); + let r1 = f.y.idx(0, (2 * y + 1) as isize); + for x in 0..w { + let s = f.y.data[r0 + 2 * x] as u32 + f.y.data[r0 + 2 * x + 1] as u32 + f.y.data[r1 + 2 * x] as u32 + f.y.data[r1 + 2 * x + 1] as u32; + out[x] = ((s + 2) >> 2) as u8; + } + }; + #[cfg(feature = "threads")] + { + use rayon::prelude::*; + data.par_chunks_mut(w).enumerate().for_each(|(y, r)| row(y, r)); + } + #[cfg(not(feature = "threads"))] + for (y, r) in data.chunks_mut(w).enumerate() { + row(y, r); + } + LowRes { w, h, data } + } +} + +/// Per-frame complexity estimates (sum over 8x8 low-res blocks, i.e. per macroblock). +#[derive(Clone, Copy, Debug, Default)] +pub struct Complexity { + pub intra: u64, + /// Inter cost against the previous frame (min with intra per block); equals `intra` when there is no previous frame. + pub inter: u64, +} + +fn block_rows (u64, u64) + Sync + Send>(rows: usize, f: F) -> (u64, u64) { + #[cfg(feature = "threads")] + { + use rayon::prelude::*; + (0..rows).into_par_iter().map(f).reduce(|| (0, 0), |a, b| (a.0 + b.0, a.1 + b.1)) + } + #[cfg(not(feature = "threads"))] + { + (0..rows).map(f).fold((0, 0), |a, b| (a.0 + b.0, a.1 + b.1)) + } +} + +pub fn complexity(cur: &LowRes, prev: Option<&LowRes>) -> Complexity { + let bw = cur.w / 8; + let bh = cur.h / 8; + let (intra, inter) = block_rows(bh, |by| { + let mut ci = 0u64; + let mut cp = 0u64; + let mut blk = [0u8; 64]; + for bx in 0..bw { + let (x0, y0) = (bx * 8, by * 8); + let base = y0 * cur.w + x0; + // intra: best of DC / H / V predictions from source neighbours + let mut best = u32::MAX; + for mode in 0..3 { + if (mode == 1 && x0 == 0) || (mode == 2 && y0 == 0) { + continue; + } + for y in 0..8 { + for x in 0..8 { + blk[y * 8 + x] = match mode { + 0 => { + if x0 > 0 && y0 > 0 { + ((cur.data[(y0 - 1) * cur.w + x0 + x] as u32 + cur.data[(y0 + y) * cur.w + x0 - 1] as u32) / 2) as u8 + } else { + 128 + } + } + 1 => cur.data[(y0 + y) * cur.w + x0 - 1], + _ => cur.data[(y0 - 1) * cur.w + x0 + x], + }; + } + } + best = best.min(satd(&cur.data[base..], cur.w, &blk, 8, 8, 8)); + } + let intra_c = best as u64 + 16; + ci += intra_c; + if let Some(p) = prev { + // small diamond search on SAD, then SATD of the best + let mut bmv = (0i32, 0i32); + let cost_at = |mx: i32, my: i32| -> u32 { + let x = x0 as i32 + mx; + let y = y0 as i32 + my; + if x < 0 || y < 0 || x + 8 > p.w as i32 || y + 8 > p.h as i32 { + return u32::MAX; + } + sad(&cur.data[base..], cur.w, &p.data[y as usize * p.w + x as usize..], p.w, 8, 8) + }; + let mut bc = cost_at(0, 0); + for _ in 0..8 { + let c = bmv; + for (dx, dy) in [(-1, 0), (1, 0), (0, -1), (0, 1), (-2, 0), (2, 0), (0, -2), (0, 2)] { + let k = cost_at(c.0 + dx, c.1 + dy); + if k < bc { + bc = k; + bmv = (c.0 + dx, c.1 + dy); + } + } + if bmv == c { + break; + } + } + let x = (x0 as i32 + bmv.0) as usize; + let y = (y0 as i32 + bmv.1) as usize; + let inter_c = satd(&cur.data[base..], cur.w, &p.data[y * p.w + x..], p.w, 8, 8) as u64 + 8; + cp += inter_c.min(intra_c); + } else { + cp += intra_c; + } + } + (ci, cp) + }); + Complexity { intra, inter } +} + +/// Variance-based adaptive quantisation offsets per macroblock (QP units, mean ~0). +pub fn aq_offsets(f: &Frame, mbw: usize, mbh: usize, strength: f32) -> Vec { + let mut e = vec![0f32; mbw * mbh]; + let row = |my: usize, e: &mut [f32]| { + for mx in 0..mbw { + let i = f.y.idx((mx * 16) as isize, (my * 16) as isize); + let vy = block_var(&f.y.data[i..], f.y.stride, 16, 16) as f32 / 256.0; + let iu = f.u.idx((mx * 8) as isize, (my * 8) as isize); + let vu = block_var(&f.u.data[iu..], f.u.stride, 8, 8) as f32 / 64.0; + let iv = f.v.idx((mx * 8) as isize, (my * 8) as isize); + let vv = block_var(&f.v.data[iv..], f.v.stride, 8, 8) as f32 / 64.0; + e[mx] = (vy + 0.5 * (vu + vv) + 1.0).log2(); + } + }; + #[cfg(feature = "threads")] + { + use rayon::prelude::*; + e.par_chunks_mut(mbw).enumerate().for_each(|(my, r)| row(my, r)); + } + #[cfg(not(feature = "threads"))] + for (my, r) in e.chunks_mut(mbw).enumerate() { + row(my, r); + } + let mean = e.iter().sum::() / e.len() as f32; + e.iter().map(|&v| (strength * (v - mean)).clamp(-8.0, 8.0)).collect() +} diff --git a/crates/h264enc/src/mbinfo.rs b/crates/h264enc/src/mbinfo.rs new file mode 100644 index 0000000000..99264d9ef0 --- /dev/null +++ b/crates/h264enc/src/mbinfo.rs @@ -0,0 +1,126 @@ +//! Per-macroblock state kept for neighbour context derivation, deblocking and co-located (direct) prediction. + +#[derive(Clone, Copy, PartialEq, Eq, Debug, Default, Hash)] +pub struct Mv { + pub x: i16, + pub y: i16, +} + +impl Mv { + pub const ZERO: Mv = Mv { x: 0, y: 0 }; + #[inline] + pub const fn new(x: i32, y: i32) -> Mv { + Mv { x: x as i16, y: y as i16 } + } +} + +#[derive(Clone, Copy, PartialEq, Eq, Debug, Default)] +pub enum MbKind { + #[default] + None, + I4x4, + I8x8, + I16x16, + PSkip, + PInter, + BSkip, + BDirect, + BInter, +} + +impl MbKind { + #[inline] + pub fn is_intra(self) -> bool { + matches!(self, MbKind::I4x4 | MbKind::I8x8 | MbKind::I16x16) + } + #[inline] + pub fn is_skip(self) -> bool { + matches!(self, MbKind::PSkip | MbKind::BSkip) + } + #[inline] + pub fn is_inxn(self) -> bool { + matches!(self, MbKind::I4x4 | MbKind::I8x8) + } +} + +/// Partition shapes for inter macroblocks. +#[derive(Clone, Copy, PartialEq, Eq, Debug, Default)] +pub enum Part { + #[default] + P16x16, + P16x8, + P8x16, + P8x8, +} + +impl Part { + pub fn count(self) -> usize { + match self { + Part::P16x16 => 1, + Part::P16x8 | Part::P8x16 => 2, + Part::P8x8 => 4, + } + } + /// (x, y, w, h) in pixels of partition `i`. + pub fn rect(self, i: usize) -> (usize, usize, usize, usize) { + match self { + Part::P16x16 => (0, 0, 16, 16), + Part::P16x8 => (0, 8 * i, 16, 8), + Part::P8x16 => (8 * i, 0, 8, 16), + Part::P8x8 => (8 * (i & 1), 8 * (i >> 1), 8, 8), + } + } +} + +#[derive(Clone, Copy, Debug)] +pub struct MbInfo { + pub kind: MbKind, + /// Slice number + 1 (0 = not yet coded). + pub slice: u16, + pub qp: u8, + /// Luma cbp in bits 0..3, chroma cbp in bits 4..5. + pub cbp: u8, + pub t8x8: bool, + pub chroma_mode: u8, + /// Non-zero coefficient counts per luma 4x4 (raster y*4+x). + pub nnz: [u8; 16], + /// Chroma AC non-zero counts: Cb raster 2x2 then Cr raster 2x2. + pub nnz_c: [u8; 8], + /// bit0 luma DC (Intra16x16), bit1 Cb DC, bit2 Cr DC. + pub dc_cbf: u8, + /// Intra 4x4/8x8 prediction modes per 4x4 (raster), -1 when the MB is not I_NxN. + pub imodes: [i8; 16], + /// Reference index per 8x8 (raster) per list; -1 = list unused. + pub ref_idx: [[i8; 4]; 2], + pub mv: [[Mv; 16]; 2], + /// |mvd| components per 4x4 per list, clamped to 127. + pub mvd: [[[u8; 2]; 16]; 2], +} + +impl Default for MbInfo { + fn default() -> Self { + MbInfo { + kind: MbKind::None, + slice: 0, + qp: 0, + cbp: 0, + t8x8: false, + chroma_mode: 0, + nnz: [0; 16], + nnz_c: [0; 8], + dc_cbf: 0, + imodes: [-1; 16], + ref_idx: [[-1; 4]; 2], + mv: [[Mv::ZERO; 16]; 2], + mvd: [[[0; 2]; 16]; 2], + } + } +} + +impl MbInfo { + #[inline] + pub fn ref_at(&self, list: usize, blk4_raster: usize) -> i8 { + let (x, y) = (blk4_raster & 3, blk4_raster >> 2); + self.ref_idx[list][(y >> 1) * 2 + (x >> 1)] + } +} diff --git a/crates/h264enc/src/nal.rs b/crates/h264enc/src/nal.rs new file mode 100644 index 0000000000..355eeb2ec6 --- /dev/null +++ b/crates/h264enc/src/nal.rs @@ -0,0 +1,352 @@ +//! NAL unit and parameter-set/slice-header generation (§7.3), SEI and the AVCDecoderConfigurationRecord. + +use craft_media::bitstream::{BitWriter, escape_rbsp}; + +pub const NAL_SLICE: u8 = 1; +pub const NAL_IDR: u8 = 5; +pub const NAL_SEI: u8 = 6; +pub const NAL_SPS: u8 = 7; +pub const NAL_PPS: u8 = 8; +pub const NAL_AUD: u8 = 9; + +/// Build a NAL unit (header + escaped RBSP), without start code. +pub fn nal(nal_ref_idc: u8, nal_type: u8, rbsp: &[u8]) -> Vec { + let mut out = Vec::with_capacity(rbsp.len() + rbsp.len() / 64 + 2); + out.push((nal_ref_idc << 5) | nal_type); + out.extend_from_slice(&escape_rbsp(rbsp)); + out +} + +#[derive(Clone, Debug)] +pub struct Vui { + pub sar: (u16, u16), + pub video_full_range: bool, + pub colour_primaries: u8, + pub transfer: u8, + pub matrix: u8, + pub num_units_in_tick: u32, + pub time_scale: u32, + pub max_num_reorder_frames: u32, + pub max_dec_frame_buffering: u32, +} + +#[derive(Clone, Debug)] +pub struct Sps { + pub profile_idc: u8, + pub constraint_flags: u8, + pub level_idc: u8, + pub width_mbs: u32, + pub height_mbs: u32, + pub crop_right: u32, + pub crop_bottom: u32, + pub log2_max_frame_num: u32, + pub log2_max_poc_lsb: u32, + pub max_num_ref_frames: u32, + pub vui: Vui, +} + +impl Sps { + pub fn rbsp(&self) -> Vec { + let mut w = BitWriter::new(); + w.write_bits(self.profile_idc as u32, 8); + w.write_bits(self.constraint_flags as u32, 8); + w.write_bits(self.level_idc as u32, 8); + w.write_ue(0); // seq_parameter_set_id + if matches!(self.profile_idc, 100 | 110 | 122 | 244 | 44 | 83 | 86 | 118 | 128 | 138 | 139 | 134 | 135) { + w.write_ue(1); // chroma_format_idc 4:2:0 + w.write_ue(0); // bit_depth_luma_minus8 + w.write_ue(0); // bit_depth_chroma_minus8 + w.write_bit(false); // qpprime_y_zero_transform_bypass_flag + w.write_bit(false); // seq_scaling_matrix_present_flag + } + w.write_ue(self.log2_max_frame_num - 4); + w.write_ue(0); // pic_order_cnt_type + w.write_ue(self.log2_max_poc_lsb - 4); + w.write_ue(self.max_num_ref_frames); + w.write_bit(false); // gaps_in_frame_num_value_allowed_flag + w.write_ue(self.width_mbs - 1); + w.write_ue(self.height_mbs - 1); + w.write_bit(true); // frame_mbs_only_flag + w.write_bit(true); // direct_8x8_inference_flag + let crop = self.crop_right > 0 || self.crop_bottom > 0; + w.write_bit(crop); + if crop { + w.write_ue(0); + w.write_ue(self.crop_right / 2); + w.write_ue(0); + w.write_ue(self.crop_bottom / 2); + } + w.write_bit(true); // vui_parameters_present_flag + let v = &self.vui; + let has_sar = v.sar != (0, 0); + w.write_bit(has_sar); + if has_sar { + if v.sar == (1, 1) { + w.write_bits(1, 8); + } else { + w.write_bits(255, 8); // Extended_SAR + w.write_bits(v.sar.0 as u32, 16); + w.write_bits(v.sar.1 as u32, 16); + } + } + w.write_bit(false); // overscan_info_present_flag + w.write_bit(true); // video_signal_type_present_flag + w.write_bits(5, 3); // video_format: unspecified + w.write_bit(v.video_full_range); + w.write_bit(true); // colour_description_present_flag + w.write_bits(v.colour_primaries as u32, 8); + w.write_bits(v.transfer as u32, 8); + w.write_bits(v.matrix as u32, 8); + w.write_bit(false); // chroma_loc_info_present_flag + w.write_bit(true); // timing_info_present_flag + w.write_bits(v.num_units_in_tick, 32); + w.write_bits(v.time_scale, 32); + w.write_bit(true); // fixed_frame_rate_flag + w.write_bit(false); // nal_hrd_parameters_present_flag + w.write_bit(false); // vcl_hrd_parameters_present_flag + w.write_bit(false); // pic_struct_present_flag + w.write_bit(true); // bitstream_restriction_flag + w.write_bit(true); // motion_vectors_over_pic_boundaries_flag + w.write_ue(0); // max_bytes_per_pic_denom + w.write_ue(0); // max_bits_per_mb_denom + w.write_ue(11); // log2_max_mv_length_horizontal (±512 samples) + w.write_ue(11); // log2_max_mv_length_vertical + w.write_ue(v.max_num_reorder_frames); + w.write_ue(v.max_dec_frame_buffering); + w.rbsp_trailing(); + w.finish() + } +} + +#[derive(Clone, Debug)] +pub struct Pps { + pub cabac: bool, + pub pic_init_qp: i32, + pub chroma_qp_offset: i32, + pub transform_8x8: bool, + pub high: bool, +} + +impl Pps { + pub fn rbsp(&self) -> Vec { + let mut w = BitWriter::new(); + w.write_ue(0); // pic_parameter_set_id + w.write_ue(0); // seq_parameter_set_id + w.write_bit(self.cabac); + w.write_bit(false); // bottom_field_pic_order_in_frame_present_flag + w.write_ue(0); // num_slice_groups_minus1 + w.write_ue(0); // num_ref_idx_l0_default_active_minus1 + w.write_ue(0); // num_ref_idx_l1_default_active_minus1 + w.write_bit(false); // weighted_pred_flag + w.write_bits(0, 2); // weighted_bipred_idc + w.write_se(self.pic_init_qp - 26); + w.write_se(0); // pic_init_qs_minus26 + w.write_se(self.chroma_qp_offset); + w.write_bit(true); // deblocking_filter_control_present_flag + w.write_bit(false); // constrained_intra_pred_flag + w.write_bit(false); // redundant_pic_cnt_present_flag + if self.high { + w.write_bit(self.transform_8x8); + w.write_bit(false); // pic_scaling_matrix_present_flag + w.write_se(self.chroma_qp_offset); // second_chroma_qp_index_offset + } + w.rbsp_trailing(); + w.finish() + } +} + +/// Access unit delimiter RBSP. `primary_pic_type`: 0 = I, 1 = I/P, 2 = I/P/B. +pub fn aud_rbsp(primary_pic_type: u8) -> Vec { + let mut w = BitWriter::new(); + w.write_bits(primary_pic_type as u32, 3); + w.rbsp_trailing(); + w.finish() +} + +/// user_data_unregistered SEI carrying an encoder identification string. +pub fn sei_user_data_rbsp(text: &str) -> Vec { + const UUID: [u8; 16] = [0x7a, 0x3c, 0x1f, 0x52, 0xd4, 0x0b, 0x4e, 0x8e, 0x9d, 0x61, 0x2f, 0xc4, 0x93, 0x05, 0xb7, 0x1a]; + let mut payload = UUID.to_vec(); + payload.extend_from_slice(text.as_bytes()); + let mut w = BitWriter::new(); + w.write_bits(5, 8); // payloadType user_data_unregistered + let mut size = payload.len(); + while size >= 255 { + w.write_bits(255, 8); + size -= 255; + } + w.write_bits(size as u32, 8); + w.write_bytes(&payload); + w.rbsp_trailing(); + w.finish() +} + +/// SEI with HDR static metadata: mastering_display_colour_volume (payloadType 137, the 24-byte +/// ST 2086 payload) and/or content_light_level_info (payloadType 144), H.264 Annex D. +pub fn sei_hdr_rbsp(mastering: Option<&[u8; 24]>, cll: Option<(u16, u16)>) -> Vec { + let mut w = BitWriter::new(); + if let Some(m) = mastering { + w.write_bits(137, 8); + w.write_bits(24, 8); + w.write_bytes(m); + } + if let Some((a, b)) = cll { + w.write_bits(144, 8); + w.write_bits(4, 8); + w.write_bits(a as u32, 16); + w.write_bits(b as u32, 16); + } + w.rbsp_trailing(); + w.finish() +} + +#[derive(Clone, Copy, PartialEq, Eq, Debug)] +pub enum SliceType { + P = 0, + B = 1, + I = 2, +} + +#[derive(Clone, Debug)] +pub struct SliceHeader { + pub first_mb: u32, + pub slice_type: SliceType, + pub nal_ref_idc: u8, + pub idr: bool, + pub idr_pic_id: u32, + pub frame_num: u32, + pub log2_max_frame_num: u32, + pub poc_lsb: u32, + pub log2_max_poc_lsb: u32, + /// Reorder RefPicList0 so that this picNum difference (abs_diff_pic_num_minus1, subtract) comes first. + pub l0_modification: Option, + pub cabac: bool, + pub slice_qp: i32, + pub pic_init_qp: i32, + pub disable_deblocking: u32, + pub alpha_offset_div2: i32, + pub beta_offset_div2: i32, +} + +impl SliceHeader { + /// Write the slice header into `w`; for CABAC the caller must byte-align with one-bits afterwards. + pub fn write(&self, w: &mut BitWriter) { + w.write_ue(self.first_mb); + w.write_ue(self.slice_type as u32); + w.write_ue(0); // pic_parameter_set_id + w.write_bits(self.frame_num, self.log2_max_frame_num); + if self.idr { + w.write_ue(self.idr_pic_id); + } + w.write_bits(self.poc_lsb, self.log2_max_poc_lsb); + if self.slice_type == SliceType::B { + w.write_bit(true); // direct_spatial_mv_pred_flag + } + if self.slice_type != SliceType::I { + w.write_bit(false); // num_ref_idx_active_override_flag + // ref_pic_list_modification + match self.l0_modification { + Some(d) => { + w.write_bit(true); + w.write_ue(0); // modification_of_pic_nums_idc: subtract + w.write_ue(d); + w.write_ue(3); + } + None => w.write_bit(false), + } + if self.slice_type == SliceType::B { + w.write_bit(false); + } + } + if self.nal_ref_idc != 0 { + if self.idr { + w.write_bit(false); // no_output_of_prior_pics_flag + w.write_bit(false); // long_term_reference_flag + } else { + w.write_bit(false); // adaptive_ref_pic_marking_mode_flag + } + } + if self.cabac && self.slice_type != SliceType::I { + w.write_ue(0); // cabac_init_idc + } + w.write_se(self.slice_qp - self.pic_init_qp); + w.write_ue(self.disable_deblocking); + if self.disable_deblocking != 1 { + w.write_se(self.alpha_offset_div2); + w.write_se(self.beta_offset_div2); + } + } +} + +/// AVCDecoderConfigurationRecord (ISO/IEC 14496-15 §5.3.3.1) with 4-byte NAL lengths. +pub fn avcc(sps_nal: &[u8], pps_nal: &[u8], profile_idc: u8, constraint: u8, level: u8) -> Vec { + let mut v = vec![1, profile_idc, constraint, level, 0xFF, 0xE1]; + v.extend_from_slice(&(sps_nal.len() as u16).to_be_bytes()); + v.extend_from_slice(sps_nal); + v.push(1); + v.extend_from_slice(&(pps_nal.len() as u16).to_be_bytes()); + v.extend_from_slice(pps_nal); + if matches!(profile_idc, 100 | 110 | 122 | 144) { + v.push(0xFC | 1); // chroma_format 4:2:0 + v.push(0xF8); // bit_depth_luma_minus8 + v.push(0xF8); // bit_depth_chroma_minus8 + v.push(0); // numOfSequenceParameterSetExt + } + v +} + +/// The requested level when it is at least `needed` (and a valid level_idc), else `needed`. +/// Level 1b (`11` with constraint_set3) is not distinguished; 9 is not a level here. +pub fn level_at_least(requested: u8, needed: u8) -> u8 { + const VALID: [u8; 19] = [10, 11, 12, 13, 20, 21, 22, 30, 31, 32, 40, 41, 42, 50, 51, 52, 60, 61, 62]; + if VALID.contains(&requested) && requested >= needed { requested } else { needed } +} + +/// Pick the lowest level satisfying frame size, macroblock rate, DPB size and (optionally) bitrate. +pub fn pick_level(width_mbs: u32, height_mbs: u32, fps: f64, dpb_frames: u32, kbps: Option, high: bool) -> Option { + // (level_idc, MaxMBPS, MaxFS, MaxDpbMbs, MaxBR kbit/s) + const LEVELS: [(u8, u64, u64, u64, u64); 19] = [ + (10, 1485, 99, 396, 64), + (11, 3000, 396, 900, 192), + (12, 6000, 396, 2376, 384), + (13, 11880, 396, 2376, 768), + (20, 11880, 396, 2376, 2000), + (21, 19800, 792, 4752, 4000), + (22, 20250, 1620, 8100, 4000), + (30, 40500, 1620, 8100, 10000), + (31, 108000, 3600, 18000, 14000), + (32, 216000, 5120, 20480, 20000), + (40, 245760, 8192, 32768, 20000), + (41, 245760, 8192, 32768, 50000), + (42, 522240, 8704, 34816, 50000), + (50, 589824, 22080, 110400, 135000), + (51, 983040, 36864, 184320, 240000), + (52, 2073600, 36864, 184320, 240000), + (60, 4177920, 139264, 696320, 240000), + (61, 8355840, 139264, 696320, 480000), + (62, 16711680, 139264, 696320, 800000), + ]; + let fs = (width_mbs * height_mbs) as u64; + let mbps = (fs as f64 * fps).ceil() as u64; + for &(l, max_mbps, max_fs, max_dpb, max_br) in &LEVELS { + let br_ok = kbps.is_none_or(|k| (k as u64) * 1000 <= max_br * if high { 1250 } else { 1000 }); + let dim_ok = (width_mbs as u64).pow(2) <= 8 * max_fs && (height_mbs as u64).pow(2) <= 8 * max_fs; + if fs <= max_fs && mbps <= max_mbps && fs * dpb_frames as u64 <= max_dpb && br_ok && dim_ok { + return Some(l); + } + } + None +} + +#[cfg(test)] +mod tests { + use super::*; + + #[test] + fn pick_level_bounds() { + // Standard 1080p @ 30fps + assert_eq!(pick_level(120, 68, 30.0, 1, None, false), Some(40)); + // Extreme dimensions exceeding Level 6.2 MaxFS + assert_eq!(pick_level(1024, 1024, 30.0, 1, None, false), None); + } +} diff --git a/crates/h264enc/src/picture.rs b/crates/h264enc/src/picture.rs new file mode 100644 index 0000000000..7443d478bb --- /dev/null +++ b/crates/h264enc/src/picture.rs @@ -0,0 +1,313 @@ +//! Padded picture planes, reference pictures and motion-compensated prediction (§8.4.2.2). + +use crate::mbinfo::{MbInfo, Mv}; + +pub const LUMA_PAD: usize = 64; +pub const CHROMA_PAD: usize = 32; + +/// A plane with `pad` replicated samples on every side. +#[derive(Clone)] +pub struct Plane { + pub data: Vec, + pub stride: usize, + pub w: usize, + pub h: usize, + pub pad: usize, +} + +impl Plane { + pub fn new(w: usize, h: usize, pad: usize) -> Self { + let stride = w + 2 * pad; + Plane { data: vec![0; stride * (h + 2 * pad)], stride, w, h, pad } + } + #[inline] + pub fn idx(&self, x: isize, y: isize) -> usize { + ((y + self.pad as isize) as usize) * self.stride + (x + self.pad as isize) as usize + } + /// Replicate edge samples into the padding. + pub fn extend_edges(&mut self) { + let (w, h, pad, stride) = (self.w, self.h, self.pad, self.stride); + for y in 0..h { + let r = (y + pad) * stride; + let l = self.data[r + pad]; + let rr = self.data[r + pad + w - 1]; + self.data[r..r + pad].fill(l); + self.data[r + pad + w..r + stride].fill(rr); + } + let first = pad * stride; + let last = (pad + h - 1) * stride; + for y in 0..pad { + self.data.copy_within(first..first + stride, y * stride); + self.data.copy_within(last..last + stride, (pad + h + y) * stride); + } + } + /// Copy the picture area out as a tightly packed plane of `cw x ch` (cropping). + pub fn copy_out(&self, cw: usize, ch: usize, out: &mut Vec) { + for y in 0..ch { + let o = self.idx(0, y as isize); + out.extend_from_slice(&self.data[o..o + cw]); + } + } +} + +/// A 4:2:0 frame with padded planes. +#[derive(Clone)] +pub struct Frame { + pub y: Plane, + pub u: Plane, + pub v: Plane, +} + +impl Frame { + pub fn new(w: usize, h: usize) -> Self { + Frame { y: Plane::new(w, h, LUMA_PAD), u: Plane::new(w / 2, h / 2, CHROMA_PAD), v: Plane::new(w / 2, h / 2, CHROMA_PAD) } + } + pub fn extend_edges(&mut self) { + self.y.extend_edges(); + self.u.extend_edges(); + self.v.extend_edges(); + } +} + +/// A decoded reference picture with half-sample planes for fast sub-pel access. +pub struct RefPic { + pub frame: Frame, + /// Horizontal half (b), vertical half (h) and centre (j) planes, same geometry as luma. + pub hpel: [Plane; 3], + pub mbs: Vec, + pub poc: i32, + pub id: u32, +} + +#[inline] +fn clip8(v: i32) -> u8 { + v.clamp(0, 255) as u8 +} + +/// Compute the H, V and centre half-sample planes over the whole padded area (clamped at the buffer edge, +/// which equals the normative picture-edge clamping because the padding is replicated). +pub fn build_hpel(f: &Plane, reuse: Option<[Plane; 3]>) -> [Plane; 3] { + let [mut hp, mut vp, mut cp] = reuse.filter(|r| r[0].data.len() == f.data.len()).unwrap_or_else(|| { + let mk = || Plane { data: vec![0; f.data.len()], stride: f.stride, w: f.w, h: f.h, pad: f.pad }; + [mk(), mk(), mk()] + }); + let rows = f.data.len() / f.stride; + let band = 16usize; + let work = |b: usize, hrows: &mut [u8], vrows: &mut [u8], crows: &mut [u8], scratch: &mut HpelScratch| { + let r0 = b * band; + let r1 = (r0 + band).min(rows); + hpel_rows(f, r0, r1, hrows, vrows, crows, scratch); + }; + let chunk = band * f.stride; + #[cfg(feature = "threads")] + { + use rayon::prelude::*; + hp.data + .par_chunks_mut(chunk) + .zip(vp.data.par_chunks_mut(chunk)) + .zip(cp.data.par_chunks_mut(chunk)) + .enumerate() + .for_each_init(HpelScratch::default, |s, (b, ((h, v), c))| work(b, h, v, c, s)); + } + #[cfg(not(feature = "threads"))] + { + let mut s = HpelScratch::default(); + for (b, ((h, v), c)) in hp.data.chunks_mut(chunk).zip(vp.data.chunks_mut(chunk)).zip(cp.data.chunks_mut(chunk)).enumerate() { + work(b, h, v, c, &mut s); + } + } + [hp, vp, cp] +} + +/// Per-worker buffers for `hpel_rows`, reused across bands. +#[derive(Default)] +struct HpelScratch { + hs: Vec, + ext: Vec, +} + +fn hpel_rows(f: &Plane, r0: usize, r1: usize, hout: &mut [u8], vout: &mut [u8], cout: &mut [u8], scratch: &mut HpelScratch) { + let stride = f.stride; + let rows = f.data.len() / stride; + let clampr = |r: isize| r.clamp(0, rows as isize - 1) as usize; + // Horizontal intermediate (unclipped, unshifted) for rows r0-2 .. r1+3. + let nrows = r1 - r0 + 5; + scratch.hs.resize(nrows * stride, 0); + scratch.ext.resize(stride + 5, 0); + let (hs, ext) = (&mut scratch.hs[..], &mut scratch.ext[..]); + for k in 0..nrows { + let r = clampr(r0 as isize - 2 + k as isize); + let row = &f.data[r * stride..r * stride + stride]; + ext[0] = row[0]; + ext[1] = row[0]; + ext[2..2 + stride].copy_from_slice(row); + ext[stride + 2] = row[stride - 1]; + ext[stride + 3] = row[stride - 1]; + ext[stride + 4] = row[stride - 1]; + let o = &mut hs[k * stride..k * stride + stride]; + for x in 0..stride { + let e = &ext[x..x + 6]; + o[x] = (e[0] as i16 + e[5] as i16) - 5 * (e[1] as i16 + e[4] as i16) + 20 * (e[2] as i16 + e[3] as i16); + } + } + for r in r0..r1 { + let lo = (r - r0) * stride; + let k = r - r0 + 2; + let hrow = &hs[k * stride..k * stride + stride]; + for x in 0..stride { + hout[lo + x] = clip8((hrow[x] as i32 + 16) >> 5); + } + // vertical + let rr: [usize; 6] = std::array::from_fn(|i| clampr(r as isize - 2 + i as isize) * stride); + for x in 0..stride { + let s = f.data[rr[0] + x] as i32 + f.data[rr[5] + x] as i32 - 5 * (f.data[rr[1] + x] as i32 + f.data[rr[4] + x] as i32) + + 20 * (f.data[rr[2] + x] as i32 + f.data[rr[3] + x] as i32); + vout[lo + x] = clip8((s + 16) >> 5); + } + // centre from horizontal intermediates + let hk: [&[i16]; 6] = std::array::from_fn(|i| &hs[(k - 2 + i) * stride..(k - 1 + i) * stride]); + for x in 0..stride { + let s = hk[0][x] as i32 + hk[5][x] as i32 - 5 * (hk[1][x] as i32 + hk[4][x] as i32) + 20 * (hk[2][x] as i32 + hk[3][x] as i32); + cout[lo + x] = clip8((s + 512) >> 10); + } + } +} + +/// Plane selection per quarter-sample position: two (plane, dx, dy) sources to average (identical = no average). +/// Plane indices: 0 full, 1 horizontal half, 2 vertical half, 3 centre. +const QPEL_SRC: [[(u8, u8, u8); 2]; 16] = [ + // fy = 0 + [(0, 0, 0), (0, 0, 0)], + [(0, 0, 0), (1, 0, 0)], + [(1, 0, 0), (1, 0, 0)], + [(1, 0, 0), (0, 1, 0)], + // fy = 1 + [(0, 0, 0), (2, 0, 0)], + [(1, 0, 0), (2, 0, 0)], + [(1, 0, 0), (3, 0, 0)], + [(1, 0, 0), (2, 1, 0)], + // fy = 2 + [(2, 0, 0), (2, 0, 0)], + [(2, 0, 0), (3, 0, 0)], + [(3, 0, 0), (3, 0, 0)], + [(3, 0, 0), (2, 1, 0)], + // fy = 3 + [(2, 0, 0), (0, 0, 1)], + [(2, 0, 0), (1, 0, 1)], + [(3, 0, 0), (1, 0, 1)], + [(2, 1, 0), (1, 0, 1)], +]; + +/// A `w` x `h` block at sample position (`x`, `y`) of a plane. +#[derive(Clone, Copy, Debug)] +pub struct McBlock { + pub x: usize, + pub y: usize, + pub w: usize, + pub h: usize, +} + +impl RefPic { + #[inline] + fn plane(&self, p: u8) -> &Plane { + match p { + 0 => &self.frame.y, + n => &self.hpel[n as usize - 1], + } + } + + /// Luma prediction of a w x h block at (x, y) displaced by `mv`, as (samples, stride). Full- and half-sample + /// positions are read straight from their plane; other positions are interpolated into `buf` (stride 16). + #[inline] + pub fn luma_ref<'s>(&'s self, x: usize, y: usize, mv: Mv, w: usize, h: usize, buf: &'s mut [u8]) -> (&'s [u8], usize) { + let [(pa, dxa, dya), (pb, dxb, dyb)] = QPEL_SRC[((mv.y & 3) * 4 + (mv.x & 3)) as usize]; + if pa == pb && dxa == dxb && dya == dyb { + let a = self.plane(pa); + let i = a.idx(x as isize + (mv.x as isize >> 2) + dxa as isize, y as isize + (mv.y as isize >> 2) + dya as isize); + (&a.data[i..], a.stride) + } else { + self.mc_luma(McBlock { x, y, w, h }, mv, buf, 16); + (buf, 16) + } + } + + /// Luma prediction of a w x h block at picture position (x, y) displaced by `mv` (quarter samples). + pub fn mc_luma(&self, blk: McBlock, mv: Mv, out: &mut [u8], ostride: usize) { + match blk.w { + 16 => self.mc_luma_w(McBlock { w: 16, ..blk }, mv, out, ostride), + 8 => self.mc_luma_w(McBlock { w: 8, ..blk }, mv, out, ostride), + _ => self.mc_luma_w(blk, mv, out, ostride), + } + } + + #[inline(always)] + fn mc_luma_w(&self, blk: McBlock, mv: Mv, out: &mut [u8], ostride: usize) { + let McBlock { x, y, w, h } = blk; + let xi = x as isize + (mv.x as isize >> 2); + let yi = y as isize + (mv.y as isize >> 2); + let q = ((mv.y & 3) * 4 + (mv.x & 3)) as usize; + let [(pa, dxa, dya), (pb, dxb, dyb)] = QPEL_SRC[q]; + let a = self.plane(pa); + let stride = a.stride; + let ia = a.idx(xi + dxa as isize, yi + dya as isize); + if pa == pb && dxa == dxb && dya == dyb { + for r in 0..h { + out[r * ostride..r * ostride + w].copy_from_slice(&a.data[ia + r * stride..ia + r * stride + w]); + } + } else { + let b = self.plane(pb); + let ib = b.idx(xi + dxb as isize, yi + dyb as isize); + for r in 0..h { + let ra = &a.data[ia + r * stride..ia + r * stride + w]; + let rb = &b.data[ib + r * stride..ib + r * stride + w]; + let o = &mut out[r * ostride..r * ostride + w]; + for i in 0..w { + o[i] = ((ra[i] as u16 + rb[i] as u16 + 1) >> 1) as u8; + } + } + } + } + + /// Chroma prediction (both planes) of a w x h chroma block at chroma position (x, y); mv in luma quarter units. + pub fn mc_chroma(&self, blk: McBlock, mv: Mv, out: (&mut [u8], &mut [u8]), ostride: usize) { + match blk.w { + 8 => self.mc_chroma_w(McBlock { w: 8, ..blk }, mv, out, ostride), + 4 => self.mc_chroma_w(McBlock { w: 4, ..blk }, mv, out, ostride), + _ => self.mc_chroma_w(blk, mv, out, ostride), + } + } + + #[inline(always)] + fn mc_chroma_w(&self, blk: McBlock, mv: Mv, out: (&mut [u8], &mut [u8]), ostride: usize) { + let McBlock { x, y, w, h } = blk; + let (out_u, out_v) = out; + let xi = x as isize + (mv.x as isize >> 3); + let yi = y as isize + (mv.y as isize >> 3); + let fx = (mv.x & 7) as u32; + let fy = (mv.y & 7) as u32; + let w00 = (8 - fx) * (8 - fy); + let w01 = fx * (8 - fy); + let w10 = (8 - fx) * fy; + let w11 = fx * fy; + for (p, out) in [(&self.frame.u, out_u), (&self.frame.v, out_v)] { + let s = p.stride; + let i0 = p.idx(xi, yi); + for r in 0..h { + let a = &p.data[i0 + r * s..i0 + r * s + w + 1]; + let b = &p.data[i0 + (r + 1) * s..i0 + (r + 1) * s + w + 1]; + let o = &mut out[r * ostride..r * ostride + w]; + for i in 0..w { + o[i] = ((w00 * a[i] as u32 + w01 * a[i + 1] as u32 + w10 * b[i] as u32 + w11 * b[i + 1] as u32 + 32) >> 6) as u8; + } + } + } + } +} + +/// Average two predictions in place (default bi-prediction). +#[inline] +pub fn avg_into(dst: &mut [u8], src: &[u8]) { + for (d, &s) in dst.iter_mut().zip(src) { + *d = ((*d as u16 + s as u16 + 1) >> 1) as u8; + } +} diff --git a/crates/h264enc/src/ratecontrol.rs b/crates/h264enc/src/ratecontrol.rs new file mode 100644 index 0000000000..7f56308de9 --- /dev/null +++ b/crates/h264enc/src/ratecontrol.rs @@ -0,0 +1,335 @@ +//! Frame-level rate control: constant QP, constant quality (CRF-like), 1-pass ABR with VBV, and 2-pass VBR. + +use crate::nal::SliceType; + +pub fn qp2qscale(qp: f64) -> f64 { + 0.85 * 2f64.powf((qp - 12.0) / 6.0) +} +pub fn qscale2qp(q: f64) -> f64 { + 12.0 + 6.0 * (q / 0.85).log2() +} + +const QCOMP: f64 = 0.6; + +fn type_idx(t: SliceType) -> usize { + match t { + SliceType::I => 0, + SliceType::P => 1, + SliceType::B => 2, + } +} + +/// Relative qscale factor per frame type (I frames finer, B frames coarser). +fn type_factor(t: SliceType) -> f64 { + match t { + SliceType::I => 1.0 / 1.4, + SliceType::P => 1.0, + SliceType::B => 1.3, + } +} + +/// Statistics of one frame from the first pass. +#[derive(Clone, Copy, Debug, PartialEq)] +pub struct FrameStat { + pub slice_type: SliceType, + pub qscale: f64, + pub bits: u64, + pub cplx: f64, +} + +/// First-pass statistics, serialisable as text. +#[derive(Clone, Debug, Default, PartialEq)] +pub struct PassStats { + pub frames: Vec, + pub fps: f64, +} + +impl PassStats { + pub fn to_text(&self) -> String { + let mut s = format!("h264enc-pass1 fps={}\n", self.fps); + for f in &self.frames { + let t = match f.slice_type { + SliceType::I => 'I', + SliceType::P => 'P', + SliceType::B => 'B', + }; + s.push_str(&format!("{t} {} {} {}\n", f.qscale, f.bits, f.cplx)); + } + s + } + pub fn from_text(text: &str) -> Option { + let mut lines = text.lines(); + let head = lines.next()?; + let fps: f64 = head.strip_prefix("h264enc-pass1 fps=")?.trim().parse().ok()?; + if !fps.is_finite() || fps <= 0.0 { + return None; + } + let mut frames = Vec::new(); + for l in lines { + let mut it = l.split_whitespace(); + let t = match it.next()? { + "I" => SliceType::I, + "P" => SliceType::P, + "B" => SliceType::B, + _ => return None, + }; + let qscale: f64 = it.next()?.parse().ok()?; + if !qscale.is_finite() || qscale <= 0.0 { + return None; + } + frames.push(FrameStat { slice_type: t, qscale, bits: it.next()?.parse().ok()?, cplx: it.next()?.parse().ok()? }); + } + Some(PassStats { frames, fps }) + } +} + +#[derive(Clone, Debug)] +pub enum RcKind { + Qp(u8), + Crf(f32), + Abr { kbps: u32 }, + TwoPass { kbps: u32, plan: Vec, planned_bits: Vec, planned_total: f64, remaining_bits: Vec }, +} + +pub struct RateControl { + kind: RcKind, + fps: f64, + /// bits ≈ coef * cplx / qscale per frame type. + coef: [f64; 3], + coef_n: [u32; 3], + total_bits: f64, + frames: u64, + sum_s: f64, + // VBV + vbv_max_bits_per_frame: f64, + vbv_size: f64, + vbv_fill: f64, + pub pass1: Vec, + model_done: f64, + inflight: std::collections::VecDeque, + last_q: f64, + /// Virtual history for 1-pass ABR: (frame count, complexity of one virtual frame), fixed at the first frame. + prior: Option<(f64, f64)>, + /// Sum and count of the rate-model terms (`coef · cplx^QCOMP / type_factor`) of the coded P and B frames. + inter_s: f64, + inter_n: u64, +} + +impl RateControl { + pub fn new(kind: RcKind, fps: f64, vbv_maxrate_kbps: Option, vbv_buf_kbits: Option) -> Self { + let max = vbv_maxrate_kbps.map_or(0.0, |k| k as f64 * 1000.0 / fps); + let size = vbv_buf_kbits.map_or(0.0, |k| k as f64 * 1000.0); + RateControl { + kind, + fps, + coef: [1.6, 1.4, 1.2], + coef_n: [0; 3], + total_bits: 0.0, + frames: 0, + sum_s: 0.0, + vbv_max_bits_per_frame: max, + vbv_size: size, + vbv_fill: size * 0.9, + pass1: Vec::new(), + model_done: 0.0, + inflight: std::collections::VecDeque::new(), + last_q: 0.0, + prior: None, + inter_s: 0.0, + inter_n: 0, + } + } + + /// Plan a second pass from first-pass statistics. + pub fn plan_two_pass(stats: &PassStats, kbps: u32) -> RcKind { + let n = stats.frames.len().max(1); + let target = kbps as f64 * 1000.0 * n as f64 / stats.fps; + let c: Vec = stats.frames.iter().map(|f| (f.bits as f64 * f.qscale).max(1.0)).collect(); + let qs = |rf: f64| -> Vec { + stats + .frames + .iter() + .zip(&c) + .map(|(f, &ci)| (rf * ci.powf(1.0 - QCOMP) * type_factor(f.slice_type)).clamp(qp2qscale(0.0), qp2qscale(51.0))) + .collect() + }; + let bits_for = |q: &[f64]| -> f64 { stats.frames.iter().zip(q).map(|(f, &qi)| f.bits as f64 * f.qscale / qi).sum() }; + // bisection on log(rf) + let (mut lo, mut hi) = (-30.0f64, 30.0f64); + for _ in 0..100 { + let mid = 0.5 * (lo + hi); + if bits_for(&qs(mid.exp())) > target { + lo = mid; + } else { + hi = mid; + } + } + let plan = qs((0.5 * (lo + hi)).exp()); + let planned_bits: Vec = stats.frames.iter().zip(&plan).map(|(f, &q)| f.bits as f64 * f.qscale / q).collect(); + let planned_total = planned_bits.iter().sum(); + // `remaining_bits[i]` is the inclusive suffix for frame `i`; retain an + // empty suffix so callers can index it without a special case. + let mut remaining_bits = vec![0.0; planned_bits.len() + 1]; + for i in (0..planned_bits.len()).rev() { + remaining_bits[i] = remaining_bits[i + 1] + planned_bits[i]; + } + RcKind::TwoPass { kbps, plan, planned_bits, planned_total, remaining_bits } + } + + /// Choose the frame QP (fractional) for frame `index` (display order) of `t` with lookahead complexity `cplx`. + pub fn frame_qp(&mut self, t: SliceType, cplx: f64, index_in_coding_order: usize) -> f64 { + let ti = type_idx(t); + let cplx = cplx.max(1.0); + let qp = match &self.kind { + RcKind::Qp(q) => { + let q = *q as f64; + return if t == SliceType::B { (q + 2.0).min(51.0) } else { q }; + } + RcKind::Crf(c) => { + let c = *c as f64; + return (c + match t { + SliceType::I => -2.0, + SliceType::P => 0.0, + SliceType::B => 1.5, + }) + .clamp(0.0, 51.0); + } + RcKind::Abr { kbps } => { + let bpf = *kbps as f64 * 1000.0 / self.fps; + // rate factor so that past frames at this rf would have hit the target + let s_here = self.coef[ti] * cplx.powf(QCOMP) / type_factor(t); + // Virtual history of one second of inter frames so the first I frame gets a realistic share of the + // budget instead of a single frame's worth. A virtual frame costs what the P and B frames coded so far + // cost on average; before the first of them, a P frame of 40% of the first frame's complexity under + // the current model. Re-estimated every frame: the initial guess of `coef` can be several times off, + // and a history frozen at that guess starves the opening second of bits. + let fps = self.fps; + let (pn0, c0) = *self.prior.get_or_insert_with(|| (fps.max(1.0), if t == SliceType::I { 0.4 * cplx } else { cplx })); + let per_frame = if self.inter_n > 0 { self.inter_s / self.inter_n as f64 } else { self.coef[1] * c0.powf(QCOMP) }; + let fade = (1.0 - self.frames as f64 / pn0).max(0.0); + let (pn, ps) = (pn0 * fade, pn0 * fade * per_frame); + let s = self.sum_s + s_here + ps; + let wanted = bpf * (self.frames as f64 + 1.0 + pn); + let mut rf = s / wanted; + // A quarter second of bits: with a longer horizon the correction is so weak that a short clip + // that starts over target never pays the excess back (+10..14% on 2 s of high motion at 1 s). + // Never shorter than one frame's budget: at a low frame rate the horizon would be a fraction of a + // frame and the correction would overshoot in the other direction every frame. + let abr_buffer = (bpf * self.fps.max(1.0) * 0.25).max(bpf); + let overflow = (1.0 + (self.total_bits - bpf * self.frames as f64) / abr_buffer).clamp(0.5, 2.0); + rf *= overflow; + let q = rf * cplx.powf(1.0 - QCOMP) * type_factor(t); + qscale2qp(q) + } + RcKind::TwoPass { kbps, plan, planned_bits, planned_total, remaining_bits } => { + // Closed-loop second pass: `m` measures how far real frame sizes deviate from the first-pass model + // (bits ∝ 1/qscale) at the qscales actually used; the remaining plan is rescaled so its predicted + // size fits the remaining budget. + let i = index_in_coding_order.min(plan.len().saturating_sub(1)); + let bpf = *kbps as f64 * 1000.0 / self.fps; + let target_total = *planned_total; + let smooth = 0.5 * bpf * self.fps.max(1.0); + let m = ((self.total_bits + smooth) / (self.model_done + smooth)).clamp(0.3, 3.0); + let inflight: f64 = self.inflight.iter().sum(); + let remaining_plan = remaining_bits[i]; + let remaining_budget = (target_total - self.total_bits - m * inflight).max(0.05 * remaining_plan.max(1.0)); + let f = (m * remaining_plan / remaining_budget).clamp(0.25, 4.0); + let q = plan[i] * f; + self.inflight.push_back(planned_bits[i] * plan[i] / q); + qscale2qp(q) + } + }; + let mut qp = qp.clamp(1.0, 51.0); + // VBV: make sure the predicted frame size fits in the buffer. + if self.vbv_size > 0.0 { + let fill = self.vbv_fill; + for _ in 0..60 { + let pred = self.coef[ti] * cplx / qp2qscale(qp); + if pred <= fill * 0.8 || qp >= 51.0 { + break; + } + qp += 0.5; + } + } + self.last_q = qp; + qp + } + + /// Update after coding a frame with actual `bits`. + pub fn update(&mut self, t: SliceType, qp: f64, cplx: f64, bits: u64) { + let ti = type_idx(t); + let cplx = cplx.max(1.0); + let q = qp2qscale(qp); + let c = bits as f64 * q / cplx; + let n = self.coef_n[ti]; + let w = if n == 0 { 1.0 } else { 0.35f64.max(1.0 / (n + 1) as f64) }; + self.coef[ti] = self.coef[ti] * (1.0 - w) + c * w; + self.coef_n[ti] += 1; + // If a type has never been observed, keep it proportional to observed ones. + for k in 0..3 { + if self.coef_n[k] == 0 { + self.coef[k] = self.coef[ti] * [1.2, 1.0, 0.85][k] / [1.2, 1.0, 0.85][ti]; + } + } + let s_frame = self.coef[ti] * cplx.powf(QCOMP) / type_factor(t); + self.sum_s += s_frame; + if t != SliceType::I { + self.inter_s += s_frame; + self.inter_n += 1; + } + self.total_bits += bits as f64; + if let Some(p) = self.inflight.pop_front() { + self.model_done += p; + } + self.frames += 1; + if self.vbv_size > 0.0 { + self.vbv_fill = (self.vbv_fill - bits as f64 + self.vbv_max_bits_per_frame).min(self.vbv_size); + if self.vbv_fill < 0.0 { + self.vbv_fill = 0.0; + } + } + } + + pub fn record_pass1(&mut self, t: SliceType, qp: f64, cplx: f64, bits: u64) { + self.pass1.push(FrameStat { slice_type: t, qscale: qp2qscale(qp), bits, cplx }); + } +} + +#[cfg(test)] +mod tests { + use super::*; + + #[test] + fn two_pass_plan_caches_total_and_inclusive_suffixes() { + let stats = PassStats { + fps: 24.0, + frames: vec![ + FrameStat { slice_type: SliceType::I, qscale: 1.0, bits: 2_000, cplx: 20.0 }, + FrameStat { slice_type: SliceType::P, qscale: 1.5, bits: 1_000, cplx: 10.0 }, + FrameStat { slice_type: SliceType::B, qscale: 2.0, bits: 500, cplx: 5.0 }, + ], + }; + + let RcKind::TwoPass { planned_bits, planned_total, remaining_bits, .. } = RateControl::plan_two_pass(&stats, 1_000) else { + panic!("two-pass planning must return a two-pass rate-control plan"); + }; + + assert_eq!(remaining_bits.len(), planned_bits.len() + 1); + assert!((planned_total - planned_bits.iter().sum::()).abs() < f64::EPSILON); + for i in 0..planned_bits.len() { + assert!((remaining_bits[i] - planned_bits[i..].iter().sum::()).abs() < f64::EPSILON); + } + assert_eq!(remaining_bits[planned_bits.len()], 0.0); + } + + #[test] + fn pass_stats_from_text_rejects_invalid_values() { + assert!(PassStats::from_text("h264enc-pass1 fps=0\nI 1.0 1000 10.0\n").is_none()); + assert!(PassStats::from_text("h264enc-pass1 fps=-30\nI 1.0 1000 10.0\n").is_none()); + assert!(PassStats::from_text("h264enc-pass1 fps=NaN\nI 1.0 1000 10.0\n").is_none()); + assert!(PassStats::from_text("h264enc-pass1 fps=30\nI 0 1000 10.0\n").is_none()); + assert!(PassStats::from_text("h264enc-pass1 fps=30\nI -1.5 1000 10.0\n").is_none()); + assert!(PassStats::from_text("h264enc-pass1 fps=30\nI NaN 1000 10.0\n").is_none()); + assert!(PassStats::from_text("h264enc-pass1 fps=30\nI 1.0 1000 10.0\n").is_some()); + } +} diff --git a/crates/h264enc/src/slice.rs b/crates/h264enc/src/slice.rs new file mode 100644 index 0000000000..68b10e46aa --- /dev/null +++ b/crates/h264enc/src/slice.rs @@ -0,0 +1,486 @@ +//! Slice encoding: macroblock analysis, mode decision, residual coding, reconstruction and entropy coding. + +use craft_media::bitstream::BitWriter; + +use crate::cabac::{BinSink, CabacEncoder, CabacEstimator, Contexts, CostTable}; +use crate::dsp::{sad, sad8, sad16, satd, ssd}; +use crate::intra::{Edge4, Edge8, filter8, mode_available, pred_chroma, pred4x4, pred8x8, pred16x16}; +use crate::mbinfo::{MbInfo, MbKind, Mv, Part}; +use crate::nal::{SliceHeader, SliceType, nal}; +use crate::picture::{CHROMA_PAD, Frame, LUMA_PAD, RefPic, avg_into}; +use crate::syntax::{MbCode, NbCtx}; +use crate::tables::{BLK4_XY, NUM_CTX, RASTER_TO_BLK4, ZIGZAG4, ZIGZAG8, chroma_qp}; +use crate::transform::{ + Deadzone, QuantTables, dequant_chroma_dc, dequant_luma_dc, dequant4, dequant8, fdct4, fdct8, idct4, idct8, quant_chroma_dc, quant_luma_dc, + quant4, quant8, +}; +use crate::{cabac_mb, cavlc}; + +mod b_slices; +mod intra; +mod macroblock; +mod motion; +mod residual; + +/// Encoder tuning derived from the preset. +#[derive(Clone, Debug)] +pub struct EncParams { + pub mbw: usize, + pub mbh: usize, + pub cabac: bool, + pub t8x8: bool, + pub i4x4: bool, + pub i8x8: bool, + pub partitions: bool, + pub me_range: i32, + /// 0 = full-pel, 1 = half-pel, 2 = quarter-pel refinement. + pub subpel: u8, + /// Extra quarter-pel refinement rounds. + pub subpel_rounds: u8, + pub rd: bool, + pub chroma_qp_offset: i32, + pub decimate: bool, + /// Adaptive 4x4/8x8 transform decision for inter macroblocks. + pub adaptive_t8: bool, + /// Always try intra in P/B frames (otherwise only when inter looks poor). + pub always_intra: bool, + /// Maximum CAVLC level magnitude (Baseline). + pub cavlc_clamp: bool, +} + +/// Per-frame shared encoding state. +pub struct FrameEnc<'a> { + pub p: &'a EncParams, + pub qt: &'a QuantTables, + pub cost_tab: &'a CostTable, + pub st: SliceType, + pub src: &'a Frame, + pub l0: Option<&'a RefPic>, + pub l1: Option<&'a RefPic>, + pub qp: u8, + pub aq: &'a [i8], + pub hdr: SliceHeader, + pub nal_ref_idc: u8, + pub nal_type: u8, + pub cabac_table: &'a [(i8, i8); NUM_CTX], + pub lam: &'a Lambdas, +} + +/// Lambda tables indexed by QP. +pub struct Lambdas { + /// SATD/SAD-domain lambda, Q8. + pub sad: [u32; 52], + /// SSD-domain lambda (for RD), Q8 per 1/256 bit. + pub ssd: [f64; 52], +} + +impl Lambdas { + pub fn new() -> Self { + let mut sad = [0u32; 52]; + let mut ssd_ = [0f64; 52]; + for qp in 0..52 { + let l = 0.85 * 2f64.powf((qp as f64 - 12.0) / 3.0); + ssd_[qp] = l; + sad[qp] = ((l.sqrt()) * 256.0).round().max(16.0) as u32; + } + Lambdas { sad, ssd: ssd_ } + } +} + +impl Default for Lambdas { + fn default() -> Self { + Self::new() + } +} + +/// Mutable band of reconstructed rows belonging to one slice. +pub struct Band<'b> { + pub y: &'b mut [u8], + pub u: &'b mut [u8], + pub v: &'b mut [u8], + pub ys: usize, + pub cs: usize, +} + +#[derive(Default, Clone, Debug)] +pub struct SliceStats { + pub bits: u64, + pub intra: u32, + pub skip: u32, + pub inter: u32, + pub qp_sum: u64, + pub mbs: u32, +} + +pub struct SliceOut { + pub nal: Vec, + #[allow(dead_code)] + pub stats: SliceStats, +} + +#[derive(Clone, Copy)] +struct Avail { + left: bool, + top: bool, + tr: bool, + tl: bool, +} + +struct MbEdges { + top: [u8; 24], + left: [u8; 16], + tl: u8, + ctop: [[u8; 8]; 2], + cleft: [[u8; 8]; 2], + ctl: [u8; 2], +} + +#[derive(Clone)] +struct Cand { + code: MbCode, + info: MbInfo, + ry: [u8; 256], + ru: [u8; 64], + rv: [u8; 64], + cost: u64, +} + +impl Cand { + fn new() -> Box { + Box::new(Cand { code: MbCode::default(), info: MbInfo::default(), ry: [0; 256], ru: [0; 64], rv: [0; 64], cost: u64::MAX }) + } +} + +#[inline] +fn se_bits(v: i32) -> u32 { + let k = if v > 0 { 2 * v as u32 - 1 } else { (-2 * v) as u32 }; + 2 * (31 - (k + 1).leading_zeros()) + 1 +} + +#[inline] +fn mv_bits(mv: Mv, mvp: Mv) -> u32 { + se_bits(mv.x as i32 - mvp.x as i32) + se_bits(mv.y as i32 - mvp.y as i32) +} + +fn median(a: i16, b: i16, c: i16) -> i16 { + a.max(b).min(a.min(b).max(c)) +} + +fn decimate_score(levels: &[i16]) -> u32 { + const T: [u32; 16] = [3, 2, 2, 1, 1, 1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0]; + let mut score = 0; + let mut run = 0usize; + for &l in levels { + if l == 0 { + run += 1; + continue; + } + if l.unsigned_abs() > 1 { + return 64; + } + score += T[run.min(15)]; + run = 0; + } + score +} + +#[derive(Clone, Copy, PartialEq, Eq)] +enum Shape { + Normal, + Top16x8, + Bottom16x8, + Left8x16, + Right8x16, +} + +pub struct SliceEnc<'a, 'b> { + f: &'a FrameEnc<'a>, + band: Band<'b>, + mbs: &'b mut [MbInfo], + y0: usize, + y1: usize, + slice_no: u16, + last_qp: u8, + prev_dqp_nz: bool, + cabac: Option, + cavlc: Option, + skip_run: u32, + stats: SliceStats, +} + +impl<'a, 'b> SliceEnc<'a, 'b> { + pub fn new(f: &'a FrameEnc<'a>, band: Band<'b>, mbs: &'b mut [MbInfo], y0: usize, y1: usize, slice_no: u16) -> Self { + SliceEnc { + f, + band, + mbs, + y0, + y1, + slice_no, + last_qp: f.qp, + prev_dqp_nz: false, + cabac: None, + cavlc: None, + skip_run: 0, + stats: SliceStats::default(), + } + } + + pub fn encode(mut self) -> SliceOut { + let f = self.f; + let mbw = f.p.mbw; + let mut hdr = f.hdr.clone(); + hdr.first_mb = (self.y0 * mbw) as u32; + let mut bw = BitWriter::new(); + hdr.write(&mut bw); + if f.p.cabac { + while !bw.is_byte_aligned() { + bw.write_bit(true); + } + let ctx = Contexts::init(f.cabac_table, f.qp as i32); + self.cabac = Some(CabacEncoder::new(ctx, bw.finish())); + } else { + self.cavlc = Some(bw); + } + for my in self.y0..self.y1 { + for mx in 0..mbw { + self.encode_mb(mx, my); + if let Some(c) = self.cabac.as_mut() { + let last = my + 1 == self.y1 && mx + 1 == mbw; + if !last { + c.terminate(0); + } + } + } + } + let rbsp = if let Some(c) = self.cabac.take() { + c.finish() + } else { + let mut w = self.cavlc.take().unwrap_or_default(); + if self.skip_run > 0 { + w.write_ue(self.skip_run); + } + w.rbsp_trailing(); + w.finish() + }; + self.stats.bits = rbsp.len() as u64 * 8; + SliceOut { nal: nal(f.nal_ref_idc, f.nal_type, &rbsp), stats: self.stats } + } + + // ------------------------------------------------------------------ neighbour access + + #[inline] + fn mb(&self, mx: usize, my: usize) -> &MbInfo { + &self.mbs[(my - self.y0) * self.f.p.mbw + mx] + } + + fn avail(&self, mx: usize, my: usize) -> Avail { + let top = my > self.y0; + Avail { left: mx > 0, top, tr: top && mx + 1 < self.f.p.mbw, tl: top && mx > 0 } + } + + #[inline] + fn ry(&self, x: usize, y: usize) -> usize { + (y - self.y0 * 16) * self.band.ys + LUMA_PAD + x + } + #[inline] + fn rc(&self, x: usize, y: usize) -> usize { + (y - self.y0 * 8) * self.band.cs + CHROMA_PAD + x + } + + fn edges(&self, mx: usize, my: usize, av: Avail) -> MbEdges { + let (px, py) = (mx * 16, my * 16); + let mut e = MbEdges { top: [128; 24], left: [128; 16], tl: 128, ctop: [[128; 8]; 2], cleft: [[128; 8]; 2], ctl: [128; 2] }; + if av.top { + let i = self.ry(px, py - 1); + e.top[..16].copy_from_slice(&self.band.y[i..i + 16]); + if av.tr { + e.top[16..24].copy_from_slice(&self.band.y[i + 16..i + 24]); + } else { + let v = e.top[15]; + e.top[16..24].fill(v); + } + for (c, pl) in [&*self.band.u, &*self.band.v].into_iter().enumerate() { + let i = self.rc(px / 2, py / 2 - 1); + e.ctop[c].copy_from_slice(&pl[i..i + 8]); + } + } + if av.left { + for y in 0..16 { + e.left[y] = self.band.y[self.ry(px - 1, py + y)]; + } + for (c, pl) in [&*self.band.u, &*self.band.v].into_iter().enumerate() { + for y in 0..8 { + e.cleft[c][y] = pl[self.rc(px / 2 - 1, py / 2 + y)]; + } + } + } + if av.tl { + e.tl = self.band.y[self.ry(px - 1, py - 1)]; + e.ctl[0] = self.band.u[self.rc(px / 2 - 1, py / 2 - 1)]; + e.ctl[1] = self.band.v[self.rc(px / 2 - 1, py / 2 - 1)]; + } + e + } + + /// Neighbouring partition data at 4x4 offset (bx, by) relative to the MB (range -1..=4). + /// None = not available; Some((ref, mv)) with ref = -1 for intra / list unused. + fn nb_part(&self, nb: MvNb, bx: i32, by: i32, list: usize) -> Option<(i8, Mv)> { + let MvNb { cur, mask, mx, my } = nb; + if (0..4).contains(&bx) && (0..4).contains(&by) { + let r = (by * 4 + bx) as usize; + if mask & (1 << r) != 0 { + return Some((cur.ref_at(list, r), cur.mv[list][r])); + } + return None; + } + let dx = if bx < 0 { + -1 + } else if bx >= 4 { + 1 + } else { + 0 + }; + let dy = if by < 0 { -1 } else { 0 }; + if by >= 4 || (dy == 0 && dx == 1) { + return None; + } + let nx = mx as i32 + dx; + let ny = my as i32 + dy; + if nx < 0 || nx >= self.f.p.mbw as i32 || ny < self.y0 as i32 { + return None; + } + let m = self.mb(nx as usize, ny as usize); + let r = (((by + 4) % 4) * 4 + (bx + 4) % 4) as usize; + if m.kind.is_intra() { + return Some((-1, Mv::ZERO)); + } + let rf = m.ref_at(list, r); + Some((rf, if rf >= 0 { m.mv[list][r] } else { Mv::ZERO })) + } + + /// Motion vector predictor (§8.4.1.3) for a partition at 4x4 coords (x, y) of size (w, h) (in 4x4 units). + fn mvp(&self, nb: MvNb, geom: PartGeom, list: usize, rf: i8) -> Mv { + let PartGeom { x, y, w, shape } = geom; + let a = self.nb_part(nb, x - 1, y, list); + let b = self.nb_part(nb, x, y - 1, list); + let mut c = self.nb_part(nb, x + w, y - 1, list); + if c.is_none() { + c = self.nb_part(nb, x - 1, y - 1, list); + } + let (mut b, mut c) = (b, c); + if b.is_none() && c.is_none() && a.is_some() { + b = a; + c = a; + } + let a = a.unwrap_or((-1, Mv::ZERO)); + let b = b.unwrap_or((-1, Mv::ZERO)); + let c = c.unwrap_or((-1, Mv::ZERO)); + match shape { + Shape::Top16x8 if b.0 == rf => return b.1, + Shape::Bottom16x8 if a.0 == rf => return a.1, + Shape::Left8x16 if a.0 == rf => return a.1, + Shape::Right8x16 if c.0 == rf => return c.1, + _ => {} + } + let n = (a.0 == rf) as u32 + (b.0 == rf) as u32 + (c.0 == rf) as u32; + if n == 1 { + if a.0 == rf { + return a.1; + } + if b.0 == rf { + return b.1; + } + return c.1; + } + Mv { x: median(a.1.x, b.1.x, c.1.x), y: median(a.1.y, b.1.y, c.1.y) } + } + + fn pskip_mv(&self, mx: usize, my: usize) -> Mv { + let cur = MbInfo::default(); + let nb = MvNb { cur: &cur, mask: 0, mx, my }; + let a = self.nb_part(nb, -1, 0, 0); + let b = self.nb_part(nb, 0, -1, 0); + match (a, b) { + (None, _) | (_, None) => Mv::ZERO, + (Some((0, m)), _) if m == Mv::ZERO => Mv::ZERO, + (_, Some((0, m))) if m == Mv::ZERO => Mv::ZERO, + _ => self.mvp(nb, PartGeom { x: 0, y: 0, w: 4, shape: Shape::Normal }, 0, 0), + } + } + + /// Whether a luma block at picture (x, y) of size w x h with `mv` stays within the padded reference. + fn mv_ok(&self, x: usize, y: usize, w: usize, h: usize, mv: Mv) -> bool { + let pw = (self.f.p.mbw * 16) as i32; + let ph = (self.f.p.mbh * 16) as i32; + let m = LUMA_PAD as i32 - 20; + let x0 = x as i32 + (mv.x as i32 >> 2); + let y0 = y as i32 + (mv.y as i32 >> 2); + mv.x.unsigned_abs() < 2000 && mv.y.unsigned_abs() < 2000 && x0 >= -m && y0 >= -m && x0 + w as i32 <= pw + m && y0 + h as i32 <= ph + m + } +} + +/// The macroblock (`mx`, `my`) being analysed: quantiser, neighbour availability and edge samples, and source. +#[derive(Clone, Copy)] +struct MbCtx<'x> { + mx: usize, + my: usize, + qp: u8, + av: Avail, + e: &'x MbEdges, + src: &'x Src, +} + +/// Where a motion-vector-predictor neighbour lookup happens: the macroblock (`mx`, `my`) whose already +/// decided partitions (`mask` over the 4x4 blocks of `cur`) are visible. +#[derive(Clone, Copy)] +struct MvNb<'c> { + cur: &'c MbInfo, + mask: u16, + mx: usize, + my: usize, +} + +/// A partition for motion-vector prediction: 4x4-block origin (`x`, `y`), width `w` (in 4x4 units), and shape. +#[derive(Clone, Copy)] +struct PartGeom { + x: i32, + y: i32, + w: i32, + shape: Shape, +} + +/// A luma block to motion-search at picture position (`x`, `y`) against `src` (row stride `sstride`). +#[derive(Clone, Copy)] +struct SearchBlock<'s> { + x: usize, + y: usize, + w: usize, + h: usize, + src: &'s [u8], + sstride: usize, +} + +/// Parameters of a sequential per-partition search of macroblock (`mx`, `my`) for one reference list. +#[derive(Clone, Copy)] +struct PartSearch { + mx: usize, + my: usize, + part: Part, + list: usize, + lam: u32, + seed: Mv, +} + +/// Destination buffers (luma 16x16, chroma 8x8 each) of a motion-compensated prediction. +struct PredOut<'p> { + y: &'p mut [u8; 256], + u: &'p mut [u8; 64], + v: &'p mut [u8; 64], +} + +struct Src { + y: [u8; 256], + u: [u8; 64], + v: [u8; 64], +} diff --git a/crates/h264enc/src/slice/b_slices.rs b/crates/h264enc/src/slice/b_slices.rs new file mode 100644 index 0000000000..95e42af7cd --- /dev/null +++ b/crates/h264enc/src/slice/b_slices.rs @@ -0,0 +1,321 @@ +use super::{ + Avail, Cand, MbCtx, MbEdges, MbInfo, MbKind, Mv, MvNb, Part, PartGeom, PartSearch, PredOut, Shape, SliceEnc, Src, avg_into, mv_bits, sad16, satd, +}; + +impl<'a, 'b> SliceEnc<'a, 'b> { + // ------------------------------------------------------------------ B slices + + /// Spatial direct prediction (§8.4.1.2.2) with direct_8x8_inference. Returns per-8x8 refs and mvs. + fn direct_spatial(&self, mx: usize, my: usize) -> ([[i8; 4]; 2], [[Mv; 4]; 2]) { + let cur = MbInfo::default(); + let nb = MvNb { cur: &cur, mask: 0, mx, my }; + let mut refs = [-1i8; 2]; + let mut mvp = [Mv::ZERO; 2]; + for l in 0..2 { + let a = self.nb_part(nb, -1, 0, l).map_or(-1, |v| v.0); + let b = self.nb_part(nb, 0, -1, l).map_or(-1, |v| v.0); + let c = self.nb_part(nb, 4, -1, l).or_else(|| self.nb_part(nb, -1, -1, l)).map_or(-1, |v| v.0); + let minp = |x: i8, y: i8| if x >= 0 && y >= 0 { x.min(y) } else { x.max(y) }; + refs[l] = minp(a, minp(b, c)); + } + let zero = refs[0] < 0 && refs[1] < 0; + if zero { + refs = [0, 0]; + } else { + for l in 0..2 { + if refs[l] >= 0 { + mvp[l] = self.mvp(nb, PartGeom { x: 0, y: 0, w: 4, shape: Shape::Normal }, l, refs[l]); + } + } + } + let Some(l1) = self.f.l1 else { return ([[0; 4]; 2], [[Mv::ZERO; 4]; 2]) }; + let col = &l1.mbs[my * self.f.p.mbw + mx]; + let mut out_ref = [[-1i8; 4]; 2]; + let mut out_mv = [[Mv::ZERO; 4]; 2]; + for q in 0..4 { + let corner = [0usize, 3, 12, 15][q]; + let col_zero = if col.kind.is_intra() { + false + } else { + let (rc, mvc) = if col.ref_idx[0][q] >= 0 { (col.ref_idx[0][q], col.mv[0][corner]) } else { (col.ref_idx[1][q], col.mv[1][corner]) }; + rc == 0 && mvc.x.abs() <= 1 && mvc.y.abs() <= 1 + }; + for l in 0..2 { + out_ref[l][q] = refs[l]; + if refs[l] >= 0 { + out_mv[l][q] = if zero || (refs[l] == 0 && col_zero) { Mv::ZERO } else { mvp[l] }; + } + } + } + (out_ref, out_mv) + } + + /// B 16x8 / 8x16 candidate: search each partition in both lists, choose L0/L1/Bi per partition, then derive + /// the motion vector predictors in decoding order from the chosen directions. With `beat`, a + /// candidate whose cost does not come below it is `None` before its residual is coded. + fn b_partition(&self, m: &MbCtx, part: Part, lam: u32, seeds: [Mv; 2], beat: Option) -> Option> { + let MbCtx { mx, my, qp, src, .. } = *m; + let f = self.f; + let rp = [f.l0?, f.l1?]; + let (px, py) = (mx * 16, my * 16); + let mut mvs = [[Mv::ZERO; 4]; 2]; + for l in 0..2 { + mvs[l] = self.search_part(rp[l], &PartSearch { mx, my, part, list: l, lam, seed: seeds[l] }, src).0; + } + let mut preds = [([0u8; 256], [0u8; 64], [0u8; 64]), ([0u8; 256], [0u8; 64], [0u8; 64])]; + for l in 0..2 { + let (a, b, c) = &mut preds[l]; + for pi in 0..2 { + self.pred_block(rp[l], (px, py), part.rect(pi), mvs[l][pi], PredOut { y: a, u: b, v: c }); + } + } + let mut bi = preds[0]; + avg_into(&mut bi.0, &preds[1].0); + avg_into(&mut bi.1, &preds[1].1); + avg_into(&mut bi.2, &preds[1].2); + let srcs = [&preds[0], &preds[1], &bi]; + let (mut py_, mut pu, mut pv) = ([0u8; 256], [0u8; 64], [0u8; 64]); + let mut dirs = [0u8; 4]; + let mut cost = ((lam * 8) >> 8) as u64; + for pi in 0..2 { + let (x, y, w, h) = part.rect(pi); + let mut bestd = (u64::MAX, 0usize); + for (d, s) in srcs.iter().enumerate() { + let bits = if d == 2 { 14 } else { 7 }; + let c = satd(&src.y[y * 16 + x..], 16, &s.0[y * 16 + x..], 16, w, h) as u64 + ((lam * bits) >> 8) as u64; + if c < bestd.0 { + bestd = (c, d); + } + } + cost += bestd.0; + dirs[pi] = bestd.1 as u8; + if beat.is_some_and(|b| cost >= b) { + return None; + } + if beat.is_some_and(|b| cost >= b) { + return None; + } + let s = srcs[bestd.1]; + for r in y..y + h { + py_[r * 16 + x..r * 16 + x + w].copy_from_slice(&s.0[r * 16 + x..r * 16 + x + w]); + } + for r in y / 2..(y + h) / 2 { + let o = r * 8 + x / 2; + pu[o..o + w / 2].copy_from_slice(&s.1[o..o + w / 2]); + pv[o..o + w / 2].copy_from_slice(&s.2[o..o + w / 2]); + } + } + // predictors in decoding order with the actual per-list references + let mut info = MbInfo::default(); + let mut refs = [[-1i8; 4]; 2]; + let mut fmvs = [[Mv::ZERO; 4]; 2]; + let mut mvds = [[Mv::ZERO; 4]; 2]; + for pi in 0..2 { + let (x, y, w, h) = part.rect(pi); + let mask = Self::mask_of(part, pi); + for l in 0..2 { + if dirs[pi] == 2 || dirs[pi] as usize == l { + let mvp = self.mvp( + MvNb { cur: &info, mask, mx, my }, + PartGeom { x: (x / 4) as i32, y: (y / 4) as i32, w: (w / 4) as i32, shape: Self::shape_of(part, pi) }, + l, + 0, + ); + let mv = mvs[l][pi]; + mvds[l][pi] = Mv::new(mv.x as i32 - mvp.x as i32, mv.y as i32 - mvp.y as i32); + refs[l][pi] = 0; + fmvs[l][pi] = mv; + } + } + for l in 0..2 { + for by in y / 4..(y + h) / 4 { + for bx in x / 4..(x + w) / 4 { + info.mv[l][by * 4 + bx] = fmvs[l][pi]; + } + } + for by8 in y / 8..(y + h).div_ceil(8) { + for bx8 in x / 8..(x + w).div_ceil(8) { + info.ref_idx[l][by8 * 2 + bx8] = refs[l][pi]; + } + } + } + } + let mut c = Cand::new(); + c.code.kind = MbKind::BInter; + c.code.part = part; + c.code.bdir = dirs; + c.code.mvd = mvds; + Self::cand_inter_info(&mut c, part, refs, fmvs, mvds); + self.code_inter(src, &py_, &pu, &pv, qp, &mut c); + c.cost = cost; + Some(c) + } + + pub(super) fn analyse_b(&self, mx: usize, my: usize, qp: u8, av: Avail, e: &MbEdges, src: &Src) -> Box { + let f = self.f; + // B slices always have both references; without them the macroblock is coded intra. + let (Some(r0), Some(r1)) = (f.l0, f.l1) else { return self.analyse_intra(&MbCtx { mx, my, qp, av, e, src }, u64::MAX) }; + let lam = f.lam.sad[qp as usize]; + let (px, py) = (mx * 16, my * 16); + // --- direct + let (dref, dmv) = self.direct_spatial(mx, my); + let mut direct_ok = true; + for q in 0..4 { + let rect = Part::P8x8.rect(q); + for l in 0..2 { + if dref[l][q] >= 0 && !self.mv_ok(px + rect.0, py + rect.1, 8, 8, dmv[l][q]) { + direct_ok = false; + } + } + } + let mut best: Option> = None; + if direct_ok { + let mut y = [0u8; 256]; + let mut u = [0u8; 64]; + let mut v = [0u8; 64]; + let mut ty = [0u8; 256]; + let mut tu = [0u8; 64]; + let mut tv = [0u8; 64]; + for q in 0..4 { + let rect = Part::P8x8.rect(q); + let (u0, u1) = (dref[0][q] >= 0, dref[1][q] >= 0); + if u0 { + self.pred_block(r0, (px, py), rect, dmv[0][q], PredOut { y: &mut y, u: &mut u, v: &mut v }); + } + if u1 { + if u0 { + self.pred_block(r1, (px, py), rect, dmv[1][q], PredOut { y: &mut ty, u: &mut tu, v: &mut tv }); + let (x, yy, w, h) = rect; + for rr in 0..h { + avg_into(&mut y[(yy + rr) * 16 + x..(yy + rr) * 16 + x + w], &ty[(yy + rr) * 16 + x..(yy + rr) * 16 + x + w]); + } + for rr in 0..h / 2 { + let o = (yy / 2 + rr) * 8 + x / 2; + avg_into(&mut u[o..o + w / 2], &tu[o..o + w / 2]); + avg_into(&mut v[o..o + w / 2], &tv[o..o + w / 2]); + } + } else { + self.pred_block(r1, (px, py), rect, dmv[1][q], PredOut { y: &mut y, u: &mut u, v: &mut v }); + } + } + } + let mut c = Cand::new(); + c.code.kind = MbKind::BDirect; + c.code.part = Part::P8x8; + // store per-8x8 refs/mvs + let refs = [[dref[0][0], dref[0][1], dref[0][2], dref[0][3]], [dref[1][0], dref[1][1], dref[1][2], dref[1][3]]]; + Self::cand_inter_info(&mut c, Part::P8x8, refs, dmv, [[Mv::ZERO; 4]; 2]); + c.code.part = Part::P16x16; + c.cost = satd(&src.y, 16, &y, 16, 16, 16) as u64 + ((lam * 2) >> 8) as u64; + self.code_inter(src, &y, &u, &v, qp, &mut c); + if c.code.cbp == 0 { + c.code.kind = MbKind::BSkip; + c.info.t8x8 = false; + // a skip is cheap; accept immediately when the prediction is good + let thresh = (f.qt.v4[(qp % 6) as usize][0] << (qp / 6)) as u64 * 24; + if sad16(&src.y, 16, &y, 16, 16) as u64 <= thresh { + return c; + } + } + best = Some(c); + } + // --- L0 / L1 16x16 + let mut res = [(Mv::ZERO, Mv::ZERO, u32::MAX); 2]; + for (l, rp) in [(0usize, r0), (1usize, r1)] { + let (m, p, c) = self.search_part(rp, &PartSearch { mx, my, part: Part::P16x16, list: l, lam, seed: dmv[l][0] }, src); + res[l] = (m[0], p[0], c); + } + let mut preds = [([0u8; 256], [0u8; 64], [0u8; 64]), ([0u8; 256], [0u8; 64], [0u8; 64])]; + for (l, rp) in [(0usize, r0), (1usize, r1)] { + let (a, b, c) = &mut preds[l]; + self.pred_block(rp, (px, py), (0, 0, 16, 16), res[l].0, PredOut { y: a, u: b, v: c }); + } + let mut bi = preds[0]; + avg_into(&mut bi.0, &preds[1].0); + avg_into(&mut bi.1, &preds[1].1); + avg_into(&mut bi.2, &preds[1].2); + let bits = |l: usize| mv_bits(res[l].0, res[l].1); + let costs = [ + res[0].2 as u64 + ((lam * 3) >> 8) as u64, + res[1].2 as u64 + ((lam * 3) >> 8) as u64, + satd(&src.y, 16, &bi.0, 16, 16, 16) as u64 + ((lam * (bits(0) + bits(1) + 5)) >> 8) as u64, + ]; + let dir = (0..3).min_by_key(|&d| costs[d]).unwrap_or(0); + let mut ci = Cand::new(); + ci.code.kind = MbKind::BInter; + ci.code.part = Part::P16x16; + ci.code.bdir[0] = dir as u8; + let mut refs = [[-1i8; 4]; 2]; + let mut mvs = [[Mv::ZERO; 4]; 2]; + let mut mvds = [[Mv::ZERO; 4]; 2]; + for l in 0..2 { + if dir == 2 || dir == l { + refs[l] = [0; 4]; + mvs[l] = [res[l].0; 4]; + mvds[l][0] = Mv::new(res[l].0.x as i32 - res[l].1.x as i32, res[l].0.y as i32 - res[l].1.y as i32); + } + } + ci.code.mvd = mvds; + Self::cand_inter_info(&mut ci, Part::P16x16, refs, mvs, mvds); + let (py_, pu, pv) = if dir == 2 { bi } else { preds[dir] }; + ci.cost = costs[dir]; + let better_than_direct = best.as_ref().is_none_or(|b| ci.cost < b.cost); + // Without RD only costs decide from here on: the 16x16 candidate's residual is coded at + // the end, if it is still the best then. + let mut deferred = false; + if better_than_direct || f.p.rd { + if f.p.rd { + self.code_inter(src, &py_, &pu, &pv, qp, &mut ci); + } else { + deferred = true; + } + best = Some(match best { + None => ci, + Some(b) => { + if f.p.rd { + if self.rd_cost(mx, my, &ci, src, qp) < self.rd_cost(mx, my, &b, src, qp) { ci } else { b } + } else { + ci + } + } + }); + } + let mut best = best.unwrap_or_else(|| self.analyse_intra(&MbCtx { mx, my, qp, av, e, src }, u64::MAX)); + // --- 16x8 / 8x16 partitions with a per-partition prediction direction + if f.p.partitions && best.cost > 256 * 4 { + for part in [Part::P16x8, Part::P8x16] { + // Without RD only the cost decides, so a losing candidate's residual is not coded. + let beat = (!f.p.rd).then_some(best.cost); + if let Some(c) = self.b_partition(&MbCtx { mx, my, qp, av, e, src }, part, lam, [res[0].0, res[1].0], beat) { + let better = if f.p.rd { self.rd_cost(mx, my, &c, src, qp) < self.rd_cost(mx, my, &best, src, qp) } else { c.cost < best.cost }; + if better { + best = c; + deferred = false; + } + } + } + } + let ctx = MbCtx { mx, my, qp, av, e, src }; + if let Some(intra) = self.better_intra_candidate(&ctx, &best, lam) { + return intra; + } + if deferred { + self.code_inter(src, &py_, &pu, &pv, qp, &mut best); + } + best + } + + fn better_intra_candidate(&self, ctx: &MbCtx<'_>, best: &Cand, lam: u32) -> Option> { + let threshold = (256 * 3 + 16 * lam as u64 / 256) * 3; + if !self.f.p.always_intra && best.cost <= threshold { + return None; + } + let intra = self.analyse_intra(ctx, best.cost); + let improves = if self.f.p.rd { + self.rd_cost(ctx.mx, ctx.my, &intra, ctx.src, ctx.qp) < self.rd_cost(ctx.mx, ctx.my, best, ctx.src, ctx.qp) + } else { + intra.cost < best.cost + }; + improves.then_some(intra) + } +} diff --git a/crates/h264enc/src/slice/intra.rs b/crates/h264enc/src/slice/intra.rs new file mode 100644 index 0000000000..5150ace103 --- /dev/null +++ b/crates/h264enc/src/slice/intra.rs @@ -0,0 +1,481 @@ +use super::{ + Avail, BLK4_XY, Cand, Deadzone, Edge4, Edge8, MbCtx, MbEdges, MbKind, RASTER_TO_BLK4, SliceEnc, Src, ZIGZAG4, ZIGZAG8, cavlc, dequant_luma_dc, + dequant4, dequant8, fdct4, fdct8, filter8, idct4, idct8, mode_available, pred_chroma, pred4x4, pred8x8, pred16x16, quant_luma_dc, quant4, quant8, + satd, +}; + +impl<'a, 'b> SliceEnc<'a, 'b> { + // ------------------------------------------------------------------ intra + + fn pred_intra_mode(&self, mx: usize, my: usize, av: Avail, local: &[i8; 16], bx: usize, by: usize) -> u8 { + let a = if bx > 0 { + Some(local[by * 4 + bx - 1]) + } else if av.left { + Some(self.mb(mx - 1, my).imodes[by * 4 + 3]) + } else { + None + }; + let b = if by > 0 { + Some(local[(by - 1) * 4 + bx]) + } else if av.top { + Some(self.mb(mx, my - 1).imodes[12 + bx]) + } else { + None + }; + match (a, b) { + (Some(a), Some(b)) => { + let a = if a < 0 { 2 } else { a }; + let b = if b < 0 { 2 } else { b }; + a.min(b) as u8 + } + _ => 2, + } + } + + /// Luma I16x16 coding. Returns luma cbp (0 or 15). + fn code_i16(&self, src: &[u8; 256], pred: &[u8; 256], qp: u8, c: &mut Cand) -> u8 { + let qt = self.f.qt; + let mut coefs = [[0i32; 16]; 16]; + let mut dc = [0i32; 16]; + for blk in 0..16 { + let (bx, by) = BLK4_XY[blk]; + let mut d = [0i32; 16]; + for y in 0..4 { + for x in 0..4 { + let i = (by * 4 + y) * 16 + bx * 4 + x; + d[y * 4 + x] = src[i] as i32 - pred[i] as i32; + } + } + let mut t = fdct4(&d); + dc[by * 4 + bx] = t[0]; + quant4(&mut t, qt, qp, Deadzone::INTRA, true); + if self.f.p.cavlc_clamp { + for v in t.iter_mut() { + *v = (*v).clamp(-cavlc::MAX_LEVEL, cavlc::MAX_LEVEL); + } + } + coefs[blk] = t; + } + let (mut dcl, _) = quant_luma_dc(&dc, qt, qp, Deadzone::INTRA); + if self.f.p.cavlc_clamp { + for v in dcl.iter_mut() { + *v = (*v).clamp(-cavlc::MAX_LEVEL, cavlc::MAX_LEVEL); + } + } + for k in 0..16 { + c.code.luma_dc[k] = dcl[ZIGZAG4[k]] as i16; + } + let ac_any = coefs.iter().any(|t| t[1..].iter().any(|&v| v != 0)); + let dcq = dequant_luma_dc(&dcl, qt, qp); + c.info.dc_cbf = (c.info.dc_cbf & !1) | dcl.iter().any(|&v| v != 0) as u8; + for blk in 0..16 { + let (bx, by) = BLK4_XY[blk]; + let mut t = coefs[blk]; + let mut scan = [0i16; 16]; + for k in 1..16 { + scan[k] = t[ZIGZAG4[k]] as i16; + } + c.code.luma4[blk] = if ac_any { scan } else { [0; 16] }; + let nz = if ac_any { scan.iter().filter(|&&v| v != 0).count() as u8 } else { 0 }; + c.info.nnz[by * 4 + bx] = nz; + let off = by * 4 * 16 + bx * 4; + if nz == 0 { + let dcv = (dcq[by * 4 + bx] + 32) >> 6; + for y in 0..4 { + for x in 0..4 { + let i = off + y * 16 + x; + c.ry[i] = (pred[i] as i32 + dcv).clamp(0, 255) as u8; + } + } + } else { + dequant4(&mut t, qt, qp, true); + t[0] = dcq[by * 4 + bx]; + let r = idct4(&t); + for y in 0..4 { + for x in 0..4 { + let i = off + y * 16 + x; + c.ry[i] = (pred[i] as i32 + r[y * 4 + x]).clamp(0, 255) as u8; + } + } + } + } + if ac_any { 15 } else { 0 } + } + + fn best_chroma_mode(&self, e: &MbEdges, av: Avail, src: &Src, lam: u32) -> (u8, [u8; 64], [u8; 64]) { + let mut best = (u32::MAX, 0u8, [0u8; 64], [0u8; 64]); + for mode in 0..4u8 { + let ok = match mode { + 0 => true, + 1 => av.left, + 2 => av.top, + _ => av.left && av.top && av.tl, + }; + if !ok { + continue; + } + let mut pu = [0u8; 64]; + let mut pv = [0u8; 64]; + pred_chroma(mode, &e.ctop[0], &e.cleft[0], e.ctl[0], av.top, av.left, &mut pu); + pred_chroma(mode, &e.ctop[1], &e.cleft[1], e.ctl[1], av.top, av.left, &mut pv); + let cost = satd(&src.u, 8, &pu, 8, 8, 8) + satd(&src.v, 8, &pv, 8, 8, 8) + ((lam * (1 + 2 * mode as u32)) >> 8); + if cost < best.0 { + best = (cost, mode, pu, pv); + } + } + (best.1, best.2, best.3) + } + + fn edge4(&self, e: &MbEdges, av: Avail, rec: &[u8; 256], bx: usize, by: usize) -> Edge4 { + let mut top = [0u8; 8]; + let mut left = [0u8; 4]; + let has_top = by > 0 || av.top; + let has_left = bx > 0 || av.left; + let has_tl = if bx > 0 && by > 0 { + true + } else if by == 0 && bx > 0 { + av.top + } else if bx == 0 && by > 0 { + av.left + } else { + av.tl + }; + let tr_avail = + if by == 0 { if bx < 3 { av.top } else { av.tr } } else { bx < 3 && RASTER_TO_BLK4[(by - 1) * 4 + bx + 1] < RASTER_TO_BLK4[by * 4 + bx] }; + if has_top { + for i in 0..4 { + top[i] = if by > 0 { rec[(by * 4 - 1) * 16 + bx * 4 + i] } else { e.top[bx * 4 + i] }; + } + for i in 4..8 { + top[i] = if tr_avail { if by > 0 { rec[(by * 4 - 1) * 16 + bx * 4 + i] } else { e.top[bx * 4 + i] } } else { top[3] }; + } + } + if has_left { + for i in 0..4 { + left[i] = if bx > 0 { rec[(by * 4 + i) * 16 + bx * 4 - 1] } else { e.left[by * 4 + i] }; + } + } + let tl = if !has_tl { + 0 + } else if bx > 0 && by > 0 { + rec[(by * 4 - 1) * 16 + bx * 4 - 1] + } else if by == 0 && bx > 0 { + e.top[bx * 4 - 1] + } else if bx == 0 && by > 0 { + e.left[by * 4 - 1] + } else { + e.tl + }; + Edge4 { top, left, tl, has_top, has_left, has_tl } + } + + fn edge8(&self, e: &MbEdges, av: Avail, rec: &[u8; 256], b8: usize) -> Edge8 { + let (x8, y8) = (b8 & 1, b8 >> 1); + let mut top = [0u8; 16]; + let mut left = [0u8; 8]; + let has_top = y8 > 0 || av.top; + let has_left = x8 > 0 || av.left; + let has_tl = match b8 { + 0 => av.tl, + 1 => av.top, + 2 => av.left, + _ => true, + }; + let tr_avail = match b8 { + 0 => av.top, + 1 => av.tr, + 2 => true, + _ => false, + }; + if has_top { + for i in 0..8 { + top[i] = if y8 > 0 { rec[7 * 16 + x8 * 8 + i] } else { e.top[x8 * 8 + i] }; + } + for i in 8..16 { + top[i] = if tr_avail { if y8 > 0 { rec[7 * 16 + x8 * 8 + i] } else { e.top[x8 * 8 + i] } } else { top[7] }; + } + } + if has_left { + for i in 0..8 { + left[i] = if x8 > 0 { rec[(y8 * 8 + i) * 16 + 7] } else { e.left[y8 * 8 + i] }; + } + } + let tl = if !has_tl { + 0 + } else { + match b8 { + 0 => e.tl, + 1 => e.top[7], + 2 => e.left[7], + _ => rec[7 * 16 + 7], + } + }; + Edge8 { top, left, tl, has_top, has_left, has_tl } + } + + /// Intra 4x4 analysis + coding. Returns cost (SATD domain) or None if it exceeded `limit`. + fn intra4x4(&self, m: &MbCtx, c: &mut Cand, limit: u64) -> Option { + let MbCtx { mx, my, qp, av, e, src } = *m; + let qt = self.f.qt; + let lam = self.f.lam.sad[qp as usize]; + let mut modes = [-1i8; 16]; + let mut total = ((lam * 24) >> 8) as u64; + for blk in 0..16 { + let (bx, by) = BLK4_XY[blk]; + let ed = self.edge4(e, av, &c.ry, bx, by); + let pm = self.pred_intra_mode(mx, my, av, &modes, bx, by); + let mut srcb = [0u8; 16]; + for y in 0..4 { + srcb[y * 4..y * 4 + 4].copy_from_slice(&src.y[(by * 4 + y) * 16 + bx * 4..(by * 4 + y) * 16 + bx * 4 + 4]); + } + let mut best = (u32::MAX, 2u8, [0u8; 16]); + for mode in 0..9u8 { + if !mode_available(mode, ed.has_top, ed.has_left, ed.has_tl) { + continue; + } + let mut p = [0u8; 16]; + pred4x4(mode, &ed, &mut p); + let bits = if mode == pm { 1 } else { 4 }; + let cost = satd(&srcb, 4, &p, 4, 4, 4) + ((lam * bits) >> 8); + if cost < best.0 { + best = (cost, mode, p); + } + } + total += best.0 as u64; + if total > limit { + return None; + } + let (mode, p) = (best.1, best.2); + modes[by * 4 + bx] = mode as i8; + c.code.ipred[blk] = if mode == pm { + -1 + } else if mode < pm { + mode as i8 + } else { + mode as i8 - 1 + }; + // code block + let mut d = [0i32; 16]; + for i in 0..16 { + d[i] = srcb[i] as i32 - p[i] as i32; + } + let mut t = fdct4(&d); + let nz = quant4(&mut t, qt, qp, Deadzone::INTRA, false); + if self.f.p.cavlc_clamp { + for v in t.iter_mut() { + *v = (*v).clamp(-cavlc::MAX_LEVEL, cavlc::MAX_LEVEL); + } + } + let mut scan = [0i16; 16]; + for k in 0..16 { + scan[k] = t[ZIGZAG4[k]] as i16; + } + c.code.luma4[blk] = scan; + c.info.nnz[by * 4 + bx] = nz as u8; + let off = by * 4 * 16 + bx * 4; + if nz == 0 { + for y in 0..4 { + c.ry[off + y * 16..off + y * 16 + 4].copy_from_slice(&p[y * 4..y * 4 + 4]); + } + } else { + dequant4(&mut t, qt, qp, false); + let r = idct4(&t); + for y in 0..4 { + for x in 0..4 { + c.ry[off + y * 16 + x] = (p[y * 4 + x] as i32 + r[y * 4 + x]).clamp(0, 255) as u8; + } + } + } + } + c.info.imodes = modes; + let mut cbp = 0; + for b8 in 0..4 { + if (0..4).any(|i| c.code.luma4[b8 * 4 + i].iter().any(|&v| v != 0)) { + cbp |= 1 << b8; + } + } + c.code.cbp = cbp; + c.code.kind = MbKind::I4x4; + c.code.t8x8 = false; + c.info.t8x8 = false; + Some(total) + } + + /// Intra 8x8 analysis + coding. + fn intra8x8(&self, m: &MbCtx, c: &mut Cand, limit: u64) -> Option { + let MbCtx { mx, my, qp, av, e, src } = *m; + let qt = self.f.qt; + let lam = self.f.lam.sad[qp as usize]; + let mut modes = [-1i8; 16]; + let mut total = ((lam * 12) >> 8) as u64; + for b8 in 0..4 { + let (x8, y8) = (b8 & 1, b8 >> 1); + let ed = self.edge8(e, av, &c.ry, b8); + let fe = filter8(&ed); + let pm = self.pred_intra_mode(mx, my, av, &modes, x8 * 2, y8 * 2); + let mut srcb = [0u8; 64]; + for y in 0..8 { + srcb[y * 8..y * 8 + 8].copy_from_slice(&src.y[(y8 * 8 + y) * 16 + x8 * 8..(y8 * 8 + y) * 16 + x8 * 8 + 8]); + } + let mut best = (u32::MAX, 2u8, [0u8; 64]); + for mode in 0..9u8 { + if !mode_available(mode, ed.has_top, ed.has_left, ed.has_tl) { + continue; + } + let mut p = [0u8; 64]; + pred8x8(mode, &fe, &mut p); + let bits = if mode == pm { 1 } else { 4 }; + let cost = satd(&srcb, 8, &p, 8, 8, 8) + ((lam * bits) >> 8); + if cost < best.0 { + best = (cost, mode, p); + } + } + total += best.0 as u64; + if total > limit { + return None; + } + let (mode, p) = (best.1, best.2); + for (dx, dy) in [(0, 0), (1, 0), (0, 1), (1, 1)] { + modes[(y8 * 2 + dy) * 4 + x8 * 2 + dx] = mode as i8; + } + c.code.ipred[b8] = if mode == pm { + -1 + } else if mode < pm { + mode as i8 + } else { + mode as i8 - 1 + }; + let mut d = [0i32; 64]; + for i in 0..64 { + d[i] = srcb[i] as i32 - p[i] as i32; + } + let mut t = fdct8(&d); + let nz = quant8(&mut t, qt, qp, Deadzone::INTRA); + let mut scan = [0i16; 64]; + for k in 0..64 { + scan[k] = t[ZIGZAG8[k]] as i16; + } + c.code.luma8[b8] = scan; + for (dx, dy) in [(0, 0), (1, 0), (0, 1), (1, 1)] { + c.info.nnz[(y8 * 2 + dy) * 4 + x8 * 2 + dx] = nz as u8; + } + let off = y8 * 8 * 16 + x8 * 8; + if nz == 0 { + for y in 0..8 { + c.ry[off + y * 16..off + y * 16 + 8].copy_from_slice(&p[y * 8..y * 8 + 8]); + } + } else { + dequant8(&mut t, qt, qp); + let r = idct8(&t); + for y in 0..8 { + for x in 0..8 { + c.ry[off + y * 16 + x] = (p[y * 8 + x] as i32 + r[y * 8 + x]).clamp(0, 255) as u8; + } + } + } + } + c.info.imodes = modes; + let mut cbp = 0; + for b8 in 0..4 { + if c.code.luma8[b8].iter().any(|&v| v != 0) { + cbp |= 1 << b8; + } + } + c.code.cbp = cbp; + c.code.kind = MbKind::I8x8; + c.code.t8x8 = true; + c.info.t8x8 = true; + Some(total) + } + + /// Full intra analysis; returns the best coded intra candidate. `inter_cost` lets P/B skip expensive modes. + pub(super) fn analyse_intra(&self, m: &MbCtx, inter_cost: u64) -> Box { + let MbCtx { mx, my, qp, av, e, src } = *m; + let lam = self.f.lam.sad[qp as usize]; + let p = self.f.p; + // I16x16 mode + let mut top16 = [0u8; 16]; + top16.copy_from_slice(&e.top[..16]); + let mut b16 = (u32::MAX, 2u8, [0u8; 256]); + for mode in 0..4u8 { + let ok = match mode { + 0 => av.top, + 1 => av.left, + 2 => true, + _ => av.top && av.left && av.tl, + }; + if !ok { + continue; + } + let mut pr = [0u8; 256]; + pred16x16(mode, &top16, &e.left, e.tl, av.top, av.left, &mut pr); + let cost = satd(&src.y, 16, &pr, 16, 16, 16); + if cost < b16.0 { + b16 = (cost, mode, pr); + } + } + let cost16 = b16.0 as u64 + ((lam * 6) >> 8) as u64; + let (cmode, pu, pv) = self.best_chroma_mode(e, av, src, lam); + let mk16 = || { + let mut c = Cand::new(); + c.code.kind = MbKind::I16x16; + c.code.i16_mode = b16.1; + c.code.chroma_mode = cmode; + let cbpl = self.code_i16(&src.y, &b16.2, qp, &mut c); + let cbpc = self.code_chroma(src, &pu, &pv, qp, true, &mut c); + c.code.cbp = cbpl | (cbpc << 4); + c.info.chroma_mode = cmode; + c.info.imodes = [-1; 16]; + c.cost = cost16; + c + }; + let mut best: Option> = None; + let try_nxn = p.i4x4 && (inter_cost == u64::MAX || cost16 < inter_cost + inter_cost / 4 || p.always_intra); + if !try_nxn { + return mk16(); + } + let limit = cost16.min(inter_cost.saturating_add(inter_cost / 8)); + let mut limit = if p.rd { cost16 + cost16 / 4 } else { limit }; + if p.i8x8 && p.t8x8 { + let mut c = Cand::new(); + if let Some(cost) = self.intra8x8(m, &mut c, limit) { + c.cost = cost; + limit = limit.min(cost); + best = Some(c); + } + } + { + let mut c = Cand::new(); + if let Some(cost) = self.intra4x4(m, &mut c, limit) { + c.cost = cost; + if best.as_ref().is_none_or(|b| cost < b.cost) { + best = Some(c); + } + } + } + let finish = |mut c: Box| { + c.code.chroma_mode = cmode; + c.info.chroma_mode = cmode; + let cbpc = self.code_chroma(src, &pu, &pv, qp, true, &mut c); + c.code.cbp = (c.code.cbp & 15) | (cbpc << 4); + c + }; + match best { + None => mk16(), + Some(c) => { + let c = finish(c); + if p.rd { + let c16 = mk16(); + let (j1, j2) = (self.rd_cost(mx, my, &c, src, qp), self.rd_cost(mx, my, &c16, src, qp)); + let mut w = if j1 <= j2 { c } else { c16 }; + w.cost = w.cost.min(cost16); + w + } else if c.cost < cost16 { + c + } else { + mk16() + } + } + } + } +} diff --git a/crates/h264enc/src/slice/macroblock.rs b/crates/h264enc/src/slice/macroblock.rs new file mode 100644 index 0000000000..42151bacee --- /dev/null +++ b/crates/h264enc/src/slice/macroblock.rs @@ -0,0 +1,148 @@ +use super::{BitWriter, CabacEstimator, Cand, MbCtx, MbKind, NbCtx, SliceEnc, SliceType, Src, cabac_mb, cavlc, ssd}; + +impl<'a, 'b> SliceEnc<'a, 'b> { + // ------------------------------------------------------------------ macroblock + + pub(super) fn encode_mb(&mut self, mx: usize, my: usize) { + let f = self.f; + let mbw = f.p.mbw; + let aq = if f.aq.is_empty() { 0 } else { f.aq[my * mbw + mx] as i32 }; + let qp = (f.qp as i32 + aq).clamp(0, 51) as u8; + let av = self.avail(mx, my); + let (px, py) = (mx * 16, my * 16); + let mut src_y = [0u8; 256]; + let mut src_u = [0u8; 64]; + let mut src_v = [0u8; 64]; + { + let s = &f.src.y; + for r in 0..16 { + let i = s.idx(px as isize, (py + r) as isize); + src_y[r * 16..r * 16 + 16].copy_from_slice(&s.data[i..i + 16]); + } + for r in 0..8 { + let i = f.src.u.idx((px / 2) as isize, (py / 2 + r) as isize); + src_u[r * 8..r * 8 + 8].copy_from_slice(&f.src.u.data[i..i + 8]); + let i = f.src.v.idx((px / 2) as isize, (py / 2 + r) as isize); + src_v[r * 8..r * 8 + 8].copy_from_slice(&f.src.v.data[i..i + 8]); + } + } + let src = Src { y: src_y, u: src_u, v: src_v }; + let edges = self.edges(mx, my, av); + let best = match f.st { + SliceType::I => self.analyse_intra(&MbCtx { mx, my, qp, av, e: &edges, src: &src }, u64::MAX), + SliceType::P => self.analyse_p(mx, my, qp, av, &edges, &src), + SliceType::B => self.analyse_b(mx, my, qp, av, &edges, &src), + }; + self.commit(mx, my, qp, *best); + } + + fn commit(&mut self, mx: usize, my: usize, qp: u8, mut c: Cand) { + let (px, py) = (mx * 16, my * 16); + // reconstruction + for r in 0..16 { + let i = self.ry(px, py + r); + self.band.y[i..i + 16].copy_from_slice(&c.ry[r * 16..r * 16 + 16]); + } + for r in 0..8 { + let i = self.rc(px / 2, py / 2 + r); + self.band.u[i..i + 8].copy_from_slice(&c.ru[r * 8..r * 8 + 8]); + self.band.v[i..i + 8].copy_from_slice(&c.rv[r * 8..r * 8 + 8]); + } + let kind = c.code.kind; + let has_dqp = !kind.is_skip() && (c.code.cbp != 0 || kind == MbKind::I16x16); + let prev_dqp_nz = self.prev_dqp_nz; + if has_dqp { + let mut d = qp as i32 - self.last_qp as i32; + if d < -26 { + d += 52; + } else if d > 25 { + d -= 52; + } + c.code.qp_delta = d; + self.prev_dqp_nz = d != 0; + self.last_qp = qp; + } else { + c.code.qp_delta = 0; + self.prev_dqp_nz = false; + } + c.info.qp = self.last_qp; + c.info.slice = self.slice_no + 1; + c.info.kind = kind; + c.info.cbp = if kind.is_skip() { 0 } else { c.code.cbp }; + self.stats.mbs += 1; + self.stats.qp_sum += c.info.qp as u64; + if kind.is_intra() { + self.stats.intra += 1; + } else if kind.is_skip() { + self.stats.skip += 1; + } else { + self.stats.inter += 1; + } + let idx = (my - self.y0) * self.f.p.mbw + mx; + self.mbs[idx] = c.info; + // entropy coding + let av = self.avail(mx, my); + let left = if av.left { Some(&self.mbs[idx - 1]) } else { None }; + let top = if av.top { Some(&self.mbs[idx - self.f.p.mbw]) } else { None }; + let nb = NbCtx { left, top, cur: &self.mbs[idx], slice_type: self.f.st, prev_dqp_nz, t8x8_mode: self.f.p.t8x8 }; + if let Some(cab) = self.cabac.as_mut() { + if nb.slice_type != SliceType::I { + cabac_mb::write_skip_flag(cab, &nb, kind.is_skip()); + } + if !kind.is_skip() { + cabac_mb::write_mb(cab, &nb, &c.code); + } + } else if let Some(w) = self.cavlc.as_mut() { + if kind.is_skip() { + self.skip_run += 1; + } else { + if nb.slice_type != SliceType::I { + w.write_ue(self.skip_run); + self.skip_run = 0; + } + cavlc::write_mb(w, &nb, &c.code); + } + } + } + + /// Estimated bits (1/256 units) of coding candidate `c` at the current entropy state. + fn est_bits(&self, mx: usize, my: usize, c: &Cand, qp: u8) -> u64 { + let av = self.avail(mx, my); + let idx = (my - self.y0) * self.f.p.mbw + mx; + let left = if av.left { Some(&self.mbs[idx - 1]) } else { None }; + let top = if av.top { Some(&self.mbs[idx - self.f.p.mbw]) } else { None }; + let mut info = c.info; + info.kind = c.code.kind; + info.cbp = c.code.cbp; + let mut code = c.code.clone(); + code.qp_delta = qp as i32 - self.last_qp as i32; + let nb = NbCtx { left, top, cur: &info, slice_type: self.f.st, prev_dqp_nz: self.prev_dqp_nz, t8x8_mode: self.f.p.t8x8 }; + if let Some(cab) = self.cabac.as_ref() { + let mut est = CabacEstimator::new(cab.ctx.clone(), self.f.cost_tab); + if nb.slice_type != SliceType::I { + cabac_mb::write_skip_flag(&mut est, &nb, code.kind.is_skip()); + } + if !code.kind.is_skip() { + cabac_mb::write_mb(&mut est, &nb, &code); + } + est.cost + } else { + if code.kind.is_skip() { + return 256; + } + let mut w = BitWriter::new(); + if nb.slice_type != SliceType::I { + w.write_ue(self.skip_run); + } + cavlc::write_mb(&mut w, &nb, &code); + w.bit_len() as u64 * 256 + } + } + + pub(super) fn rd_cost(&self, mx: usize, my: usize, c: &Cand, src: &Src, qp: u8) -> u64 { + let d = ssd(&src.y, 16, &c.ry, 16, 16, 16) + ssd(&src.u, 8, &c.ru, 8, 8, 8) + ssd(&src.v, 8, &c.rv, 8, 8, 8); + let bits = self.est_bits(mx, my, c, qp); + let lam = self.f.lam.ssd[qp as usize]; + (d as f64 * 256.0 + lam * bits as f64) as u64 + } +} diff --git a/crates/h264enc/src/slice/motion.rs b/crates/h264enc/src/slice/motion.rs new file mode 100644 index 0000000000..3ec86bc237 --- /dev/null +++ b/crates/h264enc/src/slice/motion.rs @@ -0,0 +1,386 @@ +use super::{ + Avail, Cand, MbCtx, MbEdges, MbInfo, MbKind, Mv, MvNb, Part, PartGeom, PartSearch, PredOut, RefPic, SearchBlock, Shape, SliceEnc, Src, mv_bits, + sad, sad8, sad16, satd, +}; +use crate::picture::McBlock; + +/// Motion vectors already costed in one search: one bit per point of the square of +/// `2 * half + 1` points (`step` quarter samples apart) around `centre`. A point outside the +/// square, off the grid or past the `W` words always counts as new. +struct Visited { + centre: Mv, + half: i32, + step: i32, + bits: [u64; W], +} + +impl Visited { + fn new(centre: Mv, half: i32, step: i32) -> Self { + Visited { centre, half, step: step.max(1), bits: [0; W] } + } + + /// Marks `m`; false when it was marked before. + fn first(&mut self, m: Mv) -> bool { + let (dx, dy) = (m.x as i32 - self.centre.x as i32, m.y as i32 - self.centre.y as i32); + if dx % self.step != 0 || dy % self.step != 0 { + return true; + } + let side = 2 * self.half + 1; + let (gx, gy) = (dx / self.step + self.half, dy / self.step + self.half); + if gx < 0 || gy < 0 || gx >= side || gy >= side { + return true; + } + let i = (gy * side + gx) as usize; + let Some(word) = self.bits.get_mut(i / 64) else { return true }; + let bit = 1u64 << (i % 64); + let fresh = *word & bit == 0; + *word |= bit; + fresh + } +} + +impl<'a, 'b> SliceEnc<'a, 'b> { + // ------------------------------------------------------------------ motion estimation + + /// Integer + sub-pel search for a w x h block at picture (bx, by). Returns (mv, cost, satd). + fn search(&self, r: &RefPic, blk: &SearchBlock, mvp: Mv, cands: &[Mv], lam: u32) -> (Mv, u32) { + let SearchBlock { x: bx, y: by, w, h, src, sstride } = *blk; + let pl = &r.frame.y; + let p = self.f.p; + let fcost = |mv: Mv| -> u32 { + let i = pl.idx(bx as isize + (mv.x as isize >> 2), by as isize + (mv.y as isize >> 2)); + let d = match w { + 16 => sad16(src, sstride, &pl.data[i..], pl.stride, h), + 8 => sad8(src, sstride, &pl.data[i..], pl.stride, h), + _ => sad(src, sstride, &pl.data[i..], pl.stride, w, h), + }; + d + ((lam * mv_bits(mv, mvp)) >> 8) + }; + let mut best = Mv::new((mvp.x as i32 + 2) & !3, (mvp.y as i32 + 2) & !3); + if !self.mv_ok(bx, by, w, h, best) { + best = Mv::ZERO; + } + let mut bcost = fcost(best); + for &c in cands { + let c = Mv::new((c.x as i32 + 2) & !3, (c.y as i32 + 2) & !3); + if c != best && self.mv_ok(bx, by, w, h, c) { + let k = fcost(c); + if k < bcost { + bcost = k; + best = c; + } + } + } + // hexagon search (full-pel units = 4 quarter samples) + let range = p.me_range; + let centre0 = best; + // a point costed once is never better than the best since: skip it when it comes up again + let mut seen = Visited::<40>::new(centre0, range, 4); + seen.first(centre0); + const HEX: [(i32, i32); 6] = [(-2, 0), (2, 0), (-1, -2), (1, -2), (-1, 2), (1, 2)]; + for _ in 0..range { + let mut moved = false; + let c = best; + for &(dx, dy) in &HEX { + let m = Mv::new(c.x as i32 + dx * 4, c.y as i32 + dy * 4); + if ((m.x as i32 - centre0.x as i32).abs() >> 2) > range + || ((m.y as i32 - centre0.y as i32).abs() >> 2) > range + || !self.mv_ok(bx, by, w, h, m) + || !seen.first(m) + { + continue; + } + let k = fcost(m); + if k < bcost { + bcost = k; + best = m; + moved = true; + } + } + if !moved { + break; + } + } + // small diamond / square refinement + for _ in 0..2 { + let c = best; + for (dx, dy) in [(-1, 0), (1, 0), (0, -1), (0, 1), (-1, -1), (1, -1), (-1, 1), (1, 1)] { + let m = Mv::new(c.x as i32 + dx * 4, c.y as i32 + dy * 4); + if !self.mv_ok(bx, by, w, h, m) || !seen.first(m) { + continue; + } + let k = fcost(m); + if k < bcost { + bcost = k; + best = m; + } + } + if best == c { + break; + } + } + // sub-pel refinement with SATD + let mut buf = [0u8; 256]; + let scost = |mv: Mv, buf: &mut [u8; 256]| -> u32 { + let (pr, ps) = r.luma_ref(bx, by, mv, w, h, buf); + satd(src, sstride, pr, ps, w, h) + ((lam * mv_bits(mv, mvp)) >> 8) + }; + let mut bs = scost(best, &mut buf); + let mut seen = Visited::<2>::new(best, 4, 1); + seen.first(best); + if p.subpel >= 1 { + for step in [2, 1] { + if step == 1 && p.subpel < 2 { + break; + } + let rounds = if step == 1 { 1 + p.subpel_rounds as usize } else { 1 }; + for _ in 0..rounds { + let c = best; + for (dx, dy) in [(-1, 0), (1, 0), (0, -1), (0, 1), (-1, -1), (1, -1), (-1, 1), (1, 1)] { + let m = Mv::new(c.x as i32 + dx * step, c.y as i32 + dy * step); + if !self.mv_ok(bx, by, w, h, m) || !seen.first(m) { + continue; + } + let k = scost(m, &mut buf); + if k < bs { + bs = k; + best = m; + } + } + if best == c { + break; + } + } + } + } + (best, bs) + } + + /// Motion-compensated luma + chroma prediction of `rect` (x, y, w, h within the macroblock whose + /// top-left luma sample is `origin`) into `out`. + pub(super) fn pred_block(&self, r: &RefPic, origin: (usize, usize), rect: (usize, usize, usize, usize), mv: Mv, out: PredOut) { + let (px, py) = origin; + let (x, yy, w, h) = rect; + r.mc_luma(McBlock { x: px + x, y: py + yy, w, h }, mv, &mut out.y[yy * 16 + x..], 16); + let co = (yy / 2) * 8 + x / 2; + r.mc_chroma(McBlock { x: (px + x) / 2, y: (py + yy) / 2, w: w / 2, h: h / 2 }, mv, (&mut out.u[co..], &mut out.v[co..]), 8); + } + + pub(super) fn cand_inter_info(c: &mut Cand, part: Part, refs: [[i8; 4]; 2], mvs: [[Mv; 4]; 2], mvds: [[Mv; 4]; 2]) { + for p in 0..part.count() { + let (x, y, w, h) = part.rect(p); + for by in y / 4..(y + h) / 4 { + for bx in x / 4..(x + w) / 4 { + let r = by * 4 + bx; + for l in 0..2 { + c.info.mv[l][r] = if refs[l][p] >= 0 { mvs[l][p] } else { Mv::ZERO }; + let d = if refs[l][p] >= 0 { mvds[l][p] } else { Mv::ZERO }; + c.info.mvd[l][r] = [d.x.unsigned_abs().min(127) as u8, d.y.unsigned_abs().min(127) as u8]; + } + } + } + for by8 in y / 8..(y + h).div_ceil(8) { + for bx8 in x / 8..(x + w).div_ceil(8) { + for l in 0..2 { + c.info.ref_idx[l][by8 * 2 + bx8] = refs[l][p]; + } + } + } + } + } + + pub(super) fn mask_of(part: Part, upto: usize) -> u16 { + let mut m = 0u16; + for p in 0..upto { + let (x, y, w, h) = part.rect(p); + for by in y / 4..(y + h) / 4 { + for bx in x / 4..(x + w) / 4 { + m |= 1 << (by * 4 + bx); + } + } + } + m + } + + pub(super) fn shape_of(part: Part, p: usize) -> Shape { + match (part, p) { + (Part::P16x8, 0) => Shape::Top16x8, + (Part::P16x8, _) => Shape::Bottom16x8, + (Part::P8x16, 0) => Shape::Left8x16, + (Part::P8x16, _) => Shape::Right8x16, + _ => Shape::Normal, + } + } + + /// Search each partition of `ps.part` for list `ps.list` sequentially (mvp depends on earlier partitions). + pub(super) fn search_part(&self, r: &RefPic, ps: &PartSearch, src: &Src) -> ([Mv; 4], [Mv; 4], u32) { + let PartSearch { mx, my, part, list, lam, seed } = *ps; + let (px, py) = (mx * 16, my * 16); + let mut info = MbInfo::default(); + let mut mvs = [Mv::ZERO; 4]; + let mut mvps = [Mv::ZERO; 4]; + let mut total = 0; + for p in 0..part.count() { + let (x, y, w, h) = part.rect(p); + let nb = MvNb { cur: &info, mask: Self::mask_of(part, p), mx, my }; + let geom = PartGeom { x: (x / 4) as i32, y: (y / 4) as i32, w: (w / 4) as i32, shape: Self::shape_of(part, p) }; + let mvp = self.mvp(nb, geom, list, 0); + let blk = SearchBlock { x: px + x, y: py + y, w, h, src: &src.y[y * 16 + x..], sstride: 16 }; + let (mv, cost) = self.search(r, &blk, mvp, &[seed, mvs[0]], lam); + mvs[p] = mv; + mvps[p] = mvp; + total += cost; + // record in scratch info for the next partition's predictor + for by in y / 4..(y + h) / 4 { + for bx in x / 4..(x + w) / 4 { + info.mv[list][by * 4 + bx] = mv; + } + } + for by8 in y / 8..(y + h).div_ceil(8) { + for bx8 in x / 8..(x + w).div_ceil(8) { + info.ref_idx[list][by8 * 2 + bx8] = 0; + } + } + } + (mvs, mvps, total) + } + + pub(super) fn analyse_p(&self, mx: usize, my: usize, qp: u8, av: Avail, e: &MbEdges, src: &Src) -> Box { + let f = self.f; + // P slices always have a reference; without one the macroblock is coded intra. + let Some(r) = f.l0 else { return self.analyse_intra(&MbCtx { mx, my, qp, av, e, src }, u64::MAX) }; + let lam = f.lam.sad[qp as usize]; + let (px, py) = (mx * 16, my * 16); + let skip_mv = self.pskip_mv(mx, my); + let skip_ok = self.mv_ok(px, py, 16, 16, skip_mv); + // --- try P_Skip early + let mut skip_pred = None; + if skip_ok { + let mut y = [0u8; 256]; + let mut u = [0u8; 64]; + let mut v = [0u8; 64]; + self.pred_block(r, (px, py), (0, 0, 16, 16), skip_mv, PredOut { y: &mut y, u: &mut u, v: &mut v }); + let s = sad16(&src.y, 16, &y, 16, 16); + let qstep_thresh = (f.qt.v4[(qp % 6) as usize][0] << (qp / 6)) as u32 * 16; + if s < qstep_thresh * 2 { + let mut c = Cand::new(); + c.code.kind = MbKind::PInter; + c.code.part = Part::P16x16; + self.code_inter(src, &y, &u, &v, qp, &mut c); + if c.code.cbp == 0 { + c.code.kind = MbKind::PSkip; + Self::cand_inter_info(&mut c, Part::P16x16, [[0; 4], [-1; 4]], [[skip_mv; 4], [Mv::ZERO; 4]], [[Mv::ZERO; 4]; 2]); + c.info.nnz = [0; 16]; + c.info.nnz_c = [0; 8]; + c.info.dc_cbf = 0; + c.info.t8x8 = false; + c.ry = y; + c.ru = u; + c.rv = v; + return c; + } + } + skip_pred = Some((y, u, v)); + } + // --- 16x16 search + let mut cands = vec![skip_mv, Mv::ZERO]; + let colo = r.mbs[my * f.p.mbw + mx]; + if colo.ref_idx[0][0] >= 0 { + cands.push(colo.mv[0][0]); + } + let (mv16, mvp16, cost16) = { + let (m, p, c) = self.search_part(r, &PartSearch { mx, my, part: Part::P16x16, list: 0, lam, seed: skip_mv }, src); + (m, p, c) + }; + let _ = cands; + let mut best_part = Part::P16x16; + let mut best_mvs = mv16; + let mut best_mvps = mvp16; + let mut best_cost = cost16 as u64 + (lam >> 8) as u64; + if f.p.partitions { + let big = cost16 as u64 > (256 * 4) as u64; + if big { + for part in [Part::P16x8, Part::P8x16, Part::P8x8] { + let (m, p, c) = self.search_part(r, &PartSearch { mx, my, part, list: 0, lam, seed: mv16[0] }, src); + let extra = match part { + Part::P8x8 => 10, + _ => 3, + }; + let c = c as u64 + ((lam * extra) >> 8) as u64; + if c < best_cost { + best_cost = c; + best_part = part; + best_mvs = m; + best_mvps = p; + } + } + } + } + // --- build inter candidate + let mut ci = Cand::new(); + ci.code.kind = MbKind::PInter; + ci.code.part = best_part; + let mut py_ = [0u8; 256]; + let mut pu = [0u8; 64]; + let mut pv = [0u8; 64]; + if let (Part::P16x16, true, Some((a, b, c))) = (best_part, best_mvs[0] == skip_mv, skip_pred) { + py_ = a; + pu = b; + pv = c; + } else { + for p in 0..best_part.count() { + self.pred_block(r, (px, py), best_part.rect(p), best_mvs[p], PredOut { y: &mut py_, u: &mut pu, v: &mut pv }); + } + } + let mut mvds = [[Mv::ZERO; 4]; 2]; + for p in 0..best_part.count() { + mvds[0][p] = Mv::new(best_mvs[p].x as i32 - best_mvps[p].x as i32, best_mvs[p].y as i32 - best_mvps[p].y as i32); + } + ci.code.mvd = mvds; + Self::cand_inter_info(&mut ci, best_part, [[0; 4], [-1; 4]], [best_mvs, [Mv::ZERO; 4]], mvds); + // The residual decides a P_Skip and the RD comparisons; otherwise it only matters when the + // inter candidate is kept, so it is coded after an intra candidate had its chance. + let skip_candidate = best_part == Part::P16x16 && best_mvs[0] == skip_mv && skip_ok; + let coded_now = skip_candidate || f.p.rd; + if coded_now { + self.code_inter(src, &py_, &pu, &pv, qp, &mut ci); + } + ci.cost = best_cost; + if skip_candidate && ci.code.cbp == 0 { + ci.code.kind = MbKind::PSkip; + ci.info.t8x8 = false; + ci.info.mvd = [[[0; 2]; 16]; 2]; + return ci; + } + // --- intra + let need_intra = f.p.always_intra || best_cost > (256 * 3 + 16 * lam as u64 / 256) * 2; + if need_intra { + let ic = self.analyse_intra(&MbCtx { mx, my, qp, av, e, src }, best_cost); + if f.p.rd { + if self.rd_cost(mx, my, &ic, src, qp) < self.rd_cost(mx, my, &ci, src, qp) { + return ic; + } + } else if ic.cost < best_cost { + return ic; + } + } + if !coded_now { + self.code_inter(src, &py_, &pu, &pv, qp, &mut ci); + } + if f.p.rd && skip_ok && ci.code.cbp != 0 { + // RD check against P_Skip + if let Some((y, u, v)) = skip_pred { + let mut sc = Cand::new(); + sc.code.kind = MbKind::PSkip; + Self::cand_inter_info(&mut sc, Part::P16x16, [[0; 4], [-1; 4]], [[skip_mv; 4], [Mv::ZERO; 4]], [[Mv::ZERO; 4]; 2]); + sc.ry = y; + sc.ru = u; + sc.rv = v; + if self.rd_cost(mx, my, &sc, src, qp) < self.rd_cost(mx, my, &ci, src, qp) { + return sc; + } + } + } + ci + } +} diff --git a/crates/h264enc/src/slice/residual.rs b/crates/h264enc/src/slice/residual.rs new file mode 100644 index 0000000000..b0c463ffc1 --- /dev/null +++ b/crates/h264enc/src/slice/residual.rs @@ -0,0 +1,320 @@ +use super::{ + BLK4_XY, Cand, Deadzone, SliceEnc, Src, ZIGZAG4, ZIGZAG8, cavlc, chroma_qp, decimate_score, dequant_chroma_dc, dequant4, dequant8, fdct4, fdct8, + idct4, idct8, quant_chroma_dc, quant4, quant8, ssd, +}; + +/// The transform trial's bit estimate of a level `v`: `0.4` for zero, `3 + 2·log2|v|` otherwise. +/// Levels below 256 (nearly all) read it from a table of exactly those values. +#[inline] +fn level_bits(table: &[f64; 256], v: i16) -> f64 { + let a = v.unsigned_abs(); + match table.get(usize::from(a)) { + Some(&b) => b, + None => 3.0 + 2.0 * (a as f64).log2(), + } +} + +fn level_bits_table() -> &'static [f64; 256] { + static T: std::sync::OnceLock<[f64; 256]> = std::sync::OnceLock::new(); + T.get_or_init(|| std::array::from_fn(|a| if a == 0 { 0.4 } else { 3.0 + 2.0 * (a as f64).log2() })) +} + +/// `rec = clamp(pred + residual(x, y))` for the 4x4 chroma block at (`bx`, `by`) of an 8x8 chroma plane. +fn add_chroma_residual(rec: &mut [u8], pred: &[u8; 64], bx: usize, by: usize, residual: impl Fn(usize, usize) -> i32) { + for y in 0..4 { + for x in 0..4 { + let i = (by + y) * 8 + bx + x; + rec[i] = (pred[i] as i32 + residual(x, y)).clamp(0, 255) as u8; + } + } +} + +impl<'a, 'b> SliceEnc<'a, 'b> { + // ------------------------------------------------------------------ residual coding helpers + + /// Transform + quantise chroma 4x4 block `b` of a component. Returns (unquantised DC, quantised + /// coefficients in raster order, AC coefficients in scan order). + fn quant_chroma_ac_block(&self, s: &[u8; 64], p: &[u8; 64], b: usize, qpc: u8, dz: Deadzone) -> (i32, [i32; 16], [i16; 16]) { + let (bx, by) = ((b & 1) * 4, (b >> 1) * 4); + let mut d = [0i32; 16]; + for y in 0..4 { + for x in 0..4 { + d[y * 4 + x] = s[(by + y) * 8 + bx + x] as i32 - p[(by + y) * 8 + bx + x] as i32; + } + } + let mut t = fdct4(&d); + let dc = t[0]; + quant4(&mut t, self.f.qt, qpc, dz, true); + if self.f.p.cavlc_clamp { + for v in t.iter_mut().skip(1) { + *v = (*v).clamp(-cavlc::MAX_LEVEL, cavlc::MAX_LEVEL); + } + } + let mut scan = [0i16; 16]; + for k in 1..16 { + scan[k] = t[ZIGZAG4[k]] as i16; + } + (dc, t, scan) + } + + /// Code chroma residual for prediction `pu`, `pv`; fills code/info and reconstruction. Returns chroma cbp. + pub(super) fn code_chroma(&self, src: &Src, pu: &[u8; 64], pv: &[u8; 64], qp: u8, intra: bool, c: &mut Cand) -> u8 { + let qt = self.f.qt; + let qpc = chroma_qp(qp as i32 + self.f.p.chroma_qp_offset); + let dz = if intra { Deadzone::INTRA } else { Deadzone::INTER }; + let mut dc_any = false; + let mut ac_any = false; + let mut coefs = [[[0i32; 16]; 4]; 2]; + let mut dcl = [[0i32; 4]; 2]; + for comp in 0..2 { + let (s, p) = if comp == 0 { (&src.u, pu) } else { (&src.v, pv) }; + let mut dc = [0i32; 4]; + let mut ac_score = 0; + for b in 0..4 { + let (dc_coef, t, scan) = self.quant_chroma_ac_block(s, p, b, qpc, dz); + dc[b] = dc_coef; + ac_score += decimate_score(&scan[1..]); + c.code.cac[comp * 4 + b] = scan; + coefs[comp][b] = t; + } + if !intra && self.f.p.decimate && ac_score < 7 { + for b in 0..4 { + c.code.cac[comp * 4 + b] = [0; 16]; + for k in 1..16 { + coefs[comp][b][k] = 0; + } + } + } + let (mut lv, _) = quant_chroma_dc(&dc, qt, qpc, dz); + if self.f.p.cavlc_clamp { + for v in lv.iter_mut() { + *v = (*v).clamp(-cavlc::MAX_LEVEL, cavlc::MAX_LEVEL); + } + } + dcl[comp] = lv; + for b in 0..4 { + c.code.cdc[comp][b] = lv[b] as i16; + } + dc_any |= lv.iter().any(|&v| v != 0); + ac_any |= c.code.cac[comp * 4..comp * 4 + 4].iter().any(|b| b.iter().any(|&v| v != 0)); + } + let cbp = if ac_any { + 2 + } else if dc_any { + 1 + } else { + 0 + }; + c.info.dc_cbf &= 1; + for comp in 0..2 { + let p = if comp == 0 { pu } else { pv }; + let dcq = dequant_chroma_dc(&dcl[comp], qt, qpc); + if dcl[comp].iter().any(|&v| v != 0) { + c.info.dc_cbf |= 2 << comp; + } + for b in 0..4 { + let (bx, by) = ((b & 1) * 4, (b >> 1) * 4); + let mut t = coefs[comp][b]; + let nz = if cbp == 2 { t.iter().skip(1).filter(|&&v| v != 0).count() as u8 } else { 0 }; + c.info.nnz_c[comp * 4 + b] = nz; + if cbp != 2 { + t = [0; 16]; + } else { + dequant4(&mut t, qt, qpc, true); + } + t[0] = dcq[b]; + let rec = if comp == 0 { &mut c.ru } else { &mut c.rv }; + if nz == 0 { + let dcv = (t[0] + 32) >> 6; + add_chroma_residual(rec, p, bx, by, |_, _| dcv); + } else { + let r = idct4(&t); + add_chroma_residual(rec, p, bx, by, |x, y| r[y * 4 + x]); + } + } + } + if cbp == 0 { + c.code.cdc = [[0; 4]; 2]; + } + if cbp != 2 { + c.code.cac = [[0; 16]; 8]; + } + cbp + } + + /// Inter luma residual with the 4x4 transform. Returns luma cbp. + fn code_luma4(&self, src: &[u8; 256], pred: &[u8; 256], qp: u8, dz: Deadzone, decimate: bool, c: &mut Cand) -> u8 { + let qt = self.f.qt; + let mut coefs = [[0i32; 16]; 16]; + let mut score8 = [0u32; 4]; + for blk in 0..16 { + let (bx, by) = BLK4_XY[blk]; + let mut d = [0i32; 16]; + for y in 0..4 { + for x in 0..4 { + let i = (by * 4 + y) * 16 + bx * 4 + x; + d[y * 4 + x] = src[i] as i32 - pred[i] as i32; + } + } + let mut t = fdct4(&d); + quant4(&mut t, qt, qp, dz, false); + if self.f.p.cavlc_clamp { + for v in t.iter_mut() { + *v = (*v).clamp(-cavlc::MAX_LEVEL, cavlc::MAX_LEVEL); + } + } + let mut scan = [0i16; 16]; + for k in 0..16 { + scan[k] = t[ZIGZAG4[k]] as i16; + } + score8[blk / 4] += decimate_score(&scan); + c.code.luma4[blk] = scan; + coefs[blk] = t; + } + let mut cbp = 0u8; + let total: u32 = score8.iter().sum(); + for b8 in 0..4 { + let kill = decimate && (score8[b8] < 4 || total < 6); + for i in 0..4 { + let blk = b8 * 4 + i; + if kill { + c.code.luma4[blk] = [0; 16]; + coefs[blk] = [0; 16]; + } + if c.code.luma4[blk].iter().any(|&v| v != 0) { + cbp |= 1 << b8; + } + } + } + for blk in 0..16 { + let (bx, by) = BLK4_XY[blk]; + let nz = c.code.luma4[blk].iter().filter(|&&v| v != 0).count() as u8; + c.info.nnz[by * 4 + bx] = nz; + let off = by * 4 * 16 + bx * 4; + if nz == 0 { + for y in 0..4 { + c.ry[off + y * 16..off + y * 16 + 4].copy_from_slice(&pred[off + y * 16..off + y * 16 + 4]); + } + } else { + let mut t = coefs[blk]; + dequant4(&mut t, qt, qp, false); + let r = idct4(&t); + for y in 0..4 { + for x in 0..4 { + let i = off + y * 16 + x; + c.ry[i] = (pred[i] as i32 + r[y * 4 + x]).clamp(0, 255) as u8; + } + } + } + } + cbp + } + + /// Inter luma residual with the 8x8 transform. Returns luma cbp. + fn code_luma8(&self, src: &[u8; 256], pred: &[u8; 256], qp: u8, dz: Deadzone, decimate: bool, c: &mut Cand) -> u8 { + let qt = self.f.qt; + let mut coefs = [[0i32; 64]; 4]; + let mut score = [0u32; 4]; + for b8 in 0..4 { + let (ox, oy) = ((b8 & 1) * 8, (b8 >> 1) * 8); + let mut d = [0i32; 64]; + for y in 0..8 { + for x in 0..8 { + let i = (oy + y) * 16 + ox + x; + d[y * 8 + x] = src[i] as i32 - pred[i] as i32; + } + } + let mut t = fdct8(&d); + quant8(&mut t, qt, qp, dz); + let mut scan = [0i16; 64]; + for k in 0..64 { + scan[k] = t[ZIGZAG8[k]] as i16; + } + score[b8] = decimate_score(&scan); + c.code.luma8[b8] = scan; + coefs[b8] = t; + } + let total: u32 = score.iter().sum(); + let mut cbp = 0; + for b8 in 0..4 { + let (ox, oy) = ((b8 & 1) * 8, (b8 >> 1) * 8); + if decimate && (score[b8] < 4 || total < 6) { + c.code.luma8[b8] = [0; 64]; + coefs[b8] = [0; 64]; + } + let nz = c.code.luma8[b8].iter().filter(|&&v| v != 0).count() as u8; + let (bx, by) = (ox / 4, oy / 4); + for (dx, dy) in [(0, 0), (1, 0), (0, 1), (1, 1)] { + c.info.nnz[(by + dy) * 4 + bx + dx] = nz; + } + if nz == 0 { + for y in 0..8 { + let o = (oy + y) * 16 + ox; + c.ry[o..o + 8].copy_from_slice(&pred[o..o + 8]); + } + continue; + } + cbp |= 1 << b8; + let mut t = coefs[b8]; + dequant8(&mut t, qt, qp); + let r = idct8(&t); + for y in 0..8 { + for x in 0..8 { + let i = (oy + y) * 16 + ox + x; + c.ry[i] = (pred[i] as i32 + r[y * 8 + x]).clamp(0, 255) as u8; + } + } + } + cbp + } + + /// Code an inter candidate's residual given its luma/chroma prediction. + pub(super) fn code_inter(&self, src: &Src, py: &[u8; 256], pu: &[u8; 64], pv: &[u8; 64], qp: u8, c: &mut Cand) { + let t8_allowed = self.f.p.t8x8; + let (use8, cbpl) = if t8_allowed { + if self.f.p.adaptive_t8 { + // Compare 4x4 and 8x8 transform by SSD + lambda * (approximate) bits. Neither + // coder reads the fields it writes, so the 4x4 trial is coded into `c` itself + // and the 8x8 trial into a copy that replaces `c` when it wins: the winner is + // not coded a second time. + let mut c8 = c.clone(); + let cbp8 = self.code_luma8(&src.y, py, qp, Deadzone::INTER, self.f.p.decimate, &mut c8); + let cbp4 = self.code_luma4(&src.y, py, qp, Deadzone::INTER, self.f.p.decimate, c); + let lam = self.f.lam.ssd[qp as usize]; + let lb = level_bits_table(); + let cost = |cc: &Cand, cbp: u8, eight: bool| { + let d = ssd(&src.y, 16, &cc.ry, 16, 16, 16) as f64; + let mut bits = 0f64; + if eight { + for b in 0..4 { + if (cbp >> b) & 1 != 0 { + bits += cc.code.luma8[b].iter().map(|&v| level_bits(lb, v)).sum::(); + } + } + } else { + for b in 0..16 { + if (cbp >> (b / 4)) & 1 != 0 { + bits += 1.0 + cc.code.luma4[b].iter().map(|&v| level_bits(lb, v)).sum::(); + } + } + } + d + lam * bits + }; + if cost(&c8, cbp8, true) <= cost(c, cbp4, false) { + *c = c8; + (true, cbp8) + } else { + (false, cbp4) + } + } else { + (true, self.code_luma8(&src.y, py, qp, Deadzone::INTER, self.f.p.decimate, c)) + } + } else { + (false, self.code_luma4(&src.y, py, qp, Deadzone::INTER, self.f.p.decimate, c)) + }; + c.code.t8x8 = use8 && cbpl != 0; + c.info.t8x8 = c.code.t8x8; + let cbpc = self.code_chroma(src, pu, pv, qp, false, c); + c.code.cbp = cbpl | (cbpc << 4); + } +} diff --git a/crates/h264enc/src/syntax.rs b/crates/h264enc/src/syntax.rs new file mode 100644 index 0000000000..c35d6bd9bf --- /dev/null +++ b/crates/h264enc/src/syntax.rs @@ -0,0 +1,120 @@ +//! Macroblock-layer syntax values produced by mode decision and consumed by the entropy coders. + +use crate::mbinfo::{MbInfo, MbKind, Mv, Part}; +use crate::nal::SliceType; + +#[derive(Clone)] +pub struct MbCode { + pub kind: MbKind, + pub part: Part, + /// Prediction direction per partition: 0 = L0, 1 = L1, 2 = Bi. + pub bdir: [u8; 4], + pub i16_mode: u8, + pub chroma_mode: u8, + /// Per 4x4 (or 8x8, first four entries) block in decoding order: -1 = use predicted mode, else rem_intra_pred_mode. + pub ipred: [i8; 16], + pub t8x8: bool, + /// mvd per list per partition. + pub mvd: [[Mv; 4]; 2], + pub cbp: u8, + pub qp_delta: i32, + /// Intra16x16 DC levels in zig-zag order. + pub luma_dc: [i16; 16], + /// Luma 4x4 levels by luma4x4BlkIdx, zig-zag order (index 0 unused for Intra16x16 AC). + pub luma4: [[i16; 16]; 16], + /// Luma 8x8 levels by 8x8 block, zig-zag order. + pub luma8: [[i16; 64]; 4], + /// Chroma DC levels (Cb, Cr) in raster (c0..c3) order. + pub cdc: [[i16; 4]; 2], + /// Chroma AC levels, Cb blocks 0..3 then Cr 0..3, zig-zag order (index 0 unused). + pub cac: [[i16; 16]; 8], +} + +impl Default for MbCode { + fn default() -> Self { + MbCode { + kind: MbKind::None, + part: Part::P16x16, + bdir: [0; 4], + i16_mode: 0, + chroma_mode: 0, + ipred: [-1; 16], + t8x8: false, + mvd: [[Mv::ZERO; 4]; 2], + cbp: 0, + qp_delta: 0, + luma_dc: [0; 16], + luma4: [[0; 16]; 16], + luma8: [[0; 64]; 4], + cdc: [[0; 4]; 2], + cac: [[0; 16]; 8], + } + } +} + +impl MbCode { + /// B macroblock type number (Table 7-14) for inter B macroblocks. + pub fn b_type_number(&self) -> u32 { + match self.kind { + MbKind::BDirect => 0, + _ => match self.part { + Part::P16x16 => 1 + self.bdir[0] as u32, + Part::P16x8 | Part::P8x16 => { + const PAIRS: [(u8, u8); 9] = [(0, 0), (1, 1), (0, 1), (1, 0), (0, 2), (1, 2), (2, 0), (2, 1), (2, 2)]; + let k = PAIRS.iter().position(|&p| p == (self.bdir[0], self.bdir[1])).unwrap_or(0) as u32; + 4 + 2 * k + (self.part == Part::P8x16) as u32 + } + Part::P8x8 => 22, + }, + } + } + pub fn uses_list(&self, part: usize, list: usize) -> bool { + match self.kind { + MbKind::PInter => list == 0, + MbKind::BInter => self.bdir[part] == 2 || self.bdir[part] as usize == list, + _ => false, + } + } +} + +/// Neighbour context for entropy coding one macroblock. +pub struct NbCtx<'a> { + pub left: Option<&'a MbInfo>, + pub top: Option<&'a MbInfo>, + pub cur: &'a MbInfo, + pub slice_type: SliceType, + pub prev_dqp_nz: bool, + pub t8x8_mode: bool, +} + +impl NbCtx<'_> { + /// coded_block_flag-style condition for an unavailable neighbour. + #[inline] + pub fn unavail_cbf(&self) -> u32 { + self.cur.kind.is_intra() as u32 + } + /// Luma 4x4 neighbour non-zero count (A = left when `left`, else B = above). Returns None if unavailable. + #[inline] + pub fn luma_nnz(&self, raster: usize, left: bool) -> Option { + let (x, y) = (raster & 3, raster >> 2); + if left { + if x > 0 { Some(self.cur.nnz[raster - 1]) } else { self.left.map(|m| m.nnz[y * 4 + 3]) } + } else if y > 0 { + Some(self.cur.nnz[raster - 4]) + } else { + self.top.map(|m| m.nnz[12 + x]) + } + } + /// Chroma AC neighbour count for component `c`, block raster `b` (2x2). + #[inline] + pub fn chroma_nnz(&self, c: usize, b: usize, left: bool) -> Option { + let (x, y) = (b & 1, b >> 1); + if left { + if x > 0 { Some(self.cur.nnz_c[c * 4 + b - 1]) } else { self.left.map(|m| m.nnz_c[c * 4 + y * 2 + 1]) } + } else if y > 0 { + Some(self.cur.nnz_c[c * 4 + b - 2]) + } else { + self.top.map(|m| m.nnz_c[c * 4 + 2 + x]) + } + } +} diff --git a/crates/h264enc/src/tables.rs b/crates/h264enc/src/tables.rs new file mode 100644 index 0000000000..2dde174ca9 --- /dev/null +++ b/crates/h264enc/src/tables.rs @@ -0,0 +1,1097 @@ +//! Normative constant tables from ITU-T H.264 (scans, CABAC state machine and initialisation values, +//! CAVLC code tables, quantisation/deblocking tables). + +/// 4x4 frame zig-zag scan: scan index -> raster index (y*4+x). +pub const ZIGZAG4: [usize; 16] = [0, 1, 4, 8, 5, 2, 3, 6, 9, 12, 13, 10, 7, 11, 14, 15]; + +/// 8x8 frame zig-zag scan: scan index -> raster index (y*8+x). +pub const ZIGZAG8: [usize; 64] = [ + 0, 1, 8, 16, 9, 2, 3, 10, 17, 24, 32, 25, 18, 11, 4, 5, 12, 19, 26, 33, 40, 48, 41, 34, 27, 20, 13, 6, 7, 14, 21, 28, 35, 42, 49, 56, 57, 50, 43, + 36, 29, 22, 15, 23, 30, 37, 44, 51, 58, 59, 52, 45, 38, 31, 39, 46, 53, 60, 61, 54, 47, 55, 62, 63, +]; + +/// Luma 4x4 block index (decoding order) -> (x, y) in 4x4 units within the macroblock. +pub const BLK4_XY: [(usize, usize); 16] = + [(0, 0), (1, 0), (0, 1), (1, 1), (2, 0), (3, 0), (2, 1), (3, 1), (0, 2), (1, 2), (0, 3), (1, 3), (2, 2), (3, 2), (2, 3), (3, 3)]; + +/// Raster 4x4 position (y*4+x) -> luma 4x4 block index. +pub const RASTER_TO_BLK4: [usize; 16] = [0, 1, 4, 5, 2, 3, 6, 7, 8, 9, 12, 13, 10, 11, 14, 15]; + +/// rangeTabLPS[pStateIdx][qCodIRangeIdx]. +pub const RANGE_LPS: [[u8; 4]; 64] = [ + [128, 176, 208, 240], + [128, 167, 197, 227], + [128, 158, 187, 216], + [123, 150, 178, 205], + [116, 142, 169, 195], + [111, 135, 160, 185], + [105, 128, 152, 175], + [100, 122, 144, 166], + [95, 116, 137, 158], + [90, 110, 130, 150], + [85, 104, 123, 142], + [81, 99, 117, 135], + [77, 94, 111, 128], + [73, 89, 105, 122], + [69, 85, 100, 116], + [66, 80, 95, 110], + [62, 76, 90, 104], + [59, 72, 86, 99], + [56, 69, 81, 94], + [53, 65, 77, 89], + [51, 62, 73, 85], + [48, 59, 69, 80], + [46, 56, 66, 76], + [43, 53, 63, 72], + [41, 50, 59, 69], + [39, 48, 56, 65], + [37, 45, 54, 62], + [35, 43, 51, 59], + [33, 41, 48, 56], + [32, 39, 46, 53], + [30, 37, 43, 50], + [29, 35, 41, 48], + [27, 33, 39, 45], + [26, 31, 37, 43], + [24, 30, 35, 41], + [23, 28, 33, 39], + [22, 27, 32, 37], + [21, 26, 30, 35], + [20, 24, 29, 33], + [19, 23, 27, 31], + [18, 22, 26, 30], + [17, 21, 25, 28], + [16, 20, 23, 27], + [15, 19, 22, 25], + [14, 18, 21, 24], + [14, 17, 20, 23], + [13, 16, 19, 22], + [12, 15, 18, 21], + [12, 14, 17, 20], + [11, 14, 16, 19], + [11, 13, 15, 18], + [10, 12, 15, 17], + [10, 12, 14, 16], + [9, 11, 13, 15], + [9, 11, 12, 14], + [8, 10, 12, 14], + [8, 9, 11, 13], + [7, 9, 11, 12], + [7, 9, 10, 12], + [7, 8, 10, 11], + [6, 8, 9, 11], + [6, 7, 9, 10], + [6, 7, 8, 9], + [2, 2, 2, 2], +]; + +/// transIdxLPS. +pub const TRANS_LPS: [u8; 64] = [ + 0, 0, 1, 2, 2, 4, 4, 5, 6, 7, 8, 9, 9, 11, 11, 12, 13, 13, 15, 15, 16, 16, 18, 18, 19, 19, 21, 21, 22, 22, 23, 24, 24, 25, 26, 26, 27, 27, 28, + 29, 29, 30, 30, 30, 31, 32, 32, 33, 33, 33, 34, 34, 35, 35, 35, 36, 36, 36, 37, 37, 37, 38, 38, 63, +]; + +/// Number of CABAC contexts we keep (0..=459 covers every frame-coded 4:2:0 syntax element). +pub const NUM_CTX: usize = 460; + +/// Context initialisation (m, n) for I slices. Field-coding contexts are left as zero (never used). +pub fn cabac_init_i() -> [(i8, i8); NUM_CTX] { + let mut t = [(0i8, 0i8); NUM_CTX]; + set(&mut t, 0, &COMMON_0_10); + set(&mut t, 60, &COMMON_60_69); + set(&mut t, 70, &I_70_104); + set(&mut t, 105, &I_105_165); + set(&mut t, 166, &I_166_226); + set(&mut t, 227, &I_227_275); + set(&mut t, 399, &I_399_401); + set(&mut t, 402, &I_402_435); + t +} + +/// Context initialisation (m, n) for P/B slices with cabac_init_idc = 0. +pub fn cabac_init_pb0() -> [(i8, i8); NUM_CTX] { + let mut t = [(0i8, 0i8); NUM_CTX]; + set(&mut t, 0, &COMMON_0_10); + set(&mut t, 11, &PB0_11_59); + set(&mut t, 60, &COMMON_60_69); + set(&mut t, 70, &PB0_70_104); + set(&mut t, 105, &PB0_105_165); + set(&mut t, 166, &PB0_166_226); + set(&mut t, 227, &PB0_227_275); + set(&mut t, 399, &PB0_399_401); + set(&mut t, 402, &PB0_402_435); + t +} + +fn set(t: &mut [(i8, i8); NUM_CTX], start: usize, v: &[(i8, i8)]) { + t[start..start + v.len()].copy_from_slice(v); +} + +const COMMON_0_10: [(i8, i8); 11] = [(20, -15), (2, 54), (3, 74), (20, -15), (2, 54), (3, 74), (-28, 127), (-23, 104), (-6, 53), (-1, 54), (7, 51)]; + +const COMMON_60_69: [(i8, i8); 10] = [(0, 41), (0, 63), (0, 63), (0, 63), (-9, 83), (4, 86), (0, 97), (-7, 72), (13, 41), (3, 62)]; + +const PB0_11_59: [(i8, i8); 49] = [ + // 11..23 P mb_skip / mb_type / sub_mb_type + (23, 33), + (23, 2), + (21, 0), + (1, 9), + (0, 49), + (-37, 118), + (5, 57), + (-13, 78), + (-11, 65), + (1, 62), + (12, 49), + (-4, 73), + (17, 50), + // 24..39 B mb_skip / mb_type / sub_mb_type + (18, 64), + (9, 43), + (29, 0), + (26, 67), + (16, 90), + (9, 104), + (-46, 127), + (-20, 104), + (1, 67), + (-13, 78), + (-11, 65), + (1, 62), + (-6, 86), + (-17, 95), + (-6, 61), + (9, 45), + // 40..53 mvd + (-3, 69), + (-6, 81), + (-11, 96), + (6, 55), + (7, 67), + (-5, 86), + (2, 88), + (0, 58), + (-3, 76), + (-10, 94), + (5, 54), + (4, 69), + (-3, 81), + (0, 88), + // 54..59 ref_idx + (-7, 67), + (-5, 74), + (-4, 74), + (-5, 80), + (-7, 72), + (1, 58), +]; + +const I_70_104: [(i8, i8); 35] = [ + (0, 11), + (1, 55), + (0, 69), + (-17, 127), + (-13, 102), + (0, 82), + (-7, 74), + (-21, 107), + (-27, 127), + (-31, 127), + (-24, 127), + (-18, 95), + (-27, 127), + (-21, 114), + (-30, 127), + (-17, 123), + (-12, 115), + (-16, 122), + (-11, 115), + (-12, 63), + (-2, 68), + (-15, 84), + (-13, 104), + (-3, 70), + (-8, 93), + (-10, 90), + (-30, 127), + (-1, 74), + (-6, 97), + (-7, 91), + (-20, 127), + (-4, 56), + (-5, 82), + (-7, 76), + (-22, 125), +]; + +const PB0_70_104: [(i8, i8); 35] = [ + (0, 45), + (-4, 78), + (-3, 96), + (-27, 126), + (-28, 98), + (-25, 101), + (-23, 67), + (-28, 82), + (-20, 94), + (-16, 83), + (-22, 110), + (-21, 91), + (-18, 102), + (-13, 93), + (-29, 127), + (-7, 92), + (-5, 89), + (-7, 96), + (-13, 108), + (-3, 46), + (-1, 65), + (-1, 57), + (-9, 93), + (-3, 74), + (-9, 92), + (-8, 87), + (-23, 126), + (5, 54), + (6, 60), + (6, 59), + (6, 69), + (-1, 48), + (0, 68), + (-4, 69), + (-8, 88), +]; + +const I_105_165: [(i8, i8); 61] = [ + (-7, 93), + (-11, 87), + (-3, 77), + (-5, 71), + (-4, 63), + (-4, 68), + (-12, 84), + (-7, 62), + (-7, 65), + (8, 61), + (5, 56), + (-2, 66), + (1, 64), + (0, 61), + (-2, 78), + (1, 50), + (7, 52), + (10, 35), + (0, 44), + (11, 38), + (1, 45), + (0, 46), + (5, 44), + (31, 17), + (1, 51), + (7, 50), + (28, 19), + (16, 33), + (14, 62), + (-13, 108), + (-15, 100), + (-13, 101), + (-13, 91), + (-12, 94), + (-10, 88), + (-16, 84), + (-10, 86), + (-7, 83), + (-13, 87), + (-19, 94), + (1, 70), + (0, 72), + (-5, 74), + (18, 59), + (-8, 102), + (-15, 100), + (0, 95), + (-4, 75), + (2, 72), + (-11, 75), + (-3, 71), + (15, 46), + (-13, 69), + (0, 62), + (0, 65), + (21, 37), + (-15, 72), + (9, 57), + (16, 54), + (0, 62), + (12, 72), +]; + +const PB0_105_165: [(i8, i8); 61] = [ + (-2, 85), + (-6, 78), + (-1, 75), + (-7, 77), + (2, 54), + (5, 50), + (-3, 68), + (1, 50), + (6, 42), + (-4, 81), + (1, 63), + (-4, 70), + (0, 67), + (2, 57), + (-2, 76), + (11, 35), + (4, 64), + (1, 61), + (11, 35), + (18, 25), + (12, 24), + (13, 29), + (13, 36), + (-10, 93), + (-7, 73), + (-2, 73), + (13, 46), + (9, 49), + (-7, 100), + (9, 53), + (2, 53), + (5, 53), + (-2, 61), + (0, 56), + (0, 56), + (-13, 63), + (-5, 60), + (-1, 62), + (4, 57), + (-6, 69), + (4, 57), + (14, 39), + (4, 51), + (13, 68), + (3, 64), + (1, 61), + (9, 63), + (7, 50), + (16, 39), + (5, 44), + (4, 52), + (11, 48), + (-5, 60), + (-1, 59), + (0, 59), + (22, 33), + (5, 44), + (14, 43), + (-1, 78), + (0, 60), + (9, 69), +]; + +const I_166_226: [(i8, i8); 61] = [ + (24, 0), + (15, 9), + (8, 25), + (13, 18), + (15, 9), + (13, 19), + (10, 37), + (12, 18), + (6, 29), + (20, 33), + (15, 30), + (4, 45), + (1, 58), + (0, 62), + (7, 61), + (12, 38), + (11, 45), + (15, 39), + (11, 42), + (13, 44), + (16, 45), + (12, 41), + (10, 49), + (30, 34), + (18, 42), + (10, 55), + (17, 51), + (17, 46), + (0, 89), + (26, -19), + (22, -17), + (26, -17), + (30, -25), + (28, -20), + (33, -23), + (37, -27), + (33, -23), + (40, -28), + (38, -17), + (33, -11), + (40, -15), + (41, -6), + (38, 1), + (41, 17), + (30, -6), + (27, 3), + (26, 22), + (37, -16), + (35, -4), + (38, -8), + (38, -3), + (37, 3), + (38, 5), + (42, 0), + (35, 16), + (39, 22), + (14, 48), + (27, 37), + (21, 60), + (12, 68), + (2, 97), +]; + +const PB0_166_226: [(i8, i8); 61] = [ + (11, 28), + (2, 40), + (3, 44), + (0, 49), + (0, 46), + (2, 44), + (2, 51), + (0, 47), + (4, 39), + (2, 62), + (6, 46), + (0, 54), + (3, 54), + (2, 58), + (4, 63), + (6, 51), + (6, 57), + (7, 53), + (6, 52), + (6, 55), + (11, 45), + (14, 36), + (8, 53), + (-1, 82), + (7, 55), + (-3, 78), + (15, 46), + (22, 31), + (-1, 84), + (25, 7), + (30, -7), + (28, 3), + (28, 4), + (32, 0), + (34, -1), + (30, 6), + (30, 6), + (32, 9), + (31, 19), + (26, 27), + (26, 30), + (37, 20), + (28, 34), + (17, 70), + (1, 67), + (5, 59), + (9, 67), + (16, 30), + (18, 32), + (18, 35), + (22, 29), + (24, 31), + (23, 38), + (18, 43), + (20, 41), + (11, 63), + (9, 59), + (9, 64), + (-1, 94), + (-2, 89), + (-9, 108), +]; + +const I_227_275: [(i8, i8); 49] = [ + (-3, 71), + (-6, 42), + (-5, 50), + (-3, 54), + (-2, 62), + (0, 58), + (1, 63), + (-2, 72), + (-1, 74), + (-9, 91), + (-5, 67), + (-5, 27), + (-3, 39), + (-2, 44), + (0, 46), + (-16, 64), + (-8, 68), + (-10, 78), + (-6, 77), + (-10, 86), + (-12, 92), + (-15, 55), + (-10, 60), + (-6, 62), + (-4, 65), + (-12, 73), + (-8, 76), + (-7, 80), + (-9, 88), + (-17, 110), + (-11, 97), + (-20, 84), + (-11, 79), + (-6, 73), + (-4, 74), + (-13, 86), + (-13, 96), + (-11, 97), + (-19, 117), + (-8, 78), + (-5, 33), + (-4, 48), + (-2, 53), + (-3, 62), + (-13, 71), + (-10, 79), + (-12, 86), + (-13, 90), + (-14, 97), +]; + +const PB0_227_275: [(i8, i8); 49] = [ + (-6, 76), + (-2, 44), + (0, 45), + (0, 52), + (-3, 64), + (-2, 59), + (-4, 70), + (-4, 75), + (-8, 82), + (-17, 102), + (-9, 77), + (3, 24), + (0, 42), + (0, 48), + (0, 55), + (-6, 59), + (-7, 71), + (-12, 83), + (-11, 87), + (-30, 119), + (1, 58), + (-3, 29), + (-1, 36), + (1, 38), + (2, 43), + (-6, 55), + (0, 58), + (0, 64), + (-3, 74), + (-10, 90), + (0, 70), + (-4, 29), + (5, 31), + (7, 42), + (1, 59), + (-2, 58), + (-3, 72), + (-3, 81), + (-11, 97), + (0, 58), + (8, 5), + (10, 14), + (14, 18), + (13, 27), + (2, 40), + (0, 58), + (-3, 70), + (-6, 79), + (-8, 85), +]; + +const I_399_401: [(i8, i8); 3] = [(31, 21), (31, 31), (25, 50)]; +const PB0_399_401: [(i8, i8); 3] = [(12, 40), (11, 51), (14, 59)]; + +const I_402_435: [(i8, i8); 34] = [ + (-17, 120), + (-20, 112), + (-18, 114), + (-11, 85), + (-15, 92), + (-14, 89), + (-26, 71), + (-15, 81), + (-14, 80), + (0, 68), + (-14, 70), + (-24, 56), + (-23, 68), + (-24, 50), + (-11, 74), + (23, -13), + (26, -13), + (40, -15), + (49, -14), + (44, 3), + (45, 6), + (44, 34), + (33, 54), + (19, 82), + (-3, 75), + (-1, 23), + (1, 34), + (1, 43), + (0, 54), + (-2, 55), + (0, 61), + (1, 64), + (0, 68), + (-9, 92), +]; + +const PB0_402_435: [(i8, i8); 34] = [ + (-4, 79), + (-7, 71), + (-5, 69), + (-9, 70), + (-8, 66), + (-10, 68), + (-19, 73), + (-12, 69), + (-16, 70), + (-15, 67), + (-20, 62), + (-19, 70), + (-16, 66), + (-22, 65), + (-20, 63), + (9, -2), + (26, -9), + (33, -9), + (39, -7), + (41, -2), + (45, 3), + (49, 9), + (45, 27), + (36, 59), + (-6, 66), + (-7, 35), + (-7, 42), + (-8, 45), + (-5, 48), + (-12, 56), + (-6, 60), + (-5, 62), + (-8, 66), + (-8, 76), +]; + +/// significant_coeff_flag ctxIdxInc for 8x8 frame-coded blocks (Table 9-43). +pub const SIG8X8_FRAME: [u8; 63] = [ + 0, 1, 2, 3, 4, 5, 5, 4, 4, 3, 3, 4, 4, 4, 5, 5, 4, 4, 4, 4, 3, 3, 6, 7, 7, 7, 8, 9, 10, 9, 8, 7, 7, 6, 11, 12, 13, 11, 6, 7, 8, 9, 14, 10, 9, 8, + 6, 11, 12, 13, 11, 6, 9, 14, 10, 9, 11, 12, 13, 11, 14, 10, 12, +]; + +/// last_significant_coeff_flag ctxIdxInc for 8x8 blocks (Table 9-43). +pub const LAST8X8: [u8; 63] = [ + 0, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 3, 3, 3, 3, 3, 3, 3, 3, 4, 4, 4, 4, 4, 4, 4, 4, + 5, 5, 5, 5, 6, 6, 6, 6, 7, 7, 7, 7, 8, 8, 8, +]; + +// ---------------------------------------------------------------- CAVLC + +/// coeff_token (length, code) indexed [table][total_coeff][trailing_ones] for nC ranges 0..2, 2..4, 4..8. +pub const COEFF_TOKEN_LEN: [[[u8; 4]; 17]; 3] = [ + [ + [1, 0, 0, 0], + [6, 2, 0, 0], + [8, 6, 3, 0], + [9, 8, 7, 5], + [10, 9, 8, 6], + [11, 10, 9, 7], + [13, 11, 10, 8], + [13, 13, 11, 9], + [13, 13, 13, 10], + [14, 14, 13, 11], + [14, 14, 14, 13], + [15, 15, 14, 14], + [15, 15, 15, 14], + [16, 15, 15, 15], + [16, 16, 16, 15], + [16, 16, 16, 16], + [16, 16, 16, 16], + ], + [ + [2, 0, 0, 0], + [6, 2, 0, 0], + [6, 5, 3, 0], + [7, 6, 6, 4], + [8, 6, 6, 4], + [8, 7, 7, 5], + [9, 8, 8, 6], + [11, 9, 9, 6], + [11, 11, 11, 7], + [12, 11, 11, 9], + [12, 12, 12, 11], + [12, 12, 12, 11], + [13, 13, 13, 12], + [13, 13, 13, 13], + [13, 14, 13, 13], + [14, 14, 14, 13], + [14, 14, 14, 14], + ], + [ + [4, 0, 0, 0], + [6, 4, 0, 0], + [6, 5, 4, 0], + [6, 5, 5, 4], + [7, 5, 5, 4], + [7, 5, 5, 4], + [7, 6, 6, 4], + [7, 6, 6, 4], + [8, 7, 7, 5], + [8, 8, 7, 6], + [9, 8, 8, 7], + [9, 9, 8, 8], + [9, 9, 9, 8], + [10, 9, 9, 9], + [10, 10, 10, 10], + [10, 10, 10, 10], + [10, 10, 10, 10], + ], +]; + +pub const COEFF_TOKEN_CODE: [[[u8; 4]; 17]; 3] = [ + [ + [1, 0, 0, 0], + [5, 1, 0, 0], + [7, 4, 1, 0], + [7, 6, 5, 3], + [7, 6, 5, 3], + [7, 6, 5, 4], + [15, 6, 5, 4], + [11, 14, 5, 4], + [8, 10, 13, 4], + [15, 14, 9, 4], + [11, 10, 13, 12], + [15, 14, 9, 12], + [11, 10, 13, 8], + [15, 1, 9, 12], + [11, 14, 13, 8], + [7, 10, 9, 12], + [4, 6, 5, 8], + ], + [ + [3, 0, 0, 0], + [11, 2, 0, 0], + [7, 7, 3, 0], + [7, 10, 9, 5], + [7, 6, 5, 4], + [4, 6, 5, 6], + [7, 6, 5, 8], + [15, 6, 5, 4], + [11, 14, 13, 4], + [15, 10, 9, 4], + [11, 14, 13, 12], + [8, 10, 9, 8], + [15, 14, 13, 12], + [11, 10, 9, 12], + [7, 11, 6, 8], + [9, 8, 10, 1], + [7, 6, 5, 4], + ], + [ + [15, 0, 0, 0], + [15, 14, 0, 0], + [11, 15, 13, 0], + [8, 12, 14, 12], + [15, 10, 11, 11], + [11, 8, 9, 10], + [9, 14, 13, 9], + [8, 10, 9, 8], + [15, 14, 13, 13], + [11, 14, 10, 12], + [15, 10, 13, 12], + [11, 14, 9, 12], + [8, 10, 13, 8], + [13, 7, 9, 12], + [9, 12, 11, 10], + [5, 8, 7, 6], + [1, 4, 3, 2], + ], +]; + +/// Chroma DC (nC = -1) coeff_token [total_coeff][trailing_ones]. +pub const COEFF_TOKEN_CDC_LEN: [[u8; 4]; 5] = [[2, 0, 0, 0], [6, 1, 0, 0], [6, 6, 3, 0], [6, 7, 7, 6], [6, 8, 8, 7]]; +pub const COEFF_TOKEN_CDC_CODE: [[u8; 4]; 5] = [[1, 0, 0, 0], [7, 1, 0, 0], [4, 6, 1, 0], [3, 3, 2, 5], [2, 3, 2, 0]]; + +/// total_zeros for 4x4 blocks: [total_coeff - 1][total_zeros] = (len, code). +pub const TOTAL_ZEROS: [[(u8, u8); 16]; 15] = [ + [(1, 1), (3, 3), (3, 2), (4, 3), (4, 2), (5, 3), (5, 2), (6, 3), (6, 2), (7, 3), (7, 2), (8, 3), (8, 2), (9, 3), (9, 2), (9, 1)], + [(3, 7), (3, 6), (3, 5), (3, 4), (3, 3), (4, 5), (4, 4), (4, 3), (4, 2), (5, 3), (5, 2), (6, 3), (6, 2), (6, 1), (6, 0), (0, 0)], + [(4, 5), (3, 7), (3, 6), (3, 5), (4, 4), (4, 3), (3, 4), (3, 3), (4, 2), (5, 3), (5, 2), (6, 1), (5, 1), (6, 0), (0, 0), (0, 0)], + [(5, 3), (3, 7), (4, 5), (4, 4), (3, 6), (3, 5), (3, 4), (4, 3), (3, 3), (4, 2), (5, 2), (5, 1), (5, 0), (0, 0), (0, 0), (0, 0)], + [(4, 5), (4, 4), (4, 3), (3, 7), (3, 6), (3, 5), (3, 4), (3, 3), (4, 2), (5, 1), (4, 1), (5, 0), (0, 0), (0, 0), (0, 0), (0, 0)], + [(6, 1), (5, 1), (3, 7), (3, 6), (3, 5), (3, 4), (3, 3), (3, 2), (4, 1), (3, 1), (6, 0), (0, 0), (0, 0), (0, 0), (0, 0), (0, 0)], + [(6, 1), (5, 1), (3, 5), (3, 4), (3, 3), (2, 3), (3, 2), (4, 1), (3, 1), (6, 0), (0, 0), (0, 0), (0, 0), (0, 0), (0, 0), (0, 0)], + [(6, 1), (4, 1), (5, 1), (3, 3), (2, 3), (2, 2), (3, 2), (3, 1), (6, 0), (0, 0), (0, 0), (0, 0), (0, 0), (0, 0), (0, 0), (0, 0)], + [(6, 1), (6, 0), (4, 1), (2, 3), (2, 2), (3, 1), (2, 1), (5, 1), (0, 0), (0, 0), (0, 0), (0, 0), (0, 0), (0, 0), (0, 0), (0, 0)], + [(5, 1), (5, 0), (3, 1), (2, 3), (2, 2), (2, 1), (4, 1), (0, 0), (0, 0), (0, 0), (0, 0), (0, 0), (0, 0), (0, 0), (0, 0), (0, 0)], + [(4, 0), (4, 1), (3, 1), (3, 2), (1, 1), (3, 3), (0, 0), (0, 0), (0, 0), (0, 0), (0, 0), (0, 0), (0, 0), (0, 0), (0, 0), (0, 0)], + [(4, 0), (4, 1), (2, 1), (1, 1), (3, 1), (0, 0), (0, 0), (0, 0), (0, 0), (0, 0), (0, 0), (0, 0), (0, 0), (0, 0), (0, 0), (0, 0)], + [(3, 0), (3, 1), (1, 1), (2, 1), (0, 0), (0, 0), (0, 0), (0, 0), (0, 0), (0, 0), (0, 0), (0, 0), (0, 0), (0, 0), (0, 0), (0, 0)], + [(2, 0), (2, 1), (1, 1), (0, 0), (0, 0), (0, 0), (0, 0), (0, 0), (0, 0), (0, 0), (0, 0), (0, 0), (0, 0), (0, 0), (0, 0), (0, 0)], + [(1, 0), (1, 1), (0, 0), (0, 0), (0, 0), (0, 0), (0, 0), (0, 0), (0, 0), (0, 0), (0, 0), (0, 0), (0, 0), (0, 0), (0, 0), (0, 0)], +]; + +/// total_zeros for 2x2 chroma DC: [total_coeff - 1][total_zeros]. +pub const TOTAL_ZEROS_CDC: [[(u8, u8); 4]; 3] = + [[(1, 1), (2, 1), (3, 1), (3, 0)], [(1, 1), (2, 1), (2, 0), (0, 0)], [(1, 1), (1, 0), (0, 0), (0, 0)]]; + +/// run_before: [min(zeros_left, 7) - 1][run_before] = (len, code). +pub const RUN_BEFORE: [[(u8, u8); 15]; 7] = [ + [(1, 1), (1, 0), (0, 0), (0, 0), (0, 0), (0, 0), (0, 0), (0, 0), (0, 0), (0, 0), (0, 0), (0, 0), (0, 0), (0, 0), (0, 0)], + [(1, 1), (2, 1), (2, 0), (0, 0), (0, 0), (0, 0), (0, 0), (0, 0), (0, 0), (0, 0), (0, 0), (0, 0), (0, 0), (0, 0), (0, 0)], + [(2, 3), (2, 2), (2, 1), (2, 0), (0, 0), (0, 0), (0, 0), (0, 0), (0, 0), (0, 0), (0, 0), (0, 0), (0, 0), (0, 0), (0, 0)], + [(2, 3), (2, 2), (2, 1), (3, 1), (3, 0), (0, 0), (0, 0), (0, 0), (0, 0), (0, 0), (0, 0), (0, 0), (0, 0), (0, 0), (0, 0)], + [(2, 3), (2, 2), (3, 3), (3, 2), (3, 1), (3, 0), (0, 0), (0, 0), (0, 0), (0, 0), (0, 0), (0, 0), (0, 0), (0, 0), (0, 0)], + [(2, 3), (3, 0), (3, 1), (3, 3), (3, 2), (3, 5), (3, 4), (0, 0), (0, 0), (0, 0), (0, 0), (0, 0), (0, 0), (0, 0), (0, 0)], + [(3, 7), (3, 6), (3, 5), (3, 4), (3, 3), (3, 2), (3, 1), (4, 1), (5, 1), (6, 1), (7, 1), (8, 1), (9, 1), (10, 1), (11, 1)], +]; + +/// coded_block_pattern -> codeNum for Intra_4x4/8x8 macroblocks (ChromaArrayType 1/2). +pub const CBP_INTRA_TO_CODE: [u8; 48] = invert_cbp(&CODE_TO_CBP_INTRA); +/// coded_block_pattern -> codeNum for inter macroblocks. +pub const CBP_INTER_TO_CODE: [u8; 48] = invert_cbp(&CODE_TO_CBP_INTER); + +const CODE_TO_CBP_INTRA: [u8; 48] = [ + 47, 31, 15, 0, 23, 27, 29, 30, 7, 11, 13, 14, 39, 43, 45, 46, 16, 3, 5, 10, 12, 19, 21, 26, 28, 35, 37, 42, 44, 1, 2, 4, 8, 17, 18, 20, 24, 6, 9, + 22, 25, 32, 33, 34, 36, 40, 38, 41, +]; +const CODE_TO_CBP_INTER: [u8; 48] = [ + 0, 16, 1, 2, 4, 8, 32, 3, 5, 10, 12, 15, 47, 7, 11, 13, 14, 6, 9, 31, 35, 37, 42, 44, 33, 34, 36, 40, 39, 43, 45, 46, 17, 18, 20, 24, 19, 21, 26, + 28, 23, 27, 29, 30, 22, 25, 38, 41, +]; + +const fn invert_cbp(t: &[u8; 48]) -> [u8; 48] { + let mut out = [0u8; 48]; + let mut i = 0; + while i < 48 { + out[t[i] as usize] = i as u8; + i += 1; + } + out +} + +// ---------------------------------------------------------------- quantisation + +/// Forward quantisation multipliers for 4x4 (position classes: even/even, odd/odd, other). +pub const QUANT4: [[i32; 3]; 6] = + [[13107, 5243, 8066], [11916, 4660, 7490], [10082, 4194, 6554], [9362, 3647, 5825], [8192, 3355, 5243], [7282, 2893, 4559]]; + +/// normAdjust4x4 (v) values. +pub const DEQUANT4: [[i32; 3]; 6] = [[10, 16, 13], [11, 18, 14], [13, 20, 16], [14, 23, 18], [16, 25, 20], [18, 29, 23]]; + +/// normAdjust8x8 (v) values. +pub const DEQUANT8: [[i32; 6]; 6] = [ + [20, 18, 32, 19, 25, 24], + [22, 19, 35, 21, 28, 26], + [26, 23, 42, 24, 33, 31], + [28, 25, 45, 26, 35, 33], + [32, 28, 51, 30, 40, 38], + [36, 32, 58, 34, 46, 43], +]; + +#[inline] +pub fn pos_class4(i: usize) -> usize { + let (x, y) = (i & 3, i >> 2); + if x % 2 == 0 && y % 2 == 0 { + 0 + } else if x % 2 == 1 && y % 2 == 1 { + 1 + } else { + 2 + } +} + +#[inline] +pub fn pos_class8(i: usize) -> usize { + let (x, y) = (i & 7, i >> 3); + if x % 4 == 0 && y % 4 == 0 { + 0 + } else if x % 2 == 1 && y % 2 == 1 { + 1 + } else if x % 4 == 2 && y % 4 == 2 { + 2 + } else if (x % 4 == 0 && y % 2 == 1) || (x % 2 == 1 && y % 4 == 0) { + 3 + } else if (x % 4 == 0 && y % 4 == 2) || (x % 4 == 2 && y % 4 == 0) { + 4 + } else { + 5 + } +} + +/// QPc as a function of qPI (Table 8-15). +pub fn chroma_qp(qpi: i32) -> u8 { + const T: [u8; 22] = [29, 30, 31, 32, 32, 33, 34, 34, 35, 35, 36, 36, 37, 37, 37, 38, 38, 38, 39, 39, 39, 39]; + let q = qpi.clamp(0, 51); + if q < 30 { q as u8 } else { T[(q - 30) as usize] } +} + +// ---------------------------------------------------------------- deblocking + +pub const ALPHA: [u8; 52] = [ + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 4, 4, 5, 6, 7, 8, 9, 10, 12, 13, 15, 17, 20, 22, 25, 28, 32, 36, 40, 45, 50, 56, 63, 71, 80, 90, + 101, 113, 127, 144, 162, 182, 203, 226, 255, 255, +]; +pub const BETA: [u8; 52] = [ + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 2, 2, 2, 3, 3, 3, 3, 4, 4, 4, 6, 6, 7, 7, 8, 8, 9, 9, 10, 10, 11, 11, 12, 12, 13, 13, 14, 14, 15, + 15, 16, 16, 17, 17, 18, 18, +]; +pub const TC0: [[u8; 3]; 52] = [ + [0, 0, 0], + [0, 0, 0], + [0, 0, 0], + [0, 0, 0], + [0, 0, 0], + [0, 0, 0], + [0, 0, 0], + [0, 0, 0], + [0, 0, 0], + [0, 0, 0], + [0, 0, 0], + [0, 0, 0], + [0, 0, 0], + [0, 0, 0], + [0, 0, 0], + [0, 0, 0], + [0, 0, 0], + [0, 0, 1], + [0, 0, 1], + [0, 0, 1], + [0, 0, 1], + [0, 1, 1], + [0, 1, 1], + [1, 1, 1], + [1, 1, 1], + [1, 1, 1], + [1, 1, 1], + [1, 1, 2], + [1, 1, 2], + [1, 1, 2], + [1, 1, 2], + [1, 2, 3], + [1, 2, 3], + [2, 2, 3], + [2, 2, 4], + [2, 3, 4], + [2, 3, 4], + [3, 3, 5], + [3, 4, 6], + [3, 4, 6], + [4, 5, 7], + [4, 5, 8], + [4, 6, 9], + [5, 7, 10], + [6, 8, 11], + [6, 8, 13], + [7, 10, 14], + [8, 11, 16], + [9, 12, 18], + [10, 13, 20], + [11, 15, 23], + [13, 17, 25], +]; + +#[cfg(test)] +mod tests { + use super::*; + + #[test] + fn cbp_tables_are_permutations() { + let mut seen = [false; 48]; + for &c in &CBP_INTRA_TO_CODE { + seen[c as usize] = true; + } + assert!(seen.iter().all(|&s| s)); + let mut seen = [false; 48]; + for &c in &CBP_INTER_TO_CODE { + seen[c as usize] = true; + } + assert!(seen.iter().all(|&s| s)); + } + + #[test] + fn scans_are_permutations() { + let mut s = ZIGZAG8; + s.sort(); + assert!(s.iter().enumerate().all(|(i, &v)| i == v)); + let mut s = ZIGZAG4; + s.sort(); + assert!(s.iter().enumerate().all(|(i, &v)| i == v)); + for (b, &(x, y)) in BLK4_XY.iter().enumerate() { + assert_eq!(RASTER_TO_BLK4[y * 4 + x], b); + } + } + + /// Every VLC table must be prefix-free (a cheap consistency check on transcription). + #[test] + fn vlc_tables_prefix_free() { + fn check(codes: &[(u8, u32)]) { + for (i, &(la, ca)) in codes.iter().enumerate() { + for (j, &(lb, cb)) in codes.iter().enumerate() { + if i == j || la == 0 || lb == 0 || la > lb { + continue; + } + assert!(cb >> (lb - la) != ca, "prefix clash {la}:{ca:b} vs {lb}:{cb:b}"); + } + } + } + for t in 0..3 { + let mut v = vec![]; + for tc in 0..17 { + for t1 in 0..4 { + if t1 <= tc && COEFF_TOKEN_LEN[t][tc][t1] > 0 { + v.push((COEFF_TOKEN_LEN[t][tc][t1], COEFF_TOKEN_CODE[t][tc][t1] as u32)); + } + } + } + assert_eq!(v.len(), 62); + check(&v); + } + let mut v = vec![]; + for tc in 0..5 { + for t1 in 0..4 { + if t1 <= tc && COEFF_TOKEN_CDC_LEN[tc][t1] > 0 { + v.push((COEFF_TOKEN_CDC_LEN[tc][t1], COEFF_TOKEN_CDC_CODE[tc][t1] as u32)); + } + } + } + assert_eq!(v.len(), 14); + check(&v); + for (tc, row) in TOTAL_ZEROS.iter().enumerate() { + let v: Vec<_> = row.iter().take(16 - tc).map(|&(l, c)| (l, c as u32)).collect(); + check(&v); + } + for (i, row) in RUN_BEFORE.iter().enumerate() { + let n = if i < 6 { i + 2 } else { 15 }; + let v: Vec<_> = row.iter().take(n).map(|&(l, c)| (l, c as u32)).collect(); + check(&v); + } + } +} diff --git a/crates/h264enc/src/transform.rs b/crates/h264enc/src/transform.rs new file mode 100644 index 0000000000..943b70187a --- /dev/null +++ b/crates/h264enc/src/transform.rs @@ -0,0 +1,523 @@ +//! Integer transforms, quantisation and the normative inverse transforms/scaling (§8.5.12). + +use crate::tables::{DEQUANT4, DEQUANT8, QUANT4, pos_class4, pos_class8}; + +/// Forward 4x4 core transform of a residual block (raster order). +#[inline] +pub fn fdct4(d: &[i32; 16]) -> [i32; 16] { + let mut t = [0i32; 16]; + for i in 0..4 { + let (a, b, c, e) = (d[i * 4], d[i * 4 + 1], d[i * 4 + 2], d[i * 4 + 3]); + let s03 = a + e; + let d03 = a - e; + let s12 = b + c; + let d12 = b - c; + t[i * 4] = s03 + s12; + t[i * 4 + 1] = 2 * d03 + d12; + t[i * 4 + 2] = s03 - s12; + t[i * 4 + 3] = d03 - 2 * d12; + } + let mut o = [0i32; 16]; + for i in 0..4 { + let (a, b, c, e) = (t[i], t[4 + i], t[8 + i], t[12 + i]); + let s03 = a + e; + let d03 = a - e; + let s12 = b + c; + let d12 = b - c; + o[i] = s03 + s12; + o[4 + i] = 2 * d03 + d12; + o[8 + i] = s03 - s12; + o[12 + i] = d03 - 2 * d12; + } + o +} + +/// Normative inverse 4x4 transform of scaled coefficients `d` (raster), returns residual r (after the >>6). +#[inline] +pub fn idct4(d: &[i32; 16]) -> [i32; 16] { + let mut f = [0i32; 16]; + for i in 0..4 { + let (d0, d1, d2, d3) = (d[i * 4], d[i * 4 + 1], d[i * 4 + 2], d[i * 4 + 3]); + let e = d0 + d2; + let ff = d0 - d2; + let g = (d1 >> 1) - d3; + let h = d1 + (d3 >> 1); + f[i * 4] = e + h; + f[i * 4 + 1] = ff + g; + f[i * 4 + 2] = ff - g; + f[i * 4 + 3] = e - h; + } + let mut r = [0i32; 16]; + for j in 0..4 { + let (f0, f1, f2, f3) = (f[j], f[4 + j], f[8 + j], f[12 + j]); + let e = f0 + f2; + let ff = f0 - f2; + let g = (f1 >> 1) - f3; + let h = f1 + (f3 >> 1); + r[j] = (e + h + 32) >> 6; + r[4 + j] = (ff + g + 32) >> 6; + r[8 + j] = (ff - g + 32) >> 6; + r[12 + j] = (e - h + 32) >> 6; + } + r +} + +#[inline(always)] +fn fdct8_1d(s: [i32; 8]) -> [i32; 8] { + let a0 = s[0] + s[7]; + let a1 = s[1] + s[6]; + let a2 = s[2] + s[5]; + let a3 = s[3] + s[4]; + let b0 = s[0] - s[7]; + let b1 = s[1] - s[6]; + let b2 = s[2] - s[5]; + let b3 = s[3] - s[4]; + let e0 = a0 + a3; + let e1 = a1 + a2; + let e2 = a0 - a3; + let e3 = a1 - a2; + [ + 8 * (e0 + e1), + 12 * b0 + 10 * b1 + 6 * b2 + 3 * b3, + 8 * e2 + 4 * e3, + 10 * b0 - 3 * b1 - 12 * b2 - 6 * b3, + 8 * (e0 - e1), + 6 * b0 - 12 * b1 + 3 * b2 + 10 * b3, + 4 * e2 - 8 * e3, + 3 * b0 - 6 * b1 + 10 * b2 - 12 * b3, + ] +} + +/// Forward 8x8 transform X = A x A^T (unnormalised; rows of A are the H.264 8x8 basis scaled by 8). +pub fn fdct8(d: &[i32; 64]) -> [i32; 64] { + let mut t = [0i32; 64]; + for (y, row) in d.as_chunks::<8>().0.iter().enumerate() { + let o = fdct8_1d(*row); + t[y * 8..y * 8 + 8].copy_from_slice(&o); + } + let mut out = [0i32; 64]; + for x in 0..8 { + let col = [t[x], t[8 + x], t[16 + x], t[24 + x], t[32 + x], t[40 + x], t[48 + x], t[56 + x]]; + let o = fdct8_1d(col); + for k in 0..8 { + out[k * 8 + x] = o[k]; + } + } + out +} + +#[cfg(test)] +const A8: [[i32; 8]; 8] = [ + [8, 8, 8, 8, 8, 8, 8, 8], + [12, 10, 6, 3, -3, -6, -10, -12], + [8, 4, -4, -8, -8, -4, 4, 8], + [10, -3, -12, -6, 6, 12, 3, -10], + [8, -8, -8, 8, 8, -8, -8, 8], + [6, -12, 3, 10, -10, -3, 12, -6], + [4, -8, 8, -4, -4, 8, -8, 4], + [3, -6, 10, -12, 12, -10, 6, -3], +]; + +/// Normative inverse 8x8 transform (§8.5.13), returns residual after (+32)>>6. +pub fn idct8(d: &[i32; 64]) -> [i32; 64] { + fn one(s: [i32; 8]) -> [i32; 8] { + let a0 = s[0] + s[4]; + let a4 = s[0] - s[4]; + let a2 = (s[2] >> 1) - s[6]; + let a6 = s[2] + (s[6] >> 1); + let b0 = a0 + a6; + let b2 = a4 + a2; + let b4 = a4 - a2; + let b6 = a0 - a6; + let a1 = -s[3] + s[5] - s[7] - (s[7] >> 1); + let a3 = s[1] + s[7] - s[3] - (s[3] >> 1); + let a5 = -s[1] + s[7] + s[5] + (s[5] >> 1); + let a7 = s[3] + s[5] + s[1] + (s[1] >> 1); + let b1 = a1 + (a7 >> 2); + let b7 = a7 - (a1 >> 2); + let b3 = a3 + (a5 >> 2); + let b5 = (a3 >> 2) - a5; + [b0 + b7, b2 + b5, b4 + b3, b6 + b1, b6 - b1, b4 - b3, b2 - b5, b0 - b7] + } + let mut g = [0i32; 64]; + for (i, row) in d.as_chunks::<8>().0.iter().enumerate() { + let o = one(*row); + g[i * 8..i * 8 + 8].copy_from_slice(&o); + } + let mut r = [0i32; 64]; + for j in 0..8 { + let col = [g[j], g[8 + j], g[16 + j], g[24 + j], g[32 + j], g[40 + j], g[48 + j], g[56 + j]]; + let o = one(col); + for i in 0..8 { + r[i * 8 + j] = (o[i] + 32) >> 6; + } + } + r +} + +/// Per-QP quantiser tables. +pub struct QuantTables { + /// mf4[qp%6][pos] + pub mf4: [[u32; 16]; 6], + pub v4: [[i32; 16]; 6], + pub mf8: [[u32; 64]; 6], + pub v8: [[i32; 64]; 6], +} + +impl QuantTables { + pub fn new() -> Self { + let mut mf4 = [[0; 16]; 6]; + let mut v4 = [[0; 16]; 6]; + let mut mf8 = [[0; 64]; 6]; + let mut v8 = [[0; 64]; 6]; + let norm8 = [512.0, 578.0, 320.0, 578.0, 512.0, 578.0, 320.0, 578.0f64]; + for q in 0..6 { + for i in 0..16 { + mf4[q][i] = QUANT4[q][pos_class4(i)] as u32; + v4[q][i] = DEQUANT4[q][pos_class4(i)]; + } + for i in 0..64 { + let v = DEQUANT8[q][pos_class8(i)]; + v8[q][i] = v; + let (x, y) = (i & 7, i >> 3); + mf8[q][i] = ((1u64 << 36) as f64 / (norm8[x] * norm8[y] * v as f64)).round() as u32; + } + } + QuantTables { mf4, v4, mf8, v8 } + } +} + +impl Default for QuantTables { + fn default() -> Self { + Self::new() + } +} + +/// Deadzone rounding fractions (of 2^qbits), as numerator over 6*... we express as shift-based fractions. +#[derive(Clone, Copy)] +pub struct Deadzone { + /// rounding offset as a fraction of one quantisation step, in 1/256 units. + pub f256: i64, +} + +impl Deadzone { + pub const INTRA: Deadzone = Deadzone { f256: 85 }; // ~1/3 + pub const INTER: Deadzone = Deadzone { f256: 43 }; // ~1/6 +} + +/// Quantise a 4x4 block of transform coefficients (raster) in place into levels; returns number of non-zeros. +/// `skip_dc` leaves coefficient 0 untouched (for Intra16x16/chroma AC where DC is coded separately). +#[inline] +pub fn quant4(c: &mut [i32; 16], qt: &QuantTables, qp: u8, dz: Deadzone, skip_dc: bool) -> u32 { + let qbits = 15 + (qp / 6) as u32; + let f = (((1i64 << qbits) * dz.f256) >> 8) as u32; + let mf = &qt.mf4[(qp % 6) as usize]; + let dc = c[0]; + let mut nz = 0; + for (v, &m) in c.iter_mut().zip(mf) { + // Residual coefficients stay below 2^14; the clamp keeps the u32 product from overflowing on any input. + let a = ((v.unsigned_abs().min(0xffff) * m + f) >> qbits) as i32; + nz += (a != 0) as u32; + *v = if *v < 0 { -a } else { a }; + } + if skip_dc { + nz -= (c[0] != 0) as u32; + c[0] = dc; + } + nz +} + +/// Dequantise 4x4 levels (raster) for flat scaling matrices (§8.5.12.1). `skip_dc` leaves c[0] unchanged. +#[inline] +pub fn dequant4(c: &mut [i32; 16], qt: &QuantTables, qp: u8, skip_dc: bool) { + let v = &qt.v4[(qp % 6) as usize]; + let sh = (qp / 6) as u32; + let start = if skip_dc { 1 } else { 0 }; + for i in start..16 { + // LevelScale = 16*v; qP>=24: (c*LS) << (qP/6-4); else (c*LS + 2^(3-qP/6)) >> (4-qP/6) == c*v << qP/6. + c[i] = (c[i] * v[i]) << sh; + } +} + +/// Quantise an 8x8 block (raster), returns non-zero count. +pub fn quant8(c: &mut [i32; 64], qt: &QuantTables, qp: u8, dz: Deadzone) -> u32 { + let qbits = 22 + (qp / 6) as u32; + let f = (((1i64 << qbits) * dz.f256) >> 8) as u32; + let mf = &qt.mf8[(qp % 6) as usize]; + let mut nz = 0; + for (v, &m) in c.iter_mut().zip(mf) { + // |v| * m needs up to 36 bits. Splitting |v| at bit 8 keeps both partial products in u32, and + // (256 * hi + lo) >> qbits == (hi + (lo >> 8)) >> (qbits - 8) keeps the result exact. + // Residual coefficients stay below 2^21; the clamp keeps the sums from overflowing on any input. + let x = v.unsigned_abs().min(0xff_ffff); + let a = (((x >> 8) * m + (((x & 0xff) * m + f) >> 8)) >> (qbits - 8)) as i32; + nz += (a != 0) as u32; + *v = if *v < 0 { -a } else { a }; + } + nz +} + +/// Dequantise 8x8 levels (raster), flat scaling. +pub fn dequant8(c: &mut [i32; 64], qt: &QuantTables, qp: u8) { + let v = &qt.v8[(qp % 6) as usize]; + let q6 = (qp / 6) as i32; + for i in 0..64 { + let ls = 16 * v[i]; + c[i] = if q6 >= 6 { (c[i] * ls) << (q6 - 6) } else { (c[i] * ls + (1 << (5 - q6))) >> (6 - q6) }; + } +} + +/// 4x4 Hadamard used for the Intra16x16 luma DC (unnormalised). +#[inline] +pub fn hadamard4(d: &[i32; 16]) -> [i32; 16] { + let mut t = [0i32; 16]; + for i in 0..4 { + let (a, b, c, e) = (d[i * 4], d[i * 4 + 1], d[i * 4 + 2], d[i * 4 + 3]); + t[i * 4] = a + b + c + e; + t[i * 4 + 1] = a + b - c - e; + t[i * 4 + 2] = a - b - c + e; + t[i * 4 + 3] = a - b + c - e; + } + let mut o = [0i32; 16]; + for i in 0..4 { + let (a, b, c, e) = (t[i], t[4 + i], t[8 + i], t[12 + i]); + o[i] = a + b + c + e; + o[4 + i] = a + b - c - e; + o[8 + i] = a - b - c + e; + o[12 + i] = a - b + c - e; + } + o +} + +/// Forward quantisation of the Intra16x16 DC (input: raster 4x4 of block DC coefficients). +pub fn quant_luma_dc(dc: &[i32; 16], qt: &QuantTables, qp: u8, dz: Deadzone) -> ([i32; 16], u32) { + let h = hadamard4(dc); + let qbits = 16 + (qp / 6) as u32; + let f = ((1i64 << qbits) * dz.f256) >> 8; + let mf = qt.mf4[(qp % 6) as usize][0] as i64; + let mut out = [0i32; 16]; + let mut nz = 0; + for i in 0..16 { + let v = h[i] >> 1; + let a = ((v.unsigned_abs() as i64 * mf + f) >> qbits) as i32; + out[i] = if v < 0 { -a } else { a }; + nz += (a != 0) as u32; + } + (out, nz) +} + +/// Normative Intra16x16 DC inverse (§8.5.10): levels (raster 4x4) -> dcY (raster 4x4). +pub fn dequant_luma_dc(c: &[i32; 16], qt: &QuantTables, qp: u8) -> [i32; 16] { + let f = hadamard4(c); + let ls = 16 * qt.v4[(qp % 6) as usize][0]; + let q6 = (qp / 6) as i32; + let mut o = [0i32; 16]; + for i in 0..16 { + o[i] = if q6 >= 6 { (f[i] * ls) << (q6 - 6) } else { (f[i] * ls + (1 << (5 - q6))) >> (6 - q6) }; + } + o +} + +/// Forward quantisation of chroma DC (2x2 raster of block DC coefficients). +pub fn quant_chroma_dc(dc: &[i32; 4], qt: &QuantTables, qp: u8, dz: Deadzone) -> ([i32; 4], u32) { + let h = [dc[0] + dc[1] + dc[2] + dc[3], dc[0] - dc[1] + dc[2] - dc[3], dc[0] + dc[1] - dc[2] - dc[3], dc[0] - dc[1] - dc[2] + dc[3]]; + let qbits = 16 + (qp / 6) as u32; + let f = ((1i64 << qbits) * dz.f256) >> 8; + let mf = qt.mf4[(qp % 6) as usize][0] as i64; + let mut out = [0i32; 4]; + let mut nz = 0; + for i in 0..4 { + let v = h[i]; + let a = ((v.unsigned_abs() as i64 * mf + f) >> qbits) as i32; + out[i] = if v < 0 { -a } else { a }; + nz += (a != 0) as u32; + } + (out, nz) +} + +/// Normative chroma DC inverse (§8.5.11.2) for 4:2:0: levels (raster 2x2) -> dcC. +pub fn dequant_chroma_dc(c: &[i32; 4], qt: &QuantTables, qpc: u8) -> [i32; 4] { + let f = [c[0] + c[1] + c[2] + c[3], c[0] - c[1] + c[2] - c[3], c[0] + c[1] - c[2] - c[3], c[0] - c[1] - c[2] + c[3]]; + let ls = 16 * qt.v4[(qpc % 6) as usize][0]; + let q6 = (qpc / 6) as u32; + let mut o = [0i32; 4]; + for i in 0..4 { + o[i] = ((f[i] * ls) << q6) >> 5; + } + o +} + +#[cfg(test)] +mod tests { + use super::*; + + fn quant4_i64(c: &mut [i32; 16], qp: u8, dz: Deadzone, skip_dc: bool) -> u32 { + let qbits = 15 + (qp / 6) as u32; + let f = ((1i64 << qbits) * dz.f256) >> 8; + let mut nz = 0; + for i in (skip_dc as usize)..16 { + let v = c[i]; + let a = ((v.unsigned_abs() as i64 * QUANT4[(qp % 6) as usize][pos_class4(i)] as i64 + f) >> qbits) as i32; + c[i] = if v < 0 { -a } else { a }; + nz += (a != 0) as u32; + } + nz + } + + fn mf8_i64() -> [[i64; 64]; 6] { + let norm8 = [512.0, 578.0, 320.0, 578.0, 512.0, 578.0, 320.0, 578.0f64]; + std::array::from_fn(|q| { + std::array::from_fn(|i| { + let v = DEQUANT8[q][pos_class8(i)] as f64; + ((1u64 << 36) as f64 / (norm8[i & 7] * norm8[i >> 3] * v)).round() as i64 + }) + }) + } + + fn quant8_i64(c: &mut [i32; 64], mf8: &[[i64; 64]; 6], qp: u8, dz: Deadzone) -> u32 { + let qbits = 22 + (qp / 6) as u32; + let f = ((1i64 << qbits) * dz.f256) >> 8; + let mut nz = 0; + for i in 0..64 { + let v = c[i]; + let a = ((v.unsigned_abs() as i64 * mf8[(qp % 6) as usize][i] + f) >> qbits) as i32; + c[i] = if v < 0 { -a } else { a }; + nz += (a != 0) as u32; + } + nz + } + + const DEADZONES: [Deadzone; 3] = [Deadzone::INTRA, Deadzone::INTER, Deadzone { f256: 128 }]; + + #[test] + fn quant4_matches_i64() { + let qt = QuantTables::new(); + for qp in 0..52u8 { + for dz in DEADZONES { + // Every magnitude below 2^16 (4x4 residual coefficients stay below 2^14) at every position. + for base in (0..1 << 16).step_by(16) { + for rot in [0, 5, 10] { + let mut c: [i32; 16] = std::array::from_fn(|i| { + let v = base + (i + rot) as i32 % 16; + if (i ^ (base as usize / 16)) & 1 == 0 { v } else { -v } + }); + let mut r = c; + let skip_dc = base & 16 != 0; + let nz = quant4(&mut c, &qt, qp, dz, skip_dc); + assert_eq!(nz, quant4_i64(&mut r, qp, dz, skip_dc), "qp {qp} base {base}"); + assert_eq!(c, r, "qp {qp} base {base}"); + } + } + } + } + } + + #[test] + fn quant8_matches_i64() { + let qt = QuantTables::new(); + let mf8 = mf8_i64(); + assert!(qt.mf8.iter().flatten().all(|&m| m <= 0xffff)); + let mut seed = 1u32; + let mut rnd = || { + seed = seed.wrapping_mul(1664525).wrapping_add(1013904223); + seed + }; + for qp in 0..52u8 { + for dz in DEADZONES { + // Every magnitude below 2^21 (8x8 residual coefficients stay below 2^20), rotated over positions. + for (n, base) in (0..1 << 21).step_by(64).enumerate() { + let mut c: [i32; 64] = std::array::from_fn(|i| { + let v = base + ((i + n * 7) % 64) as i32; + if (i + n) & 1 == 0 { v } else { -v } + }); + let mut r = c; + assert_eq!(quant8(&mut c, &qt, qp, dz), quant8_i64(&mut r, &mf8, qp, dz), "qp {qp} base {base}"); + assert_eq!(c, r, "qp {qp} base {base}"); + } + for _ in 0..2000 { + let mut c: [i32; 64] = std::array::from_fn(|_| (rnd() >> 10) as i32 - (1 << 21)); + let mut r = c; + assert_eq!(quant8(&mut c, &qt, qp, dz), quant8_i64(&mut r, &mf8, qp, dz), "qp {qp}"); + assert_eq!(c, r, "qp {qp}"); + } + } + } + } + + #[test] + fn roundtrip_4x4_low_qp() { + let qt = QuantTables::new(); + let mut d = [0i32; 16]; + for (i, v) in d.iter_mut().enumerate() { + *v = ((i * 37) % 51) as i32 - 25; + } + let mut c = fdct4(&d); + quant4(&mut c, &qt, 4, Deadzone { f256: 128 }, false); + dequant4(&mut c, &qt, 4, false); + let r = idct4(&c); + for i in 0..16 { + assert!((r[i] - d[i]).abs() <= 1, "{i}: {} vs {}", r[i], d[i]); + } + } + + #[test] + fn roundtrip_8x8_low_qp() { + let qt = QuantTables::new(); + let mut d = [0i32; 64]; + for (i, v) in d.iter_mut().enumerate() { + *v = ((i * 53) % 71) as i32 - 35; + } + let mut c = fdct8(&d); + quant8(&mut c, &qt, 4, Deadzone { f256: 128 }); + dequant8(&mut c, &qt, 4); + let r = idct8(&c); + for i in 0..64 { + assert!((r[i] - d[i]).abs() <= 1, "{i}: {} vs {}", r[i], d[i]); + } + } + + #[test] + fn fdct8_matches_matrix() { + let mut d = [0i32; 64]; + for (i, v) in d.iter_mut().enumerate() { + *v = ((i * 97) % 211) as i32 - 105; + } + let f = fdct8(&d); + for k in 0..8 { + for l in 0..8 { + let mut s = 0; + for y in 0..8 { + for x in 0..8 { + s += A8[k][y] * d[y * 8 + x] * A8[l][x]; + } + } + assert_eq!(f[k * 8 + l], s); + } + } + } + + #[test] + fn luma_dc_roundtrip() { + let qt = QuantTables::new(); + // DC of a flat block of value 10 is 160 per 4x4 block. + let dc = [160i32; 16]; + let (lv, _) = quant_luma_dc(&dc, &qt, 10, Deadzone { f256: 128 }); + let o = dequant_luma_dc(&lv, &qt, 10); + let mut blk = [0i32; 16]; + blk[0] = o[0]; + let r = idct4(&blk); + assert!(r.iter().all(|&v| (v - 10).abs() <= 1), "{r:?}"); + } + + #[test] + fn chroma_dc_roundtrip() { + let qt = QuantTables::new(); + let dc = [16 * 7, 16 * 7, 16 * 7, 16 * 7]; + let (lv, _) = quant_chroma_dc(&dc, &qt, 10, Deadzone { f256: 128 }); + let o = dequant_chroma_dc(&lv, &qt, 10); + let mut blk = [0i32; 16]; + blk[0] = o[0]; + let r = idct4(&blk); + assert!(r.iter().all(|&v| (v - 7).abs() <= 1), "{r:?}"); + } +} diff --git a/crates/hevcenc/Cargo.toml b/crates/hevcenc/Cargo.toml new file mode 100644 index 0000000000..83577de699 --- /dev/null +++ b/crates/hevcenc/Cargo.toml @@ -0,0 +1,13 @@ +[package] +name = "craft-hevcenc" +description = "Clean-room pure-Rust HEVC (H.265) encoder for the Craft ecosystem" +version.workspace = true +edition.workspace = true +license.workspace = true +rust-version.workspace = true +repository.workspace = true + +[lints] +workspace = true + +[dependencies] diff --git a/crates/hevcenc/README.md b/crates/hevcenc/README.md new file mode 100644 index 0000000000..1b7f883786 --- /dev/null +++ b/crates/hevcenc/README.md @@ -0,0 +1,82 @@ +# craft-hevcenc + +Clean-room, pure-Rust HEVC / H.265 encoder for export pipelines (layer L0, `std` only, +no `unsafe`, builds for `wasm32-unknown-unknown`). + +## What it produces + +- **Profiles:** Main (8-bit) and Main 10 (10-bit), 4:2:0, Main tier. `general_level_idc` is taken + from the config or chosen as the lowest level whose MaxLumaPs, MaxLumaSr (and, in bitrate mode, + MaxBR) fit (Table A.8). +- **Parameter sets:** VPS, SPS (with VUI: BT.709 colour description, `video_full_range_flag`, + timing info from the frame rate) and PPS, as raw NAL units (`Encoder::parameter_sets`) and as an + `hvcC` record (ISO/IEC 14496-15 §8.3.3.1, `lengthSizeMinusOne` 3, complete VPS / SPS / PPS + arrays, `Encoder::hvcc`). +- **Pictures:** one slice per picture; `IDR_N_LP` every `keyint` pictures (`1` = all intra, `0` = + only the first), `TRAIL_R` P pictures otherwise, each referencing the previous picture (one + short-term RPS in the SPS, `num_ref_idx_l0_active` 1, no reordering, pts = dts). Packets are + length-prefixed (4-byte big-endian) NAL units without parameter sets. +- **Picture size:** any even width / height; the coded size is padded to a multiple of 8 by edge + replication and cropped with the conformance window. Odd sizes are rejected with + `Error::InvalidConfig` (4:2:0 conformance-window offsets are in units of two luma samples). + +## Coding tools + +- 64x64 CTUs, coding units 64x64 .. 8x8 chosen by rate-distortion optimisation: every candidate is + actually predicted, transformed, quantised and reconstructed, and its rate is measured with a + CABAC bit estimator that evolves the same context states as the real coder. Split decisions + compare the best unsplit candidate with the recursively coded children (early exit after a skip). +- **Intra:** all 35 modes (SATD search on a coarse grid plus refinement and the MPMs), 2Nx2N at + 8..32 and NxN (four 4x4 PUs) at 8x8, chroma in DM mode, MPM / remaining-mode coding, reference + substitution, [1 2 1] and strong (32x32) smoothing, DC / angular boundary filters. +- **Inter (P):** skip and merge (spatial candidates + zero candidates, `MaxNumMergeCand` 5) and AMVP + with mvd coding; integer motion search (predictor starts + square pattern, steps 16..1) and + half / quarter-sample refinement on pre-interpolated half-sample planes; the coded prediction uses + the normative 8-tap / 4-tap interpolation filters and reference-picture edge clamping. +- **Transforms:** DST-VII 4x4 (intra luma) and DCT 4..32 (inverse by even/odd decomposition, + bit-identical to the matrix form), dead-zone quantisation (1/3 intra, 1/6 inter), flat scaling, + sign data hiding, inferred transform splits only (`max_transform_hierarchy_depth_*` 0, so 64x64 + CUs use four 32x32 TUs). +- **Entropy coding:** CABAC (context initialisation by slice QP and init type, decision / bypass / + terminate bins, flush), `end_of_slice_segment_flag`, byte alignment, emulation prevention. +- **In-loop filters:** the deblocking filter is on (the encoder deblocks its own reconstruction, so + references match the decoder); SAO is disabled. +- **Rate control:** `ConstantQp(qp)` or `Bitrate { kbps }`, a per-picture QP from a bits-x-Qstep + model per picture type (I pictures get roughly 4x the bits of a P picture) with a buffer that + spreads over- and undershoot over the following pictures. + +Not implemented: B pictures / reordering, multiple references, TMVP, AMP and other inter partition +shapes, residual quadtree splits, transform skip, scaling lists, PCM, SAO, RDOQ, cu_qp_delta / +adaptive quantisation, tiles / WPP / multiple slices, range extensions (4:2:2, 4:4:4, > 10 bits). + +## Measurements + +On a shared, heavily loaded machine, opt-level 3: about 0.45 s of CPU per 1920x1080 picture +(ffmpeg `testsrc2`, QP 22 / 32). On ffmpeg's `mandelbrot` 1080p source (8 pictures, IPPP, QP 32) +the stream is about 12 % larger at about 0.2 dB lower mean PSNR-Y than x265 `--preset medium` at +the same QP without psy tuning (110.5 kB / 42.3 dB vs 98.3 kB / 42.5 dB). The synthetic test image +reaches 45.8 dB PSNR-Y at QP 22. + +## Tests + +`cargo test -p craft-hevcenc`: + +- unit tests for CABAC, bitstream writer, parameter sets, transforms, intra modes, and frame coding; +- covered: 8-bit and 10-bit, all-intra and IP, QP 0 noise (escape codes), QP 0..51, strong + deblocking at high QP, sizes 8x8 .. 200x136 with cropping, POC wrap, bitrate mode, `hvcC` layout. + +## Specifications + +- ITU-T H.265 (V8, 08/2021) | ISO/IEC 23008-2, *High efficiency video coding*: 7 (syntax and + semantics), 8.4 - 8.7 (intra / inter prediction, scaling and transforms, deblocking), 9.3 (CABAC, + including the informative encoder description of 9.3.4), Annex A (profiles, tiers and levels), + Annex E (VUI). +- ISO/IEC 14496-15 (2019), §8.3.3.1 HEVCDecoderConfigurationRecord. + +Implemented from the specification text. Constant tables (CABAC context layout and initValues, +rangeTabLPS / transIdx, scan orders, the DCT / DST matrices, intra angles, interpolation filters, +the chroma QP table, the deblocking beta / tC tables) and the decoder-side reconstruction +arithmetic (intra reference substitution / filtering / prediction, fractional interpolation, +deblocking) are mirrored from FilmCraft's first-party, spec-derived `filmcraft-hevc` decoder (same +organisation, MIT OR Apache-2.0), so that encoder and decoder reconstruct identically. No +third-party encoder or decoder source (x265, libde265, ffmpeg, HM, ...) was consulted. diff --git a/crates/hevcenc/src/bitstream.rs b/crates/hevcenc/src/bitstream.rs new file mode 100644 index 0000000000..27ed3063e7 --- /dev/null +++ b/crates/hevcenc/src/bitstream.rs @@ -0,0 +1,135 @@ +//! RBSP bit writer (7.2, 9.2 Exp-Golomb) and NAL unit encapsulation with emulation prevention (7.3.1). + +/// MSB-first bit writer. +#[derive(Default)] +pub struct BitWriter { + buf: Vec, + acc: u64, + nbits: u32, +} + +impl BitWriter { + pub fn new() -> Self { + Self::default() + } + + /// Write the `n` (<= 32) low bits of `v`. + #[inline] + pub fn put(&mut self, v: u32, n: u32) { + debug_assert!(n <= 32); + if n == 0 { + return; + } + let v = (v as u64) & ((1u64 << n) - 1); + self.acc = (self.acc << n) | v; + self.nbits += n; + while self.nbits >= 8 { + self.nbits -= 8; + self.buf.push((self.acc >> self.nbits) as u8); + } + self.acc &= (1u64 << self.nbits) - 1; + } + + #[inline] + pub fn bit(&mut self, b: u32) { + self.put(b & 1, 1); + } + + pub fn flag(&mut self, b: bool) { + self.put(b as u32, 1); + } + + /// ue(v). + pub fn ue(&mut self, v: u32) { + let x = v as u64 + 1; + let len = 64 - x.leading_zeros(); // bits of x + // len - 1 leading zeros, then x in len bits + self.put(0, len - 1); + if len > 32 { + self.put((x >> 32) as u32, len - 32); + self.put(x as u32, 32); + } else { + self.put(x as u32, len); + } + } + + /// se(v). + pub fn se(&mut self, v: i32) { + let k = if v > 0 { 2 * v as u32 - 1 } else { (-(v as i64) as u32) * 2 }; + self.ue(k); + } + + pub fn is_aligned(&self) -> bool { + self.nbits == 0 + } + + /// rbsp_trailing_bits(): stop bit and zero alignment. + pub fn trailing(&mut self) { + self.bit(1); + self.align_zero(); + } + + pub fn align_zero(&mut self) { + if self.nbits > 0 { + self.put(0, 8 - self.nbits); + } + } + + pub fn into_bytes(mut self) -> Vec { + self.align_zero(); + self.buf + } +} + +/// Build a NAL unit (2-byte header, `nuh_layer_id` 0, `TemporalId` 0) from an RBSP, inserting +/// emulation prevention bytes. +pub fn nal_unit(nal_type: u8, rbsp: &[u8]) -> Vec { + let mut out = Vec::with_capacity(rbsp.len() + rbsp.len() / 64 + 4); + out.push(nal_type << 1); + out.push(1); + let mut zeros = 0; + for &b in rbsp { + if zeros >= 2 && b <= 3 { + out.push(3); + zeros = 0; + } + out.push(b); + if b == 0 { + zeros += 1; + } else { + zeros = 0; + } + } + // cabac_zero_words / trailing zero bytes never occur: every RBSP ends with the stop bit. + out +} + +pub const NAL_TRAIL_R: u8 = 1; +pub const NAL_IDR_N_LP: u8 = 20; +pub const NAL_VPS: u8 = 32; +pub const NAL_SPS: u8 = 33; +pub const NAL_PPS: u8 = 34; + +#[cfg(test)] +mod tests { + use super::*; + + #[test] + fn exp_golomb() { + let mut w = BitWriter::new(); + w.ue(0); // 1 + w.ue(1); // 010 + w.ue(2); // 011 + w.ue(3); // 00100 + w.se(-1); // ue(2) = 011 + w.trailing(); + // 1 010 011 00100 011 + stop bit: 1010 0110 0100 0111 + assert_eq!(w.into_bytes(), vec![0xa6, 0x47]); + } + + #[test] + fn emulation_prevention() { + let n = nal_unit(1, &[0, 0, 1, 0, 0, 0, 0, 0, 4]); + assert_eq!(&n[2..], &[0, 0, 3, 1, 0, 0, 3, 0, 0, 3, 0, 4]); + } +} diff --git a/crates/hevcenc/src/cabac.rs b/crates/hevcenc/src/cabac.rs new file mode 100644 index 0000000000..220b999cd6 --- /dev/null +++ b/crates/hevcenc/src/cabac.rs @@ -0,0 +1,220 @@ +//! CABAC: context initialisation (9.3.2.2), the arithmetic encoder (9.3.4.x, informative encoder +//! description: EncodeDecision, EncodeBypass, EncodeTerminate, EncodeFlush) and a fractional-bit +//! estimator with the same context-state evolution, used for rate-distortion decisions. + +use crate::bitstream::BitWriter; +use crate::tables::{CABAC_INIT, NEXT_STATE, NUM_CTX, RANGE_TAB_LPS}; +use std::sync::OnceLock; + +/// Context states, each `pStateIdx << 1 | valMps`. +pub type Contexts = [u8; NUM_CTX]; + +/// Initialise all context variables for `slice_qp` and `init_type` (9.3.2.2). +pub fn init_contexts(slice_qp: i32, init_type: usize) -> Contexts { + let mut ctx = [0u8; NUM_CTX]; + let qp = slice_qp.clamp(0, 51); + for (c, &v) in ctx.iter_mut().zip(CABAC_INIT[init_type].iter()) { + let slope = (v >> 4) as i32; + let offset = (v & 15) as i32; + let m = slope * 5 - 45; + let n = (offset << 3) - 16; + let pre = (((m * qp) >> 4) + n).clamp(1, 126); + *c = if pre <= 63 { ((63 - pre) << 1) as u8 } else { (((pre - 64) << 1) | 1) as u8 }; + } + ctx +} + +/// Destination of binarised syntax elements: the real arithmetic coder or the bit estimator. +pub trait Sink { + fn decision(&mut self, ctx: usize, bin: u32); + fn bypass(&mut self, bin: u32); + /// `n` bypass bins carrying the low `n` bits of `v`, MSB first. + fn bypass_bits(&mut self, v: u32, n: u32) { + for i in (0..n).rev() { + self.bypass((v >> i) & 1); + } + } + fn terminate(&mut self, bin: u32); +} + +/// Arithmetic encoder writing into a bit writer. +pub struct CabacEncoder { + pub ctx: Contexts, + low: u32, + range: u32, + outstanding: u32, + first: bool, + pub w: BitWriter, +} + +impl CabacEncoder { + /// Start encoding slice data into `w` (which must be byte aligned). + pub fn new(w: BitWriter, ctx: Contexts) -> Self { + debug_assert!(w.is_aligned()); + CabacEncoder { ctx, low: 0, range: 510, outstanding: 0, first: true, w } + } + + #[inline] + fn put_bit(&mut self, b: u32) { + if self.first { + self.first = false; + } else { + self.w.bit(b); + } + while self.outstanding > 0 { + self.w.bit(1 - b); + self.outstanding -= 1; + } + } + + #[inline] + fn renorm(&mut self) { + while self.range < 256 { + if self.low < 256 { + self.put_bit(0); + } else if self.low >= 512 { + self.low -= 512; + self.put_bit(1); + } else { + self.low -= 256; + self.outstanding += 1; + } + self.range <<= 1; + self.low <<= 1; + } + } + + /// EncodeFlush after a terminating bin equal to 1; the final bit written is the + /// rbsp_stop_one_bit. Returns the writer (byte aligned with zero bits). + pub fn finish(mut self) -> BitWriter { + self.range = 2; + self.renorm(); + let b = (self.low >> 9) & 1; + self.put_bit(b); + self.w.put(((self.low >> 7) & 3) | 1, 2); + self.w.align_zero(); + self.w + } +} + +impl Sink for CabacEncoder { + #[inline] + fn decision(&mut self, ctx_idx: usize, bin: u32) { + let s = self.ctx[ctx_idx] as usize; + let mps = (s & 1) as u32; + let q = ((self.range >> 6) & 3) as usize; + let lps = RANGE_TAB_LPS[s >> 1][q] as u32; + self.range -= lps; + if bin != mps { + self.low += self.range; + self.range = lps; + self.ctx[ctx_idx] = NEXT_STATE[s][1]; + } else { + self.ctx[ctx_idx] = NEXT_STATE[s][0]; + } + self.renorm(); + } + + #[inline] + fn bypass(&mut self, bin: u32) { + self.low <<= 1; + if bin != 0 { + self.low += self.range; + } + if self.low >= 1024 { + self.put_bit(1); + self.low -= 1024; + } else if self.low < 512 { + self.put_bit(0); + } else { + self.low -= 512; + self.outstanding += 1; + } + } + + fn terminate(&mut self, bin: u32) { + self.range -= 2; + if bin != 0 { + self.low += self.range; + // the caller flushes with finish() + } else { + self.renorm(); + } + } +} + +/// Fractional bit cost (1/32768 bit units) of coding the MPS / LPS in each state. +fn cost_table() -> &'static [[u32; 2]; 64] { + static T: OnceLock<[[u32; 2]; 64]> = OnceLock::new(); + T.get_or_init(|| { + let mut t = [[0u32; 2]; 64]; + // pLPS(s) = 0.5 * alpha^s with alpha = (0.01875 / 0.5)^(1/63) (9.3.4.3 design) + let alpha = (0.01875f64 / 0.5).powf(1.0 / 63.0); + for (s, e) in t.iter_mut().enumerate() { + let p = 0.5 * alpha.powi(s as i32); + e[0] = (-(1.0 - p).log2() * 32768.0).round() as u32; + e[1] = (-p.log2() * 32768.0).round() as u32; + } + t + }) +} + +pub const BIT: u64 = 32768; + +/// Bit estimator: tracks context states like the encoder and accumulates the ideal code length. +#[derive(Clone)] +pub struct Estimator { + pub ctx: Contexts, + /// Accumulated cost in 1/32768 bits. + pub bits: u64, + table: &'static [[u32; 2]; 64], +} + +impl Estimator { + pub fn new(ctx: Contexts) -> Self { + Estimator { ctx, bits: 0, table: cost_table() } + } +} + +impl Sink for Estimator { + #[inline] + fn decision(&mut self, ctx_idx: usize, bin: u32) { + let s = self.ctx[ctx_idx] as usize; + let lps = (bin != (s & 1) as u32) as usize; + self.bits += self.table[s >> 1][lps] as u64; + self.ctx[ctx_idx] = NEXT_STATE[s][lps]; + } + #[inline] + fn bypass(&mut self, _bin: u32) { + self.bits += BIT; + } + #[inline] + fn bypass_bits(&mut self, _v: u32, n: u32) { + self.bits += BIT * n as u64; + } + fn terminate(&mut self, _bin: u32) { + self.bits += 7; // ~0 bits for a 0 bin + } +} + +#[cfg(test)] +mod tests { + use super::*; + + #[test] + fn context_init_formula() { + let ctx = init_contexts(30, 0); + // initValue 154: m = 0, n = 64 -> pStateIdx 0, valMps 1 + assert_eq!(ctx[crate::tables::CU_TRANSQUANT_BYPASS], 1); + // split_cu_flag ctx 0 (initValue 139) at QP 30: preCtxState 62 + assert_eq!(ctx[crate::tables::SPLIT_CU], (63 - 62) << 1); + } + + #[test] + fn costs_are_sane() { + let t = cost_table(); + assert_eq!(t[0][0], 32768); + assert_eq!(t[0][1], 32768); + assert!(t[62][0] < 1000 && t[62][1] > 5 * 32768); + } +} diff --git a/crates/hevcenc/src/deblock.rs b/crates/hevcenc/src/deblock.rs new file mode 100644 index 0000000000..6d326e5d4c --- /dev/null +++ b/crates/hevcenc/src/deblock.rs @@ -0,0 +1,148 @@ +//! Deblocking filter (8.7.2) over a whole reconstructed picture, mirrored from the filmcraft-hevc +//! decoder: all vertical edges on the 8x8 grid first, then all horizontal edges. One slice, one QP, +//! uni-directional prediction from a single reference picture, offsets 0. + +use crate::frame::{Blk, E_EDGE_H, E_EDGE_V, F_CBF, F_INTRA}; +use crate::tables::{BETA_TABLE, TC_TABLE, qpc_420}; + +#[inline] +fn tc_beta(q: i32, bs: i32, bd: u32) -> (i32, i32) { + let qb = q.clamp(0, 51); + let qt = (q + 2 * (bs - 1)).clamp(0, 53); + ((TC_TABLE[qt as usize] as i32) << (bd - 8), (BETA_TABLE[qb as usize] as i32) << (bd - 8)) +} + +/// Boundary strength (8.7.2.4) between 4x4 blocks p and q on a transform / prediction edge. +fn boundary_strength(p: &Blk, q: &Blk) -> i32 { + if (p.flags | q.flags) & F_INTRA != 0 { + return 2; + } + if (p.flags | q.flags) & F_CBF != 0 { + return 1; + } + let far = (p.mv[0] as i32 - q.mv[0] as i32).abs() >= 4 || (p.mv[1] as i32 - q.mv[1] as i32).abs() >= 4; + far as i32 +} + +/// Deblock the planes in place. `qp` is the slice QpY (without QpBdOffset). +pub fn deblock(planes: &mut [Vec; 3], blk: &[Blk], w: usize, h: usize, qp: i32, bd: u32) { + let (w4, h4) = (w / 4, h / 4); + let cw = w / 2; + // chroma tc (bS 2 only; QpC from the averaged luma QP, no offsets) + let qt = (qpc_420(qp) + 2).clamp(0, 53); + let tc_c = (TC_TABLE[qt as usize] as i32) << (bd - 8); + // vertical edges + for by in 0..h4 { + for bx in (2..w4).step_by(2) { + let qi = by * w4 + bx; + if blk[qi].flags & E_EDGE_V == 0 { + continue; + } + let bs = boundary_strength(&blk[qi - 1], &blk[qi]); + if bs == 0 { + continue; + } + let (x, y) = (bx * 4, by * 4); + let (tc, beta) = tc_beta(qp, bs, bd); + filter_luma(&mut planes[0], y * w + x, 1, w, tc, beta, bd); + // chroma: 8x8 chroma grid (16 luma samples), one 4-sample chroma segment per 8 luma rows + if bs == 2 && bx % 4 == 0 && by % 2 == 0 { + for c in 1..3 { + filter_chroma(&mut planes[c], (y / 2) * cw + x / 2, 1, cw, tc_c, bd); + } + } + } + } + // horizontal edges + for by in (2..h4).step_by(2) { + for bx in 0..w4 { + let qi = by * w4 + bx; + if blk[qi].flags & E_EDGE_H == 0 { + continue; + } + let bs = boundary_strength(&blk[qi - w4], &blk[qi]); + if bs == 0 { + continue; + } + let (x, y) = (bx * 4, by * 4); + let (tc, beta) = tc_beta(qp, bs, bd); + filter_luma(&mut planes[0], y * w + x, w, 1, tc, beta, bd); + if bs == 2 && by % 4 == 0 && bx % 2 == 0 { + for c in 1..3 { + filter_chroma(&mut planes[c], (y / 2) * cw + x / 2, cw, 1, tc_c, bd); + } + } + } + } +} + +/// Filter one 4-sample luma edge segment. `o` = offset of q0 of the first line, `step` = distance +/// between samples across the edge, `along` = distance between lines. +fn filter_luma(s: &mut [u16], o: usize, step: usize, along: usize, tc: i32, beta: i32, bd: u32) { + let px = |s: &[u16], k: usize, i: usize| s[o + k * along - (i + 1) * step] as i32; + let qx = |s: &[u16], k: usize, i: usize| s[o + k * along + i * step] as i32; + let dp0 = (px(s, 0, 2) - 2 * px(s, 0, 1) + px(s, 0, 0)).abs(); + let dp3 = (px(s, 3, 2) - 2 * px(s, 3, 1) + px(s, 3, 0)).abs(); + let dq0 = (qx(s, 0, 2) - 2 * qx(s, 0, 1) + qx(s, 0, 0)).abs(); + let dq3 = (qx(s, 3, 2) - 2 * qx(s, 3, 1) + qx(s, 3, 0)).abs(); + let (dpq0, dpq3) = (dp0 + dq0, dp3 + dq3); + let (dp, dq) = (dp0 + dp3, dq0 + dq3); + let d = dpq0 + dpq3; + if d >= beta { + return; + } + let dsam = |s: &[u16], k: usize, dpq: i32| -> bool { + dpq < (beta >> 2) + && (px(s, k, 3) - px(s, k, 0)).abs() + (qx(s, k, 0) - qx(s, k, 3)).abs() < (beta >> 3) + && (px(s, k, 0) - qx(s, k, 0)).abs() < ((5 * tc + 1) >> 1) + }; + let strong = dsam(s, 0, 2 * dpq0) && dsam(s, 3, 2 * dpq3); + let dep = dp < ((beta + (beta >> 1)) >> 3); + let deq = dq < ((beta + (beta >> 1)) >> 3); + let max = (1i32 << bd) - 1; + for k in 0..4 { + let base = o + k * along; + let p = |i: usize| s[base - (i + 1) * step] as i32; + let q = |i: usize| s[base + i * step] as i32; + let (p0, p1, p2, p3) = (p(0), p(1), p(2), p(3)); + let (q0, q1, q2, q3) = (q(0), q(1), q(2), q(3)); + if strong { + let t2 = 2 * tc; + s[base - step] = ((p2 + 2 * p1 + 2 * p0 + 2 * q0 + q1 + 4) >> 3).clamp(p0 - t2, p0 + t2) as u16; + s[base - 2 * step] = ((p2 + p1 + p0 + q0 + 2) >> 2).clamp(p1 - t2, p1 + t2) as u16; + s[base - 3 * step] = ((2 * p3 + 3 * p2 + p1 + p0 + q0 + 4) >> 3).clamp(p2 - t2, p2 + t2) as u16; + s[base] = ((p1 + 2 * p0 + 2 * q0 + 2 * q1 + q2 + 4) >> 3).clamp(q0 - t2, q0 + t2) as u16; + s[base + step] = ((p0 + q0 + q1 + q2 + 2) >> 2).clamp(q1 - t2, q1 + t2) as u16; + s[base + 2 * step] = ((p0 + q0 + q1 + 3 * q2 + 2 * q3 + 4) >> 3).clamp(q2 - t2, q2 + t2) as u16; + } else { + let mut delta = (9 * (q0 - p0) - 3 * (q1 - p1) + 8) >> 4; + if delta.abs() >= tc * 10 { + continue; + } + delta = delta.clamp(-tc, tc); + s[base - step] = (p0 + delta).clamp(0, max) as u16; + s[base] = (q0 - delta).clamp(0, max) as u16; + if dep { + let dp = ((((p2 + p0 + 1) >> 1) - p1 + delta) >> 1).clamp(-(tc >> 1), tc >> 1); + s[base - 2 * step] = (p1 + dp).clamp(0, max) as u16; + } + if deq { + let dq = ((((q2 + q0 + 1) >> 1) - q1 - delta) >> 1).clamp(-(tc >> 1), tc >> 1); + s[base + step] = (q1 + dq).clamp(0, max) as u16; + } + } + } +} + +/// Filter one 4-sample chroma edge segment. +fn filter_chroma(s: &mut [u16], o: usize, step: usize, along: usize, tc: i32, bd: u32) { + let max = (1i32 << bd) - 1; + for k in 0..4 { + let base = o + k * along; + let (p0, p1) = (s[base - step] as i32, s[base - 2 * step] as i32); + let (q0, q1) = (s[base] as i32, s[base + step] as i32); + let delta = ((((q0 - p0) << 2) + p1 - q1 + 4) >> 3).clamp(-tc, tc); + s[base - step] = (p0 + delta).clamp(0, max) as u16; + s[base] = (q0 - delta).clamp(0, max) as u16; + } +} diff --git a/crates/hevcenc/src/frame.rs b/crates/hevcenc/src/frame.rs new file mode 100644 index 0000000000..81884ec80b --- /dev/null +++ b/crates/hevcenc/src/frame.rs @@ -0,0 +1,1489 @@ +//! Picture coding: CTU loop, rate-distortion mode decisions (with a CABAC bit estimator) and the +//! slice_segment_data() syntax (7.3.8): coding quadtree, coding unit, prediction unit, transform tree +//! and residual coding. Reconstruction mirrors the decoding process exactly (no in-loop filters). + +use crate::cabac::{BIT, CabacEncoder, Estimator, Sink}; +use crate::inter::{self, PlaneRef}; +use crate::intra::{self, Refs}; +use crate::me::MeRef; +use crate::params::{LOG2_CTB, LOG2_MAX_TB, LOG2_MIN_CB, MAX_MERGE_CAND}; +use crate::tables::{ + ABS_MVD_GT0, ABS_MVD_GT1, CBF_CHROMA, CBF_LUMA, CODED_SUB_BLOCK, CTX_IDX_MAP, CU_SKIP, GT1, GT2, INTRA_CHROMA, LAST_X_PREFIX, LAST_Y_PREFIX, + MERGE_FLAG, MERGE_IDX, MVP_FLAG, PART_MODE, PRED_MODE, PREV_INTRA_LUMA, RQT_ROOT_CBF, SCAN_4, SIG_COEFF, SPLIT_CU, qpc_420, scan_order, +}; +use crate::transform; + +pub const F_INTRA: u8 = 1; +pub const F_SKIP: u8 = 2; +/// The luma transform block covering this 4x4 block has non-zero coefficients. +pub const F_CBF: u8 = 4; +/// The left / top edge of this 4x4 block is a transform (and prediction) block edge. +pub const E_EDGE_V: u8 = 8; +pub const E_EDGE_H: u8 = 16; + +/// Per 4x4 luma block state of the current picture. +#[derive(Clone, Copy, Default)] +pub struct Blk { + pub depth: u8, + pub flags: u8, + /// IntraPredModeY (DC for inter blocks). + pub ipm: u8, + /// L0 motion vector (quarter samples) of inter blocks. + pub mv: [i16; 2], +} + +#[derive(Clone, Copy, Debug, PartialEq, Eq)] +pub enum CuMode { + Intra { nxn: bool, modes: [u8; 4] }, + Skip { idx: u8 }, + Merge { idx: u8 }, + Amvp { mv: [i16; 2], mvp: u8 }, +} + +#[derive(Clone, Copy, Debug)] +struct CuDec { + x: u32, + y: u32, + log2: u32, + mode: CuMode, +} + +/// One coded transform block of the current CU. +#[derive(Clone, Copy)] +struct Tb { + c: u8, + /// Luma-coordinate region the block belongs to (for cbf derivation). + lx: u32, + ly: u32, + log2: u32, + nz: bool, + off: usize, + scan: u8, +} + +/// Saved samples and block state of a square region. +struct Snap { + x: usize, + y: usize, + n: usize, + planes: [Vec; 3], + blk: Vec, +} + +#[derive(Clone, Copy, PartialEq, Eq)] +pub enum SliceKind { + I, + P, +} + +/// Picture geometry and slice coding parameters shared by the CTU encoder. +pub struct FrameConfig { + pub bit_depth: u32, + pub width: usize, + pub height: usize, + pub kind: SliceKind, + pub slice_qp: i32, +} + +/// A square node in the coding or transform quadtree. +#[derive(Clone, Copy)] +struct TreeNode { + x: usize, + y: usize, + log2: u32, + depth: u32, +} + +impl TreeNode { + fn children(self) -> impl Iterator { + let h = 1usize << (self.log2 - 1); + [(0, 0), (h, 0), (0, h), (h, h)].into_iter().map(move |(dx, dy)| Self { + x: self.x + dx, + y: self.y + dy, + log2: self.log2 - 1, + depth: self.depth + 1, + }) + } +} + +/// Transform syntax inherited from the parent node and the enclosing CU. +#[derive(Clone, Copy)] +struct TransformState { + blk_idx: u32, + parent: [bool; 2], + intra: bool, + nxn: bool, +} + +pub struct FrameCoder<'a> { + pub bd: u32, + pub w: usize, + pub h: usize, + pub cw: usize, + pub ch: usize, + w4: usize, + wctb: usize, + hctb: usize, + pub kind: SliceKind, + /// QP' (with QpBdOffset) for luma and chroma. + slice_qp: i32, + qp_y: i32, + qp_c: i32, + lambda: f64, + lambda_sad: f64, + src: &'a [Vec; 3], + pub rec: [Vec; 3], + refp: Option<&'a [Vec; 3]>, + me: Option<&'a MeRef>, + blk: Vec, + tbs: Vec, + coefs: Vec, + pred: Vec, + scratch: [Vec; 2], +} + +#[inline] +fn morton(x: u32, y: u32) -> u32 { + let mut r = 0; + for i in 0..5 { + r |= ((x >> i) & 1) << (2 * i); + r |= ((y >> i) & 1) << (2 * i + 1); + } + r +} + +/// Intra modes tried in the coarse pass of the SATD search. +const COARSE_MODES: [u32; 11] = [0, 1, 2, 6, 10, 14, 18, 22, 26, 30, 34]; + +impl<'a> FrameCoder<'a> { + pub fn new(config: FrameConfig, src: &'a [Vec; 3], refp: Option<&'a [Vec; 3]>, me: Option<&'a MeRef>) -> Self { + let FrameConfig { bit_depth: bd, width: w, height: h, kind, slice_qp } = config; + let off = 6 * (bd as i32 - 8); + let qpi = slice_qp.clamp(-off, 57); + let qp_c = qpc_420(qpi) + off; + let base = if kind == SliceKind::I { 0.57 } else { 0.62 }; + let lambda = base * 2f64.powf((slice_qp as f64 - 12.0) / 3.0) * (1u64 << (2 * (bd - 8))) as f64; + let ctb = 1usize << LOG2_CTB; + FrameCoder { + bd, + w, + h, + cw: w / 2, + ch: h / 2, + w4: w / 4, + wctb: w.div_ceil(ctb), + hctb: h.div_ceil(ctb), + kind, + slice_qp, + qp_y: slice_qp + off, + qp_c, + lambda, + lambda_sad: lambda.sqrt(), + src, + rec: [vec![0; w * h], vec![0; w * h / 4], vec![0; w * h / 4]], + refp, + me, + blk: vec![Blk::default(); (w / 4) * (h / 4)], + tbs: Vec::with_capacity(16), + coefs: Vec::with_capacity(64 * 64 * 2), + pred: vec![0; 64 * 64], + scratch: [vec![0; 32 * 32], vec![0; 32 * 32]], + } + } + + // --------------------------------------------------------------------------------------- + // neighbourhood + + /// z-scan order availability (6.4.1) of (xn, yn) for the block at (xc, yc): single slice, no tiles. + #[inline] + fn zavail(&self, xc: i32, yc: i32, xn: i32, yn: i32) -> bool { + if xn < 0 || yn < 0 || xn >= self.w as i32 || yn >= self.h as i32 { + return false; + } + let s = LOG2_CTB; + let ctb_n = (yn >> s) as usize * self.wctb + (xn >> s) as usize; + let ctb_c = (yc >> s) as usize * self.wctb + (xc >> s) as usize; + if ctb_n != ctb_c { + ctb_n < ctb_c + } else { + let m = (1 << s) - 1; + morton(((xn & m) >> 2) as u32, ((yn & m) >> 2) as u32) <= morton(((xc & m) >> 2) as u32, ((yc & m) >> 2) as u32) + } + } + + #[inline] + fn blk_at(&self, x: i32, y: i32) -> &Blk { + &self.blk[(y as usize >> 2) * self.w4 + (x as usize >> 2)] + } + + fn fill(&mut self, x: usize, y: usize, n: usize, f: impl Fn(&mut Blk)) { + for by in (y >> 2)..((y + n) >> 2) { + for b in &mut self.blk[by * self.w4 + (x >> 2)..by * self.w4 + ((x + n) >> 2)] { + f(b); + } + } + } + + /// Mark the CU's left / top edges and, for CUs larger than the maximum transform size, the + /// inner transform edges (deblocking). + fn mark_edges(&mut self, x: usize, y: usize, log2: u32) { + let n = 1usize << log2; + let t = 1usize << log2.min(LOG2_MAX_TB); + for ty in (y..y + n).step_by(t) { + for tx in (x..x + n).step_by(t) { + for by in ty / 4..(ty + t) / 4 { + self.blk[by * self.w4 + tx / 4].flags |= E_EDGE_V; + } + for b in &mut self.blk[(ty / 4) * self.w4 + tx / 4..(ty / 4) * self.w4 + (tx + t) / 4] { + b.flags |= E_EDGE_H; + } + } + } + } + + /// In-loop deblocking of the finished picture. + pub fn deblock(&mut self) { + crate::deblock::deblock(&mut self.rec, &self.blk, self.w, self.h, self.slice_qp, self.bd); + } + + fn snap(&self, x: usize, y: usize, n: usize) -> Snap { + let mut planes: [Vec; 3] = Default::default(); + for (c, p) in planes.iter_mut().enumerate() { + let (px, py, pn, stride) = if c == 0 { (x, y, n, self.w) } else { (x / 2, y / 2, n / 2, self.cw) }; + p.reserve(pn * pn); + for r in 0..pn { + p.extend_from_slice(&self.rec[c][(py + r) * stride + px..][..pn]); + } + } + let mut blk = Vec::with_capacity((n / 4) * (n / 4)); + for by in (y >> 2)..((y + n) >> 2) { + blk.extend_from_slice(&self.blk[by * self.w4 + (x >> 2)..by * self.w4 + ((x + n) >> 2)]); + } + Snap { x, y, n, planes, blk } + } + + fn restore(&mut self, s: &Snap) { + let (x, y, n) = (s.x, s.y, s.n); + for c in 0..3 { + let (px, py, pn, stride) = if c == 0 { (x, y, n, self.w) } else { (x / 2, y / 2, n / 2, self.cw) }; + for r in 0..pn { + self.rec[c][(py + r) * stride + px..][..pn].copy_from_slice(&s.planes[c][r * pn..r * pn + pn]); + } + } + let bw = n / 4; + for (i, by) in ((y >> 2)..((y + n) >> 2)).enumerate() { + self.blk[by * self.w4 + (x >> 2)..by * self.w4 + (x >> 2) + bw].copy_from_slice(&s.blk[i * bw..i * bw + bw]); + } + } + + // --------------------------------------------------------------------------------------- + // picture loop + + /// Code all CTUs of the picture into `enc` (one slice); the caller flushes the encoder. + pub fn encode(&mut self, enc: &mut CabacEncoder) { + let ctb = 1usize << LOG2_CTB; + let total = self.wctb * self.hctb; + for i in 0..total { + let (x, y) = ((i % self.wctb) * ctb, (i / self.wctb) * ctb); + let mut est = Estimator::new(enc.ctx); + let mut decs = Vec::with_capacity(64); + self.decide(x, y, LOG2_CTB, 0, &mut est, &mut decs); + let mut idx = 0; + self.write_cq(enc, TreeNode { x, y, log2: LOG2_CTB, depth: 0 }, &decs, &mut idx); + debug_assert_eq!(idx, decs.len()); + debug_assert_eq!(est.ctx, enc.ctx); + enc.terminate((i + 1 == total) as u32); // end_of_slice_segment_flag + } + } + + fn write_split(&self, s: &mut S, x: usize, y: usize, depth: u32, split: bool) { + let (x, y) = (x as i32, y as i32); + let mut inc = 0; + if self.zavail(x, y, x - 1, y) && self.blk_at(x - 1, y).depth as u32 > depth { + inc += 1; + } + if self.zavail(x, y, x, y - 1) && self.blk_at(x, y - 1).depth as u32 > depth { + inc += 1; + } + s.decision(SPLIT_CU + inc, split as u32); + } + + /// Final pass: re-code the decided quadtree with the real arithmetic coder. + fn write_cq(&mut self, enc: &mut CabacEncoder, node: TreeNode, decs: &[CuDec], idx: &mut usize) { + let TreeNode { x, y, log2, depth } = node; + if x >= self.w || y >= self.h { + return; + } + let n = 1usize << log2; + let inside = x + n <= self.w && y + n <= self.h; + let d = decs[*idx]; + let leaf = d.x as usize == x && d.y as usize == y && d.log2 == log2; + if inside && log2 > LOG2_MIN_CB { + self.write_split(enc, x, y, depth, !leaf); + } else { + debug_assert_eq!(leaf, log2 == LOG2_MIN_CB); + } + if leaf { + *idx += 1; + let (eff, _) = self.encode_cu(enc, x, y, log2, depth, d.mode); + debug_assert_eq!(eff, d.mode); + } else { + for child in node.children() { + self.write_cq(enc, child, decs, idx); + } + } + } + + #[inline] + fn cost(&self, dist: u64, bits: u64) -> f64 { + dist as f64 + self.lambda * bits as f64 / BIT as f64 + } + + /// Rate-distortion decision for the quadtree node at (x, y); leaves the chosen reconstruction + /// and block state in place, advances `est` and appends the decided CUs. Returns the RD cost. + fn decide(&mut self, x: usize, y: usize, log2: u32, depth: u32, est: &mut Estimator, decs: &mut Vec) -> f64 { + if x >= self.w || y >= self.h { + return 0.0; + } + let n = 1usize << log2; + let inside = x + n <= self.w && y + n <= self.h; + let can_split = log2 > LOG2_MIN_CB; + let base = est.bits; + let leaf_ok = inside && (self.kind == SliceKind::P || log2 <= LOG2_MAX_TB); + let mut best: Option<(f64, Estimator, Snap, CuMode)> = None; + if leaf_ok { + let mut e0 = est.clone(); + if can_split { + self.write_split(&mut e0, x, y, depth, false); + } + let try_mode = |me: &mut Self, mode: CuMode, best: &mut Option<(f64, Estimator, Snap, CuMode)>| { + let mut e = e0.clone(); + let (eff, dist) = me.encode_cu(&mut e, x, y, log2, depth, mode); + let cost = me.cost(dist, e.bits - base); + if best.as_ref().is_none_or(|b| cost < b.0) { + *best = Some((cost, e, me.snap(x, y, n), eff)); + } + }; + let mut inter_satd = u64::MAX; + if self.kind == SliceKind::P { + let merge = self.merge_list(x, y, n); + let (midx, msatd) = self.best_merge(x, y, n, &merge); + try_mode(self, CuMode::Skip { idx: midx as u8 }, &mut best); + try_mode(self, CuMode::Merge { idx: midx as u8 }, &mut best); + let amvp = self.amvp(x, y, n); + let (mv, mvp, asatd) = self.motion_search(x, y, n, &amvp, &merge); + if mv != merge[midx] { + try_mode(self, CuMode::Amvp { mv, mvp }, &mut best); + } + inter_satd = msatd.min(asatd); + } + if log2 <= LOG2_MAX_TB && !matches!(best, Some((_, _, _, CuMode::Skip { .. }))) { + let (mode, satd) = self.search_intra_mode(x, y, log2); + if satd < inter_satd { + try_mode(self, CuMode::Intra { nxn: false, modes: [mode as u8; 4] }, &mut best); + if log2 == LOG2_MIN_CB { + let modes = self.search_nxn_modes(x, y); + try_mode(self, CuMode::Intra { nxn: true, modes }, &mut best); + } + } + } + if let Some(b) = &best { + self.restore(&b.2); + } + } + let early = matches!(best, Some((_, _, _, CuMode::Skip { .. }))); + if can_split && (!leaf_ok || !early) { + let mut e = est.clone(); + if inside { + self.write_split(&mut e, x, y, depth, true); + } + let limit = best.as_ref().map_or(f64::INFINITY, |b| b.0); + let mut cost = self.cost(0, e.bits - base); + let mut d2 = Vec::new(); + let h = n / 2; + for (dx, dy) in [(0, 0), (h, 0), (0, h), (h, h)] { + cost += self.decide(x + dx, y + dy, log2 - 1, depth + 1, &mut e, &mut d2); + if cost >= limit { + break; + } + } + if cost < limit { + *est = e; + decs.extend(d2); + return cost; + } + } + // A coding unit always has at least one candidate mode (intra, or skip/merge in P + // slices); without one, report an infinite cost. + let Some((cost, e, snap, mode)) = best else { return f64::INFINITY }; + self.restore(&snap); + *est = e; + decs.push(CuDec { x: x as u32, y: y as u32, log2, mode }); + cost + } + + // --------------------------------------------------------------------------------------- + // coding unit + + fn skip_ctx(&self, x: usize, y: usize) -> usize { + let (x, y) = (x as i32, y as i32); + let mut inc = 0; + if self.zavail(x, y, x - 1, y) && self.blk_at(x - 1, y).flags & F_SKIP != 0 { + inc += 1; + } + if self.zavail(x, y, x, y - 1) && self.blk_at(x, y - 1).flags & F_SKIP != 0 { + inc += 1; + } + inc + } + + /// Predict, transform, quantise and reconstruct one CU and write its syntax into `s`. + /// Returns the effective mode (a residual-free merge becomes a skip) and the SSE. + fn encode_cu(&mut self, s: &mut S, x: usize, y: usize, log2: u32, depth: u32, mode: CuMode) -> (CuMode, u64) { + let n = 1usize << log2; + self.tbs.clear(); + self.coefs.clear(); + let p_slice = self.kind == SliceKind::P; + let skip_ctx = if p_slice { self.skip_ctx(x, y) } else { 0 }; + let d8 = depth as u8; + match mode { + CuMode::Intra { nxn, modes } => { + self.fill(x, y, n, |b| { + b.depth = d8; + b.flags = F_INTRA; + b.ipm = modes[0]; + }); + self.mark_edges(x, y, log2); + if nxn { + let h = n / 2; + for k in 1..4 { + let m = modes[k]; + self.fill(x + (k & 1) * h, y + (k >> 1) * h, h, |b| b.ipm = m); + } + for k in 0..4 { + let (px, py) = (x + (k & 1) * h, y + (k >> 1) * h); + self.code_tb(0, px, py, 2, Some(modes[k] as u32), (x, y)); + } + for c in 1..3 { + self.code_tb(c, x / 2, y / 2, 2, Some(modes[0] as u32), (x, y)); + } + } else { + self.code_tus(x, y, log2, Some(modes[0] as u32)); + } + if p_slice { + s.decision(CU_SKIP + skip_ctx, 0); + s.decision(PRED_MODE, 1); + } + if log2 == LOG2_MIN_CB { + s.decision(PART_MODE, (!nxn) as u32); + } + self.write_intra_modes(s, x, y, n, nxn, &modes); + s.decision(INTRA_CHROMA, 0); // intra_chroma_pred_mode 4 (DM) + let mut ti = 0; + self.write_tt(s, TreeNode { x, y, log2, depth: 0 }, TransformState { blk_idx: 0, parent: [true, true], intra: true, nxn }, &mut ti); + (mode, self.cu_dist(x, y, n)) + } + _ => { + let mv = match mode { + CuMode::Skip { idx } | CuMode::Merge { idx } => self.merge_list(x, y, n)[idx as usize], + CuMode::Amvp { mv, .. } => mv, + // Handled by the arm above. + CuMode::Intra { .. } => [0, 0], + }; + let amvp = if let CuMode::Amvp { .. } = mode { self.amvp(x, y, n) } else { [[0; 2]; 2] }; + let skip = matches!(mode, CuMode::Skip { .. }); + self.fill(x, y, n, |b| { + b.depth = d8; + b.flags = if skip { F_SKIP } else { 0 }; + b.ipm = 1; + b.mv = mv; + }); + self.mark_edges(x, y, log2); + self.predict_inter(x, y, n, mv); + let mut eff = mode; + let mut root = false; + if !skip { + self.code_tus(x, y, log2, None); + root = self.tbs.iter().any(|t| t.nz); + if !root && let CuMode::Merge { idx } = mode { + // rqt_root_cbf would be inferred: code a skipped CU instead + eff = CuMode::Skip { idx }; + self.fill(x, y, n, |b| b.flags |= F_SKIP); + } + } + match eff { + CuMode::Skip { idx } => { + s.decision(CU_SKIP + skip_ctx, 1); + write_merge_idx(s, idx as usize); + } + CuMode::Merge { idx } => { + s.decision(CU_SKIP + skip_ctx, 0); + s.decision(PRED_MODE, 0); + s.decision(PART_MODE, 1); // PART_2Nx2N + s.decision(MERGE_FLAG, 1); + write_merge_idx(s, idx as usize); + let mut ti = 0; + self.write_tt( + s, + TreeNode { x, y, log2, depth: 0 }, + TransformState { blk_idx: 0, parent: [true, true], intra: false, nxn: false }, + &mut ti, + ); + } + CuMode::Amvp { mv, mvp } => { + s.decision(CU_SKIP + skip_ctx, 0); + s.decision(PRED_MODE, 0); + s.decision(PART_MODE, 1); + s.decision(MERGE_FLAG, 0); + let p = amvp[mvp as usize]; + write_mvd(s, [mv[0] as i32 - p[0] as i32, mv[1] as i32 - p[1] as i32]); + s.decision(MVP_FLAG, mvp as u32); + s.decision(RQT_ROOT_CBF, root as u32); + if root { + let mut ti = 0; + self.write_tt( + s, + TreeNode { x, y, log2, depth: 0 }, + TransformState { blk_idx: 0, parent: [true, true], intra: false, nxn: false }, + &mut ti, + ); + } + } + // Handled by the arm above. + CuMode::Intra { .. } => {} + } + (eff, self.cu_dist(x, y, n)) + } + } + } + + /// Transform units of a 2Nx2N CU (split to the maximum TB size), luma then Cb, Cr per TU. + fn code_tus(&mut self, x: usize, y: usize, log2: u32, intra_mode: Option) { + let tl = log2.min(LOG2_MAX_TB); + let t = 1usize << tl; + let k = 1usize << (log2 - tl); + for i in 0..k * k { + // z-order of at most 2x2 TUs + let (tx, ty) = (x + (i & 1) * t, y + (i >> 1) * t); + self.code_tb(0, tx, ty, tl, intra_mode, (tx, ty)); + self.code_tb(1, tx / 2, ty / 2, tl - 1, intra_mode, (tx, ty)); + self.code_tb(2, tx / 2, ty / 2, tl - 1, intra_mode, (tx, ty)); + } + } + + fn cu_dist(&self, x: usize, y: usize, n: usize) -> u64 { + let mut d = 0u64; + for c in 0..3 { + let (px, py, pn, stride) = if c == 0 { (x, y, n, self.w) } else { (x / 2, y / 2, n / 2, self.cw) }; + for r in 0..pn { + let o = (py + r) * stride + px; + let a = &self.src[c][o..o + pn]; + let b = &self.rec[c][o..o + pn]; + d += a.iter().zip(b).map(|(&p, &q)| (p as i64 - q as i64).pow(2) as u64).sum::(); + } + } + d + } + + /// Code one transform block of component `c` at component position (x, y): optional intra + /// prediction, residual, transform, quantisation and reconstruction. + fn code_tb(&mut self, c: usize, x: usize, y: usize, log2: u32, intra_mode: Option, lpos: (usize, usize)) { + let n = 1usize << log2; + let stride = if c == 0 { self.w } else { self.cw }; + if let Some(m) = intra_mode { + let refs = self.build_refs(c, x, y, log2); + let bd = self.bd; + predict_from_refs(&refs, c, m, n, bd, &mut self.rec[c][y * stride + x..], stride); + } + let mut scr = std::mem::take(&mut self.scratch); + let [r, coef] = &mut scr; + for yy in 0..n { + let o = (y + yy) * stride + x; + for xx in 0..n { + r[yy * n + xx] = self.src[c][o + xx] as i32 - self.rec[c][o + xx] as i32; + } + } + let intra = intra_mode.is_some(); + let dst = intra && c == 0 && n == 4; + transform::forward(&r[..n * n], &mut coef[..n * n], n, dst, self.bd); + let qp = if c == 0 { self.qp_y } else { self.qp_c }; + let off = self.coefs.len(); + self.coefs.resize(off + n * n, 0); + let lv = &mut self.coefs[off..off + n * n]; + let scan = match intra_mode { + Some(m) if log2 == 2 || (log2 == 3 && c == 0) => scan_idx_for_mode(m), + _ => 0, + }; + let nz = transform::quantize(&coef[..n * n], lv, n, qp, intra, self.bd) > 0; + if nz { + transform::sign_hiding(&coef[..n * n], lv, log2, scan as usize, qp, self.bd); + } + if nz { + let d = &mut r[..n * n]; + transform::dequantize(lv, d, n, qp, self.bd); + transform::inverse(d, n, dst, self.bd); + let max = (1i32 << self.bd) - 1; + for yy in 0..n { + let o = (y + yy) * stride + x; + for xx in 0..n { + let v = &mut self.rec[c][o + xx]; + *v = (*v as i32 + d[yy * n + xx]).clamp(0, max) as u16; + } + } + } + self.scratch = scr; + if nz && c == 0 { + self.fill(x, y, n, |b| b.flags |= F_CBF); + } + self.tbs.push(Tb { c: c as u8, lx: lpos.0 as u32, ly: lpos.1 as u32, log2, nz, off, scan }); + } + + /// Reference samples (substituted, unfiltered) of an intra block, as in 8.4.4.2.1 / 8.4.4.2.2. + fn build_refs(&self, c: usize, x0: usize, y0: usize, log2: u32) -> Refs { + let n = 1usize << log2; + let sc = if c == 0 { 1 } else { 2 }; + let (x0, y0) = (x0 as i32, y0 as i32); + let (xl, yl) = (x0 * sc, y0 * sc); + let unit = 4 / sc as usize; + let stride = if c == 0 { self.w } else { self.cw }; + let mut al = [false; 129]; + let mut at = [false; 129]; + let plane = &self.rec[c]; + let mut r = Refs::default(); + if self.zavail(xl, yl, xl - 1, yl - 1) { + al[0] = true; + at[0] = true; + let v = plane[(y0 - 1) as usize * stride + (x0 - 1) as usize]; + r.left[0] = v; + r.top[0] = v; + } + let mut k = 0; + while k < 2 * n { + let yn = y0 + k as i32; + if self.zavail(xl, yl, xl - 1, yn * sc) { + for i in 0..unit { + al[1 + k + i] = true; + r.left[1 + k + i] = plane[(yn as usize + i) * stride + (x0 - 1) as usize]; + } + } + let xn = x0 + k as i32; + if self.zavail(xl, yl, xn * sc, yl - 1) { + let o = (y0 - 1) as usize * stride + xn as usize; + for i in 0..unit { + at[1 + k + i] = true; + r.top[1 + k + i] = plane[o + i]; + } + } + k += unit; + } + intra::substitute(&mut r, &al, &at, n, self.bd); + r + } + + // --------------------------------------------------------------------------------------- + // syntax writers + + /// Candidate modes candModeList (8.4.2) of the prediction block at (xp, yp). + fn mpm(&self, xp: usize, yp: usize) -> [u32; 3] { + let (xp, yp) = (xp as i32, yp as i32); + let cand_a = if self.zavail(xp, yp, xp - 1, yp) { + let b = self.blk_at(xp - 1, yp); + if b.flags & F_INTRA != 0 { b.ipm as u32 } else { 1 } + } else { + 1 + }; + let cand_b = if self.zavail(xp, yp, xp, yp - 1) && yp > ((yp >> LOG2_CTB) << LOG2_CTB) { + let b = self.blk_at(xp, yp - 1); + if b.flags & F_INTRA != 0 { b.ipm as u32 } else { 1 } + } else { + 1 + }; + if cand_a == cand_b { + if cand_a < 2 { [0, 1, 26] } else { [cand_a, 2 + ((cand_a + 29) % 32), 2 + ((cand_a - 2 + 1) % 32)] } + } else { + let c = if cand_a != 0 && cand_b != 0 { + 0 + } else if cand_a != 1 && cand_b != 1 { + 1 + } else { + 26 + }; + [cand_a, cand_b, c] + } + } + + fn write_intra_modes(&self, s: &mut S, x: usize, y: usize, n: usize, nxn: bool, modes: &[u8; 4]) { + let nb = if nxn { 2 } else { 1 }; + let pb = n / nb; + let mut codes = [(false, 0u32); 4]; + for j in 0..nb { + for i in 0..nb { + let k = j * nb + i; + let list = self.mpm(x + i * pb, y + j * pb); + let m = modes[k] as u32; + codes[k] = match list.iter().position(|&c| c == m) { + Some(idx) => (true, idx as u32), + None => { + let mut sorted = list; + sorted.sort_unstable(); + let mut r = m; + for &c in sorted.iter().rev() { + if r > c { + r -= 1; + } + } + (false, r) + } + }; + } + } + for code in codes.iter().take(nb * nb) { + s.decision(PREV_INTRA_LUMA, code.0 as u32); + } + for &(prev, v) in codes.iter().take(nb * nb) { + if prev { + s.bypass((v > 0) as u32); + if v > 0 { + s.bypass((v > 1) as u32); + } + } else { + s.bypass_bits(v, 5); + } + } + } + + fn chroma_cbf(&self, c: u8, x0: usize, y0: usize, n: usize) -> bool { + self.tbs + .iter() + .any(|t| t.c == c && t.nz && (t.lx as usize) >= x0 && (t.lx as usize) < x0 + n && (t.ly as usize) >= y0 && (t.ly as usize) < y0 + n) + } + + /// transform_tree() (7.3.8.8) over the coded transform blocks of the CU; no split flag is ever + /// coded (max_transform_hierarchy_depth_* = 0), splits are inferred. + fn write_tt(&self, s: &mut S, node: TreeNode, state: TransformState, ti: &mut usize) { + let TreeNode { x: x0, y: y0, log2, depth } = node; + let TransformState { blk_idx, parent, intra, nxn } = state; + let n = 1usize << log2; + let split = log2 > LOG2_MAX_TB || (nxn && depth == 0); + let mut cbf_c = [false; 2]; + if log2 > 2 { + for c in 0..2 { + if depth == 0 || parent[c] { + cbf_c[c] = self.chroma_cbf(c as u8 + 1, x0, y0, n); + s.decision(CBF_CHROMA + depth as usize, cbf_c[c] as u32); + } + } + } else if depth > 0 { + cbf_c = parent; + } + if split { + for (i, child) in node.children().enumerate() { + self.write_tt(s, child, TransformState { blk_idx: i as u32, parent: cbf_c, ..state }, ti); + } + return; + } + let tb = self.tbs[*ti]; + *ti += 1; + debug_assert_eq!(tb.c, 0); + if intra || depth != 0 || cbf_c[0] || cbf_c[1] { + s.decision(CBF_LUMA + (depth == 0) as usize, tb.nz as u32); + } else { + debug_assert!(tb.nz, "inferred cbf_luma needs coefficients"); + } + if tb.nz { + let n2 = 1usize << (2 * tb.log2); + residual_coding(s, &self.coefs[tb.off..tb.off + n2], tb.log2, 0, tb.scan as usize); + } + if log2 > 2 || blk_idx == 3 { + for c in 0..2 { + let tb = self.tbs[*ti]; + *ti += 1; + debug_assert_eq!(tb.c as usize, c + 1); + debug_assert_eq!(tb.nz, cbf_c[c]); + if cbf_c[c] { + let n2 = 1usize << (2 * tb.log2); + residual_coding(s, &self.coefs[tb.off..tb.off + n2], tb.log2, c + 1, tb.scan as usize); + } + } + } + } + + // --------------------------------------------------------------------------------------- + // inter prediction + + /// Prediction block availability (6.4.2) for a 2Nx2N CU: available and not intra. + #[inline] + fn pb_avail(&self, xp: i32, yp: i32, xn: i32, yn: i32) -> bool { + self.zavail(xp, yp, xn, yn) && self.blk_at(xn, yn).flags & F_INTRA == 0 + } + + /// Merge candidate list (8.5.3.2.2 - 8.5.3.2.5) of a 2Nx2N P-slice CU: spatial candidates and + /// zero candidates (no temporal candidate, one reference picture). + pub fn merge_list(&self, x: usize, y: usize, n: usize) -> [[i16; 2]; MAX_MERGE_CAND] { + let (xp, yp, w, h) = (x as i32, y as i32, n as i32, n as i32); + let mv = |p: (i32, i32)| self.blk_at(p.0, p.1).mv; + let mut list = [[0i16; 2]; MAX_MERGE_CAND]; + let mut k = 0; + let pa1 = (xp - 1, yp + h - 1); + let av_a1 = self.pb_avail(xp, yp, pa1.0, pa1.1); + if av_a1 { + list[k] = mv(pa1); + k += 1; + } + let pb1 = (xp + w - 1, yp - 1); + let av_b1 = self.pb_avail(xp, yp, pb1.0, pb1.1); + let fl_b1 = av_b1 && !(av_a1 && mv(pa1) == mv(pb1)); + if fl_b1 { + list[k] = mv(pb1); + k += 1; + } + let pb0 = (xp + w, yp - 1); + let fl_b0 = self.pb_avail(xp, yp, pb0.0, pb0.1) && !(av_b1 && mv(pb1) == mv(pb0)); + if fl_b0 { + list[k] = mv(pb0); + k += 1; + } + let pa0 = (xp - 1, yp + h); + let fl_a0 = self.pb_avail(xp, yp, pa0.0, pa0.1) && !(av_a1 && mv(pa1) == mv(pa0)); + if fl_a0 { + list[k] = mv(pa0); + k += 1; + } + let pb2 = (xp - 1, yp - 1); + let fl_b2 = self.pb_avail(xp, yp, pb2.0, pb2.1) + && !((av_a1 && mv(pa1) == mv(pb2)) || (av_b1 && mv(pb1) == mv(pb2))) + && (fl_a0 as u32 + av_a1 as u32 + fl_b0 as u32 + fl_b1 as u32) != 4; + if fl_b2 { + list[k] = mv(pb2); + } + // remaining entries are zero candidates with refIdx 0 + list + } + + /// AMVP candidates (8.5.3.2.6, 8.5.3.2.7) of a 2Nx2N CU with one reference picture. + pub fn amvp(&self, x: usize, y: usize, n: usize) -> [[i16; 2]; 2] { + let (xp, yp, w, h) = (x as i32, y as i32, n as i32, n as i32); + let pa = [(xp - 1, yp + h), (xp - 1, yp + h - 1)]; + let av_a = [self.pb_avail(xp, yp, pa[0].0, pa[0].1), self.pb_avail(xp, yp, pa[1].0, pa[1].1)]; + let is_scaled = av_a[0] || av_a[1]; + let mut a = None; + for k in 0..2 { + if av_a[k] && a.is_none() { + a = Some(self.blk_at(pa[k].0, pa[k].1).mv); + } + } + let pb = [(xp + w, yp - 1), (xp + w - 1, yp - 1), (xp - 1, yp - 1)]; + let mut b = None; + for p in pb { + if b.is_none() && self.pb_avail(xp, yp, p.0, p.1) { + b = Some(self.blk_at(p.0, p.1).mv); + } + } + if !is_scaled && b.is_some() { + a = b; + } + let mut list = [[0i16; 2]; 2]; + match (a, b) { + (Some(a), Some(b)) => { + list[0] = a; + if b != a { + list[1] = b; + } + } + (Some(a), None) => list[0] = a, + (None, Some(b)) => list[0] = b, + (None, None) => {} + } + list + } + + fn predict_inter(&mut self, x: usize, y: usize, n: usize, mv: [i16; 2]) { + // Inter modes are only tried in P slices, which have a reference picture. + let Some(refp) = self.refp else { return }; + for c in 0..3 { + let (px, py, pn, pw, ph) = if c == 0 { (x, y, n, self.w, self.h) } else { (x / 2, y / 2, n / 2, self.cw, self.ch) }; + let pr = PlaneRef { data: &refp[c], w: pw, h: ph }; + if c == 0 { + inter::mc_luma(pr, self.bd, px as i32, py as i32, mv, (pn, pn), &mut self.pred); + } else { + inter::mc_chroma(pr, self.bd, px as i32, py as i32, mv, (pn, pn), &mut self.pred); + } + inter::put_uni(&self.pred, pn, pn, self.bd, &mut self.rec[c][py * pw + px..], pw); + } + } + + // --------------------------------------------------------------------------------------- + // estimation helpers + + /// Best merge index by SATD of the (approximate) luma prediction. + fn best_merge(&self, x: usize, y: usize, n: usize, list: &[[i16; 2]; MAX_MERGE_CAND]) -> (usize, u64) { + let Some(me) = self.me else { return (0, u64::MAX / 4) }; + let mut buf = vec![0u16; n * n]; + let mut best = (0, u64::MAX); + for (i, &mv) in list.iter().enumerate() { + if list[..i].contains(&mv) { + continue; + } + me.approx_pred(x as i32, y as i32, n, mv, &mut buf); + let satd = satd(&self.src[0][y * self.w + x..], self.w, &buf, n, n) as u64; + let cost = satd + (self.lambda_sad * (i + 1) as f64) as u64; + if cost < best.1 { + best = (i, cost); + } + } + best + } + + /// Integer then fractional motion search. Returns (mv, mvp index, SATD cost). + fn motion_search(&self, x: usize, y: usize, n: usize, amvp: &[[i16; 2]; 2], merge: &[[i16; 2]; MAX_MERGE_CAND]) -> ([i16; 2], u8, u64) { + let Some(me) = self.me else { return (amvp[0], 0, u64::MAX / 4) }; + let (xi, yi) = (x as i32, y as i32); + let src = &self.src[0][y * self.w + x..]; + let ls = self.lambda_sad; + let mv_bits = + |mv: [i32; 2]| -> f64 { amvp.iter().map(|p| mvd_bits(mv[0] - p[0] as i32) + mvd_bits(mv[1] - p[1] as i32)).fold(f64::MAX, f64::min) }; + let (lo_x, hi_x, lo_y, hi_y) = me.int_range(xi, yi, n); + let clamp_i = |v: [i32; 2]| [v[0].clamp(lo_x, hi_x), v[1].clamp(lo_y, hi_y)]; + let sad_cost = |v: [i32; 2]| -> f64 { me.sad(src, self.w, xi + v[0], yi + v[1], n) as f64 + ls * mv_bits([v[0] * 4, v[1] * 4]) }; + // starting points + let mut best = [0i32; 2]; + let mut best_cost = sad_cost(best); + for mv in amvp.iter().chain(merge.iter()) { + let v = clamp_i([(mv[0] as i32 + 2) >> 2, (mv[1] as i32 + 2) >> 2]); + let c = sad_cost(v); + if c < best_cost { + best_cost = c; + best = v; + } + } + // square pattern search with decreasing steps + let mut step = 16; + while step >= 1 { + let mut moved = true; + let mut iters = 0; + while moved && iters < 4 { + moved = false; + iters += 1; + let center = best; + for (dx, dy) in [(-1, -1), (0, -1), (1, -1), (-1, 0), (1, 0), (-1, 1), (0, 1), (1, 1)] { + let v = clamp_i([center[0] + dx * step, center[1] + dy * step]); + if v == center { + continue; + } + let c = sad_cost(v); + if c < best_cost { + best_cost = c; + best = v; + moved = true; + } + } + } + step /= 2; + } + // fractional refinement with SATD on the approximate interpolated planes + let mut buf = vec![0u16; n * n]; + let mut satd_cost = |mv: [i32; 2]| -> f64 { + me.approx_pred(xi, yi, n, [mv[0] as i16, mv[1] as i16], &mut buf); + satd(src, self.w, &buf, n, n) as f64 + ls * mv_bits(mv) + }; + let mut bq = [best[0] * 4, best[1] * 4]; + let mut bc = satd_cost(bq); + for step in [2, 1] { + let center = bq; + for (dx, dy) in [(-1, -1), (0, -1), (1, -1), (-1, 0), (1, 0), (-1, 1), (0, 1), (1, 1)] { + let v = [center[0] + dx * step, center[1] + dy * step]; + let c = satd_cost(v); + if c < bc { + bc = c; + bq = v; + } + } + } + let mv = [bq[0] as i16, bq[1] as i16]; + let bits = |p: &[i16; 2]| mvd_bits(bq[0] - p[0] as i32) + mvd_bits(bq[1] - p[1] as i32); + let mvp = if bits(&amvp[1]) < bits(&amvp[0]) { 1 } else { 0 }; + (mv, mvp, bc as u64) + } + + /// Best 2Nx2N intra luma mode by SATD (coarse grid, then refinement around the best angle). + fn search_intra_mode(&self, x: usize, y: usize, log2: u32) -> (u32, u64) { + let n = 1usize << log2; + let refs = self.build_refs(0, x, y, log2); + let mpm = self.mpm(x, y); + let mut buf = vec![0u16; n * n]; + let src = &self.src[0][y * self.w + x..]; + let mut eval = |m: u32| -> u64 { + predict_from_refs(&refs, 0, m, n, self.bd, &mut buf, n); + let bits = match mpm.iter().position(|&c| c == m) { + Some(0) => 2.0, + Some(_) => 3.0, + None => 6.0, + }; + satd(src, self.w, &buf, n, n) as u64 + (self.lambda_sad * bits) as u64 + }; + let mut best = (1u32, u64::MAX); + let mut tried = [false; 35]; + for &m in COARSE_MODES.iter().chain(mpm.iter()) { + if !tried[m as usize] { + tried[m as usize] = true; + let c = eval(m); + if c < best.1 { + best = (m, c); + } + } + } + if best.0 >= 2 { + for d in [2i32, 1] { + let center = best.0 as i32; + for m in [center - d, center + d] { + if (2..=34).contains(&m) && !tried[m as usize] { + tried[m as usize] = true; + let c = eval(m as u32); + if c < best.1 { + best = (m as u32, c); + } + } + } + } + } + best + } + + /// Modes of the four 4x4 PUs of an NxN intra CU, chosen sequentially (each PU reconstructed + /// before the next one is searched, as in the real coding order). + fn search_nxn_modes(&mut self, x: usize, y: usize) -> [u8; 4] { + let mut modes = [1u8; 4]; + self.fill(x, y, 8, |b| { + b.flags = F_INTRA; + b.ipm = 1; + }); + for k in 0..4 { + let (px, py) = (x + (k & 1) * 4, y + (k >> 1) * 4); + let (m, _) = self.search_intra_mode(px, py, 2); + modes[k] = m as u8; + self.fill(px, py, 4, |b| b.ipm = m as u8); + self.tbs.clear(); + self.coefs.clear(); + self.code_tb(0, px, py, 2, Some(m), (px, py)); + } + modes + } +} + +/// Filter (luma) and predict an intra block from substituted references. +fn predict_from_refs(refs: &Refs, c: usize, mode: u32, n: usize, bd: u32, out: &mut [u16], os: usize) { + if c == 0 { + let mut r = refs.clone(); + intra::filter(&mut r, mode, n, true, true, bd); + intra::predict(&r, mode, n, true, bd, out, os); + } else { + intra::predict(refs, mode, n, false, bd, out, os); + } +} + +/// scanIdx (7.4.9.11) for an intra prediction mode. +fn scan_idx_for_mode(m: u32) -> u8 { + if (6..=14).contains(&m) { + 2 + } else if (22..=30).contains(&m) { + 1 + } else { + 0 + } +} + +/// Approximate bits of one motion vector difference component. +fn mvd_bits(v: i32) -> f64 { + if v == 0 { 1.0 } else { (2 * (32 - (v.unsigned_abs()).leading_zeros()) + 1) as f64 } +} + +fn write_merge_idx(s: &mut S, idx: usize) { + if MAX_MERGE_CAND > 1 { + s.decision(MERGE_IDX, (idx > 0) as u32); + if idx > 0 { + let mut i = 1; + while i < MAX_MERGE_CAND - 1 { + let b = idx > i; + s.bypass(b as u32); + if !b { + break; + } + i += 1; + } + } + } +} + +/// mvd_coding() (7.3.8.9). +fn write_mvd(s: &mut S, d: [i32; 2]) { + let g0 = [d[0] != 0, d[1] != 0]; + s.decision(ABS_MVD_GT0, g0[0] as u32); + s.decision(ABS_MVD_GT0, g0[1] as u32); + for k in 0..2 { + if g0[k] { + s.decision(ABS_MVD_GT1, (d[k].abs() > 1) as u32); + } + } + for k in 0..2 { + if g0[k] { + let a = d[k].unsigned_abs(); + if a > 1 { + // abs_mvd_minus2: EG1 + let mut v = a - 2; + let mut kk = 1; + while v >= (1 << kk) { + s.bypass(1); + v -= 1 << kk; + kk += 1; + } + s.bypass(0); + s.bypass_bits(v, kk); + } + s.bypass((d[k] < 0) as u32); + } + } +} + +/// coeff_abs_level_remaining binarisation (9.3.3.11): TR prefix up to 3 with Rice suffix, then +/// an escape with an Exp-Golomb-like suffix. +fn write_remaining(s: &mut S, v: u32, rice: u32) { + if v < (4 << rice) { + let p = v >> rice; + for _ in 0..p { + s.bypass(1); + } + s.bypass(0); + s.bypass_bits(v & ((1 << rice) - 1), rice); + } else { + let u = v - (2 << rice); + let kr = 31 - u.leading_zeros(); // k + rice + let k = kr - rice; + for _ in 0..k + 3 { + s.bypass(1); + } + s.bypass(0); + s.bypass_bits(u - (1 << kr), kr); + } +} + +/// last_sig_coeff_{x,y}_prefix value and suffix for a position. +fn last_prefix(v: u32) -> (u32, u32, u32) { + if v < 4 { + return (v, 0, 0); + } + let mut p = 4; + loop { + let nb = (p >> 1) - 1; + let min = (1 << nb) * (2 + (p & 1)); + let next_nb = ((p + 1) >> 1) - 1; + let next = (1 << next_nb) * (2 + ((p + 1) & 1)); + if v < next { + return (p, v - min, nb); + } + p += 1; + } +} + +/// Context increment for significant_coeff_flag (H.265 9.3.4.2). +fn significance_context(group: (usize, usize), pos: (usize, usize), log2: u32, prev_csbf: u8, c: usize, scan_idx: usize) -> usize { + let (xs, ys) = group; + let (xp, yp) = pos; + let (xc, yc) = (xs * 4 + xp, ys * 4 + yp); + if log2 == 2 { + CTX_IDX_MAP[(yc << 2) + xc] as usize + } else if xc + yc == 0 { + 0 + } else { + let mut sc = match prev_csbf { + 0 => { + if xp + yp == 0 { + 2 + } else if xp + yp < 3 { + 1 + } else { + 0 + } + } + 1 => { + if yp == 0 { + 2 + } else if yp == 1 { + 1 + } else { + 0 + } + } + 2 => { + if xp == 0 { + 2 + } else if xp == 1 { + 1 + } else { + 0 + } + } + _ => 2, + }; + if c == 0 { + if xs + ys > 0 { + sc += 3; + } + sc += if log2 == 3 { if scan_idx == 0 { 9 } else { 15 } } else { 21 }; + } else { + sc += if log2 == 3 { 9 } else { 12 }; + } + sc + } +} + +/// Absolute levels, signs and Rice remainders for one significant coefficient group. +fn write_coefficient_levels(s: &mut S, sig: &[u8], at: impl Fn(usize) -> i32, i: usize, c: usize, prev_g1: &mut Option) { + let nsig = sig.len(); + let abs_at = |k: usize| at(sig[k] as usize).unsigned_abs(); + let mut ctx_set = if i == 0 || c > 0 { 0 } else { 2 }; + if *prev_g1 == Some(0) { + ctx_set += 1; + } + let mut g1ctx = 1u32; + let mut gt1 = [0u8; 16]; + let mut first_g1: Option = None; + let c_off_g1 = if c > 0 { 16 } else { 0 }; + for k in 0..nsig.min(8) { + let f = abs_at(k) > 1; + s.decision(GT1 + ctx_set * 4 + g1ctx.min(3) as usize + c_off_g1, f as u32); + gt1[k] = f as u8; + if f { + g1ctx = 0; + if first_g1.is_none() { + first_g1 = Some(k); + } + } else if g1ctx > 0 { + g1ctx += 1; + } + } + *prev_g1 = Some(g1ctx); + let mut gt2 = 0u32; + if let Some(k) = first_g1 { + gt2 = (abs_at(k) > 2) as u32; + s.decision(GT2 + ctx_set + if c > 0 { 4 } else { 0 }, gt2); + } + // sign data hiding: the sign of the first coefficient in scan order (sig[nsig - 1]) is + // inferred from the parity of the sum of absolute levels + let hidden = sig[0] as i32 - sig[nsig - 1] as i32 > 3; + let nsigns = if hidden { nsig - 1 } else { nsig }; + for k in 0..nsigns { + s.bypass((at(sig[k] as usize) < 0) as u32); + } + if hidden { + let sum: u32 = (0..nsig).map(abs_at).sum(); + debug_assert_eq!(at(sig[nsig - 1] as usize) < 0, sum & 1 == 1, "sign hiding parity"); + } + let mut rice = 0u32; + for k in 0..nsig { + let mut base = 1 + gt1[k] as u32; + if Some(k) == first_g1 { + base += gt2; + } + let thresh = if k < 8 { if Some(k) == first_g1 { 3 } else { 2 } } else { 1 }; + if base == thresh { + let level = abs_at(k); + write_remaining(s, level - base, rice); + if level > 3 * (1 << rice) { + rice = (rice + 1).min(4); + } + } + } +} + +/// residual_coding() (7.3.8.11) of one transform block of levels (row-major, n x n). No transform +/// skip; signs use sign data hiding where the coefficient span permits it. +fn residual_coding(s: &mut S, lv: &[i32], log2: u32, c: usize, scan_idx: usize) { + let n = 1usize << log2; + let log2sb = log2 - 2; + let sb_w = 1usize << log2sb; + let sb_scan = scan_order(log2sb, scan_idx); + let pos_scan = &SCAN_4[scan_idx]; + let at = |xs: usize, ys: usize, p: usize| lv[(ys * 4 + pos_scan[p].1 as usize) * n + xs * 4 + pos_scan[p].0 as usize]; + // last significant coefficient in scan order + let mut last = None; + 'outer: for i in (0..sb_scan.len()).rev() { + let (xs, ys) = (sb_scan[i].0 as usize, sb_scan[i].1 as usize); + for p in (0..16).rev() { + if at(xs, ys, p) != 0 { + last = Some((i, p)); + break 'outer; + } + } + } + // Callers only code blocks with a nonzero level (coded_block_flag = 1). + let Some((last_sb, last_pos)) = last else { return }; + let lx = sb_scan[last_sb].0 as u32 * 4 + pos_scan[last_pos].0 as u32; + let ly = sb_scan[last_sb].1 as u32 * 4 + pos_scan[last_pos].1 as u32; + let (cx, cy) = if scan_idx == 2 { (ly, lx) } else { (lx, ly) }; + let cmax = (log2 << 1) - 1; + let (off, shift) = if c == 0 { (3 * (log2 - 2) + ((log2 - 1) >> 2), (log2 + 1) >> 2) } else { (15, log2 - 2) }; + let (px, sx, nbx) = last_prefix(cx); + let (py, sy, nby) = last_prefix(cy); + for (p, base) in [(px, LAST_X_PREFIX), (py, LAST_Y_PREFIX)] { + for i in 0..p { + s.decision(base + (off + (i >> shift)) as usize, 1); + } + if p < cmax { + s.decision(base + (off + (p >> shift)) as usize, 0); + } + } + if px > 3 { + s.bypass_bits(sx, nbx); + } + if py > 3 { + s.bypass_bits(sy, nby); + } + let mut csbf = [0u8; 64]; + let mut prev_g1: Option = None; + let c_off_sig = if c == 0 { 0 } else { 27 }; + for i in (0..=last_sb).rev() { + let (xs, ys) = (sb_scan[i].0 as usize, sb_scan[i].1 as usize); + let right = if xs + 1 < sb_w { csbf[ys * 8 + xs + 1] } else { 0 }; + let below = if ys + 1 < sb_w { csbf[(ys + 1) * 8 + xs] } else { 0 }; + let mut infer_dc = false; + if i < last_sb && i > 0 { + let has = (0..16).any(|p| at(xs, ys, p) != 0); + let inc = (right | below).min(1) as usize + if c > 0 { 2 } else { 0 }; + s.decision(CODED_SUB_BLOCK + inc, has as u32); + csbf[ys * 8 + xs] = has as u8; + infer_dc = true; + } else { + csbf[ys * 8 + xs] = 1; + } + if csbf[ys * 8 + xs] == 0 { + continue; + } + let mut sig = [0u8; 16]; + let mut nsig = 0; + let start: i32 = if i == last_sb { + sig[0] = last_pos as u8; + nsig = 1; + last_pos as i32 - 1 + } else { + 15 + }; + let prev_csbf = right + (below << 1); + let mut np = start; + while np >= 0 { + let p = np as usize; + let (xp, yp) = (pos_scan[p].0 as usize, pos_scan[p].1 as usize); + let (xc, yc) = (xs * 4 + xp, ys * 4 + yp); + let v = lv[yc * n + xc] != 0; + if np > 0 || !infer_dc { + let sig_ctx = significance_context((xs, ys), (xp, yp), log2, prev_csbf, c, scan_idx); + s.decision(SIG_COEFF + c_off_sig + sig_ctx, v as u32); + if v { + sig[nsig] = p as u8; + nsig += 1; + infer_dc = false; + } + } else { + debug_assert!(v, "inferred DC coefficient must be non-zero"); + sig[nsig] = 0; + nsig += 1; + } + np -= 1; + } + if nsig == 0 { + continue; + } + write_coefficient_levels(s, &sig[..nsig], |p| at(xs, ys, p), i, c, &mut prev_g1); + } +} + +/// Sum of absolute Hadamard-transformed differences (8x8 blocks, 4x4 for 4-sample blocks). +pub fn satd(src: &[u16], ss: usize, pred: &[u16], ps: usize, n: usize) -> u32 { + if n == 4 { + return satd4(src, ss, pred, ps); + } + let mut total = 0; + for by in (0..n).step_by(8) { + for bx in (0..n).step_by(8) { + total += satd8(&src[by * ss + bx..], ss, &pred[by * ps + bx..], ps); + } + } + total +} + +fn satd4(src: &[u16], ss: usize, pred: &[u16], ps: usize) -> u32 { + let mut d = [0i32; 16]; + for y in 0..4 { + for x in 0..4 { + d[y * 4 + x] = src[y * ss + x] as i32 - pred[y * ps + x] as i32; + } + } + let mut m = [0i32; 16]; + for y in 0..4 { + let r = &d[y * 4..y * 4 + 4]; + let (a, b, c, e) = (r[0] + r[3], r[1] + r[2], r[1] - r[2], r[0] - r[3]); + m[y * 4] = a + b; + m[y * 4 + 1] = e + c; + m[y * 4 + 2] = a - b; + m[y * 4 + 3] = e - c; + } + let mut sum = 0; + for x in 0..4 { + let (r0, r1, r2, r3) = (m[x], m[4 + x], m[8 + x], m[12 + x]); + let (a, b, c, e) = (r0 + r3, r1 + r2, r1 - r2, r0 - r3); + sum += (a + b).abs() + (e + c).abs() + (a - b).abs() + (e - c).abs(); + } + ((sum + 1) >> 1) as u32 +} + +fn hadamard8(v: &mut [i32; 8]) { + let mut t = *v; + let mut h = 1; + while h < 8 { + for i in (0..8).step_by(h * 2) { + for j in i..i + h { + let (a, b) = (t[j], t[j + h]); + t[j] = a + b; + t[j + h] = a - b; + } + } + h *= 2; + } + *v = t; +} + +fn satd8(src: &[u16], ss: usize, pred: &[u16], ps: usize) -> u32 { + let mut m = [[0i32; 8]; 8]; + for y in 0..8 { + for x in 0..8 { + m[y][x] = src[y * ss + x] as i32 - pred[y * ps + x] as i32; + } + hadamard8(&mut m[y]); + } + let mut sum = 0; + for x in 0..8 { + let mut col = [0i32; 8]; + for y in 0..8 { + col[y] = m[y][x]; + } + hadamard8(&mut col); + sum += col.iter().map(|v| v.abs()).sum::(); + } + ((sum + 2) >> 2) as u32 +} + +#[cfg(test)] +mod tests { + use super::*; + + #[test] + fn last_prefix_groups() { + assert_eq!(last_prefix(3), (3, 0, 0)); + assert_eq!(last_prefix(4), (4, 0, 1)); + assert_eq!(last_prefix(5), (4, 1, 1)); + assert_eq!(last_prefix(7), (5, 1, 1)); + assert_eq!(last_prefix(8), (6, 0, 2)); + assert_eq!(last_prefix(15), (7, 3, 2)); + assert_eq!(last_prefix(16), (8, 0, 3)); + assert_eq!(last_prefix(31), (9, 7, 3)); + } +} diff --git a/crates/hevcenc/src/inter.rs b/crates/hevcenc/src/inter.rs new file mode 100644 index 0000000000..03bf50ffb2 --- /dev/null +++ b/crates/hevcenc/src/inter.rs @@ -0,0 +1,157 @@ +//! Fractional sample interpolation (8.5.3.3.3) and default weighted prediction (8-262), mirrored +//! from the filmcraft-hevc decoder. Reference sample fetches clamp to the picture (8-228, 8-229). + +use crate::tables::{CHROMA_FILTER, LUMA_FILTER}; + +/// One plane of a reference picture. +#[derive(Clone, Copy)] +pub struct PlaneRef<'a> { + pub data: &'a [u16], + pub w: usize, + pub h: usize, +} + +impl PlaneRef<'_> { + /// Copy a w x h window at (x0, y0) with edge replication. + fn window(&self, x0: i32, y0: i32, w: usize, h: usize, out: &mut [i16], os: usize) { + let (pw, ph) = (self.w as i32, self.h as i32); + let inside_x = x0 >= 0 && x0 + w as i32 <= pw; + for r in 0..h { + let yy = (y0 + r as i32).clamp(0, ph - 1) as usize; + let row = &self.data[yy * self.w..yy * self.w + self.w]; + let dst = &mut out[r * os..r * os + w]; + if inside_x { + for (d, &s) in dst.iter_mut().zip(&row[x0 as usize..x0 as usize + w]) { + *d = s as i16; + } + } else { + for (c, d) in dst.iter_mut().enumerate() { + *d = row[(x0 + c as i32).clamp(0, pw - 1) as usize] as i16; + } + } + } + } +} + +/// Luma prediction samples (14-bit intermediate) of a w x h block at (x, y) displaced by the +/// quarter-sample vector `mv`, into `out` (stride w). +pub fn mc_luma(f: PlaneRef, bd: u32, x: i32, y: i32, mv: [i16; 2], size: (usize, usize), out: &mut [i16]) { + let (w, h) = size; + let (fx, fy) = ((mv[0] & 3) as usize, (mv[1] & 3) as usize); + let xi = x + (mv[0] as i32 >> 2); + let yi = y + (mv[1] as i32 >> 2); + let shift1 = bd.min(12) - 8; + let shift3 = 14 - bd; + let ws = w + 7; + let mut win = vec![0i16; ws * (h + 7)]; + if fx == 0 && fy == 0 { + f.window(xi, yi, w, h, &mut win, ws); + for r in 0..h { + for c in 0..w { + out[r * w + c] = win[r * ws + c] << shift3; + } + } + return; + } + f.window(xi - 3, yi - 3, w + 7, h + 7, &mut win, ws); + let hf = &LUMA_FILTER[fx]; + let vf = &LUMA_FILTER[fy]; + if fy == 0 { + for r in 0..h { + let row = &win[(r + 3) * ws..]; + for c in 0..w { + let s: i32 = (0..8).map(|k| hf[k] as i32 * row[c + k] as i32).sum(); + out[r * w + c] = (s >> shift1) as i16; + } + } + } else if fx == 0 { + for r in 0..h { + for c in 0..w { + let s: i32 = (0..8).map(|k| vf[k] as i32 * win[(r + k) * ws + c + 3] as i32).sum(); + out[r * w + c] = (s >> shift1) as i16; + } + } + } else { + let mut tmp = vec![0i16; ws * (h + 7)]; + for r in 0..h + 7 { + let row = &win[r * ws..]; + for c in 0..w { + let s: i32 = (0..8).map(|k| hf[k] as i32 * row[c + k] as i32).sum(); + tmp[r * ws + c] = (s >> shift1) as i16; + } + } + for r in 0..h { + for c in 0..w { + let s: i32 = (0..8).map(|k| vf[k] as i32 * tmp[(r + k) * ws + c] as i32).sum(); + out[r * w + c] = (s >> 6) as i16; + } + } + } +} + +/// Chroma prediction samples of a w x h block at chroma position (x, y) with a 1/8-sample vector. +pub fn mc_chroma(f: PlaneRef, bd: u32, x: i32, y: i32, mv: [i16; 2], size: (usize, usize), out: &mut [i16]) { + let (w, h) = size; + let (fx, fy) = ((mv[0] & 7) as usize, (mv[1] & 7) as usize); + let xi = x + (mv[0] as i32 >> 3); + let yi = y + (mv[1] as i32 >> 3); + let shift1 = bd.min(12) - 8; + let shift3 = 14 - bd; + let ws = w + 7; + let mut win = vec![0i16; ws * (h + 7)]; + if fx == 0 && fy == 0 { + f.window(xi, yi, w, h, &mut win, ws); + for r in 0..h { + for c in 0..w { + out[r * w + c] = win[r * ws + c] << shift3; + } + } + return; + } + f.window(xi - 1, yi - 1, w + 3, h + 3, &mut win, ws); + let hf = &CHROMA_FILTER[fx]; + let vf = &CHROMA_FILTER[fy]; + if fy == 0 { + for r in 0..h { + let row = &win[(r + 1) * ws..]; + for c in 0..w { + let s: i32 = (0..4).map(|k| hf[k] as i32 * row[c + k] as i32).sum(); + out[r * w + c] = (s >> shift1) as i16; + } + } + } else if fx == 0 { + for r in 0..h { + for c in 0..w { + let s: i32 = (0..4).map(|k| vf[k] as i32 * win[(r + k) * ws + c + 1] as i32).sum(); + out[r * w + c] = (s >> shift1) as i16; + } + } + } else { + let mut tmp = vec![0i16; ws * (h + 7)]; + for r in 0..h + 3 { + let row = &win[r * ws..]; + for c in 0..w { + let s: i32 = (0..4).map(|k| hf[k] as i32 * row[c + k] as i32).sum(); + tmp[r * ws + c] = (s >> shift1) as i16; + } + } + for r in 0..h { + for c in 0..w { + let s: i32 = (0..4).map(|k| vf[k] as i32 * tmp[(r + k) * ws + c] as i32).sum(); + out[r * w + c] = (s >> 6) as i16; + } + } + } +} + +/// Default weighted prediction, one list (8-262): 14-bit samples to `bd`-bit samples. +pub fn put_uni(p: &[i16], w: usize, h: usize, bd: u32, dst: &mut [u16], ds: usize) { + let shift = 14 - bd; + let off = if shift > 0 { 1 << (shift - 1) } else { 0 }; + let max = (1i32 << bd) - 1; + for r in 0..h { + for c in 0..w { + dst[r * ds + c] = ((p[r * w + c] as i32 + off) >> shift).clamp(0, max) as u16; + } + } +} diff --git a/crates/hevcenc/src/intra.rs b/crates/hevcenc/src/intra.rs new file mode 100644 index 0000000000..f1fe9e4cbf --- /dev/null +++ b/crates/hevcenc/src/intra.rs @@ -0,0 +1,254 @@ +//! Intra sample prediction (8.4.4.2), mirrored from the filmcraft-hevc decoder so the encoder +//! reconstructs exactly like a decoder: reference substitution, filtering (incl. strong intra +//! smoothing), planar, DC and the 33 angular modes. + +use crate::tables::{INTRA_PRED_ANGLE, INV_ANGLE}; + +/// Reference samples of an nTbS block: `left[0]` = `top[0]` = p[-1][-1], `left[1 + y]` = p[-1][y], +/// `top[1 + x]` = p[x][-1] for x, y = 0..2*nTbS-1. +#[derive(Clone)] +pub struct Refs { + pub left: [u16; 129], + pub top: [u16; 129], +} + +impl Default for Refs { + fn default() -> Self { + Refs { left: [0; 129], top: [0; 129] } + } +} + +/// Substitution process (8.4.4.2.2). `avail_left[1 + y]` / `avail_top[1 + x]` (index 0 = corner) tell +/// which samples are available; unavailable ones are replaced. +pub fn substitute(r: &mut Refs, avail_left: &[bool; 129], avail_top: &[bool; 129], n: usize, bit_depth: u32) { + let n2 = 2 * n; + // Sequence order: p[-1][2n-1] .. p[-1][-1], p[0][-1] .. p[2n-1][-1] + let any = avail_left[..=n2].iter().any(|&a| a) || avail_top[1..=n2].iter().any(|&a| a); + if !any { + let v = 1u16 << (bit_depth - 1); + r.left[..=n2].fill(v); + r.top[..=n2].fill(v); + return; + } + if !avail_left[n2] { + // search upwards then along the top + let mut found = None; + for y in (0..n2).rev() { + if avail_left[y] { + found = Some(r.left[y]); + break; + } + } + if found.is_none() { + for x in 1..=n2 { + if avail_top[x] { + found = Some(r.top[x]); + break; + } + } + } + r.left[n2] = found.unwrap_or(0); + } + for y in (0..n2).rev() { + if !avail_left[y] { + r.left[y] = r.left[y + 1]; + } + } + r.top[0] = r.left[0]; + for x in 1..=n2 { + if !avail_top[x] { + r.top[x] = r.top[x - 1]; + } + } +} + +/// Filtering process of neighbouring samples (8.4.4.2.3) for luma (and 4:4:4 chroma). +pub fn filter(r: &mut Refs, mode: u32, n: usize, strong_enabled: bool, is_luma: bool, bit_depth: u32) { + if mode == 1 || n == 4 { + return; + } + let min_dist = (mode as i32 - 26).abs().min((mode as i32 - 10).abs()); + let thres = match n { + 8 => 7, + 16 => 1, + _ => 0, + }; + if min_dist <= thres { + return; + } + let n2 = 2 * n; + let c = r.left[0] as i32; + if strong_enabled && is_luma && n == 32 { + let (bl, tr) = (r.left[n2] as i32, r.top[n2] as i32); + let thr = 1 << (bit_depth - 5); + if (c + tr - 2 * r.top[n] as i32).abs() < thr && (c + bl - 2 * r.left[n] as i32).abs() < thr { + for i in 0..63 { + r.left[1 + i] = (((63 - i as i32) * c + (i as i32 + 1) * bl + 32) >> 6) as u16; + r.top[1 + i] = (((63 - i as i32) * c + (i as i32 + 1) * tr + 32) >> 6) as u16; + } + return; + } + } + let (l, t) = (r.left, r.top); + let corner = ((l[1] as u32 + 2 * c as u32 + t[1] as u32 + 2) >> 2) as u16; + for i in 1..n2 { + r.left[i] = ((l[i - 1] as u32 + 2 * l[i] as u32 + l[i + 1] as u32 + 2) >> 2) as u16; + r.top[i] = ((t[i - 1] as u32 + 2 * t[i] as u32 + t[i + 1] as u32 + 2) >> 2) as u16; + } + r.left[0] = corner; + r.top[0] = corner; +} + +/// Predict an n x n block with `mode` into `out` (stride `os`). `edge_filters`: DC / horizontal / +/// vertical boundary smoothing (luma blocks smaller than 32x32). +pub fn predict(r: &Refs, mode: u32, n: usize, edge_filters: bool, bit_depth: u32, out: &mut [u16], os: usize) { + let max = (1i32 << bit_depth) - 1; + match mode { + 0 => { + // planar + let log2 = n.trailing_zeros(); + let tr = r.top[1 + n] as i32; + let bl = r.left[1 + n] as i32; + for y in 0..n { + let l = r.left[1 + y] as i32; + for x in 0..n { + let t = r.top[1 + x] as i32; + let v = ((n - 1 - x) as i32 * l + (x as i32 + 1) * tr + (n - 1 - y) as i32 * t + (y as i32 + 1) * bl + n as i32) >> (log2 + 1); + out[y * os + x] = v as u16; + } + } + } + 1 => { + let log2 = n.trailing_zeros(); + let sum: u32 = r.top[1..=n].iter().map(|&v| v as u32).sum::() + r.left[1..=n].iter().map(|&v| v as u32).sum::(); + let dc = ((sum + n as u32) >> (log2 + 1)) as u16; + for y in 0..n { + out[y * os..y * os + n].fill(dc); + } + if edge_filters && n < 32 { + let d = dc as u32; + out[0] = ((r.left[1] as u32 + 2 * d + r.top[1] as u32 + 2) >> 2) as u16; + for x in 1..n { + out[x] = ((r.top[1 + x] as u32 + 3 * d + 2) >> 2) as u16; + } + for y in 1..n { + out[y * os] = ((r.left[1 + y] as u32 + 3 * d + 2) >> 2) as u16; + } + } + } + _ => { + let angle = INTRA_PRED_ANGLE[mode as usize] as i32; + // reference array ref[-n..=2n] stored at offset n + let mut refa = [0i32; 3 * 64 + 1]; + let base = n as i32; + let (main, side) = if mode >= 18 { (&r.top, &r.left) } else { (&r.left, &r.top) }; + for x in 0..=n { + refa[(base + x as i32) as usize] = main[x] as i32; // ref[x] = p[-1+x][-1] + } + if angle < 0 { + let lim = (n as i32 * angle) >> 5; + if lim < -1 { + let inv = INV_ANGLE[(mode - 11) as usize] as i32; + for x in lim..=-1 { + let idx = -1 + ((x * inv + 128) >> 8); + refa[(base + x) as usize] = side[(idx + 1) as usize] as i32; + } + } + } else { + for x in n + 1..=2 * n { + refa[(base + x as i32) as usize] = main[x] as i32; + } + } + let horizontal = mode < 18; + for j in 0..n { + let pos = (j as i32 + 1) * angle; + let idx = pos >> 5; + let fact = pos & 31; + for i in 0..n { + let k = (base + i as i32 + idx + 1) as usize; + let v = if fact != 0 { ((32 - fact) * refa[k] + fact * refa[k + 1] + 16) >> 5 } else { refa[k] }; + if horizontal { + out[i * os + j] = v as u16; + } else { + out[j * os + i] = v as u16; + } + } + } + if edge_filters && n < 32 { + if mode == 26 { + for y in 0..n { + let v = r.top[1] as i32 + ((r.left[1 + y] as i32 - r.left[0] as i32) >> 1); + out[y * os] = v.clamp(0, max) as u16; + } + } else if mode == 10 { + for x in 0..n { + let v = r.left[1] as i32 + ((r.top[1 + x] as i32 - r.top[0] as i32) >> 1); + out[x] = v.clamp(0, max) as u16; + } + } + } + } + } +} + +#[cfg(test)] +mod tests { + use super::*; + + fn refs(f: impl Fn(i32) -> u16) -> Refs { + let mut r = Refs::default(); + r.left[0] = f(0); + r.top[0] = f(0); + for i in 1..129 { + r.left[i] = f(-(i as i32)); + r.top[i] = f(i as i32); + } + r + } + + #[test] + fn dc_and_planar_flat() { + let r = refs(|_| 100); + let mut out = [0u16; 64]; + predict(&r, 1, 8, true, 8, &mut out, 8); + assert!(out.iter().all(|&v| v == 100)); + predict(&r, 0, 8, true, 8, &mut out, 8); + assert!(out.iter().all(|&v| v == 100)); + } + + #[test] + fn pure_vertical_and_horizontal() { + let r = refs(|i| (128 + i) as u16); + let mut out = [0u16; 16]; + predict(&r, 26, 4, false, 8, &mut out, 4); + for y in 0..4 { + for x in 0..4 { + assert_eq!(out[y * 4 + x], r.top[1 + x]); + } + } + predict(&r, 10, 4, false, 8, &mut out, 4); + for y in 0..4 { + for x in 0..4 { + assert_eq!(out[y * 4 + x], r.left[1 + y]); + } + } + // diagonal mode 34 reads top[x + y + 2] + predict(&r, 34, 4, false, 8, &mut out, 4); + assert_eq!(out[4 + 1], r.top[1 + 1 + 1 + 1]); + } + + #[test] + fn substitution() { + let mut r = Refs::default(); + let mut al = [false; 129]; + let at = [false; 129]; + r.left[3] = 77; + al[3] = true; + substitute(&mut r, &al, &at, 4, 8); + assert!(r.left[..=8].iter().all(|&v| v == 77)); + assert!(r.top[..=8].iter().all(|&v| v == 77)); + let mut r = Refs::default(); + substitute(&mut r, &[false; 129], &[false; 129], 4, 10); + assert_eq!(r.top[5], 512); + } +} diff --git a/crates/hevcenc/src/lib.rs b/crates/hevcenc/src/lib.rs new file mode 100644 index 0000000000..5d0fc66563 --- /dev/null +++ b/crates/hevcenc/src/lib.rs @@ -0,0 +1,364 @@ +//! Clean-room pure-Rust HEVC (H.265) encoder, implemented from ITU-T Rec. H.265 (ISO/IEC 23008-2). +//! +//! Main and Main 10 profiles, 4:2:0, one slice per picture: IDR pictures and single-reference P +//! pictures (no reordering), 64x64 CTUs with rate-distortion coding-unit decisions (8x8 .. 64x64), +//! intra (35 modes, NxN at 8x8), merge / skip and AMVP inter prediction with quarter-sample motion, +//! DCT / DST transforms and CABAC. The deblocking filter is on; SAO is not used. +//! +//! ``` +//! use craft_hevcenc::{Encoder, EncoderConfig, Frame}; +//! let mut enc = Encoder::new(EncoderConfig::new(64, 48, 30, 1)).unwrap(); +//! let (y, c) = (vec![128u16; 64 * 48], vec![128u16; 32 * 24]); +//! let packet = enc.encode(&Frame { y: &y, u: &c, v: &c, y_stride: 64, uv_stride: 32 }).unwrap(); +//! assert!(packet.keyframe && !packet.data.is_empty()); +//! let _hvcc = enc.hvcc(); +//! ``` + +#![deny(clippy::unwrap_used, clippy::expect_used, clippy::panic, clippy::unimplemented, clippy::todo, clippy::unreachable)] +#![allow(clippy::needless_range_loop)] + +mod bitstream; +mod cabac; +mod deblock; +mod frame; +mod inter; +mod intra; +mod me; +mod params; +#[rustfmt::skip] +mod tables; +mod transform; + +use bitstream::{BitWriter, NAL_IDR_N_LP, NAL_TRAIL_R, nal_unit}; +use frame::{FrameCoder, SliceKind}; +use params::{LOG2_MAX_POC_LSB, LOG2_MIN_CB, MAX_MERGE_CAND, SeqParams}; + +/// Profile written in the parameter sets. +#[derive(Clone, Copy, Debug, Default, PartialEq, Eq)] +pub enum Profile { + /// Main: 8-bit 4:2:0. + #[default] + Main, + /// Main 10: 10-bit 4:2:0. + Main10, +} + +impl Profile { + pub fn bit_depth(self) -> u8 { + match self { + Profile::Main => 8, + Profile::Main10 => 10, + } + } + fn idc(self) -> u8 { + match self { + Profile::Main => 1, + Profile::Main10 => 2, + } + } +} + +/// Rate control. +#[derive(Clone, Copy, Debug, PartialEq, Eq)] +pub enum RateControl { + /// Constant QP (0–51; lower is better). + ConstantQp(u8), + /// Average bitrate target in kbit/s (frame-level QP adaptation). + Bitrate { kbps: u32 }, +} + +/// Encoder settings. +#[derive(Clone, Debug, PartialEq)] +pub struct EncoderConfig { + /// Picture size; must be even (4:2:0 cropping is in units of two luma samples). + pub width: u32, + pub height: u32, + /// Frame rate numerator / denominator (VUI timing and rate control). + pub fps_num: u32, + pub fps_den: u32, + pub profile: Profile, + /// `general_level_idc` (30 × level, e.g. 93 = level 3.1); `None` picks the lowest level that fits. + pub level_idc: Option, + pub rate: RateControl, + /// Frames between IDR pictures (1 = all intra, 0 = only the first picture). + pub keyint: u32, + /// BT.709 limited range is signalled in the VUI unless this is set. + pub full_range: bool, +} + +impl EncoderConfig { + pub fn new(width: u32, height: u32, fps_num: u32, fps_den: u32) -> Self { + EncoderConfig { + width, + height, + fps_num, + fps_den, + profile: Profile::Main, + level_idc: None, + rate: RateControl::ConstantQp(28), + keyint: 60, + full_range: false, + } + } +} + +/// One input picture, 4:2:0, samples at the profile's bit depth (8-bit values 0–255 for Main). +/// Luma is `width` x `height`, chroma `width / 2` x `height / 2`. +#[derive(Clone, Copy, Debug)] +pub struct Frame<'a> { + pub y: &'a [u16], + pub u: &'a [u16], + pub v: &'a [u16], + pub y_stride: usize, + pub uv_stride: usize, +} + +/// One coded picture (an access unit): NAL units, each prefixed by a 4-byte big-endian length +/// (ISO/IEC 14496-15 sample format; parameter sets are only in [`Encoder::hvcc`]). +#[derive(Clone, Debug, PartialEq, Eq)] +pub struct Packet { + pub data: Vec, + /// IDR picture. + pub keyframe: bool, +} + +#[derive(Debug, PartialEq, Eq)] +pub enum Error { + InvalidConfig(String), + InvalidFrame(String), +} + +impl std::fmt::Display for Error { + fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { + match self { + Error::InvalidConfig(s) => write!(f, "invalid HEVC encoder config: {s}"), + Error::InvalidFrame(s) => write!(f, "invalid HEVC input frame: {s}"), + } + } +} + +impl std::error::Error for Error {} + +/// Frame-level rate control for [`RateControl::Bitrate`]: a bits ≈ X / Qstep model per picture type +/// and a buffer that spreads over- / undershoot over the following pictures. +struct RateCtl { + target: f64, + /// sum(actual) - sum(target), bits. + buffer: f64, + /// Complexity X = bits * Qstep for I and P pictures. + x: [Option; 2], + last_qp: [i32; 2], + init_qp: i32, +} + +fn qstep(qp: i32) -> f64 { + 2f64.powf((qp as f64 - 4.0) / 6.0) +} + +impl RateCtl { + fn new(kbps: u32, cfg: &EncoderConfig) -> Self { + let fps = cfg.fps_num as f64 / cfg.fps_den.max(1) as f64; + let target = kbps as f64 * 1000.0 / fps.max(0.001); + let bpp = target / (cfg.width as f64 * cfg.height as f64); + let init_qp = (28.0 - 6.0 * (bpp / 0.08).log2()).round().clamp(10.0, 46.0) as i32; + RateCtl { target, buffer: 0.0, x: [None, None], last_qp: [init_qp - 3, init_qp], init_qp } + } + + fn qp(&self, idr: bool) -> i32 { + let t = idr as usize ^ 1; // 0 = I, 1 = P + let tp = (self.target - self.buffer / 12.0).clamp(0.3 * self.target, 2.0 * self.target); + let want = if idr { 4.0 * tp } else { tp }; + let x = match (self.x[t], self.x[0]) { + (Some(x), _) => x, + (None, Some(xi)) => xi / 5.0, + (None, None) => return if idr { self.init_qp - 3 } else { self.init_qp }, + }; + let qp = (4.0 + 6.0 * (x / want).log2()).round() as i32; + qp.clamp(self.last_qp[t] - 6, self.last_qp[t] + 6).clamp(1, 51) + } + + fn update(&mut self, idr: bool, qp: i32, bits: f64) { + let t = idr as usize ^ 1; + let x = bits * qstep(qp); + self.x[t] = Some(match self.x[t] { + Some(old) => 0.5 * old + 0.5 * x, + None => x, + }); + self.last_qp[t] = qp; + self.buffer += bits - self.target; + } +} + +/// HEVC encoder. Pictures come out in input order (no reordering: I and P only). +pub struct Encoder { + cfg: EncoderConfig, + seq: SeqParams, + sets: [Vec; 3], + frames: u64, + /// POC of the previous picture (relative to the last IDR). + poc: u32, + reference: Option<[Vec; 3]>, + recon: Option<[Vec; 3]>, + rc: Option, +} + +impl Encoder { + pub fn new(cfg: EncoderConfig) -> Result { + if cfg.width == 0 || cfg.height == 0 { + return Err(Error::InvalidConfig("empty frame".into())); + } + if !cfg.width.is_multiple_of(2) || !cfg.height.is_multiple_of(2) { + return Err(Error::InvalidConfig(format!("{}x{}: 4:2:0 HEVC needs even dimensions", cfg.width, cfg.height))); + } + if cfg.width > 16384 || cfg.height > 16384 { + return Err(Error::InvalidConfig("picture larger than 16384 samples".into())); + } + if cfg.fps_num == 0 || cfg.fps_den == 0 { + return Err(Error::InvalidConfig("zero frame rate".into())); + } + match cfg.rate { + RateControl::ConstantQp(q) if q > 51 => return Err(Error::InvalidConfig(format!("QP {q} > 51"))), + RateControl::Bitrate { kbps: 0 } => return Err(Error::InvalidConfig("zero bitrate".into())), + _ => {} + } + let min_cb = 1u32 << LOG2_MIN_CB; + let (coded_width, coded_height) = (cfg.width.div_ceil(min_cb) * min_cb, cfg.height.div_ceil(min_cb) * min_cb); + let kbps = match cfg.rate { + RateControl::Bitrate { kbps } => Some(kbps), + RateControl::ConstantQp(_) => None, + }; + let level_idc = cfg.level_idc.unwrap_or_else(|| params::choose_level(coded_width, coded_height, cfg.fps_num, cfg.fps_den, kbps)); + let seq = SeqParams { + coded_width, + coded_height, + width: cfg.width, + height: cfg.height, + bit_depth: cfg.profile.bit_depth() as u32, + profile_idc: cfg.profile.idc(), + level_idc, + full_range: cfg.full_range, + fps_num: cfg.fps_num, + fps_den: cfg.fps_den, + }; + let sets = [params::vps(&seq), params::sps(&seq), params::pps()]; + let rc = kbps.map(|k| RateCtl::new(k, &cfg)); + Ok(Encoder { cfg, seq, sets, frames: 0, poc: 0, reference: None, recon: None, rc }) + } + + pub fn config(&self) -> &EncoderConfig { + &self.cfg + } + + /// VPS, SPS, PPS NAL units (2-byte NAL header, emulation prevention, no start codes or length + /// prefixes). + pub fn parameter_sets(&self) -> [Vec; 3] { + self.sets.clone() + } + + /// HEVCDecoderConfigurationRecord (`hvcC` payload) with the parameter sets. + pub fn hvcc(&self) -> Vec { + params::hvcc(&self.seq, &self.sets) + } + + /// `general_level_idc` written in the parameter sets. + pub fn level_idc(&self) -> u8 { + self.seq.level_idc + } + + /// Reconstruction of the last encoded picture (what a decoder outputs), cropped planes + /// `[Y, Cb, Cr]` with strides `width` and `width / 2`. For tests. + #[doc(hidden)] + pub fn last_reconstruction(&self) -> Option<&[Vec; 3]> { + self.recon.as_ref() + } + + /// Copy the input into coded-size planes, replicating the right / bottom edges. + fn pad_input(&self, f: &Frame) -> Result<[Vec; 3], Error> { + let max = (1u32 << self.seq.bit_depth) - 1; + let mut out: [Vec; 3] = Default::default(); + for (c, plane) in out.iter_mut().enumerate() { + let (src, stride, w, h, cw, ch) = if c == 0 { + (f.y, f.y_stride, self.seq.width, self.seq.height, self.seq.coded_width, self.seq.coded_height) + } else { + let s = if c == 1 { f.u } else { f.v }; + (s, f.uv_stride, self.seq.width / 2, self.seq.height / 2, self.seq.coded_width / 2, self.seq.coded_height / 2) + }; + let (w, h, cw, ch) = (w as usize, h as usize, cw as usize, ch as usize); + let need = stride.checked_mul(h.saturating_sub(1)).and_then(|v| v.checked_add(w)); + if stride < w || need.is_none_or(|n| src.len() < n) { + return Err(Error::InvalidFrame(format!("plane {c} smaller than {w}x{h} (stride {stride}, len {})", src.len()))); + } + plane.reserve(cw * ch); + for y in 0..ch { + let sy = y.min(h.saturating_sub(1)); + let row = &src[sy * stride..sy * stride + w]; + plane.extend(row.iter().map(|&v| (v as u32).min(max) as u16)); + let last = plane.last().copied().unwrap_or(0); + plane.extend(std::iter::repeat_n(last, cw.saturating_sub(w))); + } + } + Ok(out) + } + + /// Encodes one picture. + pub fn encode(&mut self, frame: &Frame) -> Result { + let src = self.pad_input(frame)?; + let keyint = self.cfg.keyint as u64; + let idr = self.reference.is_none() || (keyint > 0 && self.frames.is_multiple_of(keyint)); + self.poc = if idr { 0 } else { self.poc + 1 }; + let qp = match (&self.rc, self.cfg.rate) { + (Some(rc), _) => rc.qp(idr), + (None, RateControl::ConstantQp(q)) => q as i32, + (None, RateControl::Bitrate { .. }) => 28, + }; + let (w, h) = (self.seq.coded_width as usize, self.seq.coded_height as usize); + let bd = self.seq.bit_depth; + let kind = if idr { SliceKind::I } else { SliceKind::P }; + let me = if idr { None } else { self.reference.as_ref().map(|r| me::MeRef::new(&r[0], w, h, bd)) }; + let refp = if idr { None } else { self.reference.as_ref() }; + let mut fc = FrameCoder::new(frame::FrameConfig { bit_depth: bd, width: w, height: h, kind, slice_qp: qp }, &src, refp, me.as_ref()); + + // slice_segment_header() (7.3.6.1) + let mut bw = BitWriter::new(); + bw.put(1, 1); // first_slice_segment_in_pic_flag + if idr { + bw.put(0, 1); // no_output_of_prior_pics_flag + } + bw.ue(0); // slice_pic_parameter_set_id + bw.ue(if idr { 2 } else { 1 }); // slice_type I / P + if !idr { + bw.put(self.poc & ((1 << LOG2_MAX_POC_LSB) - 1), LOG2_MAX_POC_LSB); // slice_pic_order_cnt_lsb + bw.put(1, 1); // short_term_ref_pic_set_sps_flag (the only SPS set, no index coded) + bw.put(0, 1); // num_ref_idx_active_override_flag + bw.ue(5 - MAX_MERGE_CAND as u32); // five_minus_max_num_merge_cand + } + bw.se(qp - 26); // slice_qp_delta (init_qp = 26) + // byte_alignment() + bw.put(1, 1); + bw.align_zero(); + + let ctx = cabac::init_contexts(qp, if idr { 0 } else { 1 }); + let mut enc = cabac::CabacEncoder::new(bw, ctx); + fc.encode(&mut enc); + fc.deblock(); + let rbsp = enc.finish().into_bytes(); + let nal = nal_unit(if idr { NAL_IDR_N_LP } else { NAL_TRAIL_R }, &rbsp); + let mut data = Vec::with_capacity(nal.len() + 4); + data.extend_from_slice(&(nal.len() as u32).to_be_bytes()); + data.extend_from_slice(&nal); + + if let Some(rc) = &mut self.rc { + rc.update(idr, qp, data.len() as f64 * 8.0); + } + // output picture: cropped reconstruction + let rec = std::mem::take(&mut fc.rec); + let (ow, oh) = (self.seq.width as usize, self.seq.height as usize); + let crop = |p: &[u16], stride: usize, w: usize, h: usize| -> Vec { + (0..h).flat_map(|y| p[y * stride..y * stride + w].iter().copied()).collect() + }; + self.recon = Some([crop(&rec[0], w, ow, oh), crop(&rec[1], w / 2, ow / 2, oh / 2), crop(&rec[2], w / 2, ow / 2, oh / 2)]); + self.reference = Some(rec); + self.frames += 1; + Ok(Packet { data, keyframe: idr }) + } +} diff --git a/crates/hevcenc/src/me.rs b/crates/hevcenc/src/me.rs new file mode 100644 index 0000000000..5f4cb2ce3f --- /dev/null +++ b/crates/hevcenc/src/me.rs @@ -0,0 +1,144 @@ +//! Motion search reference: the previous reconstructed luma plane padded by edge replication, plus +//! half-sample planes interpolated with the 8-tap half-sample filter. Quarter-sample positions are +//! approximated by averaging neighbouring half-grid samples. Used for estimation only; the coded +//! prediction always uses the normative interpolation in `inter`. + +use crate::tables::LUMA_FILTER; + +const MARGIN: i32 = 80; + +pub struct MeRef { + /// [integer, horizontal half, vertical half, diagonal half] samples, each `stride` x `rows`. + planes: [Vec; 4], + stride: usize, + rows: usize, + w: i32, + h: i32, +} + +impl MeRef { + pub fn new(y: &[u16], w: usize, h: usize, bd: u32) -> MeRef { + let m = MARGIN as usize; + let (stride, rows) = (w + 2 * m, h + 2 * m); + let mut pad = vec![0u16; stride * rows]; + for r in 0..rows { + let sy = (r as i32 - MARGIN).clamp(0, h as i32 - 1) as usize; + let src = &y[sy * w..sy * w + w]; + let row = &mut pad[r * stride..r * stride + stride]; + row[..m].fill(src[0]); + row[m..m + w].copy_from_slice(src); + row[m + w..].fill(src[w - 1]); + } + let max = (1i32 << bd) - 1; + let f = &LUMA_FILTER[2]; + let filt_h = |p: &[u16]| -> Vec { + let mut o = vec![0u16; stride * rows]; + for r in 0..rows { + let row = &p[r * stride..r * stride + stride]; + for x in 0..stride { + let mut s = 0i32; + for k in 0..8 { + let xx = (x as i32 - 3 + k as i32).clamp(0, stride as i32 - 1) as usize; + s += f[k] as i32 * row[xx] as i32; + } + o[r * stride + x] = ((s + 32) >> 6).clamp(0, max) as u16; + } + } + o + }; + let filt_v = |p: &[u16]| -> Vec { + let mut o = vec![0u16; stride * rows]; + let mut acc = vec![0i32; stride]; + for r in 0..rows { + acc.fill(0); + for k in 0..8 { + let rr = (r as i32 - 3 + k as i32).clamp(0, rows as i32 - 1) as usize; + let src = &p[rr * stride..rr * stride + stride]; + let coef = f[k] as i32; + for (a, &s) in acc.iter_mut().zip(src) { + *a += coef * s as i32; + } + } + for (d, &a) in o[r * stride..r * stride + stride].iter_mut().zip(&acc) { + *d = ((a + 32) >> 6).clamp(0, max) as u16; + } + } + o + }; + let hp = filt_h(&pad); + let vp = filt_v(&pad); + let dp = filt_v(&hp); + MeRef { planes: [pad, hp, vp, dp], stride, rows, w: w as i32, h: h as i32 } + } + + /// Integer motion vector range keeping an n x n block (plus filter margin) inside the padding. + pub fn int_range(&self, x: i32, y: i32, n: usize) -> (i32, i32, i32, i32) { + let n = n as i32; + let lo_x = -MARGIN + 4 - x; + let hi_x = self.w + MARGIN - n - 4 - x; + let lo_y = -MARGIN + 4 - y; + let hi_y = self.h + MARGIN - n - 4 - y; + (lo_x.max(-256), hi_x.min(256), lo_y.max(-256), hi_y.min(256)) + } + + /// SAD of the source block against the integer-position reference block at (rx, ry). + pub fn sad(&self, src: &[u16], ss: usize, rx: i32, ry: i32, n: usize) -> u32 { + let p = &self.planes[0]; + let base = (ry + MARGIN) as usize * self.stride + (rx + MARGIN) as usize; + let mut s = 0u32; + for r in 0..n { + let a = &src[r * ss..r * ss + n]; + let b = &p[base + r * self.stride..base + r * self.stride + n]; + s += a.iter().zip(b).map(|(&x, &y)| (x as i32 - y as i32).unsigned_abs()).sum::(); + } + s + } + + /// Approximate luma prediction of an n x n block at (x, y) with quarter-sample vector `mv`. + pub fn approx_pred(&self, x: i32, y: i32, n: usize, mv: [i16; 2], out: &mut [u16]) { + // per axis: one or two (half-sample fraction, integer offset) taps + let axis = |v: i32| -> [(usize, i32); 2] { + if v & 1 == 0 { + let hv = v >> 1; + [((hv & 1) as usize, hv >> 1); 2] + } else { + let (a, b) = ((v - 1) >> 1, (v + 1) >> 1); + [((a & 1) as usize, a >> 1), ((b & 1) as usize, b >> 1)] + } + }; + let (mx, my) = (mv[0] as i32, mv[1] as i32); + let ax = axis(mx); + let ay = axis(my); + let two = mx & 1 != 0 || my & 1 != 0; + let taps = [(ax[0], ay[0]), (ax[1], ay[1])]; + let ntaps = if two { 2 } else { 1 }; + let mut first = true; + for &((fx, ox), (fy, oy)) in taps.iter().take(ntaps) { + let plane = &self.planes[fx + 2 * fy]; + let (bx, by) = (x + ox + MARGIN, y + oy + MARGIN); + let inside = bx >= 0 && by >= 0 && bx as usize + n <= self.stride && by as usize + n <= self.rows; + for r in 0..n { + let dst = &mut out[r * n..r * n + n]; + if inside { + let o = (by as usize + r) * self.stride + bx as usize; + let src = &plane[o..o + n]; + if first { + dst.copy_from_slice(src); + } else { + for (d, &s) in dst.iter_mut().zip(src) { + *d = ((*d as u32 + s as u32 + 1) >> 1) as u16; + } + } + } else { + let yy = (by + r as i32).clamp(0, self.rows as i32 - 1) as usize; + for (c, d) in dst.iter_mut().enumerate() { + let xx = (bx + c as i32).clamp(0, self.stride as i32 - 1) as usize; + let s = plane[yy * self.stride + xx]; + *d = if first { s } else { ((*d as u32 + s as u32 + 1) >> 1) as u16 }; + } + } + } + first = false; + } + } +} diff --git a/crates/hevcenc/src/params.rs b/crates/hevcenc/src/params.rs new file mode 100644 index 0000000000..f1b89f731d --- /dev/null +++ b/crates/hevcenc/src/params.rs @@ -0,0 +1,262 @@ +//! Parameter sets (7.3.2.1 VPS, 7.3.2.2 SPS with E.2.1 VUI, 7.3.2.3 PPS), profile_tier_level (7.3.3), +//! level selection (Annex A, Table A.8) and the `hvcC` record (ISO/IEC 14496-15 §8.3.3.1). + +use crate::bitstream::{BitWriter, NAL_PPS, NAL_SPS, NAL_VPS, nal_unit}; + +/// Fixed coding structure of the encoder. +pub const LOG2_CTB: u32 = 6; +pub const LOG2_MIN_CB: u32 = 3; +pub const LOG2_MIN_TB: u32 = 2; +pub const LOG2_MAX_TB: u32 = 5; +pub const LOG2_MAX_POC_LSB: u32 = 8; +/// MaxNumMergeCand. +pub const MAX_MERGE_CAND: usize = 5; + +/// Sequence-level settings shared by the parameter-set writers. +#[derive(Clone, Debug)] +pub struct SeqParams { + /// Coded (padded) size, multiples of the minimum CB size. + pub coded_width: u32, + pub coded_height: u32, + /// Output (cropped) size. + pub width: u32, + pub height: u32, + pub bit_depth: u32, + pub profile_idc: u8, + pub level_idc: u8, + pub full_range: bool, + pub fps_num: u32, + pub fps_den: u32, +} + +/// (level_idc, MaxLumaPs, MaxLumaSr, MaxBR main tier in kbit/s) of Table A.8 (and A.9 for luma rates). +const LEVELS: [(u8, u64, u64, u64); 13] = [ + (30, 36_864, 552_960, 128), + (60, 122_880, 3_686_400, 1_500), + (63, 245_760, 7_372_800, 3_000), + (90, 552_960, 16_588_800, 6_000), + (93, 983_040, 33_177_600, 10_000), + (120, 2_228_224, 66_846_720, 12_000), + (123, 2_228_224, 133_693_440, 20_000), + (150, 8_912_896, 267_386_880, 25_000), + (153, 8_912_896, 534_773_760, 40_000), + (156, 8_912_896, 1_069_547_520, 60_000), + (180, 35_651_584, 1_069_547_520, 60_000), + (183, 35_651_584, 2_139_095_040, 120_000), + (186, 35_651_584, 4_278_190_080, 240_000), +]; + +/// Lowest level whose picture size, sample rate and (when known) bitrate limits fit. +pub fn choose_level(width: u32, height: u32, fps_num: u32, fps_den: u32, kbps: Option) -> u8 { + let ps = width as u64 * height as u64; + let sr = (ps as f64 * fps_num as f64 / fps_den.max(1) as f64).ceil() as u64; + for &(idc, max_ps, max_sr, max_br) in &LEVELS { + let max_dim = ((max_ps * 8) as f64).sqrt() as u64; + if ps <= max_ps && (width as u64) <= max_dim && (height as u64) <= max_dim && sr <= max_sr && kbps.is_none_or(|k| k as u64 <= max_br) { + return idc; + } + } + 186 +} + +fn profile_tier_level(w: &mut BitWriter, p: &SeqParams) { + w.put(0, 2); // general_profile_space + w.put(0, 1); // general_tier_flag (Main tier) + w.put(p.profile_idc as u32, 5); + w.put(compat_flags(p.profile_idc), 32); + w.put(1, 1); // general_progressive_source_flag + w.put(0, 1); // general_interlaced_source_flag + w.put(0, 1); // general_non_packed_constraint_flag + w.put(1, 1); // general_frame_only_constraint_flag + w.put(0, 32); // general_reserved_zero_43bits ... + w.put(0, 11); + w.put(0, 1); // general_inbld_flag / reserved + w.put(p.level_idc as u32, 8); + // sps_max_sub_layers_minus1 == 0: no sub-layer flags +} + +/// general_profile_compatibility_flag[j] as a 32-bit word (flag 0 is the MSB). A Main stream is also +/// Main 10 compatible. +fn compat_flags(profile_idc: u8) -> u32 { + let mut f = 1u32 << (31 - profile_idc as u32); + if profile_idc == 1 { + f |= 1 << (31 - 2); + } + f +} + +pub fn vps(p: &SeqParams) -> Vec { + let mut w = BitWriter::new(); + w.put(0, 4); // vps_video_parameter_set_id + w.put(1, 1); // vps_base_layer_internal_flag + w.put(1, 1); // vps_base_layer_available_flag + w.put(0, 6); // vps_max_layers_minus1 + w.put(0, 3); // vps_max_sub_layers_minus1 + w.put(1, 1); // vps_temporal_id_nesting_flag + w.put(0xffff, 16); + profile_tier_level(&mut w, p); + w.put(1, 1); // vps_sub_layer_ordering_info_present_flag + w.ue(1); // vps_max_dec_pic_buffering_minus1 + w.ue(0); // vps_max_num_reorder_pics + w.ue(0); // vps_max_latency_increase_plus1 + w.put(0, 6); // vps_max_layer_id + w.ue(0); // vps_num_layer_sets_minus1 + w.put(0, 1); // vps_timing_info_present_flag + w.put(0, 1); // vps_extension_flag + w.trailing(); + nal_unit(NAL_VPS, &w.into_bytes()) +} + +pub fn sps(p: &SeqParams) -> Vec { + let mut w = BitWriter::new(); + w.put(0, 4); // sps_video_parameter_set_id + w.put(0, 3); // sps_max_sub_layers_minus1 + w.put(1, 1); // sps_temporal_id_nesting_flag + profile_tier_level(&mut w, p); + w.ue(0); // sps_seq_parameter_set_id + w.ue(1); // chroma_format_idc 4:2:0 + w.ue(p.coded_width); + w.ue(p.coded_height); + let crop = p.coded_width != p.width || p.coded_height != p.height; + w.flag(crop); // conformance_window_flag + if crop { + // offsets in chroma sample units (SubWidthC = SubHeightC = 2) + w.ue(0); + w.ue((p.coded_width - p.width) / 2); + w.ue(0); + w.ue((p.coded_height - p.height) / 2); + } + w.ue(p.bit_depth - 8); // bit_depth_luma_minus8 + w.ue(p.bit_depth - 8); // bit_depth_chroma_minus8 + w.ue(LOG2_MAX_POC_LSB - 4); + w.put(1, 1); // sps_sub_layer_ordering_info_present_flag + w.ue(1); // sps_max_dec_pic_buffering_minus1 + w.ue(0); // sps_max_num_reorder_pics + w.ue(0); // sps_max_latency_increase_plus1 + w.ue(LOG2_MIN_CB - 3); + w.ue(LOG2_CTB - LOG2_MIN_CB); + w.ue(LOG2_MIN_TB - 2); + w.ue(LOG2_MAX_TB - LOG2_MIN_TB); + w.ue(0); // max_transform_hierarchy_depth_inter + w.ue(0); // max_transform_hierarchy_depth_intra + w.put(0, 1); // scaling_list_enabled_flag + w.put(0, 1); // amp_enabled_flag + w.put(0, 1); // sample_adaptive_offset_enabled_flag + w.put(0, 1); // pcm_enabled_flag + w.ue(1); // num_short_term_ref_pic_sets + // st_ref_pic_set(0): one negative picture at delta POC -1, used by the current picture + w.ue(1); // num_negative_pics + w.ue(0); // num_positive_pics + w.ue(0); // delta_poc_s0_minus1 + w.put(1, 1); // used_by_curr_pic_s0_flag + w.put(0, 1); // long_term_ref_pics_present_flag + w.put(0, 1); // sps_temporal_mvp_enabled_flag + w.put(1, 1); // strong_intra_smoothing_enabled_flag + w.put(1, 1); // vui_parameters_present_flag + vui(&mut w, p); + w.put(0, 1); // sps_extension_present_flag + w.trailing(); + nal_unit(NAL_SPS, &w.into_bytes()) +} + +fn vui(w: &mut BitWriter, p: &SeqParams) { + w.put(0, 1); // aspect_ratio_info_present_flag + w.put(0, 1); // overscan_info_present_flag + w.put(1, 1); // video_signal_type_present_flag + w.put(5, 3); // video_format: unspecified + w.flag(p.full_range); + w.put(1, 1); // colour_description_present_flag + w.put(1, 8); // colour_primaries BT.709 + w.put(1, 8); // transfer_characteristics BT.709 + w.put(1, 8); // matrix_coeffs BT.709 + w.put(0, 1); // chroma_loc_info_present_flag + w.put(0, 1); // neutral_chroma_indication_flag + w.put(0, 1); // field_seq_flag + w.put(0, 1); // frame_field_info_present_flag + w.put(0, 1); // default_display_window_flag + w.put(1, 1); // vui_timing_info_present_flag + w.put(p.fps_den, 32); // vui_num_units_in_tick + w.put(p.fps_num, 32); // vui_time_scale + w.put(0, 1); // vui_poc_proportional_to_timing_flag + w.put(0, 1); // vui_hrd_parameters_present_flag + w.put(0, 1); // bitstream_restriction_flag +} + +pub fn pps() -> Vec { + let mut w = BitWriter::new(); + w.ue(0); // pps_pic_parameter_set_id + w.ue(0); // pps_seq_parameter_set_id + w.put(0, 1); // dependent_slice_segments_enabled_flag + w.put(0, 1); // output_flag_present_flag + w.put(0, 3); // num_extra_slice_header_bits + w.put(1, 1); // sign_data_hiding_enabled_flag + w.put(0, 1); // cabac_init_present_flag + w.ue(0); // num_ref_idx_l0_default_active_minus1 + w.ue(0); // num_ref_idx_l1_default_active_minus1 + w.se(0); // init_qp_minus26 + w.put(0, 1); // constrained_intra_pred_flag + w.put(0, 1); // transform_skip_enabled_flag + w.put(0, 1); // cu_qp_delta_enabled_flag + w.se(0); // pps_cb_qp_offset + w.se(0); // pps_cr_qp_offset + w.put(0, 1); // pps_slice_chroma_qp_offsets_present_flag + w.put(0, 1); // weighted_pred_flag + w.put(0, 1); // weighted_bipred_flag + w.put(0, 1); // transquant_bypass_enabled_flag + w.put(0, 1); // tiles_enabled_flag + w.put(0, 1); // entropy_coding_sync_enabled_flag + w.put(0, 1); // pps_loop_filter_across_slices_enabled_flag + w.put(1, 1); // deblocking_filter_control_present_flag + w.put(0, 1); // deblocking_filter_override_enabled_flag + w.put(0, 1); // pps_deblocking_filter_disabled_flag + w.se(0); // pps_beta_offset_div2 + w.se(0); // pps_tc_offset_div2 + w.put(0, 1); // pps_scaling_list_data_present_flag + w.put(0, 1); // lists_modification_present_flag + w.ue(0); // log2_parallel_merge_level_minus2 + w.put(0, 1); // slice_segment_header_extension_present_flag + w.put(0, 1); // pps_extension_present_flag + w.trailing(); + nal_unit(NAL_PPS, &w.into_bytes()) +} + +/// HEVCDecoderConfigurationRecord with one VPS, SPS and PPS (lengthSizeMinusOne = 3). +pub fn hvcc(p: &SeqParams, sets: &[Vec; 3]) -> Vec { + let mut o = vec![1u8]; // configurationVersion + o.push(p.profile_idc); // profile_space 0, tier 0, profile_idc + o.extend_from_slice(&compat_flags(p.profile_idc).to_be_bytes()); + // general_constraint_indicator_flags (48 bits): progressive, interlaced, non_packed, frame_only + o.extend_from_slice(&[0x90, 0, 0, 0, 0, 0]); + o.push(p.level_idc); + o.extend_from_slice(&[0xf0, 0x00]); // reserved + min_spatial_segmentation_idc 0 + o.push(0xfc); // reserved + parallelismType 0 + o.push(0xfc | 1); // reserved + chroma_format_idc 1 + o.push(0xf8 | (p.bit_depth - 8) as u8); + o.push(0xf8 | (p.bit_depth - 8) as u8); + o.extend_from_slice(&[0, 0]); // avgFrameRate unspecified + // constantFrameRate 0, numTemporalLayers 1, temporalIdNested 1, lengthSizeMinusOne 3 + o.push((1 << 3) | (1 << 2) | 3); + o.push(3); // numOfArrays + for (set, ty) in sets.iter().zip([NAL_VPS, NAL_SPS, NAL_PPS]) { + o.push(0x80 | ty); // array_completeness 1 + o.extend_from_slice(&1u16.to_be_bytes()); + o.extend_from_slice(&(set.len() as u16).to_be_bytes()); + o.extend_from_slice(set); + } + o +} + +#[cfg(test)] +mod tests { + use super::*; + + #[test] + fn levels() { + assert_eq!(choose_level(1920, 1080, 30, 1, None), 120); + assert_eq!(choose_level(1920, 1080, 60, 1, None), 123); + assert_eq!(choose_level(3840, 2160, 30, 1, None), 150); + assert_eq!(choose_level(1280, 720, 30, 1, None), 93); + assert_eq!(choose_level(64, 64, 30, 1, None), 30); + assert_eq!(choose_level(1920, 1080, 30, 1, Some(15_000)), 123); + } +} diff --git a/crates/hevcenc/src/tables.rs b/crates/hevcenc/src/tables.rs new file mode 100644 index 0000000000..7c2fc44702 --- /dev/null +++ b/crates/hevcenc/src/tables.rs @@ -0,0 +1,236 @@ +//! Constant tables of ITU-T H.265, mirrored from the first-party, spec-derived `filmcraft-hevc` +//! decoder (same organisation, MIT OR Apache-2.0): CABAC context layout and initValues (9.3.2.2), +//! rangeTabLPS / transIdx (Tables 9-52, 9-53), scans (6.5.3 - 6.5.5), transform matrices (8.6.4.2), +//! intra angles (Tables 8-5, 8-6), interpolation filters (8.5.3.3.3) and the chroma QP mapping (Table 8-10). + +#![allow(dead_code)] + +/// Table 9-52 (same values as H.264) rangeTabLPS[pStateIdx][qCodIRangeIdx]. +#[rustfmt::skip] +pub static RANGE_TAB_LPS: [[u8; 4]; 64] = [ + [128, 176, 208, 240], [128, 167, 197, 227], [128, 158, 187, 216], [123, 150, 178, 205], + [116, 142, 169, 195], [111, 135, 160, 185], [105, 128, 152, 175], [100, 122, 144, 166], + [95, 116, 137, 158], [90, 110, 130, 150], [85, 104, 123, 142], [81, 99, 117, 135], + [77, 94, 111, 128], [73, 89, 105, 122], [69, 85, 100, 116], [66, 80, 95, 110], + [62, 76, 90, 104], [59, 72, 86, 99], [56, 69, 81, 94], [53, 65, 77, 89], + [51, 62, 73, 85], [48, 59, 69, 80], [46, 56, 66, 76], [43, 53, 63, 72], + [41, 50, 59, 69], [39, 48, 56, 65], [37, 45, 54, 62], [35, 43, 51, 59], + [33, 41, 48, 56], [32, 39, 46, 53], [30, 37, 43, 50], [29, 35, 41, 48], + [27, 33, 39, 45], [26, 31, 37, 43], [24, 30, 35, 41], [23, 28, 33, 39], + [22, 27, 32, 37], [21, 26, 30, 35], [20, 24, 29, 33], [19, 23, 27, 31], + [18, 22, 26, 30], [17, 21, 25, 28], [16, 20, 23, 27], [15, 19, 22, 25], + [14, 18, 21, 24], [14, 17, 20, 23], [13, 16, 19, 22], [12, 15, 18, 21], + [12, 14, 17, 20], [11, 14, 16, 19], [11, 13, 15, 18], [10, 12, 15, 17], + [10, 12, 14, 16], [9, 11, 13, 15], [9, 11, 12, 14], [8, 10, 12, 14], + [8, 9, 11, 13], [7, 9, 11, 12], [7, 9, 10, 12], [7, 8, 10, 11], + [6, 8, 9, 11], [6, 7, 9, 10], [6, 7, 8, 9], [2, 2, 2, 2], +]; + +/// Table 9-53 transIdxLPS. +#[rustfmt::skip] +static TRANS_IDX_LPS: [u8; 64] = [ + 0, 0, 1, 2, 2, 4, 4, 5, 6, 7, 8, 9, 9, 11, 11, 12, + 13, 13, 15, 15, 16, 16, 18, 18, 19, 19, 21, 21, 22, 22, 23, 24, + 24, 25, 26, 26, 27, 27, 28, 29, 29, 30, 30, 30, 31, 32, 32, 33, + 33, 33, 34, 34, 35, 35, 35, 36, 36, 36, 37, 37, 37, 38, 38, 63, +]; + +/// Table 9-53 transIdxMPS. +#[rustfmt::skip] +static TRANS_IDX_MPS: [u8; 64] = [ + 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15, 16, + 17, 18, 19, 20, 21, 22, 23, 24, 25, 26, 27, 28, 29, 30, 31, 32, + 33, 34, 35, 36, 37, 38, 39, 40, 41, 42, 43, 44, 45, 46, 47, 48, + 49, 50, 51, 52, 53, 54, 55, 56, 57, 58, 59, 60, 61, 62, 62, 63, +]; + +/// Combined state transition: index = (pStateIdx << 1 | valMPS) * 2 + bin_is_lps. +/// Each context is stored as `pStateIdx << 1 | valMPS`. +pub static NEXT_STATE: [[u8; 2]; 128] = { + let mut t = [[0u8; 2]; 128]; + let mut s = 0; + while s < 128 { + let p = s >> 1; + let mps = s & 1; + // MPS path + t[s][0] = (TRANS_IDX_MPS[p] << 1) | mps as u8; + // LPS path + let new_mps = if p == 0 { 1 - mps } else { mps }; + t[s][1] = (TRANS_IDX_LPS[p] << 1) | new_mps as u8; + s += 1; + } + t +}; + +/// Up-right diagonal scan (6.5.3) of a `N x N` block as (x, y) pairs. +const fn diag(n: usize) -> [(u8, u8); L] { + let mut out = [(0u8, 0u8); L]; + let mut i = 0; + let mut x: i32 = 0; + let mut y: i32 = 0; + while i < L { + while y >= 0 { + if (x as usize) < n && (y as usize) < n { + out[i] = (x as u8, y as u8); + i += 1; + } + y -= 1; + x += 1; + } + y = x; + x = 0; + } + out +} + +/// Horizontal (6.5.4, `vertical == false`) or vertical (6.5.5) scan. +const fn hv(n: usize, vertical: bool) -> [(u8, u8); L] { + let mut out = [(0u8, 0u8); L]; + let mut i = 0; + while i < L { + let (a, b) = ((i % n) as u8, (i / n) as u8); + out[i] = if vertical { (b, a) } else { (a, b) }; + i += 1; + } + out +} + +pub static SCAN_1: [[(u8, u8); 1]; 3] = [[(0, 0)], [(0, 0)], [(0, 0)]]; +pub static SCAN_2: [[(u8, u8); 4]; 3] = [diag::<4>(2), hv::<4>(2, false), hv::<4>(2, true)]; +pub static SCAN_4: [[(u8, u8); 16]; 3] = [diag::<16>(4), hv::<16>(4, false), hv::<16>(4, true)]; +pub static SCAN_8: [[(u8, u8); 64]; 3] = [diag::<64>(8), hv::<64>(8, false), hv::<64>(8, true)]; + +/// ScanOrder[log2BlockSize][scanIdx] (log2BlockSize 0..=3). +#[inline] +pub fn scan_order(log2: u32, scan_idx: usize) -> &'static [(u8, u8)] { + match log2 { + 0 => &SCAN_1[scan_idx], + 1 => &SCAN_2[scan_idx], + 2 => &SCAN_4[scan_idx], + _ => &SCAN_8[scan_idx], + } +} + +/// Up-right diagonal scan for log2 size 2 or 3. +pub fn scan_diag(log2: u32) -> &'static [(u8, u8)] { + scan_order(log2, 0) +} + +/// Table 8-10: QpC as a function of qPi (ChromaArrayType 1). +#[inline] +pub fn qpc_420(qpi: i32) -> i32 { + if qpi < 30 { + qpi + } else if qpi > 43 { + qpi - 6 + } else { + QPC_30_43[(qpi - 30) as usize] as i32 + } +} + +/// Table 9-50 ctxIdxMap for 4x4 sig_coeff_flag contexts. +pub static CTX_IDX_MAP: [u8; 16] = [0, 1, 4, 5, 2, 3, 4, 5, 6, 6, 8, 8, 7, 7, 8, 8]; + +/// levelScale (8-309). +pub static LEVEL_SCALE: [i32; 6] = [40, 45, 51, 57, 64, 72]; + +/// DST-VII 4x4 matrix (8.6.4.2, trType 1). +pub static DST_MATRIX: [[i8; 4]; 4] = [[29, 55, 74, 84], [74, 74, 0, -74], [84, -29, -74, 55], [55, -84, 74, -29]]; + + +// CABAC context layout: offset of each syntax element in the context array (Table 9-4). +pub const SAO_MERGE: usize = 0; +pub const SAO_TYPE: usize = 1; +pub const SPLIT_CU: usize = 2; +pub const CU_TRANSQUANT_BYPASS: usize = 5; +pub const CU_SKIP: usize = 6; +pub const PRED_MODE: usize = 9; +pub const PART_MODE: usize = 10; +pub const PREV_INTRA_LUMA: usize = 14; +pub const INTRA_CHROMA: usize = 15; +pub const RQT_ROOT_CBF: usize = 16; +pub const MERGE_FLAG: usize = 17; +pub const MERGE_IDX: usize = 18; +pub const INTER_PRED_IDC: usize = 19; +pub const REF_IDX: usize = 24; +pub const MVP_FLAG: usize = 26; +pub const SPLIT_TRANSFORM: usize = 27; +pub const CBF_LUMA: usize = 30; +pub const CBF_CHROMA: usize = 32; +pub const ABS_MVD_GT0: usize = 37; +pub const ABS_MVD_GT1: usize = 38; +pub const CU_QP_DELTA_ABS: usize = 39; +pub const TRANSFORM_SKIP: usize = 41; +pub const LAST_X_PREFIX: usize = 43; +pub const LAST_Y_PREFIX: usize = 61; +pub const CODED_SUB_BLOCK: usize = 79; +pub const SIG_COEFF: usize = 83; +pub const GT1: usize = 127; +pub const GT2: usize = 151; +pub const EXPLICIT_RDPCM: usize = 157; +pub const EXPLICIT_RDPCM_DIR: usize = 159; +pub const CU_CHROMA_QP_OFFSET_FLAG: usize = 161; +pub const CU_CHROMA_QP_OFFSET_IDX: usize = 162; +pub const NUM_CTX: usize = 163; + +/// initValue per initType (9.3.2.2). +pub static CABAC_INIT: [[u8; NUM_CTX]; 3] = [ + [153, 200, 139, 141, 157, 154, 154, 154, 154, 154, 184, 154, 154, 154, 184, 63, 154, 154, 154, 154, 154, 154, 154, 154, 154, 154, 154, 153, 138, 138, 111, 141, 94, 138, 182, 154, 154, 154, 154, 154, 154, 139, 139, 110, 110, 124, 125, 140, 153, 125, 127, 140, 109, 111, 143, 127, 111, 79, 108, 123, 63, 110, 110, 124, 125, 140, 153, 125, 127, 140, 109, 111, 143, 127, 111, 79, 108, 123, 63, 91, 171, 134, 141, 111, 111, 125, 110, 110, 94, 124, 108, 124, 107, 125, 141, 179, 153, 125, 107, 125, 141, 179, 153, 125, 107, 125, 141, 179, 153, 125, 140, 139, 182, 182, 152, 136, 152, 136, 153, 136, 139, 111, 136, 139, 111, 141, 111, 140, 92, 137, 138, 140, 152, 138, 139, 153, 74, 149, 92, 139, 107, 122, 152, 140, 179, 166, 182, 140, 227, 122, 197, 138, 153, 136, 167, 152, 152, 154, 154, 154, 154, 154, 154], + [153, 185, 107, 139, 126, 154, 197, 185, 201, 149, 154, 139, 154, 154, 154, 152, 79, 110, 122, 95, 79, 63, 31, 31, 153, 153, 168, 124, 138, 94, 153, 111, 149, 107, 167, 154, 154, 140, 198, 154, 154, 139, 139, 125, 110, 94, 110, 95, 79, 125, 111, 110, 78, 110, 111, 111, 95, 94, 108, 123, 108, 125, 110, 94, 110, 95, 79, 125, 111, 110, 78, 110, 111, 111, 95, 94, 108, 123, 108, 121, 140, 61, 154, 155, 154, 139, 153, 139, 123, 123, 63, 153, 166, 183, 140, 136, 153, 154, 166, 183, 140, 136, 153, 154, 166, 183, 140, 136, 153, 154, 170, 153, 123, 123, 107, 121, 107, 121, 167, 151, 183, 140, 151, 183, 140, 140, 140, 154, 196, 196, 167, 154, 152, 167, 182, 182, 134, 149, 136, 153, 121, 136, 137, 169, 194, 166, 167, 154, 167, 137, 182, 107, 167, 91, 122, 107, 167, 139, 139, 139, 139, 154, 154], + [153, 160, 107, 139, 126, 154, 197, 185, 201, 134, 154, 139, 154, 154, 183, 152, 79, 154, 137, 95, 79, 63, 31, 31, 153, 153, 168, 224, 167, 122, 153, 111, 149, 92, 167, 154, 154, 169, 198, 154, 154, 139, 139, 125, 110, 124, 110, 95, 94, 125, 111, 111, 79, 125, 126, 111, 111, 79, 108, 123, 93, 125, 110, 124, 110, 95, 94, 125, 111, 111, 79, 125, 126, 111, 111, 79, 108, 123, 93, 121, 140, 61, 154, 170, 154, 139, 153, 139, 123, 123, 63, 124, 166, 183, 140, 136, 153, 154, 166, 183, 140, 136, 153, 154, 166, 183, 140, 136, 153, 154, 170, 153, 138, 138, 122, 121, 122, 121, 167, 151, 183, 140, 151, 183, 140, 140, 140, 154, 196, 167, 167, 154, 152, 167, 182, 182, 134, 149, 136, 153, 121, 136, 122, 169, 208, 166, 167, 154, 152, 167, 182, 107, 167, 91, 107, 107, 167, 139, 139, 139, 139, 154, 154], +]; + + +/// Table 8-5: intraPredAngle for predModeIntra 0..34 (0 for planar / DC). +pub static INTRA_PRED_ANGLE: [i8; 35] = [0, 0, 32, 26, 21, 17, 13, 9, 5, 2, 0, -2, -5, -9, -13, -17, -21, -26, -32, -26, -21, -17, -13, -9, -5, -2, 0, 2, 5, 9, 13, 17, 21, 26, 32]; +/// Table 8-6: invAngle for predModeIntra 11..25 (index = mode - 11). +pub static INV_ANGLE: [i16; 15] = [-4096, -1638, -910, -630, -482, -390, -315, -256, -315, -390, -482, -630, -910, -1638, -4096]; + +/// Table 8-10: QpC for qPi = 30..43 (qPi < 30: QpC = qPi; qPi > 43: QpC = qPi - 6). +pub static QPC_30_43: [u8; 14] = [29, 30, 31, 32, 33, 33, 34, 34, 35, 35, 36, 36, 37, 37]; + + +/// Luma 8-tap interpolation filter fL[xFrac][k] (8-236..8-238), taps at offsets -3..4. +pub static LUMA_FILTER: [[i8; 8]; 4] = [[0, 0, 0, 64, 0, 0, 0, 0], [-1, 4, -10, 58, 17, -5, 1, 0], [-1, 4, -11, 40, 40, -11, 4, -1], [0, 1, -5, 17, 58, -10, 4, -1]]; +/// Chroma 4-tap interpolation filter fC[xFrac][k] (8-255..8-261), taps at offsets -1..2. +pub static CHROMA_FILTER: [[i8; 4]; 8] = [[0, 64, 0, 0], [-2, 58, 10, -2], [-4, 54, 16, -2], [-6, 46, 28, -4], [-4, 36, 36, -4], [-4, 28, 46, -6], [-2, 16, 54, -4], [-2, 10, 58, -2]]; + +/// DCT matrix (8-319, 8-321): TRANS_MATRIX[n][m] = transMatrix[m][n] (row n = basis function). +pub static TRANS_MATRIX: [[i8; 32]; 32] = [ + [64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64], + [90, 90, 88, 85, 82, 78, 73, 67, 61, 54, 46, 38, 31, 22, 13, 4, -4, -13, -22, -31, -38, -46, -54, -61, -67, -73, -78, -82, -85, -88, -90, -90], + [90, 87, 80, 70, 57, 43, 25, 9, -9, -25, -43, -57, -70, -80, -87, -90, -90, -87, -80, -70, -57, -43, -25, -9, 9, 25, 43, 57, 70, 80, 87, 90], + [90, 82, 67, 46, 22, -4, -31, -54, -73, -85, -90, -88, -78, -61, -38, -13, 13, 38, 61, 78, 88, 90, 85, 73, 54, 31, 4, -22, -46, -67, -82, -90], + [89, 75, 50, 18, -18, -50, -75, -89, -89, -75, -50, -18, 18, 50, 75, 89, 89, 75, 50, 18, -18, -50, -75, -89, -89, -75, -50, -18, 18, 50, 75, 89], + [88, 67, 31, -13, -54, -82, -90, -78, -46, -4, 38, 73, 90, 85, 61, 22, -22, -61, -85, -90, -73, -38, 4, 46, 78, 90, 82, 54, 13, -31, -67, -88], + [87, 57, 9, -43, -80, -90, -70, -25, 25, 70, 90, 80, 43, -9, -57, -87, -87, -57, -9, 43, 80, 90, 70, 25, -25, -70, -90, -80, -43, 9, 57, 87], + [85, 46, -13, -67, -90, -73, -22, 38, 82, 88, 54, -4, -61, -90, -78, -31, 31, 78, 90, 61, 4, -54, -88, -82, -38, 22, 73, 90, 67, 13, -46, -85], + [83, 36, -36, -83, -83, -36, 36, 83, 83, 36, -36, -83, -83, -36, 36, 83, 83, 36, -36, -83, -83, -36, 36, 83, 83, 36, -36, -83, -83, -36, 36, 83], + [82, 22, -54, -90, -61, 13, 78, 85, 31, -46, -90, -67, 4, 73, 88, 38, -38, -88, -73, -4, 67, 90, 46, -31, -85, -78, -13, 61, 90, 54, -22, -82], + [80, 9, -70, -87, -25, 57, 90, 43, -43, -90, -57, 25, 87, 70, -9, -80, -80, -9, 70, 87, 25, -57, -90, -43, 43, 90, 57, -25, -87, -70, 9, 80], + [78, -4, -82, -73, 13, 85, 67, -22, -88, -61, 31, 90, 54, -38, -90, -46, 46, 90, 38, -54, -90, -31, 61, 88, 22, -67, -85, -13, 73, 82, 4, -78], + [75, -18, -89, -50, 50, 89, 18, -75, -75, 18, 89, 50, -50, -89, -18, 75, 75, -18, -89, -50, 50, 89, 18, -75, -75, 18, 89, 50, -50, -89, -18, 75], + [73, -31, -90, -22, 78, 67, -38, -90, -13, 82, 61, -46, -88, -4, 85, 54, -54, -85, 4, 88, 46, -61, -82, 13, 90, 38, -67, -78, 22, 90, 31, -73], + [70, -43, -87, 9, 90, 25, -80, -57, 57, 80, -25, -90, -9, 87, 43, -70, -70, 43, 87, -9, -90, -25, 80, 57, -57, -80, 25, 90, 9, -87, -43, 70], + [67, -54, -78, 38, 85, -22, -90, 4, 90, 13, -88, -31, 82, 46, -73, -61, 61, 73, -46, -82, 31, 88, -13, -90, -4, 90, 22, -85, -38, 78, 54, -67], + [64, -64, -64, 64, 64, -64, -64, 64, 64, -64, -64, 64, 64, -64, -64, 64, 64, -64, -64, 64, 64, -64, -64, 64, 64, -64, -64, 64, 64, -64, -64, 64], + [61, -73, -46, 82, 31, -88, -13, 90, -4, -90, 22, 85, -38, -78, 54, 67, -67, -54, 78, 38, -85, -22, 90, 4, -90, 13, 88, -31, -82, 46, 73, -61], + [57, -80, -25, 90, -9, -87, 43, 70, -70, -43, 87, 9, -90, 25, 80, -57, -57, 80, 25, -90, 9, 87, -43, -70, 70, 43, -87, -9, 90, -25, -80, 57], + [54, -85, -4, 88, -46, -61, 82, 13, -90, 38, 67, -78, -22, 90, -31, -73, 73, 31, -90, 22, 78, -67, -38, 90, -13, -82, 61, 46, -88, 4, 85, -54], + [50, -89, 18, 75, -75, -18, 89, -50, -50, 89, -18, -75, 75, 18, -89, 50, 50, -89, 18, 75, -75, -18, 89, -50, -50, 89, -18, -75, 75, 18, -89, 50], + [46, -90, 38, 54, -90, 31, 61, -88, 22, 67, -85, 13, 73, -82, 4, 78, -78, -4, 82, -73, -13, 85, -67, -22, 88, -61, -31, 90, -54, -38, 90, -46], + [43, -90, 57, 25, -87, 70, 9, -80, 80, -9, -70, 87, -25, -57, 90, -43, -43, 90, -57, -25, 87, -70, -9, 80, -80, 9, 70, -87, 25, 57, -90, 43], + [38, -88, 73, -4, -67, 90, -46, -31, 85, -78, 13, 61, -90, 54, 22, -82, 82, -22, -54, 90, -61, -13, 78, -85, 31, 46, -90, 67, 4, -73, 88, -38], + [36, -83, 83, -36, -36, 83, -83, 36, 36, -83, 83, -36, -36, 83, -83, 36, 36, -83, 83, -36, -36, 83, -83, 36, 36, -83, 83, -36, -36, 83, -83, 36], + [31, -78, 90, -61, 4, 54, -88, 82, -38, -22, 73, -90, 67, -13, -46, 85, -85, 46, 13, -67, 90, -73, 22, 38, -82, 88, -54, -4, 61, -90, 78, -31], + [25, -70, 90, -80, 43, 9, -57, 87, -87, 57, -9, -43, 80, -90, 70, -25, -25, 70, -90, 80, -43, -9, 57, -87, 87, -57, 9, 43, -80, 90, -70, 25], + [22, -61, 85, -90, 73, -38, -4, 46, -78, 90, -82, 54, -13, -31, 67, -88, 88, -67, 31, 13, -54, 82, -90, 78, -46, 4, 38, -73, 90, -85, 61, -22], + [18, -50, 75, -89, 89, -75, 50, -18, -18, 50, -75, 89, -89, 75, -50, 18, 18, -50, 75, -89, 89, -75, 50, -18, -18, 50, -75, 89, -89, 75, -50, 18], + [13, -38, 61, -78, 88, -90, 85, -73, 54, -31, 4, 22, -46, 67, -82, 90, -90, 82, -67, 46, -22, -4, 31, -54, 73, -85, 90, -88, 78, -61, 38, -13], + [9, -25, 43, -57, 70, -80, 87, -90, 90, -87, 80, -70, 57, -43, 25, -9, -9, 25, -43, 57, -70, 80, -87, 90, -90, 87, -80, 70, -57, 43, -25, 9], + [4, -13, 22, -31, 38, -46, 54, -61, 67, -73, 78, -82, 85, -88, 90, -90, 90, -90, 88, -85, 82, -78, 73, -67, 61, -54, 46, -38, 31, -22, 13, -4], +]; +/// Table 8-12: beta' for Q = 0..51. +pub static BETA_TABLE: [u8; 52] = [0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15, 16, 17, 18, 20, 22, 24, 26, 28, 30, 32, 34, 36, 38, 40, 42, 44, 46, 48, 50, 52, 54, 56, 58, 60, 62, 64]; +/// Table 8-12: tC' for Q = 0..53. +pub static TC_TABLE: [u8; 54] = [0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1, 1, 1, 1, 1, 1, 1, 1, 1, 2, 2, 2, 2, 3, 3, 3, 3, 4, 4, 4, 5, 5, 6, 6, 7, 8, 9, 10, 11, 13, 14, 16, 18, 20, 22, 24]; diff --git a/crates/hevcenc/src/transform.rs b/crates/hevcenc/src/transform.rs new file mode 100644 index 0000000000..cffea75c2f --- /dev/null +++ b/crates/hevcenc/src/transform.rs @@ -0,0 +1,328 @@ +//! Forward transforms (encoder side, any integer approximation is allowed), quantisation, and the +//! normative scaling (8.6.3) and inverse transforms (8.6.4.2) the decoder applies. The inverse path +//! is exact integer arithmetic: the even/odd decomposition computes the same sums as the matrix +//! products of the specification. + +use crate::tables::{DST_MATRIX, LEVEL_SCALE, TRANS_MATRIX}; + +#[inline] +fn m(n: usize, k: usize, i: usize) -> i32 { + // k-th basis function of the n-point DCT, sample i + TRANS_MATRIX[k * (32 / n)][i] as i32 +} + +/// 1-D forward DCT of `x` (length n) into `out`. +fn fdct1d(x: &[i32], out: &mut [i32], n: usize) { + if n <= 4 { + for k in 0..n { + let mut s = 0; + for i in 0..n { + s += m(n, k, i) * x[i]; + } + out[k] = s; + } + return; + } + let h = n / 2; + let mut e = [0i32; 16]; + let mut o = [0i32; 16]; + for i in 0..h { + e[i] = x[i] + x[n - 1 - i]; + o[i] = x[i] - x[n - 1 - i]; + } + let mut ev = [0i32; 16]; + fdct1d(&e[..h], &mut ev[..h], h); + for k in 0..h { + out[2 * k] = ev[k]; + let kk = 2 * k + 1; + let mut s = 0; + for i in 0..h { + s += m(n, kk, i) * o[i]; + } + out[kk] = s; + } +} + +/// 1-D inverse DCT: x[i] = sum_k M[k][i] * c[k]. +fn idct1d(c: &[i32], out: &mut [i32], n: usize) { + if n <= 4 { + for i in 0..n { + let mut s = 0; + for k in 0..n { + s += m(n, k, i) * c[k]; + } + out[i] = s; + } + return; + } + let h = n / 2; + let mut ce = [0i32; 16]; + for k in 0..h { + ce[k] = c[2 * k]; + } + let mut e = [0i32; 16]; + idct1d(&ce[..h], &mut e[..h], h); + for i in 0..h { + let mut o = 0; + let mut k = 1; + while k < n { + let v = c[k]; + if v != 0 { + o += m(n, k, i) * v; + } + k += 2; + } + out[i] = e[i] + o; + out[n - 1 - i] = e[i] - o; + } +} + +fn fdst1d(x: &[i32], out: &mut [i32]) { + for k in 0..4 { + out[k] = (0..4).map(|i| DST_MATRIX[k][i] as i32 * x[i]).sum(); + } +} + +fn idst1d(c: &[i32], out: &mut [i32]) { + for i in 0..4 { + out[i] = (0..4).map(|k| DST_MATRIX[k][i] as i32 * c[k]).sum(); + } +} + +/// Forward 2-D transform of the n x n residual `r` (row-major) into `c`. +pub fn forward(r: &[i32], c: &mut [i32], n: usize, dst: bool, bit_depth: u32) { + let log2 = n.trailing_zeros(); + let shift1 = log2 + bit_depth - 9; + let shift2 = log2 + 6; + let (r1, r2) = (1i32 << (shift1 - 1), 1i32 << (shift2 - 1)); + let mut tmp = vec![0i32; n * n]; + let mut out = [0i32; 32]; + // rows (horizontal frequencies) + for y in 0..n { + let row = &r[y * n..y * n + n]; + if dst { + fdst1d(row, &mut out); + } else { + fdct1d(row, &mut out, n); + } + for k in 0..n { + tmp[k * n + y] = (out[k] + r1) >> shift1; // transposed: tmp[kx][y] + } + } + // columns + let mut col = [0i32; 32]; + for kx in 0..n { + col[..n].copy_from_slice(&tmp[kx * n..kx * n + n]); + if dst { + fdst1d(&col, &mut out); + } else { + fdct1d(&col, &mut out, n); + } + for ky in 0..n { + c[ky * n + kx] = (out[ky] + r2) >> shift2; + } + } +} + +/// Inverse 2-D transform (8.6.4.2) of scaled coefficients `d` in place, producing residuals. +pub fn inverse(d: &mut [i32], n: usize, dst: bool, bit_depth: u32) { + let bd_shift = 20 - bit_depth; + let mut tmp = vec![0i32; n * n]; + let mut col = [0i32; 32]; + let mut out = [0i32; 32]; + // max non-zero row, to skip empty work + let mut nz_cols = [false; 32]; + for y in 0..n { + for x in 0..n { + if d[y * n + x] != 0 { + nz_cols[x] = true; + } + } + } + // 1. columns + for x in 0..n { + if !nz_cols[x] { + continue; // tmp column stays zero + } + for j in 0..n { + col[j] = d[j * n + x]; + } + if dst { + idst1d(&col, &mut out); + } else { + idct1d(&col, &mut out, n); + } + for y in 0..n { + tmp[y * n + x] = ((out[y] + 64) >> 7).clamp(-32768, 32767); + } + } + // 2. rows + let round = 1 << (bd_shift - 1); + for y in 0..n { + let row = &tmp[y * n..y * n + n]; + if dst { + idst1d(row, &mut out); + } else { + idct1d(row, &mut out, n); + } + for x in 0..n { + d[y * n + x] = (out[x] + round) >> bd_shift; + } + } +} + +const QUANT_SCALE: [i64; 6] = [26214, 23302, 20560, 18396, 16384, 14564]; + +/// Quantise transform coefficients `c` into levels `l` with a dead-zone rounding offset +/// (`intra`: 1/3, otherwise 1/6). `qp` includes QpBdOffset. Returns the number of non-zero levels. +pub fn quantize(c: &[i32], l: &mut [i32], n: usize, qp: i32, intra: bool, bit_depth: u32) -> usize { + let log2 = n.trailing_zeros() as i32; + let tshift = 15 - bit_depth as i32 - log2; + let qbits = 14 + qp / 6 + tshift; + let scale = QUANT_SCALE[(qp % 6) as usize]; + let offset = (if intra { 171i64 } else { 85 }) << (qbits - 9); + let mut nz = 0; + for i in 0..n * n { + let v = c[i] as i64; + let a = ((v.abs() * scale + offset) >> qbits).min(32767); + if a != 0 { + nz += 1; + } + l[i] = if v < 0 { -(a as i32) } else { a as i32 }; + } + nz +} + +/// Sign data hiding (7.3.8.11 signHidden): in every 4x4 sub-block whose first and last non-zero +/// coefficients (in scan order) are more than 3 positions apart, the sign of the first one is not +/// coded but inferred from the parity of the sum of absolute levels. Fix the parity where needed by +/// the +-1 level change that adds the least quantisation error, without moving the first / last +/// non-zero positions. +pub fn sign_hiding(c: &[i32], l: &mut [i32], log2: u32, scan_idx: usize, qp: i32, bit_depth: u32) { + use crate::tables::{SCAN_4, scan_order}; + let n = 1usize << log2; + let tshift = 15 - bit_depth as i32 - log2 as i32; + let qbits = 14 + qp / 6 + tshift; + let scale = QUANT_SCALE[(qp % 6) as usize]; + let sb_scan = scan_order(log2 - 2, scan_idx); + let pos = &SCAN_4[scan_idx]; + for &(xs, ys) in sb_scan { + let idx = |p: usize| (ys as usize * 4 + pos[p].1 as usize) * n + xs as usize * 4 + pos[p].0 as usize; + let (Some(first), Some(last)) = ((0..16).find(|&p| l[idx(p)] != 0), (0..16).rev().find(|&p| l[idx(p)] != 0)) else { continue }; + if last - first <= 3 { + continue; + } + let sum: i32 = (0..16).map(|p| l[idx(p)].abs()).sum(); + let negative = l[idx(first)] < 0; + if negative == (sum & 1 == 1) { + continue; + } + // cost of +-1 changes in 1/256 units of quantisation error + let mut best: Option<(i64, usize, i32)> = None; + for p in first..=last { + let i = idx(p); + let q = ((c[i] as i64).abs() * scale) >> (qbits - 8); + let a = l[i].abs() as i64; + let err = |lv: i64| (q - (lv << 8)).abs(); + let mut consider = |cost: i64, d: i32| { + if best.is_none_or(|b| cost < b.0) { + best = Some((cost, i, d)); + } + }; + if a < 32767 && (a > 0 || (p > first && p < last)) { + consider(err(a + 1) - err(a), 1); + } + if a > 1 || (a == 1 && p > first && p < last) { + consider(err(a - 1) - err(a), -1); + } + } + if let Some((_, i, d)) = best { + let a = l[i].abs() + d; + l[i] = if c[i] < 0 { -a } else { a }; + } + } +} + +/// Scaling process (8.6.2 / 8.6.3, flat scaling matrix): levels to scaled coefficients. +pub fn dequantize(l: &[i32], d: &mut [i32], n: usize, qp: i32, bit_depth: u32) { + let log2 = n.trailing_zeros(); + let bd_shift = bit_depth + log2 - 5; + let round = 1i64 << (bd_shift - 1); + let scale = (LEVEL_SCALE[(qp % 6) as usize] as i64) << (qp / 6); + for i in 0..n * n { + let v = l[i]; + d[i] = if v == 0 { 0 } else { ((v as i64 * 16 * scale + round) >> bd_shift).clamp(-32768, 32767) as i32 }; + } +} + +#[cfg(test)] +mod tests { + use super::*; + + /// Direct matrix form of the inverse transform, as written in the specification. + fn inverse_ref(c: &mut [i32], n: usize, dst: bool, bd: u32) { + let mut tmp = vec![0i32; n * n]; + let step = 32 / n; + for x in 0..n { + for y in 0..n { + let mut s = 0i32; + for j in 0..n { + let m = if dst { DST_MATRIX[j][y] as i32 } else { TRANS_MATRIX[j * step][y] as i32 }; + s += m * c[j * n + x]; + } + tmp[y * n + x] = ((s + 64) >> 7).clamp(-32768, 32767); + } + } + let bd_shift = 20 - bd; + for y in 0..n { + for x in 0..n { + let mut s = 0i32; + for j in 0..n { + let m = if dst { DST_MATRIX[j][x] as i32 } else { TRANS_MATRIX[j * step][x] as i32 }; + s += m * tmp[y * n + j]; + } + c[y * n + x] = (s + (1 << (bd_shift - 1))) >> bd_shift; + } + } + } + + #[test] + fn inverse_matches_matrix_form() { + let mut seed = 7u32; + for &(n, dst) in &[(4usize, true), (4, false), (8, false), (16, false), (32, false)] { + for _ in 0..20 { + let mut c = vec![0i32; n * n]; + for v in c.iter_mut() { + seed = seed.wrapping_mul(1664525).wrapping_add(1013904223); + if (seed >> 24).is_multiple_of(3) { + *v = ((seed >> 8) % 2001) as i32 - 1000; + } + } + let mut a = c.clone(); + let mut b = c.clone(); + inverse(&mut a, n, dst, 8); + inverse_ref(&mut b, n, dst, 8); + assert_eq!(a, b, "n {n} dst {dst}"); + } + } + } + + #[test] + fn round_trip_is_close() { + for &(n, dst) in &[(4usize, true), (8, false), (32, false)] { + for bd in [8u32, 10] { + let r: Vec = (0..n * n).map(|i| ((i * 37 + i / n * 11) % 61) as i32 - 30).collect(); + let mut c = vec![0; n * n]; + forward(&r, &mut c, n, dst, bd); + let mut l = vec![0; n * n]; + let qp = 4; + quantize(&c, &mut l, n, qp, true, bd); + let mut d = vec![0; n * n]; + dequantize(&l, &mut d, n, qp, bd); + inverse(&mut d, n, dst, bd); + let err: i32 = r.iter().zip(&d).map(|(a, b)| (a - b).abs()).max().unwrap(); + assert!(err <= 2, "n {n} bd {bd} err {err}"); + } + } + } +} diff --git a/crates/media-testkit/src/lib.rs b/crates/media-testkit/src/lib.rs index 36f609f789..38ef118831 100644 --- a/crates/media-testkit/src/lib.rs +++ b/crates/media-testkit/src/lib.rs @@ -157,3 +157,154 @@ pub fn generate_and_report(name: &str, outputs: &[PathBuf], generate: impl Fn println!("FIXTURE {status} {name}"); r } + +/// Check if ffmpeg has a specific encoder compiled in. +pub fn ffmpeg_has_encoder(ff: &Path, encoder: &str) -> bool { + let Ok(output) = Command::new(ff).args(["-hide_banner", "-encoders"]).output() else { + return false; + }; + if !output.status.success() { + return false; + } + parse_has_encoder(&String::from_utf8_lossy(&output.stdout), encoder) +} + +fn parse_has_encoder(stdout: &str, encoder: &str) -> bool { + stdout.lines().any(|line| { + let mut parts = line.split_whitespace(); + if let (Some(_flags), Some(name)) = (parts.next(), parts.next()) { name == encoder } else { false } + }) +} + +/// Find ffmpeg, or print a skip message and return None. +pub fn ffmpeg_or_skip(reason: &str) -> Option { + let ff = ffmpeg(); + if ff.is_none() { + eprintln!("SKIPPED ({reason}): ffmpeg not found"); + } + ff +} + +/// Log progress of long tests. +pub fn set_progress(label: &str) { + eprintln!("PROGRESS: {label}"); +} + +/// Run closure with an execution deadline, terminating with a panic if the limit is exceeded. +#[allow(clippy::panic)] +pub fn with_deadline(secs: u64, f: F) { + let timeout_secs = timeout_from_env( + secs, + std::env::var("CRAFT_TEST_TIMEOUT_SECS").ok().as_deref(), + std::env::var("FILMCRAFT_HOSTILE_TIMEOUT_SECS").ok().as_deref(), + ); + with_timeout(timeout_secs, f); +} + +fn timeout_from_env(secs: u64, craft_timeout: Option<&str>, legacy_timeout: Option<&str>) -> u64 { + craft_timeout.or(legacy_timeout).and_then(|s| s.parse().ok()).unwrap_or(secs) +} + +#[allow(clippy::panic)] +fn with_timeout(timeout_secs: u64, f: F) { + let (tx, rx) = std::sync::mpsc::channel(); + let handle = std::thread::spawn(move || { + f(); + let _ = tx.send(()); + }); + match rx.recv_timeout(std::time::Duration::from_secs(timeout_secs)) { + Ok(()) => { + if let Err(payload) = handle.join() { + std::panic::resume_unwind(payload); + } + } + Err(std::sync::mpsc::RecvTimeoutError::Timeout) => { + panic!("test exceeded execution deadline of {timeout_secs} seconds"); + } + Err(std::sync::mpsc::RecvTimeoutError::Disconnected) => { + if let Err(payload) = handle.join() { + std::panic::resume_unwind(payload); + } + } + } +} + +/// Return fixture directory for a crate/category, creating it if needed. +pub fn fixtures_dir(subdir: &str) -> PathBuf { + fixture_subdir(subdir) +} + +pub mod fixtures { + use super::temp_path; + use std::path::{Path, PathBuf}; + + /// Atomically publish a fixture by renaming `tmp` to `path`. + pub fn publish(tmp: &Path, path: &Path) -> std::io::Result<()> { + std::fs::rename(tmp, path) + } + + /// Generate fixture `out` using generator closure if it does not already exist. + pub fn generate bool>(out: &Path, f: F) -> Option { + if out.exists() { + return Some(out.to_path_buf()); + } + let tmp = temp_path(out); + if f(&tmp) { + match std::fs::rename(&tmp, out) { + Ok(()) => Some(out.to_path_buf()), + Err(_) => { + let _ = std::fs::remove_file(&tmp); + None + } + } + } else { + let _ = std::fs::remove_file(&tmp); + None + } + } +} + +#[cfg(test)] +mod tests { + use super::*; + + #[test] + fn parse_has_encoder_exact_match() { + let sample = "\ +Encoders: + V..... = Video + ------ + V....D libvpx-vp9 libvpx VP9 (codec vp9) + V....D libvpx libvpx VP8 (codec vp8) + A....D flac FLAC (Free Lossless Audio Codec) +"; + assert!(parse_has_encoder(sample, "libvpx")); + assert!(parse_has_encoder(sample, "libvpx-vp9")); + assert!(parse_has_encoder(sample, "flac")); + assert!(!parse_has_encoder(sample, "vp8")); + assert!(!parse_has_encoder(sample, "libvp")); + } + + #[test] + fn with_deadline_completes() { + with_deadline(5, || { + // fast operation + }); + } + + #[test] + #[should_panic(expected = "test exceeded execution deadline")] + fn with_deadline_times_out() { + with_timeout(1, || { + std::thread::sleep(std::time::Duration::from_secs(3)); + }); + } + + #[test] + fn timeout_environment_overrides_are_selected_separately() { + assert_eq!(timeout_from_env(300, Some("4"), Some("5")), 4); + assert_eq!(timeout_from_env(300, None, Some("5")), 5); + assert_eq!(timeout_from_env(300, Some("invalid"), Some("5")), 300); + assert_eq!(timeout_from_env(300, None, None), 300); + } +} diff --git a/crates/prores/Cargo.toml b/crates/prores/Cargo.toml new file mode 100644 index 0000000000..d1bab613ae --- /dev/null +++ b/crates/prores/Cargo.toml @@ -0,0 +1,24 @@ +[package] +name = "craft-prores" +description = "Clean-room pure-Rust Apple ProRes (SMPTE RDD 36) decoder and encoder for the Craft ecosystem" +version.workspace = true +edition.workspace = true +license.workspace = true +rust-version.workspace = true +repository.workspace = true + +[lints] +workspace = true + +[features] +default = ["threads"] +threads = ["dep:rayon"] + +[dependencies] +craft-media = { workspace = true, default-features = false, features = ["bitstream"] } +thiserror = { workspace = true } +rayon = { workspace = true, optional = true } + +[dev-dependencies] +craft-testkit = { workspace = true } + diff --git a/crates/prores/README.md b/crates/prores/README.md new file mode 100644 index 0000000000..e5ac32b7c3 --- /dev/null +++ b/crates/prores/README.md @@ -0,0 +1,109 @@ +# craft-prores + +Clean-room Apple ProRes decoder and encoder in pure Rust, written from the public [SMPTE RDD 36:2022](https://pub.smpte.org/doc/rdd36/20220909-pub/rdd36-2022.pdf) +bitstream description. Layer L0: depends only on `craft-media` (with `bitstream` feature), `thiserror` and (optional, +feature `threads`, default on) `rayon`. Builds for `wasm32-unknown-unknown`; no `unsafe`. + +## API + +```rust +use craft_prores::*; + +// Decode one QuickTime sample (one ProRes frame). +let frame: Frame = decode_frame(&sample)?; // 10-bit 4:2:2 / 12-bit 4:4:4 +let f10 = decode_frame_with(&sample, &DecodeOptions { bit_depth: Some(10), threads: true })?; +decode_frame_into(&sample, &DecodeOptions::default(), &mut frame_buf)?; // reuses planes +let hdr: FrameHeader = probe(&sample)?; // header only + +// Encode (progressive). +let mut enc = Encoder::new(Profile::Hq, 1920, 1080); // or Encoder::with_config(EncoderConfig { .. }) +let sample: Vec = enc.encode(&frame)?; // the QuickTime sample for fourcc Profile::fourcc() +``` + +`Frame { width, height, chroma: ChromaFormat::{Yuv422, Yuv444}, bit_depth, y, cb, cr: Vec, +alpha: Option>, interlace: Interlace, color: ColorInfo { primaries, transfer, matrix }, +aspect_ratio, frame_rate_code }` holds planar samples with no row padding (`chroma_width()` is +`ceil(width/2)` for 4:2:2). The colour codes are H.273 code points straight from the frame header. + +`Profile` = `Proxy` (`apco`), `Lt` (`apcs`), `Standard` (`apcn`), `Hq` (`apch`), `P4444` (`ap4h`), +`P4444Xq` (`ap4x`). The frame itself doesn't say which profile it is (only the container fourcc +does), so the decoder doesn't need to know. + +## Decoder + +- Frame header (dimensions, chroma format, interlace mode, aspect/frame-rate codes, colour + description, alpha type, optional luma/chroma quantisation matrices), picture header(s), slice + table, slices. +- Entropy decoding: RDD 36's adaptive Golomb-Rice/Exp-Golomb codebooks. DC is coded as the first + value plus sign-predicted differences. AC is run/level coded, interleaved across all blocks of a + slice component. +- Progressive and interlaced scan orders. Interlaced frames are two field pictures (top or bottom + field first) of `ceil(height/32)` macroblock rows each. +- Dequantisation `F = level × qmat[i] × qscale(qidx)` (qscale = qidx up to 128, then + `128 + 4·(qidx−128)`). +- IDCT: the exact 2-D orthonormal inverse DCT, evaluated separably in `f32`. It uses even/odd + symmetry, skips zero rows and fills DC-only blocks directly. Its error is below 2⁻⁸ in the + 12-bit domain. The IDCT output `f` is the 12-bit-domain sample minus 2048. The output at depth + `d` is `floor(f·2^(d−12) + 2^(d−1) + ½)`, clipped to `[4, 2^d − 5]` for d ≥ 10 (the four + lowest/highest codes are SDI reserved values; this matches common decoders at 10 and 12 bits) + and to `[1, 2^d − 2]` below 10 bits. +- 4:2:2 chroma blocks per macroblock are top/bottom. 4:4:4 chroma blocks run TL, BL, TR, BR, + while luma runs TL, TR, BL, BR. +- Alpha (8- or 16-bit coding): lossless difference/run coding in raster order over each slice's + 16 lines. The starting value is all-ones. Output is scaled to the output depth: 8-bit coding is + bit-replicated, 16-bit coding is truncated. +- Default output depth: 10 bits for 4:2:2 and 12 bits for 4:4:4. Any depth from 8 to 16 is + available on request. +- Slice-parallel: each macroblock row, covering both fields of interlaced frames, is decoded by a + rayon task directly into the output planes. +- Robustness: every size field is validated before allocation, codewords longer than 56 bits are + rejected, and arithmetic is overflow-safe. Mutated streams return `Err`, never panic (see + `tests/it/fuzz.rs`). + +## Encoder + +- Progressive ProRes 422 (Proxy/LT/Standard/HQ) and 4444/4444 XQ. For 4444 the frame's alpha + plane is coded with 16-bit alpha coding by default (`EncoderConfig::encode_alpha`). +- Input: planar `u16` at any depth from 8 to 16 (normally 10). The chroma format must match the + profile. Edge macroblocks are padded by replicating the edge. +- Flat quantisation matrices (all 4, not transmitted), 8 macroblocks per slice by default + (`log2_slice_mbs`), encoder id `fmcr`. +- Rate control targets Apple's published nominal rates (Mbit/s at 1920×1080, 29.97 fps: 45, 102, + 147, 220, 330, 500). These are converted to bits per macroblock (184, 417, 601, 900, 1349, + 2045), so frame targets scale with frame area (`Encoder::target_frame_bytes`). Alpha comes on + top of the target. Every slice is transformed once. Then a frame-level binary search finds the + smallest uniform quantiser index whose frame fits the budget, and as many slices as the rest of + the budget allows get one finer step. Detailed content lands within 0.1% of the target. Simple + content uses qidx 1 and comes in under the target. Every slice is kept within the 16-bit + size fields. + +## Accuracy and Conformance + +Decoded frames and encoders adhere strictly to SMPTE RDD 36 specifications. +Unit tests in `src/` verify header parsing, DCT/IDCT roundtrips, quantization, +entropy coding, progressive encoding, and alpha channel lossless reconstruction. + +## Alpha bitstream conformance + +Every alpha difference is followed by a run code, including a final singleton +(RDD 36:2022 §5.3.3, Tables 12–14). Omitting the final run produces a truncated +alpha payload: FFmpeg may accept it, while macOS native HD decoding fails with AVFoundation `-11821` / underlying `-12137`. + +The encoder tests check exact singleton codewords at 8 and 16 bits, run-length +boundaries through 2048 and split runs, final-value bit alignments, and complete +encoded slices for both profiles and all four slice widths. + +## Performance + +A 1080p HQ frame at the full nominal rate (918 KB) decodes at over 300 fps multi-threaded +and encodes in under 40 ms threaded on modern multi-core systems. + +## Gaps + +- The encoder is progressive-only (the decoder handles interlaced), uses flat quantisation + matrices (no perceptual matrices), and has no trellis/RDO. +- Output is not bit-exact with ffmpeg's integer IDCT; it stays within ±1 LSB of it. +- The 4:2:2 decoder output is fixed at the coded 10-bit precision unless another depth is + requested; there is no direct output of packed formats (v210, etc.). +- No container handling: frames are QuickTime samples; demuxing/muxing lives in + the `isobmff` feature of `craft-media`. diff --git a/crates/prores/src/bits.rs b/crates/prores/src/bits.rs new file mode 100644 index 0000000000..3089757be8 --- /dev/null +++ b/crates/prores/src/bits.rs @@ -0,0 +1,280 @@ +//! Fast MSB-first bit reader/writer for slice payloads (the hot path of the codec). +//! +//! Frame and picture headers use `craft_media::bitstream`; the entropy-coded slice data uses these +//! specialised helpers, which read 64-bit windows and decode a whole codeword per call. + +use crate::tables::Codebook; + +/// Bit reader over one slice component with a 64-bit MSB-aligned cache. Reads past the end +/// yield zero bits; callers check [`Reader::overrun`] once a component has been consumed. +pub(crate) struct Reader<'a> { + data: &'a [u8], + /// Next byte of `data` to load into the cache (may run past the end: zeros are loaded). + next: usize, + /// Cached bits, MSB first; bits below `avail` are zero. + cache: u64, + avail: u32, +} + +impl<'a> Reader<'a> { + #[inline] + pub fn new(data: &'a [u8]) -> Self { + let mut r = Self { data, next: 0, cache: 0, avail: 0 }; + r.refill(); + r + } + + /// Ensure at least 56 valid bits are cached. + #[inline(always)] + fn refill(&mut self) { + if self.avail >= 56 { + return; + } + if let Some(s) = self.data.get(self.next..self.next + 8) { + let w = u64::from_be_bytes([s[0], s[1], s[2], s[3], s[4], s[5], s[6], s[7]]); + self.cache |= w >> self.avail; + self.next += ((63 - self.avail) >> 3) as usize; + self.avail |= 56; + } else { + while self.avail <= 56 { + let b = self.data.get(self.next).copied().unwrap_or(0) as u64; + self.cache |= b << (56 - self.avail); + self.avail += 8; + self.next += 1; + } + } + } + + #[inline(always)] + fn consume(&mut self, n: u32) { + // n <= 56 <= avail + self.cache = if n >= 64 { 0 } else { self.cache << n }; + self.avail -= n; + } + + #[inline] + fn consumed(&self) -> usize { + self.next * 8 - self.avail as usize + } + + #[inline] + pub fn bits_left(&self) -> isize { + (self.data.len() * 8) as isize - self.consumed() as isize + } + + #[inline] + pub fn overrun(&self) -> bool { + self.consumed() > self.data.len() * 8 + } + + /// True when every remaining bit is zero (the component's padding), or nothing is left. + #[inline(always)] + pub fn only_padding_left(&mut self) -> bool { + let left = self.bits_left(); + if left > 56 { + return false; + } + if left <= 0 { + return true; + } + self.refill(); + self.cache >> (64 - left as u32) == 0 + } + + #[inline] + pub fn read(&mut self, n: u32) -> u32 { + debug_assert!(n <= 32); + if n == 0 { + return 0; + } + self.refill(); + let v = (self.cache >> (64 - n)) as u32; + self.consume(n); + v + } + + #[inline(always)] + pub fn read_bit(&mut self) -> bool { + if self.avail == 0 { + self.refill(); + } + let b = self.cache >> 63 == 1; + self.consume(1); + b + } + + /// Decode one codeword; `None` for codewords longer than 56 bits (never produced by valid + /// streams). + #[inline(always)] + pub fn read_cw(&mut self, cb: Codebook) -> Option { + self.refill(); + let w = self.cache; + let q = w.leading_zeros(); + let rq = cb.rice_q_max as u32; + if q <= rq { + let rice = cb.rice as u32; + let len = q + 1 + rice; + let low = if rice == 0 { 0 } else { ((w << (q + 1)) >> (64 - rice)) as u32 }; + self.consume(len); + Some((q << rice) | low) + } else { + let exp = cb.exp as u32; + let len = 2 * q - rq + exp; + if len > 56 { + return None; + } + let code = w >> (64 - len); + self.consume(len); + let v = code - (1u64 << exp) + (((rq + 1) as u64) << cb.rice); + u32::try_from(v).ok() + } + } +} + +/// Number of bits of the codeword for `val` in codebook `cb`. +#[inline] +pub(crate) fn cw_len(cb: Codebook, val: u32) -> u32 { + let rq = cb.rice_q_max as u32; + let rice = cb.rice as u32; + let rice_span = (rq + 1) << rice; + if val < rice_span { + (val >> rice) + 1 + rice + } else { + let exp = cb.exp as u32; + let v = (val - rice_span) as u64 + (1u64 << exp); + let nbits = 64 - v.leading_zeros(); // n + exp + 1 + let n = nbits - exp - 1; + (rq + 1 + n) + nbits + } +} + +/// A bit sink: either a real writer or a bit counter (for rate control trials). +pub(crate) trait Sink { + fn put(&mut self, val: u32, bits: u32); + fn put_cw(&mut self, cb: Codebook, val: u32); +} + +/// Counts bits without storing them. +#[derive(Default)] +pub(crate) struct Counter(pub u64); + +impl Sink for Counter { + #[inline] + fn put(&mut self, _val: u32, bits: u32) { + self.0 += bits as u64; + } + #[inline] + fn put_cw(&mut self, cb: Codebook, val: u32) { + self.0 += cw_len(cb, val) as u64; + } +} + +/// MSB-first bit writer. +#[derive(Default)] +pub(crate) struct Writer { + pub buf: Vec, + acc: u64, + n: u32, +} + +impl Writer { + pub fn new() -> Self { + Self::default() + } + /// Pad with zero bits to a byte boundary and return the bytes. + pub fn finish(mut self) -> Vec { + if self.n > 0 { + let pad = 8 - self.n; + self.put(0, pad); + } + self.buf + } +} + +impl Sink for Writer { + #[inline] + fn put(&mut self, val: u32, bits: u32) { + debug_assert!(bits <= 32); + if bits == 0 { + return; + } + let v = (val as u64) & ((1u64 << bits) - 1); + self.acc = (self.acc << bits) | v; + self.n += bits; + while self.n >= 8 { + self.n -= 8; + self.buf.push((self.acc >> self.n) as u8); + } + self.acc &= (1u64 << self.n) - 1; + } + + fn put_cw(&mut self, cb: Codebook, val: u32) { + let rq = cb.rice_q_max as u32; + let rice = cb.rice as u32; + let rice_span = (rq + 1) << rice; + if val < rice_span { + let q = val >> rice; + // q zeros, a one, then `rice` low bits + self.put(0, q); + self.put(1, 1); + self.put(val & ((1 << rice) - 1), rice); + } else { + let exp = cb.exp as u32; + let v = (val - rice_span) as u64 + (1u64 << exp); + let nbits = 64 - v.leading_zeros(); + let n = nbits - exp - 1; + let zeros = rq + 1 + n; + self.put(0, zeros.min(32)); + if zeros > 32 { + self.put(0, zeros - 32); + } + if nbits > 32 { + self.put((v >> 32) as u32, nbits - 32); + self.put(v as u32, 32); + } else { + self.put(v as u32, nbits); + } + } + } +} + +#[cfg(test)] +mod tests { + use super::*; + use crate::tables::*; + + #[test] + fn codeword_roundtrip_all_codebooks() { + let mut books = vec![FIRST_DC_CB]; + books.extend_from_slice(&DC_CB); + books.extend_from_slice(&RUN_CB); + books.extend_from_slice(&LEVEL_CB); + let vals: Vec = (0..300).chain([1000, 4095, 65535, 1 << 20]).collect(); + for cb in books { + let mut w = Writer::new(); + let mut total = 0; + for &v in &vals { + w.put_cw(cb, v); + total += cw_len(cb, v); + } + let bits = w.buf.len() as u32 * 8 + w.n; + assert_eq!(bits, total); + let data = w.finish(); + let mut r = Reader::new(&data); + for &v in &vals { + assert_eq!(r.read_cw(cb), Some(v), "{cb:?}"); + } + assert!(!r.overrun()); + } + } + + #[test] + fn exp_golomb5_equivalence() { + // FIRST_DC_CB is Exp-Golomb order 5: value 32 is `01` + 6 bits (000000) + assert_eq!(cw_len(FIRST_DC_CB, 0), 6); + assert_eq!(cw_len(FIRST_DC_CB, 31), 6); + assert_eq!(cw_len(FIRST_DC_CB, 32), 8); + assert_eq!(cw_len(FIRST_DC_CB, 95), 8); + assert_eq!(cw_len(FIRST_DC_CB, 96), 10); + } +} diff --git a/crates/prores/src/dct.rs b/crates/prores/src/dct.rs new file mode 100644 index 0000000000..01faf6613a --- /dev/null +++ b/crates/prores/src/dct.rs @@ -0,0 +1,160 @@ +//! 8×8 orthonormal DCT-II / DCT-III (IDCT) in single precision. +//! +//! The reconstruction IDCT is defined as the exact real-valued 2-D inverse DCT +//! `f(x,y) = Σ_u Σ_v C(u)C(v)/4 · F(v,u) · cos((2x+1)uπ/16) · cos((2y+1)vπ/16)` +//! evaluated separably (rows then columns) in `f32`. For ProRes coefficient ranges the `f32` +//! evaluation error is below 2⁻⁸ in the 12-bit sample domain, so the rounded output equals the +//! rounded exact IDCT except when the exact value lies within that distance of a rounding tie. + +use crate::tables::BASIS; + +/// In-place inverse DCT. `block` holds dequantised coefficients in raster order +/// (`block[v*8 + u]`, `v` = vertical frequency) and receives samples (`block[y*8 + x]`). +/// +/// Each 1-D pass uses the even/odd symmetry of the basis (`B[u][7-x] = ±B[u][x]` for even/odd +/// `u`) and skips all-zero rows; DC-only blocks are filled directly. +#[inline] +pub(crate) fn idct8x8(block: &mut [f32; 64]) { + let mut t = [[0f32; 8]; 8]; + let mut nz = [false; 8]; + let mut dc_only = true; + // Horizontal pass: t[r][x] = Σ_u F[r][u] · B[u][x] + for (r, c) in block.as_chunks::<8>().0.iter().enumerate() { + if c[1..].iter().all(|&v| v == 0.0) { + if c[0] != 0.0 { + t[r] = [c[0] * BASIS[0][0]; 8]; + nz[r] = true; + dc_only &= r == 0; + } + continue; + } + dc_only = false; + nz[r] = true; + let mut out = [0f32; 8]; + for x in 0..4 { + let e = c[0] * BASIS[0][x] + c[2] * BASIS[2][x] + c[4] * BASIS[4][x] + c[6] * BASIS[6][x]; + let o = c[1] * BASIS[1][x] + c[3] * BASIS[3][x] + c[5] * BASIS[5][x] + c[7] * BASIS[7][x]; + out[x] = e + o; + out[7 - x] = e - o; + } + t[r] = out; + } + if dc_only { + *block = [t[0][0] * BASIS[0][0]; 64]; + return; + } + // Vertical pass: f[y][x] = Σ_v t[v][x] · B[v][y] + for y in 0..4 { + let mut e = [0f32; 8]; + let mut o = [0f32; 8]; + for v in [0, 2, 4, 6] { + if nz[v] { + let b = BASIS[v][y]; + for x in 0..8 { + e[x] += t[v][x] * b; + } + } + } + for v in [1, 3, 5, 7] { + if nz[v] { + let b = BASIS[v][y]; + for x in 0..8 { + o[x] += t[v][x] * b; + } + } + } + for x in 0..8 { + block[y * 8 + x] = e[x] + o[x]; + block[(7 - y) * 8 + x] = e[x] - o[x]; + } + } +} + +/// In-place forward DCT (samples in, coefficients out; same layout as [`idct8x8`]). +pub(crate) fn fdct8x8(block: &mut [f32; 64]) { + let mut t = [[0f32; 8]; 8]; + // Horizontal: t[y][u] = Σ_x f[y][x] · B[u][x] + for y in 0..8 { + for u in 0..8 { + let mut s = 0f32; + for x in 0..8 { + s += block[y * 8 + x] * BASIS[u][x]; + } + t[y][u] = s; + } + } + // Vertical: F[v][u] = Σ_y t[y][u] · B[v][y] + for v in 0..8 { + let mut acc = [0f32; 8]; + for y in 0..8 { + let b = BASIS[v][y]; + for u in 0..8 { + acc[u] += t[y][u] * b; + } + } + block[v * 8..v * 8 + 8].copy_from_slice(&acc); + } +} + +#[cfg(test)] +mod tests { + use super::*; + + fn reference_idct(c: &[f32; 64]) -> [f64; 64] { + let mut out = [0f64; 64]; + for y in 0..8 { + for x in 0..8 { + let mut s = 0f64; + for v in 0..8 { + for u in 0..8 { + let cu = if u == 0 { std::f64::consts::FRAC_1_SQRT_2 } else { 1.0 }; + let cv = if v == 0 { std::f64::consts::FRAC_1_SQRT_2 } else { 1.0 }; + s += cu * cv / 4.0 + * c[v * 8 + u] as f64 + * (((2 * x + 1) * u) as f64 * std::f64::consts::PI / 16.0).cos() + * (((2 * y + 1) * v) as f64 * std::f64::consts::PI / 16.0).cos(); + } + } + out[y * 8 + x] = s; + } + } + out + } + + #[test] + fn idct_matches_double_reference() { + let mut seed = 12345u32; + let mut rnd = || { + seed = seed.wrapping_mul(1664525).wrapping_add(1013904223); + seed >> 8 + }; + for _ in 0..200 { + let mut c = [0f32; 64]; + for v in c.iter_mut() { + if rnd() % 3 == 0 { + *v = ((rnd() % 8192) as i32 - 4096) as f32; + } + } + let r = reference_idct(&c); + let mut b = c; + idct8x8(&mut b); + for i in 0..64 { + assert!((b[i] as f64 - r[i]).abs() < 4e-3, "{} vs {}", b[i], r[i]); + } + } + } + + #[test] + fn fdct_inverts_idct() { + let mut b = [0f32; 64]; + for (i, v) in b.iter_mut().enumerate() { + *v = ((i * 37) % 200) as f32 - 100.0; + } + let orig = b; + fdct8x8(&mut b); + idct8x8(&mut b); + for i in 0..64 { + assert!((b[i] - orig[i]).abs() < 1e-3); + } + } +} diff --git a/crates/prores/src/decode.rs b/crates/prores/src/decode.rs new file mode 100644 index 0000000000..bf475ba606 --- /dev/null +++ b/crates/prores/src/decode.rs @@ -0,0 +1,519 @@ +//! ProRes frame decoding. + +use crate::bits::Reader; +use crate::dct::idct8x8; +use crate::header::{FrameHeader, PictureHeader, row_slice_widths}; +use crate::tables::{DC_CB, DC_CTX_INIT, FIRST_DC_CB, LEVEL_CB, LEVEL_CTX_INIT, RUN_CB, RUN_CTX_INIT, SCAN_INTERLACED, SCAN_PROGRESSIVE, qscale}; +use crate::{AlphaType, ChromaFormat, Error, Frame, Interlace, Result}; + +/// Maximum aggregate decoded plane storage for one ProRes frame (300 MiB; 8K ProRes 4444 with alpha is ~265 MiB). +const MAX_FRAME_BYTES: usize = 300 * 1024 * 1024; + +/// Decoder options. +#[derive(Debug, Clone, Copy, PartialEq, Eq)] +pub struct DecodeOptions { + /// Output bit depth (8..=16). `None`: 10 for 4:2:2, 12 for 4:4:4 (the precision ProRes + /// codes each format at). + pub bit_depth: Option, + /// Decode slices in parallel (requires the `threads` feature). + pub threads: bool, +} + +impl Default for DecodeOptions { + fn default() -> Self { + DecodeOptions { bit_depth: None, threads: true } + } +} + +/// Parse only the frame header (cheap; for probing geometry and format). +pub fn probe(data: &[u8]) -> Result { + FrameHeader::parse(data).map(|(h, _, _)| h) +} + +/// Decode one ProRes frame (the contents of one QuickTime sample) with default options. +pub fn decode_frame(data: &[u8]) -> Result { + decode_frame_with(data, &DecodeOptions::default()) +} + +/// Maps IDCT output (12-bit-domain signed samples) to the output bit depth. +#[derive(Clone, Copy)] +struct OutConv { + scale: f32, + bias: f32, + min: i32, + max: i32, +} + +impl OutConv { + fn new(depth: u8) -> OutConv { + // The IDCT produces `f`, where the 12-bit sample is `f + 2048`. + let scale = (2f32).powi(depth as i32 - 12); + // Samples are clipped away from the extremes: the four lowest and highest codes are + // excluded at 10 bits and above (0..=3 and 1020..=1023 are the SDI timing-reference codes + // at 10 bits; the same margin is kept at 12 bits, matching common decoders), and the + // single lowest/highest code at 8 and 9 bits. + let reserved = if depth >= 10 { 4 } else { 1 }; + OutConv { scale, bias: (1u32 << (depth - 1)) as f32 + 0.5, min: reserved, max: (1i32 << depth) - 1 - reserved } + } + #[inline(always)] + fn conv(&self, v: f32) -> u16 { + // Truncation equals floor here: negative values clamp to `min` (> 0) either way. + ((v * self.scale + self.bias) as i32).clamp(self.min, self.max) as u16 + } +} + +struct Ctx { + width: usize, + cwidth: usize, + chroma444: bool, + qmat_luma: [u8; 64], + qmat_chroma: [u8; 64], + scan: &'static [u8; 64], + alpha: AlphaType, + conv: OutConv, + depth: u8, +} + +/// Output rows belonging to one macroblock row of every coded picture. +struct Band<'a> { + y: &'a mut [u16], + cb: &'a mut [u16], + cr: &'a mut [u16], + a: Option<&'a mut [u16]>, + rows: usize, +} + +struct Picture { + /// Line parity within the frame (0 = even lines) for field pictures. + parity: usize, + /// Byte ranges of each slice, row-major. + slices: Vec<(usize, usize)>, +} + +struct Scratch { + coeffs: Vec<[f32; 64]>, + alpha: Vec, +} + +impl Scratch { + fn new() -> Scratch { + Scratch { coeffs: vec![[0f32; 64]; 32], alpha: vec![0; 16 * 128] } + } +} + +/// Decode one ProRes frame. +pub fn decode_frame_with(data: &[u8], opts: &DecodeOptions) -> Result { + let mut f = Frame::new(0, 0, ChromaFormat::Yuv422, 10, false); + decode_frame_into(data, opts, &mut f)?; + Ok(f) +} + +/// Decode one ProRes frame into `out`, reusing its plane allocations (the fastest way to decode +/// a sequence). On error the contents of `out` are unspecified. +pub fn decode_frame_into(data: &[u8], opts: &DecodeOptions, out: &mut Frame) -> Result<()> { + let (hdr, mut off, frame_size) = FrameHeader::parse(data)?; + let data = &data[..frame_size]; + let depth = opts.bit_depth.unwrap_or(match hdr.chroma { + ChromaFormat::Yuv422 => 10, + ChromaFormat::Yuv444 => 12, + }); + if !(8..=16).contains(&depth) { + return Err(Error::Unsupported("output bit depth")); + } + let width = hdr.width as usize; + let height = hdr.height as usize; + let cwidth = hdr.chroma.chroma_width(hdr.width as u32) as usize; + let luma = width.checked_mul(height).ok_or(Error::Invalid("frame dimensions overflow"))?; + let chroma = cwidth.checked_mul(height).ok_or(Error::Invalid("chroma dimensions overflow"))?; + let samples = luma + .checked_add(chroma.checked_mul(2).ok_or(Error::Invalid("chroma plane count overflows"))?) + .and_then(|n| if hdr.alpha == AlphaType::None { Some(n) } else { n.checked_add(luma) }) + .ok_or(Error::Invalid("frame plane count overflows"))?; + if samples.checked_mul(std::mem::size_of::()).is_none_or(|n| n > MAX_FRAME_BYTES) { + return Err(Error::Invalid("frame dimensions exceed the decoder memory limit")); + } + let interlaced = hdr.interlace != Interlace::Progressive; + let mb_width = width.div_ceil(16); + let mb_height = if interlaced { height.div_ceil(32) } else { height.div_ceil(16) }; + + let npics = if interlaced { 2 } else { 1 }; + let mut pics = Vec::with_capacity(npics); + for p in 0..npics { + let ph = PictureHeader::parse(data.get(off..).ok_or(Error::Truncated)?)?; + let pic_end = off.checked_add(ph.picture_size).ok_or(Error::Truncated)?; + if pic_end > data.len() || ph.picture_size < ph.header_size { + return Err(Error::Truncated); + } + let widths = row_slice_widths(mb_width, ph.log2_slice_mbs); + let nslices = widths.len() * mb_height; + if ph.num_slices != 0 && ph.num_slices != nslices { + return Err(Error::Invalid("slice count does not match frame geometry")); + } + let table = off + ph.header_size; + let mut pos = table + 2 * nslices; + if pos > pic_end { + return Err(Error::Truncated); + } + let mut slices = Vec::with_capacity(nslices); + for i in 0..nslices { + let sz = u16::from_be_bytes([data[table + 2 * i], data[table + 2 * i + 1]]) as usize; + // Every slice carries at least its 6-byte header. + if sz < 6 || pos + sz > pic_end { + return Err(Error::Truncated); + } + slices.push((pos, sz)); + pos += sz; + } + let parity = match (hdr.interlace, p) { + (Interlace::BottomFieldFirst, 0) | (Interlace::TopFieldFirst, 1) => 1, + _ => 0, + }; + pics.push((Picture { parity, slices }, widths)); + off = pic_end; + } + + let ctx = Ctx { + width, + cwidth, + chroma444: hdr.chroma == ChromaFormat::Yuv444, + qmat_luma: hdr.qmat_luma, + qmat_chroma: hdr.qmat_chroma, + scan: if interlaced { &SCAN_INTERLACED } else { &SCAN_PROGRESSIVE }, + alpha: hdr.alpha, + conv: OutConv::new(depth), + depth, + }; + + let take = |v: &mut Vec, n: usize| { + let mut v = std::mem::take(v); + v.resize(n, 0); + v + }; + let mut y = take(&mut out.y, width * height); + let mut cb = take(&mut out.cb, cwidth * height); + let mut cr = take(&mut out.cr, cwidth * height); + let mut alpha = match (hdr.alpha, out.alpha.take()) { + (AlphaType::None, _) => None, + (_, a) => Some(take(&mut a.unwrap_or_default(), width * height)), + }; + + let band_h = if interlaced { 32 } else { 16 }; + let mut bands: Vec = Vec::with_capacity(mb_height); + { + let mut yc = y.chunks_mut(band_h * width); + let mut cbc = cb.chunks_mut(band_h * cwidth); + let mut crc = cr.chunks_mut(band_h * cwidth); + let mut ac = alpha.as_mut().map(|a| a.chunks_mut(band_h * width)); + while let (Some(y), Some(cb), Some(cr)) = (yc.next(), cbc.next(), crc.next()) { + let rows = y.len() / width; + let a = ac.as_mut().and_then(|it| it.next()); + bands.push(Band { y, cb, cr, a, rows }); + } + } + + let step = if interlaced { 2 } else { 1 }; + let run_band = |(row, band): (usize, &mut Band), scratch: &mut Scratch| -> Result<()> { + for (pi, (pic, widths)) in pics.iter().enumerate() { + let mut mb_x = 0; + for (k, &w) in widths.iter().enumerate() { + let si = row * widths.len() + k; + let (start, len) = pic.slices[si]; + decode_slice(&ctx, &data[start..start + len], mb_x, w, band, Lines { step, parity: pic.parity }, scratch) + .map_err(|_| Error::Slice { picture: pi, slice: si })?; + mb_x += w; + } + } + Ok(()) + }; + + #[cfg(feature = "threads")] + if opts.threads { + use rayon::prelude::*; + bands.par_iter_mut().enumerate().try_for_each_init(Scratch::new, |s, b| run_band(b, s))?; + } else { + let mut s = Scratch::new(); + bands.iter_mut().enumerate().try_for_each(|b| run_band(b, &mut s))?; + } + #[cfg(not(feature = "threads"))] + { + let mut s = Scratch::new(); + bands.iter_mut().enumerate().try_for_each(|b| run_band(b, &mut s))?; + } + drop(bands); + + *out = Frame { + width: hdr.width as u32, + height: hdr.height as u32, + chroma: hdr.chroma, + bit_depth: depth, + y, + cb, + cr, + alpha, + interlace: hdr.interlace, + color: hdr.color, + aspect_ratio: hdr.aspect_ratio, + frame_rate_code: hdr.frame_rate_code, + }; + Ok(()) +} + +#[inline] +fn be16(d: &[u8], i: usize) -> usize { + u16::from_be_bytes([d[i], d[i + 1]]) as usize +} + +/// Dequantisation factors (matrix × scale) for a slice. +fn qfactors(qmat: &[u8; 64], qs: u32) -> [f32; 64] { + let mut q = [0f32; 64]; + for i in 0..64 { + q[i] = (qmat[i] as u32 * qs) as f32; + } + q +} + +/// Line stepping of the picture inside its frame: `step` is 2 for field pictures, `parity` the first line. +#[derive(Clone, Copy)] +struct Lines { + step: usize, + parity: usize, +} + +fn decode_slice(ctx: &Ctx, d: &[u8], mb_x: usize, mbs: usize, band: &mut Band, il: Lines, s: &mut Scratch) -> std::result::Result<(), ()> { + let Lines { step, parity } = il; + if d.len() < 6 { + return Err(()); + } + let hsize = (d[0] >> 3) as usize; + let has_alpha = ctx.alpha != AlphaType::None; + if hsize < if has_alpha { 8 } else { 6 } || hsize > d.len() { + return Err(()); + } + let qs = qscale(d[1]); + let ysize = be16(d, 2); + let cbsize = be16(d, 4); + let body = d.len() - hsize; + let crsize = if has_alpha { be16(d, 6) } else { body.checked_sub(ysize + cbsize).ok_or(())? }; + if ysize + cbsize + crsize > body { + return Err(()); + } + let mut p = hsize; + let ydata = &d[p..p + ysize]; + p += ysize; + let cbdata = &d[p..p + cbsize]; + p += cbsize; + let crdata = &d[p..p + crsize]; + p += crsize; + let adata = &d[p..]; + + let log2_mbs = mbs.trailing_zeros(); + let x0 = mb_x * 16; + + // Luma + let ql = qfactors(&ctx.qmat_luma, qs); + let nb = 4 * mbs; + decode_component(ydata, log2_mbs + 2, ctx.scan, &ql, &mut s.coeffs[..nb])?; + for (b, blk) in s.coeffs[..nb].iter_mut().enumerate() { + idct8x8(blk); + let (mb, i) = (b >> 2, b & 3); + put_block(blk, band.y, ctx.width, (x0 + mb * 16 + (i & 1) * 8, (i >> 1) * 8), band.rows, il, ctx.conv); + } + + // Chroma + let qc = qfactors(&ctx.qmat_chroma, qs); + let (cnb, clog2) = if ctx.chroma444 { (4 * mbs, log2_mbs + 2) } else { (2 * mbs, log2_mbs + 1) }; + for (data, plane) in [(cbdata, &mut *band.cb), (crdata, &mut *band.cr)] { + decode_component(data, clog2, ctx.scan, &qc, &mut s.coeffs[..cnb])?; + for (b, blk) in s.coeffs[..cnb].iter_mut().enumerate() { + idct8x8(blk); + let (bx, by) = if ctx.chroma444 { + // 4:4:4 chroma blocks run down each 8-wide column pair: TL, BL, TR, BR. + let (mb, i) = (b >> 2, b & 3); + (x0 + mb * 16 + (i >> 1) * 8, (i & 1) * 8) + } else { + let (mb, i) = (b >> 1, b & 1); + (mb_x * 8 + mb * 8, i * 8) + }; + put_block(blk, plane, ctx.cwidth, (bx, by), band.rows, il, ctx.conv); + } + } + + // Alpha + if let Some(a) = band.a.as_deref_mut() { + let w = mbs * 16; + let n = w * 16; + let buf = &mut s.alpha[..n]; + decode_alpha(adata, ctx.alpha, buf)?; + let depth = ctx.depth as u32; + let conv = |v: u16| -> u16 { + let v = v as u32; + (match ctx.alpha { + AlphaType::Bits8 => { + if depth >= 8 { + let r = (v << (depth - 8)) | (v >> (16 - depth).min(8)); + r & ((1 << depth) - 1) + } else { + v + } + } + _ => v >> (16 - depth), + }) as u16 + }; + for py in 0..16 { + let line = py * step + parity; + if line >= band.rows { + break; + } + let row = &mut a[line * ctx.width..(line + 1) * ctx.width]; + let xe = (x0 + w).min(ctx.width); + if x0 >= xe { + continue; + } + for (o, &v) in row[x0..xe].iter_mut().zip(&buf[py * w..py * w + (xe - x0)]) { + *o = conv(v); + } + } + } + Ok(()) +} + +#[inline] +fn to_signed(code: u32) -> i64 { + let c = code as i64; + (c >> 1) ^ -(c & 1) +} + +/// Entropy-decode and dequantise one slice component into `blocks` (raster-order coefficients). +fn decode_component(data: &[u8], log2_blocks: u32, scan: &[u8; 64], q: &[f32; 64], blocks: &mut [[f32; 64]]) -> std::result::Result<(), ()> { + for b in blocks.iter_mut() { + *b = [0f32; 64]; + } + let n = blocks.len(); + let mut r = Reader::new(data); + + // DC coefficients + let code = r.read_cw(FIRST_DC_CB).ok_or(())?; + let mut dc = to_signed(code); + blocks[0][0] = dc as f32 * q[0]; + let mut code = DC_CTX_INIT; + let mut sign = 0i64; + for blk in blocks.iter_mut().skip(1) { + code = r.read_cw(DC_CB[code.min(6) as usize]).ok_or(())?; + if code == 0 { + sign = 0; + } else { + sign ^= -((code & 1) as i64); + } + let mag = (code as i64 + 1) >> 1; + dc = (dc + ((mag ^ sign) - sign)).clamp(-(1 << 24), 1 << 24); + blk[0] = dc as f32 * q[0]; + } + + // AC coefficients, interleaved across blocks: position = scan_index * n + block + let mask = n - 1; + let total = 64 * n; + let mut pos = mask; + let mut run_ctx = RUN_CTX_INIT; + let mut lev_ctx = LEVEL_CTX_INIT; + while !r.only_padding_left() { + let run = r.read_cw(RUN_CB[run_ctx.min(15) as usize]).ok_or(())?; + run_ctx = run; + pos += run as usize + 1; + if pos >= total { + return Err(()); + } + let lev = r.read_cw(LEVEL_CB[lev_ctx.min(9) as usize]).ok_or(())?.saturating_add(1); + lev_ctx = lev; + let neg = r.read_bit(); + let i = scan[pos >> log2_blocks] as usize; + let v = lev as f32 * q[i]; + blocks[pos & mask][i] = if neg { -v } else { v }; + } + if r.overrun() { + return Err(()); + } + Ok(()) +} + +/// Decode a slice's alpha plane (raster order over the slice's 16 lines) into `out` +/// (native 8- or 16-bit values). +fn decode_alpha(data: &[u8], ty: AlphaType, out: &mut [u16]) -> std::result::Result<(), ()> { + let bits = if ty == AlphaType::Bits16 { 16 } else { 8 }; + let short_bits = if bits == 16 { 7 } else { 4 }; + let mask: u32 = (1 << bits) - 1; + let mut r = Reader::new(data); + let mut val: u32 = mask; + let n = out.len(); + let mut idx = 0; + 'outer: loop { + loop { + let d = if r.read_bit() { + r.read(bits) + } else { + let c = r.read(short_bits); + let m = (c + 2) >> 1; + if c & 1 == 1 { m.wrapping_neg() } else { m } + }; + val = val.wrapping_add(d) & mask; + out[idx] = val as u16; + idx += 1; + if idx >= n { + break 'outer; + } + if r.bits_left() <= 0 || !r.read_bit() { + break; + } + } + let mut run = r.read(4) as usize; + if run == 0 { + run = r.read(11) as usize; + } + let run = run.min(n - idx); + out[idx..idx + run].fill(val as u16); + idx += run; + if idx >= n { + break; + } + if r.bits_left() <= 0 { + // Streams may end early; the remainder repeats the last value. + out[idx..].fill(val as u16); + break; + } + } + if r.bits_left() < -64 { + return Err(()); + } + Ok(()) +} + +#[inline] +fn put_block(f: &[f32; 64], plane: &mut [u16], stride: usize, (x0, y0): (usize, usize), rows: usize, il: Lines, conv: OutConv) { + let Lines { step, parity } = il; + if x0 >= stride { + return; + } + let xn = (stride - x0).min(8); + if xn == 8 && (y0 + 7) * step + parity < rows { + for yy in 0..8 { + let o = ((y0 + yy) * step + parity) * stride + x0; + let (Some(row), Some(src)) = (plane.get_mut(o..).and_then(|p| p.first_chunk_mut::<8>()), f.as_chunks::<8>().0.get(yy)) else { + return; + }; + for x in 0..8 { + row[x] = conv.conv(src[x]); + } + } + return; + } + for yy in 0..8 { + let line = (y0 + yy) * step + parity; + if line >= rows { + break; + } + let row = &mut plane[line * stride + x0..line * stride + x0 + xn]; + let src = &f[yy * 8..yy * 8 + xn]; + for (o, &v) in row.iter_mut().zip(src) { + *o = conv.conv(v); + } + } +} diff --git a/crates/prores/src/encode.rs b/crates/prores/src/encode.rs new file mode 100644 index 0000000000..1c4fccb3d0 --- /dev/null +++ b/crates/prores/src/encode.rs @@ -0,0 +1,775 @@ +//! ProRes frame encoding (progressive) with per-slice quantiser rate control. + +use crate::bits::{Counter, Sink, Writer}; +use crate::dct::fdct8x8; +use crate::header::{FRAME_ID, FrameHeader, PictureHeader, row_slice_widths}; +use crate::tables::{DC_CB, DC_CTX_INIT, FIRST_DC_CB, LEVEL_CB, LEVEL_CTX_INIT, RUN_CB, RUN_CTX_INIT, SCAN_PROGRESSIVE, qscale}; +use crate::{AlphaType, ChromaFormat, ColorInfo, Error, Frame, Interlace, Profile, Result}; + +/// Encoder configuration. +#[derive(Debug, Clone, PartialEq, Eq)] +pub struct EncoderConfig { + pub profile: Profile, + pub width: u32, + pub height: u32, + /// Coded-bits budget per macroblock; `None` uses [`Profile::nominal_bits_per_mb`]. + pub bits_per_mb: Option, + /// log2 of the macroblocks per slice (0..=3; 3 = 8 macroblocks, the common choice). + pub log2_slice_mbs: u8, + pub color: ColorInfo, + pub aspect_ratio: u8, + pub frame_rate_code: u8, + /// Encode the frame's alpha plane (4444 profiles only; 16-bit alpha coding). + pub encode_alpha: bool, + /// Encode slices in parallel (requires the `threads` feature). + pub threads: bool, + /// Four-character encoder identifier written into the frame header. + pub encoder_id: [u8; 4], +} + +impl EncoderConfig { + pub fn new(profile: Profile, width: u32, height: u32) -> EncoderConfig { + EncoderConfig { + profile, + width, + height, + bits_per_mb: None, + log2_slice_mbs: 3, + color: ColorInfo::default(), + aspect_ratio: 0, + frame_rate_code: 0, + encode_alpha: true, + threads: true, + encoder_id: *b"fmcr", + } + } +} + +/// ProRes encoder for one stream of same-sized progressive frames. +#[derive(Debug, Clone)] +pub struct Encoder { + cfg: EncoderConfig, + /// The previous frame's uniform quantiser: where the next frame's search starts. + last_q: u8, +} + +/// Quantiser search range (RDD 36 allows 1..=224). +const Q_MIN: u8 = 1; +const Q_MAX: u8 = 224; + +impl Encoder { + pub fn new(profile: Profile, width: u32, height: u32) -> Encoder { + Encoder::with_config(EncoderConfig::new(profile, width, height)) + } + + pub fn with_config(cfg: EncoderConfig) -> Encoder { + Encoder { cfg, last_q: Q_MIN } + } + + pub fn config(&self) -> &EncoderConfig { + &self.cfg + } + + pub fn bits_per_mb(&self) -> u32 { + self.cfg.bits_per_mb.unwrap_or_else(|| self.cfg.profile.nominal_bits_per_mb()) + } + + /// The rate-control target for one frame in bytes (excluding alpha). + pub fn target_frame_bytes(&self) -> usize { + let mbs = (self.cfg.width as usize).div_ceil(16) * (self.cfg.height as usize).div_ceil(16); + mbs * self.bits_per_mb() as usize / 8 + } + + /// Encode one frame. Input planes may be at any bit depth 8..=16 (10 is typical); the frame's + /// chroma format must match the profile. + pub fn encode(&mut self, frame: &Frame) -> Result> { + let cfg = &self.cfg; + if frame.width != cfg.width || frame.height != cfg.height { + return Err(Error::Input(format!("frame is {}x{}, encoder is {}x{}", frame.width, frame.height, cfg.width, cfg.height))); + } + if cfg.width == 0 || cfg.height == 0 || cfg.width > 65535 || cfg.height > 65535 { + return Err(Error::Input("unsupported frame size".into())); + } + let chroma = cfg.profile.chroma(); + if frame.chroma != chroma { + return Err(Error::Input(format!("{:?} input for a {:?} profile", frame.chroma, chroma))); + } + if !(8..=16).contains(&frame.bit_depth) { + return Err(Error::Input("bit depth must be 8..=16".into())); + } + let (w, h) = (cfg.width as usize, cfg.height as usize); + let cw = chroma.chroma_width(cfg.width) as usize; + if frame.y.len() < w * h || frame.cb.len() < cw * h || frame.cr.len() < cw * h { + return Err(Error::Input("plane too small".into())); + } + let with_alpha = cfg.encode_alpha && chroma == ChromaFormat::Yuv444 && frame.alpha.is_some(); + if let Some(a) = &frame.alpha + && with_alpha + && a.len() < w * h + { + return Err(Error::Input("alpha plane too small".into())); + } + if cfg.log2_slice_mbs > 3 { + return Err(Error::Input("log2_slice_mbs must be 0..=3".into())); + } + + let mb_width = w.div_ceil(16); + let mb_height = h.div_ceil(16); + let widths = row_slice_widths(mb_width, cfg.log2_slice_mbs); + if widths.len().saturating_mul(mb_height) > u16::MAX as usize { + return Err(Error::Input("too many slices for the picture header; increase log2_slice_mbs".into())); + } + let mut jobs = Vec::with_capacity(widths.len() * mb_height); + for row in 0..mb_height { + let mut x = 0; + for &sw in &widths { + jobs.push((x, row, sw)); + x += sw; + } + } + let src = Source { + frame, + w, + h, + cw, + chroma444: chroma == ChromaFormat::Yuv444, + scale: (2f32).powi(12 - frame.bit_depth as i32), + alpha: with_alpha, + bits_per_mb: self.bits_per_mb(), + }; + let threads = cfg.threads; + let prepared: Vec = par_map(threads, &jobs, |&(x, row, sw)| prepare_slice(&src, x, row, sw)); + let (qs, qg) = choose_quantisers(&prepared, &src, mb_width * mb_height, threads, self.last_q); + self.last_q = qg; + let items: Vec<(&SliceData, u8)> = prepared.iter().zip(qs).collect(); + let slices: Vec> = par_map(threads, &items, |&(sd, q)| write_slice(sd, q)); + + let hdr = FrameHeader { + header_size: 0, + bitstream_version: if chroma == ChromaFormat::Yuv444 { 1 } else { 0 }, + encoder_id: cfg.encoder_id, + width: cfg.width as u16, + height: cfg.height as u16, + chroma, + interlace: Interlace::Progressive, + aspect_ratio: cfg.aspect_ratio & 15, + frame_rate_code: cfg.frame_rate_code & 15, + color: cfg.color, + alpha: if with_alpha { AlphaType::Bits16 } else { AlphaType::None }, + qmat_luma: [4; 64], + qmat_chroma: [4; 64], + luma_matrix_present: false, + chroma_matrix_present: false, + }; + let fh = hdr.write(); + let slice_bytes: usize = slices.iter().map(Vec::len).sum(); + let pic_size = 8 + 2 * slices.len() + slice_bytes; + let total = 8 + fh.len() + pic_size; + let mut out = Vec::with_capacity(total); + out.extend_from_slice(&(total as u32).to_be_bytes()); + out.extend_from_slice(&FRAME_ID); + out.extend_from_slice(&fh); + PictureHeader { header_size: 8, picture_size: pic_size, num_slices: slices.len(), log2_slice_mbs: cfg.log2_slice_mbs }.write(&mut out); + for s in &slices { + out.extend_from_slice(&(s.len() as u16).to_be_bytes()); + } + for s in &slices { + out.extend_from_slice(s); + } + debug_assert_eq!(out.len(), total); + Ok(out) + } +} + +struct Source<'a> { + frame: &'a Frame, + w: usize, + h: usize, + cw: usize, + chroma444: bool, + /// Multiplier from input sample values to the 12-bit domain. + scale: f32, + alpha: bool, + bits_per_mb: u32, +} + +/// Load an 8×8 block (edge-replicated) into the 12-bit signed domain and transform it. +fn load_block(plane: &[u16], stride: usize, height: usize, x0: usize, y0: usize, scale: f32) -> [f32; 64] { + let mut b = [0f32; 64]; + for yy in 0..8 { + let y = (y0 + yy).min(height - 1); + let row = &plane[y * stride..(y + 1) * stride]; + for xx in 0..8 { + let x = (x0 + xx).min(stride - 1); + b[yy * 8 + xx] = row[x] as f32 * scale - 2048.0; + } + } + fdct8x8(&mut b); + b +} + +/// Rounding offsets for quantisation: nearest for DC, a slight dead zone for AC. +const AC_ROUND: f32 = 0.45; + +fn quantise(coeffs: &[[f32; 64]], qmat_inv: &[f32; 64], out: &mut Vec<[i32; 64]>) { + out.clear(); + for c in coeffs { + let mut l = [0i32; 64]; + let dc = c[0] * qmat_inv[0]; + l[0] = dc.round() as i32; + for i in 1..64 { + let v = c[i] * qmat_inv[i]; + let a = (v.abs() + AC_ROUND) as i32; + l[i] = if v < 0.0 { -a } else { a }; + } + out.push(l); + } +} + +fn encode_component(s: &mut S, levels: &[[i32; 64]], scan: &[u8; 64]) { + let n = levels.len(); + // DC + let first = levels[0][0]; + let zz = if first >= 0 { (first as u32) << 1 } else { ((-(first as i64)) as u32) * 2 - 1 }; + s.put_cw(FIRST_DC_CB, zz); + let mut prev = first; + let mut ctx = DC_CTX_INIT; + let mut sign = false; + for blk in &levels[1..] { + let dc = blk[0]; + let delta = dc - prev; + prev = dc; + let code = if delta == 0 { + sign = false; + 0 + } else { + let neg = delta < 0; + let mag = delta.unsigned_abs(); + let c = if neg != sign { 2 * mag - 1 } else { 2 * mag }; + sign = neg; + c + }; + s.put_cw(DC_CB[ctx.min(6) as usize], code); + ctx = code; + } + // AC, interleaved across blocks + let mut run = 0u32; + let mut run_ctx = RUN_CTX_INIT; + let mut lev_ctx = LEVEL_CTX_INIT; + for &nat in &scan[1..] { + let nat = nat as usize; + for blk in levels.iter().take(n) { + let l = blk[nat]; + if l == 0 { + run += 1; + } else { + s.put_cw(RUN_CB[run_ctx.min(15) as usize], run); + run_ctx = run; + let a = l.unsigned_abs(); + s.put_cw(LEVEL_CB[lev_ctx.min(9) as usize], a - 1); + lev_ctx = a; + s.put((l < 0) as u32, 1); + run = 0; + } + } + } +} + +fn component_bits(levels: &[[i32; 64]]) -> u64 { + let mut c = Counter::default(); + encode_component(&mut c, levels, &SCAN_PROGRESSIVE); + c.0.div_ceil(8) * 8 +} + +/// A slice's transformed blocks (Y, Cb, Cr) and its coded alpha. +struct SliceData { + comps: [Vec<[f32; 64]>; 3], + alpha: Option>, +} + +impl SliceData { + fn header_size(&self) -> usize { + if self.alpha.is_some() { 8 } else { 6 } + } + /// Largest DCT payload (bits) that keeps every 16-bit size field valid. + fn limit_bits(&self) -> u64 { + (65535 - self.header_size() - self.alpha.as_ref().map_or(0, Vec::len)) as u64 * 8 + } +} + +fn par_map(threads: bool, items: &[T], f: impl Fn(&T) -> R + Sync + Send) -> Vec { + #[cfg(feature = "threads")] + if threads { + use rayon::prelude::*; + return items.par_iter().map(f).collect(); + } + let _ = threads; + items.iter().map(f).collect() +} + +fn prepare_slice(src: &Source, mb_x: usize, mb_y: usize, mbs: usize) -> SliceData { + let f = src.frame; + let x0 = mb_x * 16; + let y0 = mb_y * 16; + let mut luma = Vec::with_capacity(4 * mbs); + for mb in 0..mbs { + for i in 0..4 { + luma.push(load_block(&f.y, src.w, src.h, x0 + mb * 16 + (i & 1) * 8, y0 + (i >> 1) * 8, src.scale)); + } + } + let mut chroma = [Vec::with_capacity(4 * mbs), Vec::with_capacity(4 * mbs)]; + for (k, plane) in [&f.cb, &f.cr].into_iter().enumerate() { + for mb in 0..mbs { + if src.chroma444 { + // 4:4:4 chroma block order within a macroblock: TL, BL, TR, BR. + for i in 0..4 { + chroma[k].push(load_block(plane, src.cw, src.h, x0 + mb * 16 + (i >> 1) * 8, y0 + (i & 1) * 8, src.scale)); + } + } else { + for i in 0..2 { + chroma[k].push(load_block(plane, src.cw, src.h, mb_x * 8 + mb * 8, y0 + i * 8, src.scale)); + } + } + } + } + let [cb, cr] = chroma; + let alpha = src.alpha.then(|| encode_alpha_slice(src, x0, y0, mbs)); + SliceData { comps: [luma, cb, cr], alpha } +} + +fn quantise_all(sd: &SliceData, q: u8, lv: &mut [Vec<[i32; 64]>; 3]) { + let inv = [1.0 / (4.0 * qscale(q) as f32); 64]; + for (c, l) in sd.comps.iter().zip(lv.iter_mut()) { + quantise(c, &inv, l); + } +} + +/// Coded DCT payload bits of a slice at quantiser `q` (components padded to bytes). +fn slice_bits(sd: &SliceData, q: u8) -> u64 { + let mut lv = [Vec::new(), Vec::new(), Vec::new()]; + quantise_all(sd, q, &mut lv); + lv.iter().map(|l| component_bits(l)).sum() +} + +/// Per-slice coded bits at the quantisers tried so far, so no quantiser is measured twice. +struct Probes<'a> { + slices: &'a [SliceData], + threads: bool, + tried: Vec<(u8, Vec, u64)>, +} + +impl Probes<'_> { + /// The frame's coded bits at `q` (the sum over slices). + fn total(&mut self, q: u8) -> u64 { + if let Some((_, _, t)) = self.tried.iter().find(|(tq, _, _)| *tq == q) { + return *t; + } + let v = par_map(self.threads, self.slices, |sd| slice_bits(sd, q)); + let t = v.iter().sum(); + self.tried.push((q, v, t)); + t + } + + /// The per-slice bits at `q`, measuring them if `q` was not tried yet. + fn take(&mut self, q: u8) -> Vec { + match self.tried.iter().position(|(tq, _, _)| *tq == q) { + Some(i) => self.tried.swap_remove(i).1, + None => par_map(self.threads, self.slices, |sd| slice_bits(sd, q)), + } + } +} + +/// The smallest quantiser in `Q_MIN..=Q_MAX` whose frame fits `budget` (`Q_MAX` when none does), +/// assuming bits fall as the quantiser grows. The search starts at `hint` (the previous frame's +/// quantiser) and widens its steps away from it until the answer is bracketed, then bisects: the +/// frames of a shot need the same quantiser or a neighbour, which takes two measurements instead of +/// the eight of a bisection over the whole range. +fn smallest_fitting(total: &mut impl FnMut(u8) -> u64, hint: u8, budget: u64) -> u8 { + let h = hint.clamp(Q_MIN, Q_MAX); + // `lo` does not fit, `hi` does: the answer is in `lo + 1..=hi`. + let (mut lo, mut hi) = if total(h) <= budget { + let (mut hi, mut step) = (h, 1u8); + loop { + if hi == Q_MIN { + return Q_MIN; + } + let q = hi.saturating_sub(step).max(Q_MIN); + if total(q) > budget { + break (q, hi); + } + hi = q; + step = step.saturating_mul(2); + } + } else { + let (mut lo, mut step) = (h, 1u8); + loop { + if lo == Q_MAX { + return Q_MAX; + } + let q = lo.saturating_add(step).min(Q_MAX); + if total(q) <= budget { + break (lo, q); + } + lo = q; + step = step.saturating_mul(2); + } + }; + while hi - lo > 1 { + let mid = lo + (hi - lo) / 2; + if total(mid) <= budget { + hi = mid; + } else { + lo = mid; + } + } + hi +} + +/// Frame-level rate control: the smallest uniform quantiser whose frame fits the budget, then +/// one step finer on as many slices as the remaining budget allows. Uniform quantisation +/// approximately minimises frame MSE for a given size. Returns the slices' quantisers and the +/// uniform one (the next frame's `hint`). +fn choose_quantisers(slices: &[SliceData], src: &Source, total_mbs: usize, threads: bool, hint: u8) -> (Vec, u8) { + let n = slices.len(); + let hsize = slices.first().map_or(6, SliceData::header_size); + let fixed = 8 * (8 + 20 + 8 + n * (2 + hsize)) as u64; + let budget = (src.bits_per_mb as u64 * total_mbs as u64).saturating_sub(fixed); + let mut probes = Probes { slices, threads, tried: Vec::new() }; + let qg = smallest_fitting(&mut |q| probes.total(q), hint, budget); + let mut qs = vec![qg; n]; + let mut cur = probes.take(qg); + let total = cur.iter().sum::(); + if qg > Q_MIN && total < budget { + // usually measured already: the search's last quantiser that did not fit + let finer = probes.take(qg - 1); + let mut left = budget - total; + for (((sd, f), c), q) in slices.iter().zip(&finer).zip(cur.iter_mut()).zip(qs.iter_mut()) { + let d = f.saturating_sub(*c); + if d <= left && *f <= sd.limit_bits() { + left -= d; + *q = qg - 1; + *c = *f; + } + } + } + // Keep every slice within its 16-bit size fields. + for ((sd, q), c) in slices.iter().zip(qs.iter_mut()).zip(cur.iter_mut()) { + while *c > sd.limit_bits() && *q < Q_MAX { + *q += 1; + *c = slice_bits(sd, *q); + } + } + (qs, qg) +} + +fn write_slice(sd: &SliceData, q: u8) -> Vec { + let mut lv = [Vec::new(), Vec::new(), Vec::new()]; + quantise_all(sd, q, &mut lv); + let comps: Vec> = lv + .iter() + .map(|l| { + let mut w = Writer::new(); + encode_component(&mut w, l, &SCAN_PROGRESSIVE); + w.finish() + }) + .collect(); + let hsize = sd.header_size(); + let alpha = sd.alpha.as_deref().unwrap_or(&[]); + let mut out = Vec::with_capacity(hsize + comps.iter().map(Vec::len).sum::() + alpha.len()); + out.push((hsize as u8) << 3); + out.push(q); + out.extend_from_slice(&(comps[0].len() as u16).to_be_bytes()); + out.extend_from_slice(&(comps[1].len() as u16).to_be_bytes()); + if sd.alpha.is_some() { + out.extend_from_slice(&(comps[2].len() as u16).to_be_bytes()); + } + for c in &comps { + out.extend_from_slice(c); + } + out.extend_from_slice(alpha); + out +} + +/// Encode the slice's alpha samples (16-bit coding) in raster order over its 16 lines. +fn encode_alpha_slice(src: &Source, x0: usize, y0: usize, mbs: usize) -> Vec { + let Some(a) = src.frame.alpha.as_ref() else { return Vec::new() }; + let d = src.frame.bit_depth as u32; + let to16 = |v: u16| -> u32 { + let v = v as u32 & ((1 << d) - 1); + if d >= 16 { v } else { ((v << (16 - d)) | (v >> (2 * d).saturating_sub(16).min(d))) & 0xffff } + }; + let sw = mbs * 16; + let mut vals = Vec::with_capacity(sw * 16); + for yy in 0..16 { + let y = (y0 + yy).min(src.h - 1); + for xx in 0..sw { + let x = (x0 + xx).min(src.w - 1); + vals.push(to16(a[y * src.w + x])); + } + } + let mut w = Writer::new(); + encode_alpha(&mut w, &vals, 16); + w.finish() +} + +/// Alpha plane coding: a value (difference from the previous value, short or full width), then +/// either a one bit (another value follows) or a zero bit and a run of repeats of the value. +pub(crate) fn encode_alpha(w: &mut Writer, vals: &[u32], bits: u32) { + let mask = (1u32 << bits) - 1; + let (short_bits, short_max) = if bits == 16 { (7, 64i32) } else { (4, 8i32) }; + let n = vals.len(); + let mut prev = mask; + let mut idx = 0; + while idx < n { + let v = vals[idx]; + let diff = v.wrapping_sub(prev) & mask; + // signed difference in (-2^(bits-1), 2^(bits-1)] + let sd = if diff > mask >> 1 { diff as i32 - (mask as i32 + 1) } else { diff as i32 }; + if sd != 0 && sd.abs() <= short_max { + w.put(0, 1); + let c = (2 * sd.unsigned_abs() - 2) | (sd < 0) as u32; + w.put(c, short_bits); + } else { + w.put(1, 1); + w.put(diff, bits); + } + prev = v; + idx += 1; + // RDD 36:2022 §5.3.3 requires a run after every alpha difference, + // including a singleton at the end of the slice. Do not replace that + // final `1` with byte-alignment zeros: native decoders reject it. + let mut run = 0; + while idx + run < n && vals[idx + run] == prev && run < 2047 { + run += 1; + } + if run == 0 { + w.put(1, 1); + continue; + } + w.put(0, 1); + if run < 16 { + w.put(run as u32, 4); + } else { + w.put(0, 4); + w.put(run as u32, 11); + } + idx += run; + } +} + +#[cfg(test)] +mod rate_tests { + use super::*; + + /// `take` gives a quantiser's measured per-slice bits whether or not `total` tried it: an + /// empty list would skip the slices' 16-bit size checks in `choose_quantisers`. + #[test] + fn take_measures_an_untried_quantiser() { + let block = |v: f32| std::array::from_fn::(|i| v * (64 - i) as f32); + let slices: Vec = + (1..=3).map(|k| SliceData { comps: [vec![block(40.0 * k as f32); 4], vec![block(10.0); 2], vec![block(5.0); 2]], alpha: None }).collect(); + let want = |q| slices.iter().map(|sd| slice_bits(sd, q)).collect::>(); + assert!(want(4).iter().all(|&b| b > 0)); + let mut probes = Probes { slices: &slices, threads: false, tried: Vec::new() }; + assert_eq!(probes.take(4), want(4), "untried"); + probes.total(6); + assert_eq!(probes.take(6), want(6), "tried"); + assert!(probes.tried.is_empty()); + } + + /// Frame bits falling with the quantiser (steeply at first, then flat), as real content does. + fn bits(q: u8) -> u64 { + 4_000_000 / (q as u64 + 3) + 1_000 + } + + #[test] + fn hinted_search_finds_the_smallest_fitting_quantiser() { + for budget in (0..1_200_000).step_by(997).chain([0, 1_000, 2_000, bits(Q_MIN), bits(Q_MAX), u64::MAX]) { + // the reference: a scan from the finest quantiser + let want = (Q_MIN..=Q_MAX).find(|&q| bits(q) <= budget).unwrap_or(Q_MAX); + for hint in [0, Q_MIN, 2, 7, 64, 128, 223, Q_MAX, 255] { + let mut calls = 0; + let got = smallest_fitting( + &mut |q| { + assert!((Q_MIN..=Q_MAX).contains(&q), "measured an invalid quantiser {q}"); + calls += 1; + bits(q) + }, + hint, + budget, + ); + assert_eq!(got, want, "budget {budget}, hint {hint}"); + assert!(calls <= 16, "budget {budget}, hint {hint}: {calls} measurements"); + } + } + } + + /// The common case (the previous frame's quantiser, or a neighbour) takes two measurements. + #[test] + fn a_good_hint_takes_two_measurements() { + for want in [Q_MIN + 1, 9, 40, 150, Q_MAX] { + let budget = bits(want); + for hint in [want - 1, want] { + let mut calls = 0; + let got = smallest_fitting( + &mut |q| { + calls += 1; + bits(q) + }, + hint, + budget, + ); + assert_eq!((got, calls), (want, 2), "hint {hint}"); + } + } + } + + /// Two frames, the second encoded after very different content: the encoder's memory of the + /// previous quantiser changes the work, not the stream. + #[test] + fn previous_frames_do_not_change_the_stream() { + let (w, h) = (256, 64); + let frame = |seed: u32, detail: u32| { + let mut f = Frame::new(w, h, ChromaFormat::Yuv422, 10, false); + let mut s = seed; + let cw = f.chroma_width() as usize; + for y in 0..h as usize { + for x in 0..w as usize { + s ^= s << 13; + s ^= s >> 17; + s ^= s << 5; + f.y[y * w as usize + x] = (400 + (x * 3 + y) as u32 % 200 + s % detail) as u16; + } + for x in 0..cw { + f.cb[y * cw + x] = (500 + (x + y) as u32 % 40) as u16; + f.cr[y * cw + x] = (520 + (x * 5) as u32 % 30) as u16; + } + } + f + }; + let (busy, plain) = (frame(7, 400), frame(9, 2)); + for profile in [Profile::Proxy, Profile::Standard, Profile::Hq] { + let alone = |f: &Frame| Encoder::new(profile, w, h).encode(f).unwrap(); + let mut enc = Encoder::new(profile, w, h); + enc.encode(&plain).unwrap(); + assert!(enc.encode(&busy).unwrap() == alone(&busy), "{profile:?}: busy after plain"); + assert!(enc.encode(&plain).unwrap() == alone(&plain), "{profile:?}: plain after busy"); + assert!(enc.encode(&plain).unwrap() == alone(&plain), "{profile:?}: plain after plain"); + } + } +} + +#[cfg(test)] +mod alpha_tests { + use super::*; + + #[test] + fn alpha_final_singleton_has_a_run_code() { + // RDD 36:2022 §5.3.3: every difference is followed by a run, including + // the final one. Tables 13/14 encode -1; Table 12 encodes run 1 as `1`. + // With 16-bit alpha the difference occupies exactly one byte, so the + // required final run must start another byte, followed by zero padding. + for (bits, expected) in [(16, vec![0x01, 0x80]), (8, vec![0x0c])] { + let mut w = Writer::new(); + encode_alpha(&mut w, &[(1 << bits) - 2], bits); + assert_eq!(w.finish(), expected, "{bits}-bit final singleton"); + } + } +} + +#[cfg(test)] +mod alpha_conformance_tests { + use super::*; + use craft_media::bitstream::BitReader; + + /// RDD 36:2022 §5.3.3 and Tables 12–14, using a bounds-checked reader. + /// Unlike the production decoder's zero-padded hot path, this checks every + /// final run and all alignment bits, so a truncated run cannot be accepted. + fn read_alpha(data: &[u8], bits: u32, count: usize) -> Vec { + let mut r = BitReader::new(data); + let mask = (1u32 << bits) - 1; + let mut prev = mask; + let mut out = Vec::new(); + while out.len() < count { + let diff = if r.read_bits(1).unwrap() == 1 { + r.read_bits(bits).unwrap() + } else { + let code = r.read_bits(if bits == 16 { 7 } else { 4 }).unwrap(); + let magnitude = (code >> 1) + 1; + if code & 1 == 0 { magnitude } else { magnitude.wrapping_neg() } + }; + prev = prev.wrapping_add(diff) & mask; + let run = if r.read_bits(1).unwrap() == 1 { + 1 + } else { + let short = r.read_bits(4).unwrap(); + if short == 0 { r.read_bits(11).unwrap() + 1 } else { short + 1 } + } as usize; + assert!(run <= count - out.len(), "alpha run exceeds the slice"); + out.extend(std::iter::repeat_n(prev, run)); + } + assert!(r.bits_left() < 8, "only byte-alignment padding may remain"); + while r.bits_left() > 0 { + assert_eq!(r.read_bits(1).unwrap(), 0, "alignment padding must be zero"); + } + out + } + + #[test] + fn alpha_runs_cover_boundaries_and_final_values() { + for bits in [8, 16] { + let mask = (1 << bits) - 1; + for run in [1, 2, 15, 16, 17, 2047, 2048, 2049, 4096] { + for tail in 0..=8 { + let mut values = vec![mask - 1; run]; + // Short negative/positive deltas, modulo transitions and + // escaped full-width deltas, ending on every bit alignment. + values.extend([0, mask, 1, 7, 8, 64, 65, mask / 2].into_iter().take(tail)); + let mut w = Writer::new(); + encode_alpha(&mut w, &values, bits); + assert_eq!(read_alpha(&w.finish(), bits, values.len()), values, "bits={bits}, run={run}, tail={tail}"); + } + } + } + } + + #[test] + fn encoded_alpha_slices_have_complete_final_runs() { + for profile in [Profile::P4444, Profile::P4444Xq] { + for log2_slice_mbs in 0..=3 { + let (width, height) = (128, 32); + let mut frame = Frame::new(width, height, ChromaFormat::Yuv444, 16, true); + frame.y.fill(32768); + frame.cb.fill(32768); + frame.cr.fill(32768); + let alpha = frame.alpha.as_mut().unwrap(); + for (i, a) in alpha.iter_mut().enumerate() { + *a = (i as u16).wrapping_mul(257); + } + let mut cfg = EncoderConfig::new(profile, width, height); + cfg.log2_slice_mbs = log2_slice_mbs; + let bytes = Encoder::with_config(cfg).encode(&frame).unwrap(); + let (_, start, _) = FrameHeader::parse(&bytes).unwrap(); + let picture = &bytes[start..]; + let header = PictureHeader::parse(picture).unwrap(); + let table = &picture[header.header_size..][..2 * header.num_slices]; + let mut offset = header.header_size + table.len(); + let sw = 16 << log2_slice_mbs; + for (i, size) in table.as_chunks::<2>().0.iter().enumerate() { + let size = u16::from_be_bytes([size[0], size[1]]) as usize; + let slice = &picture[offset..offset + size]; + offset += size; + let color_bytes: usize = slice[2..8].as_chunks::<2>().0.iter().map(|b| u16::from_be_bytes([b[0], b[1]]) as usize).sum(); + let payload = &slice[(slice[0] >> 3) as usize + color_bytes..]; + let actual = read_alpha(payload, 16, sw * 16); + let x0 = i % (width as usize / sw) * sw; + let y0 = i / (width as usize / sw) * 16; + let alpha = frame.alpha.as_ref().unwrap(); + let expected: Vec = (y0..y0 + 16).flat_map(|y| (x0..x0 + sw).map(move |x| alpha[y * width as usize + x] as u32)).collect(); + assert_eq!(actual, expected, "{profile:?}, slice {i}, log2={log2_slice_mbs}"); + } + assert_eq!(offset, picture.len()); + } + } + } +} diff --git a/crates/prores/src/header.rs b/crates/prores/src/header.rs new file mode 100644 index 0000000000..ec554f5d88 --- /dev/null +++ b/crates/prores/src/header.rs @@ -0,0 +1,234 @@ +//! Frame and picture headers (RDD 36 §5). + +use crate::{AlphaType, ChromaFormat, ColorInfo, Error, Interlace, Result}; +use craft_media::bitstream::BitReader; + +/// `'icpf'`: the frame identifier following the 32-bit frame size. +pub const FRAME_ID: [u8; 4] = *b"icpf"; + +/// A parsed ProRes frame header. +#[derive(Debug, Clone, PartialEq, Eq)] +pub struct FrameHeader { + /// Size of the frame header in bytes (from its own first field). + pub header_size: u16, + pub bitstream_version: u8, + /// Four-character encoder identifier (e.g. `apl0`, `Lavc`, `fmcr`). + pub encoder_id: [u8; 4], + pub width: u16, + pub height: u16, + pub chroma: ChromaFormat, + pub interlace: Interlace, + /// `aspect_ratio_information` (0 = unknown/square, 1 = 1:1, 2 = 4:3, 3 = 16:9). + pub aspect_ratio: u8, + /// `frame_rate_code` (0 = unknown). + pub frame_rate_code: u8, + pub color: ColorInfo, + pub alpha: AlphaType, + /// Luma quantisation matrix in raster order (all 4 when not transmitted). + pub qmat_luma: [u8; 64], + /// Chroma quantisation matrix in raster order (the luma matrix when not transmitted). + pub qmat_chroma: [u8; 64], + pub luma_matrix_present: bool, + pub chroma_matrix_present: bool, +} + +impl FrameHeader { + /// Parse the 8-byte frame prefix and frame header. Returns the header, the byte offset of the + /// first picture, and the frame size (bytes of `data` that belong to this frame). + pub fn parse(data: &[u8]) -> Result<(FrameHeader, usize, usize)> { + if data.len() < 8 + 20 { + return Err(Error::Truncated); + } + let frame_size = u32::from_be_bytes([data[0], data[1], data[2], data[3]]) as usize; + if data[4..8] != FRAME_ID { + return Err(Error::Invalid("missing 'icpf' frame identifier")); + } + if frame_size < 28 || frame_size > data.len() { + return Err(Error::Truncated); + } + let data = &data[..frame_size]; + let h = &data[8..]; + let mut r = BitReader::new(h); + let rd = |r: &mut BitReader, n: u32| r.read_bits(n).map_err(|_| Error::Truncated); + let header_size = rd(&mut r, 16)? as u16; + if (header_size as usize) < 20 || 8 + header_size as usize > data.len() { + return Err(Error::Invalid("bad frame header size")); + } + let _reserved = rd(&mut r, 8)?; + let bitstream_version = rd(&mut r, 8)? as u8; + let encoder_id = [h[4], h[5], h[6], h[7]]; + r.skip(32).map_err(|_| Error::Truncated)?; + let width = rd(&mut r, 16)? as u16; + let height = rd(&mut r, 16)? as u16; + let chroma = match rd(&mut r, 2)? { + 2 => ChromaFormat::Yuv422, + 3 => ChromaFormat::Yuv444, + _ => return Err(Error::Unsupported("chroma format")), + }; + let _ = rd(&mut r, 2)?; + let interlace = match rd(&mut r, 2)? { + 0 => Interlace::Progressive, + 1 => Interlace::TopFieldFirst, + 2 => Interlace::BottomFieldFirst, + _ => return Err(Error::Invalid("interlace mode")), + }; + let _ = rd(&mut r, 2)?; + let aspect_ratio = rd(&mut r, 4)? as u8; + let frame_rate_code = rd(&mut r, 4)? as u8; + let color = ColorInfo { primaries: rd(&mut r, 8)? as u8, transfer: rd(&mut r, 8)? as u8, matrix: rd(&mut r, 8)? as u8 }; + let _ = rd(&mut r, 4)?; + let alpha = match rd(&mut r, 4)? { + 0 => AlphaType::None, + 1 => AlphaType::Bits8, + 2 => AlphaType::Bits16, + _ => return Err(Error::Unsupported("alpha channel type")), + }; + let _ = rd(&mut r, 14)?; + let luma_matrix_present = rd(&mut r, 1)? == 1; + let chroma_matrix_present = rd(&mut r, 1)? == 1; + let mut pos = 20usize; + let mut qmat_luma = [4u8; 64]; + if luma_matrix_present { + let m = h.get(pos..pos + 64).ok_or(Error::Truncated)?; + qmat_luma.copy_from_slice(m); + pos += 64; + } + let mut qmat_chroma = qmat_luma; + if chroma_matrix_present { + let m = h.get(pos..pos + 64).ok_or(Error::Truncated)?; + qmat_chroma.copy_from_slice(m); + pos += 64; + } + if pos > header_size as usize { + return Err(Error::Invalid("quantisation matrices exceed frame header")); + } + if qmat_luma.contains(&0) || qmat_chroma.contains(&0) { + return Err(Error::Invalid("zero quantisation matrix entry")); + } + if width == 0 || height == 0 { + return Err(Error::Invalid("zero frame dimension")); + } + let hdr = FrameHeader { + header_size, + bitstream_version, + encoder_id, + width, + height, + chroma, + interlace, + aspect_ratio, + frame_rate_code, + color, + alpha, + qmat_luma, + qmat_chroma, + luma_matrix_present, + chroma_matrix_present, + }; + Ok((hdr, 8 + header_size as usize, frame_size)) + } + + /// Serialise the frame header (without the 8-byte size/identifier prefix). + pub fn write(&self) -> Vec { + let mut v = Vec::with_capacity(148); + let size = 20 + 64 * (self.luma_matrix_present as usize + self.chroma_matrix_present as usize); + v.extend_from_slice(&(size as u16).to_be_bytes()); + v.push(0); + v.push(self.bitstream_version); + v.extend_from_slice(&self.encoder_id); + v.extend_from_slice(&self.width.to_be_bytes()); + v.extend_from_slice(&self.height.to_be_bytes()); + let cf = match self.chroma { + ChromaFormat::Yuv422 => 2u8, + ChromaFormat::Yuv444 => 3, + }; + let il = match self.interlace { + Interlace::Progressive => 0u8, + Interlace::TopFieldFirst => 1, + Interlace::BottomFieldFirst => 2, + }; + v.push((cf << 6) | (il << 2)); + v.push((self.aspect_ratio << 4) | (self.frame_rate_code & 15)); + v.push(self.color.primaries); + v.push(self.color.transfer); + v.push(self.color.matrix); + v.push(match self.alpha { + AlphaType::None => 0, + AlphaType::Bits8 => 1, + AlphaType::Bits16 => 2, + }); + v.push(0); + v.push(((self.luma_matrix_present as u8) << 1) | self.chroma_matrix_present as u8); + if self.luma_matrix_present { + v.extend_from_slice(&self.qmat_luma); + } + if self.chroma_matrix_present { + v.extend_from_slice(&self.qmat_chroma); + } + v + } +} + +/// A parsed picture header (one per progressive frame, two per interlaced frame). +#[derive(Debug, Clone, Copy, PartialEq, Eq)] +pub struct PictureHeader { + pub header_size: usize, + /// Total picture size in bytes including this header, the slice table and the slices. + pub picture_size: usize, + pub num_slices: usize, + pub log2_slice_mbs: u8, +} + +impl PictureHeader { + pub fn parse(data: &[u8]) -> Result { + if data.len() < 8 { + return Err(Error::Truncated); + } + let header_size = (data[0] >> 3) as usize; + if header_size < 8 || header_size > data.len() { + return Err(Error::Invalid("bad picture header size")); + } + let picture_size = u32::from_be_bytes([data[1], data[2], data[3], data[4]]) as usize; + let num_slices = u16::from_be_bytes([data[5], data[6]]) as usize; + let log2_slice_mbs = (data[7] >> 4) & 3; + Ok(PictureHeader { header_size, picture_size, num_slices, log2_slice_mbs }) + } + + pub fn write(&self, out: &mut Vec) { + out.push(8 << 3); + out.extend_from_slice(&(self.picture_size as u32).to_be_bytes()); + out.extend_from_slice(&(self.num_slices as u16).to_be_bytes()); + out.push((self.log2_slice_mbs & 3) << 4); + } +} + +/// Slice widths (in macroblocks) of one macroblock row: as many `1 << log2` slices as fit, then +/// the remainder split into decreasing powers of two. +pub fn row_slice_widths(mb_width: usize, log2_slice_mbs: u8) -> Vec { + let size = 1usize << log2_slice_mbs; + let mut v = vec![size; mb_width / size]; + let mut rem = mb_width % size; + let mut s = size >> 1; + while rem > 0 { + if rem >= s { + v.push(s); + rem -= s; + } + s >>= 1; + } + v +} + +#[cfg(test)] +mod tests { + use super::*; + + #[test] + fn slice_widths() { + assert_eq!(row_slice_widths(120, 3), vec![8; 15]); + assert_eq!(row_slice_widths(45, 3), vec![8, 8, 8, 8, 8, 4, 1]); + assert_eq!(row_slice_widths(7, 3), vec![4, 2, 1]); + assert_eq!(row_slice_widths(2, 3), vec![2]); + assert_eq!(row_slice_widths(5, 0), vec![1; 5]); + } +} diff --git a/crates/prores/src/lib.rs b/crates/prores/src/lib.rs new file mode 100644 index 0000000000..fac0aa6cf2 --- /dev/null +++ b/crates/prores/src/lib.rs @@ -0,0 +1,214 @@ +//! Clean-room Apple ProRes codec (implemented from SMPTE RDD 36). +//! +//! - **Decoder** ([`decode_frame`], [`decode_frame_with`]): ProRes 422 Proxy/LT/Standard/HQ and +//! 4444/4444 XQ including the alpha channel; progressive and interlaced frames; slice-parallel +//! with the `threads` feature (rayon). Output is planar `u16` at 10 bits for 4:2:2 and 12 bits +//! for 4:4:4 by default (any depth 8..=16 on request). +//! - **Encoder** ([`Encoder`]): ProRes 422 (all four flavours) and 4444/4444 XQ (optionally with +//! alpha), progressive, with per-slice quantiser rate control targeting Apple's nominal rates. +//! +//! See the crate README for bitstream details, accuracy and limitations. + +#![deny(clippy::unwrap_used, clippy::expect_used, clippy::panic, clippy::unimplemented, clippy::todo, clippy::unreachable)] +#![allow(clippy::needless_range_loop)] + +mod bits; +mod dct; +mod decode; +mod encode; +pub mod header; +mod tables; + +pub use decode::{DecodeOptions, decode_frame, decode_frame_into, decode_frame_with, probe}; +pub use encode::{Encoder, EncoderConfig}; +pub use header::{FrameHeader, PictureHeader}; + +/// Codec errors. +#[derive(Debug, Clone, PartialEq, Eq, thiserror::Error)] +pub enum Error { + #[error("truncated ProRes data")] + Truncated, + #[error("invalid ProRes data: {0}")] + Invalid(&'static str), + #[error("unsupported ProRes feature: {0}")] + Unsupported(&'static str), + #[error("corrupt slice {slice} in picture {picture}")] + Slice { picture: usize, slice: usize }, + #[error("invalid encoder input: {0}")] + Input(String), +} + +pub type Result = std::result::Result; + +/// Chroma subsampling of a frame. +#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)] +pub enum ChromaFormat { + /// Chroma planes are half width, full height. + Yuv422, + /// Chroma planes are full size. + Yuv444, +} + +impl ChromaFormat { + /// Width of a chroma plane for a luma width. + pub fn chroma_width(self, width: u32) -> u32 { + match self { + ChromaFormat::Yuv422 => width.div_ceil(2), + ChromaFormat::Yuv444 => width, + } + } +} + +/// Frame scan structure. +#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash, Default)] +pub enum Interlace { + #[default] + Progressive, + /// Two fields; the first coded picture is the top field (even lines). + TopFieldFirst, + /// Two fields; the first coded picture is the bottom field (odd lines). + BottomFieldFirst, +} + +/// Alpha channel coding in the bitstream. +#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash, Default)] +pub enum AlphaType { + #[default] + None, + Bits8, + Bits16, +} + +/// Colour description codes from the frame header (ITU-T H.273 / ISO 23091-2 code points; +/// 0 = unspecified in ProRes, 2 = unspecified in H.273). +#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)] +pub struct ColorInfo { + pub primaries: u8, + pub transfer: u8, + pub matrix: u8, +} + +impl Default for ColorInfo { + /// BT.709 primaries, transfer and matrix. + fn default() -> Self { + ColorInfo { primaries: 1, transfer: 1, matrix: 1 } + } +} + +/// ProRes profiles (flavours). +#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)] +pub enum Profile { + /// `apco` + Proxy, + /// `apcs` + Lt, + /// `apcn` + Standard, + /// `apch` + Hq, + /// `ap4h` + P4444, + /// `ap4x` + P4444Xq, +} + +impl Profile { + pub const ALL: [Profile; 6] = [Profile::Proxy, Profile::Lt, Profile::Standard, Profile::Hq, Profile::P4444, Profile::P4444Xq]; + + /// The QuickTime sample-entry four-character code. + pub fn fourcc(self) -> [u8; 4] { + *match self { + Profile::Proxy => b"apco", + Profile::Lt => b"apcs", + Profile::Standard => b"apcn", + Profile::Hq => b"apch", + Profile::P4444 => b"ap4h", + Profile::P4444Xq => b"ap4x", + } + } + + pub fn from_fourcc(f: &[u8; 4]) -> Option { + Profile::ALL.into_iter().find(|p| &p.fourcc() == f) + } + + pub fn chroma(self) -> ChromaFormat { + match self { + Profile::P4444 | Profile::P4444Xq => ChromaFormat::Yuv444, + _ => ChromaFormat::Yuv422, + } + } + + /// Apple's published nominal data rate at 1920×1080, 29.97 fps, in Mbit/s. + pub fn nominal_mbps_1080p30(self) -> f64 { + match self { + Profile::Proxy => 45.0, + Profile::Lt => 102.0, + Profile::Standard => 147.0, + Profile::Hq => 220.0, + Profile::P4444 => 330.0, + Profile::P4444Xq => 500.0, + } + } + + /// Nominal coded bits per 16×16 macroblock (the nominal rate spread over the 8160 + /// macroblocks of a 1920×1080 frame at 30000/1001 fps). Frame targets scale with area. + pub fn nominal_bits_per_mb(self) -> u32 { + (self.nominal_mbps_1080p30() * 1e6 * 1001.0 / 30000.0 / 8160.0).round() as u32 + } +} + +/// A decoded (or to-be-encoded) picture: planar `u16` samples, one sample per element, +/// rows packed without padding. +#[derive(Debug, Clone, PartialEq, Eq)] +pub struct Frame { + pub width: u32, + pub height: u32, + pub chroma: ChromaFormat, + /// Significant bits per sample of every plane (including alpha). + pub bit_depth: u8, + /// `width × height` + pub y: Vec, + /// `chroma_width × height` + pub cb: Vec, + /// `chroma_width × height` + pub cr: Vec, + /// `width × height` (full range: 0 = transparent, max = opaque) + pub alpha: Option>, + pub interlace: Interlace, + pub color: ColorInfo, + /// `aspect_ratio_information` from the frame header. + pub aspect_ratio: u8, + /// `frame_rate_code` from the frame header. + pub frame_rate_code: u8, +} + +impl Frame { + /// A black (video-range) frame with the given geometry. + pub fn new(width: u32, height: u32, chroma: ChromaFormat, bit_depth: u8, with_alpha: bool) -> Frame { + let n = width as usize * height as usize; + let cn = chroma.chroma_width(width) as usize * height as usize; + let s = bit_depth.saturating_sub(8) as u32; + Frame { + width, + height, + chroma, + bit_depth, + y: vec![(16u32 << s) as u16; n], + cb: vec![(128u32 << s) as u16; cn], + cr: vec![(128u32 << s) as u16; cn], + alpha: with_alpha.then(|| vec![((1u32 << bit_depth) - 1) as u16; n]), + interlace: Interlace::Progressive, + color: ColorInfo::default(), + aspect_ratio: 0, + frame_rate_code: 0, + } + } + + pub fn chroma_width(&self) -> u32 { + self.chroma.chroma_width(self.width) + } + + pub fn is_interlaced(&self) -> bool { + self.interlace != Interlace::Progressive + } +} diff --git a/crates/prores/src/tables.rs b/crates/prores/src/tables.rs new file mode 100644 index 0000000000..975e9d3f57 --- /dev/null +++ b/crates/prores/src/tables.rs @@ -0,0 +1,133 @@ +//! Constant tables defined by the ProRes bitstream format (SMPTE RDD 36). + +/// A ProRes variable-length codebook: a Golomb-Rice code for small values that switches to an +/// Exp-Golomb code for large ones. +/// +/// A codeword starts with `q` zero bits and a one bit. +/// - If `q <= rice_q_max` it is a Rice codeword: `value = (q << rice) | next(rice bits)`. +/// - Otherwise it is an Exp-Golomb codeword of order `exp` whose prefix is `q - rice_q_max - 1` +/// zeros, offset by the number of values the Rice part covers: `(rice_q_max + 1) << rice`. +#[derive(Debug, Clone, Copy, PartialEq, Eq)] +pub(crate) struct Codebook { + pub rice: u8, + pub exp: u8, + pub rice_q_max: u8, +} + +const fn cb(rice: u8, exp: u8, rice_q_max: u8) -> Codebook { + Codebook { rice, exp, rice_q_max } +} + +/// Codebook of the first DC coefficient in a slice component (equivalent to Exp-Golomb order 5). +pub(crate) const FIRST_DC_CB: Codebook = cb(5, 6, 0); + +/// DC difference codebooks, selected by the previous DC codeword value (clamped to 6). +pub(crate) const DC_CB: [Codebook; 7] = [cb(0, 1, 0), cb(1, 2, 0), cb(1, 2, 0), cb(2, 3, 1), cb(2, 3, 1), cb(3, 4, 0), cb(3, 4, 0)]; + +/// AC run codebooks, selected by the previous run (clamped to 15). +pub(crate) const RUN_CB: [Codebook; 16] = [ + cb(0, 1, 2), + cb(0, 1, 2), + cb(0, 1, 1), + cb(0, 1, 1), + cb(0, 1, 0), + cb(1, 2, 1), + cb(1, 2, 1), + cb(1, 2, 1), + cb(1, 2, 1), + cb(1, 2, 0), + cb(1, 2, 0), + cb(1, 2, 0), + cb(1, 2, 0), + cb(1, 2, 0), + cb(1, 2, 0), + cb(2, 3, 0), +]; + +/// AC level codebooks (level magnitude minus one), selected by the previous level magnitude (clamped to 9). +pub(crate) const LEVEL_CB: [Codebook; 10] = + [cb(0, 1, 0), cb(0, 2, 2), cb(0, 1, 1), cb(0, 1, 2), cb(0, 1, 0), cb(1, 2, 0), cb(1, 2, 0), cb(1, 2, 0), cb(1, 2, 0), cb(2, 3, 0)]; + +/// Initial DC codebook context for the second DC of a component. +pub(crate) const DC_CTX_INIT: u32 = 5; +/// Initial AC run / level codebook contexts. +pub(crate) const RUN_CTX_INIT: u32 = 4; +pub(crate) const LEVEL_CTX_INIT: u32 = 2; + +/// Progressive-frame coefficient scan: scan position → raster index within the 8×8 block. +pub(crate) const SCAN_PROGRESSIVE: [u8; 64] = [ + 0, 1, 8, 9, 2, 3, 10, 11, // + 16, 17, 24, 25, 18, 19, 26, 27, // + 4, 5, 12, 20, 13, 6, 7, 14, // + 21, 28, 29, 22, 15, 23, 30, 31, // + 32, 33, 40, 48, 41, 34, 35, 42, // + 49, 56, 57, 50, 43, 36, 37, 44, // + 51, 58, 59, 52, 45, 38, 39, 46, // + 53, 60, 61, 54, 47, 55, 62, 63, +]; + +/// Interlaced-field coefficient scan: scan position → raster index within the 8×8 block. +pub(crate) const SCAN_INTERLACED: [u8; 64] = [ + 0, 8, 1, 9, 16, 24, 17, 25, // + 2, 10, 3, 11, 18, 26, 19, 27, // + 32, 40, 33, 34, 41, 48, 56, 49, // + 42, 35, 43, 50, 57, 58, 51, 59, // + 4, 12, 5, 6, 13, 20, 28, 21, // + 14, 7, 15, 22, 29, 36, 44, 37, // + 30, 23, 31, 38, 45, 52, 60, 53, // + 46, 39, 47, 54, 61, 62, 55, 63, +]; + +/// Quantisation scale for a slice `quantization_index` (1..=224). +#[inline] +pub(crate) fn qscale(qidx: u8) -> u32 { + let q = qidx.max(1) as u32; + if q <= 128 { q } else { 128 + ((q - 128) << 2) } +} + +/// Orthonormal 1-D DCT-II basis: `BASIS[u][x] = C(u)/2 · cos((2x+1)uπ/16)`, `C(0) = 1/√2`. +pub(crate) const BASIS: [[f32; 8]; 8] = [ + [0.353_553_39, 0.353_553_39, 0.353_553_39, 0.353_553_39, 0.353_553_39, 0.353_553_39, 0.353_553_39, 0.353_553_39], + [0.49039264, 0.415_734_8, 0.277_785_12, 0.097_545_16, -0.097_545_16, -0.277_785_12, -0.415_734_8, -0.49039264], + [0.461_939_77, 0.191_341_72, -0.191_341_72, -0.461_939_77, -0.461_939_77, -0.191_341_72, 0.191_341_72, 0.461_939_77], + [0.415_734_8, -0.097_545_16, -0.49039264, -0.277_785_12, 0.277_785_12, 0.49039264, 0.097_545_16, -0.415_734_8], + [0.353_553_39, -0.353_553_39, -0.353_553_39, 0.353_553_39, 0.353_553_39, -0.353_553_39, -0.353_553_39, 0.353_553_39], + [0.277_785_12, -0.49039264, 0.097_545_16, 0.415_734_8, -0.415_734_8, -0.097_545_16, 0.49039264, -0.277_785_12], + [0.191_341_72, -0.461_939_77, 0.461_939_77, -0.191_341_72, -0.191_341_72, 0.461_939_77, -0.461_939_77, 0.191_341_72], + [0.097_545_16, -0.277_785_12, 0.415_734_8, -0.49039264, 0.49039264, -0.415_734_8, 0.277_785_12, -0.097_545_16], +]; + +#[cfg(test)] +mod tests { + use super::*; + + #[test] + fn scans_are_permutations() { + for scan in [&SCAN_PROGRESSIVE, &SCAN_INTERLACED] { + let mut seen = [false; 64]; + for &i in scan.iter() { + assert!(!seen[i as usize]); + seen[i as usize] = true; + } + } + } + + #[test] + fn basis_matches_cosines() { + for u in 0..8 { + for x in 0..8 { + let c = if u == 0 { (0.125f64).sqrt() } else { 0.5 }; + let v = c * (((2 * x + 1) * u) as f64 * std::f64::consts::PI / 16.0).cos(); + assert!((v - BASIS[u][x] as f64).abs() < 1e-7); + } + } + } + + #[test] + fn qscale_mapping() { + assert_eq!(qscale(1), 1); + assert_eq!(qscale(128), 128); + assert_eq!(qscale(129), 132); + assert_eq!(qscale(224), 512); + } +} diff --git a/crates/prores/tests/fuzz_hostile.rs b/crates/prores/tests/fuzz_hostile.rs new file mode 100644 index 0000000000..524d25f089 --- /dev/null +++ b/crates/prores/tests/fuzz_hostile.rs @@ -0,0 +1,137 @@ +//! Hostile-input robustness for ProRes: truncation, header corruption, +//! and random bit mutations must never panic or crash the decoder. + +use std::panic::{AssertUnwindSafe, catch_unwind}; +use std::sync::atomic::{AtomicUsize, Ordering}; + +use craft_prores::{ChromaFormat, DecodeOptions, Encoder, EncoderConfig, Frame, Profile, decode_frame, decode_frame_with, probe}; + +struct Rng(u64); +impl Rng { + fn next(&mut self) -> u64 { + self.0 ^= self.0 << 13; + self.0 ^= self.0 >> 7; + self.0 ^= self.0 << 17; + self.0 + } + fn below(&mut self, n: usize) -> usize { + (self.next() % n.max(1) as u64) as usize + } +} + +static PANICS: AtomicUsize = AtomicUsize::new(0); + +fn count_panics() { + static ONCE: std::sync::Once = std::sync::Once::new(); + ONCE.call_once(|| { + std::panic::set_hook(Box::new(|info| { + PANICS.fetch_add(1, Ordering::SeqCst); + eprintln!("PANIC: {info}"); + })); + }); +} + +fn run_one(data: &[u8], label: &str) { + craft_testkit::set_progress(label); + count_panics(); + let before = PANICS.load(Ordering::SeqCst); + let res = catch_unwind(AssertUnwindSafe(|| { + let _ = probe(data); + let _ = decode_frame(data); + let opts = DecodeOptions { bit_depth: Some(10), threads: false }; + let _ = decode_frame_with(data, &opts); + })); + assert!(res.is_ok() && PANICS.load(Ordering::SeqCst) == before, "panic on input: {label}"); +} + +fn make_seeds() -> Vec<(&'static str, Vec)> { + let mut seeds = Vec::new(); + + // Seed 1: ProRes Standard 422 + let (w, h) = (64, 32); + let mut frame = Frame::new(w, h, ChromaFormat::Yuv422, 10, false); + frame.y.fill(512); + frame.cb.fill(512); + frame.cr.fill(512); + let mut enc = Encoder::new(Profile::Standard, w, h); + if let Ok(data) = enc.encode(&frame) { + seeds.push(("prores_std_422", data)); + } + + // Seed 2: ProRes 4444 with alpha + let (w, h) = (64, 32); + let mut frame = Frame::new(w, h, ChromaFormat::Yuv444, 12, true); + frame.y.fill(2048); + frame.cb.fill(2048); + frame.cr.fill(2048); + if let Some(alpha) = frame.alpha.as_mut() { + alpha.fill(4095); + } + let mut cfg = EncoderConfig::new(Profile::P4444, w, h); + cfg.encode_alpha = true; + let mut enc = Encoder::with_config(cfg); + if let Ok(data) = enc.encode(&frame) { + seeds.push(("prores_4444_alpha", data)); + } + + seeds +} + +const DEADLINE_SECS: u64 = 300; + +#[test] +fn hostile_prores_streams_never_panic() { + craft_testkit::with_deadline(DEADLINE_SECS, sweep); +} + +fn sweep() { + let seeds = make_seeds(); + assert!(!seeds.is_empty(), "failed to generate initial prores seeds"); + + for (name, seed) in &seeds { + // 1. Truncate at every offset + let len = seed.len(); + for n in (0..len).step_by((len / 150).max(1)) { + let truncated = &seed[..n]; + run_one(truncated, &format!("{name} truncate {n}")); + } + + // 2. Sweep header region (first 128 bytes) + let header_len = seed.len().min(128); + for p in 0..header_len { + for v in [0u8, 0xff, 0x80, 0x01, 0x7f, 0x55] { + let mut mutated = seed.clone(); + mutated[p] = v; + run_one(&mutated, &format!("{name} byte {p}={v:#x}")); + } + for b in 0..8 { + let mut mutated = seed.clone(); + mutated[p] ^= 1 << b; + run_one(&mutated, &format!("{name} flip {p}.{b}")); + } + } + + // 3. Random mutations + let mut rng = Rng(0xdeadbeef ^ name.len() as u64); + let iters = std::env::var("FILMCRAFT_FUZZ_ITERS").ok().and_then(|s| s.parse().ok()).unwrap_or(200); + + for i in 0..iters { + let mut mutated = seed.clone(); + let count = 1 + rng.below(8); + for _ in 0..count { + let p = rng.below(mutated.len()); + if rng.below(2) == 0 { + mutated[p] ^= 1 << rng.below(8); + } else { + mutated[p] = rng.next() as u8; + } + } + run_one(&mutated, &format!("{name} random {i}")); + } + } + + // 4. Also test empty and garbage inputs + run_one(&[], "empty input"); + run_one(&[0u8; 16], "all zeros"); + run_one(&[0xffu8; 64], "all ones"); +} diff --git a/crates/testkit/Cargo.toml b/crates/testkit/Cargo.toml new file mode 100644 index 0000000000..10e72bca69 --- /dev/null +++ b/crates/testkit/Cargo.toml @@ -0,0 +1,16 @@ +[package] +name = "craft-testkit" +description = "Shared test helpers and ffmpeg oracle discovery for Craft media codecs" +version.workspace = true +edition.workspace = true +license.workspace = true +repository.workspace = true +rust-version.workspace = true +publish = false + +[lints] +workspace = true + +[dependencies] +craft-media-testkit = { workspace = true } + diff --git a/crates/testkit/README.md b/crates/testkit/README.md new file mode 100644 index 0000000000..c9270b208f --- /dev/null +++ b/crates/testkit/README.md @@ -0,0 +1,3 @@ +# craft-testkit + +Shared test helpers and ffmpeg/ffprobe discovery utilities for Craft media codecs. diff --git a/crates/testkit/src/lib.rs b/crates/testkit/src/lib.rs new file mode 100644 index 0000000000..479257875c --- /dev/null +++ b/crates/testkit/src/lib.rs @@ -0,0 +1,11 @@ +//! Shared test helpers and ffmpeg oracle discovery for Craft media codecs. +//! Unifies with and re-exports [`craft_media_testkit`]. + +#![deny(clippy::unwrap_used, clippy::expect_used, clippy::panic, clippy::unimplemented, clippy::todo, clippy::unreachable)] + +pub use craft_media_testkit::*; + +/// Report a skipped test. +pub fn skip(what: &str, reason: &str) { + eprintln!("SKIPPED ({what}): {reason}"); +} diff --git a/crates/vp8/Cargo.toml b/crates/vp8/Cargo.toml new file mode 100644 index 0000000000..421afd26fb --- /dev/null +++ b/crates/vp8/Cargo.toml @@ -0,0 +1,18 @@ +[package] +name = "craft-vp8" +description = "Clean-room pure-Rust VP8 decoder (RFC 6386) for the Craft ecosystem" +version.workspace = true +edition.workspace = true +license.workspace = true +rust-version.workspace = true +repository.workspace = true + +[lints] +workspace = true + +[dependencies] +thiserror = { workspace = true } + +[dev-dependencies] +craft-testkit = { workspace = true } + diff --git a/crates/vp8/README.md b/crates/vp8/README.md new file mode 100644 index 0000000000..04e1b9904e --- /dev/null +++ b/crates/vp8/README.md @@ -0,0 +1,72 @@ +# craft-vp8 + +Clean-room, pure-Rust (no `unsafe`) VP8 decoder, implemented from RFC 6386, *VP8 Data Format and +Decoding Guide*, sections 1-19. It is an L0 crate: it depends only on `thiserror`, and builds for +`wasm32-unknown-unknown`. + +The constant tables (coefficient and mode probabilities and their update probabilities, the +subblock mode contexts, quantizer lookups, MV probabilities, interpolation filters...) were +extracted from the text of the RFC by [`tools/extract_tables.py`](tools/extract_tables.py) into +`src/spec_tables.rs`; the script cuts the text at section 20, so nothing is read from the +reference decoder attached to the RFC. No code was taken or ported from libvpx, FFmpeg or any +other decoder; ffmpeg (with libvpx) and vpxenc are used only as external fixture generators and +test oracles. + +## Status + +| Area | Status | +|---|---| +| Frame tag, key frame header, frame header (segmentation, loop filter type / level / sharpness, deltas, partitions, quantizer indices, reference updates and sign bias, probability updates) | done | +| Boolean decoder, tree decoding, probability persistence across frames and `refresh_entropy_probs` | done | +| Intra prediction: 16x16 and chroma modes, the ten 4x4 subblock modes, key frame subblock mode contexts, edge rules | done | +| Inter prediction: last / golden / altref with sign bias, NEAREST / NEAR / ZERO / NEW / SPLIT (all four partitionings), MV decoding and clamping, six-tap and bilinear filters, full-pixel chroma (bitstream versions 0-3) | done | +| Coefficient tokens (1-8 partitions), dequantization (frame and segment quantizers), inverse WHT and DCT | done | +| Normal and simple loop filters, per-macroblock levels from segments and reference / mode deltas | done | +| Golden / altref copies, hidden (altref) frames, frame size changes at key frames | done | +| Damaged input: errors (never panics), decoding resumes at the next key frame | done | +| Upscaling hints of the key frame header (9.1) | not applied (reported in `KeyframeInfo`) | + +Bit-exact with libvpx on the 62 libvpx VP8 test vectors checked (`vp80-00-comprehensive-001` to +`018` and the `vp80-01` to `vp80-06` intra, inter, segmentation, partition, sharpness and small +size vectors; `tests/it/vectors.rs` checks them when they are present) and on the generated fixtures +of `tests/it/conformance.rs`. + +## API + +```rust +use craft_vp8::Decoder; + +let mut dec = Decoder::new(); +for (pts, frame) in frames { // one IVF frame / WebM block / MP4 sample each + let skippable = dec.is_disposable(frame); // could this frame be left out (catching up)? + if let Some(pic) = dec.decode(frame, pts)? { + // pic.width / pic.height, pic.chroma_width / chroma_height, + // pic.y / pic.u / pic.v: tight 8-bit 4:2:0 planes, pic.pts, pic.key, + // pic.color_space (0: BT.601 YUV) + } + // None: a hidden (altref) frame +} +dec.reset(); // forget the reference frames (seek): decoding resumes at the next key frame +let stats = dec.stats(); // frames, key frames, hidden frames + +// Random access points +let key: bool = craft_vp8::is_keyframe(frame); +let info: Option = craft_vp8::keyframe_info(frame); +``` + +`craft-codecs` wires the decoder into Matroska / WebM (`V_VP8`, including the alpha layer of +transparent WebM in `BlockAdditional`) and MP4 (`vp08`). VP8 has no colour signalling: pictures are +BT.601, studio swing (RFC 6386 9.2), unless the container's `Colour` says otherwise. VP8 has no +superframes either: a hidden altref frame is a block of its own, and the WebM reader takes a +request for its time as a request for the shown frame that follows it. + +## Tests and tools + +- `cargo test -p craft-vp8`: unit tests, bit-exactness against libvpx on generated fixtures + (`target/fixtures/vp8-tests`, skipped without ffmpeg / libvpx), robustness (corrupted, + truncated, reordered and random input), and the libvpx test vectors when present in + `target/fixtures/vp8/conformance` (or `$VP8_TEST_VECTORS`). +- `cargo run --release -p craft-vp8 --example vp8dec in.ivf out.yuv`: decode to raw I420. +- `cargo run -p craft-vp8 --example fuzz -- files.ivf...`: decode + randomly damaged variants of IVF files (in a debug build, so overflow panics too) and report + any panic. diff --git a/crates/vp8/examples/fuzz.rs b/crates/vp8/examples/fuzz.rs new file mode 100644 index 0000000000..238cb47c04 --- /dev/null +++ b/crates/vp8/examples/fuzz.rs @@ -0,0 +1,100 @@ +//! Decode corrupted variants of IVF files and report panics (`fuzz iterations seed files...`). +//! Run in a debug build so arithmetic overflow panics too. + +fn frames(data: &[u8]) -> Vec> { + let mut out = Vec::new(); + let Some(h) = data.get(6..8) else { return out }; + let mut pos = usize::from(u16::from_le_bytes([h[0], h[1]])); + while pos + 12 <= data.len() { + let size = u32::from_le_bytes([data[pos], data[pos + 1], data[pos + 2], data[pos + 3]]) as usize; + pos += 12; + out.push(data[pos..(pos + size).min(data.len())].to_vec()); + pos += size; + } + out +} + +fn main() { + let args: Vec = std::env::args().collect(); + let iterations: usize = args[1].parse().unwrap_or(100); + let mut seed: u64 = args[2].parse().unwrap_or(1); + let mut rnd = move || { + seed ^= seed << 13; + seed ^= seed >> 7; + seed ^= seed << 17; + seed + }; + std::panic::set_hook(Box::new(|_| {})); + let mut panics = 0; + let mut decoded = 0u64; + for path in &args[3..] { + let original = frames(&std::fs::read(path).unwrap_or_default()); + for it in 0..iterations { + let mut fr = original.clone(); + let mode = rnd() % 6; + for f in fr.iter_mut() { + if f.is_empty() { + continue; + } + match mode { + 0 => { + // a few bit flips per frame + for _ in 0..1 + rnd() % 4 { + let i = (rnd() as usize) % f.len(); + f[i] ^= 1 << (rnd() % 8); + } + } + 1 => { + let n = (rnd() as usize) % (f.len() + 1); + f.truncate(n); + } + 2 => { + // random bytes in the header region (frame tag, sizes, first partition) + for _ in 0..1 + rnd() % 3 { + let i = (rnd() as usize) % f.len().min(24); + f[i] = rnd() as u8; + } + } + 3 => { + for b in f.iter_mut().skip(10) { + if rnd() % 50 == 0 { + *b = rnd() as u8; + } + } + } + // trailing garbage on some frames + 4 if rnd() % 3 == 0 => f.extend((0..rnd() % 64).map(|_| rnd() as u8)), + _ => {} + } + } + if mode == 5 { + // decode order shuffled: inter frames after the wrong references + for i in (1..fr.len()).rev() { + let j = (rnd() as usize) % (i + 1); + fr.swap(i, j); + } + } + let r = std::panic::catch_unwind(|| { + let mut dec = craft_vp8::Decoder::new(); + let mut n = 0u64; + for (k, f) in fr.iter().enumerate() { + let _ = dec.is_disposable(f); + if dec.decode(f, k as i64).is_ok() { + n += 1; + } + let _ = craft_vp8::keyframe_info(f); + } + n + }); + match r { + Ok(n) => decoded += n, + Err(e) => { + panics += 1; + let msg = e.downcast_ref::().cloned().or_else(|| e.downcast_ref::<&str>().map(ToString::to_string)).unwrap_or_default(); + eprintln!("PANIC {path} iteration {it} mode {mode}: {msg}"); + } + } + } + } + eprintln!("done: {panics} panics, {decoded} frames decoded"); +} diff --git a/crates/vp8/examples/vp8dec.rs b/crates/vp8/examples/vp8dec.rs new file mode 100644 index 0000000000..6e1bb27b29 --- /dev/null +++ b/crates/vp8/examples/vp8dec.rs @@ -0,0 +1,40 @@ +//! Decode an IVF file to raw I420 (`vp8dec in.ivf out.yuv`), for comparison with other decoders. +//! With `VP8DEC_SIZES` set, prints the size of each picture. + +use std::io::Write; + +fn main() -> Result<(), Box> { + let args: Vec = std::env::args().collect(); + let [_, input, output] = args.as_slice() else { + return Err("usage: vp8dec in.ivf out.yuv".into()); + }; + let data = std::fs::read(input)?; + let hdr_len = usize::from(u16::from_le_bytes([data[6], data[7]])); + let mut pos = hdr_len; + let mut out = std::io::BufWriter::new(std::fs::File::create(output)?); + let mut dec = craft_vp8::Decoder::new(); + let mut n = 0; + let t = std::time::Instant::now(); + while pos + 12 <= data.len() { + let size = u32::from_le_bytes(data[pos..pos + 4].try_into()?) as usize; + let pts = i64::from_le_bytes(data[pos + 4..pos + 12].try_into()?); + pos += 12; + let frame = &data[pos..(pos + size).min(data.len())]; + pos += size; + match dec.decode(frame, pts) { + Ok(Some(p)) => { + if std::env::var_os("VP8DEC_SIZES").is_some() { + println!("{} {}", p.width, p.height); + } + out.write_all(&p.y)?; + out.write_all(&p.u)?; + out.write_all(&p.v)?; + n += 1; + } + Ok(None) => {} + Err(e) => eprintln!("frame at pts {pts}: {e}"), + } + } + eprintln!("{n} pictures in {:.2}s; {:?}", t.elapsed().as_secs_f64(), dec.stats()); + Ok(()) +} diff --git a/crates/vp8/src/boolcoder.rs b/crates/vp8/src/boolcoder.rs new file mode 100644 index 0000000000..85c1997c57 --- /dev/null +++ b/crates/vp8/src/boolcoder.rs @@ -0,0 +1,218 @@ +//! Boolean entropy decoder (RFC 6386 section 7). +//! +//! The specification keeps two bytes of the input in `value` and shifts in one byte every eight +//! renormalisation steps. This implementation keeps the active bits at the top of a 64-bit window +//! whose lower bits hold the next bytes of the input, which is equivalent and refills only every +//! few bytes. Reading past the end of the data yields zero bits. + +pub struct BoolDecoder<'a> { + data: &'a [u8], + pos: usize, + /// The bits being decoded, most significant first: the top 8 bits correspond to the + /// specification's `value >> 8`, followed by `count` further bits of input. + value: u64, + count: i32, + range: u32, +} + +impl<'a> BoolDecoder<'a> { + pub fn new(data: &'a [u8]) -> Self { + let mut d = BoolDecoder { data, pos: 0, value: 0, count: -8, range: 255 }; + d.fill(); + d + } + + fn fill(&mut self) { + let mut shift = 48 - self.count; + while shift >= 0 { + match self.data.get(self.pos) { + Some(&b) => { + self.pos += 1; + self.value |= u64::from(b) << shift; + self.count += 8; + shift -= 8; + } + None => { + // past the end: zero bits for as long as anyone reads + self.count += 0x4000; + return; + } + } + } + } + + /// `read_bool` (7.3): a bool whose probability of being zero is `p / 256`. + #[inline(always)] + pub fn read_bool(&mut self, p: u8) -> bool { + let split = 1 + (((self.range - 1) * u32::from(p)) >> 8); + if self.count < 0 { + self.fill(); + } + let big_split = u64::from(split) << 56; + let bit = if self.value >= big_split { + self.range -= split; + self.value -= big_split; + true + } else { + self.range = split; + false + }; + // renormalise: shift until range >= 128 + let shift = self.range.leading_zeros().saturating_sub(24); + self.range <<= shift; + self.value <<= shift; + self.count -= shift as i32; + bit + } + + /// `read_literal` (7.3): an `n`-bit unsigned value, most significant bit first, each bit at + /// probability 1/2. + #[inline] + pub fn read_literal(&mut self, n: u32) -> u32 { + let mut v = 0; + for _ in 0..n { + v = (v << 1) | u32::from(self.read_bool(128)); + } + v + } + + /// An `L(1)` flag. + #[inline] + pub fn read_flag(&mut self) -> bool { + self.read_bool(128) + } + + /// A magnitude `L(n)` followed by a sign `L(1)` (1 = negative), as the frame header codes its + /// signed fields. + pub fn read_signed(&mut self, n: u32) -> i32 { + let v = self.read_literal(n) as i32; + if self.read_flag() { -v } else { v } + } + + /// An optional signed field: a flag, then [`Self::read_signed`] when it is set, else 0. + pub fn read_optional_signed(&mut self, n: u32) -> i32 { + if self.read_flag() { self.read_signed(n) } else { 0 } + } + + /// `treed_read` (8.1): a tree-coded value. `tree` uses the specification's layout: positive + /// entries index deeper nodes, non-positive entries `-v` are leaves of value `v`; `probs[i]` + /// is the probability of node `2 * i`. + #[inline] + pub fn read_tree(&mut self, tree: &[i8], probs: &[u8]) -> u8 { + self.read_tree_from(tree, probs, 0) + } + + /// [`Self::read_tree`] starting at node `start` instead of the root. + #[inline] + pub fn read_tree_from(&mut self, tree: &[i8], probs: &[u8], start: usize) -> u8 { + let mut i = start; + loop { + let p = probs.get(i >> 1).copied().unwrap_or(128); + let next = tree.get(i + usize::from(self.read_bool(p))).copied().unwrap_or(0); + if next <= 0 { + return (-next) as u8; + } + i = next as usize; + } + } +} + +#[cfg(test)] +mod tests { + use super::*; + + /// The encoder of section 7.3, to round-trip the decoder. + struct BoolEncoder { + out: Vec, + range: u32, + bottom: u32, + bit_count: i32, + } + + impl BoolEncoder { + fn new() -> Self { + BoolEncoder { out: Vec::new(), range: 255, bottom: 0, bit_count: 24 } + } + fn add_one(&mut self) { + let mut i = self.out.len(); + while i > 0 { + i -= 1; + if self.out[i] == 255 { + self.out[i] = 0; + } else { + self.out[i] += 1; + break; + } + } + } + fn write(&mut self, p: u8, bit: bool) { + let split = 1 + (((self.range - 1) * u32::from(p)) >> 8); + if bit { + self.bottom = self.bottom.wrapping_add(split); + self.range -= split; + } else { + self.range = split; + } + while self.range < 128 { + self.range <<= 1; + if self.bottom & (1 << 31) != 0 { + self.add_one(); + } + self.bottom <<= 1; + self.bit_count -= 1; + if self.bit_count == 0 { + self.out.push((self.bottom >> 24) as u8); + self.bottom &= (1 << 24) - 1; + self.bit_count = 8; + } + } + } + fn finish(mut self) -> Vec { + let mut c = self.bit_count; + let mut v = self.bottom; + if v & (1u32.wrapping_shl((32 - c) as u32)) != 0 { + self.add_one(); + } + v <<= c & 7; + c >>= 3; + while c > 0 { + c -= 1; + v <<= 8; + } + for _ in 0..4 { + self.out.push((v >> 24) as u8); + v <<= 8; + } + self.out + } + } + + #[test] + fn round_trips_bools_at_every_probability() { + let mut seed = 0x1234_5678u32; + let mut rnd = || { + seed ^= seed << 13; + seed ^= seed >> 17; + seed ^= seed << 5; + seed + }; + let bits: Vec<(u8, bool)> = (0..20_000).map(|_| (((rnd() % 255) + 1) as u8, rnd() % 3 == 0)).collect(); + let mut e = BoolEncoder::new(); + for &(p, b) in &bits { + e.write(p, b); + } + let data = e.finish(); + let mut d = BoolDecoder::new(&data); + for (i, &(p, b)) in bits.iter().enumerate() { + assert_eq!(d.read_bool(p), b, "bool {i}"); + } + } + + #[test] + fn reads_zeros_past_the_end() { + let mut d = BoolDecoder::new(&[]); + for _ in 0..1000 { + assert!(!d.read_bool(128)); + } + } +} diff --git a/crates/vp8/src/decoder.rs b/crates/vp8/src/decoder.rs new file mode 100644 index 0000000000..6581695b60 --- /dev/null +++ b/crates/vp8/src/decoder.rs @@ -0,0 +1,928 @@ +//! Frame decoding: macroblock prediction records (11, 16, 17), coefficients (13), reconstruction +//! (12, 14, 18), the loop filter (15) and reference frame management (9.7, 9.8). + +use std::sync::Arc; + +use crate::Picture; +use crate::boolcoder::BoolDecoder; +use crate::error::{Error, Result, ensure}; +use crate::header::{EntropyContext, FrameHeader, FrameTag, LfDeltas, Segmentation, read_header}; +use crate::loopfilter::{self, Kind, Params}; +use crate::predict::{self, BlockRect, DstPlane, RefPlane}; +use crate::spec_tables::{ + AC_QLOOKUP, BILINEAR_FILTERS, BMODE_PROBS, COEFF_BANDS, DC_QLOOKUP, KF_BMODE_PROBS, KF_UV_MODE_PROBS, KF_YMODE_PROBS, MODE_CONTEXTS, + MVPARTITION_PROBS, SIXTAP_FILTERS, SUB_MV_REF_PROBS, +}; +use crate::tables::{ + ABOVE_4X4, B_DC_PRED, B_HE_PRED, B_PRED, B_TM_PRED, B_VE_PRED, BMODE_TREE, CAT_BASE, COEFF_TREE, DC_PRED, DCT_0, DCT_4, DCT_CAT1, DCT_EOB, + H_PRED, KF_YMODE_TREE, LEFT_4X4, MV_NEAR, MV_NEAREST, MV_NEW, MV_REF_TREE, MV_SPLIT, MV_ZERO, MVP_BITS, MVP_IS_SHORT, MVP_SHORT, MVP_SIGN, + MVPARTITION_TREE, PCAT, SEGMENT_TREE, SMALL_MV_TREE, SPLIT_COUNTS, SPLIT_MAPS, SUB_MV_REF_TREE, TM_PRED, UV_MODE_TREE, V_PRED, YMODE_TREE, + ZERO_4X4, ZIGZAG, +}; +use crate::transform; + +/// Largest frame dimension in macroblocks (14-bit sizes). +const MAX_MBS: usize = 16384 / 16 + 1; +/// Maximum padded luma samples retained by one VP8 reference picture (about 8K UHD). +/// 2^26 luma samples: 8192x8192, above every VP8 frame a camera or encoder writes (8192x4320 is 35M). +const MAX_LUMA_SAMPLES: usize = 1 << 26; + +/// A reconstructed frame: planes sized to whole macroblocks. +#[derive(Clone)] +struct Frame { + y: Vec, + u: Vec, + v: Vec, + /// Luma width / height (multiples of 16); chroma is half. + w: usize, + h: usize, +} + +impl Frame { + fn new(w: usize, h: usize) -> Frame { + Frame { y: vec![0; w * h], u: vec![0; w * h / 4], v: vec![0; w * h / 4], w, h } + } +} + +/// A motion vector in quarter luma pixels. +#[derive(Clone, Copy, Default, PartialEq, Eq, Debug)] +struct Mv { + row: i32, + col: i32, +} + +impl Mv { + const ZERO: Mv = Mv { row: 0, col: 0 }; + fn is_zero(self) -> bool { + self == Mv::ZERO + } +} + +/// What later macroblocks (and the loop filter) need to know about a decoded macroblock. +#[derive(Clone, Copy, Default)] +struct MbInfo { + /// Intra mode (`DC_PRED`..`B_PRED`) or inter mode (`MV_NEAREST`..`MV_SPLIT`). + mode: u8, + /// 0 intra, 1 last, 2 golden, 3 altref. + ref_frame: u8, + mv: Mv, + /// Motion vector of each Y subblock (all `mv` unless split). + bmv: [Mv; 16], +} + +/// Statistics of the frames decoded so far. +#[derive(Clone, Copy, Debug, Default, PartialEq, Eq)] +pub struct DecodeStats { + pub frames: u64, + pub key_frames: u64, + pub hidden_frames: u64, +} + +/// Dequantization factors of one segment: [DC, AC] for Y, Y2 and chroma (14.1). +#[derive(Clone, Copy, Default)] +struct Dequant { + y: [i32; 2], + y2: [i32; 2], + uv: [i32; 2], +} + +impl Dequant { + fn new(q: i32, h: &FrameHeader) -> Dequant { + let dc = |d: i32| i32::from(DC_QLOOKUP[(q + d).clamp(0, 127) as usize]); + let ac = |d: i32| i32::from(AC_QLOOKUP[(q + d).clamp(0, 127) as usize]); + let qi = &h.quant; + Dequant { + y: [dc(qi.y_dc_delta), ac(0)], + y2: [dc(qi.y2_dc_delta) * 2, (ac(qi.y2_ac_delta) * 155 / 100).max(8)], + uv: [dc(qi.uv_dc_delta).min(132), ac(qi.uv_ac_delta)], + } + } +} + +/// VP8 decoder (RFC 6386). Feed it one compressed frame at a time ([`Decoder::decode`]). +pub struct Decoder { + ctx: EntropyContext, + seg: Segmentation, + lf: LfDeltas, + /// Segment of each macroblock (persists while the map is not updated). + seg_map: Vec, + width: usize, + height: usize, + mb_w: usize, + mb_h: usize, + /// Last, golden and altref frames. + refs: [Option>; 3], + /// `ref_frame_sign_bias`, indexed by reference frame (0 intra, 1 last, 2 golden, 3 altref). + sign_bias: [bool; 4], + stats: DecodeStats, +} + +impl Default for Decoder { + fn default() -> Self { + Self::new() + } +} + +impl Decoder { + pub fn new() -> Decoder { + Decoder { + ctx: EntropyContext::default(), + seg: Segmentation::default(), + lf: LfDeltas::default(), + seg_map: Vec::new(), + width: 0, + height: 0, + mb_w: 0, + mb_h: 0, + refs: [None, None, None], + sign_bias: [false; 4], + stats: DecodeStats::default(), + } + } + + /// Forget the reference frames (after a seek): decoding resumes at the next key frame. + pub fn reset(&mut self) { + self.refs = [None, None, None]; + } + + pub fn stats(&self) -> DecodeStats { + self.stats + } + + /// Whether leaving `frame` out, decoded next, would change no other picture: an inter frame + /// that refreshes and copies no reference buffer, does not update the segment map, and leaves + /// the probabilities, segment data and loop filter deltas as they are now (or keeps its + /// probability updates to itself). Only the frame header is read. + pub fn is_disposable(&self, frame: &[u8]) -> bool { + let Ok(tag) = FrameTag::parse(frame) else { return false }; + if tag.key_frame || self.refs[0].is_none() { + return false; + } + let Some(first) = frame.get(tag.header_len..tag.header_len.saturating_add(tag.first_part_size)) else { return false }; + let mut d = BoolDecoder::new(first); + let (mut seg, mut lf, mut ctx, mut saved) = (self.seg, self.lf, self.ctx.clone(), None); + let h = read_header(&mut d, false, &mut seg, &mut lf, &mut ctx, &mut saved); + let refs_untouched = !h.refresh_golden && !h.refresh_altref && !h.refresh_last && h.copy_to_golden == 0 && h.copy_to_altref == 0; + let probs_kept = !h.refresh_entropy || ctx == self.ctx; + let segments_kept = !seg.update_map && seg.absolute == self.seg.absolute && seg.quant == self.seg.quant && seg.lf_level == self.seg.lf_level; + let deltas_kept = lf.ref_deltas == self.lf.ref_deltas && lf.mode_deltas == self.lf.mode_deltas; + refs_untouched && probs_kept && segments_kept && deltas_kept + } + + /// Key frame setup: frame size (with its limits) and the default state of 9.3, 9.4, 9.7, + /// 13.4, 16.1 and 17.2. + fn begin_key_frame(&mut self, tag: &FrameTag) -> Result<()> { + ensure!(tag.width > 0 && tag.height > 0, "key frame of size {}x{}", tag.width, tag.height); + let (w, h) = (usize::from(tag.width), usize::from(tag.height)); + let (mb_w, mb_h) = (w.div_ceil(16), h.div_ceil(16)); + let samples = mb_w.checked_mul(16).and_then(|w| w.checked_mul(mb_h)).and_then(|h| h.checked_mul(16)); + ensure!(samples.is_some_and(|n| n <= MAX_LUMA_SAMPLES), "frame size {}x{} exceeds the decoder memory limit", tag.width, tag.height); + if w != self.width || h != self.height { + self.width = w; + self.height = h; + self.mb_w = mb_w; + self.mb_h = mb_h; + self.seg_map = vec![0; self.mb_w * self.mb_h]; + } + self.ctx = EntropyContext::default(); + self.seg.absolute = false; + self.seg.quant = [0; 4]; + self.seg.lf_level = [0; 4]; + self.lf.ref_deltas = [0; 4]; + self.lf.mode_deltas = [0; 4]; + self.sign_bias = [false; 4]; + Ok(()) + } + + /// Dequantization factors of each of the four segments (9.6, 14.1). + fn segment_dequant(&self, hdr: &FrameHeader) -> [Dequant; 4] { + std::array::from_fn(|s| { + let q = if self.seg.enabled { + let v = i32::from(self.seg.quant[s]); + if self.seg.absolute { v } else { hdr.quant.y_ac + v } + } else { + hdr.quant.y_ac + }; + Dequant::new(q.clamp(0, 127), hdr) + }) + } + + /// Reads the segment id (when the map is updated) and returns the segment of the macroblock. + fn read_segment(&mut self, bd: &mut BoolDecoder, seg_idx: usize) -> usize { + if self.seg.update_map { + let s = bd.read_tree(&SEGMENT_TREE, &self.seg.tree_probs); + if let Some(m) = self.seg_map.get_mut(seg_idx) { + *m = s; + } + } + if self.seg.enabled { self.seg_map.get(seg_idx).copied().unwrap_or(0) as usize & 3 } else { 0 } + } + + /// Reference updates (9.7, 9.8): the copies first (altref, then golden from the possibly + /// updated altref), then the refreshes with the new frame. + fn update_references(&mut self, hdr: &FrameHeader, frame: &Arc) { + let [last, golden, altref] = self.refs.clone(); + let mut new_golden = golden; + let mut new_altref = altref; + match hdr.copy_to_altref { + 1 => new_altref = last.clone(), + 2 => new_altref = new_golden.clone(), + _ => {} + } + match hdr.copy_to_golden { + 1 => new_golden = last.clone(), + 2 => new_golden = new_altref.clone(), + _ => {} + } + if hdr.refresh_golden { + new_golden = Some(frame.clone()); + } + if hdr.refresh_altref { + new_altref = Some(frame.clone()); + } + let new_last = if hdr.refresh_last { Some(frame.clone()) } else { last }; + self.refs = [new_last, new_golden, new_altref]; + } + + /// Prediction record of a macroblock of an inter frame (11.2, 16). + fn read_inter_frame_modes(&self, bd: &mut BoolDecoder, hdr: &FrameHeader, infos: &[MbInfo], pos: MbPos) -> MbModes { + let mut modes = MbModes::default(); + if bd.read_bool(hdr.prob_intra) { + modes.info.ref_frame = if bd.read_bool(hdr.prob_last) { 2 + u8::from(bd.read_bool(hdr.prob_golden)) } else { 1 }; + self.read_inter_modes(bd, infos, pos, &mut modes.info); + } else { + modes.info.mode = bd.read_tree(&YMODE_TREE, &self.ctx.ymode); + if modes.info.mode == B_PRED { + for m in &mut modes.bmodes { + *m = bd.read_tree(&BMODE_TREE, &BMODE_PROBS); + } + } + modes.uv_mode = bd.read_tree(&UV_MODE_TREE, &self.ctx.uv_mode); + } + modes + } + + /// Decode one compressed frame. Returns the picture when the frame is meant to be shown + /// (`show_frame`), `None` for a hidden frame (an altref). + pub fn decode(&mut self, data: &[u8], pts: i64) -> Result> { + let tag = FrameTag::parse(data)?; + ensure!(tag.first_part_size > 0, "empty first partition"); + if tag.key_frame { + self.begin_key_frame(&tag)?; + } else if self.refs[0].is_none() { + return Err(Error::MissingReference("inter frame before the first key frame".into())); + } + ensure!(self.mb_w > 0 && self.mb_w <= MAX_MBS && self.mb_h <= MAX_MBS, "no frame size"); + let first_start = tag.header_len; + let first_end = first_start.saturating_add(tag.first_part_size); + ensure!(first_end <= data.len(), "first partition ({} bytes) runs past the frame ({} bytes)", tag.first_part_size, data.len()); + let mut bd = BoolDecoder::new(&data[first_start..first_end]); + let mut saved = None; + let hdr = read_header(&mut bd, tag.key_frame, &mut self.seg, &mut self.lf, &mut self.ctx, &mut saved); + if !tag.key_frame { + self.sign_bias[2] = hdr.sign_bias_golden; + self.sign_bias[3] = hdr.sign_bias_altref; + } + + let n = hdr.partitions; + let mut token_decoders = split_token_partitions(data, first_end, n); + + let mut frame = Frame::new(self.mb_w * 16, self.mb_h * 16); + let dequant = self.segment_dequant(&hdr); + let recon = InterRecon { + refs: self.refs.clone(), + filters: if tag.version == 0 || tag.version > 3 { &SIXTAP_FILTERS } else { &BILINEAR_FILTERS }, + full_pixel: tag.version == 3, + }; + + let stride_info = self.mb_w + 1; + let mut infos = vec![MbInfo::default(); stride_info * (self.mb_h + 1)]; + let mut lf_info: Vec<(u8, bool)> = vec![(0, false); self.mb_w * self.mb_h]; + // contexts: B_PRED modes above / left (key frames), non-zero flags above / left + let mut above_bmodes = vec![[B_DC_PRED; 4]; self.mb_w]; + let mut above_nz = vec![[0u8; 9]; self.mb_w]; + + for mb_y in 0..self.mb_h { + let mut left_bmodes = [B_DC_PRED; 4]; + let mut left_nz = [0u8; 9]; + let part = mb_y % n; + for mb_x in 0..self.mb_w { + let pos = MbPos { idx: (mb_y + 1) * stride_info + mb_x + 1, stride: stride_info, mb_x, mb_y }; + // ---- prediction record (first partition) + let seg_idx = mb_y * self.mb_w + mb_x; + let segment = self.read_segment(&mut bd, seg_idx); + let coeff_skip = hdr.skip_prob.is_some_and(|p| bd.read_bool(p)); + let modes = if tag.key_frame { + read_key_frame_modes(&mut bd, &above_bmodes[mb_x], &left_bmodes) + } else { + self.read_inter_frame_modes(&mut bd, &hdr, &infos, pos) + }; + let info = modes.info; + above_bmodes[mb_x] = [modes.bmodes[12], modes.bmodes[13], modes.bmodes[14], modes.bmodes[15]]; + left_bmodes = [modes.bmodes[3], modes.bmodes[7], modes.bmodes[11], modes.bmodes[15]]; + if let Some(slot) = infos.get_mut(pos.idx) { + *slot = info; + } + + // ---- residual (token partition) + let has_y2 = info.mode != B_PRED && info.mode != MV_SPLIT; + let residual = if coeff_skip { + skip_residual(has_y2, &mut above_nz[mb_x], &mut left_nz); + MbResidual::default() + } else { + read_residual(&mut token_decoders[part], &self.ctx.coeff, &dequant[segment], has_y2, &mut above_nz[mb_x], &mut left_nz) + }; + + // ---- loop filter level (15.1, 9.3, 9.4) + let level = self.filter_level(&hdr, segment, &info); + let inner = !has_y2 || residual.has_coeffs; + if let Some(l) = lf_info.get_mut(seg_idx) { + *l = (level, inner); + } + + // ---- reconstruction + reconstruct_mb(&mut frame, (mb_x, mb_y), self.mb_w, &modes, &residual, &recon)?; + } + } + + if hdr.filter_level > 0 { + loop_filter(&mut frame, self.mb_w, self.mb_h, &lf_info, &hdr, tag.key_frame); + } + if let Some(s) = saved { + self.ctx = s; + } + + let frame = Arc::new(frame); + self.update_references(&hdr, &frame); + + self.stats.frames += 1; + self.stats.key_frames += u64::from(tag.key_frame); + if !tag.show_frame { + self.stats.hidden_frames += 1; + return Ok(None); + } + Ok(Some(self.picture(&frame, pts, tag.key_frame, hdr.color_space))) + } + + /// The visible part of a reconstructed frame. + fn picture(&self, f: &Frame, pts: i64, key: bool, color_space: u8) -> Picture { + let (w, h) = (self.width, self.height); + let (cw, ch) = (w.div_ceil(2), h.div_ceil(2)); + let crop = |plane: &[u8], stride: usize, w: usize, h: usize| { + let mut out = Vec::with_capacity(w * h); + for r in 0..h { + out.extend_from_slice(plane.get(r * stride..r * stride + w).unwrap_or(&[])); + } + out + }; + Picture { + width: w as u32, + height: h as u32, + chroma_width: cw as u32, + chroma_height: ch as u32, + y: crop(&f.y, f.w, w, h), + u: crop(&f.u, f.w / 2, cw, ch), + v: crop(&f.v, f.w / 2, cw, ch), + pts, + key, + color_space, + } + } + + /// Loop filter level of a macroblock (15.1 with the adjustments of 9.3 and 9.4). + fn filter_level(&self, hdr: &FrameHeader, segment: usize, info: &MbInfo) -> u8 { + let mut level = i32::from(hdr.filter_level); + if self.seg.enabled { + let v = i32::from(self.seg.lf_level[segment]); + level = if self.seg.absolute { v } else { level + v }.clamp(0, 63); + } + if self.lf.enabled { + level += i32::from(self.lf.ref_deltas[usize::from(info.ref_frame)]); + let mode_delta = match (info.ref_frame, info.mode) { + (0, B_PRED) => Some(0), + (0, _) => None, + (_, MV_ZERO) => Some(1), + (_, MV_SPLIT) => Some(3), + _ => Some(2), + }; + if let Some(m) = mode_delta { + level += i32::from(self.lf.mode_deltas[m]); + } + level = level.clamp(0, 63); + } + level as u8 + } + + /// Inter prediction record (16.2-16.4, 17) of the macroblock at `idx` in `infos`. + fn read_inter_modes(&self, bd: &mut BoolDecoder, infos: &[MbInfo], pos: MbPos, info: &mut MbInfo) { + let MbPos { idx, stride, mb_x, mb_y } = pos; + let get = |i: usize| infos.get(i).copied().unwrap_or_default(); + let above = get(idx - stride); + let left = get(idx - 1); + let above_left = get(idx - stride - 1); + // vp8_find_near_mvs (16.3) + let bias = |n: &MbInfo| { + let mut m = n.mv; + if self.sign_bias[usize::from(n.ref_frame)] != self.sign_bias[usize::from(info.ref_frame)] { + m.row = -m.row; + m.col = -m.col; + } + m + }; + let mut near = [Mv::ZERO; 4]; + let mut cnt = [0usize; 4]; + let mut k = 0; + if above.ref_frame != 0 { + if !above.mv.is_zero() { + k += 1; + near[k] = bias(&above); + } + cnt[k] += 2; + } + if left.ref_frame != 0 { + if !left.mv.is_zero() { + let m = bias(&left); + if m != near[k] { + k += 1; + near[k] = m; + } + cnt[k] += 2; + } else { + cnt[0] += 2; + } + } + if above_left.ref_frame != 0 { + if !above_left.mv.is_zero() { + let m = bias(&above_left); + if m != near[k] { + k += 1; + near[k] = m; + } + cnt[k] += 1; + } else { + cnt[0] += 1; + } + } + // three distinct vectors: merge the above-left one into "nearest" when equal + if cnt[3] > 0 && near[3] == near[1] { + cnt[1] += 1; + } + cnt[3] = usize::from(above.mode == MV_SPLIT) * 2 + usize::from(left.mode == MV_SPLIT) * 2 + usize::from(above_left.mode == MV_SPLIT); + if cnt[2] > cnt[1] { + cnt.swap(1, 2); + near.swap(1, 2); + } + if cnt[1] >= cnt[0] { + near[0] = near[1]; + } + let bounds = MvBounds::new(mb_x, mb_y, self.mb_w, self.mb_h); + let best = bounds.clamp(near[0]); + let probs: [u8; 4] = std::array::from_fn(|i| MODE_CONTEXTS[cnt[i].min(5) * 4 + i]); + info.mode = MV_NEAREST + bd.read_tree(&MV_REF_TREE, &probs); + info.mv = match info.mode { + MV_NEAREST => bounds.clamp(near[1]), + MV_NEAR => bounds.clamp(near[2]), + MV_ZERO => Mv::ZERO, + MV_NEW => { + let d = self.read_mv(bd); + Mv { row: best.row + d.row, col: best.col + d.col } + } + _ => { + self.read_split(bd, &left, &above, best, info); + return; + } + }; + info.bmv = [info.mv; 16]; + } + + /// SPLITMV partitioning and subblock vectors (16.4). + fn read_split(&self, bd: &mut BoolDecoder, left: &MbInfo, above: &MbInfo, best: Mv, info: &mut MbInfo) { + let part = usize::from(bd.read_tree(&MVPARTITION_TREE, &MVPARTITION_PROBS)) & 3; + let map = &SPLIT_MAPS[part]; + let mut part_mv = [Mv::ZERO; 16]; + for j in 0..SPLIT_COUNTS[part] { + // first subblock of partition j + let first = map.iter().position(|&p| usize::from(p) == j).unwrap_or(0); + let lmv = if first & 3 == 0 { left.bmv[first + 3] } else { part_mv[usize::from(map[first - 1])] }; + let amv = if first < 4 { above.bmv[first + 12] } else { part_mv[usize::from(map[first - 4])] }; + let context = if lmv == amv { + if amv.is_zero() { 4 } else { 3 } + } else if amv.is_zero() { + 2 + } else if lmv.is_zero() { + 1 + } else { + 0 + }; + let mv = match bd.read_tree(&SUB_MV_REF_TREE, &SUB_MV_REF_PROBS[context * 3..context * 3 + 3]) { + LEFT_4X4 => lmv, + ABOVE_4X4 => amv, + ZERO_4X4 => Mv::ZERO, + _ => { + let d = self.read_mv(bd); + Mv { row: best.row + d.row, col: best.col + d.col } + } + }; + part_mv[j] = mv; + } + for (b, &p) in map.iter().enumerate() { + info.bmv[b] = part_mv[usize::from(p)]; + } + info.mv = info.bmv[15]; + } + + /// `read_mv` (17.2): row, then column. + fn read_mv(&self, bd: &mut BoolDecoder) -> Mv { + let row = read_mv_component(bd, &self.ctx.mv[0]); + let col = read_mv_component(bd, &self.ctx.mv[1]); + Mv { row, col } + } +} + +/// Position of a macroblock: its index in the bordered `infos` array (row stride `stride`) and +/// its macroblock coordinates. +#[derive(Clone, Copy)] +struct MbPos { + idx: usize, + stride: usize, + mb_x: usize, + mb_y: usize, +} + +/// Prediction record of one macroblock. +struct MbModes { + info: MbInfo, + /// Subblock intra modes (`B_DC_PRED` unless the macroblock is `B_PRED`, or on key frames). + bmodes: [u8; 16], + uv_mode: u8, +} + +impl Default for MbModes { + fn default() -> Self { + MbModes { info: MbInfo::default(), bmodes: [B_DC_PRED; 16], uv_mode: DC_PRED } + } +} + +/// Dequantized coefficients of one macroblock: 16 Y, 4 U, 4 V and the Y2 block. +#[derive(Default)] +struct MbResidual { + coeffs: [[i16; 16]; 25], + nonzero: [bool; 25], + has_coeffs: bool, +} + +/// What inter prediction of a frame needs: the reference frames and the sub-pixel filter setup. +struct InterRecon { + refs: [Option>; 3], + filters: &'static [i32; 48], + full_pixel: bool, +} + +/// Token partitions (9.5): sizes of all but the last, then the data. +fn split_token_partitions(data: &[u8], sizes_start: usize, n: usize) -> Vec> { + let mut pos = sizes_start.saturating_add(3 * (n - 1)).min(data.len()); + let mut parts: Vec = Vec::with_capacity(n); + for i in 0..n { + let size = if i + 1 < n { + let s = sizes_start + 3 * i; + match data.get(s..s + 3) { + Some(b) => usize::from(b[0]) | usize::from(b[1]) << 8 | usize::from(b[2]) << 16, + None => 0, + } + } else { + data.len() - pos + }; + let end = pos.saturating_add(size).min(data.len()); + parts.push(BoolDecoder::new(&data[pos..end])); + pos = end; + } + parts +} + +/// Prediction record of a key frame macroblock (11.2, 11.3). +fn read_key_frame_modes(bd: &mut BoolDecoder, above_bmodes: &[u8; 4], left_bmodes: &[u8; 4]) -> MbModes { + let mut modes = MbModes::default(); + modes.info.mode = bd.read_tree(&KF_YMODE_TREE, &KF_YMODE_PROBS); + if modes.info.mode == B_PRED { + for b in 0..16 { + let a = if b < 4 { above_bmodes[b] } else { modes.bmodes[b - 4] }; + let l = if b & 3 == 0 { left_bmodes[b >> 2] } else { modes.bmodes[b - 1] }; + let o = (usize::from(a) * 10 + usize::from(l)) * 9; + modes.bmodes[b] = bd.read_tree(&BMODE_TREE, &KF_BMODE_PROBS[o..o + 9]); + } + } else { + modes.bmodes = [implied_bmode(modes.info.mode); 16]; + } + modes.uv_mode = bd.read_tree(&UV_MODE_TREE, &KF_UV_MODE_PROBS); + modes +} + +/// Non-zero context update for a macroblock without coefficients: everything is reset, except +/// the Y2 flags when the macroblock has no Y2 block. +fn skip_residual(has_y2: bool, above_nz: &mut [u8; 9], left_nz: &mut [u8; 9]) { + let keep_y2 = if has_y2 { 0 } else { left_nz[8] }; + let keep_y2_above = if has_y2 { 0 } else { above_nz[8] }; + *left_nz = [0; 9]; + *above_nz = [0; 9]; + left_nz[8] = keep_y2; + above_nz[8] = keep_y2_above; +} + +/// Tokens of all blocks of one macroblock (13), updating the non-zero contexts. +fn read_residual( + td: &mut BoolDecoder, + coeff_probs: &[[[[u8; 11]; 3]; 8]; 4], + dq: &Dequant, + has_y2: bool, + an: &mut [u8; 9], + left_nz: &mut [u8; 9], +) -> MbResidual { + let mut r = MbResidual::default(); + let first = if has_y2 { + let ctx = usize::from(an[8] + left_nz[8]); + let eob = read_coefficients(td, &coeff_probs[1], ctx, 0, dq.y2, &mut r.coeffs[24]); + let nz = u8::from(eob > 0); + an[8] = nz; + left_nz[8] = nz; + r.nonzero[24] = eob > 0; + r.has_coeffs |= eob > 0; + 1 + } else { + 0 + }; + let y_probs = if has_y2 { &coeff_probs[0] } else { &coeff_probs[3] }; + for b in 0..16 { + let (bx, by) = (b & 3, b >> 2); + let ctx = usize::from(an[bx] + left_nz[by]); + let eob = read_coefficients(td, y_probs, ctx, first, dq.y, &mut r.coeffs[b]); + let nz = eob > first; + an[bx] = u8::from(nz); + left_nz[by] = u8::from(nz); + r.nonzero[b] = nz; + r.has_coeffs |= nz; + } + for b in 16..24 { + let (off, k) = if b < 20 { (4, b - 16) } else { (6, b - 20) }; + let (bx, by) = (k & 1, k >> 1); + let ctx = usize::from(an[off + bx] + left_nz[off + by]); + let eob = read_coefficients(td, &coeff_probs[2], ctx, 0, dq.uv, &mut r.coeffs[b]); + let nz = eob > 0; + an[off + bx] = u8::from(nz); + left_nz[off + by] = u8::from(nz); + r.nonzero[b] = nz; + r.has_coeffs |= nz; + } + if has_y2 && r.nonzero[24] { + let dc = transform::inverse_wht(&r.coeffs[24]); + for b in 0..16 { + r.coeffs[b][0] = dc[b]; + r.nonzero[b] |= dc[b] != 0; + } + } + r +} + +/// Prediction plus residue of one macroblock into `frame`. +fn reconstruct_mb(frame: &mut Frame, (mb_x, mb_y): (usize, usize), mb_w: usize, modes: &MbModes, res: &MbResidual, recon: &InterRecon) -> Result<()> { + let info = &modes.info; + let (px, py) = (mb_x * 16, mb_y * 16); + let ys = frame.w; + let cs = frame.w / 2; + if info.ref_frame == 0 { + if info.mode == B_PRED { + for b in 0..16 { + predict::predict_subblock(&mut frame.y, ys, px, py, b, modes.bmodes[b], mb_x + 1 == mb_w); + if res.nonzero[b] { + let pos = (py + 4 * (b >> 2)) * ys + px + 4 * (b & 3); + transform::add_residue(&mut frame.y, ys, pos, &transform::inverse_dct(&res.coeffs[b])); + } + } + } else { + predict::predict_block(&mut frame.y, ys, px, py, 16, info.mode); + add_luma_residue(&mut frame.y, ys, px, py, &res.coeffs, &res.nonzero); + } + predict::predict_block(&mut frame.u, cs, px / 2, py / 2, 8, modes.uv_mode); + predict::predict_block(&mut frame.v, cs, px / 2, py / 2, 8, modes.uv_mode); + } else { + let Some(r) = recon.refs.get(usize::from(info.ref_frame) - 1).and_then(Option::as_deref) else { + return Err(Error::MissingReference(format!("reference frame {}", info.ref_frame))); + }; + predict_inter_mb(r, frame, mb_x, mb_y, info, recon.filters, recon.full_pixel); + add_luma_residue(&mut frame.y, ys, px, py, &res.coeffs, &res.nonzero); + } + for b in 16..24 { + if res.nonzero[b] { + let k = (b - 16) & 3; + let pos = (py / 2 + 4 * (k >> 1)) * cs + px / 2 + 4 * (k & 1); + let plane = if b < 20 { &mut frame.u } else { &mut frame.v }; + transform::add_residue(plane, cs, pos, &transform::inverse_dct(&res.coeffs[b])); + } + } + Ok(()) +} + +/// `read_mvcomponent` (17.1), in quarter pixels. +fn read_mv_component(bd: &mut BoolDecoder, p: &[u8; 19]) -> i32 { + let a = if bd.read_bool(p[MVP_IS_SHORT]) { + let mut a = 0i32; + for i in 0..3 { + a += i32::from(bd.read_bool(p[MVP_BITS + i])) << i; + } + for i in (4..10).rev() { + a += i32::from(bd.read_bool(p[MVP_BITS + i])) << i; + } + if a & 0xfff0 == 0 || bd.read_bool(p[MVP_BITS + 3]) { + a += 8; + } + a + } else { + i32::from(bd.read_tree(&SMALL_MV_TREE, &p[MVP_SHORT..MVP_SHORT + 7])) + }; + if a != 0 && bd.read_bool(p[MVP_SIGN]) { -a } else { a } +} + +/// The limits `vp8_clamp_mv` (16.3) keeps "nearest", "near" and "best" within: 16 pixels +/// beyond the frame's macroblocks, in quarter pixels. +struct MvBounds { + min_col: i32, + max_col: i32, + min_row: i32, + max_row: i32, +} + +impl MvBounds { + fn new(mb_x: usize, mb_y: usize, mb_w: usize, mb_h: usize) -> MvBounds { + const MARGIN: i32 = 16 << 2; + MvBounds { + min_col: -((mb_x as i32 * 16) << 2) - MARGIN, + max_col: (((mb_w - 1 - mb_x) as i32 * 16) << 2) + MARGIN, + min_row: -((mb_y as i32 * 16) << 2) - MARGIN, + max_row: (((mb_h - 1 - mb_y) as i32 * 16) << 2) + MARGIN, + } + } + fn clamp(&self, m: Mv) -> Mv { + Mv { row: m.row.clamp(self.min_row, self.max_row), col: m.col.clamp(self.min_col, self.max_col) } + } +} + +/// Subblock mode a 16x16 luma mode stands for in B_PRED contexts (11.3). +fn implied_bmode(mode: u8) -> u8 { + match mode { + V_PRED => B_VE_PRED, + H_PRED => B_HE_PRED, + TM_PRED => B_TM_PRED, + _ => B_DC_PRED, + } +} + +/// Decode the tokens of one block (13), storing dequantized coefficients in raster order. +/// Returns the position after the last token (where the end of block was read, or 16). +fn read_coefficients(bd: &mut BoolDecoder, probs: &[[[u8; 11]; 3]; 8], ctx: usize, first: usize, dq: [i32; 2], out: &mut [i16; 16]) -> usize { + let mut ctx = ctx.min(2); + let mut prev_zero = false; + let mut i = first; + while i < 16 { + let p = &probs[usize::from(COEFF_BANDS[i])][ctx]; + let token = if prev_zero { bd.read_tree_from(&COEFF_TREE, p, 2) } else { bd.read_tree(&COEFF_TREE, p) }; + if token == DCT_EOB { + break; + } + let abs = match token { + DCT_0..=DCT_4 => i32::from(token), + _ => { + let cat = usize::from(token - DCT_CAT1).min(5); + let mut extra = 0i32; + for &pb in PCAT[cat] { + extra = extra + extra + i32::from(bd.read_bool(pb)); + } + CAT_BASE[cat] + extra + } + }; + let v = if abs != 0 && bd.read_bool(128) { -abs } else { abs }; + ctx = match abs { + 0 => 0, + 1 => 1, + _ => 2, + }; + prev_zero = token == DCT_0; + out[ZIGZAG[i]] = (v * dq[usize::from(i > 0)]) as i16; + i += 1; + } + i +} + +/// Add the residue of the 16 Y subblocks of the macroblock at `(px, py)`. +fn add_luma_residue(plane: &mut [u8], stride: usize, px: usize, py: usize, coeffs: &[[i16; 16]; 25], nonzero: &[bool; 25]) { + for b in 0..16 { + if nonzero[b] { + let pos = (py + 4 * (b >> 2)) * stride + px + 4 * (b & 3); + transform::add_residue(plane, stride, pos, &transform::inverse_dct(&coeffs[b])); + } + } +} + +/// Inter prediction of a macroblock (18) into `frame`. +fn predict_inter_mb(r: &Frame, frame: &mut Frame, mb_x: usize, mb_y: usize, info: &MbInfo, filters: &[i32; 48], full_pixel: bool) { + let (px, py) = (mb_x * 16, mb_y * 16); + let ys = frame.w; + let cs = frame.w / 2; + let ry = RefPlane { data: &r.y, stride: r.w, width: r.w, height: r.h }; + let ru = RefPlane { data: &r.u, stride: r.w / 2, width: r.w / 2, height: r.h / 2 }; + let rv = RefPlane { data: &r.v, stride: r.w / 2, width: r.w / 2, height: r.h / 2 }; + let fp = |v: i32| if full_pixel { v & !7 } else { v }; + if info.mode != MV_SPLIT { + let m = info.mv; + predict::predict_inter( + &ry, + DstPlane { data: &mut frame.y, stride: ys }, + BlockRect { x: px, y: py, w: 16, h: 16 }, + (m.col * 2, m.row * 2), + filters, + ); + // chroma: the luma vector in quarter pixels is the chroma vector in eighth pixels + let (cx, cy) = (fp(m.col), fp(m.row)); + predict::predict_inter(&ru, DstPlane { data: &mut frame.u, stride: cs }, BlockRect { x: px / 2, y: py / 2, w: 8, h: 8 }, (cx, cy), filters); + predict::predict_inter(&rv, DstPlane { data: &mut frame.v, stride: cs }, BlockRect { x: px / 2, y: py / 2, w: 8, h: 8 }, (cx, cy), filters); + return; + } + for b in 0..16 { + let m = info.bmv[b]; + let rect = BlockRect { x: px + 4 * (b & 3), y: py + 4 * (b >> 2), w: 4, h: 4 }; + predict::predict_inter(&ry, DstPlane { data: &mut frame.y, stride: ys }, rect, (m.col * 2, m.row * 2), filters); + } + for i in 0..2 { + for j in 0..2 { + let k = 8 * i + 2 * j; + let sum = |f: fn(Mv) -> i32| [k, k + 1, k + 4, k + 5].iter().map(|&b| f(info.bmv[b])).sum::(); + // average of the four luma vectors, in eighth chroma pixels (18.1) + let avg = |s: i32| (s + 2 + if s < 0 { -1 } else { 0 }) >> 2; + let cx = fp(avg(sum(|m| m.col))); + let cy = fp(avg(sum(|m| m.row))); + let (bx, by) = (px / 2 + 4 * j, py / 2 + 4 * i); + let rect = BlockRect { x: bx, y: by, w: 4, h: 4 }; + predict::predict_inter(&ru, DstPlane { data: &mut frame.u, stride: cs }, rect, (cx, cy), filters); + predict::predict_inter(&rv, DstPlane { data: &mut frame.v, stride: cs }, rect, (cx, cy), filters); + } + } +} + +/// Loop filter of the whole frame (15.1), macroblocks in raster order. +fn loop_filter(frame: &mut Frame, mb_w: usize, mb_h: usize, lf_info: &[(u8, bool)], hdr: &FrameHeader, key_frame: bool) { + let ys = frame.w; + let cs = frame.w / 2; + let simple = hdr.filter_type == 1; + for mb_y in 0..mb_h { + for mb_x in 0..mb_w { + let Some(&(level, inner)) = lf_info.get(mb_y * mb_w + mb_x) else { continue }; + if level == 0 { + continue; + } + let prm = Params::new(level, hdr.sharpness, key_frame); + let y0 = mb_y * 16 * ys + mb_x * 16; + let c0 = mb_y * 8 * cs + mb_x * 8; + if simple { + if mb_x > 0 { + loopfilter::filter_edge::<16>(&mut frame.y, ys, y0, true, Kind::Simple, &prm); + } + if inner { + for x in [4, 8, 12] { + loopfilter::filter_edge::<16>(&mut frame.y, ys, y0 + x, true, Kind::SimpleInner, &prm); + } + } + if mb_y > 0 { + loopfilter::filter_edge::<16>(&mut frame.y, ys, y0, false, Kind::Simple, &prm); + } + if inner { + for y in [4, 8, 12] { + loopfilter::filter_edge::<16>(&mut frame.y, ys, y0 + y * ys, false, Kind::SimpleInner, &prm); + } + } + continue; + } + if mb_x > 0 { + loopfilter::filter_edge::<16>(&mut frame.y, ys, y0, true, Kind::NormalMb, &prm); + loopfilter::filter_edge::<8>(&mut frame.u, cs, c0, true, Kind::NormalMb, &prm); + loopfilter::filter_edge::<8>(&mut frame.v, cs, c0, true, Kind::NormalMb, &prm); + } + if inner { + for x in [4, 8, 12] { + loopfilter::filter_edge::<16>(&mut frame.y, ys, y0 + x, true, Kind::NormalSub, &prm); + } + loopfilter::filter_edge::<8>(&mut frame.u, cs, c0 + 4, true, Kind::NormalSub, &prm); + loopfilter::filter_edge::<8>(&mut frame.v, cs, c0 + 4, true, Kind::NormalSub, &prm); + } + if mb_y > 0 { + loopfilter::filter_edge::<16>(&mut frame.y, ys, y0, false, Kind::NormalMb, &prm); + loopfilter::filter_edge::<8>(&mut frame.u, cs, c0, false, Kind::NormalMb, &prm); + loopfilter::filter_edge::<8>(&mut frame.v, cs, c0, false, Kind::NormalMb, &prm); + } + if inner { + for y in [4, 8, 12] { + loopfilter::filter_edge::<16>(&mut frame.y, ys, y0 + y * ys, false, Kind::NormalSub, &prm); + } + loopfilter::filter_edge::<8>(&mut frame.u, cs, c0 + 4 * cs, false, Kind::NormalSub, &prm); + loopfilter::filter_edge::<8>(&mut frame.v, cs, c0 + 4 * cs, false, Kind::NormalSub, &prm); + } + } + } +} diff --git a/crates/vp8/src/error.rs b/crates/vp8/src/error.rs new file mode 100644 index 0000000000..ea7b51164e --- /dev/null +++ b/crates/vp8/src/error.rs @@ -0,0 +1,22 @@ +/// Errors produced by the decoder. +#[derive(Debug, Clone, PartialEq, Eq, thiserror::Error)] +pub enum Error { + /// A syntax element had a value outside its legal range, or a size did not fit the data. + #[error("invalid bitstream: {0}")] + Invalid(String), + /// An inter frame came before any key frame (decoding must start at a key frame). + #[error("missing reference frame: {0}")] + MissingReference(String), +} + +pub type Result = std::result::Result; + +/// `ensure!(cond, "message")` returns `Error::Invalid` when `cond` is false. +macro_rules! ensure { + ($cond:expr, $($arg:tt)+) => { + if !$cond { + return Err($crate::error::Error::Invalid(format!($($arg)+))); + } + }; +} +pub(crate) use ensure; diff --git a/crates/vp8/src/header.rs b/crates/vp8/src/header.rs new file mode 100644 index 0000000000..0d9d60bc6a --- /dev/null +++ b/crates/vp8/src/header.rs @@ -0,0 +1,287 @@ +//! Frame tag (9.1, 19.1) and the frame header in the first partition (9.2-9.11, 19.2). + +use crate::boolcoder::BoolDecoder; +use crate::error::{Error, Result, ensure}; + +/// The uncompressed data chunk at the start of every frame (9.1). +#[derive(Clone, Copy, Debug, PartialEq, Eq)] +pub struct FrameTag { + pub key_frame: bool, + /// Bitstream version (0-3): reconstruction filter and full-pixel chroma. + pub version: u8, + pub show_frame: bool, + pub first_part_size: usize, + /// Key frames only: dimensions and upscaling codes. + pub width: u16, + pub height: u16, + pub horizontal_scale: u8, + pub vertical_scale: u8, + /// Bytes of the uncompressed chunk (3, or 10 for key frames). + pub header_len: usize, +} + +impl FrameTag { + pub fn parse(data: &[u8]) -> Result { + let [b0, b1, b2, ..] = *data else { + return Err(Error::Invalid("frame shorter than its 3-byte tag".into())); + }; + let tmp = u32::from(b0) | u32::from(b1) << 8 | u32::from(b2) << 16; + let key_frame = tmp & 1 == 0; + let mut tag = FrameTag { + key_frame, + version: ((tmp >> 1) & 7) as u8, + show_frame: (tmp >> 4) & 1 == 1, + first_part_size: (tmp >> 5) as usize, + width: 0, + height: 0, + horizontal_scale: 0, + vertical_scale: 0, + header_len: 3, + }; + if key_frame { + let Some(c) = data.get(3..10) else { + return Err(Error::Invalid("key frame shorter than its 10-byte header".into())); + }; + ensure!(c[0] == 0x9d && c[1] == 0x01 && c[2] == 0x2a, "bad key frame start code"); + let w = u16::from_le_bytes([c[3], c[4]]); + let h = u16::from_le_bytes([c[5], c[6]]); + tag.width = w & 0x3fff; + tag.horizontal_scale = (w >> 14) as u8; + tag.height = h & 0x3fff; + tag.vertical_scale = (h >> 14) as u8; + tag.header_len = 10; + } + Ok(tag) + } +} + +/// Segment-based adjustments (9.3): carried from frame to frame. +#[derive(Clone, Copy, Debug, Default)] +pub struct Segmentation { + pub enabled: bool, + pub update_map: bool, + /// Feature values are absolute (else deltas from the frame values). + pub absolute: bool, + pub quant: [i8; 4], + pub lf_level: [i8; 4], + pub tree_probs: [u8; 3], +} + +/// Loop filter adjustments by reference frame and mode (9.4): carried from frame to frame. +#[derive(Clone, Copy, Debug, Default, PartialEq, Eq)] +pub struct LfDeltas { + pub enabled: bool, + /// Intra, last, golden, altref. + pub ref_deltas: [i8; 4], + /// B_PRED, ZEROMV, other MV modes, SPLITMV. + pub mode_deltas: [i8; 4], +} + +/// Probabilities that persist across frames (until the next key frame), saved and restored +/// around a frame whose `refresh_entropy_probs` is 0. +#[derive(Clone, Debug, PartialEq, Eq)] +pub struct EntropyContext { + pub coeff: [[[[u8; 11]; 3]; 8]; 4], + pub ymode: [u8; 4], + pub uv_mode: [u8; 3], + pub mv: [[u8; 19]; 2], +} + +impl Default for EntropyContext { + fn default() -> Self { + use crate::spec_tables::{DEFAULT_COEFF_PROBS, DEFAULT_MV_PROBS, UV_MODE_PROBS, YMODE_PROBS}; + let mut coeff = [[[[0u8; 11]; 3]; 8]; 4]; + for (i, p) in coeff.iter_mut().flatten().flatten().flatten().enumerate() { + *p = DEFAULT_COEFF_PROBS[i]; + } + let mut mv = [[0u8; 19]; 2]; + for (i, p) in mv.iter_mut().flatten().enumerate() { + *p = DEFAULT_MV_PROBS[i]; + } + EntropyContext { coeff, ymode: YMODE_PROBS, uv_mode: UV_MODE_PROBS, mv } + } +} + +/// Dequantization indices (9.6). +#[derive(Clone, Copy, Debug, Default)] +pub struct QuantIndices { + pub y_ac: i32, + pub y_dc_delta: i32, + pub y2_dc_delta: i32, + pub y2_ac_delta: i32, + pub uv_dc_delta: i32, + pub uv_ac_delta: i32, +} + +/// Per-frame header fields (9.2-9.11) that do not persist. +#[derive(Clone, Debug, Default)] +pub struct FrameHeader { + pub color_space: u8, + pub clamping_type: u8, + /// 0 normal, 1 simple. + pub filter_type: u8, + pub filter_level: u8, + pub sharpness: u8, + pub partitions: usize, + pub quant: QuantIndices, + pub refresh_golden: bool, + pub refresh_altref: bool, + /// 0 none, 1 last, 2 altref. + pub copy_to_golden: u8, + /// 0 none, 1 last, 2 golden. + pub copy_to_altref: u8, + pub sign_bias_golden: bool, + pub sign_bias_altref: bool, + pub refresh_entropy: bool, + pub refresh_last: bool, + /// `prob_skip_false` when `mb_no_skip_coeff` is set. + pub skip_prob: Option, + pub prob_intra: u8, + pub prob_last: u8, + pub prob_golden: u8, +} + +/// segmentation_header (9.3). +fn read_segmentation(d: &mut BoolDecoder, seg: &mut Segmentation) { + seg.enabled = d.read_flag(); + seg.update_map = false; + if !seg.enabled { + return; + } + seg.update_map = d.read_flag(); + let update_data = d.read_flag(); + if update_data { + seg.absolute = d.read_flag(); + for q in &mut seg.quant { + *q = d.read_optional_signed(7) as i8; + } + for l in &mut seg.lf_level { + *l = d.read_optional_signed(6) as i8; + } + } + if seg.update_map { + for p in &mut seg.tree_probs { + *p = if d.read_flag() { d.read_literal(8) as u8 } else { crate::tables::DEFAULT_SEGMENT_PROB }; + } + } +} + +/// Loop filter delta adjustments (9.6). +fn read_lf_deltas(d: &mut BoolDecoder, lf: &mut LfDeltas) { + lf.enabled = d.read_flag(); + if lf.enabled && d.read_flag() { + for v in lf.ref_deltas.iter_mut().chain(lf.mode_deltas.iter_mut()) { + if d.read_flag() { + *v = d.read_signed(6) as i8; + } + } + } +} + +/// Reference frame refresh / copy / sign bias flags (9.7, 9.8) and the entropy refresh flag. +fn read_reference_updates(d: &mut BoolDecoder, key_frame: bool, h: &mut FrameHeader) { + if key_frame { + h.refresh_golden = true; + h.refresh_altref = true; + h.refresh_entropy = d.read_flag(); + h.refresh_last = true; + return; + } + h.refresh_golden = d.read_flag(); + h.refresh_altref = d.read_flag(); + if !h.refresh_golden { + h.copy_to_golden = d.read_literal(2) as u8; + } + if !h.refresh_altref { + h.copy_to_altref = d.read_literal(2) as u8; + } + h.sign_bias_golden = d.read_flag(); + h.sign_bias_altref = d.read_flag(); + h.refresh_entropy = d.read_flag(); + h.refresh_last = d.read_flag(); +} + +/// token_prob_update (13.4). +fn read_token_prob_updates(d: &mut BoolDecoder, ctx: &mut EntropyContext) { + let mut i = 0; + for plane in ctx.coeff.iter_mut() { + for band in plane.iter_mut() { + for c in band.iter_mut() { + for p in c.iter_mut() { + if d.read_bool(crate::spec_tables::COEFF_UPDATE_PROBS[i]) { + *p = d.read_literal(8) as u8; + } + i += 1; + } + } + } + } +} + +/// Inter frame probabilities: intra/last/golden, mode probabilities and mv_prob_update (17.2). +fn read_inter_probs(d: &mut BoolDecoder, h: &mut FrameHeader, ctx: &mut EntropyContext) { + h.prob_intra = d.read_literal(8) as u8; + h.prob_last = d.read_literal(8) as u8; + h.prob_golden = d.read_literal(8) as u8; + if d.read_flag() { + for p in &mut ctx.ymode { + *p = d.read_literal(8) as u8; + } + } + if d.read_flag() { + for p in &mut ctx.uv_mode { + *p = d.read_literal(8) as u8; + } + } + let mut i = 0; + for comp in ctx.mv.iter_mut() { + for p in comp.iter_mut() { + if d.read_bool(crate::spec_tables::MV_UPDATE_PROBS[i]) { + let x = d.read_literal(7) as u8; + *p = if x != 0 { x << 1 } else { 1 }; + } + i += 1; + } + } +} + +/// Read the frame header from the first partition, updating the persistent state. +/// `saved` receives the entropy context to restore after the frame when +/// `refresh_entropy_probs` is 0. +pub fn read_header( + d: &mut BoolDecoder, + key_frame: bool, + seg: &mut Segmentation, + lf: &mut LfDeltas, + ctx: &mut EntropyContext, + saved: &mut Option, +) -> FrameHeader { + let mut h = FrameHeader::default(); + if key_frame { + h.color_space = d.read_literal(1) as u8; + h.clamping_type = d.read_literal(1) as u8; + } + read_segmentation(d, seg); + h.filter_type = d.read_literal(1) as u8; + h.filter_level = d.read_literal(6) as u8; + h.sharpness = d.read_literal(3) as u8; + read_lf_deltas(d, lf); + h.partitions = 1 << d.read_literal(2); + h.quant = QuantIndices { + y_ac: d.read_literal(7) as i32, + y_dc_delta: d.read_optional_signed(4), + y2_dc_delta: d.read_optional_signed(4), + y2_ac_delta: d.read_optional_signed(4), + uv_dc_delta: d.read_optional_signed(4), + uv_ac_delta: d.read_optional_signed(4), + }; + read_reference_updates(d, key_frame, &mut h); + // The probabilities in effect before this frame's updates come back after it. + *saved = if h.refresh_entropy { None } else { Some(ctx.clone()) }; + read_token_prob_updates(d, ctx); + h.skip_prob = if d.read_flag() { Some(d.read_literal(8) as u8) } else { None }; + if !key_frame { + read_inter_probs(d, &mut h, ctx); + } + h +} diff --git a/crates/vp8/src/lib.rs b/crates/vp8/src/lib.rs new file mode 100644 index 0000000000..b96c102025 --- /dev/null +++ b/crates/vp8/src/lib.rs @@ -0,0 +1,79 @@ +//! Clean-room pure-Rust VP8 decoder, implemented from RFC 6386 ("VP8 Data Format and Decoding +//! Guide"), sections 1-19. +//! +//! Supported: the whole format — key frames and inter frames, all intra modes (16x16, chroma, +//! the ten 4x4 subblock modes), inter prediction from the last, golden and altref frames with +//! sign bias, NEAREST / NEAR / ZERO / NEW / SPLIT motion vectors, six-tap and bilinear +//! interpolation (versions 0-3, full-pixel chroma), segmentation, the normal and simple loop +//! filters with mode / reference deltas, multiple token partitions, probability persistence +//! (`refresh_entropy_probs`), golden / altref copies and hidden (altref) frames. +//! +//! ```no_run +//! let mut dec = craft_vp8::Decoder::new(); +//! let frame: Vec = Vec::new(); // one IVF / WebM frame +//! if let Some(pic) = dec.decode(&frame, 0).unwrap() { +//! println!("{}x{}", pic.width, pic.height); +//! } +//! ``` + +#![deny(clippy::unwrap_used, clippy::expect_used, clippy::panic, clippy::unimplemented, clippy::todo, clippy::unreachable)] +#![allow(clippy::needless_range_loop)] + +mod boolcoder; +mod decoder; +mod error; +mod header; +mod loopfilter; +mod predict; +#[rustfmt::skip] +mod spec_tables; +mod tables; +mod transform; + +pub use decoder::{DecodeStats, Decoder}; +pub use error::{Error, Result}; + +/// A decoded picture: 8-bit 4:2:0, planes tight (stride = width). +#[derive(Clone, Debug)] +pub struct Picture { + pub width: u32, + pub height: u32, + pub chroma_width: u32, + pub chroma_height: u32, + pub y: Vec, + pub u: Vec, + pub v: Vec, + /// `pts` passed to [`Decoder::decode`] with the frame. + pub pts: i64, + pub key: bool, + /// `color_space` of the last key frame (0: BT.601 YUV, 1: reserved). + pub color_space: u8, +} + +/// Stream parameters read from a key frame's uncompressed chunk (see [`keyframe_info`]). +#[derive(Clone, Copy, Debug, PartialEq, Eq)] +pub struct KeyframeInfo { + pub width: u32, + pub height: u32, + /// Upscaling codes (9.1), informational only. + pub horizontal_scale: u8, + pub vertical_scale: u8, + pub version: u8, +} + +/// If `frame` is a key frame (a random access point), its parameters. +pub fn keyframe_info(frame: &[u8]) -> Option { + let tag = header::FrameTag::parse(frame).ok()?; + (tag.key_frame && tag.width > 0 && tag.height > 0).then_some(KeyframeInfo { + width: u32::from(tag.width), + height: u32::from(tag.height), + horizontal_scale: tag.horizontal_scale, + vertical_scale: tag.vertical_scale, + version: tag.version, + }) +} + +/// Whether `frame` is a key frame (decoding can start there). +pub fn is_keyframe(frame: &[u8]) -> bool { + keyframe_info(frame).is_some() +} diff --git a/crates/vp8/src/loopfilter.rs b/crates/vp8/src/loopfilter.rs new file mode 100644 index 0000000000..d94d5a2a9f --- /dev/null +++ b/crates/vp8/src/loopfilter.rs @@ -0,0 +1,156 @@ +//! Loop filter (15). + +/// Filter parameters of one macroblock (15.4). +#[derive(Clone, Copy, Debug)] +pub struct Params { + pub mbedge_limit: i32, + pub sub_bedge_limit: i32, + pub interior_limit: i32, + pub hev_threshold: i32, +} + +impl Params { + pub fn new(level: u8, sharpness: u8, key_frame: bool) -> Params { + let level = i32::from(level); + let mut interior = level; + if sharpness > 0 { + interior >>= if sharpness > 4 { 2 } else { 1 }; + interior = interior.min(9 - i32::from(sharpness)); + } + let interior = interior.max(1); + let hev_threshold = match (key_frame, level) { + (_, l) if l >= 40 => 2 + i32::from(!key_frame), + (false, l) if l >= 20 => 2, + (_, l) if l >= 15 => 1, + _ => 0, + }; + Params { mbedge_limit: (level + 2) * 2 + interior, sub_bedge_limit: level * 2 + interior, interior_limit: interior, hev_threshold } + } +} + +/// Which edge filter to apply along an edge. +#[derive(Clone, Copy)] +pub enum Kind { + /// The simple filter (15.2) with the macroblock edge limit. + Simple, + /// The simple filter with the subblock edge limit. + SimpleInner, + /// The normal macroblock edge filter (`MBfilter`, 15.3). + NormalMb, + /// The normal subblock edge filter (`subblock_filter`, 15.3). + NormalSub, +} + +/// Filter `N` segments at once: `px[k][l]` is pixel `k - 4` across the edge of segment `l` +/// (p3 p2 p1 p0 | q0 q1 q2 q3). Branch-free: the conditions of 15.2 / 15.3 become masks +/// (all ones or zero) that zero the adjustment where the filter does not apply, which gives the +/// same values as the specification's procedures; each step is a plain loop over the lanes, which +/// the compiler vectorizes. +#[inline(always)] +fn filter_lanes(px: &mut [[u8; N]; 8], edge_limit: i16, prm: &Params) { + let (lim, thr) = (prm.interior_limit as i16, prm.hev_threshold as i16); + let ld = |k: usize| -> [i16; N] { std::array::from_fn(|l| i16::from(px[k][l])) }; + let (p3, p2, p1, p0, q0, q1, q2, q3) = (ld(0), ld(1), ld(2), ld(3), ld(4), ld(5), ld(6), ld(7)); + let mut m = [0i16; N]; + let mut hev = [0i16; N]; + for l in 0..N { + let edge = (p0[l] - q0[l]).abs() * 2 + (p1[l] - q1[l]).abs() / 2; + m[l] = -i16::from(edge <= edge_limit); + } + if NORMAL { + for l in 0..N { + let d = (p3[l] - p2[l]) + .abs() + .max((p2[l] - p1[l]).abs()) + .max((p1[l] - p0[l]).abs()) + .max((q3[l] - q2[l]).abs()) + .max((q2[l] - q1[l]).abs()) + .max((q1[l] - q0[l]).abs()); + m[l] &= -i16::from(d <= lim); + hev[l] = -i16::from((p1[l] - p0[l]).abs().max((q1[l] - q0[l]).abs()) > thr); + } + } + let c = |v: i16| v.clamp(-128, 127); + let out = |v: i16| (c(v) + 128) as u8; + for l in 0..N { + let (sp2, sp1, sp0, sq0, sq1, sq2) = (p2[l] - 128, p1[l] - 128, p0[l] - 128, q0[l] - 128, q1[l] - 128, q2[l] - 128); + if !NORMAL { + // simple_segment / common_adjust with the outer taps + let f = c(c(sp1 - sq1) + 3 * (sq0 - sp0)) & m[l]; + px[4][l] = out(sq0 - (c(f + 4) >> 3)); + px[3][l] = out(sp0 + (c(f + 3) >> 3)); + } else if !MB { + // subblock_filter: common_adjust (outer taps on high edge variance), then half of it + // on p1 / q1 without high edge variance + let f = c((c(sp1 - sq1) & hev[l]) + 3 * (sq0 - sp0)) & m[l]; + let f1 = c(f + 4) >> 3; + px[4][l] = out(sq0 - f1); + px[3][l] = out(sp0 + (c(f + 3) >> 3)); + let a = ((f1 + 1) >> 1) & !hev[l]; + px[5][l] = out(sq1 - a); + px[2][l] = out(sp1 + a); + } else { + // MBfilter: common_adjust with outer taps on high edge variance, the 27/18/9 + // adjustments otherwise + let w = c(c(sp1 - sq1) + 3 * (sq0 - sp0)) & m[l]; + let f = w & hev[l]; + let q0a = c(sq0 - (c(f + 4) >> 3)); + let p0a = c(sp0 + (c(f + 3) >> 3)); + let w = w & !hev[l]; + // 27 * w + 63 fits 16 bits: |w| <= 128 + let a = |mul: i16| c((mul * w + 63) >> 7); + let (a27, a18, a9) = (a(27), a(18), a(9)); + px[4][l] = out(q0a - a27); + px[3][l] = out(p0a + a27); + px[5][l] = out(sq1 - a18); + px[2][l] = out(sp1 + a18); + px[6][l] = out(sq2 - a9); + px[1][l] = out(sp2 + a9); + } + } +} + +/// Filter an edge (15.2, 15.3) of `N` segments. `start` is q0 of the first segment; a vertical +/// edge (`vertical`) has its segments one row apart, a horizontal one one pixel apart. Every +/// segment must have 4 pixels inside the plane on both sides of the edge; an edge that does not +/// is left alone. +pub fn filter_edge(plane: &mut [u8], stride: usize, start: usize, vertical: bool, kind: Kind, prm: &Params) { + let (step, along) = if vertical { (1, stride) } else { (stride, 1) }; + if start < 4 * step || start + (N - 1) * along + 3 * step >= plane.len() { + return; + } + let mut px = [[0u8; N]; 8]; + let first = start - 4 * step; + if vertical { + for l in 0..N { + let row = first + l * stride; + for k in 0..8 { + px[k][l] = plane[row + k]; + } + } + } else { + for (k, row) in px.iter_mut().enumerate() { + let at = first + k * stride; + row.copy_from_slice(&plane[at..at + N]); + } + } + match kind { + Kind::Simple => filter_lanes::(&mut px, prm.mbedge_limit as i16, prm), + Kind::SimpleInner => filter_lanes::(&mut px, prm.sub_bedge_limit as i16, prm), + Kind::NormalMb => filter_lanes::(&mut px, prm.mbedge_limit as i16, prm), + Kind::NormalSub => filter_lanes::(&mut px, prm.sub_bedge_limit as i16, prm), + } + if vertical { + for l in 0..N { + let row = first + l * stride; + for k in 1..7 { + plane[row + k] = px[k][l]; + } + } + } else { + for (k, row) in px.iter().enumerate().take(7).skip(1) { + let at = first + k * stride; + plane[at..at + N].copy_from_slice(row); + } + } +} diff --git a/crates/vp8/src/predict.rs b/crates/vp8/src/predict.rs new file mode 100644 index 0000000000..ddc41ffa93 --- /dev/null +++ b/crates/vp8/src/predict.rs @@ -0,0 +1,340 @@ +//! Intra prediction (12) and inter prediction (18). +//! +//! Planes are the decoder's reconstruction buffers, whose size is a whole number of macroblocks. +//! Intra prediction reads the not yet loop-filtered current frame; pixels above the frame are 127 +//! and pixels left of it 129 (the corner above-left of a row's first macroblock is 127 on the top +//! row and 129 below it). Inter prediction reads a reference plane extended infinitely by +//! repeating its edge pixels. + +use crate::tables::{B_HD_PRED, B_HE_PRED, B_HU_PRED, B_LD_PRED, B_RD_PRED, B_TM_PRED, B_VE_PRED, B_VL_PRED, B_VR_PRED, H_PRED, TM_PRED, V_PRED}; + +/// A pixel of the current frame for intra prediction, with the out-of-frame values of section 12. +#[inline] +fn edge(plane: &[u8], stride: usize, x: isize, y: isize) -> u8 { + if y < 0 { + 127 + } else if x < 0 { + 129 + } else { + plane.get(y as usize * stride + x as usize).copied().unwrap_or(127) + } +} + +/// Predict a whole `size`x`size` block (16x16 luma or 8x8 chroma) at `(x0, y0)` with a +/// macroblock-level mode (12.2, 12.3), writing the prediction into the plane. +pub fn predict_block(plane: &mut [u8], stride: usize, x0: usize, y0: usize, size: usize, mode: u8) { + let (xi, yi) = (x0 as isize, y0 as isize); + let mut above = [0u8; 16]; + let mut left = [0u8; 16]; + for i in 0..size { + above[i] = edge(plane, stride, xi + i as isize, yi - 1); + left[i] = edge(plane, stride, xi - 1, yi + i as isize); + } + let p = i32::from(edge(plane, stride, xi - 1, yi - 1)); + let shift = size.trailing_zeros(); + let fill = |plane: &mut [u8], f: &dyn Fn(usize, usize) -> u8| { + for r in 0..size { + let start = (y0 + r) * stride + x0; + if let Some(row) = plane.get_mut(start..start + size) { + for (c, px) in row.iter_mut().enumerate() { + *px = f(r, c); + } + } + } + }; + match mode { + V_PRED => fill(plane, &|_, c| above[c]), + H_PRED => fill(plane, &|r, _| left[r]), + TM_PRED => fill(plane, &|r, c| (i32::from(left[r]) + i32::from(above[c]) - p).clamp(0, 255) as u8), + _ => { + // DC_PRED: only the genuinely available edges count + let sum = |v: &[u8]| v[..size].iter().map(|&x| u32::from(x)).sum::(); + let dc = match (y0 > 0, x0 > 0) { + (true, true) => (sum(&above) + sum(&left) + (1 << shift)) >> (shift + 1), + (true, false) => (sum(&above) + (1 << (shift - 1))) >> shift, + (false, true) => (sum(&left) + (1 << (shift - 1))) >> shift, + (false, false) => 128, + } as u8; + fill(plane, &|_, _| dc); + } + } +} + +/// Predict the 4x4 luma subblock `b` (raster index in its macroblock) of the macroblock at +/// `(mx, my)` (in pixels) with subblock mode `mode` (12.3). `last_column`: the macroblock is the +/// rightmost of its row. +pub fn predict_subblock(plane: &mut [u8], stride: usize, mx: usize, my: usize, b: usize, mode: u8, last_column: bool) { + let (bx, by) = (b & 3, b >> 2); + let x = (mx + 4 * bx) as isize; + let y = (my + 4 * by) as isize; + // A[-1] = P, A[0..8]: the 4 pixels above and the 4 above-right + let mut a = [0i32; 9]; + a[0] = i32::from(edge(plane, stride, x - 1, y - 1)); + for i in 0..4 { + a[1 + i] = i32::from(edge(plane, stride, x + i as isize, y - 1)); + } + for i in 0..4 { + a[5 + i] = i32::from(if bx < 3 { + edge(plane, stride, x + 4 + i as isize, y - 1) + } else if my == 0 { + 127 + } else if last_column { + // the macroblock's above-right pixels lie outside the frame: repeat (-1, 15) + edge(plane, stride, mx as isize + 15, my as isize - 1) + } else { + // right-column subblocks all use the pixels above-right of the macroblock + edge(plane, stride, mx as isize + 16 + i as isize, my as isize - 1) + }); + } + let mut l = [0i32; 4]; + for (i, v) in l.iter_mut().enumerate() { + *v = i32::from(edge(plane, stride, x - 1, y + i as isize)); + } + let av = |i: isize| a[(i + 1) as usize]; // A[i], i in -1..8 + // E[0..9] = L[3], L[2], L[1], L[0], P, A[0..4] + let e = [l[3], l[2], l[1], l[0], a[0], a[1], a[2], a[3], a[4]]; + let avg3 = |x: i32, y: i32, z: i32| (x + y + y + z + 2) >> 2; + let avg2 = |x: i32, y: i32| (x + y + 1) >> 1; + let a3 = |i: isize| avg3(av(i - 1), av(i), av(i + 1)); // avg3p(A + i) + let a2 = |i: isize| avg2(av(i), av(i + 1)); // avg2p(A + i) + let e3 = |i: usize| avg3(e[i - 1], e[i], e[i + 1]); + let e2 = |i: usize| avg2(e[i], e[i + 1]); + // L[-1] = P + let lv = |i: isize| if i < 0 { a[0] } else { l[i as usize] }; + let l3 = |i: isize| avg3(lv(i - 1), lv(i), lv(i + 1)); + let l2 = |i: isize| avg2(lv(i), lv(i + 1)); + let mut out = [[0i32; 4]; 4]; + match mode { + B_TM_PRED => { + for r in 0..4 { + for c in 0..4 { + out[r][c] = (l[r] + av(c as isize) - a[0]).clamp(0, 255); + } + } + } + B_VE_PRED => { + for c in 0..4 { + let v = a3(c as isize); + for row in &mut out { + row[c] = v; + } + } + } + B_HE_PRED => { + for r in 0..4 { + let v = if r == 3 { avg3(l[2], l[3], l[3]) } else { l3(r as isize) }; + out[r] = [v; 4]; + } + } + B_LD_PRED => { + for r in 0..4 { + for c in 0..4 { + let d = r + c; + out[r][c] = if d == 6 { avg3(av(6), av(7), av(7)) } else { a3(d as isize + 1) }; + } + } + } + B_RD_PRED => { + for r in 0..4 { + for c in 0..4 { + // (3,0) uses E[1], each step towards the upper right one further + out[r][c] = e3((4 + c as isize - r as isize) as usize); + } + } + } + B_VR_PRED => { + out[3][0] = e3(2); + out[2][0] = e3(3); + out[3][1] = e3(4); + out[1][0] = e3(4); + out[2][1] = e2(4); + out[0][0] = e2(4); + out[3][2] = e3(5); + out[1][1] = e3(5); + out[2][2] = e2(5); + out[0][1] = e2(5); + out[3][3] = e3(6); + out[1][2] = e3(6); + out[2][3] = e2(6); + out[0][2] = e2(6); + out[1][3] = e3(7); + out[0][3] = e2(7); + } + B_VL_PRED => { + out[0][0] = a2(0); + out[1][0] = a3(1); + out[2][0] = a2(1); + out[0][1] = a2(1); + out[1][1] = a3(2); + out[3][0] = a3(2); + out[2][1] = a2(2); + out[0][2] = a2(2); + out[3][1] = a3(3); + out[1][2] = a3(3); + out[2][2] = a2(3); + out[0][3] = a2(3); + out[3][2] = a3(4); + out[1][3] = a3(4); + out[2][3] = a3(5); + out[3][3] = a3(6); + } + B_HD_PRED => { + out[3][0] = e2(0); + out[3][1] = e3(1); + out[2][0] = e2(1); + out[3][2] = e2(1); + out[2][1] = e3(2); + out[3][3] = e3(2); + out[2][2] = e2(2); + out[1][0] = e2(2); + out[2][3] = e3(3); + out[1][1] = e3(3); + out[1][2] = e2(3); + out[0][0] = e2(3); + out[1][3] = e3(4); + out[0][1] = e3(4); + out[0][2] = e3(5); + out[0][3] = e3(6); + } + B_HU_PRED => { + out[0][0] = l2(0); + out[0][1] = l3(1); + out[0][2] = l2(1); + out[1][0] = l2(1); + out[0][3] = l3(2); + out[1][1] = l3(2); + out[1][2] = l2(2); + out[2][0] = l2(2); + out[1][3] = avg3(l[2], l[3], l[3]); + out[2][1] = avg3(l[2], l[3], l[3]); + out[2][2] = l[3]; + out[2][3] = l[3]; + out[3] = [l[3]; 4]; + } + _ => { + // B_DC_PRED + let v = (4 + (1..5).map(|i| a[i]).sum::() + l.iter().sum::()) >> 3; + out = [[v; 4]; 4]; + } + } + for (r, row) in out.iter().enumerate() { + let start = (y as usize + r) * stride + x as usize; + if let Some(dst) = plane.get_mut(start..start + 4) { + for (d, &v) in dst.iter_mut().zip(row) { + *d = v as u8; + } + } + } +} + +/// A reference plane for inter prediction: reads outside it repeat the nearest edge pixel. +pub struct RefPlane<'a> { + pub data: &'a [u8], + pub stride: usize, + pub width: usize, + pub height: usize, +} + +impl RefPlane<'_> { + #[inline] + fn at(&self, x: isize, y: isize) -> u8 { + let x = x.clamp(0, self.width as isize - 1) as usize; + let y = y.clamp(0, self.height as isize - 1) as usize; + self.data.get(y * self.stride + x).copied().unwrap_or(0) + } +} + +/// Destination plane of an inter prediction (same layout as the block's plane). +pub struct DstPlane<'a> { + pub data: &'a mut [u8], + pub stride: usize, +} + +/// A `w`x`h` block at `(x, y)` of a plane. +#[derive(Clone, Copy)] +pub struct BlockRect { + pub x: usize, + pub y: usize, + pub w: usize, + pub h: usize, +} + +/// Predict the block `rect` of a plane from `reference` displaced by the motion vector +/// `(mvx, mvy)` in eighth pixels, using `filters` (8 x 6 taps: the six-tap or bilinear set, +/// 18.3), writing into `dst`. +pub fn predict_inter(reference: &RefPlane, dst: DstPlane, rect: BlockRect, (mvx, mvy): (i32, i32), filters: &[i32; 48]) { + let DstPlane { data: dst, stride: dst_stride } = dst; + let BlockRect { x: bx, y: by, w, h } = rect; + let x0 = bx as isize + (mvx >> 3) as isize; + let y0 = by as isize + (mvy >> 3) as isize; + let (fx, fy) = ((mvx & 7) as usize, (mvy & 7) as usize); + // source window: 2 before, 3 after in each direction + const MAX: usize = 16 + 5; + let mut src = [[0u8; MAX]; MAX]; + let full_inside = x0 >= 2 && y0 >= 2 && x0 + w as isize + 3 <= reference.width as isize && y0 + h as isize + 3 <= reference.height as isize; + for r in 0..h + 5 { + let yy = y0 + r as isize - 2; + if full_inside { + let start = yy as usize * reference.stride + x0 as usize - 2; + if let Some(row) = reference.data.get(start..start + w + 5) { + src[r][..w + 5].copy_from_slice(row); + continue; + } + } + for c in 0..w + 5 { + src[r][c] = reference.at(x0 + c as isize - 2, yy); + } + } + if fx == 0 && fy == 0 { + for r in 0..h { + let start = (by + r) * dst_stride + bx; + if let Some(row) = dst.get_mut(start..start + w) { + row.copy_from_slice(&src[r + 2][2..2 + w]); + } + } + return; + } + let tap6 = |f: usize| -> [i32; 6] { std::array::from_fn(|k| filters.get(f * 6 + k).copied().unwrap_or(0)) }; + let (hf, vf) = (tap6(fx), tap6(fy)); + let round = |v: i32| ((v + 64) >> 7).clamp(0, 255); + // horizontal pass over the h + 5 rows (an identity when fx is 0), then vertical + let mut tmp = [[0i32; 16]; MAX]; + for r in 0..h + 5 { + let s = &src[r]; + let t = &mut tmp[r]; + if fx == 0 { + for c in 0..w { + t[c] = i32::from(s[c + 2]); + } + } else { + for c in 0..w { + let v = i32::from(s[c]) * hf[0] + + i32::from(s[c + 1]) * hf[1] + + i32::from(s[c + 2]) * hf[2] + + i32::from(s[c + 3]) * hf[3] + + i32::from(s[c + 4]) * hf[4] + + i32::from(s[c + 5]) * hf[5]; + t[c] = round(v); + } + } + } + for r in 0..h { + let start = (by + r) * dst_stride + bx; + let Some(row) = dst.get_mut(start..start + w) else { continue }; + if fy == 0 { + for (c, d) in row.iter_mut().enumerate() { + *d = tmp[r + 2][c] as u8; + } + } else { + for (c, d) in row.iter_mut().enumerate() { + let v = tmp[r][c] * vf[0] + + tmp[r + 1][c] * vf[1] + + tmp[r + 2][c] * vf[2] + + tmp[r + 3][c] * vf[3] + + tmp[r + 4][c] * vf[4] + + tmp[r + 5][c] * vf[5]; + *d = round(v) as u8; + } + } + } +} diff --git a/crates/vp8/src/spec_tables.rs b/crates/vp8/src/spec_tables.rs new file mode 100644 index 0000000000..e31ebb5d41 --- /dev/null +++ b/crates/vp8/src/spec_tables.rs @@ -0,0 +1,302 @@ +// Generated by tools/extract_tables.py from the text of RFC 6386 (sections 1-19). Do not edit. +// Every table is flat, row-major, in the order the specification prints it. + +/// `coeff_update_probs` (RFC 6386), 1056 entries. +pub const COEFF_UPDATE_PROBS: [u8; 1056] = [ + 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, + 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, + 255, 176, 246, 255, 255, 255, 255, 255, 255, 255, 255, 255, 223, 241, 252, 255, + 255, 255, 255, 255, 255, 255, 255, 249, 253, 253, 255, 255, 255, 255, 255, 255, + 255, 255, 255, 244, 252, 255, 255, 255, 255, 255, 255, 255, 255, 234, 254, 254, + 255, 255, 255, 255, 255, 255, 255, 255, 253, 255, 255, 255, 255, 255, 255, 255, + 255, 255, 255, 255, 246, 254, 255, 255, 255, 255, 255, 255, 255, 255, 239, 253, + 254, 255, 255, 255, 255, 255, 255, 255, 255, 254, 255, 254, 255, 255, 255, 255, + 255, 255, 255, 255, 255, 248, 254, 255, 255, 255, 255, 255, 255, 255, 255, 251, + 255, 254, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, + 255, 255, 255, 255, 255, 255, 253, 254, 255, 255, 255, 255, 255, 255, 255, 255, + 251, 254, 254, 255, 255, 255, 255, 255, 255, 255, 255, 254, 255, 254, 255, 255, + 255, 255, 255, 255, 255, 255, 255, 254, 253, 255, 254, 255, 255, 255, 255, 255, + 255, 250, 255, 254, 255, 254, 255, 255, 255, 255, 255, 255, 254, 255, 255, 255, + 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, + 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, + 255, 255, 255, 255, 255, 255, 255, 255, 217, 255, 255, 255, 255, 255, 255, 255, + 255, 255, 255, 225, 252, 241, 253, 255, 255, 254, 255, 255, 255, 255, 234, 250, + 241, 250, 253, 255, 253, 254, 255, 255, 255, 255, 254, 255, 255, 255, 255, 255, + 255, 255, 255, 255, 223, 254, 254, 255, 255, 255, 255, 255, 255, 255, 255, 238, + 253, 254, 254, 255, 255, 255, 255, 255, 255, 255, 255, 248, 254, 255, 255, 255, + 255, 255, 255, 255, 255, 249, 254, 255, 255, 255, 255, 255, 255, 255, 255, 255, + 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 253, 255, 255, 255, + 255, 255, 255, 255, 255, 255, 247, 254, 255, 255, 255, 255, 255, 255, 255, 255, + 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 253, 254, 255, + 255, 255, 255, 255, 255, 255, 255, 252, 255, 255, 255, 255, 255, 255, 255, 255, + 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 254, 254, + 255, 255, 255, 255, 255, 255, 255, 255, 253, 255, 255, 255, 255, 255, 255, 255, + 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 254, + 253, 255, 255, 255, 255, 255, 255, 255, 255, 250, 255, 255, 255, 255, 255, 255, + 255, 255, 255, 255, 254, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, + 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, + 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, + 186, 251, 250, 255, 255, 255, 255, 255, 255, 255, 255, 234, 251, 244, 254, 255, + 255, 255, 255, 255, 255, 255, 251, 251, 243, 253, 254, 255, 254, 255, 255, 255, + 255, 255, 253, 254, 255, 255, 255, 255, 255, 255, 255, 255, 236, 253, 254, 255, + 255, 255, 255, 255, 255, 255, 255, 251, 253, 253, 254, 254, 255, 255, 255, 255, + 255, 255, 255, 254, 254, 255, 255, 255, 255, 255, 255, 255, 255, 254, 254, 254, + 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, + 255, 255, 255, 255, 254, 255, 255, 255, 255, 255, 255, 255, 255, 255, 254, 254, + 255, 255, 255, 255, 255, 255, 255, 255, 255, 254, 255, 255, 255, 255, 255, 255, + 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 254, + 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, + 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, + 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, + 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, + 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, + 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, + 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, + 255, 255, 255, 255, 255, 255, 255, 255, 248, 255, 255, 255, 255, 255, 255, 255, + 255, 255, 255, 250, 254, 252, 254, 255, 255, 255, 255, 255, 255, 255, 248, 254, + 249, 253, 255, 255, 255, 255, 255, 255, 255, 255, 253, 253, 255, 255, 255, 255, + 255, 255, 255, 255, 246, 253, 253, 255, 255, 255, 255, 255, 255, 255, 255, 252, + 254, 251, 254, 254, 255, 255, 255, 255, 255, 255, 255, 254, 252, 255, 255, 255, + 255, 255, 255, 255, 255, 248, 254, 253, 255, 255, 255, 255, 255, 255, 255, 255, + 253, 255, 254, 254, 255, 255, 255, 255, 255, 255, 255, 255, 251, 254, 255, 255, + 255, 255, 255, 255, 255, 255, 245, 251, 254, 255, 255, 255, 255, 255, 255, 255, + 255, 253, 253, 254, 255, 255, 255, 255, 255, 255, 255, 255, 255, 251, 253, 255, + 255, 255, 255, 255, 255, 255, 255, 252, 253, 254, 255, 255, 255, 255, 255, 255, + 255, 255, 255, 254, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 252, 255, + 255, 255, 255, 255, 255, 255, 255, 255, 249, 255, 254, 255, 255, 255, 255, 255, + 255, 255, 255, 255, 255, 254, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, + 253, 255, 255, 255, 255, 255, 255, 255, 255, 250, 255, 255, 255, 255, 255, 255, + 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, + 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 254, 255, 255, 255, 255, 255, + 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, +]; + +/// `default_coeff_probs` (RFC 6386), 1056 entries. +pub const DEFAULT_COEFF_PROBS: [u8; 1056] = [ + 128, 128, 128, 128, 128, 128, 128, 128, 128, 128, 128, 128, 128, 128, 128, 128, + 128, 128, 128, 128, 128, 128, 128, 128, 128, 128, 128, 128, 128, 128, 128, 128, + 128, 253, 136, 254, 255, 228, 219, 128, 128, 128, 128, 128, 189, 129, 242, 255, + 227, 213, 255, 219, 128, 128, 128, 106, 126, 227, 252, 214, 209, 255, 255, 128, + 128, 128, 1, 98, 248, 255, 236, 226, 255, 255, 128, 128, 128, 181, 133, 238, + 254, 221, 234, 255, 154, 128, 128, 128, 78, 134, 202, 247, 198, 180, 255, 219, + 128, 128, 128, 1, 185, 249, 255, 243, 255, 128, 128, 128, 128, 128, 184, 150, + 247, 255, 236, 224, 128, 128, 128, 128, 128, 77, 110, 216, 255, 236, 230, 128, + 128, 128, 128, 128, 1, 101, 251, 255, 241, 255, 128, 128, 128, 128, 128, 170, + 139, 241, 252, 236, 209, 255, 255, 128, 128, 128, 37, 116, 196, 243, 228, 255, + 255, 255, 128, 128, 128, 1, 204, 254, 255, 245, 255, 128, 128, 128, 128, 128, + 207, 160, 250, 255, 238, 128, 128, 128, 128, 128, 128, 102, 103, 231, 255, 211, + 171, 128, 128, 128, 128, 128, 1, 152, 252, 255, 240, 255, 128, 128, 128, 128, + 128, 177, 135, 243, 255, 234, 225, 128, 128, 128, 128, 128, 80, 129, 211, 255, + 194, 224, 128, 128, 128, 128, 128, 1, 1, 255, 128, 128, 128, 128, 128, 128, + 128, 128, 246, 1, 255, 128, 128, 128, 128, 128, 128, 128, 128, 255, 128, 128, + 128, 128, 128, 128, 128, 128, 128, 128, 198, 35, 237, 223, 193, 187, 162, 160, + 145, 155, 62, 131, 45, 198, 221, 172, 176, 220, 157, 252, 221, 1, 68, 47, + 146, 208, 149, 167, 221, 162, 255, 223, 128, 1, 149, 241, 255, 221, 224, 255, + 255, 128, 128, 128, 184, 141, 234, 253, 222, 220, 255, 199, 128, 128, 128, 81, + 99, 181, 242, 176, 190, 249, 202, 255, 255, 128, 1, 129, 232, 253, 214, 197, + 242, 196, 255, 255, 128, 99, 121, 210, 250, 201, 198, 255, 202, 128, 128, 128, + 23, 91, 163, 242, 170, 187, 247, 210, 255, 255, 128, 1, 200, 246, 255, 234, + 255, 128, 128, 128, 128, 128, 109, 178, 241, 255, 231, 245, 255, 255, 128, 128, + 128, 44, 130, 201, 253, 205, 192, 255, 255, 128, 128, 128, 1, 132, 239, 251, + 219, 209, 255, 165, 128, 128, 128, 94, 136, 225, 251, 218, 190, 255, 255, 128, + 128, 128, 22, 100, 174, 245, 186, 161, 255, 199, 128, 128, 128, 1, 182, 249, + 255, 232, 235, 128, 128, 128, 128, 128, 124, 143, 241, 255, 227, 234, 128, 128, + 128, 128, 128, 35, 77, 181, 251, 193, 211, 255, 205, 128, 128, 128, 1, 157, + 247, 255, 236, 231, 255, 255, 128, 128, 128, 121, 141, 235, 255, 225, 227, 255, + 255, 128, 128, 128, 45, 99, 188, 251, 195, 217, 255, 224, 128, 128, 128, 1, + 1, 251, 255, 213, 255, 128, 128, 128, 128, 128, 203, 1, 248, 255, 255, 128, + 128, 128, 128, 128, 128, 137, 1, 177, 255, 224, 255, 128, 128, 128, 128, 128, + 253, 9, 248, 251, 207, 208, 255, 192, 128, 128, 128, 175, 13, 224, 243, 193, + 185, 249, 198, 255, 255, 128, 73, 17, 171, 221, 161, 179, 236, 167, 255, 234, + 128, 1, 95, 247, 253, 212, 183, 255, 255, 128, 128, 128, 239, 90, 244, 250, + 211, 209, 255, 255, 128, 128, 128, 155, 77, 195, 248, 188, 195, 255, 255, 128, + 128, 128, 1, 24, 239, 251, 218, 219, 255, 205, 128, 128, 128, 201, 51, 219, + 255, 196, 186, 128, 128, 128, 128, 128, 69, 46, 190, 239, 201, 218, 255, 228, + 128, 128, 128, 1, 191, 251, 255, 255, 128, 128, 128, 128, 128, 128, 223, 165, + 249, 255, 213, 255, 128, 128, 128, 128, 128, 141, 124, 248, 255, 255, 128, 128, + 128, 128, 128, 128, 1, 16, 248, 255, 255, 128, 128, 128, 128, 128, 128, 190, + 36, 230, 255, 236, 255, 128, 128, 128, 128, 128, 149, 1, 255, 128, 128, 128, + 128, 128, 128, 128, 128, 1, 226, 255, 128, 128, 128, 128, 128, 128, 128, 128, + 247, 192, 255, 128, 128, 128, 128, 128, 128, 128, 128, 240, 128, 255, 128, 128, + 128, 128, 128, 128, 128, 128, 1, 134, 252, 255, 255, 128, 128, 128, 128, 128, + 128, 213, 62, 250, 255, 255, 128, 128, 128, 128, 128, 128, 55, 93, 255, 128, + 128, 128, 128, 128, 128, 128, 128, 128, 128, 128, 128, 128, 128, 128, 128, 128, + 128, 128, 128, 128, 128, 128, 128, 128, 128, 128, 128, 128, 128, 128, 128, 128, + 128, 128, 128, 128, 128, 128, 128, 128, 202, 24, 213, 235, 186, 191, 220, 160, + 240, 175, 255, 126, 38, 182, 232, 169, 184, 228, 174, 255, 187, 128, 61, 46, + 138, 219, 151, 178, 240, 170, 255, 216, 128, 1, 112, 230, 250, 199, 191, 247, + 159, 255, 255, 128, 166, 109, 228, 252, 211, 215, 255, 174, 128, 128, 128, 39, + 77, 162, 232, 172, 180, 245, 178, 255, 255, 128, 1, 52, 220, 246, 198, 199, + 249, 220, 255, 255, 128, 124, 74, 191, 243, 183, 193, 250, 221, 255, 255, 128, + 24, 71, 130, 219, 154, 170, 243, 182, 255, 255, 128, 1, 182, 225, 249, 219, + 240, 255, 224, 128, 128, 128, 149, 150, 226, 252, 216, 205, 255, 171, 128, 128, + 128, 28, 108, 170, 242, 183, 194, 254, 223, 255, 255, 128, 1, 81, 230, 252, + 204, 203, 255, 192, 128, 128, 128, 123, 102, 209, 247, 188, 196, 255, 233, 128, + 128, 128, 20, 95, 153, 243, 164, 173, 255, 203, 128, 128, 128, 1, 222, 248, + 255, 216, 213, 128, 128, 128, 128, 128, 168, 175, 246, 252, 235, 205, 255, 255, + 128, 128, 128, 47, 116, 215, 255, 211, 212, 255, 255, 128, 128, 128, 1, 121, + 236, 253, 212, 214, 255, 255, 128, 128, 128, 141, 84, 213, 252, 201, 202, 255, + 219, 128, 128, 128, 42, 80, 160, 240, 162, 185, 255, 205, 128, 128, 128, 1, + 1, 255, 128, 128, 128, 128, 128, 128, 128, 128, 244, 1, 255, 128, 128, 128, + 128, 128, 128, 128, 128, 238, 1, 255, 128, 128, 128, 128, 128, 128, 128, 128, +]; + +/// `kf_bmode_prob` (RFC 6386), 900 entries. +pub const KF_BMODE_PROBS: [u8; 900] = [ + 231, 120, 48, 89, 115, 113, 120, 152, 112, 152, 179, 64, 126, 170, 118, 46, + 70, 95, 175, 69, 143, 80, 85, 82, 72, 155, 103, 56, 58, 10, 171, 218, + 189, 17, 13, 152, 144, 71, 10, 38, 171, 213, 144, 34, 26, 114, 26, 17, + 163, 44, 195, 21, 10, 173, 121, 24, 80, 195, 26, 62, 44, 64, 85, 170, + 46, 55, 19, 136, 160, 33, 206, 71, 63, 20, 8, 114, 114, 208, 12, 9, + 226, 81, 40, 11, 96, 182, 84, 29, 16, 36, 134, 183, 89, 137, 98, 101, + 106, 165, 148, 72, 187, 100, 130, 157, 111, 32, 75, 80, 66, 102, 167, 99, + 74, 62, 40, 234, 128, 41, 53, 9, 178, 241, 141, 26, 8, 107, 104, 79, + 12, 27, 217, 255, 87, 17, 7, 74, 43, 26, 146, 73, 166, 49, 23, 157, + 65, 38, 105, 160, 51, 52, 31, 115, 128, 87, 68, 71, 44, 114, 51, 15, + 186, 23, 47, 41, 14, 110, 182, 183, 21, 17, 194, 66, 45, 25, 102, 197, + 189, 23, 18, 22, 88, 88, 147, 150, 42, 46, 45, 196, 205, 43, 97, 183, + 117, 85, 38, 35, 179, 61, 39, 53, 200, 87, 26, 21, 43, 232, 171, 56, + 34, 51, 104, 114, 102, 29, 93, 77, 107, 54, 32, 26, 51, 1, 81, 43, + 31, 39, 28, 85, 171, 58, 165, 90, 98, 64, 34, 22, 116, 206, 23, 34, + 43, 166, 73, 68, 25, 106, 22, 64, 171, 36, 225, 114, 34, 19, 21, 102, + 132, 188, 16, 76, 124, 62, 18, 78, 95, 85, 57, 50, 48, 51, 193, 101, + 35, 159, 215, 111, 89, 46, 111, 60, 148, 31, 172, 219, 228, 21, 18, 111, + 112, 113, 77, 85, 179, 255, 38, 120, 114, 40, 42, 1, 196, 245, 209, 10, + 25, 109, 100, 80, 8, 43, 154, 1, 51, 26, 71, 88, 43, 29, 140, 166, + 213, 37, 43, 154, 61, 63, 30, 155, 67, 45, 68, 1, 209, 142, 78, 78, + 16, 255, 128, 34, 197, 171, 41, 40, 5, 102, 211, 183, 4, 1, 221, 51, + 50, 17, 168, 209, 192, 23, 25, 82, 125, 98, 42, 88, 104, 85, 117, 175, + 82, 95, 84, 53, 89, 128, 100, 113, 101, 45, 75, 79, 123, 47, 51, 128, + 81, 171, 1, 57, 17, 5, 71, 102, 57, 53, 41, 49, 115, 21, 2, 10, + 102, 255, 166, 23, 6, 38, 33, 13, 121, 57, 73, 26, 1, 85, 41, 10, + 67, 138, 77, 110, 90, 47, 114, 101, 29, 16, 10, 85, 128, 101, 196, 26, + 57, 18, 10, 102, 102, 213, 34, 20, 43, 117, 20, 15, 36, 163, 128, 68, + 1, 26, 138, 31, 36, 171, 27, 166, 38, 44, 229, 67, 87, 58, 169, 82, + 115, 26, 59, 179, 63, 59, 90, 180, 59, 166, 93, 73, 154, 40, 40, 21, + 116, 143, 209, 34, 39, 175, 57, 46, 22, 24, 128, 1, 54, 17, 37, 47, + 15, 16, 183, 34, 223, 49, 45, 183, 46, 17, 33, 183, 6, 98, 15, 32, + 183, 65, 32, 73, 115, 28, 128, 23, 128, 205, 40, 3, 9, 115, 51, 192, + 18, 6, 223, 87, 37, 9, 115, 59, 77, 64, 21, 47, 104, 55, 44, 218, + 9, 54, 53, 130, 226, 64, 90, 70, 205, 40, 41, 23, 26, 57, 54, 57, + 112, 184, 5, 41, 38, 166, 213, 30, 34, 26, 133, 152, 116, 10, 32, 134, + 75, 32, 12, 51, 192, 255, 160, 43, 51, 39, 19, 53, 221, 26, 114, 32, + 73, 255, 31, 9, 65, 234, 2, 15, 1, 118, 73, 88, 31, 35, 67, 102, + 85, 55, 186, 85, 56, 21, 23, 111, 59, 205, 45, 37, 192, 55, 38, 70, + 124, 73, 102, 1, 34, 98, 102, 61, 71, 37, 34, 53, 31, 243, 192, 69, + 60, 71, 38, 73, 119, 28, 222, 37, 68, 45, 128, 34, 1, 47, 11, 245, + 171, 62, 17, 19, 70, 146, 85, 55, 62, 70, 75, 15, 9, 9, 64, 255, + 184, 119, 16, 37, 43, 37, 154, 100, 163, 85, 160, 1, 63, 9, 92, 136, + 28, 64, 32, 201, 85, 86, 6, 28, 5, 64, 255, 25, 248, 1, 56, 8, + 17, 132, 137, 255, 55, 116, 128, 58, 15, 20, 82, 135, 57, 26, 121, 40, + 164, 50, 31, 137, 154, 133, 25, 35, 218, 51, 103, 44, 131, 131, 123, 31, + 6, 158, 86, 40, 64, 135, 148, 224, 45, 183, 128, 22, 26, 17, 131, 240, + 154, 14, 1, 209, 83, 12, 13, 54, 192, 255, 68, 47, 28, 45, 16, 21, + 91, 64, 222, 7, 1, 197, 56, 21, 39, 155, 60, 138, 23, 102, 213, 85, + 26, 85, 85, 128, 128, 32, 146, 171, 18, 11, 7, 63, 144, 171, 4, 4, + 246, 35, 27, 10, 146, 174, 171, 12, 26, 128, 190, 80, 35, 99, 180, 80, + 126, 54, 45, 85, 126, 47, 87, 176, 51, 41, 20, 32, 101, 75, 128, 139, + 118, 146, 116, 128, 85, 56, 41, 15, 176, 236, 85, 37, 9, 62, 146, 36, + 19, 30, 171, 255, 97, 27, 20, 71, 30, 17, 119, 118, 255, 17, 18, 138, + 101, 38, 60, 138, 55, 70, 43, 26, 142, 138, 45, 61, 62, 219, 1, 81, + 188, 64, 32, 41, 20, 117, 151, 142, 20, 21, 163, 112, 19, 12, 61, 195, + 128, 48, 4, 24, +]; + +/// `dc_qlookup` (RFC 6386), 128 entries. +pub const DC_QLOOKUP: [u16; 128] = [ + 4, 5, 6, 7, 8, 9, 10, 10, 11, 12, 13, 14, 15, 16, 17, 17, + 18, 19, 20, 20, 21, 21, 22, 22, 23, 23, 24, 25, 25, 26, 27, 28, + 29, 30, 31, 32, 33, 34, 35, 36, 37, 37, 38, 39, 40, 41, 42, 43, + 44, 45, 46, 46, 47, 48, 49, 50, 51, 52, 53, 54, 55, 56, 57, 58, + 59, 60, 61, 62, 63, 64, 65, 66, 67, 68, 69, 70, 71, 72, 73, 74, + 75, 76, 76, 77, 78, 79, 80, 81, 82, 83, 84, 85, 86, 87, 88, 89, + 91, 93, 95, 96, 98, 100, 101, 102, 104, 106, 108, 110, 112, 114, 116, 118, + 122, 124, 126, 128, 130, 132, 134, 136, 138, 140, 143, 145, 148, 151, 154, 157, +]; + +/// `ac_qlookup` (RFC 6386), 128 entries. +pub const AC_QLOOKUP: [u16; 128] = [ + 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15, 16, 17, 18, 19, + 20, 21, 22, 23, 24, 25, 26, 27, 28, 29, 30, 31, 32, 33, 34, 35, + 36, 37, 38, 39, 40, 41, 42, 43, 44, 45, 46, 47, 48, 49, 50, 51, + 52, 53, 54, 55, 56, 57, 58, 60, 62, 64, 66, 68, 70, 72, 74, 76, + 78, 80, 82, 84, 86, 88, 90, 92, 94, 96, 98, 100, 102, 104, 106, 108, + 110, 112, 114, 116, 119, 122, 125, 128, 131, 134, 137, 140, 143, 146, 149, 152, + 155, 158, 161, 164, 167, 170, 173, 177, 181, 185, 189, 193, 197, 201, 205, 209, + 213, 217, 221, 225, 229, 234, 239, 245, 249, 254, 259, 264, 269, 274, 279, 284, +]; + +/// `vp8_mode_contexts` (RFC 6386), 24 entries. +pub const MODE_CONTEXTS: [u8; 24] = [ + 7, 1, 1, 143, 14, 18, 14, 107, 135, 64, 57, 68, 60, 56, 128, 65, + 159, 134, 128, 34, 234, 188, 128, 28, +]; + +/// `sub_mv_ref_prob` (RFC 6386), 15 entries. +pub const SUB_MV_REF_PROBS: [u8; 15] = [ + 147, 136, 18, 106, 145, 1, 179, 121, 1, 223, 1, 34, 208, 1, 1, +]; + +/// `vp8_mv_update_probs` (RFC 6386), 38 entries. +pub const MV_UPDATE_PROBS: [u8; 38] = [ + 237, 246, 253, 253, 254, 254, 254, 254, 254, 254, 254, 254, 254, 254, 250, 250, + 252, 254, 254, 231, 243, 245, 253, 254, 254, 254, 254, 254, 254, 254, 254, 254, + 254, 251, 251, 254, 254, 254, +]; + +/// `default_mv_context` (RFC 6386), 38 entries. +pub const DEFAULT_MV_PROBS: [u8; 38] = [ + 162, 128, 225, 146, 172, 147, 214, 39, 156, 128, 129, 132, 75, 145, 178, 206, + 239, 254, 254, 164, 128, 204, 170, 119, 235, 140, 230, 228, 128, 130, 130, 74, + 148, 180, 203, 236, 254, 254, +]; + +/// `bmode_prob` (RFC 6386), 9 entries. +pub const BMODE_PROBS: [u8; 9] = [ + 120, 90, 79, 133, 87, 85, 80, 111, 151, +]; + +/// `coeff_bands` (RFC 6386), 16 entries. +pub const COEFF_BANDS: [u8; 16] = [ + 0, 1, 2, 3, 6, 4, 5, 6, 6, 6, 6, 6, 6, 6, 6, 7, +]; + +/// `kf_ymode_prob` (RFC 6386), 4 entries. +pub const KF_YMODE_PROBS: [u8; 4] = [ + 145, 156, 163, 128, +]; + +/// `kf_uv_mode_prob` (RFC 6386), 3 entries. +pub const KF_UV_MODE_PROBS: [u8; 3] = [ + 142, 114, 183, +]; + +/// `ymode_prob` (RFC 6386), 4 entries. +pub const YMODE_PROBS: [u8; 4] = [ + 112, 86, 140, 37, +]; + +/// `uv_mode_prob` (RFC 6386), 3 entries. +pub const UV_MODE_PROBS: [u8; 3] = [ + 162, 101, 204, +]; + +/// `mvpartition_probs` (RFC 6386), 3 entries. +pub const MVPARTITION_PROBS: [u8; 3] = [ + 110, 111, 150, +]; + +/// `BilinearFilters` (RFC 6386), 48 entries. +pub const BILINEAR_FILTERS: [i32; 48] = [ + 0, 0, 128, 0, 0, 0, 0, 0, 112, 16, 0, 0, 0, 0, 96, 32, + 0, 0, 0, 0, 80, 48, 0, 0, 0, 0, 64, 64, 0, 0, 0, 0, + 48, 80, 0, 0, 0, 0, 32, 96, 0, 0, 0, 0, 16, 112, 0, 0, +]; + +/// `filters` (RFC 6386), 48 entries. +pub const SIXTAP_FILTERS: [i32; 48] = [ + 0, 0, 128, 0, 0, 0, 0, -6, 123, 12, -1, 0, 2, -11, 108, 36, + -8, 1, 0, -9, 93, 50, -6, 0, 3, -16, 77, 77, -16, 3, 0, -6, + 50, 93, -9, 0, 1, -8, 36, 108, -11, 2, 0, -1, 12, 123, -6, 0, +]; + diff --git a/crates/vp8/src/tables.rs b/crates/vp8/src/tables.rs new file mode 100644 index 0000000000..f03963259d --- /dev/null +++ b/crates/vp8/src/tables.rs @@ -0,0 +1,121 @@ +//! Trees, enumerations and small tables of RFC 6386 that are not printed as `= { ... }` arrays +//! (the large tables are in `spec_tables.rs`). + +// Macroblock luma / chroma modes (8.2), in the specification's enumeration order. +pub const DC_PRED: u8 = 0; +pub const V_PRED: u8 = 1; +pub const H_PRED: u8 = 2; +pub const TM_PRED: u8 = 3; +pub const B_PRED: u8 = 4; + +// Subblock modes (11.2). +pub const B_DC_PRED: u8 = 0; +pub const B_TM_PRED: u8 = 1; +pub const B_VE_PRED: u8 = 2; +pub const B_HE_PRED: u8 = 3; +pub const B_LD_PRED: u8 = 4; +pub const B_RD_PRED: u8 = 5; +pub const B_VR_PRED: u8 = 6; +pub const B_VL_PRED: u8 = 7; +pub const B_HD_PRED: u8 = 8; +pub const B_HU_PRED: u8 = 9; + +// Inter modes (16.2), numbered after the intra modes as in the specification. +pub const MV_NEAREST: u8 = 5; +pub const MV_NEAR: u8 = 6; +pub const MV_ZERO: u8 = 7; +pub const MV_NEW: u8 = 8; +pub const MV_SPLIT: u8 = 9; + +// Split MV subblock modes (16.4), relative numbering. +pub const LEFT_4X4: u8 = 0; +pub const ABOVE_4X4: u8 = 1; +pub const ZERO_4X4: u8 = 2; +pub const NEW_4X4: u8 = 3; + +/// `ymode_tree` (8.2). +pub const YMODE_TREE: [i8; 8] = [-(DC_PRED as i8), 2, 4, 6, -(V_PRED as i8), -(H_PRED as i8), -(TM_PRED as i8), -(B_PRED as i8)]; +/// `kf_ymode_tree` (8.2). +pub const KF_YMODE_TREE: [i8; 8] = [-(B_PRED as i8), 2, 4, 6, -(DC_PRED as i8), -(V_PRED as i8), -(H_PRED as i8), -(TM_PRED as i8)]; +/// `uv_mode_tree` (8.2). +pub const UV_MODE_TREE: [i8; 6] = [-(DC_PRED as i8), 2, -(V_PRED as i8), 4, -(H_PRED as i8), -(TM_PRED as i8)]; +/// `bmode_tree` (11.2). +pub const BMODE_TREE: [i8; 18] = [ + -(B_DC_PRED as i8), + 2, + -(B_TM_PRED as i8), + 4, + -(B_VE_PRED as i8), + 6, + 8, + 12, + -(B_HE_PRED as i8), + 10, + -(B_RD_PRED as i8), + -(B_VR_PRED as i8), + -(B_LD_PRED as i8), + 14, + -(B_VL_PRED as i8), + 16, + -(B_HD_PRED as i8), + -(B_HU_PRED as i8), +]; +/// `mb_segment_tree` (10). +pub const SEGMENT_TREE: [i8; 6] = [2, 4, 0, -1, -2, -3]; +/// `mv_ref_tree` (16.2), values relative to `MV_NEAREST`. +pub const MV_REF_TREE: [i8; 8] = + [-((MV_ZERO - MV_NEAREST) as i8), 2, 0, 4, -((MV_NEAR - MV_NEAREST) as i8), 6, -((MV_NEW - MV_NEAREST) as i8), -((MV_SPLIT - MV_NEAREST) as i8)]; + +// Split partitionings (16.4). +pub const SPLIT_TOP_BOTTOM: u8 = 0; +pub const SPLIT_LEFT_RIGHT: u8 = 1; +pub const SPLIT_QUARTERS: u8 = 2; +pub const SPLIT_16: u8 = 3; +/// `mvpartition_tree` (16.4). +pub const MVPARTITION_TREE: [i8; 6] = [-(SPLIT_16 as i8), 2, -(SPLIT_QUARTERS as i8), 4, -(SPLIT_TOP_BOTTOM as i8), -(SPLIT_LEFT_RIGHT as i8)]; +/// `sub_mv_ref_tree` (16.4). +pub const SUB_MV_REF_TREE: [i8; 6] = [-(LEFT_4X4 as i8), 2, -(ABOVE_4X4 as i8), 4, -(ZERO_4X4 as i8), -(NEW_4X4 as i8)]; +/// The partition of each Y subblock, per partitioning (the layouts listed with the MVpartition +/// enumeration, 16.4). +pub const SPLIT_MAPS: [[u8; 16]; 4] = [ + [0, 0, 0, 0, 0, 0, 0, 0, 1, 1, 1, 1, 1, 1, 1, 1], + [0, 0, 1, 1, 0, 0, 1, 1, 0, 0, 1, 1, 0, 0, 1, 1], + [0, 0, 1, 1, 0, 0, 1, 1, 2, 2, 3, 3, 2, 2, 3, 3], + [0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15], +]; +/// Number of partitions per partitioning. +pub const SPLIT_COUNTS: [usize; 4] = [2, 2, 4, 16]; + +/// `small_mvtree` (17.1). +pub const SMALL_MV_TREE: [i8; 14] = [2, 8, 4, 6, 0, -1, -2, -3, 10, 12, -4, -5, -6, -7]; +// Offsets in an MV_CONTEXT (17.1). +pub const MVP_IS_SHORT: usize = 0; +pub const MVP_SIGN: usize = 1; +pub const MVP_SHORT: usize = 2; +pub const MVP_BITS: usize = 9; + +// DCT tokens (13.2). +pub const DCT_0: u8 = 0; +pub const DCT_4: u8 = 4; +pub const DCT_CAT1: u8 = 5; +pub const DCT_EOB: u8 = 11; +/// `coeff_tree` (13.2). +pub const COEFF_TREE: [i8; 22] = [-(DCT_EOB as i8), 2, -(DCT_0 as i8), 4, -1, 6, 8, 12, -2, 10, -3, -4, 14, 16, -5, -6, 18, 20, -7, -8, -9, -10]; +/// `Pcat1` ... `Pcat6` (13.2), without the terminating zeros. +pub const PCAT: [&[u8]; 6] = [ + &[159], + &[165, 145], + &[173, 148, 140], + &[176, 155, 140, 135], + &[180, 157, 141, 134, 130], + &[254, 254, 243, 230, 196, 177, 153, 140, 133, 130, 129], +]; +/// `categoryBase` (13.3). +pub const CAT_BASE: [i32; 6] = [5, 7, 11, 19, 35, 67]; + +/// Coefficient scan order (13): position `i` of the token stream is coefficient +/// `ZIGZAG[i]` of the 4x4 block in raster order — the usual 4x4 zig-zag. +pub const ZIGZAG: [usize; 16] = [0, 1, 4, 8, 5, 2, 3, 6, 9, 12, 13, 10, 7, 11, 14, 15]; + +/// Probabilities of the segment tree when not updated (9.3). +pub const DEFAULT_SEGMENT_PROB: u8 = 255; diff --git a/crates/vp8/src/transform.rs b/crates/vp8/src/transform.rs new file mode 100644 index 0000000000..4c545ad5a1 --- /dev/null +++ b/crates/vp8/src/transform.rs @@ -0,0 +1,94 @@ +//! Inverse transforms (14.3, 14.4). Inputs, the intermediate signal and the outputs are 16-bit +//! signed integers, as the specification requires; the casts to `i16` reproduce that. + +const COSPI8SQRT2MINUS1: i32 = 20091; +const SINPI8SQRT2: i32 = 35468; + +/// Inverse WHT of the Y2 block (14.3): `input` in raster order; `out[i]` becomes the DC of +/// Y subblock `i`. +pub fn inverse_wht(input: &[i16; 16]) -> [i16; 16] { + let mut tmp = [0i16; 16]; + for i in 0..4 { + let ip = |k: usize| i32::from(input[i + 4 * k]); + let a1 = ip(0) + ip(3); + let b1 = ip(1) + ip(2); + let c1 = ip(1) - ip(2); + let d1 = ip(0) - ip(3); + tmp[i] = (a1 + b1) as i16; + tmp[i + 4] = (c1 + d1) as i16; + tmp[i + 8] = (a1 - b1) as i16; + tmp[i + 12] = (d1 - c1) as i16; + } + let mut out = [0i16; 16]; + for i in 0..4 { + let ip = |k: usize| i32::from(tmp[4 * i + k]); + let a1 = ip(0) + ip(3); + let b1 = ip(1) + ip(2); + let c1 = ip(1) - ip(2); + let d1 = ip(0) - ip(3); + out[4 * i] = ((a1 + b1 + 3) >> 3) as i16; + out[4 * i + 1] = ((c1 + d1 + 3) >> 3) as i16; + out[4 * i + 2] = ((a1 - b1 + 3) >> 3) as i16; + out[4 * i + 3] = ((d1 - c1 + 3) >> 3) as i16; + } + out +} + +/// Inverse DCT (14.4) of a 4x4 block in raster order, returning the residue in raster order. +pub fn inverse_dct(input: &[i16; 16]) -> [i16; 16] { + let mul_s = |x: i32| (x * SINPI8SQRT2) >> 16; + let mul_c = |x: i32| x + ((x * COSPI8SQRT2MINUS1) >> 16); + let mut tmp = [0i16; 16]; + for i in 0..4 { + let ip = |k: usize| i32::from(input[i + 4 * k]); + let a1 = ip(0) + ip(2); + let b1 = ip(0) - ip(2); + let c1 = mul_s(ip(1)) - mul_c(ip(3)); + let d1 = mul_c(ip(1)) + mul_s(ip(3)); + tmp[i] = (a1 + d1) as i16; + tmp[i + 12] = (a1 - d1) as i16; + tmp[i + 4] = (b1 + c1) as i16; + tmp[i + 8] = (b1 - c1) as i16; + } + let mut out = [0i16; 16]; + for i in 0..4 { + let ip = |k: usize| i32::from(tmp[4 * i + k]); + let a1 = ip(0) + ip(2); + let b1 = ip(0) - ip(2); + let c1 = mul_s(ip(1)) - mul_c(ip(3)); + let d1 = mul_c(ip(1)) + mul_s(ip(3)); + out[4 * i] = ((a1 + d1 + 4) >> 3) as i16; + out[4 * i + 3] = ((a1 - d1 + 4) >> 3) as i16; + out[4 * i + 1] = ((b1 + c1 + 4) >> 3) as i16; + out[4 * i + 2] = ((b1 - c1 + 4) >> 3) as i16; + } + out +} + +/// Add `residue` to the 4x4 block at `pos` of a plane (14.5), saturating to 8 bits. +#[inline] +pub fn add_residue(plane: &mut [u8], stride: usize, pos: usize, residue: &[i16; 16]) { + for r in 0..4 { + let Some(row) = plane.get_mut(pos + r * stride..pos + r * stride + 4) else { return }; + for (c, px) in row.iter_mut().enumerate() { + *px = (i32::from(*px) + i32::from(residue[4 * r + c])).clamp(0, 255) as u8; + } + } +} + +#[cfg(test)] +mod tests { + use super::*; + + #[test] + fn dc_only_blocks_are_flat() { + for dc in [-2048i16, -100, -5, -4, -3, 0, 3, 4, 5, 100, 2047] { + let mut b = [0i16; 16]; + b[0] = dc; + let out = inverse_dct(&b); + assert!(out.iter().all(|&v| i32::from(v) == (i32::from(dc) + 4) >> 3), "dct dc {dc}"); + let out = inverse_wht(&b); + assert!(out.iter().all(|&v| i32::from(v) == (i32::from(dc) + 3) >> 3), "wht dc {dc}"); + } + } +} diff --git a/crates/vp8/tests/fuzz_hostile.rs b/crates/vp8/tests/fuzz_hostile.rs new file mode 100644 index 0000000000..3980a6567d --- /dev/null +++ b/crates/vp8/tests/fuzz_hostile.rs @@ -0,0 +1,158 @@ +//! Hostile-input robustness: truncation, header-region sweeps and random mutation of small +//! ffmpeg-generated VP8 streams (frame by frame) must never panic or run away. Skips without ffmpeg. + +use std::panic::{AssertUnwindSafe, catch_unwind}; +use std::process::{Command, Stdio}; +use std::sync::atomic::{AtomicUsize, Ordering}; + +use craft_vp8::Decoder; + +struct Rng(u64); +impl Rng { + fn next(&mut self) -> u64 { + self.0 ^= self.0 << 13; + self.0 ^= self.0 >> 7; + self.0 ^= self.0 << 17; + self.0 + } + fn below(&mut self, n: usize) -> usize { + (self.next() % n.max(1) as u64) as usize + } +} + +/// (name, encoder args, pix_fmt) +const SEEDS: &[(&str, &str, &str)] = &[ + ("vp8_fz_default", "-c:v libvpx -g 4 -b:v 300k", "yuv420p"), + ("vp8_fz_partitions", "-c:v libvpx -g 6 -b:v 300k -slices 8 -auto-alt-ref 0 -lag-in-frames 0 -error-resilient 1", "yuv420p"), +]; + +fn ivf_frames(data: &[u8]) -> Vec> { + let mut out = Vec::new(); + let Some(h) = data.get(6..8) else { return out }; + let mut pos = usize::from(u16::from_le_bytes([h[0], h[1]])); + while let Some(hd) = data.get(pos..pos + 12) { + let size = u32::from_le_bytes([hd[0], hd[1], hd[2], hd[3]]) as usize; + pos += 12; + let end = pos.saturating_add(size).min(data.len()); + out.push(data[pos..end].to_vec()); + pos = end; + } + out +} + +fn make(ff: &std::path::Path, name: &str, args: &str, pix: &str) -> Option>> { + let out = craft_testkit::fixtures_dir("vp8").join(format!("{name}.ivf")); + let p = craft_testkit::fixtures::generate(&out, |tmp| { + let a: Vec<&str> = args.split_whitespace().collect(); + matches!( + Command::new(ff) + .args(["-hide_banner", "-loglevel", "error", "-y", "-f", "lavfi", "-i", "testsrc2=size=64x48:rate=25", "-frames:v", "6", "-pix_fmt", pix]) + .args(a) + .args(["-f", "ivf"]) + .arg(tmp) + .stdin(Stdio::null()) + .status(), + Ok(s) if s.success() + ) + })?; + Some(ivf_frames(&std::fs::read(p).ok()?)) +} + +static PANICS: AtomicUsize = AtomicUsize::new(0); + +/// Decoder worker threads may catch panics and turn them into errors; count them so they still fail the test. +fn count_panics() { + static ONCE: std::sync::Once = std::sync::Once::new(); + ONCE.call_once(|| { + std::panic::set_hook(Box::new(|info| { + PANICS.fetch_add(1, Ordering::SeqCst); + eprintln!("PANIC: {info}"); + })); + }); +} + +fn run_all(frames: &[Vec], label: &str) { + craft_testkit::set_progress(label); + count_panics(); + let before = PANICS.load(Ordering::SeqCst); + let res = catch_unwind(AssertUnwindSafe(|| { + for _ in 0..1 { + let mut dec = Decoder::new(); + for (i, f) in frames.iter().enumerate() { + let _ = dec.decode(f, i as i64); + } + } + })); + assert!(res.is_ok() && PANICS.load(Ordering::SeqCst) == before, "panic: {label}"); +} + +/// Random-mutation rounds per seed (raise with FILMCRAFT_FUZZ_ITERS for a long hunt). +fn iters() -> usize { + std::env::var("FILMCRAFT_FUZZ_ITERS").ok().and_then(|s| s.parse().ok()).unwrap_or(300) +} + +/// Seconds the whole sweep may take before it counts as a hang (override: FILMCRAFT_HOSTILE_TIMEOUT_SECS). +const DEADLINE_SECS: u64 = 600; + +#[test] +fn hostile_streams_never_panic() { + craft_testkit::with_deadline(DEADLINE_SECS, sweep); +} + +fn sweep() { + let Some(ff) = craft_testkit::ffmpeg_or_skip("VP8 fuzz") else { return }; + for &(name, args, pix) in SEEDS { + if !craft_testkit::ffmpeg_has_encoder(&ff, "libvpx") { + craft_testkit::skip("VP8 fuzz", &format!("this ffmpeg has no libvpx encoder (seed {name})")); + continue; + } + let Some(seed) = make(&ff, name, args, pix) else { + panic!("ffmpeg has libvpx but could not encode the {name} seed"); + }; + if seed.is_empty() { + craft_testkit::skip("VP8 fuzz", &format!("the {name} seed has no frames")); + continue; + } + // truncate each of the first frames at every length + for fi in 0..seed.len().min(3) { + let len = seed[fi].len(); + for n in (0..len).step_by((len / 200).max(1)) { + let mut d = seed.clone(); + d[fi].truncate(n); + run_all(&d, &format!("{name} frame {fi} truncate {n}")); + } + } + // header region of every frame: every byte to a few values, every bit + for fi in 0..seed.len() { + for p in 0..seed[fi].len().min(96) { + for v in [0u8, 0xff, 0x80, 0x01, 0x7f] { + let mut d = seed.clone(); + d[fi][p] = v; + run_all(&d, &format!("{name} frame {fi} byte {p}={v:#x}")); + } + for b in 0..8 { + let mut d = seed.clone(); + d[fi][p] ^= 1 << b; + run_all(&d, &format!("{name} frame {fi} flip {p}.{b}")); + } + } + } + let mut rng = Rng(0x9e3779b97f4a7c15 ^ name.len() as u64); + for i in 0..iters() { + let mut d = seed.clone(); + for _ in 0..1 + rng.below(6) { + let fi = rng.below(d.len()); + if d[fi].is_empty() { + continue; + } + let p = rng.below(d[fi].len()); + if rng.below(2) == 0 { + d[fi][p] ^= 1 << rng.below(8); + } else { + d[fi][p] = rng.next() as u8; + } + } + run_all(&d, &format!("{name} random {i}")); + } + } +} diff --git a/crates/vp8/tests/it/common/mod.rs b/crates/vp8/tests/it/common/mod.rs new file mode 100644 index 0000000000..f36f3be1a9 --- /dev/null +++ b/crates/vp8/tests/it/common/mod.rs @@ -0,0 +1,205 @@ +//! Test fixture generation (ffmpeg + libvpx as external encoder and oracle), a tiny IVF reader +//! and the decode helpers. +#![allow(dead_code)] + +use craft_vp8::{Decoder, Picture}; +use std::path::{Path, PathBuf}; +use std::process::Command; + +/// One encoder configuration of the fixture matrix. +pub struct Fixture { + pub name: &'static str, + /// lavfi source (without size / rate). + pub source: &'static str, + pub width: u32, + pub height: u32, + pub frames: u32, + /// libvpx arguments; a leading "2pass" encodes in two passes. + pub args: &'static [&'static str], +} + +macro_rules! fx { + ($name:expr, $src:expr, $w:expr, $h:expr, $n:expr, [$($a:expr),* $(,)?]) => { + Fixture { name: $name, source: $src, width: $w, height: $h, frames: $n, args: &[$($a),*] } + }; +} + +pub const FIXTURES: &[Fixture] = &[ + fx!("default", "testsrc2", 352, 288, 20, ["-b:v", "600k"]), + fx!("odd_size", "testsrc2", 177, 99, 12, ["-b:v", "300k"]), + fx!("tiny", "testsrc2", 33, 17, 10, ["-b:v", "50k"]), + fx!("intra_only", "mandelbrot", 352, 288, 6, ["-g", "1", "-b:v", "2M"]), + fx!("noise_high_rate", "cellauto", 352, 288, 10, ["-b:v", "4M"]), + fx!("near_lossless", "testsrc2", 176, 144, 8, ["-qmin", "0", "-qmax", "0", "-b:v", "20M"]), + fx!("coarse", "mandelbrot", 352, 288, 12, ["-qmin", "60", "-qmax", "63", "-crf", "62", "-b:v", "100k"]), + fx!("partitions8", "testsrc2", 640, 360, 8, ["-b:v", "1M", "-slices", "8"]), + fx!("altref_2pass", "mandelbrot", 352, 288, 30, ["2pass", "-b:v", "500k", "-auto-alt-ref", "1", "-lag-in-frames", "16"]), + fx!("sharpness7", "testsrc2", 352, 288, 10, ["-b:v", "300k", "-sharpness", "7"]), + fx!("version1_bilinear_simple", "testsrc2", 352, 288, 12, ["-b:v", "500k", "-profile:v", "1"]), + fx!("version2", "testsrc2", 352, 288, 12, ["-b:v", "500k", "-profile:v", "2"]), + fx!("version3_fullpixel", "mandelbrot", 352, 288, 12, ["-b:v", "500k", "-profile:v", "3"]), + fx!("realtime_segments", "testsrc2", 352, 288, 20, ["-b:v", "300k", "-deadline", "realtime", "-cpu-used", "8", "-error-resilient", "1"]), + fx!("screen", "smptehdbars", 352, 288, 12, ["-b:v", "200k", "-screen-content-mode", "1"]), + fx!( + "temporal_layers", + "testsrc2", + 352, + 288, + 20, + [ + "-b:v", + "800k", + "-deadline", + "realtime", + "-cpu-used", + "8", + // error resilient: frames keep their probability updates to themselves, so the + // enhancement layer can be dropped + "-error-resilient", + "1", + "-ts-parameters", + "ts_number_layers=2:ts_target_bitrate=400,800:ts_rate_decimator=2,1:ts_periodicity=2:ts_layer_id=0,1:ts_layering_mode=2" + ] + ), + fx!("hd_1080p", "testsrc2", 1920, 1080, 3, ["-b:v", "4M", "-deadline", "realtime", "-cpu-used", "8"]), +]; + +pub fn fixture(name: &str) -> &'static Fixture { + FIXTURES.iter().find(|f| f.name == name).unwrap_or_else(|| panic!("unknown fixture {name}")) +} + +pub fn ffmpeg() -> Option { + for p in ["/opt/homebrew/bin/ffmpeg", "/usr/local/bin/ffmpeg", "/usr/bin/ffmpeg"] { + if Path::new(p).exists() { + return Some(PathBuf::from(p)); + } + } + None +} + +pub fn fixtures_dir() -> PathBuf { + let d = Path::new(env!("CARGO_MANIFEST_DIR")).join("../../target/fixtures/vp8-tests"); + std::fs::create_dir_all(&d).expect("create fixtures dir"); + d +} + +/// Unique suffix for temporary files: tests run on several threads and may generate the same +/// fixture concurrently. +pub fn unique() -> String { + static N: std::sync::atomic::AtomicU32 = std::sync::atomic::AtomicU32::new(0); + let n = N.fetch_add(1, std::sync::atomic::Ordering::Relaxed); + format!("{}-{n}", std::process::id()) +} + +pub fn run(cmd: &mut Command) -> bool { + match cmd.output() { + Ok(o) if o.status.success() => true, + Ok(o) => { + eprintln!("command failed: {cmd:?}\n{}", String::from_utf8_lossy(&o.stderr)); + false + } + Err(e) => { + eprintln!("failed to run {cmd:?}: {e}"); + false + } + } +} + +/// Generate (if needed) the fixture stream and its reference decode by libvpx through ffmpeg. +/// `None` (the test skips) without ffmpeg or its libvpx encoder. +pub fn ensure(f: &Fixture) -> Option<(PathBuf, PathBuf)> { + let Some(ff) = ffmpeg() else { + eprintln!("SKIP {}: ffmpeg not found", f.name); + return None; + }; + let dir = fixtures_dir(); + // one generation at a time, across test processes too: tests share fixtures, and realtime + // libvpx encodes differ from run to run, so a stream and its reference decode must come from + // the same encode + // a lock failure is a broken environment, not a missing tool: fail rather than skip + let lock = std::fs::File::create(dir.join("vp8.generate.lock")).expect("create the fixture lock file"); + lock.lock().expect("lock the fixture lock file"); + let ivf = dir.join(format!("{}.ivf", f.name)); + let yuv = dir.join(format!("{}.yuv", f.name)); + if !ivf.exists() { + let tmp = dir.join(format!("{}.tmp.{}.ivf", f.name, unique())); + // the source at an even size, scaled to the fixture's (possibly odd) size + let src = format!( + "{}=size={}x{}:rate=25,scale={}:{},format=yuv420p", + f.source, + f.width.next_multiple_of(2), + f.height.next_multiple_of(2), + f.width, + f.height + ); + let two_pass = f.args.first() == Some(&"2pass"); + let args: Vec<&str> = f.args.iter().copied().filter(|a| *a != "2pass").collect(); + let log = dir.join(format!("{}.{}.passlog", f.name, unique())); + let enc = |pass: Option| { + let mut c = Command::new(&ff); + c.args(["-hide_banner", "-loglevel", "error", "-y", "-f", "lavfi", "-i", &src]); + c.args(["-frames:v", &f.frames.to_string(), "-c:v", "libvpx"]); + c.args(&args); + if let Some(p) = pass { + c.args(["-pass", &p.to_string(), "-passlogfile"]).arg(&log); + } + if pass == Some(1) { + c.args(["-f", "null", "-"]); + } else { + c.args(["-f", "ivf"]).arg(&tmp); + } + c + }; + if two_pass && !run(&mut enc(Some(1))) { + eprintln!("SKIP {}: encode failed (ffmpeg without libvpx?)", f.name); + return None; + } + if !run(&mut enc(two_pass.then_some(2))) { + eprintln!("SKIP {}: encode failed (ffmpeg without libvpx?)", f.name); + return None; + } + if std::fs::rename(&tmp, &ivf).is_err() && !ivf.exists() { + return None; + } + } + if !yuv.exists() { + let tmp = dir.join(format!("{}.tmp.{}.yuv", f.name, unique())); + let mut c = Command::new(&ff); + c.args(["-hide_banner", "-loglevel", "error", "-y", "-c:v", "libvpx", "-i"]).arg(&ivf); + c.args(["-fps_mode", "passthrough", "-f", "rawvideo", "-pix_fmt", "yuv420p"]).arg(&tmp); + if !run(&mut c) { + eprintln!("SKIP {}: reference decode failed", f.name); + return None; + } + if std::fs::rename(&tmp, &yuv).is_err() && !yuv.exists() { + return None; + } + } + Some((ivf, yuv)) +} + +/// Frames of an IVF file. +pub fn ivf_frames(data: &[u8]) -> Vec<&[u8]> { + let mut out = Vec::new(); + let Some(h) = data.get(6..8) else { return out }; + let mut pos = usize::from(u16::from_le_bytes([h[0], h[1]])); + while pos + 12 <= data.len() { + let size = u32::from_le_bytes([data[pos], data[pos + 1], data[pos + 2], data[pos + 3]]) as usize; + pos += 12; + let end = (pos + size).min(data.len()); + out.push(&data[pos..end]); + pos = end; + } + out +} + +/// Decode every frame; the pictures in display order. +pub fn decode_all(frames: &[&[u8]]) -> Vec { + let mut dec = Decoder::new(); + frames.iter().enumerate().filter_map(|(i, f)| dec.decode(f, i as i64).expect("decode")).collect() +} + +/// A picture as I420 bytes. +pub fn i420(p: &Picture) -> Vec { + [p.y.as_slice(), &p.u, &p.v].concat() +} diff --git a/crates/vp8/tests/it/conformance.rs b/crates/vp8/tests/it/conformance.rs new file mode 100644 index 0000000000..9ce9a72200 --- /dev/null +++ b/crates/vp8/tests/it/conformance.rs @@ -0,0 +1,91 @@ +//! Bit-exactness against libvpx (through ffmpeg) on generated fixtures covering every coding +//! tool: skipped when ffmpeg or its libvpx encoder is absent. + +use crate::common; + +fn check(name: &str) { + let f = common::fixture(name); + let Some((ivf, yuv)) = common::ensure(f) else { return }; + let data = std::fs::read(&ivf).unwrap(); + let reference = std::fs::read(&yuv).unwrap(); + let pictures = common::decode_all(&common::ivf_frames(&data)); + let (w, h) = (f.width as usize, f.height as usize); + let size = w * h + 2 * w.div_ceil(2) * h.div_ceil(2); + assert_eq!(pictures.len(), reference.len() / size, "{name}: picture count"); + for (i, p) in pictures.iter().enumerate() { + assert_eq!((p.width as usize, p.height as usize), (w, h), "{name}: size of picture {i}"); + assert!(common::i420(p) == reference[i * size..(i + 1) * size], "{name}: picture {i} differs from libvpx"); + } +} + +macro_rules! fixture_tests { + ($($name:ident),* $(,)?) => { + $( + #[test] + fn $name() { + check(stringify!($name)); + } + )* + }; +} + +fixture_tests!( + default, + odd_size, + tiny, + intra_only, + noise_high_rate, + near_lossless, + coarse, + partitions8, + altref_2pass, + sharpness7, + version1_bilinear_simple, + version2, + version3_fullpixel, + realtime_segments, + screen, + temporal_layers, + hd_1080p, +); + +/// Hidden (altref) frames produce no picture, and leaving out the frames the decoder calls +/// disposable changes none of the other pictures. +#[test] +fn hidden_and_disposable_frames() { + for name in ["altref_2pass", "temporal_layers"] { + let f = common::fixture(name); + let Some((ivf, _)) = common::ensure(f) else { return }; + let data = std::fs::read(&ivf).unwrap(); + let frames = common::ivf_frames(&data); + // every picture, and which frames could be left out (asked right before each is decoded) + let mut dec = craft_vp8::Decoder::new(); + let mut all = Vec::new(); + let mut disposable = Vec::new(); + for (i, f) in frames.iter().enumerate() { + disposable.push(dec.is_disposable(f)); + if let Some(p) = dec.decode(f, i as i64).unwrap() { + all.push((p.pts, common::i420(&p))); + } + } + let stats = dec.stats(); + assert_eq!(stats.frames as usize, frames.len()); + assert_eq!(all.len() + stats.hidden_frames as usize, frames.len(), "{name}: one picture per shown frame"); + if name == "altref_2pass" { + assert!(stats.hidden_frames > 0, "{name}: has hidden altref frames"); + } + if name == "temporal_layers" { + assert!(disposable.iter().filter(|&&d| d).count() >= frames.len() / 3, "{name}: the enhancement layer is disposable: {disposable:?}"); + } + let mut dec = craft_vp8::Decoder::new(); + for (i, f) in frames.iter().enumerate() { + if disposable[i] { + continue; + } + if let Some(p) = dec.decode(f, i as i64).unwrap() { + let reference = all.iter().find(|(pts, _)| *pts == p.pts).expect("picture of a shown frame"); + assert!(common::i420(&p) == reference.1, "{name}: picture {} changed after leaving out disposable frames", p.pts); + } + } + } +} diff --git a/crates/vp8/tests/it/main.rs b/crates/vp8/tests/it/main.rs new file mode 100644 index 0000000000..ffc58b90c7 --- /dev/null +++ b/crates/vp8/tests/it/main.rs @@ -0,0 +1,4 @@ +mod common; +mod conformance; +mod robustness; +mod vectors; diff --git a/crates/vp8/tests/it/robustness.rs b/crates/vp8/tests/it/robustness.rs new file mode 100644 index 0000000000..f56b31e022 --- /dev/null +++ b/crates/vp8/tests/it/robustness.rs @@ -0,0 +1,106 @@ +//! Damaged, truncated, reordered and random input must never panic (errors are fine). + +use crate::common; + +use craft_vp8::{Decoder, Error}; + +fn xorshift(seed: &mut u64) -> u64 { + *seed ^= *seed << 13; + *seed ^= *seed >> 7; + *seed ^= *seed << 17; + *seed +} + +fn decode_all(frames: &[Vec]) { + let mut dec = Decoder::new(); + for (i, f) in frames.iter().enumerate() { + let _ = dec.is_disposable(f); + let _ = dec.decode(f, i as i64); + let _ = craft_vp8::keyframe_info(f); + } +} + +#[test] +fn corrupted_streams_do_not_panic() { + for name in ["default", "altref_2pass", "partitions8", "realtime_segments", "version3_fullpixel", "odd_size"] { + let f = common::fixture(name); + let Some((ivf, _)) = common::ensure(f) else { return }; + let data = std::fs::read(ivf).unwrap(); + let frames: Vec> = common::ivf_frames(&data).into_iter().map(|f| f.to_vec()).collect(); + let mut seed = 0x9e37_79b9_7f4a_7c15u64 ^ name.len() as u64; + for round in 0..12 { + let mut d = frames.clone(); + for fr in d.iter_mut() { + if fr.is_empty() { + continue; + } + match round % 4 { + 0 => { + for _ in 0..1 + round { + let i = (xorshift(&mut seed) as usize) % fr.len(); + fr[i] ^= 1 << (xorshift(&mut seed) % 8); + } + } + 1 => { + let n = (xorshift(&mut seed) as usize) % (fr.len() + 1); + fr.truncate(n); + } + 2 => { + let i = (xorshift(&mut seed) as usize) % fr.len().min(24); + fr[i] = xorshift(&mut seed) as u8; + } + // frames left intact; their order is shuffled below + _ => {} + } + } + if round % 4 == 3 { + for i in (1..d.len()).rev() { + let j = (xorshift(&mut seed) as usize) % (i + 1); + d.swap(i, j); + } + } + decode_all(&d); + } + } +} + +#[test] +fn random_bytes_and_tiny_frames_do_not_panic() { + let mut seed = 42u64; + let mut dec = Decoder::new(); + for len in 0..200 { + let f: Vec = (0..len).map(|_| xorshift(&mut seed) as u8).collect(); + let _ = dec.decode(&f, len as i64); + } + // a key frame header claiming a large size followed by garbage + let mut key = vec![0u8; 64]; + key[0] = 0x10; // key frame, version 0, shown, first partition 0 bytes + key[3..6].copy_from_slice(&[0x9d, 0x01, 0x2a]); + key[6..8].copy_from_slice(&1920u16.to_le_bytes()); + key[8..10].copy_from_slice(&1080u16.to_le_bytes()); + assert!(dec.decode(&key, 0).is_err(), "empty first partition must not reconstruct a frame"); + + // A header can declare nearly 16K square dimensions with just ten bytes. It must be rejected + // before the macroblock map and reference planes are allocated. + key[0] = 0x30; // one-byte first partition + key[10] = 0; + key[6..8].copy_from_slice(&16_383u16.to_le_bytes()); + key[8..10].copy_from_slice(&16_383u16.to_le_bytes()); + assert!(dec.decode(&key, 0).is_err(), "oversized key frame must be rejected"); +} + +#[test] +fn inter_frame_without_key_frame_is_an_error() { + let f = common::fixture("default"); + let Some((ivf, _)) = common::ensure(f) else { return }; + let data = std::fs::read(ivf).unwrap(); + let frames = common::ivf_frames(&data); + let mut dec = Decoder::new(); + assert!(matches!(dec.decode(frames[1], 1), Err(Error::MissingReference(_)))); + // and decoding works from the next key frame on; after a reset again only from a key frame + assert!(dec.decode(frames[0], 0).unwrap().is_some()); + assert!(dec.decode(frames[1], 1).unwrap().is_some()); + dec.reset(); + assert!(matches!(dec.decode(frames[2], 2), Err(Error::MissingReference(_)))); + assert!(craft_vp8::is_keyframe(frames[0]) && !craft_vp8::is_keyframe(frames[1])); +} diff --git a/crates/vp8/tests/it/vectors.rs b/crates/vp8/tests/it/vectors.rs new file mode 100644 index 0000000000..74d8a6bcbc --- /dev/null +++ b/crates/vp8/tests/it/vectors.rs @@ -0,0 +1,77 @@ +//! The libvpx VP8 test vectors (`vp80-0*.ivf` with their `.md5` files of per-frame I420 MD5s), +//! when present in `target/fixtures/vp8/conformance` or `$VP8_TEST_VECTORS`. They are not +//! downloaded by the tests; fetch them from +//! `https://storage.googleapis.com/downloads.webmproject.org/test_data/libvpx/`. + +use crate::common; + +/// MD5 (RFC 1321), for comparing with the vectors' checksums. +fn md5(data: &[u8]) -> String { + let s: [u32; 64] = [ + 7, 12, 17, 22, 7, 12, 17, 22, 7, 12, 17, 22, 7, 12, 17, 22, 5, 9, 14, 20, 5, 9, 14, 20, 5, 9, 14, 20, 5, 9, 14, 20, 4, 11, 16, 23, 4, 11, 16, + 23, 4, 11, 16, 23, 4, 11, 16, 23, 6, 10, 15, 21, 6, 10, 15, 21, 6, 10, 15, 21, 6, 10, 15, 21, + ]; + let k: Vec = (0..64).map(|i| ((i as f64 + 1.0).sin().abs() * 4294967296.0) as u32).collect(); + let (mut a0, mut b0, mut c0, mut d0) = (0x67452301u32, 0xefcdab89u32, 0x98badcfeu32, 0x10325476u32); + let mut msg = data.to_vec(); + let bits = (data.len() as u64).wrapping_mul(8); + msg.push(0x80); + while msg.len() % 64 != 56 { + msg.push(0); + } + msg.extend_from_slice(&bits.to_le_bytes()); + for chunk in msg.chunks(64) { + let m: Vec = chunk.chunks(4).map(|w| u32::from_le_bytes([w[0], w[1], w[2], w[3]])).collect(); + let (mut a, mut b, mut c, mut d) = (a0, b0, c0, d0); + for i in 0..64 { + let (f, g) = match i / 16 { + 0 => ((b & c) | (!b & d), i), + 1 => ((d & b) | (!d & c), (5 * i + 1) % 16), + 2 => (b ^ c ^ d, (3 * i + 5) % 16), + _ => (c ^ (b | !d), (7 * i) % 16), + }; + let f = f.wrapping_add(a).wrapping_add(k[i]).wrapping_add(m[g]); + a = d; + d = c; + c = b; + b = b.wrapping_add(f.rotate_left(s[i])); + } + a0 = a0.wrapping_add(a); + b0 = b0.wrapping_add(b); + c0 = c0.wrapping_add(c); + d0 = d0.wrapping_add(d); + } + [a0, b0, c0, d0].iter().flat_map(|v| v.to_le_bytes()).map(|b| format!("{b:02x}")).collect() +} + +#[test] +fn md5_matches_rfc_1321() { + assert_eq!(md5(b""), "d41d8cd98f00b204e9800998ecf8427e"); + assert_eq!(md5(b"The quick brown fox jumps over the lazy dog"), "9e107d9d372bb6826bd81d3542a419d6"); +} + +#[test] +fn libvpx_test_vectors() { + let dir = std::env::var_os("VP8_TEST_VECTORS") + .map(std::path::PathBuf::from) + .unwrap_or_else(|| std::path::Path::new(env!("CARGO_MANIFEST_DIR")).join("../../target/fixtures/vp8/conformance")); + let Ok(entries) = std::fs::read_dir(&dir) else { + eprintln!("SKIP: no test vectors in {}", dir.display()); + return; + }; + let mut names: Vec<_> = entries.filter_map(|e| e.ok()).map(|e| e.path()).filter(|p| p.extension().is_some_and(|x| x == "ivf")).collect(); + names.sort(); + let mut checked = 0; + for ivf in names { + let Ok(sums) = std::fs::read_to_string(ivf.with_extension("ivf.md5")) else { continue }; + let expected: Vec<&str> = sums.lines().filter_map(|l| l.split_whitespace().next()).collect(); + let data = std::fs::read(&ivf).unwrap(); + let pictures = common::decode_all(&common::ivf_frames(&data)); + assert_eq!(pictures.len(), expected.len(), "{}: picture count", ivf.display()); + for (i, (p, want)) in pictures.iter().zip(&expected).enumerate() { + assert_eq!(md5(&common::i420(p)), *want, "{} frame {i}", ivf.display()); + } + checked += 1; + } + eprintln!("{checked} test vectors bit-exact"); +} diff --git a/crates/vp8/tools/extract_tables.py b/crates/vp8/tools/extract_tables.py new file mode 100644 index 0000000000..2daaf87e47 --- /dev/null +++ b/crates/vp8/tools/extract_tables.py @@ -0,0 +1,94 @@ +#!/usr/bin/env python3 +"""Extract the VP8 constant tables from the text of RFC 6386 ("VP8 Data Format and Decoding Guide"). + +Usage: + curl -L -o rfc6386.txt https://www.rfc-editor.org/rfc/rfc6386.txt + python3 extract_tables.py rfc6386.txt > ../src/spec_tables.rs + +Only sections 1-19 (the format description) are read: the text is cut at section 20 ("Attachment +One: Reference Decoder Source Code"), so nothing comes from the reference decoder. Every table is +emitted flat (row-major, as printed) with its element count checked against the declared dimensions. +""" +import re +import sys +from pathlib import Path + + +def _spec_path(arg): + """Resolve a CLI path argument and require it to be an existing regular file.""" + path = Path(arg).resolve() + if not path.is_file(): + sys.exit(f"not a readable file: {arg}") + return path + + +text = open(_spec_path(sys.argv[1]), encoding="utf-8").read() +cut = text.find("\n20. Attachment One") +if cut < 0: + sys.exit("section 20 not found") +text = text[:cut] +# page breaks: footer / header lines and form feeds +text = "\n".join(l for l in text.split("\n") if not l.startswith("Bankoski") and not l.startswith("RFC 6386 ") and "\f" not in l) + + +def table(name, count, occurrence=-1): + """Integers of the table `name ... = { ... }` (by default the last definition in the text).""" + found = [] + # a definition: `name [dim] [dim] ... = {` (uses of the table never have `= {` after the brackets) + for m in re.finditer(r"\b%s\s*(?:\[[^\]\n]*\]\s*)+=\s*\{" % re.escape(name), text): + brace = m.end() - 1 + depth = 0 + i = brace + while True: + ch = text[i] + if ch == "{": + depth += 1 + elif ch == "}": + depth -= 1 + if depth == 0: + break + i += 1 + body = re.sub(r"/\*.*?\*/", "", text[brace:i], flags=re.S) + body = re.sub(r"//[^\n]*", "", body) + found.append([int(v) for v in re.findall(r"-?\d+", body)]) + if not found: + sys.exit("table %s not found" % name) + vals = found[occurrence] + if len(vals) != count: + sys.exit("table %s: %d values, expected %d" % (name, len(vals), count)) + return vals + + +TABLES = [ + # (spec name, rust name, rust type, count) + ("coeff_update_probs", "COEFF_UPDATE_PROBS", "u8", 4 * 8 * 3 * 11), + ("default_coeff_probs", "DEFAULT_COEFF_PROBS", "u8", 4 * 8 * 3 * 11), + ("kf_bmode_prob", "KF_BMODE_PROBS", "u8", 10 * 10 * 9), + ("dc_qlookup", "DC_QLOOKUP", "u16", 128), + ("ac_qlookup", "AC_QLOOKUP", "u16", 128), + ("vp8_mode_contexts", "MODE_CONTEXTS", "u8", 6 * 4), + ("sub_mv_ref_prob", "SUB_MV_REF_PROBS", "u8", 5 * 3), + ("vp8_mv_update_probs", "MV_UPDATE_PROBS", "u8", 2 * 19), + ("default_mv_context", "DEFAULT_MV_PROBS", "u8", 2 * 19), + ("bmode_prob", "BMODE_PROBS", "u8", 9), + ("coeff_bands", "COEFF_BANDS", "u8", 16), + ("kf_ymode_prob", "KF_YMODE_PROBS", "u8", 4), + ("kf_uv_mode_prob", "KF_UV_MODE_PROBS", "u8", 3), + ("ymode_prob", "YMODE_PROBS", "u8", 4), + ("uv_mode_prob", "UV_MODE_PROBS", "u8", 3), + ("mvpartition_probs", "MVPARTITION_PROBS", "u8", 3), + ("BilinearFilters", "BILINEAR_FILTERS", "i32", 8 * 6), + ("filters", "SIXTAP_FILTERS", "i32", 8 * 6), +] + +print("// Generated by tools/extract_tables.py from the text of RFC 6386 (sections 1-19). Do not edit.") +print("// Every table is flat, row-major, in the order the specification prints it.") +print() +for spec, rust, ty, count in TABLES: + vals = table(spec, count) + print("/// `%s` (RFC 6386), %d entries." % (spec, count)) + print("pub const %s: [%s; %d] = [" % (rust, ty, count)) + for i in range(0, count, 16): + print(" " + ", ".join(str(v) for v in vals[i:i + 16]) + ",") + print("];") + print() diff --git a/crates/vp9enc/Cargo.toml b/crates/vp9enc/Cargo.toml new file mode 100644 index 0000000000..fc6b7e054e --- /dev/null +++ b/crates/vp9enc/Cargo.toml @@ -0,0 +1,13 @@ +[package] +name = "craft-vp9enc" +description = "Clean-room pure-Rust VP9 encoder for the Craft ecosystem" +version.workspace = true +edition.workspace = true +license.workspace = true +rust-version.workspace = true +repository.workspace = true + +[lints] +workspace = true + +[dependencies] diff --git a/crates/vp9enc/README.md b/crates/vp9enc/README.md new file mode 100644 index 0000000000..38d4589a6b --- /dev/null +++ b/crates/vp9enc/README.md @@ -0,0 +1,47 @@ +# craft-vp9enc + +A small VP9 encoder for WebM export (profile 0, 8-bit 4:2:0, BT.709). + +* **Key frames**: 8×8 intra blocks (DC / V / H / TM prediction chosen per block), 4×4 DCT/ADST + transforms, or the Walsh–Hadamard transform at quality 100 (mathematically lossless). +* **Inter frames**: one reference (the previous frame, LAST), 16×16 or 8×8 blocks chosen by a + cost comparison; motion search from the predicted vectors (NearestMv / NearMv / last frame's + co-located vector) with a whole-sample cross/diamond search refined to half and quarter + samples with the regular 8-tap interpolation filters; NEARESTMV / NEARMV / ZEROMV / NEWMV + chosen by bit cost; blocks with no coefficients worth coding are skipped; 4×4 DCT residual + with a rate–distortion check per transform block. Inter frames are coded error resilient + (`error_resilient_mode` = 1: no dependence on earlier probability contexts or motion vectors), + so `UsePrevFrameMvs` is never needed. No high-precision (1/8) vectors, no compound prediction, + no intra blocks inside inter frames. +* **Loop filter**: the in-loop deblocking filter (§8.8) on every lossy frame; the level is the + best of three trials against the source (sharpness 0, no mode/reference deltas). +* **Probabilities**: per-frame forward updates in the compressed header (`diff_update_prob` / + `update_mv_prob`) of the coefficient, skip, inter-mode, is-inter, reference, partition and + motion-vector probabilities. Tiles are recorded as symbols first, the frame's own counts pick + the updates that pay for their signalling, then the symbols are entropy coded with the updated + probabilities. There is no backward adaptation (`refresh_frame_context` = 0). +* **Rate control**: constant quality (quality → `base_q_idx`) or a target bitrate: a per-frame + q index from a bits ≈ complexity / step model per frame type, key frames getting a larger share + of each key-frame interval, with the running over/undershoot paid back over about a second. +* **Key frame interval** is configurable (1 = every frame a key frame, the old intra-only mode). + +The encoder reconstructs exactly what a decoder reconstructs (inter prediction with the +decoder's vector clamping and edge-clamped reference fetches, integer inverse transforms, the +loop filter) and exposes it (`Vp9Encoder::reconstruction`); the tests require FilmCraft's VP9 +decoder (and ffmpeg, when installed) to output exactly that reconstruction, frame by frame, for +moving synthetic sequences at several sizes and qualities, with tile columns, and lossless. + +On a moving-gradient sequence (192×108, 24 frames) inter coding is 3.9–7× smaller than all-intra +at the same quality setting with equal or better PSNR. + +Written from the **VP9 Bitstream & Decoding Process Specification, version 0.6 (31 March +2016)**: the encoder emits the uncompressed and compressed headers (§6.2, §6.3), partition, mode +info, motion vector and token syntax (§6.4), derives motion vector candidates with the §6.4 +`find_mv_refs` / `find_best_ref_mvs` processes, and reconstructs with the specification's intra +and inter prediction (§8.5), integer inverse transforms (§8.7) and loop filter (§8.8) so it stays +in step with any conformant decoder. The boolean encoder is the inverse of the §9.2 decoder in +the form given in RFC 6386 §7.3. Spec tables (§10) are the specification's; the key-frame tables +were transcribed from FilmCraft's first-party decoder `filmcraft-vp9` (MIT OR Apache-2.0), which +is also the decoder the tests round-trip through and whose behaviour (loop filter edge rules, +vector candidate order) the encoder was checked against; ffmpeg is used only as an external test +oracle. No libvpx or other third-party encoder/decoder source was consulted. diff --git a/crates/vp9enc/src/bool.rs b/crates/vp9enc/src/bool.rs new file mode 100644 index 0000000000..303779c9ea --- /dev/null +++ b/crates/vp9enc/src/bool.rs @@ -0,0 +1,270 @@ +//! Boolean (arithmetic) encoder: the inverse of the VP9 boolean decoder (spec §9.2), written as +//! the classic carry-propagating range coder described in RFC 6386 §7.3 (VP8 and VP9 share the +//! bool coder). + +pub struct BoolEncoder { + out: Vec, + range: u32, + bottom: u32, + bit_count: i32, + /// Start of the run of 0xFF bytes at the end of `out` (`out.len()` when it ends in another + /// byte). A carry only touches that run and the byte before it. + ff_start: usize, +} + +impl Default for BoolEncoder { + fn default() -> Self { + BoolEncoder::new() + } +} + +impl BoolEncoder { + pub fn new() -> Self { + BoolEncoder { out: Vec::new(), range: 255, bottom: 0, bit_count: 24, ff_start: 0 } + } + + fn push_byte(&mut self, b: u8) { + if b != 255 { + self.ff_start = self.out.len() + 1; + } + self.out.push(b); + } + + /// Carry into the bytes written so far: the trailing 0xFF run becomes 0x00 and the byte + /// before it grows by one (a carry out of the first byte is dropped). Each 0xFF byte is + /// cleared once, so a long run costs O(1) per byte instead of a rescan for every carry. + fn add_one(&mut self) { + let end = self.out.len(); + let start = self.ff_start.min(end); + if let Some(run) = self.out.get_mut(start..) { + run.fill(0); + } + self.ff_start = end; + if let Some(i) = start.checked_sub(1) + && let Some(b) = self.out.get_mut(i) + { + *b = b.wrapping_add(1); + if *b == 255 && start == end { + // The grown byte is the last byte: it joins the 0xFF bytes before it. + // (When a run was cleared, `out` ends in 0x00 and `ff_start` stays `end`.) + let mut s = i; + while s > 0 && self.out.get(s - 1) == Some(&255) { + s -= 1; + } + self.ff_start = s; + } + } + } + + /// Write `bit` with probability `prob`/256 of it being 0. + #[inline] + pub fn write(&mut self, bit: bool, prob: u8) { + let split = 1 + (((self.range - 1) * prob as u32) >> 8); + if bit { + self.bottom = self.bottom.wrapping_add(split); + self.range -= split; + } else { + self.range = split; + } + while self.range < 128 { + self.range <<= 1; + if self.bottom & (1 << 31) != 0 { + self.add_one(); + } + self.bottom <<= 1; + self.bit_count -= 1; + if self.bit_count == 0 { + self.push_byte((self.bottom >> 24) as u8); + self.bottom &= (1 << 24) - 1; + self.bit_count = 8; + } + } + } + + pub fn literal(&mut self, v: u32, bits: u32) { + for i in (0..bits).rev() { + self.write((v >> i) & 1 != 0, 128); + } + } + + /// Flush (RFC 6386 §7.3) and return the bytes. + pub fn finish(mut self) -> Vec { + let mut c = self.bit_count; + let mut v = self.bottom; + if v & (1u32 << (32 - c)) != 0 { + self.add_one(); + } + v <<= c & 7; + c >>= 3; + while { + c -= 1; + c >= 0 + } { + v <<= 8; + } + for _ in 0..4 { + self.push_byte((v >> 24) as u8); + v <<= 8; + } + self.out + } +} + +#[cfg(test)] +mod tests { + use super::*; + + /// A straightforward decoder per VP9 spec §9.2. + struct Dec<'a> { + d: &'a [u8], + pos: usize, + value: u64, + bits: i32, + range: u32, + } + impl<'a> Dec<'a> { + fn new(d: &'a [u8]) -> Self { + let mut x = Dec { d, pos: 0, value: 0, bits: 0, range: 255 }; + x.fill(); + x + } + fn fill(&mut self) { + while self.bits <= 48 { + let b = if self.pos < self.d.len() { self.d[self.pos] } else { 0 }; + self.pos += 1; + self.value |= (b as u64) << (56 - self.bits); + self.bits += 8; + } + } + fn read(&mut self, p: u8) -> bool { + let split = 1 + (((self.range - 1) * p as u32) >> 8); + let big = (split as u64) << 56; + let bit = if self.value >= big { + self.range -= split; + self.value -= big; + true + } else { + self.range = split; + false + }; + while self.range < 128 { + self.range <<= 1; + self.value <<= 1; + self.bits -= 1; + } + self.fill(); + bit + } + } + + #[test] + fn round_trip() { + let mut e = BoolEncoder::new(); + let mut seq = vec![]; + let mut s = 12345u32; + for i in 0..20000 { + s = s.wrapping_mul(1103515245).wrapping_add(12345); + let p = ((s >> 8) % 255 + 1) as u8; + let bit = (s >> 20) % 256 >= p as u32 || i % 97 == 0; + seq.push((bit, p)); + e.write(bit, p); + } + let d = e.finish(); + let mut dec = Dec::new(&d); + for (i, (b, p)) in seq.into_iter().enumerate() { + assert_eq!(dec.read(p), b, "bit {i}"); + } + } + + #[test] + fn all_ones_carry() { + let mut e = BoolEncoder::new(); + for _ in 0..5000 { + e.write(true, 1); + } + let d = e.finish(); + let mut dec = Dec::new(&d); + for _ in 0..5000 { + assert!(dec.read(1)); + } + } + + /// The encoder as it was before the carry run was tracked: a carry rescans the bytes. + struct Scan { + out: Vec, + range: u32, + bottom: u32, + bit_count: i32, + } + impl Scan { + fn add_one(&mut self) { + let mut i = self.out.len(); + while i > 0 { + i -= 1; + if self.out[i] == 255 { + self.out[i] = 0; + } else { + self.out[i] += 1; + return; + } + } + } + fn write(&mut self, bit: bool, prob: u8) { + let split = 1 + (((self.range - 1) * prob as u32) >> 8); + if bit { + self.bottom = self.bottom.wrapping_add(split); + self.range -= split; + } else { + self.range = split; + } + while self.range < 128 { + self.range <<= 1; + if self.bottom & (1 << 31) != 0 { + self.add_one(); + } + self.bottom <<= 1; + self.bit_count -= 1; + if self.bit_count == 0 { + self.out.push((self.bottom >> 24) as u8); + self.bottom &= (1 << 24) - 1; + self.bit_count = 8; + } + } + } + } + + #[test] + fn carry_after_fe_before_ff_run() { + let mut e = BoolEncoder { out: vec![0x10, 0xFE, 0xFF], range: 255, bottom: 0, bit_count: 24, ff_start: 2 }; + e.add_one(); + assert_eq!(e.out, [0x10, 0xFF, 0x00]); + e.push_byte(0xFF); + e.add_one(); + assert_eq!(e.out, [0x10, 0xFF, 0x01, 0x00]); + } + + #[test] + fn carry_run_tracking_matches_the_rescan() { + let mut seed = 99u32; + for case in 0..300 { + let mut fast = BoolEncoder::new(); + let mut slow = Scan { out: vec![], range: 255, bottom: 0, bit_count: 24 }; + // Skewed probabilities make long 0xFF runs and carries likely. + let extreme = case % 3; + for _ in 0..4000 { + seed ^= seed << 13; + seed ^= seed >> 17; + seed ^= seed << 5; + let p = match extreme { + 0 => (seed >> 8) as u8 | 1, + 1 => 1 + (seed >> 8) as u8 % 4, + _ => 255 - (seed >> 8) as u8 % 4, + }; + let bit = (seed >> 24) % 256 >= p as u32 || (seed >> 3).is_multiple_of(11); + fast.write(bit, p); + slow.write(bit, p); + assert_eq!(fast.out, slow.out, "case {case}"); + } + } + } +} diff --git a/crates/vp9enc/src/lf.rs b/crates/vp9enc/src/lf.rs new file mode 100644 index 0000000000..eeab753840 --- /dev/null +++ b/crates/vp9enc/src/lf.rs @@ -0,0 +1,312 @@ +//! The in-loop deblocking filter (spec 8.8), as the decoder applies it to a frame coded with 4×4 +//! transforms, 8×8 / 16×16 blocks, no segmentation and no reference/mode deltas: superblocks in +//! raster order, for each plane the vertical edges (pass 0) then the horizontal edges (pass 1). +//! Transform edges inside a skipped inter block are left alone; every 4×4 edge uses the 4-tap +//! filter except edges on a 32-sample boundary, which get the 8-tap (flat) filter. + +use crate::{BLOCK_16X16, Mi, Plane}; + +#[derive(Clone, Copy)] +struct Limits { + limit: i32, + blimit: i32, + thresh: i32, +} + +/// Mode-info grid and thresholds shared by every superblock of this frame. +struct FrameFilter<'a> { + mi: &'a [Mi], + mi_rows: usize, + mi_cols: usize, + limits: Limits, +} + +/// Filter the reconstructed planes (Y, U, V, or a prefix of them) in place at `level` (0 = off; +/// sharpness 0). +pub fn filter_frame(planes: &mut [Plane], mi: &[Mi], mi_rows: usize, mi_cols: usize, level: u8) { + if level == 0 { + return; + } + let l = level as i32; + let limit = l.max(1); + let lim = Limits { limit, blimit: 2 * (l + 2) + limit, thresh: l >> 4 }; + let filter = FrameFilter { mi, mi_rows, mi_cols, limits: lim }; + for sbr in 0..mi_rows.div_ceil(8) { + for sbc in 0..mi_cols.div_ceil(8) { + for (plane, p) in planes.iter_mut().enumerate() { + for pass in 0..2 { + filter.sb_plane(p, plane, pass, sbr * 8, sbc * 8); + } + } + } + } +} + +impl FrameFilter<'_> { + fn sb_plane(&self, p: &mut Plane, plane: usize, pass: usize, row: usize, col: usize) { + let Self { mi, mi_rows, mi_cols, limits: lim } = *self; + let (sx, sy) = if plane > 0 { (1, 1) } else { (0, 0) }; + let (sub, edge_len) = if pass == 0 { (sx, 64 >> sy) } else { (sy, 64 >> sx) }; + let stride = p.w; + for edge in 0..(16usize >> sub) { + let mut i = 0; + while i < edge_len { + let (x, y) = + if pass == 0 { (col * 8 + edge * (4 << sx), row * 8 + (i << sy)) } else { (col * 8 + (i << sx), row * 8 + edge * (4 << sy)) }; + if x >= 8 * mi_cols || y >= 8 * mi_rows || (pass == 0 && x == 0) || (pass == 1 && y == 0) { + i += 8; + continue; + } + let lc = ((x >> 3) >> sx) << sx; + let lr = ((y >> 3) >> sy) << sy; + let m = mi[lr * mi_cols + lc]; + let size = if sub == 0 { m.size } else { m.size.max(BLOCK_16X16) }; + let n8 = if size >= BLOCK_16X16 { 2 } else { 1 }; + let is_block_edge = if pass == 0 { x & (8 * n8 - 1) == 0 } else { y & (8 * n8 - 1) == 0 }; + let tx_ok = !m.inter || !m.skip; + if !is_block_edge && !tx_ok { + i += 8; + continue; + } + let wide = edge % 8 == 0; + let n = if pass == 0 { (8 * mi_rows - y).div_ceil(1 << sy) } else { (8 * mi_cols - x).div_ceil(1 << sx) }.min(8).min(edge_len - i); + // A transform edge is not filtered in the last odd chroma column. + let odd_last = pass == 1 && sx == 1 && mi_cols & 1 == 1 && edge & 1 == 1; + let (px, py) = (x >> sx, y >> sy); + let mut apply = [false; LANES]; + for (l, a) in apply.iter_mut().enumerate().take(n) { + let is_tx_edge = tx_ok && !(odd_last && x + (l << sx) + 8 >= mi_cols * 8); + *a = is_block_edge || is_tx_edge; + } + if apply.iter().any(|&a| a) { + let pos = py * stride + px; + if pass == 0 { + filter_group::(&mut p.rec, pos, stride, 1, apply, wide, lim); + } else { + filter_group::(&mut p.rec, pos, 1, stride, apply, wide, lim); + } + } + i += 8; + } + } + } +} + +const LANES: usize = 8; + +/// [`filter_lane`] on the 8 lanes of a group at once (lane `l` has its q0 at `pos + l * along`; +/// samples across the edge are `step` apart; `ROWS`: `along` is 1), for lanes whose `apply` is +/// set. Every lane is computed with both filters and the result selected per lane (0 / -1 +/// masks), so the arithmetic vectorizes; lanes left alone are written back unchanged. +#[inline(always)] +fn filter_group(d: &mut [u8], pos: usize, along: usize, step: usize, apply: [bool; LANES], wide: bool, lim: Limits) { + type T = i16; + let mut v = [[0 as T; LANES]; 8]; + let start = pos - 4 * step; + if ROWS { + for (k, vk) in v.iter_mut().enumerate() { + let Some(row) = d.get(start + k * step..).and_then(|r| r.first_chunk::()) else { return }; + *vk = row.map(|s| s as T); + } + } else { + for l in 0..LANES { + let Some(line) = d.get(start + l * along..).and_then(|r| r.first_chunk::<8>()) else { return }; + for k in 0..8 { + v[k][l] = line[k] as T; + } + } + } + let (limit, blimit, thresh) = (lim.limit as T, lim.blimit as T, lim.thresh as T); + let le = |a: T, b: T| -((a <= b) as T); + let sel = |m: T, a: T, b: T| (a & m) | (b & !m); + let (p3, p2, p1, p0, q0, q1, q2, q3) = (&v[0], &v[1], &v[2], &v[3], &v[4], &v[5], &v[6], &v[7]); + let apply: [T; LANES] = apply.map(|a| -(a as T)); + let can_flat = -(wide as T); + let mut mask = [0 as T; LANES]; + let mut hev = [0 as T; LANES]; + let mut flat = [0 as T; LANES]; + for l in 0..LANES { + let d10 = (p1[l] - p0[l]).abs(); + let e10 = (q1[l] - q0[l]).abs(); + mask[l] = apply[l] + & le((p3[l] - p2[l]).abs(), limit) + & le((p2[l] - p1[l]).abs(), limit) + & le(d10, limit) + & le(e10, limit) + & le((q2[l] - q1[l]).abs(), limit) + & le((q3[l] - q2[l]).abs(), limit) + & le((p0[l] - q0[l]).abs() * 2 + (p1[l] - q1[l]).abs() / 2, blimit); + hev[l] = !(le(d10, thresh) & le(e10, thresh)); + flat[l] = can_flat + & mask[l] + & le(d10, 1) + & le(e10, 1) + & le((p2[l] - p0[l]).abs(), 1) + & le((q2[l] - q0[l]).abs(), 1) + & le((p3[l] - p0[l]).abs(), 1) + & le((q3[l] - q0[l]).abs(), 1); + } + if mask.iter().fold(0, |a, &m| a | m) == 0 { + return; + } + let mut out = v; + let c = |x: T| x.clamp(-128, 127); + for l in 0..LANES { + let (ps1, ps0, qs0, qs1) = (p1[l] - 128, p0[l] - 128, q0[l] - 128, q1[l] - 128); + let filter = c((c(ps1 - qs1) & hev[l]) + 3 * (qs0 - ps0)); + let f1 = c(filter + 4) >> 3; + let f2 = c(filter + 3) >> 3; + let f = (f1 + 1) >> 1; + let narrow = mask[l] & !flat[l]; + let outer = narrow & !hev[l]; + out[4][l] = sel(narrow, c(qs0 - f1) + 128, out[4][l]); + out[3][l] = sel(narrow, c(ps0 + f2) + 128, out[3][l]); + out[5][l] = sel(outer, c(qs1 - f) + 128, out[5][l]); + out[2][l] = sel(outer, c(ps1 + f) + 128, out[2][l]); + } + if flat.iter().fold(0, |a, &f| a | f) != 0 { + // 8-tap: positions p2..q2 (indices 1..7), each the rounded mean of a 7-wide window + // (indices clamped to 0..8) plus itself, as a sliding sum. + let s = |k: isize| &v[k.clamp(0, 7) as usize]; + let mut sum = [0 as T; LANES]; + for j in -2isize..=4 { + let a = s(j); + for l in 0..LANES { + sum[l] += a[l]; + } + } + for k in 1usize..7 { + let ki = k as isize; + let (cur, add, sub) = (s(ki), s(ki + 4), s(ki - 3)); + for l in 0..LANES { + let w8 = (sum[l] + cur[l] + 4) >> 3; + out[k][l] = sel(flat[l], w8, out[k][l]); + sum[l] += add[l] - sub[l]; + } + } + } + if ROWS { + for (k, ok) in out.iter().enumerate().take(7).skip(1) { + if let Some(row) = d.get_mut(start + k * step..).and_then(|r| r.first_chunk_mut::()) { + for l in 0..LANES { + row[l] = ok[l] as u8; + } + } + } + } else { + for l in 0..LANES { + if let Some(line) = d.get_mut(start + l * along..).and_then(|r| r.first_chunk_mut::<8>()) { + for k in 1..7 { + line[k] = out[k][l] as u8; + } + } + } + } +} + +/// Filter mask, narrow (4-tap) and 8-tap flat filters of spec 8.8.5 across one edge position; +/// `pos` is q0, samples across the edge are `step` apart. +#[cfg(test)] +fn filter_lane(d: &mut [u8], pos: usize, step: usize, wide: bool, lim: Limits) { + let at = |k: isize| d[(pos as isize + k * step as isize) as usize] as i32; + let (p3, p2, p1, p0) = (at(-4), at(-3), at(-2), at(-1)); + let (q0, q1, q2, q3) = (at(0), at(1), at(2), at(3)); + let Limits { limit, blimit, thresh } = lim; + let mask = (p3 - p2).abs() <= limit + && (p2 - p1).abs() <= limit + && (p1 - p0).abs() <= limit + && (q1 - q0).abs() <= limit + && (q2 - q1).abs() <= limit + && (q3 - q2).abs() <= limit + && (p0 - q0).abs() * 2 + (p1 - q1).abs() / 2 <= blimit; + if !mask { + return; + } + let flat = wide + && (p1 - p0).abs() <= 1 + && (q1 - q0).abs() <= 1 + && (p2 - p0).abs() <= 1 + && (q2 - q0).abs() <= 1 + && (p3 - p0).abs() <= 1 + && (q3 - q0).abs() <= 1; + let mut set = |k: isize, v: i32| d[(pos as isize + k * step as isize) as usize] = v as u8; + if flat { + // 8-tap: positions p2..q2, each the rounded mean of a 7-wide clamped window plus itself. + let s = [p3, p2, p1, p0, q0, q1, q2, q3]; + let px = |k: isize| s[(k.clamp(-4, 3) + 4) as usize]; + for k in -3isize..3 { + let mut sum = px(k); + for j in -3..=3 { + sum += px(k + j); + } + set(k, (sum + 4) >> 3); + } + return; + } + let c = |v: i32| v.clamp(-128, 127); + let hev = (p1 - p0).abs() > thresh || (q1 - q0).abs() > thresh; + let (ps1, ps0, qs0, qs1) = (p1 - 128, p0 - 128, q0 - 128, q1 - 128); + let filter = c(if hev { c(ps1 - qs1) } else { 0 } + 3 * (qs0 - ps0)); + let f1 = c(filter + 4) >> 3; + let f2 = c(filter + 3) >> 3; + set(0, c(qs0 - f1) + 128); + set(-1, c(ps0 + f2) + 128); + if !hev { + let f = (f1 + 1) >> 1; + set(1, c(qs1 - f) + 128); + set(-2, c(ps1 + f) + 128); + } +} + +#[cfg(test)] +mod tests { + use super::*; + + #[test] + fn group_matches_per_lane_filter() { + let mut seed = 0x1234_5678u32; + let mut rnd = || { + seed ^= seed << 13; + seed ^= seed >> 17; + seed ^= seed << 5; + seed + }; + for case in 0..20000 { + let stride = 24; + let base: Vec = + { + let flatish = case % 3 == 0; + let c = (rnd() % 256) as i32; + (0..stride * 24) + .map(|_| { + if flatish { + (c + (rnd() % 3) as i32 - 1).clamp(0, 255) as u8 + } else { + (c + (rnd() % 40) as i32 - 20).clamp(0, 255) as u8 + } + }) + .collect() + }; + let l = (rnd() % 64) as i32; + let lim = Limits { limit: l.max(1), blimit: 2 * (l + 2) + l.max(1), thresh: l >> 4 }; + let wide = rnd() & 1 == 1; + let apply: [bool; LANES] = std::array::from_fn(|_| rnd() % 4 != 0); + for pass in 0..2 { + let (mut a, mut b) = (base.clone(), base.clone()); + let pos = 8 * stride + 8; + for (l, &ap) in apply.iter().enumerate() { + if ap { + let (p, st) = if pass == 0 { (pos + l * stride, 1) } else { (pos + l, stride) }; + filter_lane(&mut a, p, st, wide, lim); + } + } + if pass == 0 { + filter_group::(&mut b, pos, stride, 1, apply, wide, lim); + } else { + filter_group::(&mut b, pos, 1, stride, apply, wide, lim); + } + assert_eq!(a, b, "case {case} pass {pass}"); + } + } + } +} diff --git a/crates/vp9enc/src/lib.rs b/crates/vp9enc/src/lib.rs new file mode 100644 index 0000000000..c8b6d1e5ad --- /dev/null +++ b/crates/vp9enc/src/lib.rs @@ -0,0 +1,499 @@ +//! A VP9 encoder (profile 0, 8-bit 4:2:0), written from the *VP9 Bitstream & Decoding Process +//! Specification* v0.6 by mirroring its decoding process in reverse (see README.md). +//! +//! * **Key frames**: 64×64 superblocks split to 8×8 intra blocks (DC / V / H / TM chosen per +//! block), 4×4 DCT/ADST transforms (the Walsh–Hadamard transform at quality 100: lossless). +//! * **Inter frames** (error resilient, single reference: the previous frame as LAST): 16×16 or +//! 8×8 blocks with motion vectors found by a candidate + diamond search refined to quarter +//! samples with the regular 8-tap filters; NEARESTMV / NEARMV / ZEROMV / NEWMV chosen by cost, +//! motion vector prediction exactly as the decoder derives it; skipped blocks; 4×4 DCT residual. +//! * **In-loop deblocking** (8.8) on every lossy frame; the level is picked per frame by trial. +//! * **Probabilities**: per-frame forward updates (compressed header) of the coefficient, skip, +//! inter mode, reference, partition and motion vector probabilities, chosen from the frame's +//! own symbol counts (no backward adaptation: `refresh_frame_context` = 0). +//! * **Rate control**: constant quality (quality → base_q_idx), or a target bitrate with a +//! per-frame q_idx model. +//! +//! The encoder reconstructs exactly what a decoder reconstructs (prediction, integer inverse +//! transforms, loop filter), so prediction never drifts; [`Vp9Encoder::reconstruction`] returns +//! it. + +#![deny(clippy::unwrap_used, clippy::expect_used, clippy::panic, clippy::unimplemented, clippy::todo, clippy::unreachable)] +#![allow(clippy::needless_range_loop)] + +mod bool; +mod lf; +mod mc; +mod probs; +mod tables; +mod tile; +mod transform; + +pub use mc::Mv; +use probs::Probs; +use tables::{AC_QLOOKUP, DC_QLOOKUP}; +use transform::Quant; + +/// Largest width or height the 16-bit frame size fields can hold. +const MAX_DIMENSION: u32 = 1 << 16; + +pub(crate) const BLOCK_8X8: u8 = 3; +pub(crate) const BLOCK_16X16: u8 = 6; + +/// Encoder settings. +#[derive(Clone, Debug, PartialEq)] +pub struct EncoderConfig { + /// Frame size in samples; the stream stores size - 1 in 16 bits, so values above 65536 are + /// clamped to 65536 by [`Vp9Encoder::new`]. + pub width: u32, + pub height: u32, + /// 1..=100; 100 is lossless. With a target bitrate: the quality of the first frame. + pub quality: u8, + /// Full-range (0–255) rather than limited (16–235) YUV; only signalled. + pub full_range: bool, + /// A key frame every this many frames (1 = every frame is a key frame; 0 = only the first). + pub keyframe_interval: u32, + /// Target bitrate in kbit/s (`None`: constant quality). + pub target_kbps: Option, + /// Frames per second (rate control). + pub frame_rate: f64, + /// The in-loop deblocking filter. + pub loop_filter: bool, +} + +impl Default for EncoderConfig { + fn default() -> Self { + EncoderConfig { + width: 0, + height: 0, + quality: 80, + full_range: false, + keyframe_interval: 60, + target_kbps: None, + frame_rate: 30.0, + loop_filter: true, + } + } +} + +/// One coded frame. +#[derive(Clone, Debug)] +pub struct EncodedFrame { + pub data: Vec, + pub key: bool, + pub q_idx: u8, + pub filter_level: u8, +} + +/// A plane being coded: source and reconstruction at superblock-aligned size. +pub(crate) struct Plane { + /// Stride (superblock-aligned width). + pub w: usize, + /// Last valid column / row for intra prediction edges (the 8-aligned mode-info grid). + pub max_x: usize, + pub max_y: usize, + /// Visible size. + pub vis_w: usize, + pub vis_h: usize, + pub src: Vec, + pub rec: Vec, +} + +impl Plane { + fn new(data: &[u8], vw: usize, vh: usize, w: usize, h: usize, max_x: usize, max_y: usize) -> Plane { + let mut src = vec![0u8; w * h]; + for y in 0..h { + let sy = y.min(vh.saturating_sub(1)); + for x in 0..w { + let sx = x.min(vw.saturating_sub(1)); + src[y * w + x] = data.get(sy * vw + sx).copied().unwrap_or(128); + } + } + Plane { w, max_x, max_y, vis_w: vw, vis_h: vh, src, rec: vec![0; w * h] } + } +} + +/// Mode info of one 8×8 position. +#[derive(Clone, Copy, Debug, Default)] +pub(crate) struct Mi { + pub inter: bool, + /// Intra mode (0..=9) or inter mode (NEARESTMV..=NEWMV). + pub mode: u8, + pub mv: Mv, + /// Skip as the loop filter sees it (inter blocks without coefficients). + pub skip: bool, + pub size: u8, +} + +/// Rate control state (target bitrate mode). +#[derive(Clone, Debug)] +struct RateControl { + /// bits ≈ complexity / step: per frame type, from the last frame of that type. + key_complexity: Option, + inter_complexity: Option, + /// Bits spent minus bits budgeted so far. + debt: f64, + last_inter_q: Option, +} + +pub struct Vp9Encoder { + cfg: EncoderConfig, + base_qidx: u8, + lossless: bool, + frames: u64, + since_key: u32, + /// The reconstruction of the last frame (the LAST reference): planes at their strides. + reference: Option<[Plane; 3]>, + mvs: Vec, + rc: RateControl, +} + +fn quality_to_qidx(q: u8) -> u8 { + // 99 ≈ 3, 90 ≈ 30, 50 ≈ 150, 1 ≈ 255. + ((100 - q.clamp(1, 99) as i32) * 255 / 85).clamp(1, 255) as u8 +} + +impl Vp9Encoder { + pub fn new(mut cfg: EncoderConfig) -> Self { + // The frame header stores width - 1 and height - 1 in 16 bits. + cfg.width = cfg.width.min(MAX_DIMENSION); + cfg.height = cfg.height.min(MAX_DIMENSION); + let q = cfg.quality.clamp(1, 100); + let lossless = q >= 100 && cfg.target_kbps.is_none(); + let base_qidx = if lossless { 0 } else { quality_to_qidx(q) }; + Vp9Encoder { + cfg, + base_qidx, + lossless, + frames: 0, + since_key: 0, + reference: None, + mvs: vec![], + rc: RateControl { key_complexity: None, inter_complexity: None, debt: 0.0, last_inter_q: None }, + } + } + + pub fn config(&self) -> &EncoderConfig { + &self.cfg + } + + /// Encode one frame from planar 8-bit 4:2:0 (`y`: width×height, `u`/`v`: + /// ((w+1)/2)×((h+1)/2), tightly packed). + pub fn encode_yuv420(&mut self, y: &[u8], u: &[u8], v: &[u8]) -> Vec { + self.encode_frame(y, u, v, false).data + } + + /// The decoder's reconstruction of the last coded frame (visible area, tightly packed Y, U, + /// V). + pub fn reconstruction(&self) -> Option<[Vec; 3]> { + let r = self.reference.as_ref()?; + Some(std::array::from_fn(|i| { + let p = &r[i]; + (0..p.vis_h).flat_map(|y| p.rec[y * p.w..y * p.w + p.vis_w].iter().copied()).collect() + })) + } + + fn is_key_due(&self, force: bool) -> bool { + force + || self.reference.is_none() + || self.cfg.keyframe_interval == 1 + || (self.cfg.keyframe_interval > 1 && self.since_key >= self.cfg.keyframe_interval) + } + + /// The q index of the next frame. + fn pick_qidx(&mut self, key: bool) -> u8 { + if self.lossless { + return 0; + } + let Some(kbps) = self.cfg.target_kbps else { return self.base_qidx }; + let per_frame = kbps as f64 * 1000.0 / self.cfg.frame_rate.max(1.0); + let interval = if self.cfg.keyframe_interval == 0 { 300.0 } else { self.cfg.keyframe_interval as f64 }; + // Key frames get a larger share of the group's budget. + let boost = (interval / 6.0).clamp(1.0, 5.0); + let group = per_frame * interval; + let key_share = group * boost / (boost + interval - 1.0); + let inter_share = (group - key_share) / (interval - 1.0).max(1.0); + let mut target = if key { key_share } else { inter_share }; + // Pay back (or spend) the running difference over about a second. + let horizon = self.cfg.frame_rate.max(1.0); + target = (target - self.rc.debt / horizon).max(target * 0.25); + let complexity = if key { self.rc.key_complexity } else { self.rc.inter_complexity.or(self.rc.key_complexity.map(|c| c / (boost * 2.0))) }; + let Some(cx) = complexity else { return self.base_qidx }; + let step = (cx / target).max(4.0); + let mut q = (0..=255u8).min_by_key(|&q| ((AC_QLOOKUP[q as usize] as f64 - step).abs() * 16.0) as i64).unwrap_or(self.base_qidx); + if !key && let Some(last) = self.rc.last_inter_q { + q = q.clamp(last.saturating_sub(24), last.saturating_add(24)); + } + q.max(1) + } + + fn rc_update(&mut self, key: bool, qidx: u8, bytes: usize) { + let Some(kbps) = self.cfg.target_kbps else { return }; + let bits = bytes as f64 * 8.0; + let cx = bits * AC_QLOOKUP[qidx as usize] as f64; + if key { + self.rc.key_complexity = Some(cx); + } else { + self.rc.inter_complexity = Some(match self.rc.inter_complexity { + Some(c) => c * 0.5 + cx * 0.5, + None => cx, + }); + self.rc.last_inter_q = Some(qidx); + } + let per_frame = kbps as f64 * 1000.0 / self.cfg.frame_rate.max(1.0); + self.rc.debt += bits - per_frame; + } + + /// Encode one frame; `force_key` starts a new key frame. + pub fn encode_frame(&mut self, y: &[u8], u: &[u8], v: &[u8], force_key: bool) -> EncodedFrame { + let (w, h) = (self.cfg.width.max(1) as usize, self.cfg.height.max(1) as usize); + let key = self.is_key_due(force_key); + let qidx = self.pick_qidx(key); + let lossless = qidx == 0; + let mi_cols = w.div_ceil(8); + let mi_rows = h.div_ceil(8); + let sb_cols = mi_cols.div_ceil(8); + let sb_rows = mi_rows.div_ceil(8); + let (pw, ph) = (sb_cols * 64, sb_rows * 64); + let (cw, ch) = (w.div_ceil(2), h.div_ceil(2)); + let mut planes = [ + Plane::new(y, w, h, pw, ph, mi_cols * 8 - 1, mi_rows * 8 - 1), + Plane::new(u, cw, ch, pw / 2, ph / 2, mi_cols * 4 - 1, mi_rows * 4 - 1), + Plane::new(v, cw, ch, pw / 2, ph / 2, mi_cols * 4 - 1, mi_rows * 4 - 1), + ]; + // Tile columns: as few as the width allows (tile_info, 6.2.13). + let mut min_log2 = 0; + while (64usize << min_log2) < sb_cols { + min_log2 += 1; + } + let mut max_log2 = 1; + while (sb_cols >> max_log2) >= 4 { + max_log2 += 1; + } + max_log2 -= 1; + let tile_cols = 1usize << min_log2; + let quant = Quant { dcq: DC_QLOOKUP[qidx as usize], acq: AC_QLOOKUP[qidx as usize], lossless, round_dc: 0.5, round_ac: 0.38 }; + let step = AC_QLOOKUP[qidx as usize] as f64 / 8.0; + let lambda_sse = 0.35 * step * step; + let refs = if key { + None + } else { + self.reference + .as_ref() + .map(|r| std::array::from_fn::<_, 3, _>(|i| mc::RefPlane { data: &r[i].rec, stride: r[i].w, w: r[i].vis_w, h: r[i].vis_h })) + }; + let prev = (!key && self.mvs.len() == mi_cols * mi_rows).then_some(&self.mvs[..]); + let phases = refs.as_ref().map(|r| mc::PhasePlanes::new(&r[0], 48)); + let fp = tile::FrameParams { key, quant, mi_cols, mi_rows, refs, phases, prev_mvs: prev, lambda_sad: lambda_sse.sqrt(), lambda_sse }; + let mut mi = vec![Mi::default(); mi_cols * mi_rows]; + let mut writers = vec![]; + for t in 0..tile_cols { + let start = (((t * sb_cols) >> min_log2) * 8).min(mi_cols); + let end = ((((t + 1) * sb_cols) >> min_log2) * 8).min(mi_cols); + let mut tile = tile::Tile::new(&fp, &mut planes, &mut mi, start, end); + tile.code(); + writers.push(tile.w); + } + // Forward probability updates from this frame's counts. + let defaults = Probs::defaults(); + let mut counts = vec![[0u32; 2]; probs::NPROBS]; + for wr in &writers { + wr.count(&mut counts); + } + let wanted = probs::plan_updates(&defaults, &counts, !key); + let (compressed, used) = probs::compressed_header(&defaults, &wanted, lossless, !key); + let tiles: Vec> = writers.iter().map(|wr| wr.emit(&used)).collect(); + drop(fp); + // Loop filter: the level with the least error against the source. + let level = if lossless || !self.cfg.loop_filter { 0 } else { choose_level(&mut planes, &mi, mi_rows, mi_cols, qidx, key) }; + lf::filter_frame(&mut planes, &mi, mi_rows, mi_cols, level); + // Uncompressed header. + let mut bw = BitWriter::default(); + bw.put(2, 2); // frame_marker + bw.put(0, 1); // profile low bit + bw.put(0, 1); // profile high bit + bw.put(0, 1); // show_existing_frame + bw.put(!key as u32, 1); // frame_type + bw.put(1, 1); // show_frame + if key { + bw.put(0, 1); // error_resilient_mode + bw.put(0x49, 8); + bw.put(0x83, 8); + bw.put(0x42, 8); + bw.put(2, 3); // color_space CS_BT_709 + bw.put(self.cfg.full_range as u32, 1); + bw.put(w as u32 - 1, 16); + bw.put(h as u32 - 1, 16); + bw.put(0, 1); // render_and_frame_size_different + bw.put(0, 1); // refresh_frame_context + bw.put(1, 1); // frame_parallel_decoding_mode + } else { + bw.put(1, 1); // error_resilient_mode (no reliance on earlier contexts or vectors) + bw.put(0x01, 8); // refresh_frame_flags: slot 0 becomes the new LAST + for _ in 0..3 { + bw.put(0, 3); // ref_frame_idx: slot 0 + bw.put(0, 1); // sign bias + } + bw.put(1, 1); // found_ref: the size of LAST + bw.put(0, 1); // render_and_frame_size_different + bw.put(0, 1); // allow_high_precision_mv + bw.put(0, 1); // is_filter_switchable + bw.put(1, 2); // raw_interpolation_filter → EIGHTTAP (regular) + } + bw.put(0, 2); // frame_context_idx + bw.put(level as u32, 6); // loop_filter_level + bw.put(0, 3); // sharpness + bw.put(0, 1); // mode_ref_delta_enabled + bw.put(qidx as u32, 8); + bw.put(0, 1); // delta_q_y_dc + bw.put(0, 1); // delta_q_uv_dc + bw.put(0, 1); // delta_q_uv_ac + bw.put(0, 1); // segmentation_enabled + if min_log2 < max_log2 { + bw.put(0, 1); // no extra tile columns + } + bw.put(0, 1); // tile_rows_log2 = 0 + bw.put(compressed.len() as u32, 16); + let mut out = bw.finish(); + out.extend_from_slice(&compressed); + let n = tiles.len(); + for (i, t) in tiles.into_iter().enumerate() { + if i + 1 < n { + out.extend_from_slice(&(t.len() as u32).to_be_bytes()); + } + out.extend_from_slice(&t); + } + // A frame mustn't end in a byte that reads as a superframe marker (0b110xxxxx, Annex B): + // decoders would take its last bytes for a superframe index and reject the frame. The + // last tile's bool-coded data may end in zero padding (9.2.3), so a zero byte fixes it. + if out.last().is_some_and(|b| b & 0xe0 == 0xc0) { + out.push(0); + } + // State for the next frame. + self.mvs = mi.iter().map(|m| if m.inter { m.mv } else { Mv::ZERO }).collect(); + self.reference = Some(planes); + self.frames += 1; + self.since_key = if key { 1 } else { self.since_key + 1 }; + self.rc_update(key, qidx, out.len()); + EncodedFrame { data: out, key, q_idx: qidx, filter_level: level } + } +} + +/// Try a few loop filter levels on the luma reconstruction; the one closest to the source +/// (squared error over the visible area) wins (0 when filtering doesn't help). +fn choose_level(planes: &mut [Plane; 3], mi: &[Mi], mi_rows: usize, mi_cols: usize, qidx: u8, key: bool) -> u8 { + let guess = ((qidx as f64 * if key { 0.25 } else { 0.2 }).round() as u8).clamp(1, 63); + let sse = |p: &Plane| -> u64 { + let mut s = 0u64; + for y in 0..p.vis_h { + for x in 0..p.vis_w { + let d = p.src[y * p.w + x] as i64 - p.rec[y * p.w + x] as i64; + s += (d * d) as u64; + } + } + s + }; + let mut best = (sse(&planes[0]), 0u8); + let saved = planes[0].rec.clone(); + for level in [guess / 2, guess, (guess as u16 * 3 / 2).min(63) as u8] { + if level == 0 { + continue; + } + lf::filter_frame(&mut planes[..1], mi, mi_rows, mi_cols, level); + let s = sse(&planes[0]); + planes[0].rec.copy_from_slice(&saved); + if s < best.0 { + best = (s, level); + } + } + best.1 +} + +#[derive(Default)] +struct BitWriter { + out: Vec, + acc: u32, + n: u32, +} + +impl BitWriter { + fn put(&mut self, v: u32, bits: u32) { + for i in (0..bits).rev() { + self.acc = (self.acc << 1) | ((v >> i) & 1); + self.n += 1; + if self.n == 8 { + self.out.push(self.acc as u8); + self.acc = 0; + self.n = 0; + } + } + } + fn finish(mut self) -> Vec { + if self.n > 0 { + self.out.push((self.acc << (8 - self.n)) as u8); + } + self.out + } +} + +// ---------------------------------------------------------------- colour conversion + +/// BT.709 RGB(A) 8-bit → planar YUV 4:2:0 (limited or full range). +pub fn rgba_to_yuv420(rgba: &[u8], w: u32, h: u32, full_range: bool) -> (Vec, Vec, Vec) { + let (w, h) = (w as usize, h as usize); + let (cw, ch) = (w.div_ceil(2), h.div_ceil(2)); + let mut y = vec![0u8; w * h]; + let mut u = vec![0u8; cw * ch]; + let mut v = vec![0u8; cw * ch]; + let (ys, yo, cs) = if full_range { (255.0, 0.0, 255.0) } else { (219.0, 16.0, 224.0) }; + let px = |x: usize, yy: usize| -> [f32; 3] { + let i = (yy.min(h.saturating_sub(1)) * w + x.min(w.saturating_sub(1))) * 4; + let c = rgba.get(i..i + 3).unwrap_or(&[0, 0, 0]); + [c[0] as f32 / 255.0, c[1] as f32 / 255.0, c[2] as f32 / 255.0] + }; + let luma = |c: [f32; 3]| 0.2126 * c[0] + 0.7152 * c[1] + 0.0722 * c[2]; + for yy in 0..h { + for x in 0..w { + y[yy * w + x] = (luma(px(x, yy)) * ys + yo).round().clamp(0.0, 255.0) as u8; + } + } + for cy in 0..ch { + for cx in 0..cw { + let mut acc = [0.0f32; 3]; + for (dx, dy) in [(0, 0), (1, 0), (0, 1), (1, 1)] { + let c = px(cx * 2 + dx, cy * 2 + dy); + for k in 0..3 { + acc[k] += c[k] / 4.0; + } + } + let l = luma(acc); + let cb = (acc[2] - l) / 1.8556; + let cr = (acc[0] - l) / 1.5748; + u[cy * cw + cx] = (cb * cs + 128.0).round().clamp(0.0, 255.0) as u8; + v[cy * cw + cx] = (cr * cs + 128.0).round().clamp(0.0, 255.0) as u8; + } + } + (y, u, v) +} + +/// The alpha channel as a luma plane (full range) with neutral chroma — the second VP9 stream of +/// WebM alpha. +pub fn alpha_to_yuv420(rgba: &[u8], w: u32, h: u32) -> (Vec, Vec, Vec) { + let (w, h) = (w as usize, h as usize); + let (cw, ch) = (w.div_ceil(2), h.div_ceil(2)); + let y: Vec = rgba.as_chunks::<4>().0.iter().take(w * h).map(|p| p[3]).collect(); + (y, vec![128; cw * ch], vec![128; cw * ch]) +} + +#[cfg(test)] +mod tests { + use super::*; + + #[test] + fn sizes_beyond_the_16_bit_header_fields_are_clamped() { + let enc = Vp9Encoder::new(EncoderConfig { width: 70_000, height: u32::MAX, ..Default::default() }); + assert_eq!((enc.cfg.width, enc.cfg.height), (65_536, 65_536)); + let enc = Vp9Encoder::new(EncoderConfig { width: 65_536, height: 1, ..Default::default() }); + assert_eq!((enc.cfg.width, enc.cfg.height), (65_536, 1)); + } +} diff --git a/crates/vp9enc/src/mc.rs b/crates/vp9enc/src/mc.rs new file mode 100644 index 0000000000..e1b89b46ae --- /dev/null +++ b/crates/vp9enc/src/mc.rs @@ -0,0 +1,269 @@ +//! Inter prediction exactly as the decoder forms it: motion vector clamping (spec 8.5.2.2) and +//! the unscaled block inter prediction process (8.5.2.4) with the regular 8-tap filters and +//! reference samples clamped to the reference frame's visible area. + +use crate::tables::SUBPEL_REGULAR; + +/// A motion vector in 1/8 luma samples. +#[derive(Clone, Copy, Debug, Default, PartialEq, Eq)] +pub struct Mv { + pub row: i32, + pub col: i32, +} + +impl Mv { + pub const ZERO: Mv = Mv { row: 0, col: 0 }; + pub fn new(row: i32, col: i32) -> Mv { + Mv { row, col } + } + pub fn minus(self, o: Mv) -> Mv { + Mv::new(self.row - o.row, self.col - o.col) + } +} + +/// A reference plane: `w`×`h` visible samples at `stride`. +pub struct RefPlane<'a> { + pub data: &'a [u8], + pub stride: usize, + pub w: usize, + pub h: usize, +} + +/// The block's motion vector for `plane` in 1/16 samples of that plane, clamped as the +/// decoder clamps it. `n8`: block size in 8×8 units. +pub fn clamp_for_plane(mv: Mv, r: usize, c: usize, n8: usize, mi_rows: usize, mi_cols: usize, chroma: bool) -> (i32, i32) { + let s = chroma as i32; + let (r, c, b) = (r as i32, c as i32, n8 as i32); + let to_top = (-(r * 8 * 16)) >> s; + let to_bottom = (((mi_rows as i32 - b - r) * 8) * 16) >> s; + let to_left = (-(c * 8 * 16)) >> s; + let to_right = (((mi_cols as i32 - b - c) * 8) * 16) >> s; + let spel = (4 + ((b * 8) >> s)) << 4; + let row = ((2 * mv.row) >> s).clamp(to_top - spel, (to_bottom + spel - 16).max(to_top - spel)); + let col = ((2 * mv.col) >> s).clamp(to_left - spel, (to_right + spel - 16).max(to_left - spel)); + (row, col) +} + +/// Predict a `w`×`h` block whose top-left sample is at (`x16`, `y16`) (1/16 samples) into `out` +/// (stride `w`). +pub fn predict(r: &RefPlane, x16: i32, y16: i32, w: usize, h: usize, out: &mut [u8]) { + let (x0, y0) = (x16 >> 4, y16 >> 4); + let (fx, fy) = ((x16 & 15) as usize, (y16 & 15) as usize); + let (lx, ly) = (r.w as i32 - 1, r.h as i32 - 1); + let inside = x0 - 3 >= 0 && y0 - 3 >= 0 && x0 + w as i32 + 4 <= lx && y0 + h as i32 + 4 <= ly; + let at = |x: i32, y: i32| -> i32 { + if inside { + r.data[y as usize * r.stride + x as usize] as i32 + } else { + r.data[y.clamp(0, ly) as usize * r.stride + x.clamp(0, lx) as usize] as i32 + } + }; + let fh = &SUBPEL_REGULAR[fx]; + let fv = &SUBPEL_REGULAR[fy]; + if inside && w <= 16 && h <= 16 { + // Rows straight from the reference (no edge clamping needed). + let (xs, ys) = ((x0 - 3) as usize, (y0 - 3) as usize); + let row = |i: usize| &r.data[(ys + i) * r.stride + xs..]; + if fx == 0 && fy == 0 { + for i in 0..h { + out[i * w..i * w + w].copy_from_slice(&row(i + 3)[3..3 + w]); + } + return; + } + let mut tmp = [0u8; 23 * 16]; + for i in 0..h + 7 { + let s = row(i); + filt8(std::array::from_fn(|t| &s[t..]), fh, &mut tmp[i * w..i * w + w]); + } + for i in 0..h { + filt8(std::array::from_fn(|t| &tmp[(i + t) * w..]), fv, &mut out[i * w..i * w + w]); + } + return; + } + if fx == 0 && fy == 0 { + for i in 0..h { + for j in 0..w { + out[i * w + j] = at(x0 + j as i32, y0 + i as i32) as u8; + } + } + return; + } + // Horizontal pass over h + 7 rows (from y0 - 3), then vertical. + let mut tmp = [0u8; 23 * 16]; + for i in 0..h + 7 { + let y = y0 - 3 + i as i32; + for j in 0..w { + let x = x0 + j as i32 - 3; + let mut acc = 64; + for t in 0..8 { + acc += fh[t] * at(x + t as i32, y); + } + tmp[i * w + j] = (acc >> 7).clamp(0, 255) as u8; + } + } + for i in 0..h { + for j in 0..w { + let mut acc = 64; + for t in 0..8 { + acc += fv[t] * tmp[(i + t) * w + j] as i32; + } + out[i * w + j] = (acc >> 7).clamp(0, 255) as u8; + } + } +} + +/// The reference luma pre-interpolated at the 16 quarter-sample phases the encoder uses (motion +/// vectors are even in 1/8 units: fractions 0, 4, 8, 12 in 1/16), over the visible area plus a +/// border. Each phase plane holds exactly what [`predict`] returns at that phase (the decoder's +/// separable filter with edge clamping), so motion search reads predictions directly. +pub struct PhasePlanes { + border: usize, + stride: usize, + rows: usize, + planes: Vec>, +} + +impl PhasePlanes { + pub fn new(r: &RefPlane, border: usize) -> PhasePlanes { + const M: usize = 4; + let (pw, ph) = (r.w + 2 * border, r.h + 2 * border); + // Edge-replicated source with M more samples on every side for the filter taps. + let rs = pw + 2 * M; + let rh = ph + 2 * M; + let off = (border + M) as i32; + let mut raw = vec![0u8; rs * rh]; + let o = off as usize; + for (y, row) in raw.chunks_exact_mut(rs).enumerate() { + let sy = (y as i32 - off).clamp(0, r.h as i32 - 1) as usize; + let src = &r.data[sy * r.stride..sy * r.stride + r.w]; + // Edge replication: src[clamp(x - off, 0, w - 1)]. + let (left, rest) = row.split_at_mut(o.min(rs)); + left.fill(src[0]); + let n = r.w.min(rest.len()); + rest[..n].copy_from_slice(&src[..n]); + rest[n..].fill(src[r.w - 1]); + } + // Horizontal phases over every raw row (inner columns). + let hpass = |fx: usize| -> Vec { + let f = &SUBPEL_REGULAR[fx]; + let mut out = vec![0u8; pw * rh]; + for (row, o) in raw.chunks_exact(rs).zip(out.chunks_exact_mut(pw)) { + filt8(std::array::from_fn(|t| &row[M - 3 + t..]), f, o); + } + out + }; + let unfiltered: Vec = (0..rh).flat_map(|y| raw[y * rs + M..y * rs + M + pw].iter().copied()).collect(); + let h: Vec> = (0..4).map(|k| if k == 0 { unfiltered.clone() } else { hpass(k * 4) }).collect(); + let mut planes = Vec::with_capacity(16); + for fy in 0..4 { + for hp in &h { + if fy == 0 { + planes.push(hp[M * pw..(M + ph) * pw].to_vec()); + continue; + } + let f = &SUBPEL_REGULAR[fy * 4]; + let mut out = vec![0u8; pw * ph]; + for (y, o) in out.chunks_exact_mut(pw).enumerate() { + filt8(std::array::from_fn(|t| &hp[(y + M + t - 3) * pw..]), f, o); + } + planes.push(out); + } + } + PhasePlanes { border, stride: pw, rows: ph, planes } + } + + /// The prediction of the `n`×`n` block at (`x16`, `y16`) (fractions multiples of 4) as rows of + /// a phase plane: (plane, offset of the first sample, stride). `None` beyond the border. + pub fn block(&self, x16: i32, y16: i32, n: usize) -> Option<(&[u8], usize, usize)> { + let (x0, y0) = ((x16 >> 4) + self.border as i32, (y16 >> 4) + self.border as i32); + let (fx, fy) = ((x16 & 15) as usize, (y16 & 15) as usize); + if fx % 4 != 0 || fy % 4 != 0 || x0 < 0 || y0 < 0 || x0 as usize + n > self.stride || y0 as usize + n > self.rows { + return None; + } + Some((&self.planes[(fy / 4) * 4 + fx / 4], y0 as usize * self.stride + x0 as usize, self.stride)) + } +} + +/// `out[x] = clip(Round2(sum_t f[t] * s[t][x], 7))` for 8-bit samples. Lanes are 16-bit sums +/// kept modulo 2^16: with the regular filter every sum plus the rounding term lies in +/// [-13706, 46474], so wrapped non-negative sums stay below 0xC000 and negative ones (clipped +/// to 0) land above it (the decoder's `narrow8`). +#[inline] +fn filt8(s: [&[u8]; 8], f: &[i32; 8], out: &mut [u8]) { + let n = out.len(); + let mut x = 0; + while x + 32 <= n { + filt_chunk::<32>(&s, f, out, x); + x += 32; + } + for w in [16, 8, 4] { + if x + w <= n { + match w { + 16 => filt_chunk::<16>(&s, f, out, x), + 8 => filt_chunk::<8>(&s, f, out, x), + _ => filt_chunk::<4>(&s, f, out, x), + } + x += w; + } + } + for j in x..n { + let acc: i32 = 64 + (0..8).map(|t| f[t] * s[t][j] as i32).sum::(); + out[j] = (acc >> 7).clamp(0, 255) as u8; + } +} + +#[inline(always)] +fn filt_chunk(s: &[&[u8]; 8], f: &[i32; 8], out: &mut [u8], x: usize) { + let mut acc = [64u16; W]; + for t in 0..8 { + let c = f[t] as u16; + let Some(p) = s[t].get(x..).and_then(|p| p.first_chunk::()) else { return }; + for j in 0..W { + acc[j] = acc[j].wrapping_add(c.wrapping_mul(p[j] as u16)); + } + } + if let Some(o) = out.get_mut(x..).and_then(|o| o.first_chunk_mut::()) { + for j in 0..W { + o[j] = if acc[j] < 0xC000 { (acc[j] >> 7).min(255) as u8 } else { 0 }; + } + } +} + +#[cfg(test)] +mod tests { + #[test] + fn phase_planes_match_predict() { + let (w, h) = (37usize, 29usize); + let data: Vec = (0..w * h).map(|i| ((i * 7919) % 251) as u8).collect(); + let r = RefPlane { data: &data, stride: w, w, h }; + let pp = PhasePlanes::new(&r, 24); + let mut out = [0u8; 64]; + for y16 in (-20 * 16..(h as i32 + 10) * 16).step_by(4 * 7) { + for x16 in (-20 * 16..(w as i32 + 10) * 16).step_by(4 * 5) { + let Some((p, o, s)) = pp.block(x16, y16, 8) else { continue }; + predict(&r, x16, y16, 8, 8, &mut out); + for i in 0..8 { + assert_eq!(&p[o + i * s..o + i * s + 8], &out[i * 8..i * 8 + 8], "({x16}, {y16}) row {i}"); + } + } + } + } + + use super::*; + + #[test] + fn whole_sample_and_linear_ramps() { + let (w, h) = (40usize, 30usize); + let data: Vec = (0..w * h).map(|i| ((i % w) * 4 + (i / w)) as u8).collect(); + let r = RefPlane { data: &data, stride: w, w, h }; + let mut out = [0u8; 64]; + predict(&r, 10 << 4, 8 << 4, 8, 8, &mut out); + assert_eq!(out[0], data[8 * w + 10]); + // Half a sample right on a linear ramp: exact midpoint. + predict(&r, (10 << 4) + 8, 8 << 4, 8, 8, &mut out); + assert_eq!(out[0], data[8 * w + 10] + 2); + // Off the left edge: clamped to column 0. + predict(&r, -(20 << 4), 0, 4, 4, &mut out); + assert_eq!(out[0], data[0]); + } +} diff --git a/crates/vp9enc/src/probs.rs b/crates/vp9enc/src/probs.rs new file mode 100644 index 0000000000..9773a2a09c --- /dev/null +++ b/crates/vp9enc/src/probs.rs @@ -0,0 +1,466 @@ +//! Probabilities: the frame's adaptable probability table (one flat array laid out in the order +//! the compressed header updates them), a symbol recorder that codes every boolean decision +//! against either a fixed probability or an entry of that table, bit-cost estimates, and the +//! per-frame forward updates (spec 6.3: `diff_update_prob` with `decode_term_subexp` / +//! `inv_remap_prob`, and `update_mv_prob`). +//! +//! Tiles are first recorded, then the encoder counts how often each table entry coded a 0 or a +//! 1, picks updates that pay for themselves, writes them in the compressed header and finally +//! entropy codes the recorded symbols with the updated probabilities. + +use crate::bool::BoolEncoder; +use crate::tables::{ + DEFAULT_COEF_PROBS, DEFAULT_INTER_MODE_PROBS, DEFAULT_IS_INTER_PROB, DEFAULT_MV_BITS_PROB, DEFAULT_MV_CLASS_PROBS, DEFAULT_MV_CLASS0_BIT_PROB, + DEFAULT_MV_CLASS0_FR_PROBS, DEFAULT_MV_FR_PROBS, DEFAULT_MV_JOINT_PROBS, DEFAULT_MV_SIGN_PROB, DEFAULT_PARTITION_PROBS, DEFAULT_SINGLE_REF_PROB, + DEFAULT_SKIP_PROB, PARETO_TABLE, inv_map_table, +}; + +/// Coefficient probabilities `[ptype][ref][band][ctx][3]` for 4×4 transforms (band 0 uses +/// contexts 0..3 only). +pub const COEF: usize = 0; +pub const SKIP: usize = 432; +pub const INTER_MODE: usize = SKIP + 3; +pub const IS_INTER: usize = INTER_MODE + 21; +pub const SINGLE_REF: usize = IS_INTER + 4; +pub const PARTITION: usize = SINGLE_REF + 10; +pub const MV_JOINT: usize = PARTITION + 48; +pub const MV_SIGN: usize = MV_JOINT + 3; +pub const MV_CLASS: usize = MV_SIGN + 2; +pub const MV_CLASS0_BIT: usize = MV_CLASS + 20; +pub const MV_BITS: usize = MV_CLASS0_BIT + 2; +pub const MV_CLASS0_FR: usize = MV_BITS + 20; +pub const MV_FR: usize = MV_CLASS0_FR + 12; +pub const NPROBS: usize = MV_FR + 6; + +/// Index of the first of the three coefficient probabilities of a context. +pub fn coef_index(ptype: usize, inter: bool, band: usize, ctx: usize) -> usize { + COEF + (((ptype * 2 + inter as usize) * 6 + band) * 6 + ctx) * 3 +} + +#[derive(Clone)] +pub struct Probs(pub [u8; NPROBS]); + +impl Probs { + /// The default probabilities (setup_past_independence / key frames). + pub fn defaults() -> Probs { + let mut p = [128u8; NPROBS]; + p[COEF..COEF + 432].copy_from_slice(&DEFAULT_COEF_PROBS); + p[SKIP..SKIP + 3].copy_from_slice(&DEFAULT_SKIP_PROB); + p[INTER_MODE..INTER_MODE + 21].copy_from_slice(&DEFAULT_INTER_MODE_PROBS); + p[IS_INTER..IS_INTER + 4].copy_from_slice(&DEFAULT_IS_INTER_PROB); + p[SINGLE_REF..SINGLE_REF + 10].copy_from_slice(&DEFAULT_SINGLE_REF_PROB); + p[PARTITION..PARTITION + 48].copy_from_slice(&DEFAULT_PARTITION_PROBS); + p[MV_JOINT..MV_JOINT + 3].copy_from_slice(&DEFAULT_MV_JOINT_PROBS); + p[MV_SIGN..MV_SIGN + 2].copy_from_slice(&DEFAULT_MV_SIGN_PROB); + p[MV_CLASS..MV_CLASS + 20].copy_from_slice(&DEFAULT_MV_CLASS_PROBS); + p[MV_CLASS0_BIT..MV_CLASS0_BIT + 2].copy_from_slice(&DEFAULT_MV_CLASS0_BIT_PROB); + p[MV_BITS..MV_BITS + 20].copy_from_slice(&DEFAULT_MV_BITS_PROB); + p[MV_CLASS0_FR..MV_CLASS0_FR + 12].copy_from_slice(&DEFAULT_MV_CLASS0_FR_PROBS); + p[MV_FR..MV_FR + 6].copy_from_slice(&DEFAULT_MV_FR_PROBS); + Probs(p) + } +} + +/// Probabilities of the full token tree (9.3.2) for model probability `p`. +pub fn pareto(p: u8) -> [u8; 8] { + let p = p.max(1) as usize; + let x = (p - 1) / 2; + let mut t = [0u8; 8]; + for n in 0..8 { + t[n] = + if p & 1 == 1 { PARETO_TABLE[x * 8 + n] } else { ((PARETO_TABLE[x * 8 + n] as u32 + PARETO_TABLE[(x + 1) * 8 + n] as u32) >> 1) as u8 }; + } + t +} + +/// The probability a recorded symbol is coded with. +#[derive(Clone, Copy, Debug)] +pub enum P { + Fixed(u8), + /// An entry of the frame's [`Probs`]. + Ctx(u16), + /// Node `n` of the Pareto token tree derived from the coefficient probability at the index. + Pareto(u16, u8), +} + +#[derive(Clone, Copy)] +struct Sym { + bit: bool, + p: P, +} + +/// Records the boolean decisions of a tile. +#[derive(Default)] +pub struct Writer { + syms: Vec, +} + +impl Writer { + #[inline] + pub fn put(&mut self, bit: bool, p: P) { + self.syms.push(Sym { bit, p }); + } + #[inline] + pub fn fixed(&mut self, bit: bool, prob: u8) { + self.put(bit, P::Fixed(prob)); + } + #[inline] + pub fn ctx(&mut self, bit: bool, idx: usize) { + self.put(bit, P::Ctx(idx as u16)); + } + /// Write `value` with `tree` (leaves stored negated); node `i` uses `prob(i)`. + pub fn tree(&mut self, tree: &[i8], value: u8, prob: impl Fn(usize) -> P) { + for (node, bit) in tree_path(tree, value) { + self.put(bit, prob(node)); + } + } + + /// Add the 0/1 counts of every table entry this tile used. + pub fn count(&self, counts: &mut [[u32; 2]]) { + for s in &self.syms { + if let P::Ctx(i) = s.p { + counts[i as usize][s.bit as usize] += 1; + } + } + } + + /// Entropy code the recorded symbols. + pub fn emit(&self, probs: &Probs) -> Vec { + let mut bc = BoolEncoder::new(); + bc.write(false, 128); // marker bit + for s in &self.syms { + let p = match s.p { + P::Fixed(p) => p, + P::Ctx(i) => probs.0[i as usize], + P::Pareto(i, n) => pareto(probs.0[i as usize])[n as usize], + }; + bc.write(s.bit, p); + } + bc.finish() + } +} + +/// The (node, bit) path to `value` in a tree (node = tree index / 2). +pub fn tree_path(tree: &[i8], value: u8) -> impl Iterator { + struct Path { + steps: [(u8, bool); 16], + len: usize, + } + fn walk(tree: &[i8], node: usize, value: u8, out: &mut Path) -> bool { + for b in 0..2 { + let t = tree[node + b]; + out.steps[out.len] = ((node >> 1) as u8, b == 1); + out.len += 1; + if t <= 0 { + if (-t) as u8 == value { + return true; + } + } else if walk(tree, t as usize, value, out) { + return true; + } + out.len -= 1; + } + false + } + let mut p = Path { steps: [(0, false); 16], len: 0 }; + walk(tree, 0, value, &mut p); + let Path { steps, len } = p; + steps.into_iter().take(len).map(|(n, b)| (n as usize, b)) +} + +// ---------------------------------------------------------------- costs + +/// Cost in 1/256 bit of coding a 0 (`[p][0]`) or a 1 (`[p][1]`) with probability `p`. +pub fn cost_table() -> &'static [[u32; 2]; 256] { + use std::sync::OnceLock; + static T: OnceLock<[[u32; 2]; 256]> = OnceLock::new(); + T.get_or_init(|| { + let mut t = [[0u32; 2]; 256]; + for (p, e) in t.iter_mut().enumerate() { + let q = (p.max(1) as f64) / 256.0; + e[0] = (-q.log2() * 256.0).round() as u32; + e[1] = (-(1.0 - q).log2() * 256.0).round() as u32; + } + t + }) +} + +#[inline] +pub fn cost(bit: bool, p: u8) -> u32 { + cost_table()[p as usize][bit as usize] +} + +/// Cost of `value` in a tree with probabilities `probs` (node i → probs[i]). +pub fn tree_cost(tree: &[i8], probs: &[u8], value: u8) -> u32 { + tree_path(tree, value).map(|(n, b)| cost(b, probs[n])).sum() +} + +// ---------------------------------------------------------------- forward updates + +fn inv_recenter_nonneg(v: i32, m: i32) -> i32 { + if v > 2 * m { + v + } else if v & 1 != 0 { + m - ((v + 1) >> 1) + } else { + m + (v >> 1) + } +} + +/// `inv_remap_prob` (6.3.5). +fn inv_remap_prob(delta: usize, prob: u8) -> u8 { + let v = inv_map_table()[delta.min(254)] as i32; + let m = prob as i32 - 1; + let r = if (m << 1) <= 255 { 1 + inv_recenter_nonneg(v, m) } else { 255 - inv_recenter_nonneg(v, 255 - 1 - m) }; + r.clamp(1, 255) as u8 +} + +/// The `decode_term_subexp` value that moves `old` to `new`. +fn delta_for(old: u8, new: u8) -> Option { + match delta_table().get(old as usize * 256 + new as usize) { + Some(&d) if d != NO_DELTA => Some(d as usize), + _ => None, + } +} + +/// Marks "no delta reaches this probability" in [`delta_table`]. +const NO_DELTA: u8 = 255; + +/// `delta_for` for every (old, new) pair, indexed `old * 256 + new`: the smallest delta that +/// moves `old` to `new`. Filled from the largest delta down so the smallest one wins. +fn delta_table() -> &'static [u8] { + use std::sync::OnceLock; + static T: OnceLock> = OnceLock::new(); + T.get_or_init(|| { + let mut t = vec![NO_DELTA; 256 * 256]; + for old in 0..=255u8 { + for d in (0..255usize).rev() { + let new = inv_remap_prob(d, old); + if let Some(e) = t.get_mut(old as usize * 256 + new as usize) { + *e = d as u8; + } + } + } + t + }) +} + +/// Bits of `decode_term_subexp` for `d`. +fn subexp_bits(d: usize) -> u32 { + match d { + 0..16 => 5, + 16..32 => 6, + 32..64 => 8, + 64..129 => 10, + _ => 11, + } +} + +fn write_subexp(bc: &mut BoolEncoder, d: usize) { + let d = d as u32; + if d < 16 { + bc.literal(0, 1); + bc.literal(d, 4); + } else if d < 32 { + bc.literal(2, 2); + bc.literal(d - 16, 4); + } else if d < 64 { + bc.literal(6, 3); + bc.literal(d - 32, 5); + } else { + bc.literal(7, 3); + if d < 129 { + bc.literal(d - 64, 7); + } else { + let v = (d + 1) >> 1; + bc.literal(v, 7); + bc.literal((d + 1) & 1, 1); + } + } +} + +fn counts_cost(c: [u32; 2], p: u8) -> u64 { + c[0] as u64 * cost(false, p) as u64 + c[1] as u64 * cost(true, p) as u64 +} + +/// Whether table entry `i` is an MV probability (7-bit `update_mv_prob` rather than +/// `diff_update_prob`). +fn is_mv(i: usize) -> bool { + i >= MV_JOINT +} + +/// Choose updated probabilities: an entry changes when the bits saved on this frame's symbols +/// exceed the cost of signalling the change. `inter`: the inter-frame tables are updatable. +pub fn plan_updates(old: &Probs, counts: &[[u32; 2]], inter: bool) -> Probs { + let mut new = old.clone(); + let flag = cost(true, 252) as i64 - cost(false, 252) as i64; + let limit = if inter { NPROBS } else { INTER_MODE }; + for i in 0..limit { + let c = counts[i]; + let n = c[0] + c[1]; + if n == 0 { + continue; + } + let o = old.0[i]; + let ideal = ((c[0] as f64 * 256.0 / n as f64).round() as i32).clamp(1, 255) as u8; + let cands: Vec = if is_mv(i) { vec![ideal | 1, ideal.saturating_sub(1).max(1) | 1] } else { vec![ideal] }; + let base = counts_cost(c, o) as i64; + let mut best = (0i64, o); + for p in cands { + if p == o { + continue; + } + let signal = if is_mv(i) { + 7 * 256 + } else { + match delta_for(o, p) { + Some(d) => subexp_bits(d) as i64 * 256, + None => continue, + } + }; + let gain = base - counts_cost(c, p) as i64 - signal - flag; + if gain > best.0 { + best = (gain, p); + } + } + new.0[i] = best.1; + } + new +} + +/// `diff_update_prob` for one entry. +fn diff_update(bc: &mut BoolEncoder, old: &Probs, new: &Probs, i: usize) { + let (o, n) = (old.0[i], new.0[i]); + match (o != n).then(|| delta_for(o, n)).flatten() { + Some(d) => { + bc.write(true, 252); + write_subexp(bc, d); + } + None => bc.write(false, 252), + } +} + +/// `update_mv_prob` for one entry. +fn mv_update(bc: &mut BoolEncoder, old: &Probs, new: &Probs, i: usize) { + let n = new.0[i]; + if n != old.0[i] && n & 1 == 1 { + bc.write(true, 252); + bc.literal(n as u32 >> 1, 7); + } else { + bc.write(false, 252); + } +} + +/// The compressed header (6.3) of a frame coded with ONLY_4X4 transforms, single LAST +/// references and a fixed interpolation filter, moving the probabilities from `old` to `new`. +/// Entries `new` cannot signal are left at `old` (and must be coded with `old`): the caller +/// codes the tiles with the probabilities this returns. +pub fn compressed_header(old: &Probs, new: &Probs, lossless: bool, inter: bool) -> (Vec, Probs) { + let mut bc = BoolEncoder::new(); + bc.write(false, 128); + if !lossless { + bc.literal(0, 2); // tx_mode = ONLY_4X4 + } + // read_coef_probs: TX_4X4 only. + let coef_changed = (COEF..COEF + 432).any(|i| new.0[i] != old.0[i]); + bc.literal(coef_changed as u32, 1); + if coef_changed { + for i in 0..2 { + for j in 0..2 { + for k in 0..6 { + for l in 0..if k == 0 { 3 } else { 6 } { + for m in 0..3 { + diff_update(&mut bc, old, new, coef_index(i, j == 1, k, l) + m); + } + } + } + } + } + } + for i in 0..3 { + diff_update(&mut bc, old, new, SKIP + i); + } + if inter { + for i in 0..21 { + diff_update(&mut bc, old, new, INTER_MODE + i); + } + for i in 0..4 { + diff_update(&mut bc, old, new, IS_INTER + i); + } + // reference_mode is SINGLE_REFERENCE (no compound: equal sign biases). + for i in 0..10 { + diff_update(&mut bc, old, new, SINGLE_REF + i); + } + for _ in 0..36 { + bc.write(false, 252); // y mode probabilities (no intra blocks in inter frames) + } + for i in 0..48 { + diff_update(&mut bc, old, new, PARTITION + i); + } + for j in 0..3 { + mv_update(&mut bc, old, new, MV_JOINT + j); + } + for i in 0..2 { + mv_update(&mut bc, old, new, MV_SIGN + i); + for j in 0..10 { + mv_update(&mut bc, old, new, MV_CLASS + i * 10 + j); + } + mv_update(&mut bc, old, new, MV_CLASS0_BIT + i); + for j in 0..10 { + mv_update(&mut bc, old, new, MV_BITS + i * 10 + j); + } + } + for i in 0..2 { + for j in 0..6 { + mv_update(&mut bc, old, new, MV_CLASS0_FR + i * 6 + j); + } + for j in 0..3 { + mv_update(&mut bc, old, new, MV_FR + i * 3 + j); + } + } + } + // What the decoder ends up with. + let mut used = old.clone(); + for i in 0..NPROBS { + let ok = if is_mv(i) { new.0[i] & 1 == 1 } else { delta_for(old.0[i], new.0[i]).is_some() }; + if ok && (inter || i < INTER_MODE) { + used.0[i] = new.0[i]; + } + } + (bc.finish(), used) +} + +#[cfg(test)] +mod tests { + use super::*; + + #[test] + fn remap_reaches_every_probability() { + for old in 1..=255u8 { + for new in 1..=255u8 { + if new != old { + assert!(delta_for(old, new).is_some(), "{old} -> {new}"); + } + } + } + } + + #[test] + fn delta_table_matches_the_linear_search() { + for old in 0..=255u8 { + for new in 0..=255u8 { + let slow = (0..255).find(|&d| inv_remap_prob(d, old) == new); + assert_eq!(delta_for(old, new), slow, "{old} -> {new}"); + } + } + } + + #[test] + fn inv_map_table_shape() { + let t = inv_map_table(); + assert_eq!(&t[..4], &[7, 20, 33, 46]); + assert_eq!(t[19], 254); + assert_eq!(&t[20..27], &[1, 2, 3, 4, 5, 6, 8]); + assert_eq!(&t[252..], &[252, 253, 253]); + } +} diff --git a/crates/vp9enc/src/tables.rs b/crates/vp9enc/src/tables.rs new file mode 100644 index 0000000000..fc91c34737 --- /dev/null +++ b/crates/vp9enc/src/tables.rs @@ -0,0 +1,208 @@ +//! Tables from the VP9 Bitstream & Decoding Process Specification v0.6 (section 10), as +//! transcribed in FilmCraft's first-party decoder `filmcraft-vp9` (MIT OR Apache-2.0, the same +//! licence as this crate). + +pub const DC_QLOOKUP: [i32; 256] = [ + 4, 8, 8, 9, 10, 11, 12, 12, 13, 14, 15, 16, 17, 18, 19, 19, 20, 21, 22, 23, 24, 25, 26, 26, 27, 28, 29, 30, 31, 32, 32, 33, 34, 35, 36, 37, 38, + 38, 39, 40, 41, 42, 43, 43, 44, 45, 46, 47, 48, 48, 49, 50, 51, 52, 53, 53, 54, 55, 56, 57, 57, 58, 59, 60, 61, 62, 62, 63, 64, 65, 66, 66, 67, + 68, 69, 70, 70, 71, 72, 73, 74, 74, 75, 76, 77, 78, 78, 79, 80, 81, 81, 82, 83, 84, 85, 85, 87, 88, 90, 92, 93, 95, 96, 98, 99, 101, 102, 104, + 105, 107, 108, 110, 111, 113, 114, 116, 117, 118, 120, 121, 123, 125, 127, 129, 131, 134, 136, 138, 140, 142, 144, 146, 148, 150, 152, 154, 156, + 158, 161, 164, 166, 169, 172, 174, 177, 180, 182, 185, 187, 190, 192, 195, 199, 202, 205, 208, 211, 214, 217, 220, 223, 226, 230, 233, 237, 240, + 243, 247, 250, 253, 257, 261, 265, 269, 272, 276, 280, 284, 288, 292, 296, 300, 304, 309, 313, 317, 322, 326, 330, 335, 340, 344, 349, 354, 359, + 364, 369, 374, 379, 384, 389, 395, 400, 406, 411, 417, 423, 429, 435, 441, 447, 454, 461, 467, 475, 482, 489, 497, 505, 513, 522, 530, 539, 549, + 559, 569, 579, 590, 602, 614, 626, 640, 654, 668, 684, 700, 717, 736, 755, 775, 796, 819, 843, 869, 896, 925, 955, 988, 1022, 1058, 1098, 1139, + 1184, 1232, 1282, 1336, +]; + +pub const AC_QLOOKUP: [i32; 256] = [ + 4, 8, 9, 10, 11, 12, 13, 14, 15, 16, 17, 18, 19, 20, 21, 22, 23, 24, 25, 26, 27, 28, 29, 30, 31, 32, 33, 34, 35, 36, 37, 38, 39, 40, 41, 42, 43, + 44, 45, 46, 47, 48, 49, 50, 51, 52, 53, 54, 55, 56, 57, 58, 59, 60, 61, 62, 63, 64, 65, 66, 67, 68, 69, 70, 71, 72, 73, 74, 75, 76, 77, 78, 79, + 80, 81, 82, 83, 84, 85, 86, 87, 88, 89, 90, 91, 92, 93, 94, 95, 96, 97, 98, 99, 100, 101, 102, 104, 106, 108, 110, 112, 114, 116, 118, 120, 122, + 124, 126, 128, 130, 132, 134, 136, 138, 140, 142, 144, 146, 148, 150, 152, 155, 158, 161, 164, 167, 170, 173, 176, 179, 182, 185, 188, 191, 194, + 197, 200, 203, 207, 211, 215, 219, 223, 227, 231, 235, 239, 243, 247, 251, 255, 260, 265, 270, 275, 280, 285, 290, 295, 300, 305, 311, 317, 323, + 329, 335, 341, 347, 353, 359, 366, 373, 380, 387, 394, 401, 408, 416, 424, 432, 440, 448, 456, 465, 474, 483, 492, 501, 510, 520, 530, 540, 550, + 560, 571, 582, 593, 604, 615, 627, 639, 651, 663, 676, 689, 702, 715, 729, 743, 757, 771, 786, 801, 816, 832, 848, 864, 881, 898, 915, 933, 951, + 969, 988, 1007, 1026, 1046, 1066, 1087, 1108, 1129, 1151, 1173, 1196, 1219, 1243, 1267, 1292, 1317, 1343, 1369, 1396, 1423, 1451, 1479, 1508, + 1537, 1567, 1597, 1628, 1660, 1692, 1725, 1759, 1793, 1828, +]; + +pub const DEFAULT_SCAN_4X4: [u16; 16] = [0, 4, 1, 5, 8, 2, 12, 9, 3, 6, 13, 10, 7, 14, 11, 15]; + +pub const COL_SCAN_4X4: [u16; 16] = [0, 4, 8, 1, 12, 5, 9, 2, 13, 6, 10, 3, 7, 14, 11, 15]; + +pub const ROW_SCAN_4X4: [u16; 16] = [0, 1, 4, 2, 5, 3, 6, 8, 9, 7, 12, 10, 13, 11, 14, 15]; + +pub const COEFBAND_4X4: [u8; 16] = [0, 1, 1, 2, 2, 2, 3, 3, 3, 3, 4, 4, 4, 5, 5, 5]; + +pub const ENERGY_CLASS: [u8; 12] = [0, 1, 2, 3, 3, 4, 4, 5, 5, 5, 5, 5]; + +pub const PARETO_TABLE: [u8; 1024] = [ + 3, 86, 128, 6, 86, 23, 88, 29, 9, 86, 129, 17, 88, 61, 94, 76, 15, 87, 129, 28, 89, 93, 100, 110, 20, 88, 130, 38, 91, 118, 106, 136, 26, 89, + 131, 48, 92, 139, 111, 156, 31, 90, 131, 58, 94, 156, 117, 171, 37, 90, 132, 66, 95, 171, 122, 184, 42, 91, 132, 75, 97, 183, 127, 194, 47, 92, + 133, 83, 98, 193, 132, 202, 52, 93, 133, 90, 100, 201, 137, 208, 57, 94, 134, 98, 101, 208, 142, 214, 62, 94, 135, 105, 103, 214, 146, 218, 66, + 95, 135, 111, 104, 219, 151, 222, 71, 96, 136, 117, 106, 224, 155, 225, 76, 97, 136, 123, 107, 227, 159, 228, 80, 98, 137, 129, 109, 231, 162, + 231, 84, 98, 138, 134, 110, 234, 166, 233, 89, 99, 138, 140, 112, 236, 170, 235, 93, 100, 139, 145, 113, 238, 173, 236, 97, 101, 140, 149, 115, + 240, 176, 238, 101, 102, 140, 154, 116, 242, 179, 239, 105, 103, 141, 158, 118, 243, 182, 240, 109, 104, 141, 162, 119, 244, 185, 241, 113, 104, + 142, 166, 120, 245, 187, 242, 116, 105, 143, 170, 122, 246, 190, 243, 120, 106, 143, 173, 123, 247, 192, 244, 123, 107, 144, 177, 125, 248, 195, + 244, 127, 108, 145, 180, 126, 249, 197, 245, 130, 109, 145, 183, 128, 249, 199, 245, 134, 110, 146, 186, 129, 250, 201, 246, 137, 111, 147, 189, + 131, 251, 203, 246, 140, 112, 147, 192, 132, 251, 205, 247, 143, 113, 148, 194, 133, 251, 207, 247, 146, 114, 149, 197, 135, 252, 208, 248, 149, + 115, 149, 199, 136, 252, 210, 248, 152, 115, 150, 201, 138, 252, 211, 248, 155, 116, 151, 204, 139, 253, 213, 249, 158, 117, 151, 206, 140, 253, + 214, 249, 161, 118, 152, 208, 142, 253, 216, 249, 163, 119, 153, 210, 143, 253, 217, 249, 166, 120, 153, 212, 144, 254, 218, 250, 168, 121, 154, + 213, 146, 254, 220, 250, 171, 122, 155, 215, 147, 254, 221, 250, 173, 123, 155, 217, 148, 254, 222, 250, 176, 124, 156, 218, 150, 254, 223, 250, + 178, 125, 157, 220, 151, 254, 224, 251, 180, 126, 157, 221, 152, 254, 225, 251, 183, 127, 158, 222, 153, 254, 226, 251, 185, 128, 159, 224, 155, + 255, 227, 251, 187, 129, 160, 225, 156, 255, 228, 251, 189, 131, 160, 226, 157, 255, 228, 251, 191, 132, 161, 227, 159, 255, 229, 251, 193, 133, + 162, 228, 160, 255, 230, 252, 195, 134, 163, 230, 161, 255, 231, 252, 197, 135, 163, 231, 162, 255, 231, 252, 199, 136, 164, 232, 163, 255, 232, + 252, 201, 137, 165, 233, 165, 255, 233, 252, 202, 138, 166, 233, 166, 255, 233, 252, 204, 139, 166, 234, 167, 255, 234, 252, 206, 140, 167, 235, + 168, 255, 235, 252, 207, 141, 168, 236, 169, 255, 235, 252, 209, 142, 169, 237, 171, 255, 236, 252, 210, 144, 169, 237, 172, 255, 236, 252, 212, + 145, 170, 238, 173, 255, 237, 252, 214, 146, 171, 239, 174, 255, 237, 253, 215, 147, 172, 240, 175, 255, 238, 253, 216, 148, 173, 240, 176, 255, + 238, 253, 218, 149, 173, 241, 177, 255, 239, 253, 219, 150, 174, 241, 179, 255, 239, 253, 220, 152, 175, 242, 180, 255, 240, 253, 222, 153, 176, + 242, 181, 255, 240, 253, 223, 154, 177, 243, 182, 255, 240, 253, 224, 155, 178, 244, 183, 255, 241, 253, 225, 156, 178, 244, 184, 255, 241, 253, + 226, 158, 179, 244, 185, 255, 242, 253, 228, 159, 180, 245, 186, 255, 242, 253, 229, 160, 181, 245, 187, 255, 242, 253, 230, 161, 182, 246, 188, + 255, 243, 253, 231, 163, 183, 246, 189, 255, 243, 253, 232, 164, 184, 247, 190, 255, 243, 253, 233, 165, 185, 247, 191, 255, 244, 253, 234, 166, + 185, 247, 192, 255, 244, 253, 235, 168, 186, 248, 193, 255, 244, 253, 236, 169, 187, 248, 194, 255, 244, 253, 236, 170, 188, 248, 195, 255, 245, + 253, 237, 171, 189, 249, 196, 255, 245, 254, 238, 173, 190, 249, 197, 255, 245, 254, 239, 174, 191, 249, 198, 255, 245, 254, 240, 175, 192, 249, + 199, 255, 246, 254, 240, 177, 193, 250, 200, 255, 246, 254, 241, 178, 194, 250, 201, 255, 246, 254, 242, 179, 195, 250, 202, 255, 246, 254, 242, + 181, 196, 250, 203, 255, 247, 254, 243, 182, 197, 251, 204, 255, 247, 254, 244, 184, 198, 251, 205, 255, 247, 254, 244, 185, 199, 251, 206, 255, + 247, 254, 245, 186, 200, 251, 207, 255, 247, 254, 246, 188, 201, 252, 207, 255, 248, 254, 246, 189, 202, 252, 208, 255, 248, 254, 247, 191, 203, + 252, 209, 255, 248, 254, 247, 192, 204, 252, 210, 255, 248, 254, 248, 194, 205, 252, 211, 255, 248, 254, 248, 195, 206, 252, 212, 255, 249, 254, + 249, 197, 207, 253, 213, 255, 249, 254, 249, 198, 208, 253, 214, 255, 249, 254, 250, 200, 210, 253, 215, 255, 249, 254, 250, 201, 211, 253, 215, + 255, 249, 254, 250, 203, 212, 253, 216, 255, 249, 254, 251, 204, 213, 253, 217, 255, 250, 254, 251, 206, 214, 254, 218, 255, 250, 254, 252, 207, + 216, 254, 219, 255, 250, 254, 252, 209, 217, 254, 220, 255, 250, 254, 252, 211, 218, 254, 221, 255, 250, 254, 253, 213, 219, 254, 222, 255, 250, + 254, 253, 214, 221, 254, 223, 255, 250, 254, 253, 216, 222, 254, 224, 255, 251, 254, 253, 218, 224, 254, 225, 255, 251, 254, 254, 220, 225, 254, + 225, 255, 251, 254, 254, 222, 227, 255, 226, 255, 251, 254, 254, 224, 228, 255, 227, 255, 251, 254, 254, 226, 230, 255, 228, 255, 251, 254, 255, + 228, 231, 255, 230, 255, 251, 254, 255, 230, 233, 255, 231, 255, 252, 254, 255, 232, 235, 255, 232, 255, 252, 254, 255, 235, 237, 255, 233, 255, + 252, 254, 255, 238, 240, 255, 235, 255, 252, 255, 255, 241, 243, 255, 236, 255, 252, 254, 255, 246, 247, 255, 239, 255, 253, 255, +]; + +pub const KF_PARTITION_PROBS: [u8; 48] = [ + 158, 97, 94, 93, 24, 99, 85, 119, 44, 62, 59, 67, 149, 53, 53, 94, 20, 48, 83, 53, 24, 52, 18, 18, 150, 40, 39, 78, 12, 26, 67, 33, 11, 24, 7, 5, + 174, 35, 49, 68, 11, 27, 57, 15, 9, 12, 3, 3, +]; + +pub const KF_Y_MODE_PROBS: [u8; 900] = [ + 137, 30, 42, 148, 151, 207, 70, 52, 91, 92, 45, 102, 136, 116, 180, 74, 90, 100, 73, 32, 19, 187, 222, 215, 46, 34, 100, 91, 30, 32, 116, 121, + 186, 93, 86, 94, 72, 35, 36, 149, 68, 206, 68, 63, 105, 73, 31, 28, 138, 57, 124, 55, 122, 151, 67, 23, 21, 140, 126, 197, 40, 37, 171, 86, 27, + 28, 128, 154, 212, 45, 43, 53, 74, 32, 27, 107, 86, 160, 63, 134, 102, 59, 67, 44, 140, 161, 202, 78, 67, 119, 63, 36, 126, 146, 123, 158, 60, + 90, 96, 43, 46, 168, 134, 107, 128, 69, 142, 92, 44, 29, 68, 159, 201, 177, 50, 57, 77, 58, 38, 76, 114, 97, 172, 78, 133, 92, 46, 41, 76, 140, + 63, 184, 69, 112, 57, 38, 32, 85, 140, 46, 112, 54, 151, 133, 39, 27, 61, 131, 110, 175, 44, 75, 136, 52, 30, 74, 113, 130, 175, 51, 64, 58, 47, + 35, 80, 100, 74, 143, 64, 163, 74, 36, 61, 116, 114, 128, 162, 80, 125, 82, 82, 26, 26, 171, 208, 204, 44, 32, 105, 55, 44, 68, 166, 179, 192, + 57, 57, 108, 42, 26, 11, 199, 241, 228, 23, 15, 85, 68, 42, 19, 131, 160, 199, 55, 52, 83, 58, 50, 25, 139, 115, 232, 39, 52, 118, 50, 35, 33, + 153, 104, 162, 64, 59, 131, 44, 24, 16, 150, 177, 202, 33, 19, 156, 55, 27, 12, 153, 203, 218, 26, 27, 49, 53, 49, 21, 110, 116, 168, 59, 80, 76, + 38, 72, 19, 168, 203, 212, 50, 50, 107, 103, 26, 36, 129, 132, 201, 83, 80, 93, 59, 38, 83, 112, 103, 162, 98, 136, 90, 62, 30, 23, 158, 200, + 207, 59, 57, 50, 67, 30, 29, 84, 86, 191, 102, 91, 59, 60, 32, 33, 112, 71, 220, 64, 89, 104, 53, 26, 34, 130, 56, 149, 84, 120, 103, 53, 21, 23, + 133, 109, 210, 56, 77, 172, 77, 19, 29, 112, 142, 228, 55, 66, 36, 61, 29, 29, 93, 97, 165, 83, 175, 162, 47, 47, 43, 114, 137, 181, 100, 99, 95, + 69, 23, 29, 128, 83, 199, 46, 44, 101, 53, 40, 55, 139, 69, 183, 61, 80, 110, 40, 29, 19, 161, 180, 207, 43, 24, 91, 60, 34, 19, 105, 61, 198, + 53, 64, 89, 52, 31, 22, 158, 40, 209, 58, 62, 89, 44, 31, 29, 147, 46, 158, 56, 102, 198, 35, 19, 12, 135, 87, 209, 41, 45, 167, 55, 25, 21, 118, + 95, 215, 38, 39, 66, 51, 38, 25, 113, 58, 164, 70, 93, 97, 47, 54, 34, 146, 108, 203, 72, 103, 151, 64, 19, 37, 156, 66, 138, 49, 95, 133, 46, + 27, 80, 150, 55, 124, 55, 121, 135, 36, 23, 27, 165, 149, 166, 54, 64, 118, 53, 21, 36, 131, 63, 163, 60, 109, 81, 40, 26, 35, 154, 40, 185, 51, + 97, 123, 35, 19, 34, 179, 19, 97, 48, 129, 124, 36, 20, 26, 136, 62, 164, 33, 77, 154, 45, 18, 32, 130, 90, 157, 40, 79, 91, 45, 26, 28, 129, 45, + 129, 49, 147, 123, 38, 44, 51, 136, 74, 162, 57, 97, 121, 75, 17, 22, 136, 138, 185, 32, 34, 166, 56, 39, 58, 133, 117, 173, 48, 53, 187, 35, 21, + 12, 161, 212, 207, 20, 23, 145, 56, 29, 19, 117, 109, 181, 55, 68, 112, 47, 29, 17, 153, 64, 220, 59, 51, 114, 46, 16, 24, 136, 76, 147, 41, 64, + 172, 34, 17, 11, 108, 152, 187, 13, 15, 209, 51, 24, 14, 115, 133, 209, 32, 26, 104, 55, 30, 18, 122, 79, 179, 44, 88, 116, 37, 49, 25, 129, 168, + 164, 41, 54, 148, 82, 22, 32, 127, 143, 213, 39, 41, 70, 62, 44, 61, 123, 105, 189, 48, 57, 64, 47, 25, 17, 175, 222, 220, 24, 30, 86, 68, 36, + 17, 106, 102, 206, 59, 74, 74, 57, 39, 23, 151, 68, 216, 55, 63, 58, 49, 30, 35, 141, 70, 168, 82, 40, 115, 51, 25, 15, 136, 129, 202, 38, 35, + 139, 68, 26, 16, 111, 141, 215, 29, 28, 28, 59, 39, 19, 114, 75, 180, 77, 104, 42, 40, 61, 26, 126, 152, 206, 61, 59, 93, 78, 23, 39, 111, 117, + 170, 74, 124, 94, 48, 34, 86, 101, 92, 146, 78, 179, 134, 47, 22, 24, 138, 187, 178, 68, 69, 59, 56, 25, 33, 105, 112, 187, 95, 177, 129, 48, 31, + 27, 114, 63, 183, 82, 116, 56, 43, 28, 37, 121, 63, 123, 61, 192, 169, 42, 17, 24, 109, 97, 177, 56, 76, 122, 58, 18, 28, 105, 139, 182, 70, 92, + 63, 46, 23, 32, 74, 86, 150, 67, 183, 88, 36, 38, 48, 92, 122, 165, 88, 137, 91, 65, 70, 60, 155, 159, 199, 61, 60, 81, 44, 78, 115, 132, 119, + 173, 71, 112, 93, 39, 38, 21, 184, 227, 206, 42, 32, 64, 58, 47, 36, 124, 137, 193, 80, 82, 78, 49, 50, 35, 144, 95, 205, 63, 78, 59, 41, 53, 52, + 148, 71, 142, 65, 128, 51, 40, 36, 28, 143, 143, 202, 40, 55, 137, 52, 34, 29, 129, 183, 227, 42, 35, 43, 42, 44, 44, 104, 105, 164, 64, 130, 80, + 43, 81, 53, 140, 169, 204, 68, 84, 72, +]; + +pub const KF_UV_MODE_PROBS: [u8; 90] = [ + 144, 11, 54, 157, 195, 130, 46, 58, 108, 118, 15, 123, 148, 131, 101, 44, 93, 131, 113, 12, 23, 188, 226, 142, 26, 32, 125, 120, 11, 50, 123, + 163, 135, 64, 77, 103, 113, 9, 36, 155, 111, 157, 32, 44, 161, 116, 9, 55, 176, 76, 96, 37, 61, 149, 115, 9, 28, 141, 161, 167, 21, 25, 193, 120, + 12, 32, 145, 195, 142, 32, 38, 86, 116, 12, 64, 120, 140, 125, 49, 115, 121, 102, 19, 66, 162, 182, 122, 35, 59, 128, +]; + +pub const DEFAULT_SKIP_PROB: [u8; 3] = [192, 128, 64]; + +pub const DEFAULT_COEF_PROBS: [u8; 432] = [ + 195, 29, 183, 84, 49, 136, 8, 42, 71, 0, 0, 0, 0, 0, 0, 0, 0, 0, 31, 107, 169, 35, 99, 159, 17, 82, 140, 8, 66, 114, 2, 44, 76, 1, 19, 32, 40, + 132, 201, 29, 114, 187, 13, 91, 157, 7, 75, 127, 3, 58, 95, 1, 28, 47, 69, 142, 221, 42, 122, 201, 15, 91, 159, 6, 67, 121, 1, 42, 77, 1, 17, 31, + 102, 148, 228, 67, 117, 204, 17, 82, 154, 6, 59, 114, 2, 39, 75, 1, 15, 29, 156, 57, 233, 119, 57, 212, 58, 48, 163, 29, 40, 124, 12, 30, 81, 3, + 12, 31, 191, 107, 226, 124, 117, 204, 25, 99, 155, 0, 0, 0, 0, 0, 0, 0, 0, 0, 29, 148, 210, 37, 126, 194, 8, 93, 157, 2, 68, 118, 1, 39, 69, 1, + 17, 33, 41, 151, 213, 27, 123, 193, 3, 82, 144, 1, 58, 105, 1, 32, 60, 1, 13, 26, 59, 159, 220, 23, 126, 198, 4, 88, 151, 1, 66, 114, 1, 38, 71, + 1, 18, 34, 114, 136, 232, 51, 114, 207, 11, 83, 155, 3, 56, 105, 1, 33, 65, 1, 17, 34, 149, 65, 234, 121, 57, 215, 61, 49, 166, 28, 36, 114, 12, + 25, 76, 3, 16, 42, 214, 49, 220, 132, 63, 188, 42, 65, 137, 0, 0, 0, 0, 0, 0, 0, 0, 0, 85, 137, 221, 104, 131, 216, 49, 111, 192, 21, 87, 155, 2, + 49, 87, 1, 16, 28, 89, 163, 230, 90, 137, 220, 29, 100, 183, 10, 70, 135, 2, 42, 81, 1, 17, 33, 108, 167, 237, 55, 133, 222, 15, 97, 179, 4, 72, + 135, 1, 45, 85, 1, 19, 38, 124, 146, 240, 66, 124, 224, 17, 88, 175, 4, 58, 122, 1, 36, 75, 1, 18, 37, 141, 79, 241, 126, 70, 227, 66, 58, 182, + 30, 44, 136, 12, 34, 96, 2, 20, 47, 229, 99, 249, 143, 111, 235, 46, 109, 192, 0, 0, 0, 0, 0, 0, 0, 0, 0, 82, 158, 236, 94, 146, 224, 25, 117, + 191, 9, 87, 149, 3, 56, 99, 1, 33, 57, 83, 167, 237, 68, 145, 222, 10, 103, 177, 2, 72, 131, 1, 41, 79, 1, 20, 39, 99, 167, 239, 47, 141, 224, + 10, 104, 178, 2, 73, 133, 1, 44, 85, 1, 22, 47, 127, 145, 243, 71, 129, 228, 17, 93, 177, 3, 61, 124, 1, 41, 84, 1, 21, 52, 157, 78, 244, 140, + 72, 231, 69, 58, 184, 31, 44, 137, 14, 38, 105, 8, 23, 61, +]; + +// ---------------------------------------------------------------- inter frames (spec section 10.5) + +/// `inv_map_table` (used by `inv_remap_prob`): the 20 values 7 + 13k first, then every other value +/// 1..=253 in increasing order, then 253 again. +pub fn inv_map_table() -> &'static [u8; 255] { + use std::sync::OnceLock; + static T: OnceLock<[u8; 255]> = OnceLock::new(); + T.get_or_init(|| { + let mut t = [253u8; 255]; + let mut n = 0; + for k in 0..20 { + t[n] = 7 + 13 * k; + n += 1; + } + for v in 1..=253u8 { + if v < 7 || (v - 7) % 13 != 0 { + t[n] = v; + n += 1; + } + } + t + }) +} + +/// The regular 8-tap sub-sample interpolation filters (`subpel_filters[EIGHTTAP]`), 16 positions. +pub const SUBPEL_REGULAR: [[i32; 8]; 16] = [ + [0, 0, 0, 128, 0, 0, 0, 0], + [0, 1, -5, 126, 8, -3, 1, 0], + [-1, 3, -10, 122, 18, -6, 2, 0], + [-1, 4, -13, 118, 27, -9, 3, -1], + [-1, 4, -16, 112, 37, -11, 4, -1], + [-1, 5, -18, 105, 48, -14, 4, -1], + [-1, 5, -19, 97, 58, -16, 5, -1], + [-1, 6, -19, 88, 68, -18, 5, -1], + [-1, 6, -19, 78, 78, -19, 6, -1], + [-1, 5, -18, 68, 88, -19, 6, -1], + [-1, 5, -16, 58, 97, -19, 5, -1], + [-1, 4, -14, 48, 105, -18, 5, -1], + [-1, 4, -11, 37, 112, -16, 4, -1], + [-1, 3, -9, 27, 118, -13, 4, -1], + [0, 2, -6, 18, 122, -10, 3, -1], + [0, 1, -3, 8, 126, -5, 1, 0], +]; + +/// `mv_ref_blocks` (row, column offsets) for BLOCK_8X8 and BLOCK_16X16. +pub const MV_REF_8X8: [(i32, i32); 8] = [(-1, 0), (0, -1), (-1, -1), (-2, 0), (0, -2), (-2, -1), (-1, -2), (-2, -2)]; +pub const MV_REF_16X16: [(i32, i32); 8] = [(-1, 0), (0, -1), (-1, 1), (1, -1), (-1, -1), (-3, 0), (0, -3), (-3, -3)]; + +/// `mode_2_counter` indexed by y mode (intra modes 0..=9, then NEARESTMV, NEARMV, ZEROMV, NEWMV). +pub const MODE_2_COUNTER: [u8; 14] = [9, 9, 9, 9, 9, 9, 9, 9, 9, 9, 0, 0, 3, 1]; + +/// `counter_to_context`; INVALID_CASE entries are 9. +pub const COUNTER_TO_CONTEXT: [u8; 19] = [2, 3, 4, 1, 3, 9, 0, 9, 9, 5, 5, 9, 5, 9, 9, 9, 9, 9, 6]; + +pub const DEFAULT_PARTITION_PROBS: [u8; 48] = [ + 199, 122, 141, 147, 63, 159, 148, 133, 118, 121, 104, 114, 174, 73, 87, 92, 41, 83, 82, 99, 50, 53, 39, 39, 177, 58, 59, 68, 26, 63, 52, 79, 25, + 17, 14, 12, 222, 34, 30, 72, 16, 44, 58, 32, 12, 10, 7, 6, +]; + +pub const DEFAULT_IS_INTER_PROB: [u8; 4] = [9, 102, 187, 225]; + +pub const DEFAULT_SINGLE_REF_PROB: [u8; 10] = [33, 16, 77, 74, 142, 142, 172, 170, 238, 247]; + +pub const DEFAULT_INTER_MODE_PROBS: [u8; 21] = [2, 173, 34, 7, 145, 85, 7, 166, 63, 7, 94, 66, 8, 64, 46, 17, 81, 31, 25, 29, 30]; + +pub const DEFAULT_MV_JOINT_PROBS: [u8; 3] = [32, 64, 96]; +pub const DEFAULT_MV_SIGN_PROB: [u8; 2] = [128, 128]; +pub const DEFAULT_MV_CLASS_PROBS: [u8; 20] = [224, 144, 192, 168, 192, 176, 192, 198, 198, 245, 216, 128, 176, 160, 176, 176, 192, 198, 198, 208]; +pub const DEFAULT_MV_CLASS0_BIT_PROB: [u8; 2] = [216, 208]; +pub const DEFAULT_MV_BITS_PROB: [u8; 20] = [136, 140, 148, 160, 176, 192, 224, 234, 234, 240, 136, 140, 148, 160, 176, 192, 224, 234, 234, 240]; +pub const DEFAULT_MV_CLASS0_FR_PROBS: [u8; 12] = [128, 128, 64, 96, 112, 64, 128, 128, 64, 96, 112, 64]; +pub const DEFAULT_MV_FR_PROBS: [u8; 6] = [64, 96, 64, 64, 96, 64]; diff --git a/crates/vp9enc/src/tile.rs b/crates/vp9enc/src/tile.rs new file mode 100644 index 0000000000..b847a444a5 --- /dev/null +++ b/crates/vp9enc/src/tile.rs @@ -0,0 +1,940 @@ +//! Tile coding: partitions, intra blocks (key frames) and inter blocks (motion search, mode +//! decision, motion vector prediction, residual), recorded as symbols (see [`crate::probs`]). +//! +//! Mode info, motion vector candidate lists and contexts follow the decoding process of the +//! VP9 specification (6.4, 6.4.x `find_mv_refs` / `find_best_ref_mvs`) for the subset of the +//! syntax this encoder emits: 8×8 and 16×16 blocks, single LAST references, no +//! `UsePrevFrameMvs` (inter frames are error resilient), no high-precision vectors. + +use crate::mc::{Mv, PhasePlanes, RefPlane, clamp_for_plane, predict}; +use crate::probs::{self, P, Writer, cost, tree_cost}; +use crate::tables::{ + COEFBAND_4X4, COL_SCAN_4X4, COUNTER_TO_CONTEXT, DEFAULT_SCAN_4X4, ENERGY_CLASS, KF_PARTITION_PROBS, KF_UV_MODE_PROBS, KF_Y_MODE_PROBS, + MODE_2_COUNTER, MV_REF_8X8, MV_REF_16X16, ROW_SCAN_4X4, +}; +use crate::transform::{ADST_ADST, ADST_DCT, DCT_ADST, DCT_DCT, Quant}; +use crate::{BLOCK_8X8, BLOCK_16X16, Mi, Plane}; + +pub const DC_PRED: u8 = 0; +pub const V_PRED: u8 = 1; +pub const H_PRED: u8 = 2; +pub const TM_PRED: u8 = 9; +const MODES: [u8; 4] = [DC_PRED, V_PRED, H_PRED, TM_PRED]; +pub const NEARESTMV: u8 = 10; +pub const NEARMV: u8 = 11; +pub const ZEROMV: u8 = 12; +pub const NEWMV: u8 = 13; + +/// Which reconstructed neighbours are available for intra prediction. +#[derive(Clone, Copy)] +struct IntraEdges { + left: bool, + above: bool, +} + +const PARTITION_TREE: [i8; 6] = [0, 2, -1, 4, -2, -3]; +const INTRA_MODE_TREE: [i8; 18] = [0, 2, -9, 4, -1, 6, 8, 12, -2, 10, -4, -5, -3, 14, -8, 16, -6, -7]; +/// Leaves are `mode - NEARESTMV`. +const INTER_MODE_TREE: [i8; 6] = [-2, 2, 0, 4, -1, -3]; +const MV_JOINT_TREE: [i8; 6] = [0, 2, -1, 4, -2, -3]; +const MV_CLASS_TREE: [i8; 20] = [0, 2, -1, 4, 6, 8, -2, -3, 10, 12, -4, -5, -6, 14, 16, 18, -7, -8, -9, -10]; +const MV_FR_TREE: [i8; 6] = [0, 2, -1, 4, -2, -3]; + +/// extra_bits[token] = (cat, numExtra, base). +const EXTRA_BITS: [(u8, u8, u16); 11] = + [(0, 0, 0), (0, 0, 1), (0, 0, 2), (0, 0, 3), (0, 0, 4), (1, 1, 5), (2, 2, 7), (3, 3, 11), (4, 4, 19), (5, 5, 35), (6, 14, 67)]; +const CAT_PROBS: [&[u8]; 7] = [ + &[], + &[159], + &[165, 145], + &[173, 148, 140], + &[176, 155, 140, 135], + &[180, 157, 141, 134, 130], + &[254, 254, 254, 252, 249, 243, 230, 196, 177, 153, 140, 133, 130, 129], +]; + +/// mode2txfm_map for the intra modes used here (inter blocks use DCT_DCT). +fn mode_tx_type(mode: u8) -> u8 { + match mode { + V_PRED => ADST_DCT, + H_PRED => DCT_ADST, + TM_PRED => ADST_ADST, + _ => DCT_DCT, + } +} + +fn scan(tx_type: u8) -> &'static [u16; 16] { + match tx_type { + ADST_DCT => &ROW_SCAN_4X4, + DCT_ADST => &COL_SCAN_4X4, + _ => &DEFAULT_SCAN_4X4, + } +} + +/// Coefficient context neighbours (9.3.2) of each scan position. +fn neighbors(tx_type: u8) -> [(usize, usize); 16] { + let sc = scan(tx_type); + let mut nb = [(0, 0); 16]; + for (c, &pos) in sc.iter().enumerate().skip(1) { + let pos = pos as usize; + let (i, j) = (pos / 4, pos % 4); + nb[c] = if i > 0 && j > 0 { + let a = (i - 1) * 4 + j; + let b = i * 4 + j - 1; + match tx_type { + DCT_ADST => (a, a), + ADST_DCT => (b, b), + _ => (a, b), + } + } else if i > 0 { + ((i - 1) * 4 + j, (i - 1) * 4 + j) + } else { + (j - 1, j - 1) + }; + } + nb +} + +fn eob_of(levels: &[i32; 16], tx_type: u8) -> usize { + let sc = scan(tx_type); + (0..16).rev().find(|&i| levels[sc[i] as usize] != 0).map_or(0, |i| i + 1) +} + +/// Rough coefficient bit cost (1/256 bit) for rate-distortion decisions. +fn levels_cost(levels: &[i32; 16], eob: usize) -> u32 { + if eob == 0 { + return 0; + } + let mut bits = 256; + for &l in levels { + let a = l.unsigned_abs(); + bits += if a == 0 { 160 } else { 640 + 384 * (32 - a.leading_zeros()) }; + } + bits +} + +/// Intra prediction (8.5.1) of a square block for DC / V / H / TM. +fn predict_intra(p: &Plane, x: usize, y: usize, log2: usize, mode: u8, edges: IntraEdges) -> [i32; 64] { + let IntraEdges { left: have_left, above: have_above } = edges; + let size = 1usize << log2; + let base = 128i32; + let buf = &p.rec; + let w = p.w; + let mut above = [0i32; 8]; + let mut left = [0i32; 8]; + if have_above { + for i in 0..size { + above[i] = buf[(y - 1) * w + (x + i).min(p.max_x)] as i32; + } + } else { + above[..size].fill(base - 1); + } + let top_left = if have_above && have_left { + buf[(y - 1) * w + (x - 1).min(p.max_x)] as i32 + } else if have_above { + base + 1 + } else { + base - 1 + }; + if have_left { + for i in 0..size { + left[i] = buf[(y + i).min(p.max_y) * w + x - 1] as i32; + } + } else { + left[..size].fill(base + 1); + } + let mut out = [0i32; 64]; + for i in 0..size { + for j in 0..size { + out[i * size + j] = match mode { + V_PRED => above[j], + H_PRED => left[i], + TM_PRED => (above[j] + left[i] - top_left).clamp(0, 255), + _ => 0, + }; + } + } + if mode == DC_PRED { + let v = match (have_left, have_above) { + (true, true) => (left[..size].iter().sum::() + above[..size].iter().sum::() + size as i32) >> (log2 + 1), + (true, false) => (left[..size].iter().sum::() + (1 << (log2 - 1))) >> log2, + (false, true) => (above[..size].iter().sum::() + (1 << (log2 - 1))) >> log2, + (false, false) => base, + }; + out[..size * size].fill(v); + } + out +} + +// ---------------------------------------------------------------- motion vector coding + +/// `mv_class` of an offset (|component| - 1). +fn mv_class(offset: u32) -> u32 { + if offset < 16 { 0 } else { (31 - offset.leading_zeros()) - 3 } +} + +fn mv_component_cost(i: usize, v: i32) -> u32 { + let d = &probs_defaults().0; + let offset = v.unsigned_abs() - 1; + let c = mv_class(offset); + let mut bits = cost(v < 0, d[probs::MV_SIGN + i]); + bits += tree_cost(&MV_CLASS_TREE, &d[probs::MV_CLASS + i * 10..], c as u8); + let fr = ((offset >> 1) & 3) as u8; + if c == 0 { + let b = offset >> 3; + bits += cost(b == 1, d[probs::MV_CLASS0_BIT + i]); + bits += tree_cost(&MV_FR_TREE, &d[probs::MV_CLASS0_FR + i * 6 + b as usize * 3..], fr); + } else { + let dd = (offset - (1 << (c + 3))) >> 3; + for b in 0..c { + bits += cost((dd >> b) & 1 == 1, d[probs::MV_BITS + i * 10 + b as usize]); + } + bits += tree_cost(&MV_FR_TREE, &d[probs::MV_FR + i * 3..], fr); + } + bits +} + +const MV_TABLE: i32 = 8192; + +/// Component costs for |v| ≤ MV_TABLE (index v + MV_TABLE) and the four joint costs. +fn mv_tables() -> &'static ([Vec; 2], [u32; 4]) { + use std::sync::OnceLock; + static T: OnceLock<([Vec; 2], [u32; 4])> = OnceLock::new(); + T.get_or_init(|| { + let comp = std::array::from_fn(|i| (-MV_TABLE..=MV_TABLE).map(|v| if v == 0 { 0 } else { mv_component_cost(i, v) }).collect()); + let d = &probs_defaults().0; + let joints = std::array::from_fn(|j| tree_cost(&MV_JOINT_TREE, &d[probs::MV_JOINT..], j as u8)); + (comp, joints) + }) +} + +/// Bits (1/256) of a NEWMV difference with the default probabilities. +pub fn mv_cost(diff: Mv) -> u32 { + let (comp, joints) = mv_tables(); + let joint = (diff.row != 0) as usize * 2 + (diff.col != 0) as usize; + let c = |i: usize, v: i32| if v.abs() <= MV_TABLE { comp[i][(v + MV_TABLE) as usize] } else { mv_component_cost(i, v) }; + joints[joint] + if diff.row != 0 { c(0, diff.row) } else { 0 } + if diff.col != 0 { c(1, diff.col) } else { 0 } +} + +fn probs_defaults() -> &'static probs::Probs { + use std::sync::OnceLock; + static D: OnceLock = OnceLock::new(); + D.get_or_init(probs::Probs::defaults) +} + +/// SAD of two N×N blocks: per-column 16-bit sums (at most 16 · 255), summed once. +#[inline(always)] +fn sad_n(a: &[u8], sa: usize, b: &[u8], sb: usize) -> u32 { + let mut acc = [0u16; N]; + for i in 0..N { + let (Some(ra), Some(rb)) = (a.get(i * sa..).and_then(|r| r.first_chunk::()), b.get(i * sb..).and_then(|r| r.first_chunk::())) else { + return u32::MAX / 4; + }; + for j in 0..N { + acc[j] += ra[j].abs_diff(rb[j]) as u16; + } + } + acc.iter().map(|&v| v as u32).sum() +} + +// ---------------------------------------------------------------- the tile + +/// Per-frame coding parameters shared by the tiles. +pub struct FrameParams<'a> { + pub key: bool, + pub quant: Quant, + pub mi_cols: usize, + pub mi_rows: usize, + /// Reference planes (inter frames). + pub refs: Option<[RefPlane<'a>; 3]>, + /// The reference luma at every quarter-sample phase (motion search). + pub phases: Option, + /// Encoder-side motion hints from the previous frame (by MI position). + pub prev_mvs: Option<&'a [Mv]>, + /// Lagrangian multipliers: per bit, in SAD and in squared-error units. + pub lambda_sad: f64, + pub lambda_sse: f64, +} + +pub struct Tile<'a> { + pub f: &'a FrameParams<'a>, + pub w: Writer, + pub planes: &'a mut [Plane; 3], + pub mi: &'a mut [Mi], + pub col_start: usize, + pub col_end: usize, + above_nz: [Vec; 3], + left_nz: [Vec; 3], + above_part: Vec, + left_part: Vec, + /// Motion vectors of the four 8×8 blocks evaluated for the current 16×16 area. + pending: [(usize, usize, Mv); 4], +} + +impl<'a> Tile<'a> { + pub fn new(f: &'a FrameParams<'a>, planes: &'a mut [Plane; 3], mi: &'a mut [Mi], col_start: usize, col_end: usize) -> Tile<'a> { + let n = f.mi_cols * 2 + 16; + Tile { + f, + w: Writer::default(), + planes, + mi, + col_start, + col_end, + above_nz: [vec![0; n], vec![0; n], vec![0; n]], + left_nz: [vec![0; 16], vec![0; 16], vec![0; 16]], + above_part: vec![0; f.mi_cols + 8], + left_part: vec![0; 8], + pending: [(usize::MAX, 0, Mv::ZERO); 4], + } + } + + /// Code the tile's superblocks. + pub fn code(&mut self) { + for r in (0..self.f.mi_rows).step_by(8) { + for p in 0..3 { + self.left_nz[p].fill(0); + } + self.left_part.fill(0); + for c in (self.col_start..self.col_end).step_by(8) { + self.partition(r, c, 12); + } + } + } + + fn mi_at(&self, r: usize, c: usize) -> Mi { + self.mi[r * self.f.mi_cols + c] + } + + fn set_mi(&mut self, r: usize, c: usize, n8: usize, m: Mi) { + for dr in 0..n8 { + for dc in 0..n8 { + if r + dr < self.f.mi_rows && c + dc < self.f.mi_cols { + self.mi[(r + dr) * self.f.mi_cols + c + dc] = m; + } + } + } + } + + fn partition(&mut self, r: usize, c: usize, bsize: u8) { + let (mi_rows, mi_cols) = (self.f.mi_rows, self.f.mi_cols); + if r >= mi_rows || c >= mi_cols { + return; + } + let num8 = [1usize, 1, 1, 1, 1, 2, 2, 2, 4, 4, 4, 8, 8][bsize as usize]; + let half = num8 >> 1; + let has_rows = (r + half) < mi_rows; + let has_cols = (c + half) < mi_cols; + let bsl = [0usize, 0, 0, 0, 0, 1, 1, 1, 2, 2, 2, 3, 3][bsize as usize]; + let boffset = 3 - bsl; + let mut above = 0u8; + let mut left = 0u8; + for i in 0..num8 { + above |= self.above_part[c + i]; + left |= self.left_part[(r & 7) + i]; + } + let ctx = bsl * 4 + (((left >> boffset) & 1) as usize) * 2 + ((above >> boffset) & 1) as usize; + let key = self.f.key; + let prob = |i: usize| if key { P::Fixed(KF_PARTITION_PROBS[ctx * 3 + i]) } else { P::Ctx((probs::PARTITION + ctx * 3 + i) as u16) }; + if bsize == BLOCK_8X8 { + self.w.put(false, prob(0)); // PARTITION_NONE + if key { + self.intra_block(r, c); + } else { + let mv = self.pending.iter().find(|p| p.0 == r && p.1 == c).map(|p| p.2); + let mv = mv.unwrap_or_else(|| self.search(r, c, BLOCK_8X8).0); + self.inter_block(r, c, BLOCK_8X8, mv); + } + self.above_part[c] = 15 >> 1; + self.left_part[r & 7] = 15 >> 1; + return; + } + if !key && bsize == BLOCK_16X16 && has_rows && has_cols { + if let Some(mv) = self.decide16(r, c) { + self.w.put(false, prob(0)); // PARTITION_NONE + self.inter_block(r, c, BLOCK_16X16, mv); + for i in 0..2 { + self.above_part[c + i] = 15 >> 2; + self.left_part[(r & 7) + i] = 15 >> 2; + } + return; + } + } else if !key && bsize == BLOCK_16X16 { + self.pending = [(usize::MAX, 0, Mv::ZERO); 4]; + } + // PARTITION_SPLIT. + if has_rows && has_cols { + self.w.tree(&PARTITION_TREE, 3, prob); + } else if has_cols { + self.w.put(true, prob(1)); + } else if has_rows { + self.w.put(true, prob(2)); + } + let sub = bsize - 3; + self.partition(r, c, sub); + self.partition(r, c + half, sub); + self.partition(r + half, c, sub); + self.partition(r + half, c + half, sub); + } + + // ------------------------------------------------------------ intra (key frames) + + fn intra_block(&mut self, r: usize, c: usize) { + let avail_u = r > 0; + let avail_l = c > self.col_start; + let y_mode = self.choose_intra(0, c * 8, r * 8, 3, IntraEdges { left: avail_l, above: avail_u }); + let uv_mode = { + let mut best = (i64::MAX, DC_PRED); + for m in MODES { + let cost: i64 = (1..3).map(|p| self.intra_cost(p, c * 4, r * 4, 2, m, IntraEdges { left: avail_l, above: avail_u })).sum(); + if cost < best.0 { + best = (cost, m); + } + } + best.1 + }; + // skip = 0 (neighbours never skip in key frames: context 0); tx_size implied (4×4). + self.w.ctx(false, probs::SKIP); + let above_mode = if avail_u { self.mi_at(r - 1, c).mode } else { DC_PRED }; + let left_mode = if avail_l { self.mi_at(r, c - 1).mode } else { DC_PRED }; + let o = (above_mode as usize * 10 + left_mode as usize) * 9; + self.w.tree(&INTRA_MODE_TREE, y_mode, |i| P::Fixed(KF_Y_MODE_PROBS[o + i])); + let o = y_mode as usize * 9; + self.w.tree(&INTRA_MODE_TREE, uv_mode, |i| P::Fixed(KF_UV_MODE_PROBS[o + i])); + self.set_mi(r, c, 1, Mi { inter: false, mode: y_mode, mv: Mv::ZERO, skip: false, size: BLOCK_8X8 }); + // Residual: luma 2×2 transform blocks, then one 4×4 block per chroma plane. + for (dy, dx) in [(0, 0), (0, 1), (1, 0), (1, 1)] { + let tx = if self.f.quant.lossless { DCT_DCT } else { mode_tx_type(y_mode) }; + self.intra_tx(0, c * 8 + dx * 4, r * 8 + dy * 4, y_mode, tx, IntraEdges { left: avail_l || dx > 0, above: avail_u || dy > 0 }); + } + for p in 1..3 { + self.intra_tx(p, c * 4, r * 4, uv_mode, DCT_DCT, IntraEdges { left: avail_l, above: avail_u }); + } + } + + fn intra_cost(&self, plane: usize, x: usize, y: usize, log2: usize, mode: u8, edges: IntraEdges) -> i64 { + let p = &self.planes[plane]; + let pred = predict_intra(p, x, y, log2, mode, edges); + let size = 1 << log2; + let mut s = 0i64; + for i in 0..size { + for j in 0..size { + s += (p.src[(y + i) * p.w + x + j] as i32 - pred[i * size + j]).abs() as i64; + } + } + s + } + + fn choose_intra(&self, plane: usize, x: usize, y: usize, log2: usize, edges: IntraEdges) -> u8 { + let mut best = (i64::MAX, DC_PRED); + for m in MODES { + let cost = self.intra_cost(plane, x, y, log2, m, edges); + if cost < best.0 { + best = (cost, m); + } + } + best.1 + } + + fn intra_tx(&mut self, plane: usize, x: usize, y: usize, mode: u8, tx_type: u8, edges: IntraEdges) { + let ss = (plane > 0) as usize; + let max_x = (self.f.mi_cols * 8) >> ss; + let max_y = (self.f.mi_rows * 8) >> ss; + let (x4, y4) = (x >> 2, y >> 2); + let ly = y4 & (15 >> ss); + if x >= max_x || y >= max_y { + self.above_nz[plane][x4] = 0; + self.left_nz[plane][ly] = 0; + return; + } + let p = &self.planes[plane]; + let pred = predict_intra(p, x, y, 2, mode, edges); + let mut res = [0i32; 16]; + for i in 0..4 { + for j in 0..4 { + res[i * 4 + j] = p.src[(y + i) * p.w + x + j] as i32 - pred[i * 4 + j]; + } + } + let q = Quant { round_dc: 0.5, round_ac: 0.38, ..self.f.quant }; + let levels = q.levels(&res, tx_type); + let eob = eob_of(&levels, tx_type); + let ctx0 = (self.above_nz[plane][x4] + self.left_nz[plane][ly]) as usize; + self.tokens(plane, false, tx_type, &levels, eob, ctx0); + let resid = q.residual(&levels, tx_type, eob); + let p = &mut self.planes[plane]; + for i in 0..4 { + for j in 0..4 { + p.rec[(y + i) * p.w + x + j] = (pred[i * 4 + j] + resid[i * 4 + j]).clamp(0, 255) as u8; + } + } + let nz = (eob > 0) as u8; + self.above_nz[plane][x4] = nz; + self.left_nz[plane][ly] = nz; + } + + fn tokens(&mut self, plane: usize, inter: bool, tx_type: u8, levels: &[i32; 16], eob: usize, ctx0: usize) { + let sc = scan(tx_type); + let nb = neighbors(tx_type); + let ptype = (plane > 0) as usize; + let mut tc = [0u8; 16]; + let mut check_eob = true; + let mut c = 0; + while c < 16 { + let pos = sc[c] as usize; + let band = COEFBAND_4X4[c] as usize; + let ctx = if c == 0 { ctx0 } else { ((1 + tc[nb[c].0] + tc[nb[c].1]) >> 1) as usize }; + let o = probs::coef_index(ptype, inter, band, ctx); + if check_eob { + let more = c < eob; + self.w.ctx(more, o); + if !more { + break; + } + } + let v = levels[pos].unsigned_abs(); + if v == 0 { + self.w.ctx(false, o + 1); + tc[pos] = 0; + check_eob = false; + c += 1; + continue; + } + self.w.ctx(true, o + 1); + check_eob = true; + let token = match v { + 1 => 1, + 2 => 2, + 3 => 3, + 4 => 4, + 5..=6 => 5, + 7..=10 => 6, + 11..=18 => 7, + 19..=34 => 8, + 35..=66 => 9, + _ => 10, + }; + if token == 1 { + self.w.ctx(false, o + 2); + } else { + self.w.ctx(true, o + 2); + let path: &[(u8, bool)] = match token { + 2 => &[(0, false), (1, false)], + 3 => &[(0, false), (1, true), (2, false)], + 4 => &[(0, false), (1, true), (2, true)], + 5 => &[(0, true), (3, false), (4, false)], + 6 => &[(0, true), (3, false), (4, true)], + 7 => &[(0, true), (3, true), (5, false), (6, false)], + 8 => &[(0, true), (3, true), (5, false), (6, true)], + 9 => &[(0, true), (3, true), (5, true), (7, false)], + _ => &[(0, true), (3, true), (5, true), (7, true)], + }; + for &(n, b) in path { + self.w.put(b, P::Pareto((o + 2) as u16, n)); + } + let (cat, num_extra, base_v) = EXTRA_BITS[token]; + let extra = (v - base_v as u32).min((1 << num_extra) - 1); + let cp = CAT_PROBS[cat as usize]; + for e in 0..num_extra as usize { + self.w.fixed((extra >> (num_extra as usize - 1 - e)) & 1 != 0, cp[e]); + } + } + tc[pos] = ENERGY_CLASS[token]; + self.w.fixed(levels[pos] < 0, 128); + c += 1; + } + } + + // ------------------------------------------------------------ inter + + fn is_inside(&self, r: i32, c: i32) -> bool { + r >= 0 && (r as usize) < self.f.mi_rows && c >= self.col_start as i32 && (c as usize) < self.col_end + } + + /// `find_mv_refs` + `find_best_ref_mvs` for LAST_FRAME: (NearestMv, NearMv, mode context). + fn mv_refs(&self, r: usize, c: usize, bsize: u8) -> (Mv, Mv, usize) { + let search = if bsize == BLOCK_16X16 { &MV_REF_16X16 } else { &MV_REF_8X8 }; + let mut list = [Mv::ZERO; 2]; + let mut count = 0; + let add = |mv: Mv, list: &mut [Mv; 2], count: &mut usize| { + if *count >= 2 || (*count > 0 && mv == list[0]) { + return; + } + list[*count] = mv; + *count += 1; + }; + let mut counter = 0usize; + for (i, &(dr, dc)) in search.iter().enumerate() { + let (cr, cc) = (r as i32 + dr, c as i32 + dc); + if !self.is_inside(cr, cc) { + continue; + } + let m = self.mi_at(cr as usize, cc as usize); + if i < 2 { + counter += MODE_2_COUNTER[m.mode as usize] as usize; + } + if m.inter { + add(m.mv, &mut list, &mut count); + } + } + let ctx = COUNTER_TO_CONTEXT[counter.min(18)] as usize; + let n8 = if bsize == BLOCK_16X16 { 2 } else { 1 }; + let clamp = |mv: Mv, border: i32| -> Mv { + let (r, c, b) = (r as i32, c as i32, n8); + let to_top = -(r * 64); + let to_bottom = (self.f.mi_rows as i32 - b - r) * 64; + let to_left = -(c * 64); + let to_right = (self.f.mi_cols as i32 - b - c) * 64; + Mv::new( + mv.row.clamp(to_top - border, (to_bottom + border).max(to_top - border)), + mv.col.clamp(to_left - border, (to_right + border).max(to_left - border)), + ) + }; + for mv in list.iter_mut() { + *mv = clamp(*mv, 128); + // No high precision: odd components move toward zero. + let even = |v: i32| if v & 1 != 0 { v + if v > 0 { -1 } else { 1 } } else { v }; + *mv = clamp(Mv::new(even(mv.row), even(mv.col)), (160 - 4) << 3); + } + (list[0], list[1], ctx) + } + + /// The allowed motion vector range of a block (no decoder clamping inside it). + fn mv_range(&self, r: usize, c: usize, n: i32) -> (i32, i32, i32, i32) { + let n8 = n / 8; + let (r, c) = (r as i32, c as i32); + let lo_c = -(c * 64) - (4 + n) * 8; + let hi_c = (self.f.mi_cols as i32 - n8 - c) * 64 + (4 + n) * 8 - 8; + let lo_r = -(r * 64) - (4 + n) * 8; + let hi_r = (self.f.mi_rows as i32 - n8 - r) * 64 + (4 + n) * 8 - 8; + let lim = 8 * 1000; + (lo_r.max(-lim), hi_r.min(lim), lo_c.max(-lim), hi_c.min(lim)) + } + + /// SAD of the luma prediction at `mv` against the source. + fn sad(&self, r: usize, c: usize, n: usize, mv: Mv) -> u32 { + let p = &self.planes[0]; + let (x16, y16) = (((c * 8) << 4) as i32 + 2 * mv.col, ((r * 8) << 4) as i32 + 2 * mv.row); + let mut s = 0u32; + if let Some((pl, o, st)) = self.f.phases.as_ref().and_then(|ph| ph.block(x16, y16, n)) { + let a = &p.src[(r * 8) * p.w + c * 8..]; + let b = &pl[o..]; + return match n { + 8 => sad_n::<8>(a, p.w, b, st), + 16 => sad_n::<16>(a, p.w, b, st), + _ => { + for i in 0..n { + s += a[i * p.w..][..n].iter().zip(&b[i * st..][..n]).map(|(a, b)| a.abs_diff(*b) as u32).sum::(); + } + s + } + }; + } + // Inter search only runs on frames with a reference. + let Some(refs) = self.f.refs.as_ref() else { return u32::MAX / 4 }; + let mut pred = [0u8; 256]; + predict(&refs[0], x16, y16, n, n, &mut pred); + for i in 0..n { + let a = &p.src[(r * 8 + i) * p.w + c * 8..][..n]; + s += a.iter().zip(&pred[i * n..i * n + n]).map(|(a, b)| (*a as i32 - *b as i32).unsigned_abs()).sum::(); + } + s + } + + /// Motion search for the block at (`r`, `c`): the best vector and its cost + /// (SAD + λ·bits). + fn search(&self, r: usize, c: usize, bsize: u8) -> (Mv, f64) { + let n = if bsize == BLOCK_16X16 { 16 } else { 8 }; + let (nearest, near, mctx) = self.mv_refs(r, c, bsize); + let (lo_r, hi_r, lo_c, hi_c) = self.mv_range(r, c, n as i32); + let inside = |mv: Mv| mv.row >= lo_r && mv.row <= hi_r && mv.col >= lo_c && mv.col <= hi_c; + let lambda = self.f.lambda_sad / 256.0; + let d = &probs_defaults().0; + let mb: [u32; 4] = std::array::from_fn(|m| tree_cost(&INTER_MODE_TREE, &d[probs::INTER_MODE + mctx * 3..], m as u8)); + let mode_bits = |m: u8| mb[(m - NEARESTMV) as usize]; + let cost_raw = |mv: Mv| -> f64 { + let bits = if mv == nearest { + mode_bits(NEARESTMV) + } else if mv == Mv::ZERO { + mode_bits(ZEROMV) + } else if mv == near { + mode_bits(NEARMV) + } else { + mode_bits(NEWMV) + mv_cost(mv.minus(nearest)) + }; + self.sad(r, c, n, mv) as f64 + lambda * bits as f64 + }; + // The cost is a pure function of the vector: points the search revisits (the centre it + // came from, candidates on the diamond) are looked up instead of predicted again. + let mut seen = [(Mv::ZERO, 0.0f64); 64]; + let mut nseen = 0usize; + let mut cost = |mv: Mv| -> f64 { + if let Some(&(_, j)) = seen[..nseen].iter().find(|e| e.0 == mv) { + return j; + } + let j = cost_raw(mv); + if nseen < seen.len() { + seen[nseen] = (mv, j); + nseen += 1; + } + j + }; + let mut best = (Mv::ZERO, cost(Mv::ZERO)); + let prev = self.f.prev_mvs.map(|p| p[r * self.f.mi_cols + c]); + for cand in [Some(nearest), Some(near), prev].into_iter().flatten() { + if cand != Mv::ZERO && inside(cand) { + let j = cost(cand); + if j < best.1 { + best = (cand, j); + } + } + } + // A perfect match needs no search. + let good_enough = lambda * 256.0 * 0.5; + if best.1 > good_enough { + // Whole-sample search from the best candidate: a coarse cross, then a small diamond. + let round8 = |v: i32| (v + 4).div_euclid(8) * 8; + let mut cur = Mv::new(round8(best.0.row), round8(best.0.col)); + let mut cur_j = if inside(cur) { cost(cur) } else { f64::MAX }; + if cur_j < best.1 { + best = (cur, cur_j); + } else { + cur = best.0; + cur_j = best.1; + } + for (step, max_iters) in [(16, 1), (8, 2), (4, 3), (2, 4), (1, 8)] { + let s = step * 8; + for _ in 0..max_iters { + let mut moved = false; + let center = cur; + for (dr, dc) in [(-s, 0), (s, 0), (0, -s), (0, s)] { + let cand = Mv::new(center.row + dr, center.col + dc); + if !inside(cand) { + continue; + } + let j = cost(cand); + if j < cur_j { + cur = cand; + cur_j = j; + moved = true; + } + } + if !moved { + break; + } + } + } + if cur_j < best.1 { + best = (cur, cur_j); + } + } + // Sub-sample refinement: half then quarter samples (1/8 units, even: no high precision). + for s in [4, 2] { + let center = best.0; + for (dr, dc) in [(-s, 0), (s, 0), (0, -s), (0, s), (-s, -s), (-s, s), (s, -s), (s, s)] { + let cand = Mv::new(center.row + dr, center.col + dc); + if !inside(cand) { + continue; + } + let j = cost(cand); + if j < best.1 { + best = (cand, j); + } + } + } + best + } + + /// Choose between one 16×16 block and four 8×8 blocks at (`r`, `c`): `Some(mv)` for 16×16. + fn decide16(&mut self, r: usize, c: usize) -> Option { + let (mv16, j16) = self.search(r, c, BLOCK_16X16); + let lambda = self.f.lambda_sad; + // A near-perfect 16×16 prediction needs no split. + if j16 < 2.0 * 256.0 / 256.0 * 16.0 { + return Some(mv16); + } + let mut j8 = lambda * 4.0; // partition split + three more blocks of mode info (≈ 4 bits) + for (k, (dr, dc)) in [(0, 0), (0, 1), (1, 0), (1, 1)].into_iter().enumerate() { + let (rr, cc) = (r + dr, c + dc); + let (mv, j) = self.search(rr, cc, BLOCK_8X8); + j8 += j; + self.pending[k] = (rr, cc, mv); + // Tentative mode info so the next 8×8 blocks see this one as a candidate. + self.set_mi(rr, cc, 1, Mi { inter: true, mode: NEWMV, mv, skip: false, size: BLOCK_8X8 }); + } + if j16 <= j8 { + self.pending = [(usize::MAX, 0, Mv::ZERO); 4]; + Some(mv16) + } else { + None + } + } + + /// Code an inter block with motion vector `mv`. + fn inter_block(&mut self, r: usize, c: usize, bsize: u8, mv: Mv) { + let (mi_rows, mi_cols) = (self.f.mi_rows, self.f.mi_cols); + let n8 = if bsize == BLOCK_16X16 { 2 } else { 1 }; + let n = 8 * n8; + let (nearest, near, mctx) = self.mv_refs(r, c, bsize); + let d = &probs_defaults().0; + let mode_bits = |m: u8| tree_cost(&INTER_MODE_TREE, &d[probs::INTER_MODE + mctx * 3..], m - NEARESTMV); + let mut mode = (NEWMV, mode_bits(NEWMV) + mv_cost(mv.minus(nearest))); + for (m, ok) in [(NEARESTMV, mv == nearest), (NEARMV, mv == near), (ZEROMV, mv == Mv::ZERO)] { + if ok && mode_bits(m) < mode.1 { + mode = (m, mode_bits(m)); + } + } + let mode = mode.0; + // Prediction of every plane (written straight into the reconstruction). + let Some(refs) = self.f.refs.as_ref() else { return }; + for plane in 0..3 { + let ch = plane > 0; + let (row16, col16) = clamp_for_plane(mv, r, c, n8, mi_rows, mi_cols, ch); + let bn = if ch { n / 2 } else { n }; + let (bx, by) = if ch { (c * 4, r * 4) } else { (c * 8, r * 8) }; + let mut pred = [0u8; 256]; + predict(&refs[plane], ((bx as i32) << 4) + col16, ((by as i32) << 4) + row16, bn, bn, &mut pred); + let p = &mut self.planes[plane]; + for i in 0..bn { + p.rec[(by + i) * p.w + bx..][..bn].copy_from_slice(&pred[i * bn..i * bn + bn]); + } + } + // Residual: quantise every 4×4, dropping blocks whose coefficients don't pay for themselves. + let q = Quant { round_dc: 0.5, round_ac: 0.42, ..self.f.quant }; + // Weight of the rate term when dropping a transform block's coefficients (low: keep quality on par + // with key frames at the same q index). + let zl = 0.07; + let tx = DCT_DCT; + let mut blocks: Vec<(usize, usize, usize, [i32; 16], usize)> = Vec::with_capacity(24); + let mut any = false; + for plane in 0..3 { + let ch = plane > 0; + let bn = if ch { n / 2 } else { n }; + let (bx, by) = if ch { (c * 4, r * 4) } else { (c * 8, r * 8) }; + let p = &self.planes[plane]; + for ty in (0..bn).step_by(4) { + for tx4 in (0..bn).step_by(4) { + let (x, y) = (bx + tx4, by + ty); + let mut res = [0i32; 16]; + for i in 0..4 { + for j in 0..4 { + let k = (y + i) * p.w + x + j; + res[i * 4 + j] = p.src[k] as i32 - p.rec[k] as i32; + } + } + let mut levels = q.levels(&res, tx); + let mut eob = eob_of(&levels, tx); + if eob > 0 && !q.lossless { + let back = q.residual(&levels, tx, eob); + let d_skip: i64 = res.iter().map(|v| (*v as i64) * (*v as i64)).sum(); + let d_code: i64 = res.iter().zip(&back).map(|(a, b)| ((a - b) as i64).pow(2)).sum(); + if d_skip as f64 <= d_code as f64 + zl * self.f.lambda_sse * levels_cost(&levels, eob) as f64 / 256.0 { + levels = [0; 16]; + eob = 0; + } + } + any |= eob > 0; + blocks.push((plane, x, y, levels, eob)); + } + } + } + let skip = !any; + // Mode info. + let avail_u = r > 0; + let avail_l = c > self.col_start; + let above = avail_u.then(|| self.mi_at(r - 1, c)); + let left = avail_l.then(|| self.mi_at(r, c - 1)); + let skip_ctx = above.is_some_and(|m| m.skip) as usize + left.is_some_and(|m| m.skip) as usize; + self.w.ctx(skip, probs::SKIP + skip_ctx); + // is_inter context: neighbours are inter (no intra blocks in inter frames). + let intra = |m: Option| m.is_some_and(|m| !m.inter); + let inter_ctx = match (above, left) { + (Some(_), Some(_)) => { + if intra(above) && intra(left) { + 3 + } else { + (intra(above) || intra(left)) as usize + } + } + (Some(_), None) => 2 * intra(above) as usize, + (None, Some(_)) => 2 * intra(left) as usize, + (None, None) => 0, + }; + self.w.ctx(true, probs::IS_INTER + inter_ctx); + // single_ref_p1 = 0 (LAST_FRAME); context for single-reference LAST neighbours. + let ref_ctx = match (above, left) { + (Some(a), Some(l)) => match (a.inter, l.inter) { + (false, false) => 2, + (false, true) => 4, + (true, false) => 4, + (true, true) => 4, + }, + (Some(m), None) | (None, Some(m)) => { + if m.inter { + 4 + } else { + 2 + } + } + (None, None) => 2, + }; + self.w.ctx(false, probs::SINGLE_REF + ref_ctx * 2); + self.w.tree(&INTER_MODE_TREE, mode - NEARESTMV, |i| P::Ctx((probs::INTER_MODE + mctx * 3 + i) as u16)); + if mode == NEWMV { + self.write_mv(mv.minus(nearest)); + } + self.set_mi(r, c, n8, Mi { inter: true, mode, mv, skip, size: bsize }); + // Tokens and reconstruction. + for (plane, x, y, levels, eob) in blocks { + let ss = (plane > 0) as usize; + let (x4, ly) = (x >> 2, (y >> 2) & (15 >> ss)); + if !skip { + let ctx0 = (self.above_nz[plane][x4] + self.left_nz[plane][ly]) as usize; + self.tokens(plane, true, tx, &levels, eob, ctx0); + if eob > 0 { + let resid = q.residual(&levels, tx, eob); + let p = &mut self.planes[plane]; + for i in 0..4 { + for j in 0..4 { + let k = (y + i) * p.w + x + j; + p.rec[k] = (p.rec[k] as i32 + resid[i * 4 + j]).clamp(0, 255) as u8; + } + } + } + } + let nz = (!skip && eob > 0) as u8; + self.above_nz[plane][x4] = nz; + self.left_nz[plane][ly] = nz; + } + } + + fn write_mv(&mut self, diff: Mv) { + let joint = (diff.row != 0) as u8 * 2 + (diff.col != 0) as u8; + self.w.tree(&MV_JOINT_TREE, joint, |i| P::Ctx((probs::MV_JOINT + i) as u16)); + if diff.row != 0 { + self.write_mv_component(0, diff.row); + } + if diff.col != 0 { + self.write_mv_component(1, diff.col); + } + } + + fn write_mv_component(&mut self, i: usize, v: i32) { + debug_assert!(v != 0 && v & 1 == 0, "vectors are even without high precision"); + let offset = v.unsigned_abs() - 1; + let c = mv_class(offset); + self.w.ctx(v < 0, probs::MV_SIGN + i); + self.w.tree(&MV_CLASS_TREE, c as u8, |k| P::Ctx((probs::MV_CLASS + i * 10 + k) as u16)); + let fr = ((offset >> 1) & 3) as u8; + if c == 0 { + let b = (offset >> 3) as usize; + self.w.ctx(b == 1, probs::MV_CLASS0_BIT + i); + self.w.tree(&MV_FR_TREE, fr, |k| P::Ctx((probs::MV_CLASS0_FR + i * 6 + b * 3 + k) as u16)); + } else { + let dd = (offset - (1 << (c + 3))) >> 3; + for b in 0..c { + self.w.ctx((dd >> b) & 1 == 1, probs::MV_BITS + i * 10 + b as usize); + } + self.w.tree(&MV_FR_TREE, fr, |k| P::Ctx((probs::MV_FR + i * 3 + k) as u16)); + } + } +} diff --git a/crates/vp9enc/src/transform.rs b/crates/vp9enc/src/transform.rs new file mode 100644 index 0000000000..20996d60b5 --- /dev/null +++ b/crates/vp9enc/src/transform.rs @@ -0,0 +1,270 @@ +//! 4×4 transforms: the decoder's integer inverse DCT / ADST (spec 8.7.1) and Walsh–Hadamard +//! transform (8.7.1.10), and their forward counterparts (the exact inverse matrices of the +//! decoder's linear transforms, so `inverse4(forward(res)) ≈ res`; the WHT inverse is exact). + +pub const DCT_DCT: u8 = 0; +pub const ADST_DCT: u8 = 1; +pub const DCT_ADST: u8 = 2; +pub const ADST_ADST: u8 = 3; + +fn r14(v: i32) -> i32 { + (v + (1 << 13)) >> 14 +} + +fn idct4(x: [i32; 4]) -> [i32; 4] { + let v0 = r14(x[0] * 11585 - x[2] * 11585); + let v1 = r14(x[0] * 11585 + x[2] * 11585); + let v2 = r14(x[1] * 6270 - x[3] * 15137); + let v3 = r14(x[1] * 15137 + x[3] * 6270); + [v1 + v3, v0 + v2, v0 - v2, v1 - v3] +} + +fn iadst4(x: [i32; 4]) -> [i32; 4] { + let (s19, s29, s39, s49) = (5283i64, 9929i64, 13377i64, 15212i64); + let x: [i64; 4] = x.map(|v| v as i64); + let s0 = s19 * x[0]; + let s1 = s29 * x[0]; + let s2 = s39 * x[1]; + let s3 = s49 * x[2]; + let s4 = s19 * x[2]; + let s5 = s29 * x[3]; + let s6 = s49 * x[3]; + let s7 = s39 * (x[0] - x[2] + x[3]); + let x0 = s0 + s3 + s5; + let x1 = s1 - s4 - s6; + let x2 = s7; + let x3 = s2; + let r = |v: i64| ((v + (1 << 13)) >> 14) as i32; + [r(x0 + x3), r(x1 + x3), r(x2), r(x0 + x1 - x3)] +} + +/// The decoder's 4×4 inverse transform of dequantised coefficients (row-major) to a residual. +pub fn inverse4(coefs: &[i32; 16], tx_type: u8) -> [i32; 16] { + let row_adst = matches!(tx_type, DCT_ADST | ADST_ADST); + let col_adst = matches!(tx_type, ADST_DCT | ADST_ADST); + let mut t = [[0i32; 4]; 4]; + for i in 0..4 { + let r = [coefs[i * 4], coefs[i * 4 + 1], coefs[i * 4 + 2], coefs[i * 4 + 3]]; + t[i] = if row_adst { iadst4(r) } else { idct4(r) }; + } + let mut out = [0i32; 16]; + for j in 0..4 { + let c = [t[0][j], t[1][j], t[2][j], t[3][j]]; + let c = if col_adst { iadst4(c) } else { idct4(c) }; + for i in 0..4 { + out[i * 4 + j] = (c[i] + 8) >> 4; + } + } + out +} + +/// Inverse Walsh–Hadamard (8.7.1.10) on one line. +fn iwht(x: [i32; 4], shift: u32) -> [i32; 4] { + let mut a = x[0] >> shift; + let mut c = x[1] >> shift; + let mut d = x[2] >> shift; + let mut b = x[3] >> shift; + a += c; + d -= b; + let e = (a - d) >> 1; + b = e - b; + c = e - c; + a -= b; + d += c; + [a, b, c, d] +} + +/// The exact inverse of [`iwht`] (with shift 0): inputs that produce outputs `y`. +fn fwht(y: [i32; 4]) -> [i32; 4] { + let (aa, bb, cc, dd) = (y[0], y[1], y[2], y[3]); + let d1 = dd - cc; + let a1 = aa + bb; + let e = (a1 - d1) >> 1; + let b0 = e - bb; + let c0 = e - cc; + let a0 = a1 - c0; + let d0 = d1 + b0; + [a0, c0, d0, b0] +} + +pub fn inverse_wht(coefs: &[i32; 16]) -> [i32; 16] { + let mut t = [[0i32; 4]; 4]; + for i in 0..4 { + t[i] = iwht([coefs[i * 4], coefs[i * 4 + 1], coefs[i * 4 + 2], coefs[i * 4 + 3]], 2); + } + let mut out = [0i32; 16]; + for j in 0..4 { + let r = iwht([t[0][j], t[1][j], t[2][j], t[3][j]], 0); + for i in 0..4 { + out[i * 4 + j] = r[i]; + } + } + out +} + +/// Lossless forward transform: levels (dequantised by 4) whose inverse is exactly `res`. +pub fn forward_wht(res: &[i32; 16]) -> [i32; 16] { + let mut t = [[0i32; 4]; 4]; + for j in 0..4 { + let col = fwht([res[j], res[4 + j], res[8 + j], res[12 + j]]); + for i in 0..4 { + t[i][j] = col[i]; + } + } + let mut lv = [0i32; 16]; + for i in 0..4 { + let r = fwht(t[i]); + lv[i * 4..i * 4 + 4].copy_from_slice(&r); + } + lv +} + +/// Forward transform matrices: the inverses of the decoder's (linear part of the) inverse +/// transforms. +pub fn forward_matrices() -> &'static [[[f64; 16]; 16]; 4] { + use std::sync::OnceLock; + static M: OnceLock<[[[f64; 16]; 16]; 4]> = OnceLock::new(); + M.get_or_init(|| { + let mut out = [[[0.0; 16]; 16]; 4]; + for (ty, o) in out.iter_mut().enumerate() { + // Columns of the inverse: response to a scaled unit coefficient. + let k = 1 << 16; + let mut inv = [[0.0f64; 16]; 16]; + for c in 0..16 { + let mut e = [0i32; 16]; + e[c] = k; + let r = inverse4(&e, ty as u8); + for p in 0..16 { + inv[p][c] = r[p] as f64 / k as f64; + } + } + *o = invert16(inv); + } + out + }) +} + +/// [`forward_matrices`] transposed: `[type][q][k]`. +fn forward_matrices_t() -> &'static [[[f64; 16]; 16]; 4] { + use std::sync::OnceLock; + static M: OnceLock<[[[f64; 16]; 16]; 4]> = OnceLock::new(); + M.get_or_init(|| { + let f = forward_matrices(); + std::array::from_fn(|t| std::array::from_fn(|q| std::array::from_fn(|k| f[t][k][q]))) + }) +} + +fn invert16(m: [[f64; 16]; 16]) -> [[f64; 16]; 16] { + let mut a = m; + let mut inv = [[0.0; 16]; 16]; + for (i, r) in inv.iter_mut().enumerate() { + r[i] = 1.0; + } + for col in 0..16 { + let piv = (col..16).max_by(|&x, &y| a[x][col].abs().total_cmp(&a[y][col].abs())).unwrap_or(col); + a.swap(col, piv); + inv.swap(col, piv); + let d = a[col][col]; + for j in 0..16 { + a[col][j] /= d; + inv[col][j] /= d; + } + for r in 0..16 { + if r != col { + let f = a[r][col]; + if f != 0.0 { + for j in 0..16 { + a[r][j] -= f * a[col][j]; + inv[r][j] -= f * inv[col][j]; + } + } + } + } + } + inv +} + +/// Quantisation settings of one block. +#[derive(Clone, Copy)] +pub struct Quant { + pub dcq: i32, + pub acq: i32, + pub lossless: bool, + /// Rounding offsets (fractions of a step) for DC and AC; smaller = wider dead zone. + pub round_dc: f64, + pub round_ac: f64, +} + +impl Quant { + /// Quantised levels (raster order) of a residual. + pub fn levels(&self, res: &[i32; 16], tx_type: u8) -> [i32; 16] { + if self.lossless { + return forward_wht(res); + } + let ft = &forward_matrices_t()[tx_type as usize]; + // Every output accumulates its 16 products in the same order (q ascending) as a + // per-output sum, but the 16 outputs advance together: 16 independent chains that + // vectorize instead of one serial chain per output. + let mut acc = [-0.0f64; 16]; + for (q, col) in ft.iter().enumerate() { + let r = res[q] as f64; + for k in 0..16 { + acc[k] += col[k] * r; + } + } + let mut lv = [0i32; 16]; + for k in 0..16 { + let coef = acc[k]; + let (qs, rnd) = if k == 0 { (self.dcq, self.round_dc) } else { (self.acq, self.round_ac) }; + // Non-negative: truncation is floor. + let l = (coef.abs() / qs as f64 + rnd) as i32; + lv[k] = if coef < 0.0 { -l } else { l }; + } + lv + } + + /// The decoder's residual for `levels` (all zero when `eob` is 0). + pub fn residual(&self, levels: &[i32; 16], tx_type: u8, eob: usize) -> [i32; 16] { + if eob == 0 { + return [0; 16]; + } + let mut deq = [0i32; 16]; + for k in 0..16 { + deq[k] = levels[k] * if k == 0 { self.dcq } else { self.acq }; + } + if self.lossless { inverse_wht(&deq) } else { inverse4(&deq, tx_type) } + } +} + +#[cfg(test)] +mod tests { + use super::*; + + #[test] + fn wht_is_exactly_invertible() { + let mut s = 7u32; + for _ in 0..2000 { + let mut r = [0i32; 16]; + for v in &mut r { + s = s.wrapping_mul(1664525).wrapping_add(1013904223); + *v = (s >> 16) as i32 % 511 - 255; + } + let lv = forward_wht(&r); + let deq = lv.map(|l| l * 4); + assert_eq!(inverse_wht(&deq), r); + } + } + + #[test] + fn forward_transforms_invert_the_decoder() { + for ty in 0..4u8 { + let f = &forward_matrices()[ty as usize]; + let r: [i32; 16] = std::array::from_fn(|i| (i as i32 * 37 % 61) - 30); + let c: [i32; 16] = std::array::from_fn(|k| (0..16).map(|q| f[k][q] * r[q] as f64).sum::().round() as i32); + let back = inverse4(&c, ty); + for i in 0..16 { + assert!((back[i] - r[i]).abs() <= 1, "type {ty}: {back:?} vs {r:?}"); + } + } + } +}