forked from unom/punktfunk
Merge pull request 'FFmpeg is gone from the client — native decode M0–M10' (#85) from worktree-native-decode-m0 into main
Reviewed-on: unom/punktfunk#85
This commit is contained in:
@@ -119,10 +119,15 @@ jobs:
|
||||
run: |
|
||||
apt-get update
|
||||
# python3 is used by scripts/ci/gitea-release.sh for the stable-tag release attach.
|
||||
# libvulkan-dev: /usr/include/vulkan/vulkan.h for the client's pf-ffvk bindgen
|
||||
# (FFmpeg's hwcontext_vulkan.h includes it).
|
||||
# No libvulkan-dev: nothing here compiles or links against Vulkan (ash dlopens
|
||||
# libvulkan and pf-vkdecode binds nothing at build time), so neither the compile nor
|
||||
# dpkg-shlibdeps — which resolves DT_NEEDED sonames only — ever asks for it. The
|
||||
# client's `Depends: libvulkan1` is added by hand in packaging/debian/build-client-deb.sh
|
||||
# precisely because a dlopen is invisible to shlibdeps.
|
||||
# No libav*-dev: the client links no FFmpeg since M10 (§6 of
|
||||
# design/client-native-decode.md).
|
||||
apt-get install -y --no-install-recommends dpkg-dev python3 \
|
||||
libgtk-4-dev libadwaita-1-dev libsdl3-dev libvulkan-dev
|
||||
libgtk-4-dev libadwaita-1-dev libsdl3-dev
|
||||
|
||||
# Share ci.yml's cache keys so the release build reuses its registry + target artifacts.
|
||||
- name: Cache keys
|
||||
|
||||
@@ -34,7 +34,10 @@ on:
|
||||
# The flatpak is the CLIENT — only rebuild when the client/core/manifest change, not on every
|
||||
# design/host push (this is a heavy flatpak-builder run). Tags (v*, the client release) build too.
|
||||
# The bundle ships BOTH client binaries (shell + Vulkan session), so every crate in either
|
||||
# binary's dependency closure must be listed here.
|
||||
# binary's dependency closure must be listed here — including the native decode rungs, or a
|
||||
# commit that only touches the decoder never rebuilds the bundle and the Deck canary quietly
|
||||
# stops tracking it. pf-dxvadec is absent on purpose: it is `cfg(windows)` in pf-client-core
|
||||
# and never enters the Linux closure (windows.yml / windows-msix.yml carry it instead).
|
||||
paths:
|
||||
- 'clients/linux/**'
|
||||
- 'clients/session/**'
|
||||
@@ -42,6 +45,9 @@ on:
|
||||
- 'crates/pf-client-core/**'
|
||||
- 'crates/pf-presenter/**'
|
||||
- 'crates/pf-console-ui/**'
|
||||
- 'crates/pf-bitstream/**'
|
||||
- 'crates/pf-vkdecode/**'
|
||||
- 'crates/pf-vaadec/**'
|
||||
- 'packaging/flatpak/**'
|
||||
- 'Cargo.lock'
|
||||
- '.gitea/workflows/flatpak.yml'
|
||||
@@ -128,7 +134,7 @@ jobs:
|
||||
# authselect trigger fires — so this line alone was never the fix for the failures
|
||||
# below. See the retry.sh bump for the real cause.
|
||||
sed -i 's/resolve \[!UNAVAIL=return\] //' /etc/nsswitch.conf
|
||||
# Flathub provides the GNOME runtime/SDK + the rust-stable + ffmpeg-full extensions.
|
||||
# Flathub provides the GNOME runtime/SDK + the rust-stable and llvm20 extensions.
|
||||
#
|
||||
# ROOT CAUSE (confirmed 2026-07-11 by watching a live run on home-runner-1): this is
|
||||
# NOT a deterministic nsswitch/DNS-config bug. gitea-runner-fleet on home-runner-1 is
|
||||
@@ -147,7 +153,7 @@ jobs:
|
||||
git config --global --add safe.directory "$PWD"
|
||||
|
||||
# This job was the fleet's single heaviest network consumer: every run re-downloaded
|
||||
# the GNOME runtime + SDK + llvm/rust/ffmpeg extensions (multi-GB from Flathub) and
|
||||
# the GNOME runtime + SDK + llvm/rust extensions (multi-GB from Flathub) and
|
||||
# every crate source. Both live in well-defined directories, both are idempotently
|
||||
# verified/extended by the steps below, and the central cache server restores them
|
||||
# at LAN speed — so cache them. Keyed on what actually pins them: the manifest tree
|
||||
@@ -251,7 +257,8 @@ jobs:
|
||||
# or TCP dial costs a backoff-retry instead of the whole (long) compile:
|
||||
# 1) --install-deps-only pulls everything the manifest declares from Flathub: the
|
||||
# GNOME 50 runtime/SDK + the rust-stable (//25.08, rustc 1.96) and llvm20 SDK
|
||||
# extensions, plus the runtime's auto codecs-extra (HEVC libavcodec).
|
||||
# extensions. (No codec extension: the client links no FFmpeg — see the
|
||||
# manifest header.)
|
||||
# 2) --download-only fetches every source (all crates in cargo-sources.json) into
|
||||
# the .flatpak-builder state dir. Both are resumable/idempotent, so re-running
|
||||
# after a partial failure is safe and cheap.
|
||||
|
||||
@@ -96,9 +96,12 @@ jobs:
|
||||
- name: Prep
|
||||
run: |
|
||||
git config --global --add safe.directory "$PWD"
|
||||
# vulkan-headers: the client's pf-ffvk crate runs bindgen over FFmpeg's
|
||||
# libavutil/hwcontext_vulkan.h (#include <vulkan/vulkan.h>).
|
||||
dnf -y install gtk4-devel libadwaita-devel SDL3-devel vulkan-headers
|
||||
# No vulkan-headers: nothing in the workspace compiles against the system Vulkan headers.
|
||||
# The host's Vulkan encode hand-rolls its structs, pyrowave-sys bindgens its own vendored
|
||||
# copy, and both host and client reach Vulkan through ash, which dlopens the loader. (The
|
||||
# HDR gamescope leg further down does need them, and pulls them itself via `dnf builddep
|
||||
# gamescope`.) Matches packaging/rpm/punktfunk.spec, which dropped its BuildRequires too.
|
||||
dnf -y install gtk4-devel libadwaita-devel SDL3-devel
|
||||
# sysext build (packaging/bazzite/build-sysext.sh): squashfs + SELinux labeling.
|
||||
dnf -y install squashfs-tools cpio libselinux-utils selinux-policy-targeted
|
||||
# Fedora's own gamescope, for its RUNTIME libraries only — never shipped, never run. The
|
||||
|
||||
@@ -141,8 +141,10 @@ jobs:
|
||||
# observed on a clean build on this very runner (2026-07-17). No-op for compliant
|
||||
# projects (libvpl-sys pins 3.13+).
|
||||
"CMAKE_POLICY_VERSION_MINIMUM=3.5" | Out-File -FilePath $env:GITHUB_ENV -Append -Encoding utf8
|
||||
# FFMPEG_DIR: the same BtbN lgpl-shared x64 tree the Windows CLIENT links against (provisioned
|
||||
# by scripts/ci/provision-windows-punktfunk-extras.ps1). The host's AMD/Intel AMF/QSV encode backend
|
||||
# FFMPEG_DIR: the BtbN lgpl-shared x64 tree, provisioned by
|
||||
# scripts/ci/provision-windows-punktfunk-extras.ps1. The CLIENT used to link it too; since M10
|
||||
# it links no libav* at all (windows.yml sets no FFMPEG_DIR), so this tree is the HOST's alone
|
||||
# and the provisioning step keeps fetching it for that reason. The host's AMD/Intel AMF/QSV encode backend
|
||||
# (--features amf-qsv) link-imports avcodec/avutil/swscale from it; pack-host-installer.ps1
|
||||
# then bundles its bin\*.dll into the installer. LIBCLANG_PATH is in the runner daemon env.
|
||||
if (-not $env:FFMPEG_DIR) {
|
||||
|
||||
@@ -1,13 +1,17 @@
|
||||
# Build the punktfunk Windows client as signed MSIX packages (x64 + ARM64) and publish them to
|
||||
# Gitea's generic package registry, so Windows boxes can download + install a real package (Start
|
||||
# tile, clean install/uninstall) instead of a loose exe. Runs on a self-hosted windows-amd64
|
||||
# runner (host mode; the MSVC/WinUI toolchain comes from unom/infra's windows-runner/, FFmpeg
|
||||
# runner (host mode; the MSVC/WinUI toolchain comes from unom/infra's windows-runner/, the rest
|
||||
# self-provisions via the "Ensure Windows toolchain" step below, same as windows.yml) — the
|
||||
# Windows SDK's makeappx/signtool are baked into the runner's daemon env.
|
||||
#
|
||||
# Both arches come off the ONE x64 runner: x86_64 natively, aarch64 cross-compiled (the x64 MSVC
|
||||
# toolset has the ARM64 cross compiler; the matrix points FFMPEG_DIR at the ARM64 FFmpeg tree). See
|
||||
# windows.yml for the cross-build rationale + the BOM/MAX_PATH runner gotchas.
|
||||
# toolset has the ARM64 cross compiler). See windows.yml for the cross-build rationale + the
|
||||
# BOM/MAX_PATH runner gotchas.
|
||||
#
|
||||
# NO FFmpeg since M10 (design/client-native-decode.md §6): the client decodes natively, so the
|
||||
# package carries no libav* DLLs and this workflow sets no FFMPEG_DIR. The host installer
|
||||
# (windows-host.yml) is unchanged.
|
||||
#
|
||||
# Registry (public, unom org): https://git.unom.io/unom/-/packages (generic group)
|
||||
# Packaging internals: clients/windows/packaging/README.md.
|
||||
@@ -49,7 +53,9 @@ on:
|
||||
- 'crates/pf-client-core/**'
|
||||
- 'crates/pf-presenter/**'
|
||||
- 'crates/pf-console-ui/**'
|
||||
- 'crates/pf-ffvk/**'
|
||||
- 'crates/pf-bitstream/**'
|
||||
- 'crates/pf-vkdecode/**'
|
||||
- 'crates/pf-dxvadec/**'
|
||||
- 'Cargo.lock'
|
||||
- 'Cargo.toml'
|
||||
- '.gitea/workflows/windows-msix.yml'
|
||||
@@ -80,12 +86,10 @@ jobs:
|
||||
include:
|
||||
- arch: x64
|
||||
target: x86_64-pc-windows-msvc
|
||||
ffmpeg: C:\Users\Public\ffmpeg
|
||||
td: C:\t
|
||||
session_flags: ''
|
||||
- arch: arm64
|
||||
target: aarch64-pc-windows-msvc
|
||||
ffmpeg: C:\Users\Public\ffmpeg-arm64
|
||||
td: C:\t-a64
|
||||
# No skia-binaries prebuilt for aarch64-pc-windows-msvc: the session ships
|
||||
# without the Skia console UI on ARM64 (streaming unaffected) — flip when
|
||||
@@ -94,7 +98,7 @@ jobs:
|
||||
steps:
|
||||
- uses: actions/checkout@v4
|
||||
|
||||
- name: Ensure Windows toolchain (WDK, FFmpeg, Inno Setup, ARM64 target)
|
||||
- name: Ensure Windows toolchain (WDK, Inno Setup, ARM64 target)
|
||||
shell: pwsh
|
||||
run: ./scripts/ci/ensure-windows-toolchain.ps1
|
||||
|
||||
@@ -102,12 +106,9 @@ jobs:
|
||||
shell: pwsh
|
||||
run: |
|
||||
# CARGO_TARGET_DIR (per-arch, short) dodges the MAX_PATH wall in the CMake-from-source
|
||||
# crates (see windows.yml). FFMPEG_DIR selects the arch's import libs + is read by
|
||||
# pack-msix.ps1 for the runtime DLLs. All via GITHUB_ENV.
|
||||
# crates (see windows.yml). No FFMPEG_DIR: nothing in this package links libav* (M10),
|
||||
# and pack-msix.ps1 no longer copies runtime DLLs from one.
|
||||
"CARGO_TARGET_DIR=${{ matrix.td }}" | Out-File -FilePath $env:GITHUB_ENV -Append -Encoding utf8
|
||||
"FFMPEG_DIR=${{ matrix.ffmpeg }}" | Out-File -FilePath $env:GITHUB_ENV -Append -Encoding utf8
|
||||
# pf-ffvk's bindgen needs Vulkan headers (arch-independent; provisioned alongside FFmpeg).
|
||||
"PF_FFVK_VULKAN_INCLUDE=C:\Users\Public\vulkan-headers\include" | Out-File -FilePath $env:GITHUB_ENV -Append -Encoding utf8
|
||||
rustup target add ${{ matrix.target }}
|
||||
$pf = & "$env:GITHUB_WORKSPACE/scripts/ci/pf-version.ps1" # single source of truth: base is one minor ahead of the latest stable tag
|
||||
$parts = if ($env:GITHUB_REF -like 'refs/tags/v*') {
|
||||
|
||||
@@ -1,26 +1,29 @@
|
||||
# Windows client CI — runs on a self-hosted windows-amd64 runner (host mode; the generic runner +
|
||||
# toolchain come from unom/infra's windows-runner/; punktfunk's own extras - FFmpeg,
|
||||
# Vulkan-Headers, WDK, Inno Setup, the ARM64 rustup target - self-provision via the "Ensure
|
||||
# Windows toolchain" step below, a fast no-op once already present, so any runner with that label
|
||||
# works with no manual dispatch step first). Build + clippy + fmt + test BOTH client binaries:
|
||||
# the WinUI 3 shell (windows-reactor + WASAPI + SDL3) and the punktfunk-session Vulkan client
|
||||
# (pf-presenter/pf-client-core/pf-console-ui/pf-ffvk — every stream runs in it, spawned by the
|
||||
# toolchain come from unom/infra's windows-runner/; punktfunk's own extras - WDK, Inno Setup,
|
||||
# the ARM64 rustup target - self-provision via the "Ensure Windows toolchain" step below, a fast
|
||||
# no-op once already present, so any runner with that label works with no manual dispatch step
|
||||
# first). Build + clippy + fmt + test BOTH client binaries: the WinUI 3 shell
|
||||
# (windows-reactor + WASAPI + SDL3) and the punktfunk-session Vulkan client
|
||||
# (pf-presenter/pf-client-core/pf-console-ui — every stream runs in it, spawned by the
|
||||
# shell). ARM64 note: rust-skia publishes no aarch64-pc-windows-msvc prebuilt binaries, so the
|
||||
# session builds --no-default-features there (no Skia console UI; streaming is unaffected) —
|
||||
# flip when skia-binaries adds the target.
|
||||
#
|
||||
# NO FFmpeg here since M10 (design/client-native-decode.md §6): the client decodes with
|
||||
# pf-vkdecode / pf-dxvadec / openh264+rav1d and links no libav* at all, so this workflow sets
|
||||
# no FFMPEG_DIR, no PF_FFVK_VULKAN_INCLUDE and prepends nothing to PATH. The provisioning
|
||||
# script still fetches the FFmpeg trees because the HOST keeps FFmpeg — windows-host.yml's
|
||||
# `amf-qsv` leg link-imports them.
|
||||
#
|
||||
# Two architectures from ONE x64 runner: x86_64-pc-windows-msvc natively and
|
||||
# aarch64-pc-windows-msvc by cross-compiling. The x64 MSVC toolset ships an ARM64 cross compiler
|
||||
# (VC\Tools\MSVC\<ver>\bin\Hostx64\arm64\cl.exe) and aarch64-pc-windows-msvc is a tier-2 Rust
|
||||
# target with host tools, so no ARM64 runner is needed — the cc/cmake crates pick the ARM64
|
||||
# compiler from the target triple (SDL3 + libopus build-from-source cross-compile fine). The one
|
||||
# arch-specific external dep is FFmpeg's import libs: the runner keeps an x64 tree at
|
||||
# C:\Users\Public\ffmpeg and an ARM64 tree at C:\Users\Public\ffmpeg-arm64 (both FFmpeg 7.x /
|
||||
# avcodec-61); the matrix points FFMPEG_DIR at the right one. aarch64 can't *run* on the x64 host,
|
||||
# so fmt + test run only for x64.
|
||||
# thing the aarch64 build can't do is *run* on the x64 host, so fmt + test run only for x64.
|
||||
#
|
||||
# The MSVC/WinUI/FFmpeg toolchain (cargo/rustup on ASCII paths, NASM, CMake, LLVM, the x64 FFmpeg,
|
||||
# CARGO_HOME, CMAKE_POLICY_VERSION_MINIMUM, …) is baked into the runner's daemon env. Per-checkout
|
||||
# The MSVC/WinUI toolchain (cargo/rustup on ASCII paths, NASM, CMake, LLVM, CARGO_HOME,
|
||||
# CMAKE_POLICY_VERSION_MINIMUM, …) is baked into the runner's daemon env. Per-checkout
|
||||
# / per-arch vars are set in a step:
|
||||
# - CARGO_TARGET_DIR=C:\t… the runner's host workdir is buried deep under
|
||||
# C:\Windows\System32\config\systemprofile\.cache\act\<hash>\hostexecutor\,
|
||||
@@ -29,7 +32,6 @@
|
||||
# can't create its .tlog (DirectoryNotFoundException -> MSB6003). A short
|
||||
# root keeps every nested path well under the limit (per-arch so the two
|
||||
# matrix legs don't share a target dir).
|
||||
# - FFMPEG_DIR per-arch FFmpeg import libs (x64 vs arm64 tree).
|
||||
#
|
||||
# Steps use `shell: pwsh` (PowerShell 7) deliberately: Windows PowerShell 5.1's
|
||||
# `Out-File -Encoding utf8` prepends a UTF-8 BOM that corrupts the first GITHUB_ENV line (that
|
||||
@@ -55,7 +57,9 @@ on:
|
||||
- 'crates/pf-client-core/**'
|
||||
- 'crates/pf-presenter/**'
|
||||
- 'crates/pf-console-ui/**'
|
||||
- 'crates/pf-ffvk/**'
|
||||
- 'crates/pf-bitstream/**'
|
||||
- 'crates/pf-vkdecode/**'
|
||||
- 'crates/pf-dxvadec/**'
|
||||
- 'Cargo.lock'
|
||||
- 'Cargo.toml'
|
||||
- '.gitea/workflows/windows.yml'
|
||||
@@ -67,7 +71,9 @@ on:
|
||||
- 'crates/pf-client-core/**'
|
||||
- 'crates/pf-presenter/**'
|
||||
- 'crates/pf-console-ui/**'
|
||||
- 'crates/pf-ffvk/**'
|
||||
- 'crates/pf-bitstream/**'
|
||||
- 'crates/pf-vkdecode/**'
|
||||
- 'crates/pf-dxvadec/**'
|
||||
- 'Cargo.lock'
|
||||
- 'Cargo.toml'
|
||||
- '.gitea/workflows/windows.yml'
|
||||
@@ -110,7 +116,7 @@ jobs:
|
||||
steps:
|
||||
- uses: actions/checkout@v4
|
||||
|
||||
- name: Ensure Windows toolchain (WDK, FFmpeg, Inno Setup, ARM64 target)
|
||||
- name: Ensure Windows toolchain (WDK, Inno Setup, ARM64 target)
|
||||
shell: pwsh
|
||||
run: ./scripts/ci/ensure-windows-toolchain.ps1
|
||||
|
||||
@@ -120,21 +126,13 @@ jobs:
|
||||
# Per-arch short target root (dodges MAX_PATH; keeps the two legs from sharing target\).
|
||||
$td = if ('${{ matrix.target }}' -eq 'aarch64-pc-windows-msvc') { 'C:\t-a64' } else { 'C:\t' }
|
||||
"CARGO_TARGET_DIR=$td" | Out-File -FilePath $env:GITHUB_ENV -Append -Encoding utf8
|
||||
# Per-arch FFmpeg import libs (provision-windows-punktfunk-extras.ps1 fetches both).
|
||||
$ff = if ('${{ matrix.target }}' -eq 'aarch64-pc-windows-msvc') { 'C:\Users\Public\ffmpeg-arm64' } else { 'C:\Users\Public\ffmpeg' }
|
||||
"FFMPEG_DIR=$ff" | Out-File -FilePath $env:GITHUB_ENV -Append -Encoding utf8
|
||||
# pf-ffvk's bindgen needs Vulkan headers (arch-independent; provisioned alongside FFmpeg).
|
||||
"PF_FFVK_VULKAN_INCLUDE=C:\Users\Public\vulkan-headers\include" | Out-File -FilePath $env:GITHUB_ENV -Append -Encoding utf8
|
||||
# $ff\bin on PATH too (not just FFMPEG_DIR, which only satisfies the linker): the test
|
||||
# binary needs the actual DLLs to load at runtime. Set here rather than relying on the
|
||||
# daemon's own env (project-env.ps1) - on a freshly cloned/registered runner the daemon
|
||||
# starts before this job's "Ensure Windows toolchain" step ever writes that file, so its
|
||||
# PATH doesn't include this yet on a first run (confirmed live: STATUS_DLL_NOT_FOUND).
|
||||
"$ff\bin" | Out-File -FilePath $env:GITHUB_PATH -Append -Encoding utf8
|
||||
# No FFMPEG_DIR / PF_FFVK_VULKAN_INCLUDE / PATH prepend: the client links no libav*
|
||||
# since M10 (see this file's header), so nothing here needs import libs or runtime DLLs.
|
||||
# The HOST still does — windows-host.yml sets them for its amf-qsv leg.
|
||||
rustup target add ${{ matrix.target }}
|
||||
rustc --version
|
||||
cargo --version
|
||||
Write-Output "target ${{ matrix.target }} target-dir $td ffmpeg $ff"
|
||||
Write-Output "target ${{ matrix.target }} target-dir $td"
|
||||
|
||||
# Both client binaries. ARM64: no skia-binaries prebuilt for the target, so the session
|
||||
# drops its `ui` feature there (pf-console-ui excluded; --no-default-features is a no-op
|
||||
@@ -152,7 +150,10 @@ jobs:
|
||||
- name: Clippy (-D warnings)
|
||||
shell: pwsh
|
||||
run: |
|
||||
$pkgs = @('-p','punktfunk-client-windows','-p','punktfunk-client-session','-p','punktfunk-cli','-p','pf-client-core','-p','pf-presenter','-p','pf-ffvk')
|
||||
# Every crate in the `paths:` trigger above is named here: `cargo clippy -p X` BUILDS a
|
||||
# dependency but only LINTS the packages it is given, so a decode crate that starts the
|
||||
# run but is missing from this list would be gated by nothing.
|
||||
$pkgs = @('-p','punktfunk-client-windows','-p','punktfunk-client-session','-p','punktfunk-cli','-p','pf-client-core','-p','pf-presenter','-p','pf-bitstream','-p','pf-vkdecode','-p','pf-dxvadec')
|
||||
$sf = @()
|
||||
if ('${{ matrix.target }}' -eq 'aarch64-pc-windows-msvc') { $sf = @('--no-default-features') } else { $pkgs += @('-p','pf-console-ui') }
|
||||
cargo clippy @pkgs --all-targets @sf --target ${{ matrix.target }} -- -D warnings
|
||||
@@ -160,9 +161,9 @@ jobs:
|
||||
- name: Rustfmt check
|
||||
if: matrix.target == 'x86_64-pc-windows-msvc'
|
||||
shell: pwsh
|
||||
run: cargo fmt -p punktfunk-client-windows -p punktfunk-client-session -p punktfunk-cli -p pf-client-core -p pf-presenter -p pf-console-ui -p pf-ffvk -- --check
|
||||
run: cargo fmt -p punktfunk-client-windows -p punktfunk-client-session -p punktfunk-cli -p pf-client-core -p pf-presenter -p pf-console-ui -p pf-dxvadec -- --check
|
||||
|
||||
- name: Test
|
||||
if: matrix.target == 'x86_64-pc-windows-msvc'
|
||||
shell: pwsh
|
||||
run: cargo test -p punktfunk-client-windows -p punktfunk-client-session -p punktfunk-cli -p pf-client-core -p pf-presenter -p pf-console-ui -p pf-ffvk --target ${{ matrix.target }}
|
||||
run: cargo test -p punktfunk-client-windows -p punktfunk-client-session -p punktfunk-cli -p pf-client-core -p pf-presenter -p pf-console-ui -p pf-dxvadec --target ${{ matrix.target }}
|
||||
|
||||
+5
-3
@@ -46,9 +46,11 @@ sudo apt install build-essential clang libclang-dev pkg-config cmake \
|
||||
libvulkan-dev
|
||||
```
|
||||
|
||||
(The last two groups are the Linux client shell and `pf-ffvk`; skip them only if you never build
|
||||
those crates. `scripts/bootstrap-ubuntu.sh` sets up an Ubuntu **capture-test host** — NVIDIA, Sway,
|
||||
PipeWire — and is not a substitute for the list above.)
|
||||
(The last two groups are the Linux client shell and the Vulkan session presenter; skip them only
|
||||
if you never build those crates. `libvulkan-dev` is for the LOADER's pkg-config/soname — ash
|
||||
dlopens it, and the client links no FFmpeg at all, so no libav*-dev appears here.
|
||||
`scripts/bootstrap-ubuntu.sh` sets up an Ubuntu **capture-test host** — NVIDIA, Sway, PipeWire —
|
||||
and is not a substitute for the list above.)
|
||||
|
||||
## Before you push
|
||||
|
||||
|
||||
Generated
+307
-41
@@ -65,7 +65,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "05b07e8e73d720a1f2e4b6014766e6039fd2e96a4fa44e2a78d0e1fa2ff49826"
|
||||
dependencies = [
|
||||
"android_log-sys",
|
||||
"env_filter",
|
||||
"env_filter 0.1.4",
|
||||
"log",
|
||||
]
|
||||
|
||||
@@ -204,6 +204,12 @@ dependencies = [
|
||||
"syn",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "assert_matches"
|
||||
version = "1.5.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "9b34d609dfbaf33d6889b2b7106d3ca345eacad44200913df5ba02bfd31d2ba9"
|
||||
|
||||
[[package]]
|
||||
name = "async-broadcast"
|
||||
version = "0.7.2"
|
||||
@@ -341,6 +347,26 @@ version = "1.1.2"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "1505bd5d3d116872e7271a6d4e16d81d0c8570876c8de68093a09ac269d8aac0"
|
||||
|
||||
[[package]]
|
||||
name = "atomig"
|
||||
version = "0.4.3"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "cd0f41f4bb89f5c6450325e283fb78c4a3d042181b54f3855ee2f872919f9863"
|
||||
dependencies = [
|
||||
"atomig-macro",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "atomig-macro"
|
||||
version = "0.4.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "49c98dba06b920588de7d63f6acc23f1e6a9fade5fd6198e564506334fb5a4f5"
|
||||
dependencies = [
|
||||
"proc-macro2",
|
||||
"quote",
|
||||
"syn",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "audiopus_sys"
|
||||
version = "0.2.2"
|
||||
@@ -446,7 +472,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "993776b509cfb49c750f11b8f07a46fa23e0a1386ffc01fb1e7d343efc387895"
|
||||
dependencies = [
|
||||
"annotate-snippets",
|
||||
"bitflags",
|
||||
"bitflags 2.13.0",
|
||||
"cexpr",
|
||||
"clang-sys",
|
||||
"itertools 0.13.0",
|
||||
@@ -475,6 +501,12 @@ version = "0.8.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "5e764a1d40d510daf35e07be9eb06e75770908c27d411ee6c92109c9840eaaf7"
|
||||
|
||||
[[package]]
|
||||
name = "bitflags"
|
||||
version = "1.3.2"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "bef38d45163c2f1dde094a7dfd33ccf595c92905c8f8f4fdc18d06fb1037718a"
|
||||
|
||||
[[package]]
|
||||
name = "bitflags"
|
||||
version = "2.13.0"
|
||||
@@ -538,6 +570,12 @@ dependencies = [
|
||||
"syn",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "byteorder"
|
||||
version = "1.5.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "1fd0f2584146f6f2ef48085050886acf353beff7305ebd1ae69500e27c67f64b"
|
||||
|
||||
[[package]]
|
||||
name = "byteorder-lite"
|
||||
version = "0.1.0"
|
||||
@@ -556,7 +594,7 @@ version = "0.22.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "5cc8d9aa793480744cd9a0524fef1a2e197d9eaa0f739cde19d16aba530dcb95"
|
||||
dependencies = [
|
||||
"bitflags",
|
||||
"bitflags 2.13.0",
|
||||
"cairo-sys-rs",
|
||||
"glib",
|
||||
"libc",
|
||||
@@ -885,6 +923,15 @@ dependencies = [
|
||||
"itertools 0.10.5",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "cros-codecs"
|
||||
version = "0.0.5"
|
||||
dependencies = [
|
||||
"env_logger",
|
||||
"log",
|
||||
"serde_json",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "crossbeam-deque"
|
||||
version = "0.8.6"
|
||||
@@ -991,6 +1038,37 @@ version = "2.11.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "a4ae5f15dda3c708c0ade84bfee31ccab44a3da4f88015ed22f63732abe300c8"
|
||||
|
||||
[[package]]
|
||||
name = "defmt"
|
||||
version = "1.1.1"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "e2953bfe4f93bbd20cc71198842756f77d161884c99ebbabc41d80231ded88d1"
|
||||
dependencies = [
|
||||
"bitflags 1.3.2",
|
||||
"defmt-macros",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "defmt-macros"
|
||||
version = "1.1.1"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "bad9c72e7ca2137e0dc3813245a0d282fd6daad32fd800af018306a9169b5fe8"
|
||||
dependencies = [
|
||||
"defmt-parser",
|
||||
"proc-macro2",
|
||||
"quote",
|
||||
"syn",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "defmt-parser"
|
||||
version = "1.0.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "10d60334b3b2e7c9d91ef8150abfb6fa4c1c39ebbcf4a81c2e346aad939fee3e"
|
||||
dependencies = [
|
||||
"thiserror 2.0.18",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "der"
|
||||
version = "0.7.10"
|
||||
@@ -1101,6 +1179,29 @@ dependencies = [
|
||||
"regex",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "env_filter"
|
||||
version = "2.0.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "900d271a03799a1ee8d1ca9b19893b48ca674a9284fefcfb85f05e74ed314217"
|
||||
dependencies = [
|
||||
"log",
|
||||
"regex",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "env_logger"
|
||||
version = "0.11.11"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "de671bd27a75a797dc9ae289ba1e77276e75e2026408aab65185384e2d5cd3f6"
|
||||
dependencies = [
|
||||
"anstream",
|
||||
"anstyle",
|
||||
"env_filter 2.0.0",
|
||||
"jiff",
|
||||
"log",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "equivalent"
|
||||
version = "1.0.2"
|
||||
@@ -1193,7 +1294,7 @@ version = "8.1.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "f7c4bd5ab1ac61f29c634df1175d350ded29cf74c3c6d4f7030431a5ae3c7d5d"
|
||||
dependencies = [
|
||||
"bitflags",
|
||||
"bitflags 2.13.0",
|
||||
"ffmpeg-sys-next",
|
||||
"libc",
|
||||
]
|
||||
@@ -1584,7 +1685,7 @@ version = "0.22.7"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "c207e04e51605dcf7b2924c41591b3a10e1438eaac5bcf448fb91f325381104a"
|
||||
dependencies = [
|
||||
"bitflags",
|
||||
"bitflags 2.13.0",
|
||||
"futures-channel",
|
||||
"futures-core",
|
||||
"futures-executor",
|
||||
@@ -1777,7 +1878,7 @@ checksum = "6ea2d84b969582b4b1864a92dc5d27cd2b77b622a8d79306834f1be5ba20d84b"
|
||||
dependencies = [
|
||||
"cfg-if",
|
||||
"crunchy",
|
||||
"zerocopy",
|
||||
"zerocopy 0.8.52",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
@@ -2127,6 +2228,42 @@ version = "1.0.18"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "8f42a60cbdf9a97f5d2305f08a87dc4e09308d1276d28c869c684d7777685682"
|
||||
|
||||
[[package]]
|
||||
name = "jiff"
|
||||
version = "0.2.35"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "668b7183bd07af9a4885f5c35b0cc5c83c4607a913c16b7e17291832910d2dcc"
|
||||
dependencies = [
|
||||
"defmt",
|
||||
"jiff-core",
|
||||
"jiff-static",
|
||||
"log",
|
||||
"portable-atomic",
|
||||
"portable-atomic-util",
|
||||
"serde_core",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "jiff-core"
|
||||
version = "0.1.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "7feca88439efe53da3754500c1851dedf3cb36c524dd5cf8225cc0794de95d09"
|
||||
dependencies = [
|
||||
"defmt",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "jiff-static"
|
||||
version = "0.2.35"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "3a69dcb3a21cfb32ce1cd056169337ca284af0766dd766e7878819b251a49204"
|
||||
dependencies = [
|
||||
"jiff-core",
|
||||
"proc-macro2",
|
||||
"quote",
|
||||
"syn",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "jni"
|
||||
version = "0.21.1"
|
||||
@@ -2291,7 +2428,7 @@ version = "0.9.2"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "b6b8cfa2a7656627b4c92c6b9ef929433acd673d5ab3708cda1b18478ac00df4"
|
||||
dependencies = [
|
||||
"bitflags",
|
||||
"bitflags 2.13.0",
|
||||
"cc",
|
||||
"convert_case",
|
||||
"cookie-factory",
|
||||
@@ -2486,6 +2623,7 @@ version = "0.3.2"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "706bf8a5e8c8ddb99128c3291d31bd21f4bcde17f0f4c20ec678d85c74faa149"
|
||||
dependencies = [
|
||||
"jobserver",
|
||||
"log",
|
||||
]
|
||||
|
||||
@@ -2493,7 +2631,7 @@ dependencies = [
|
||||
name = "ndk"
|
||||
version = "0.9.0"
|
||||
dependencies = [
|
||||
"bitflags",
|
||||
"bitflags 2.13.0",
|
||||
"jni-sys 0.3.1",
|
||||
"log",
|
||||
"ndk-sys",
|
||||
@@ -2517,7 +2655,7 @@ version = "0.29.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "71e2746dc3a24dd78b3cfcb7be93368c6de9963d30f43a6a73998a9cf4b17b46"
|
||||
dependencies = [
|
||||
"bitflags",
|
||||
"bitflags 2.13.0",
|
||||
"cfg-if",
|
||||
"cfg_aliases",
|
||||
"libc",
|
||||
@@ -2530,7 +2668,7 @@ version = "0.30.1"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "74523f3a35e05aba87a1d978330aef40f67b0304ac79c1c00b294c9830543db6"
|
||||
dependencies = [
|
||||
"bitflags",
|
||||
"bitflags 2.13.0",
|
||||
"cfg-if",
|
||||
"cfg_aliases",
|
||||
"libc",
|
||||
@@ -2848,6 +2986,14 @@ version = "2.3.2"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "9b4f627cb1b25917193a259e49bdad08f671f8d9708acfd5fe0a8c1455d87220"
|
||||
|
||||
[[package]]
|
||||
name = "pf-bitstream"
|
||||
version = "0.24.0"
|
||||
dependencies = [
|
||||
"cros-codecs",
|
||||
"tracing",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "pf-capture"
|
||||
version = "0.24.0"
|
||||
@@ -2876,19 +3022,26 @@ dependencies = [
|
||||
"anyhow",
|
||||
"ash",
|
||||
"async-channel",
|
||||
"ffmpeg-next",
|
||||
"libc",
|
||||
"libloading",
|
||||
"mdns-sd",
|
||||
"openh264",
|
||||
"opus",
|
||||
"pf-ffvk",
|
||||
"pf-bitstream",
|
||||
"pf-dxvadec",
|
||||
"pf-update-check",
|
||||
"pf-vaadec",
|
||||
"pf-vkdecode",
|
||||
"pipewire",
|
||||
"punktfunk-core",
|
||||
"pyrowave-sys",
|
||||
"rand 0.9.4",
|
||||
"rav1d",
|
||||
"rustls",
|
||||
"sdl3",
|
||||
"serde",
|
||||
"serde_json",
|
||||
"sha2",
|
||||
"tracing",
|
||||
"ureq",
|
||||
"wasapi",
|
||||
@@ -2935,6 +3088,16 @@ dependencies = [
|
||||
"bytemuck",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "pf-dxvadec"
|
||||
version = "0.24.0"
|
||||
dependencies = [
|
||||
"cros-codecs",
|
||||
"pf-bitstream",
|
||||
"pf-vkdecode",
|
||||
"tracing",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "pf-encode"
|
||||
version = "0.24.0"
|
||||
@@ -2959,15 +3122,6 @@ dependencies = [
|
||||
"windows 0.62.2 (registry+https://github.com/rust-lang/crates.io-index)",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "pf-ffvk"
|
||||
version = "0.24.0"
|
||||
dependencies = [
|
||||
"ash",
|
||||
"bindgen",
|
||||
"pkg-config",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "pf-frame"
|
||||
version = "0.24.0"
|
||||
@@ -3042,7 +3196,6 @@ dependencies = [
|
||||
"ash",
|
||||
"async-channel",
|
||||
"pf-client-core",
|
||||
"pf-ffvk",
|
||||
"punktfunk-core",
|
||||
"sdl3",
|
||||
"tracing",
|
||||
@@ -3069,13 +3222,22 @@ dependencies = [
|
||||
"ureq",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "pf-vaadec"
|
||||
version = "0.24.0"
|
||||
dependencies = [
|
||||
"cros-codecs",
|
||||
"pf-bitstream",
|
||||
"pf-vkdecode",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "pf-vdisplay"
|
||||
version = "0.24.0"
|
||||
dependencies = [
|
||||
"anyhow",
|
||||
"ashpd",
|
||||
"bitflags",
|
||||
"bitflags 2.13.0",
|
||||
"bytemuck",
|
||||
"futures-util",
|
||||
"hex",
|
||||
@@ -3102,6 +3264,17 @@ dependencies = [
|
||||
"x11rb",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "pf-vkdecode"
|
||||
version = "0.24.0"
|
||||
dependencies = [
|
||||
"ash",
|
||||
"cros-codecs",
|
||||
"pf-bitstream",
|
||||
"sha2",
|
||||
"tracing",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "pf-win-display"
|
||||
version = "0.24.0"
|
||||
@@ -3153,7 +3326,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "9688b89abf11d756499f7c6190711d6dbe5a3acdb30c8fbf001d6596d06a8d44"
|
||||
dependencies = [
|
||||
"anyhow",
|
||||
"bitflags",
|
||||
"bitflags 2.13.0",
|
||||
"libc",
|
||||
"libspa",
|
||||
"libspa-sys",
|
||||
@@ -3207,7 +3380,7 @@ version = "0.18.1"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "60769b8b31b2a9f263dae2776c37b1b28ae246943cf719eb6946a1db05128a61"
|
||||
dependencies = [
|
||||
"bitflags",
|
||||
"bitflags 2.13.0",
|
||||
"crc32fast",
|
||||
"fdeflate",
|
||||
"flate2",
|
||||
@@ -3251,6 +3424,21 @@ dependencies = [
|
||||
"universal-hash",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "portable-atomic"
|
||||
version = "1.14.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "3d20d5497ef88037a52ff98267d066e7f11fcc5e99bbfbd58a42336193aacec3"
|
||||
|
||||
[[package]]
|
||||
name = "portable-atomic-util"
|
||||
version = "0.2.7"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "c2a106d1259c23fac8e543272398ae0e3c0b8d33c88ed73d0cc71b0f1d902618"
|
||||
dependencies = [
|
||||
"portable-atomic",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "potential_utf"
|
||||
version = "0.1.5"
|
||||
@@ -3272,7 +3460,7 @@ version = "0.2.21"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "85eae3c4ed2f50dcfe72643da4befc30deadb458a9b590d720cde2f2b1e97da9"
|
||||
dependencies = [
|
||||
"zerocopy",
|
||||
"zerocopy 0.8.52",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
@@ -3311,7 +3499,7 @@ checksum = "4b45fcc2344c680f5025fe57779faef368840d0bd1f42f216291f0dc4ace4744"
|
||||
dependencies = [
|
||||
"bit-set",
|
||||
"bit-vec",
|
||||
"bitflags",
|
||||
"bitflags 2.13.0",
|
||||
"num-traits",
|
||||
"rand 0.9.4",
|
||||
"rand_chacha 0.9.0",
|
||||
@@ -3388,7 +3576,6 @@ name = "punktfunk-client-windows"
|
||||
version = "0.24.0"
|
||||
dependencies = [
|
||||
"async-channel",
|
||||
"ffmpeg-next",
|
||||
"mdns-sd",
|
||||
"pf-client-core",
|
||||
"punktfunk-core",
|
||||
@@ -3431,7 +3618,7 @@ dependencies = [
|
||||
"tokio",
|
||||
"tracing",
|
||||
"windows-sys 0.59.0",
|
||||
"zerocopy",
|
||||
"zerocopy 0.8.52",
|
||||
"zeroize",
|
||||
]
|
||||
|
||||
@@ -3726,6 +3913,36 @@ dependencies = [
|
||||
"rand_core 0.9.5",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "rav1d"
|
||||
version = "1.1.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "1932f060d5e7bd49dc9f8b272c1dc5e9ce0ffe141c28be900265d3989b36c9ed"
|
||||
dependencies = [
|
||||
"assert_matches",
|
||||
"atomig",
|
||||
"bitflags 2.13.0",
|
||||
"cc",
|
||||
"cfg-if",
|
||||
"libc",
|
||||
"nasm-rs",
|
||||
"parking_lot",
|
||||
"paste",
|
||||
"raw-cpuid",
|
||||
"strum",
|
||||
"to_method",
|
||||
"zerocopy 0.7.35",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "raw-cpuid"
|
||||
version = "11.6.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "498cd0dc59d73224351ee52a95fee0f1a617a2eae0e7d9d720cc622c73a54186"
|
||||
dependencies = [
|
||||
"bitflags 2.13.0",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "raw-window-handle"
|
||||
version = "0.6.2"
|
||||
@@ -3777,7 +3994,7 @@ version = "0.5.18"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "ed2bf2547551a7053d6fdfafda3f938979645c44812fbfcda098faae3f1a362d"
|
||||
dependencies = [
|
||||
"bitflags",
|
||||
"bitflags 2.13.0",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
@@ -3934,7 +4151,7 @@ version = "0.40.1"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "11438310b19e3109b6446c33d1ed5e889428cf2e278407bc7896bc4aaea43323"
|
||||
dependencies = [
|
||||
"bitflags",
|
||||
"bitflags 2.13.0",
|
||||
"fallible-iterator",
|
||||
"fallible-streaming-iterator",
|
||||
"hashlink",
|
||||
@@ -3973,7 +4190,7 @@ version = "1.1.4"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "b6fe4565b9518b83ef4f91bb47ce29620ca828bd32cb7e408f0062e9930ba190"
|
||||
dependencies = [
|
||||
"bitflags",
|
||||
"bitflags 2.13.0",
|
||||
"errno",
|
||||
"libc",
|
||||
"linux-raw-sys",
|
||||
@@ -4127,7 +4344,7 @@ version = "0.18.4"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "25bd22eb1bbc9137e914022b4994ed35591eea0884e9e3e98e6d9895cad6e1d2"
|
||||
dependencies = [
|
||||
"bitflags",
|
||||
"bitflags 2.13.0",
|
||||
"libc",
|
||||
"sdl3-image-sys",
|
||||
"sdl3-mixer-sys",
|
||||
@@ -4222,7 +4439,7 @@ version = "3.7.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "b7f4bc775c73d9a02cde8bf7b2ec4c9d12743edf609006c7facc23998404cd1d"
|
||||
dependencies = [
|
||||
"bitflags",
|
||||
"bitflags 2.13.0",
|
||||
"core-foundation",
|
||||
"core-foundation-sys",
|
||||
"libc",
|
||||
@@ -4428,7 +4645,7 @@ version = "0.87.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "0f7d94f3e7537c71ad4cf132eb26e3be8c8a886ed3649c4525c089041fc312b2"
|
||||
dependencies = [
|
||||
"bitflags",
|
||||
"bitflags 2.13.0",
|
||||
"lazy_static",
|
||||
"skia-bindings",
|
||||
]
|
||||
@@ -4521,6 +4738,28 @@ version = "0.11.1"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "7da8b5736845d9f2fcb837ea5d9e2628564b3b043a70948a3f0b778838c5fb4f"
|
||||
|
||||
[[package]]
|
||||
name = "strum"
|
||||
version = "0.26.3"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "8fec0f0aef304996cf250b31b5a10dee7980c85da9d759361292b8bca5a18f06"
|
||||
dependencies = [
|
||||
"strum_macros",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "strum_macros"
|
||||
version = "0.26.4"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "4c6bee85a5a24955dc440386795aa378cd9cf82acd5f764469152d2270e581be"
|
||||
dependencies = [
|
||||
"heck",
|
||||
"proc-macro2",
|
||||
"quote",
|
||||
"rustversion",
|
||||
"syn",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "subtle"
|
||||
version = "2.6.1"
|
||||
@@ -4724,6 +4963,12 @@ version = "0.1.1"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "1f3ccbac311fea05f86f61904b462b55fb3df8837a366dfc601a0161d0532f20"
|
||||
|
||||
[[package]]
|
||||
name = "to_method"
|
||||
version = "1.1.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "c7c4ceeeca15c8384bbc3e011dbd8fccb7f068a440b752b7d9b32ceb0ca0e2e8"
|
||||
|
||||
[[package]]
|
||||
name = "tokio"
|
||||
version = "1.52.3"
|
||||
@@ -5301,7 +5546,7 @@ version = "0.31.14"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "645c7c96bb74690c3189b5c9cb4ca1627062bb23693a4fad9d8c3de958260144"
|
||||
dependencies = [
|
||||
"bitflags",
|
||||
"bitflags 2.13.0",
|
||||
"rustix",
|
||||
"wayland-backend",
|
||||
"wayland-scanner",
|
||||
@@ -5313,7 +5558,7 @@ version = "0.32.13"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "23d0c813de3daa2ed6520af85a3bd49b0e722a3078506899aa9686fea58dc4b6"
|
||||
dependencies = [
|
||||
"bitflags",
|
||||
"bitflags 2.13.0",
|
||||
"wayland-backend",
|
||||
"wayland-client",
|
||||
"wayland-scanner",
|
||||
@@ -5325,7 +5570,7 @@ version = "0.3.12"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "6e9567599ef23e09b8dad6e429e5738d4509dfc46b3b21f32841a304d16b29c8"
|
||||
dependencies = [
|
||||
"bitflags",
|
||||
"bitflags 2.13.0",
|
||||
"wayland-backend",
|
||||
"wayland-client",
|
||||
"wayland-protocols",
|
||||
@@ -5338,7 +5583,7 @@ version = "0.3.12"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "eb04e52f7836d7c7976c78ca0250d61e33873c34156a2a1fc9474828ec268234"
|
||||
dependencies = [
|
||||
"bitflags",
|
||||
"bitflags 2.13.0",
|
||||
"wayland-backend",
|
||||
"wayland-client",
|
||||
"wayland-protocols",
|
||||
@@ -5690,7 +5935,7 @@ version = "0.7.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "d24d6bcc7f734a4091ecf8d7a64c5f7d7066f45585c1861eba06449909609c8a"
|
||||
dependencies = [
|
||||
"bitflags",
|
||||
"bitflags 2.13.0",
|
||||
"widestring",
|
||||
"windows-sys 0.52.0",
|
||||
]
|
||||
@@ -6182,13 +6427,34 @@ dependencies = [
|
||||
"zvariant",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "zerocopy"
|
||||
version = "0.7.35"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "1b9b4fd18abc82b8136838da5d50bae7bdea537c574d8dc1a34ed098d6c166f0"
|
||||
dependencies = [
|
||||
"byteorder",
|
||||
"zerocopy-derive 0.7.35",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "zerocopy"
|
||||
version = "0.8.52"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "ce1022995ff5ff5d841ad7d994facc23098cd40152f2c1d11cd607c6f530653f"
|
||||
dependencies = [
|
||||
"zerocopy-derive",
|
||||
"zerocopy-derive 0.8.52",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "zerocopy-derive"
|
||||
version = "0.7.35"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "fa4f8080344d4671fb4e831a13ad1e68092748387dfc4f55e356242fae12ce3e"
|
||||
dependencies = [
|
||||
"proc-macro2",
|
||||
"quote",
|
||||
"syn",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
|
||||
+5
-1
@@ -5,11 +5,12 @@ members = [
|
||||
"crates/punktfunk-host",
|
||||
"crates/punktfunk-host/vendor/usbip-sim",
|
||||
"crates/punktfunk-tray",
|
||||
"crates/pf-bitstream",
|
||||
"crates/pf-bitstream/vendor/cros-codecs",
|
||||
"crates/pf-client-core",
|
||||
"crates/pf-clipboard",
|
||||
"crates/pf-presenter",
|
||||
"crates/pf-console-ui",
|
||||
"crates/pf-ffvk",
|
||||
"crates/pf-driver-proto",
|
||||
"crates/pf-paths",
|
||||
"crates/pf-update",
|
||||
@@ -23,6 +24,9 @@ members = [
|
||||
"crates/pf-capture",
|
||||
"crates/pf-inject",
|
||||
"crates/pf-vdisplay",
|
||||
"crates/pf-vkdecode",
|
||||
"crates/pf-dxvadec",
|
||||
"crates/pf-vaadec",
|
||||
"crates/pyrowave-sys",
|
||||
"crates/libvpl-sys",
|
||||
"clients/probe",
|
||||
|
||||
@@ -84,7 +84,9 @@ mid-stream mode renegotiation and a wall-clock skew handshake so latency stays v
|
||||
Both run from **one process**: bare `punktfunk-host serve` is the **secure native-only default**
|
||||
(`punktfunk/1` + the management API/web console), and `serve --gamestream` additionally enables the
|
||||
GameStream/Moonlight-compat planes (opt-in, trusted-LAN only — GameStream has inherent on-path
|
||||
weaknesses). The host is managed through a REST API and web console. Builds against FFmpeg 7 or 8.
|
||||
weaknesses). The host is managed through a REST API and web console. The **host** builds against
|
||||
FFmpeg 7 or 8; the **clients** link no FFmpeg at all — they decode natively (Vulkan Video, DXVA,
|
||||
VAAPI, VideoToolbox, MediaCodec, openh264 + rav1d).
|
||||
|
||||
What works where: **[the support matrix](https://docs.punktfunk.unom.io/docs/support-matrix)** ·
|
||||
where it's heading: **[the roadmap](https://docs.punktfunk.unom.io/docs/roadmap)**.
|
||||
@@ -187,10 +189,13 @@ and the [docs site](https://docs.punktfunk.unom.io).
|
||||
crates/
|
||||
punktfunk-core/ protocol · FEC · pacing · crypto · QUIC control plane — the C ABI (lib + cdylib + staticlib)
|
||||
punktfunk-host/ the host (Linux + Windows): virtual displays · capture · encode · input · GameStream · punktfunk/1 · mgmt
|
||||
pf-client-core/ shared client plumbing (Linux + Windows): session pump · FFmpeg decode · audio · SDL3 gamepads · trust · discovery
|
||||
pf-client-core/ shared client plumbing (Linux + Windows): session pump · native decode ladder · audio · SDL3 gamepads · trust · discovery
|
||||
pf-presenter/ Vulkan session presenter: SDL3 window · ash swapchain · frame present · input capture
|
||||
pf-console-ui/ Skia console UI for the session client: gamepad shell · stats OSD · pairing · on-screen keyboard
|
||||
pf-ffvk/ FFmpeg Vulkan hwcontext bindings (AVVkFrame) for Vulkan Video decode on the presenter's device
|
||||
pf-bitstream/ H.264 / H.265 / AV1 bitstream parsing + per-AU decode plans — the one parser every native rung submits from
|
||||
pf-vkdecode/ native Vulkan Video decode (H.264 / H.265 / AV1) on the presenter's own device
|
||||
pf-dxvadec/ native DXVA buffer layouts + AuPlan → picparams conversion (the Windows D3D11VA rung)
|
||||
pf-vaadec/ native libva buffer layouts + AuPlan → picparams conversion (the Linux VAAPI rung)
|
||||
pf-driver-proto/ host ↔ pf-vdisplay driver contract: control IOCTLs + IDD-push frame transport (no_std)
|
||||
punktfunk-tray/ host tray icon (Windows notification area / Linux StatusNotifierItem)
|
||||
clients/
|
||||
@@ -245,9 +250,10 @@ additional terms or conditions. See [CONTRIBUTING.md](CONTRIBUTING.md).
|
||||
|
||||
Punktfunk's own source is MIT/Apache-2.0. Shipped binaries additionally link third-party components
|
||||
under their own (permissive) licenses — see [`THIRD-PARTY-NOTICES.txt`](THIRD-PARTY-NOTICES.txt)
|
||||
(regenerate with `scripts/gen-third-party-notices.sh`). The Windows host and client builds also
|
||||
bundle FFmpeg under the **LGPL v2.1+** (dynamically linked, replaceable DLLs; the license text and
|
||||
notice ship in the installed `licenses/` folder).
|
||||
(regenerate with `scripts/gen-third-party-notices.sh`). The Windows **host** build also
|
||||
bundles FFmpeg under the **LGPL v2.1+** (dynamically linked, replaceable DLLs; the license text and
|
||||
notice ship in the installed `licenses/` folder). The **clients** bundle no FFmpeg — they link
|
||||
none.
|
||||
|
||||
### Trademarks
|
||||
|
||||
|
||||
+565
-222
File diff suppressed because it is too large
Load Diff
+14
-2
@@ -4,10 +4,22 @@
|
||||
# cargo about generate about.hbs > THIRD-PARTY-NOTICES.txt # (or use scripts/gen-third-party-notices.sh)
|
||||
#
|
||||
# `accepted` is the allow-list of SPDX licenses permitted in the dependency tree. CI fails if a crate
|
||||
# carries anything not listed here — which is exactly the regression guard we want against a copyleft
|
||||
# dependency silently entering the linked set. All entries
|
||||
# carries anything not listed here — the regression guard against a copyleft dependency silently
|
||||
# entering the linked set. All entries
|
||||
# below are permissive / attribution-only; deliberately NO GPL/LGPL/AGPL/MPL-link/SSPL/EPL.
|
||||
#
|
||||
# ⚠ KNOW THE LIMIT OF THIS GATE. cargo-about walks the CARGO graph, so it sees CRATES. A native
|
||||
# library linked through a permissively-licensed `-sys` crate is INVISIBLE to it, licence and all.
|
||||
# FFmpeg is precisely that shape: `ffmpeg-sys-next` is WTFPL and passes cleanly, while the LGPL
|
||||
# libavcodec/libavutil/swscale it link-imports — and which the Windows host installer bundles as
|
||||
# DLLs — never appear in the harvest at all. This gate did not catch FFmpeg entering the tree and
|
||||
# would not catch the next such library. Copyleft arriving as C behind a -sys crate is a REVIEW
|
||||
# question, not a CI one; the LGPL obligations we do carry are discharged by hand (the notice files
|
||||
# and the replaceable-DLL linkage, see packaging/windows/punktfunk-host.iss).
|
||||
#
|
||||
# Since M10 this is a HOST-only concern: the client links no FFmpeg, so for every client artifact
|
||||
# the crate graph and the linked set finally coincide and the gate means what it appears to mean.
|
||||
#
|
||||
# The dependency-free fallback is scripts/gen-third-party-notices.py (reads the cargo registry cache),
|
||||
# which is what produced the committed baseline when cargo-about is unavailable offline.
|
||||
|
||||
|
||||
@@ -15,6 +15,11 @@ FROM docker.io/library/archlinux:base-devel
|
||||
# One transaction: the main build/runtime deps (first list) + the gamescope companion's
|
||||
# deps (second list) — both copied verbatim from what arch.yml installed in-job, where
|
||||
# they now no-op as `--needed` guards.
|
||||
# vulkan-headers rides the first list only because arch.yml's copy does; the package it actually
|
||||
# serves is the gamescope companion (packaging/gamescope/PKGBUILD makedepends). punktfunk itself
|
||||
# needs no system Vulkan headers — pyrowave-sys bindgens its own vendored copy and ash dlopens the
|
||||
# loader — but arch.yml builds gamescope with `makepkg -d`, so an absent makedepend would not be
|
||||
# reported as a missing dependency, only as a compile failure. Keep it.
|
||||
RUN pacman -Syu --noconfirm --needed \
|
||||
git nodejs rust clang cmake ninja nasm pkgconf python vulkan-headers \
|
||||
gtk4 libadwaita sdl3 ffmpeg pipewire wayland libxkbcommon opus libei \
|
||||
|
||||
@@ -27,8 +27,10 @@ RUN dnf -y install \
|
||||
mesa-libGL-devel mesa-libgbm-devel \
|
||||
# punktfunk-client link deps (GTK4 shell + SDL3 gamepads)
|
||||
gtk4-devel libadwaita-devel SDL3-devel \
|
||||
# pf-ffvk bindgen over libavutil/hwcontext_vulkan.h needs <vulkan/vulkan.h>
|
||||
vulkan-headers \
|
||||
# No vulkan-headers: nothing in the workspace compiles against the system Vulkan headers
|
||||
# (pyrowave-sys bindgens its own vendored copy; host and client both reach Vulkan through
|
||||
# ash, which dlopens the loader), and packaging/rpm/punktfunk.spec BuildRequires none.
|
||||
# rpm.yml's HDR gamescope leg needs them and pulls them with `dnf builddep gamescope`.
|
||||
&& dnf clean all
|
||||
|
||||
# bun — both the BUILD tool and the RUNTIME for the punktfunk-web console (`bun run build` -> the
|
||||
|
||||
@@ -29,15 +29,16 @@ RUN sed -i 's|^Types: deb$|Types: deb\nArchitectures: amd64|' /etc/apt/sources.l
|
||||
&& dpkg --add-architecture arm64
|
||||
|
||||
# 2. The cross toolchain + every arm64 dev lib the client links. Mirrors the client half of
|
||||
# rust-ci.Dockerfile's list (FFmpeg, PipeWire, Opus, SDL3, GTK4/libadwaita, xkbcommon,
|
||||
# Vulkan headers for pf-ffvk's bindgen over hwcontext_vulkan.h).
|
||||
# rust-ci.Dockerfile's list (FFmpeg, PipeWire, Opus, SDL3, GTK4/libadwaita, xkbcommon). No
|
||||
# Vulkan dev package: nothing compiles or links against Vulkan — ash dlopens the loader, and
|
||||
# pyrowave-sys bindgens its own vendored headers.
|
||||
RUN apt-get update && apt-get install -y --no-install-recommends \
|
||||
crossbuild-essential-arm64 \
|
||||
libavcodec-dev:arm64 libavformat-dev:arm64 libavutil-dev:arm64 libswscale-dev:arm64 \
|
||||
libavfilter-dev:arm64 libavdevice-dev:arm64 \
|
||||
libpipewire-0.3-dev:arm64 libopus-dev:arm64 \
|
||||
libsdl3-dev:arm64 libgtk-4-dev:arm64 libadwaita-1-dev:arm64 \
|
||||
libwayland-dev:arm64 libxkbcommon-dev:arm64 libvulkan-dev:arm64 \
|
||||
libwayland-dev:arm64 libxkbcommon-dev:arm64 \
|
||||
&& rm -rf /var/lib/apt/lists/*
|
||||
|
||||
# 3. The Rust target — installed against the toolchain the WORKSPACE pins, not the image's
|
||||
|
||||
@@ -22,8 +22,9 @@ RUN apt-get update && apt-get install -y --no-install-recommends \
|
||||
libgl-dev libegl-dev libgbm-dev \
|
||||
# punktfunk-client-linux (GTK4/libadwaita shell, SDL3 gamepads)
|
||||
libgtk-4-dev libadwaita-1-dev libsdl3-dev \
|
||||
# pf-ffvk (bindgen over libavutil/hwcontext_vulkan.h needs <vulkan/vulkan.h>)
|
||||
libvulkan-dev \
|
||||
# No libvulkan-dev: nothing in the workspace compiles or links against Vulkan (pyrowave-sys
|
||||
# bindgens its own vendored headers, and both host and client reach Vulkan through ash, which
|
||||
# dlopens the loader), so neither the build nor deb.yml's dpkg-shlibdeps ever asks for it.
|
||||
&& rm -rf /var/lib/apt/lists/*
|
||||
|
||||
# bun — builds the punktfunk-web console in deb.yml (which runs the web build in THIS image).
|
||||
|
||||
File diff suppressed because it is too large
Load Diff
@@ -58,8 +58,8 @@ struct AcknowledgementsView: View {
|
||||
.font(.geist(Self.headlineFont, .semibold, relativeTo: .headline))
|
||||
Text(
|
||||
"Punktfunk uses the open-source components below, each under its own license. "
|
||||
+ "On some platforms FFmpeg is additionally bundled under the LGPL v2.1+ "
|
||||
+ "(dynamically linked, replaceable)."
|
||||
+ "Video decoding uses the system's own VideoToolbox framework, so nothing "
|
||||
+ "is bundled for it — and no Punktfunk client bundles FFmpeg on any platform."
|
||||
)
|
||||
.font(.geist(Self.captionFont, relativeTo: .caption))
|
||||
.foregroundStyle(.secondary)
|
||||
|
||||
File diff suppressed because it is too large
Load Diff
@@ -1,6 +1,6 @@
|
||||
[package]
|
||||
name = "punktfunk-client-linux"
|
||||
description = "Native Linux punktfunk/1 client — GTK4/libadwaita shell, FFmpeg decode, PipeWire audio, SDL3 gamepads"
|
||||
description = "Native Linux punktfunk/1 client — GTK4/libadwaita shell, PipeWire audio, SDL3 gamepads; streaming runs in the spawned punktfunk-session binary"
|
||||
version.workspace = true
|
||||
edition.workspace = true
|
||||
rust-version.workspace = true
|
||||
|
||||
+13
-8
@@ -12,9 +12,11 @@ Built in Rust end to end (no C ABI): the shell shares its plumbing with the sess
|
||||
|
||||
## Features
|
||||
|
||||
- **Zero-copy hardware decode** — the session presenter decodes via **Vulkan Video** on every GPU
|
||||
vendor (including NVIDIA), falling back to FFmpeg VAAPI → DRM-PRIME dmabuf and then software when
|
||||
Vulkan Video is unavailable.
|
||||
- **Zero-copy hardware decode, and it's ours** — the session presenter decodes with Punktfunk's own
|
||||
decoders; no FFmpeg is linked or bundled. **Vulkan Video** (`pf-vkdecode`, decoding onto the
|
||||
presenter's own device) leads on NVIDIA and AMD, **VAAPI** (`pf-vaadec` driving a dlopen'd libva,
|
||||
exporting DRM-PRIME dmabufs) leads on Intel, whichever isn't first is the fallback, and an
|
||||
OpenH264/rav1d CPU rung is last.
|
||||
- **Your display's native mode** — the host builds a virtual output at exactly your WxH@Hz; no
|
||||
scaling, no letterboxing. Steady 60 fps at 1080p60, ~6 ms capture→decoded on the LAN.
|
||||
- **Audio both ways** — PipeWire playback with a jitter ring, plus mic uplink to the host.
|
||||
@@ -50,8 +52,11 @@ Per-device install steps and pairing walkthrough:
|
||||
|
||||
## Build & run from source
|
||||
|
||||
Requires GTK ≥ 4.16, libadwaita ≥ 1.5, FFmpeg 7 or 8 (with VAAPI for hardware decode), PipeWire,
|
||||
and SDL3 (with hidapi) development packages.
|
||||
Requires GTK ≥ 4.16, libadwaita ≥ 1.5, PipeWire, and SDL3 (with hidapi) development packages,
|
||||
plus a C compiler (the CPU rung builds OpenH264 from source). No *decoder* development package
|
||||
is needed: libva and the Vulkan loader are both opened at runtime rather than linked, so
|
||||
hardware decode is a fact about the box you **run** on — a Vulkan loader and your GPU's driver,
|
||||
and libva for the VAAPI rung — not about the one you build on.
|
||||
|
||||
```sh
|
||||
# from the repo root
|
||||
@@ -85,9 +90,9 @@ src/
|
||||
tools/screenshots.sh store screenshot capture (app self-capture; Xvfb fallback)
|
||||
```
|
||||
|
||||
The UI-agnostic plumbing — session pump, FFmpeg decode, PipeWire audio, SDL3 gamepads +
|
||||
keymap, trust store, mDNS discovery, library client, Wake-on-LAN — lives in
|
||||
`crates/pf-client-core`, shared with the Vulkan session binary.
|
||||
The UI-agnostic plumbing — session pump, the native decode ladder (Vulkan Video · VAAPI ·
|
||||
OpenH264/rav1d), PipeWire audio, SDL3 gamepads + keymap, trust store, mDNS discovery, library
|
||||
client, Wake-on-LAN — lives in `crates/pf-client-core`, shared with the Vulkan session binary.
|
||||
|
||||
## Related
|
||||
|
||||
|
||||
File diff suppressed because it is too large
Load Diff
@@ -728,7 +728,11 @@ const CODEC_LABELS: &[&str] = &[
|
||||
"AV1",
|
||||
"PyroWave (wired LAN)",
|
||||
];
|
||||
const DECODERS: &[&str] = &["auto", "vulkan", "vaapi", "software"];
|
||||
// Stored decoder-preference values. `native-*` since M10 — the bare "vulkan"/"vaapi"
|
||||
// named libavcodec's rungs, which are deleted; a store still holding them is migrated on
|
||||
// read (`pf_client_core::video::migrate_decoder_pref`) and simply matches no entry here
|
||||
// until the user re-picks. The labels below are unchanged and still true.
|
||||
const DECODERS: &[&str] = &["auto", "native-vulkan", "native-vaapi", "software"];
|
||||
/// Touch-input model values (persisted) paired with their display labels below — the
|
||||
/// cross-client set (Android/Apple). Only meaningful on a touchscreen (Deck/tablet).
|
||||
const TOUCH_MODES: &[&str] = &["trackpad", "pointer", "touch"];
|
||||
@@ -773,16 +777,26 @@ const APP_LICENSE: &str = concat!(
|
||||
"\n\n=============================== Apache-2.0 ===============================\n\n",
|
||||
include_str!("../../../LICENSE-APACHE"),
|
||||
);
|
||||
/// Third-party software notices for the linked Rust crates (generated by
|
||||
/// scripts/gen-third-party-notices.sh; shown as a Legal section in the About dialog).
|
||||
const THIRD_PARTY_NOTICES: &str = include_str!("../../../THIRD-PARTY-NOTICES.txt");
|
||||
/// Third-party software notices for the Rust crates THIS CLIENT links — the shell, the
|
||||
/// session streamer, the headless CLI and the update helper (generated by
|
||||
/// scripts/gen-third-party-notices.sh; shown as a Legal section in the About dialog, and
|
||||
/// shipped as /usr/share/doc/punktfunk-client/THIRD-PARTY-NOTICES.txt by the packages).
|
||||
///
|
||||
/// Deliberately the client-scoped file and not the workspace-wide one at the repo root:
|
||||
/// that root file is the HOST's, it still carries `ffmpeg-next` and the full FFmpeg licence
|
||||
/// text — and after M10 this app links no FFmpeg at all, which is exactly what the section
|
||||
/// below it claims.
|
||||
const THIRD_PARTY_NOTICES: &str = include_str!("../THIRD-PARTY-NOTICES.txt");
|
||||
|
||||
/// The dynamically linked system libraries — not in the crate notices, since they aren't
|
||||
/// crates. Their full texts ship with each project rather than being vendored here.
|
||||
const SYSTEM_LIBRARY_NOTICES: &str =
|
||||
"This application dynamically links system libraries under their own licenses, including \
|
||||
FFmpeg (LGPL v2.1+), GTK 4 and libadwaita (LGPL v2.1+), PipeWire (MIT), and SDL 3 (Zlib). \
|
||||
Their full license texts are available from each project.";
|
||||
GTK 4 and libadwaita (LGPL v2.1+), PipeWire (MIT), and SDL 3 (Zlib). \
|
||||
Their full license texts are available from each project. Video decoding uses the \
|
||||
system's own Vulkan Video and VAAPI drivers (loaded at runtime, never linked), with \
|
||||
OpenH264 and rav1d — both BSD-2-Clause, and both in the Rust crate notices — as the \
|
||||
CPU fallback; no FFmpeg is linked or bundled.";
|
||||
|
||||
/// Show the About dialog (app license + the third-party-software Legal section) — reached
|
||||
/// from the primary menu (app.rs `win.about`).
|
||||
@@ -806,7 +820,7 @@ pub fn show_about(parent: &impl IsA<gtk::Widget>) {
|
||||
.license_type(gtk::License::Custom)
|
||||
.license(license.as_str())
|
||||
.build();
|
||||
// The native (FFmpeg/GTK/PipeWire/SDL3) components are dynamically linked under their own
|
||||
// The native (GTK/PipeWire/SDL3) components are dynamically linked under their own
|
||||
// (LGPL/Zlib/MIT) licenses; the Rust crate notices are the substantive attribution set.
|
||||
about.add_legal_section(
|
||||
"Third-party software (Rust crates)",
|
||||
@@ -1628,7 +1642,11 @@ pub fn show_scoped(
|
||||
mouse_row.set_selected(mouse_i);
|
||||
set_row_subtitle(mouse_row.widget(), MOUSE_MODE_CAPTIONS[mouse_i as usize]);
|
||||
compositor_row.set_selected(index::compositor(s));
|
||||
let dec_i = DECODERS.iter().position(|&d| d == s.decoder).unwrap_or(0);
|
||||
// Migrated for the LOOKUP only (the store is left alone): a pre-M10 settings file
|
||||
// holds `vulkan`/`vaapi`, which match no entry — the combo would show Automatic and
|
||||
// a save would silently rewrite the user's hardware preference to `auto`.
|
||||
let dec_stored = pf_client_core::video::migrate_decoder_pref(&s.decoder);
|
||||
let dec_i = DECODERS.iter().position(|&d| d == dec_stored).unwrap_or(0);
|
||||
decoder_row.set_selected(dec_i as u32);
|
||||
stats_row.set_selected(index::stats(s));
|
||||
fullscreen_row.set_active(s.fullscreen_on_stream);
|
||||
|
||||
@@ -24,7 +24,6 @@ pyrowave = ["pf-client-core/pyrowave", "pf-presenter/pyrowave"]
|
||||
# (`--no-default-features`) is the ~15 MB-smaller power-user build: same streaming,
|
||||
# stats on stdout only.
|
||||
ui = ["dep:pf-console-ui", "dep:serde_json"]
|
||||
|
||||
# Same Linux+Windows gating as the rest of the client stack; elsewhere this is a stub
|
||||
# binary.
|
||||
[target.'cfg(any(target_os = "linux", windows))'.dependencies]
|
||||
|
||||
@@ -49,19 +49,67 @@ path + per-stage latency equation); any tier but Off also emits the stdout mirro
|
||||
`--no-default-features` is the ~5 MB power-user build — same streaming, stats on stdout
|
||||
only, no Skia anywhere in the dependency tree.
|
||||
|
||||
Decode follows the Settings preference (auto: Vulkan Video → VAAPI → software on Linux,
|
||||
Vulkan Video → D3D11VA → software on Windows): FFmpeg's Vulkan Video decoder runs on the
|
||||
presenter's own device where the stack supports it (every vendor, zero copy); VAAPI
|
||||
dmabufs import per-plane elsewhere (D3D11VA textures on Windows); software is the
|
||||
universal fallback. 10-bit Main10 and HDR10 are advertised
|
||||
(`VIDEO_CAP_10BIT|HDR`): P010 decodes through all three paths, and PQ streams present
|
||||
Decode follows the Settings preference (auto is vendor-ordered: Vulkan Video → VAAPI →
|
||||
software on Linux, Vulkan Video → D3D11VA → software on Windows, with VAAPI/D3D11VA first
|
||||
on Intel — every rung native since M10; see "Decode rungs" below): the Vulkan decoder runs
|
||||
on the presenter's own device where the stack supports it (every vendor, zero copy); VAAPI
|
||||
dmabufs import per-plane elsewhere (D3D11VA textures on Windows); software is the universal
|
||||
fallback. 10-bit Main10 and HDR10 are advertised (`VIDEO_CAP_10BIT|HDR`): P010 decodes
|
||||
through the Vulkan and VAAPI/D3D11VA paths (the CPU rung is 8-bit by contract and refuses
|
||||
10-bit rather than mis-scaling it), and PQ streams present
|
||||
on an HDR10/ST.2084 swapchain when the desktop offers one (KDE HDR, gamescope) or
|
||||
tone-map in-shader to SDR when it doesn't (`PUNKTFUNK_TONEMAP_PEAK` tunes the rolloff,
|
||||
default ≈1000 nits). The host still gates the upgrade behind its `PUNKTFUNK_10BIT`
|
||||
policy.
|
||||
|
||||
Debug/bisect knobs: `PUNKTFUNK_DECODER=vulkan|vaapi|d3d11va|software`, `PUNKTFUNK_PRESENT_MODE=
|
||||
## Decode rungs (M10: native only)
|
||||
|
||||
**This binary contains no FFmpeg.** `auto` walks native rungs — pf-vkdecode over Vulkan
|
||||
Video, then the platform's own (pf-dxvadec on Windows, pf-vaadec on Linux), then the CPU
|
||||
rung (openh264/rav1d). The libavcodec rungs that used to sit under each of them are
|
||||
deleted, along with `pf-ffvk` and the `ffmpeg-next` dependency.
|
||||
|
||||
Two of the native rungs have never decoded a frame on real hardware (native VAAPI at all;
|
||||
native D3D11VA's AV1 leg). They run anyway — with the libavcodec twins gone, the only
|
||||
thing below them is the CPU, so barring them would cost the session hardware decode
|
||||
outright rather than move it one rung down. What replaces the safety net is the log: every
|
||||
session names the rung it landed on with its evidence state,
|
||||
|
||||
decode rung active rung=native-vulkan codec=HEVC hardware_verified=true evidence=...
|
||||
|
||||
…and that line is a **WARNING** when nothing has ever decoded a frame through the
|
||||
rung/codec pair the session chose. `pf-client-core`'s `video.rs` module docs carry the full
|
||||
table; read any field report about M10 against it.
|
||||
|
||||
Debug/bisect knobs: `PUNKTFUNK_DECODER=native-vulkan|native-vaapi|native-d3d11va|software`
|
||||
(a pin skips the vendor order, which is how a lab run reaches a rung `auto` will not pick
|
||||
on this device; a pinned rung that cannot open still falls through to the standard ladder,
|
||||
loudly; `native-vaapi` also takes `PUNKTFUNK_VAAPI_DEVICE=/dev/dri/renderDNNN` to choose
|
||||
the GPU). The pre-M10 spellings `vulkan`/`vaapi`/`d3d11va` named the libavcodec rungs
|
||||
specifically; they are MIGRATED onto the native rung for the same hardware family, with a
|
||||
`warn` line saying so — every desktop Settings UI offered those values, so refusing them
|
||||
would end a session over a dropdown someone picked long ago.
|
||||
`PUNKTFUNK_PRESENT_MODE=
|
||||
mailbox|fifo|immediate|fifo_relaxed` (default MAILBOX, FIFO where the surface offers no
|
||||
MAILBOX — AMD on Windows), `PUNKTFUNK_VK_DEVICE=<index>` (multi-GPU), and
|
||||
`PUNKTFUNK_HW_FAULT=import` (fault every VAAPI dmabuf import — proves the three-strike
|
||||
demotion to software on healthy hardware).
|
||||
|
||||
`PUNKTFUNK_AU_FAULT=drop|truncate|flip[:period]` deliberately corrupts decoder input on the
|
||||
native Vulkan lane (default period 60 — one AU a second at 60 fps; inert everywhere else, and
|
||||
inert entirely if the value doesn't parse). `drop` swallows the AU, so the next one references a
|
||||
picture that was never decoded — the bitstream planner catches it immediately. `truncate` delivers
|
||||
a picture whose slice data stops mid-frame and `flip` alters one byte deep in the payload: both
|
||||
parse perfectly, so only the driver's per-frame decode-status query can see them, and neither is
|
||||
visible at all on a driver without `queryResultStatusSupport`. Watch the
|
||||
result on the Detailed stats line's `integrity:` term (`damaged` = concealment the planner caught,
|
||||
`refused` = AUs the decoder rejected outright, `driver-failed` = the hardware's own verdict, `run`
|
||||
= consecutive frames with no picture, `worst run` = the longest such stretch of the session — the
|
||||
once-a-second `run` sample misses the bad moment almost every time — and `no driver status` = this
|
||||
device cannot answer the driver question at all). A session that lands on any other lane says so
|
||||
in the log rather than faulting silently.
|
||||
|
||||
Note that `PUNKTFUNK_AU_DUMP` records the AU as it arrived from the HOST, while the fault injector
|
||||
runs later, at the native decoder's own entry. On a faulted run the dump is therefore the clean
|
||||
bitstream — reconstruct the damaged bytes from the spec if you need them (the injector is pure and
|
||||
deterministic).
|
||||
|
||||
@@ -346,9 +346,13 @@ mod session_main {
|
||||
bitrate_kbps: settings.bitrate_kbps,
|
||||
audio_channels: settings.audio_channels,
|
||||
preferred_codec: settings.preferred_codec(),
|
||||
// Nothing excluded on a fresh dial. Only the run loop's codec-fallback retry
|
||||
// sets this, and it does so on a CLONE of these params — a Settings-level
|
||||
// "never use HEVC" would be `preferred_codec`, not this.
|
||||
exclude_codecs: 0,
|
||||
// HDR off = don't advertise 10-bit/HDR at all; the host then never upgrades.
|
||||
// MULTI_SLICE is decoder truth for THIS embedder: every desktop decode stack
|
||||
// (FFmpeg software, VAAPI, D3D11VA, Vulkan Video) handles AUs carrying several
|
||||
// (Vulkan Video, D3D11VA, VAAPI, openh264/rav1d) handles AUs carrying several
|
||||
// slice NALs, so the host may keep its multi-slice low-latency default (§7 LN1).
|
||||
// The mobile/TV embedders must NOT copy this blindly — Amlogic MediaCodec wedges
|
||||
// on multi-slice AUs (see `VIDEO_CAP_MULTI_SLICE`), so they advertise per-decoder.
|
||||
@@ -357,12 +361,14 @@ mod session_main {
|
||||
// HEVC, and a real GPU 4:4:4 encode probe, and answers the resolved chroma in the
|
||||
// Welcome BEFORE we build a decoder. Advertised whenever the user asks because
|
||||
// every path can DISPLAY it: the Vulkan presenter samples the 2-plane 4:4:4 pool
|
||||
// formats (hardware RExt decode where the driver offers it — NVIDIA today) and
|
||||
// swscale converts anything else for the software rung, with the decoder ladder
|
||||
// demoting on its own. No capability probe gates the bit — software decode is the
|
||||
// guaranteed floor — but the cost is VISIBLE, not silent: the Detailed stats
|
||||
// overlay prints the resolved chroma ("4:4:4→4:2:0" when the host declined) and
|
||||
// the decode path frames actually took.
|
||||
// formats (hardware RExt decode where the driver offers it — NVIDIA today),
|
||||
// with the decoder ladder demoting on its own. No capability probe gates the
|
||||
// bit — but note (M8) that the software rung below it is 4:2:0 8-bit ONLY and
|
||||
// refuses anything else rather than mis-scaling it, so on a box whose hardware
|
||||
// 4:4:4 decode fails the floor is a codec fallback, not a converted picture.
|
||||
// The cost stays VISIBLE, not silent: the Detailed stats overlay prints the
|
||||
// resolved chroma ("4:4:4→4:2:0" when the host declined) and the decode path
|
||||
// frames actually took.
|
||||
video_caps: punktfunk_core::quic::VIDEO_CAP_MULTI_SLICE
|
||||
| if settings.hdr_enabled {
|
||||
punktfunk_core::quic::VIDEO_CAP_10BIT | punktfunk_core::quic::VIDEO_CAP_HDR
|
||||
@@ -488,7 +494,8 @@ mod session_main {
|
||||
///
|
||||
/// RADV-only knob: ANV/NVIDIA/other drivers ignore `RADV_PERFTEST`, and a box where video
|
||||
/// decode is already the default just no-ops. Append rather than clobber so a user's own
|
||||
/// `RADV_PERFTEST` survives; `PUNKTFUNK_DECODER=vaapi` still overrides the decoder choice.
|
||||
/// `RADV_PERFTEST` survives; `PUNKTFUNK_DECODER=native-vaapi` still overrides the decoder
|
||||
/// choice (the pre-M10 `vaapi` spelling reaches the same rung — it migrates, loudly).
|
||||
#[cfg(target_os = "linux")]
|
||||
fn enable_radv_video_decode() {
|
||||
const TOKEN: &str = "video_decode";
|
||||
|
||||
@@ -78,11 +78,6 @@ windows = { git = "https://github.com/microsoft/windows-rs", rev = "acb5a1a74410
|
||||
"winuser",
|
||||
] }
|
||||
|
||||
# FFmpeg — used only to enumerate which codecs this client can decode (probe::decodable_codecs),
|
||||
# advertised to the host on the speed-test connect. Same pin as the host/Linux client. (Real
|
||||
# decode + present live in the spawned punktfunk-session binary.)
|
||||
ffmpeg-next = "8"
|
||||
|
||||
# Gamepad enumeration + pin persistence for Settings runs on pf-client-core's shared SDL service
|
||||
# (see the `gamepad` field in app/); the spawned punktfunk-session does the actual forwarding. SDL3
|
||||
# itself (built from source via the bundled CMake on Windows) is pulled transitively by
|
||||
|
||||
@@ -2,7 +2,7 @@
|
||||
|
||||
The native **Windows** app for streaming a punktfunk host to your PC. A modern WinUI 3 app that
|
||||
discovers hosts on your network, pairs with a PIN, and streams at your display's own resolution and
|
||||
refresh rate — with a hardware-accelerated D3D11 video path and HDR.
|
||||
refresh rate — with hardware-accelerated video decode and HDR.
|
||||
|
||||
It's **pure Rust**: the UI is WinUI 3 driven through [windows-reactor](https://github.com/microsoft/windows-rs)
|
||||
(a declarative, React-like framework), and it links the shared **`punktfunk-core`** directly to speak
|
||||
@@ -10,9 +10,11 @@ the fast **`punktfunk/1`** protocol.
|
||||
|
||||
## Features
|
||||
|
||||
- **Hardware decode, GPU present** — FFmpeg HEVC with a **D3D11VA zero-copy path** (decoder and
|
||||
presenter share one D3D11 device; NV12/P010 textures sampled straight into a `SwapChainPanel`
|
||||
composition swapchain), with a robust software-decode fallback.
|
||||
- **Hardware decode, GPU present** — Punktfunk's own decoders, no FFmpeg anywhere in the client:
|
||||
**Vulkan Video** (`pf-vkdecode`) leads on NVIDIA and AMD, **D3D11VA** (`pf-dxvadec` driving
|
||||
`ID3D11VideoDecoder`) leads on Intel, whichever isn't first is the fallback, and an
|
||||
OpenH264/rav1d CPU rung is last. Either hardware rung hands its surface to the Vulkan presenter
|
||||
without a CPU copy.
|
||||
- **HDR10** — advertise 10-bit/HDR, detect PQ in-band, and flip the swapchain to `R10G10B10A2` +
|
||||
ST.2084 with HDR10 metadata.
|
||||
- **Your display's native mode** — the host builds a virtual display at exactly your WxH@Hz.
|
||||
@@ -42,9 +44,9 @@ A stock [Moonlight](https://moonlight-stream.org/) client also works over GameSt
|
||||
## Build from source
|
||||
|
||||
Windows-only (the crate builds as a stub on other platforms so the workspace stays green). You need
|
||||
the MSVC toolchain, an `FFMPEG_DIR` FFmpeg tree, and CMake (SDL3 builds from source). The Windows
|
||||
App SDK runtime bootstrap is staged next to the exe by `windows-reactor-setup` from this crate's
|
||||
own `build.rs` — no extra environment needed.
|
||||
the MSVC toolchain and CMake (SDL3 builds from source) — nothing else: decode is native since M10,
|
||||
so there is no `FFMPEG_DIR` to point anywhere, and the Windows App SDK runtime bootstrap is staged
|
||||
next to the exe by `windows-reactor-setup` from this crate's own `build.rs`.
|
||||
|
||||
```sh
|
||||
cargo build -p punktfunk-client-windows --target x86_64-pc-windows-msvc
|
||||
|
||||
File diff suppressed because it is too large
Load Diff
@@ -9,8 +9,9 @@ touches the client (canary) and on `vX.Y.Z` release tags (stable) — see
|
||||
|
||||
**Two architectures, one x64 runner.** Both `x64` and `arm64` packages are produced off the single
|
||||
x64 Windows runner — `x86_64-pc-windows-msvc` builds natively, `aarch64-pc-windows-msvc` is
|
||||
cross-compiled (the x64 MSVC toolset ships the ARM64 cross compiler; the matrix points `FFMPEG_DIR`
|
||||
at the runner's ARM64 FFmpeg tree, `C:\Users\Public\ffmpeg-arm64`). Artifacts are arch-suffixed
|
||||
cross-compiled (the x64 MSVC toolset ships the ARM64 cross compiler; since M10 nothing in the
|
||||
package links FFmpeg, so neither arch needs a per-arch `FFMPEG_DIR` tree staged on the runner —
|
||||
one less thing the ARM64 leg can be missing). Artifacts are arch-suffixed
|
||||
(`..._x64.msix` / `..._arm64.msix`, each with its matching `.cer`); `pack-msix.ps1 -Arch x64|arm64`
|
||||
stamps the manifest `ProcessorArchitecture` and names the output. See
|
||||
[`windows.yml`](../../../.gitea/workflows/windows.yml) for the cross-build rationale.
|
||||
@@ -25,10 +26,17 @@ stamps the manifest `ProcessorArchitecture` and names the output. See
|
||||
| `punktfunk-session.exe` | the release build — the Vulkan session client the shell spawns for every stream (sibling resolution, `src/spawn.rs`). Skia links statically; `vulkan-1.dll` is a GPU-driver component, never bundled. ARM64 builds it `--no-default-features` (no Skia console UI) until rust-skia ships aarch64-pc-windows-msvc prebuilts |
|
||||
| `Microsoft.WindowsAppRuntime.Bootstrap.dll`, `resources.pri` | staged by the client's `build.rs` via `windows-reactor-setup::as_framework_dependent()` |
|
||||
| `SDL3.dll` | auto-staged by the `sdl3` crate |
|
||||
| `avcodec/avformat/avutil/swscale/swresample/...-*.dll` | `FFMPEG_DIR\bin` |
|
||||
| `licenses\*` | the project's MIT/Apache texts + the generated `THIRD-PARTY-NOTICES.txt` (MSIX has no installer EULA page, so attribution ships as files) |
|
||||
| `Assets\*.png` | checked-in tile/store logos (rasterized from `packaging/flatpak/io.unom.Punktfunk.svg`) |
|
||||
| `AppxManifest.xml` | the template here, with `{VERSION}`/`{PUBLISHER}` substituted |
|
||||
|
||||
**No FFmpeg DLLs.** The client decodes natively since M10 (`pf-vkdecode` / `pf-dxvadec` /
|
||||
OpenH264+rav1d — punktfunk-planning `design/client-native-decode.md` §6), so nothing here
|
||||
link-imports `libav*` and the wildcard `avcodec/avformat/avutil/swscale/swresample-*.dll` copy is
|
||||
gone, along with the FFmpeg LGPL notice that accompanied it — shipping that notice now would claim
|
||||
a dependency the package doesn't have. The **host** installer is unchanged:
|
||||
`packaging/windows/pack-host-installer.ps1` still ships those DLLs for its AMF/QSV encode path.
|
||||
|
||||
### Why an "unpackaged" WinUI app packages cleanly
|
||||
|
||||
`main` calls `windows_reactor::bootstrap()`, which runs `MddBootstrapInitialize2` with
|
||||
@@ -87,8 +95,7 @@ cargo build --release -p punktfunk-client-windows --target x86_64-pc-windows-msv
|
||||
pwsh -File clients/windows/packaging/pack-msix.ps1 `
|
||||
-Version 0.2.0.0 -TargetDir C:\t\x86_64-pc-windows-msvc\release -OutDir C:\t\msix
|
||||
|
||||
# arm64 (cross-compiled; point FFMPEG_DIR at the ARM64 tree)
|
||||
$env:FFMPEG_DIR = 'C:\Users\Public\ffmpeg-arm64'
|
||||
# arm64 (cross-compiled; no extra environment — the client links no FFmpeg)
|
||||
cargo build --release -p punktfunk-client-windows --target aarch64-pc-windows-msvc
|
||||
pwsh -File clients/windows/packaging/pack-msix.ps1 `
|
||||
-Version 0.2.0.0 -Arch arm64 -TargetDir C:\t\aarch64-pc-windows-msvc\release -OutDir C:\t\msix
|
||||
|
||||
@@ -4,9 +4,14 @@
|
||||
|
||||
.DESCRIPTION
|
||||
Builds a packaging layout from a release `cargo build` output (exe + the reactor/SDL3 auto-staged
|
||||
DLLs + resources.pri + FFmpeg DLLs + the checked-in Assets + the manifest), runs makeappx, and
|
||||
DLLs + resources.pri + the checked-in Assets + the manifest), runs makeappx, and
|
||||
signs with signtool. Idempotent; safe to re-run.
|
||||
|
||||
NO FFmpeg DLLs since M10 (design/client-native-decode.md §6): the client decodes natively
|
||||
(pf-vkdecode / pf-dxvadec / openh264+rav1d) and link-imports no libav* at all, so the
|
||||
wildcard copy and its LGPL notice are gone with it. The HOST installer is unchanged —
|
||||
packaging/windows/pack-host-installer.ps1 still ships them for its amf-qsv encode path.
|
||||
|
||||
Signing cert precedence:
|
||||
1. -PfxBase64 / -PfxPassword (a real or shared code-signing cert, e.g. from CI secrets) — the
|
||||
cert's subject DN MUST match -Publisher (which is stamped into the manifest Identity).
|
||||
@@ -22,8 +27,7 @@
|
||||
.EXAMPLE
|
||||
# x64 (default arch):
|
||||
pwsh -File pack-msix.ps1 -Version 0.2.137.0 -TargetDir C:\t\x86_64-pc-windows-msvc\release -OutDir C:\t\msix
|
||||
# arm64 (point -TargetDir + FFMPEG_DIR at the ARM64 build/tree):
|
||||
$env:FFMPEG_DIR='C:\Users\Public\ffmpeg-arm64'
|
||||
# arm64 (point -TargetDir at the ARM64 build):
|
||||
pwsh -File pack-msix.ps1 -Version 0.2.137.0 -Arch arm64 -TargetDir C:\t-a64\aarch64-pc-windows-msvc\release -OutDir C:\t-a64\msix
|
||||
#>
|
||||
[CmdletBinding()]
|
||||
@@ -31,7 +35,6 @@ param(
|
||||
[Parameter(Mandatory = $true)][string]$Version, # 4-part numeric, e.g. 0.2.137.0
|
||||
[Parameter(Mandatory = $true)][string]$TargetDir, # cargo --release output dir (has the exe)
|
||||
[ValidateSet('x64', 'arm64')][string]$Arch = 'x64', # package ProcessorArchitecture + artifact suffix
|
||||
[string]$FfmpegBin = $(if ($env:FFMPEG_DIR) { Join-Path $env:FFMPEG_DIR 'bin' } else { 'C:\Users\Public\ffmpeg\bin' }),
|
||||
[string]$OutDir = (Join-Path $TargetDir 'msix'),
|
||||
[string]$Publisher = 'CN=unom', # MUST equal the signing cert subject DN
|
||||
[string]$PfxBase64 = $env:MSIX_CERT_PFX_B64, # optional: base64 of a code-signing .pfx
|
||||
@@ -83,28 +86,32 @@ foreach ($f in $required) {
|
||||
Copy-Item $src (Join-Path $layout $f) -Force
|
||||
}
|
||||
|
||||
# FFmpeg runtime DLLs (the exe link-imports the decode set; copy them all — small and correct).
|
||||
# These are unmodified BtbN *lgpl-shared* builds, linked dynamically (replaceable DLLs) — FFmpeg is
|
||||
# used under the LGPL v2.1+; the license text + notice ship in licenses\ below.
|
||||
$ff = Get-ChildItem -Path $FfmpegBin -Filter *.dll -ErrorAction SilentlyContinue
|
||||
if (-not $ff) { throw "no FFmpeg DLLs in $FfmpegBin" }
|
||||
$ff | ForEach-Object { Copy-Item $_.FullName (Join-Path $layout $_.Name) -Force }
|
||||
|
||||
# license/attribution payload (MSIX has no installer EULA page, so ship them as files): FFmpeg's LGPL
|
||||
# notice + license text, the project's own MIT/Apache texts, and the generated third-party notices.
|
||||
# license/attribution payload (MSIX has no installer EULA page, so ship them as files): the
|
||||
# project's own MIT/Apache texts plus the generated third-party notices, which is where every
|
||||
# vendored/statically-linked dependency's attribution lives (openh264 BSD-2, rav1d BSD-2, …).
|
||||
#
|
||||
# The FFmpeg LGPL notice + license texts that used to be copied here went with the DLLs at M10:
|
||||
# nothing in this package links libav* any more, so shipping an LGPL notice would be claiming a
|
||||
# dependency that is not there.
|
||||
#
|
||||
# For the same reason the notices come from clients/windows/ and NOT from the repo root: the root
|
||||
# file is workspace-wide, it is what the HOST ships out of, and it still lists ffmpeg-next plus the
|
||||
# full FFmpeg licence text. The client-scoped file (same generator, `--packages
|
||||
# punktfunk-client-windows,punktfunk-client-session,punktfunk-cli`) is the one that describes what
|
||||
# is actually inside this .msix — and it is the same file the app's Licenses page shows.
|
||||
$licDir = Join-Path $layout 'licenses'
|
||||
New-Item -ItemType Directory -Force -Path $licDir | Out-Null
|
||||
$repoRoot = (Resolve-Path (Join-Path $PSScriptRoot '..\..\..')).Path
|
||||
Copy-Item (Join-Path $repoRoot 'packaging\windows\licenses\FFmpeg-LGPL-NOTICE.txt') $licDir -Force -ErrorAction SilentlyContinue
|
||||
foreach ($n in @('THIRD-PARTY-NOTICES.txt', 'LICENSE-MIT', 'LICENSE-APACHE')) {
|
||||
$clientRoot = (Resolve-Path (Join-Path $PSScriptRoot '..')).Path
|
||||
foreach ($n in @('LICENSE-MIT', 'LICENSE-APACHE')) {
|
||||
$p = Join-Path $repoRoot $n
|
||||
if (Test-Path $p) { Copy-Item $p $licDir -Force }
|
||||
}
|
||||
$ffRoot = Split-Path $FfmpegBin -Parent
|
||||
foreach ($lic in @('LICENSE.txt', 'LICENSE', 'COPYING.LGPLv2.1', 'COPYING.LGPLv3', 'COPYING.txt')) {
|
||||
$p = Join-Path $ffRoot $lic
|
||||
if (Test-Path $p) { Copy-Item $p $licDir -Force }
|
||||
$notices = Join-Path $clientRoot 'THIRD-PARTY-NOTICES.txt'
|
||||
if (-not (Test-Path $notices)) {
|
||||
throw "missing $notices — run scripts/gen-third-party-notices.sh (it generates the per-client copies)"
|
||||
}
|
||||
Copy-Item $notices $licDir -Force
|
||||
|
||||
# tile/store assets
|
||||
Copy-Item (Join-Path $assets '*') (Join-Path $layout 'Assets') -Force
|
||||
|
||||
@@ -12,9 +12,15 @@ const APP_LICENSE: &str = concat!(
|
||||
"\n\n================================ Apache-2.0 ================================\n\n",
|
||||
include_str!("../../../../LICENSE-APACHE"),
|
||||
);
|
||||
/// Third-party software notices for the linked Rust crates (generated by
|
||||
/// scripts/gen-third-party-notices.sh; the MSIX also ships this under licenses/).
|
||||
const THIRD_PARTY_NOTICES: &str = include_str!("../../../../THIRD-PARTY-NOTICES.txt");
|
||||
/// Third-party software notices for the Rust crates THIS CLIENT links — the shell, the
|
||||
/// session streamer and the headless CLI (generated by scripts/gen-third-party-notices.sh;
|
||||
/// the MSIX ships the same file under licenses/).
|
||||
///
|
||||
/// Deliberately the client-scoped file and not the workspace-wide one at the repo root:
|
||||
/// that root file is the HOST's, it still carries `ffmpeg-next` and the full FFmpeg licence
|
||||
/// text — and after M10 this app bundles no FFmpeg at all, so printing that attribution
|
||||
/// three lines under a card saying so would be a false statement to the user's face.
|
||||
const THIRD_PARTY_NOTICES: &str = include_str!("../../THIRD-PARTY-NOTICES.txt");
|
||||
|
||||
pub(crate) fn licenses_page(ctx: &Arc<AppCtx>, set_screen: &AsyncSetState<Screen>) -> Element {
|
||||
let back_btn = button("Back").accent().icon(Symbol::Back).on_click({
|
||||
@@ -46,9 +52,10 @@ pub(crate) fn licenses_page(ctx: &Arc<AppCtx>, set_screen: &AsyncSetState<Screen
|
||||
vstack((
|
||||
text_block("Bundled components").font_size(15.0).semibold(),
|
||||
text_block(
|
||||
"FFmpeg is bundled under the LGPL v2.1+ (dynamically linked, replaceable DLLs); its \
|
||||
license and notice ship in the installed licenses\\ folder. SDL 3 (Zlib) and the \
|
||||
Windows App SDK (Microsoft) are also linked.",
|
||||
"SDL 3 (Zlib) and the Windows App SDK (Microsoft) are linked; their notices ship \
|
||||
in the installed licenses\\ folder. Video decoding uses Windows' own DXVA and \
|
||||
Vulkan Video, with OpenH264 and rav1d (both BSD-2-Clause) as the CPU fallback — \
|
||||
no FFmpeg is bundled.",
|
||||
)
|
||||
.font_size(12.0)
|
||||
.wrap()
|
||||
|
||||
@@ -45,12 +45,14 @@ fn render_scale_label(scale: f64) -> String {
|
||||
}
|
||||
}
|
||||
/// Decode backend presets: `(stored value, display label)`.
|
||||
// A stored legacy "hardware" (the D3D11VA era) matches no preset, so the combo shows
|
||||
// Automatic — which is exactly how the session's decoder chain reads that value.
|
||||
// A stored legacy value that matches no preset (the D3D11VA-era "hardware", and since M10
|
||||
// the bare "vulkan"/"d3d11va" that named libavcodec's rungs) shows as Automatic — which is
|
||||
// how the session's ladder reads "hardware", and near enough for the other two, which
|
||||
// `pf_client_core::video::migrate_decoder_pref` maps onto the entries below anyway.
|
||||
const DECODERS: &[(&str, &str)] = &[
|
||||
("auto", "Automatic (GPU, fall back to CPU)"),
|
||||
("vulkan", "Hardware (Vulkan Video)"),
|
||||
("d3d11va", "Hardware (Direct3D 11 / DXVA)"),
|
||||
("native-vulkan", "Hardware (Vulkan Video)"),
|
||||
("native-d3d11va", "Hardware (Direct3D 11 / DXVA)"),
|
||||
("software", "Software (CPU)"),
|
||||
];
|
||||
/// Audio channel presets: `(channel count, display label)`. The host clamps to what it can
|
||||
@@ -862,7 +864,11 @@ pub(crate) fn settings_page(
|
||||
);
|
||||
|
||||
// --- Video -----------------------------------------------------------------------------
|
||||
let (dec_names, dec_i) = presets(DECODERS, |v| *v == s.decoder);
|
||||
// Migrated for the LOOKUP only (the store is left alone): a pre-M10 settings file
|
||||
// holds `vulkan`/`d3d11va`, which match no preset — the combo would show Automatic and
|
||||
// a save would silently rewrite the user's hardware preference to `auto`.
|
||||
let stored_decoder = pf_client_core::video::migrate_decoder_pref(&s.decoder);
|
||||
let (dec_names, dec_i) = presets(DECODERS, |v| *v == stored_decoder);
|
||||
let decoder_combo = setting_combo(ctx, scope, (rev, set_rev), dec_names, dec_i, |s, i| {
|
||||
s.decoder = DECODERS[i].0.to_string();
|
||||
});
|
||||
|
||||
@@ -81,8 +81,13 @@ pub(crate) fn session_page(ctx: &Arc<super::AppCtx>, hud: &HudSample) -> Element
|
||||
.map(str::trim)
|
||||
.filter(|c| !c.is_empty())
|
||||
.map(|c| {
|
||||
// The `stats:` decode-path tags (see pf-client-core's session
|
||||
// pump). M10 removed the `vulkan`/`vaapi`/`d3d11va` tags with their
|
||||
// rungs; a hardware rung is now always a `native-*` one.
|
||||
let kind = match c {
|
||||
"vulkan" | "vaapi" => Pill::Good,
|
||||
"native-vulkan" | "native-vaapi" | "native-d3d11va" | "pyrowave" => {
|
||||
Pill::Good
|
||||
}
|
||||
"software" => Pill::Info,
|
||||
_ => Pill::Neutral,
|
||||
};
|
||||
|
||||
@@ -6,26 +6,27 @@
|
||||
//! over the real data plane, so it stays here. [`decodable_codecs`] rode along for the same
|
||||
//! reason — the probe connect still advertises which codecs this client can decode.
|
||||
|
||||
use ffmpeg_next as ffmpeg;
|
||||
use punktfunk_core::client::NativeClient;
|
||||
use punktfunk_core::config::{CompositorPref, GamepadPref, Mode};
|
||||
use std::time::{Duration, Instant};
|
||||
|
||||
/// The `quic` codec bitfield this client can decode — whatever FFmpeg has a decoder for (HEVC/H.264
|
||||
/// always; AV1 when built in). Advertised to the host so it never emits a codec we can't decode.
|
||||
/// The `quic` codec bitfield this client can decode. Advertised to the host so it never emits
|
||||
/// a codec we can't decode.
|
||||
///
|
||||
/// It is pf-client-core's [`decodable_codecs`](pf_client_core::video::decodable_codecs) —
|
||||
/// the codecs the SESSION BINARY's rungs speak, which is the process that actually decodes.
|
||||
/// This shell used to walk libavcodec's registry (`ffmpeg::decoder::find` per id) for the
|
||||
/// same answer; that was wrong in two ways even before M10 deleted the dependency. It
|
||||
/// described the decoders in THIS process, which decodes nothing, and it answered "a
|
||||
/// decoder exists" where the question is "a rung can keep up" — the AV1-on-CPU promise
|
||||
/// `decodable_codecs_for` exists to refuse.
|
||||
///
|
||||
/// ⚠ Deliberately the DEVICE-FREE answer ([`decodable_codecs`], not
|
||||
/// `decodable_codecs_for`): this connect creates no presenter and has no `VulkanDecodeDevice`
|
||||
/// to gate AV1 on, and it decodes nothing — the codec it advertises is never exercised. A
|
||||
/// real session's Hello is built in the session binary, with the device in hand.
|
||||
pub fn decodable_codecs() -> u8 {
|
||||
let _ = ffmpeg::init();
|
||||
let mut bits = 0u8;
|
||||
for (id, bit) in [
|
||||
(ffmpeg::codec::Id::HEVC, punktfunk_core::quic::CODEC_HEVC),
|
||||
(ffmpeg::codec::Id::H264, punktfunk_core::quic::CODEC_H264),
|
||||
(ffmpeg::codec::Id::AV1, punktfunk_core::quic::CODEC_AV1),
|
||||
] {
|
||||
if ffmpeg::decoder::find(id).is_some() {
|
||||
bits |= bit;
|
||||
}
|
||||
}
|
||||
bits
|
||||
pf_client_core::video::decodable_codecs()
|
||||
}
|
||||
|
||||
/// Blocking speed-test probe (the GUI's per-host "Test" and the `--headless --speed-test` CLI):
|
||||
|
||||
@@ -46,8 +46,8 @@
|
||||
{
|
||||
"type": "library",
|
||||
"name": "FFmpeg",
|
||||
"version": "7.x/8.x (system-provided on Linux; replaceable DLLs bundled with the Windows packages)",
|
||||
"description": "Dynamically linked libav* decode/encode; LGPL notice at packaging/windows/licenses/FFmpeg-LGPL-NOTICE.txt",
|
||||
"version": "7.x/8.x (HOST only \u2014 system-provided on Linux; replaceable DLLs bundled with the Windows host installer)",
|
||||
"description": "Dynamically linked libav* ENCODE for the host (pf-encode: NVENC-libav, VAAPI, AMF/QSV); LGPL notice at packaging/windows/licenses/FFmpeg-LGPL-NOTICE.txt. No punktfunk CLIENT links FFmpeg since M10 \u2014 client decode is Vulkan Video / DXVA / VAAPI / VideoToolbox / MediaCodec with openh264 + rav1d as the CPU floor.",
|
||||
"licenses": [{ "license": { "id": "LGPL-2.1-or-later" } }],
|
||||
"externalReferences": [{ "type": "website", "url": "https://ffmpeg.org" }]
|
||||
},
|
||||
|
||||
@@ -0,0 +1,16 @@
|
||||
[package]
|
||||
name = "pf-bitstream"
|
||||
description = "Client-side bitstream layer for native decode: AU parsing, POC/DPB/reference derivation and per-AU DecodePlans (H.264/HEVC/AV1) on the vendored cros-codecs parsers — the layer libavcodec used to be (design/client-native-decode.md §3.1)"
|
||||
version.workspace = true
|
||||
edition.workspace = true
|
||||
rust-version.workspace = true
|
||||
license.workspace = true
|
||||
authors.workspace = true
|
||||
repository.workspace = true
|
||||
|
||||
[dependencies]
|
||||
cros-codecs = { path = "vendor/cros-codecs" }
|
||||
tracing = "0.1"
|
||||
|
||||
[lints]
|
||||
workspace = true
|
||||
File diff suppressed because it is too large
Load Diff
File diff suppressed because it is too large
Load Diff
File diff suppressed because it is too large
Load Diff
@@ -0,0 +1,157 @@
|
||||
//! The client's bitstream layer for native decode (design/client-native-decode.md §3.1):
|
||||
//! everything a stateless hardware decoder needs to know about an AU before submission —
|
||||
//! parsed headers, POC, DPB state, reference lists (including MMCO/LTR, which the hosts'
|
||||
//! RFI recovery actively uses), recovery-point SEI — derived once here and consumed by
|
||||
//! every backend (Vulkan `StdVideo*`, DXVA picparams, libva buffers).
|
||||
//!
|
||||
//! Parsing primitives come from the vendored cros-codecs parser layer
|
||||
//! (`vendor/cros-codecs`, see its PROVENANCE.md); this crate owns what upstream keeps in
|
||||
//! its Linux-only `decoder::stateless` half — the per-AU orchestration — plus the pieces
|
||||
//! upstream lacks (SEI payload parsing: their parsers classify SEI NALUs but never read
|
||||
//! them).
|
||||
//!
|
||||
//! Scope discipline: punktfunk clients decode punktfunk hosts — zero-reorder, no
|
||||
//! B-frames, progressive, parameter sets from encoders we control. Implement to spec
|
||||
//! where cheap; reject-with-log outside that envelope rather than half-decode.
|
||||
//!
|
||||
//! Nothing in this crate may touch a GPU API, an OS handle, or the network: CPU-only by
|
||||
//! construction, so its tests run on every CI leg including macOS. And no `unsafe`,
|
||||
//! compiler-enforced — this layer exists to replace C parsers; it does not get to
|
||||
//! reintroduce their failure mode.
|
||||
#![forbid(unsafe_code)]
|
||||
|
||||
pub mod av1;
|
||||
pub mod h264;
|
||||
pub mod h265;
|
||||
pub mod sei;
|
||||
|
||||
// The vendor-pinning smoke tests below assert against byte counts and golden values from
|
||||
// the vendored snapshot's own test vectors; a cros-codecs re-sync that shifts parser
|
||||
// behavior must trip HERE, in our tree, not in a decode session.
|
||||
#[cfg(test)]
|
||||
mod vendor_smoke {
|
||||
use std::io::Cursor;
|
||||
|
||||
use cros_codecs::bitstream_utils::IvfIterator;
|
||||
use cros_codecs::codec::av1::parser::ObuAction;
|
||||
use cros_codecs::codec::av1::parser::ParsedObu;
|
||||
use cros_codecs::codec::h264::parser::Nalu as H264Nalu;
|
||||
use cros_codecs::codec::h264::parser::Parser as H264Parser;
|
||||
use cros_codecs::codec::h265::parser::Nalu as H265Nalu;
|
||||
use cros_codecs::codec::h265::parser::Parser as H265Parser;
|
||||
|
||||
const H264_25FPS: &[u8] =
|
||||
include_bytes!("../vendor/cros-codecs/src/codec/h264/test_data/test-25fps.h264");
|
||||
const H265_25FPS: &[u8] =
|
||||
include_bytes!("../vendor/cros-codecs/src/codec/h265/test_data/test-25fps.h265");
|
||||
const AV1_25FPS: &[u8] =
|
||||
include_bytes!("../vendor/cros-codecs/src/codec/av1/test_data/test-25fps.ivf.av1");
|
||||
const VP9_25FPS: &[u8] =
|
||||
include_bytes!("../vendor/cros-codecs/src/codec/vp9/test_data/test-25fps.vp9");
|
||||
|
||||
#[test]
|
||||
fn h264_parses_the_vendored_vector_to_its_goldens() {
|
||||
let mut cursor = Cursor::new(H264_25FPS);
|
||||
let mut parser = H264Parser::default();
|
||||
let (mut nalus, mut sps, mut slices) = (0u32, 0u32, 0u32);
|
||||
let mut coded = (0u32, 0u32);
|
||||
while let Ok(nalu) = H264Nalu::next(&mut cursor) {
|
||||
nalus += 1;
|
||||
if let Ok(s) = parser.parse_sps(&nalu) {
|
||||
sps += 1;
|
||||
coded = (
|
||||
(s.pic_width_in_mbs_minus1 as u32 + 1) * 16,
|
||||
(s.pic_height_in_map_units_minus1 as u32 + 1) * 16,
|
||||
);
|
||||
continue;
|
||||
}
|
||||
if parser.parse_pps(&nalu).is_ok() {
|
||||
continue;
|
||||
}
|
||||
if parser.parse_slice_header(nalu).is_ok() {
|
||||
slices += 1;
|
||||
}
|
||||
}
|
||||
// 759 is upstream's own golden for this stream (chromium h264_parser_unittest lineage).
|
||||
assert_eq!(nalus, 759);
|
||||
assert_eq!(sps, 4);
|
||||
assert_eq!(slices, 500);
|
||||
assert_eq!(coded, (320, 240));
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn h265_parses_the_vendored_vector() {
|
||||
let mut cursor = Cursor::new(H265_25FPS);
|
||||
let mut parser = H265Parser::default();
|
||||
let (mut nalus, mut sps, mut slices) = (0u32, 0u32, 0u32);
|
||||
while let Ok(nalu) = H265Nalu::next(&mut cursor) {
|
||||
nalus += 1;
|
||||
if parser.parse_sps(&nalu).is_ok() {
|
||||
sps += 1;
|
||||
continue;
|
||||
}
|
||||
if parser.parse_pps(&nalu).is_ok() {
|
||||
continue;
|
||||
}
|
||||
if parser.parse_slice_header(nalu).is_ok() {
|
||||
slices += 1;
|
||||
}
|
||||
}
|
||||
assert_eq!(nalus, 254);
|
||||
assert_eq!(sps, 1);
|
||||
assert_eq!(slices, 250);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn av1_walks_obus_and_maintains_ref_slots_across_the_stream() {
|
||||
let mut parser = cros_codecs::codec::av1::parser::Parser::default();
|
||||
let (mut obus, mut frames) = (0u32, 0u32);
|
||||
for packet in IvfIterator::new(AV1_25FPS) {
|
||||
let mut consumed = 0;
|
||||
while let Ok(action) = parser.read_obu(&packet[consumed..]) {
|
||||
let obu = match action {
|
||||
ObuAction::Process(obu) => obu,
|
||||
ObuAction::Drop(n) => {
|
||||
consumed += n as usize;
|
||||
continue;
|
||||
}
|
||||
};
|
||||
consumed += obu.bytes_used;
|
||||
obus += 1;
|
||||
// `ref_frame_update` is the parser's ref-slot bookkeeping; without it,
|
||||
// inter frames fail with "Reference is invalid" — the parser validates
|
||||
// reference integrity rather than trusting the stream.
|
||||
match parser.parse_obu(obu).expect("parse_obu") {
|
||||
ParsedObu::FrameHeader(fh) => {
|
||||
frames += 1;
|
||||
parser.ref_frame_update(&fh).expect("ref slot update");
|
||||
}
|
||||
ParsedObu::Frame(f) => {
|
||||
frames += 1;
|
||||
parser.ref_frame_update(&f.header).expect("ref slot update");
|
||||
}
|
||||
_ => {}
|
||||
}
|
||||
}
|
||||
}
|
||||
// 525 is upstream's own golden (cross-checked against GStreamer's OBU walk).
|
||||
assert_eq!(obus, 525);
|
||||
assert_eq!(frames, 274);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn vp9_splits_superframes_and_parses_headers() {
|
||||
let mut parser = cros_codecs::codec::vp9::parser::Parser::default();
|
||||
let (mut chunks, mut frames) = (0u32, 0u32);
|
||||
for packet in IvfIterator::new(VP9_25FPS) {
|
||||
chunks += 1;
|
||||
frames += parser
|
||||
.parse_chunk(packet.as_ref())
|
||||
.expect("vp9 chunk")
|
||||
.len() as u32;
|
||||
}
|
||||
assert_eq!(chunks, 250);
|
||||
// > chunks proves superframe splitting engaged.
|
||||
assert_eq!(frames, 269);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,346 @@
|
||||
//! SEI payload parsing — the piece the vendored parser layer lacks: upstream classifies
|
||||
//! SEI NALUs but never reads a payload. punktfunk needs exactly one payload type per
|
||||
//! codec: the recovery point SEI, which hosts emit on RFI recovery so the client knows
|
||||
//! where a decode-from-here point lands. Every other payload type is skipped by its
|
||||
//! declared size.
|
||||
//!
|
||||
//! Both codecs put the recovery point at payload type 6 with the same D.1 message
|
||||
//! framing, but the payload syntax differs: H.264 (D.1.8/D.2.8) counts recovery in
|
||||
//! `frame_num` increments (`recovery_frame_cnt`, ue(v)) and carries a slice-group bit
|
||||
//! pair; H.265 (D.2.8/D.3.8) counts in picture order (`recovery_poc_cnt`, se(v) — it
|
||||
//! can be negative) and has no slice-group field. Hence two parsers over one shared
|
||||
//! message walk.
|
||||
|
||||
/// Recovery point SEI (D.2.8).
|
||||
///
|
||||
/// `recovery_frame_cnt` counts in `frame_num` increments from the AU carrying the SEI to
|
||||
/// the picture at which output is exact (`exact_match`) or approximate. `broken_link` set
|
||||
/// means pictures before the recovery point may be visually broken and must not be shown.
|
||||
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
|
||||
pub struct RecoveryPoint {
|
||||
pub recovery_frame_cnt: u32,
|
||||
pub exact_match: bool,
|
||||
pub broken_link: bool,
|
||||
}
|
||||
|
||||
/// Recovery point SEI, H.265 flavour (D.3.8).
|
||||
///
|
||||
/// `recovery_poc_cnt` is the POC delta from the picture carrying the SEI to the
|
||||
/// recovery-point picture — se(v)-coded, so unlike H.264's `recovery_frame_cnt` it can
|
||||
/// be NEGATIVE (a recovery point among leading pictures). `exact_match`/`broken_link`
|
||||
/// keep their H.264 semantics.
|
||||
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
|
||||
pub struct RecoveryPointHevc {
|
||||
pub recovery_poc_cnt: i32,
|
||||
pub exact_match: bool,
|
||||
pub broken_link: bool,
|
||||
}
|
||||
|
||||
/// Parse the first recovery point SEI message out of an H.264 SEI NALU.
|
||||
///
|
||||
/// `sei_payload` are the bytes of the NALU after its one-byte NAL header, emulation
|
||||
/// prevention bytes still in place (they are removed here — 7.4.1 RBSP extraction).
|
||||
/// `Ok(None)` means the NALU parsed cleanly but carries no recovery point.
|
||||
pub fn parse_recovery_point(sei_payload: &[u8]) -> Result<Option<RecoveryPoint>, String> {
|
||||
let rbsp = strip_emulation_prevention(sei_payload);
|
||||
let Some(payload) = first_recovery_point_payload(&rbsp)? else {
|
||||
return Ok(None);
|
||||
};
|
||||
|
||||
let mut r = BitCursor::new(payload);
|
||||
let recovery_frame_cnt = r.read_ue()?;
|
||||
let exact_match = r.read_bit()? != 0;
|
||||
let broken_link = r.read_bit()? != 0;
|
||||
// changing_slice_group_idc u(2): parsed to keep the reader honest, unused —
|
||||
// slice groups are outside every profile punktfunk hosts emit.
|
||||
let _changing_slice_group_idc = r.read_bits(2)?;
|
||||
Ok(Some(RecoveryPoint {
|
||||
recovery_frame_cnt,
|
||||
exact_match,
|
||||
broken_link,
|
||||
}))
|
||||
}
|
||||
|
||||
/// Parse the first recovery point SEI message out of an H.265 prefix SEI NALU.
|
||||
///
|
||||
/// `sei_payload` are the bytes of the NALU after its TWO-byte NAL header (H.265 NALU
|
||||
/// headers are 16 bits), emulation prevention still in place. Only prefix SEI NALUs
|
||||
/// (type 39) can carry a recovery point — D.2.1 lists it as prefix-only, so suffix SEI
|
||||
/// NALUs (type 40) need never reach here.
|
||||
pub fn parse_recovery_point_hevc(sei_payload: &[u8]) -> Result<Option<RecoveryPointHevc>, String> {
|
||||
let rbsp = strip_emulation_prevention(sei_payload);
|
||||
let Some(payload) = first_recovery_point_payload(&rbsp)? else {
|
||||
return Ok(None);
|
||||
};
|
||||
|
||||
let mut r = BitCursor::new(payload);
|
||||
let recovery_poc_cnt = r.read_se()?;
|
||||
let exact_match = r.read_bit()? != 0;
|
||||
let broken_link = r.read_bit()? != 0;
|
||||
Ok(Some(RecoveryPointHevc {
|
||||
recovery_poc_cnt,
|
||||
exact_match,
|
||||
broken_link,
|
||||
}))
|
||||
}
|
||||
|
||||
/// Walk the D.1 SEI message framing (shared verbatim between H.264 and H.265) and
|
||||
/// return the payload bytes of the first recovery point message (payload type 6 in
|
||||
/// both codecs), if any. `rbsp` is already emulation-prevention-stripped.
|
||||
fn first_recovery_point_payload(rbsp: &[u8]) -> Result<Option<&[u8]>, String> {
|
||||
let mut i = 0usize;
|
||||
while i < rbsp.len() && !is_rbsp_trailing(rbsp, i) {
|
||||
// D.1: payload type and size are ff-coded — 0xFF bytes each add 255 until a
|
||||
// non-0xFF byte terminates the value. The run length is unbounded, so the type
|
||||
// accumulates saturating: an adversarial ~16M-byte 0xFF run must not overflow
|
||||
// (a saturated type simply never matches 6). The size accumulator is a usize
|
||||
// whose use is bounds-checked below.
|
||||
let mut payload_type = 0u32;
|
||||
while i < rbsp.len() && rbsp[i] == 0xFF {
|
||||
payload_type = payload_type.saturating_add(255);
|
||||
i += 1;
|
||||
}
|
||||
if i >= rbsp.len() {
|
||||
return Err("truncated SEI payload type".into());
|
||||
}
|
||||
payload_type = payload_type.saturating_add(u32::from(rbsp[i]));
|
||||
i += 1;
|
||||
|
||||
let mut payload_size = 0usize;
|
||||
while i < rbsp.len() && rbsp[i] == 0xFF {
|
||||
payload_size += 255;
|
||||
i += 1;
|
||||
}
|
||||
if i >= rbsp.len() {
|
||||
return Err("truncated SEI payload size".into());
|
||||
}
|
||||
payload_size += usize::from(rbsp[i]);
|
||||
i += 1;
|
||||
|
||||
let end = i
|
||||
.checked_add(payload_size)
|
||||
.filter(|&end| end <= rbsp.len())
|
||||
.ok_or_else(|| "SEI payload overruns the NALU".to_string())?;
|
||||
|
||||
if payload_type == 6 {
|
||||
return Ok(Some(&rbsp[i..end]));
|
||||
}
|
||||
|
||||
i = end;
|
||||
}
|
||||
|
||||
Ok(None)
|
||||
}
|
||||
|
||||
/// 7.4.1: within the RBSP, `00 00 03` encodes two zero bytes; the `03` is the emulation
|
||||
/// prevention byte and is dropped.
|
||||
fn strip_emulation_prevention(data: &[u8]) -> Vec<u8> {
|
||||
let mut out = Vec::with_capacity(data.len());
|
||||
let mut zeros = 0usize;
|
||||
for &byte in data {
|
||||
if zeros >= 2 && byte == 0x03 {
|
||||
zeros = 0;
|
||||
continue;
|
||||
}
|
||||
zeros = if byte == 0 { zeros + 1 } else { 0 };
|
||||
out.push(byte);
|
||||
}
|
||||
out
|
||||
}
|
||||
|
||||
/// `more_rbsp_data()` at a byte-aligned message boundary: the remainder is trailing bits
|
||||
/// iff it is the stop bit (0x80) followed by nothing but zero bytes.
|
||||
fn is_rbsp_trailing(rbsp: &[u8], i: usize) -> bool {
|
||||
rbsp[i] == 0x80 && rbsp[i + 1..].iter().all(|&b| b == 0)
|
||||
}
|
||||
|
||||
/// Minimal MSB-first bit reader over an already-unescaped RBSP slice. The vendored
|
||||
/// `BitReader` is `pub(crate)` to the vendored crate, so this crate carries its own.
|
||||
struct BitCursor<'a> {
|
||||
data: &'a [u8],
|
||||
/// Position in bits from the start of `data`.
|
||||
pos: usize,
|
||||
}
|
||||
|
||||
impl<'a> BitCursor<'a> {
|
||||
fn new(data: &'a [u8]) -> Self {
|
||||
Self { data, pos: 0 }
|
||||
}
|
||||
|
||||
fn read_bit(&mut self) -> Result<u32, String> {
|
||||
let byte = *self
|
||||
.data
|
||||
.get(self.pos / 8)
|
||||
.ok_or("SEI payload out of bits")?;
|
||||
let bit = (byte >> (7 - self.pos % 8)) & 1;
|
||||
self.pos += 1;
|
||||
Ok(u32::from(bit))
|
||||
}
|
||||
|
||||
fn read_bits(&mut self, count: usize) -> Result<u32, String> {
|
||||
debug_assert!(count <= 31);
|
||||
let mut out = 0u32;
|
||||
for _ in 0..count {
|
||||
out = (out << 1) | self.read_bit()?;
|
||||
}
|
||||
Ok(out)
|
||||
}
|
||||
|
||||
/// ue(v), spec 9.1.
|
||||
fn read_ue(&mut self) -> Result<u32, String> {
|
||||
let mut leading_zeros = 0usize;
|
||||
while self.read_bit()? == 0 {
|
||||
leading_zeros += 1;
|
||||
if leading_zeros > 31 {
|
||||
return Err("invalid exp-Golomb code in SEI payload".into());
|
||||
}
|
||||
}
|
||||
let suffix = self.read_bits(leading_zeros)?;
|
||||
((1u32 << leading_zeros) - 1)
|
||||
.checked_add(suffix)
|
||||
.ok_or_else(|| "exp-Golomb value overflows u32".to_string())
|
||||
}
|
||||
|
||||
/// se(v), spec 9.1.1: the ue(v) code point k maps to (−1)^(k+1) · ⌈k/2⌉.
|
||||
fn read_se(&mut self) -> Result<i32, String> {
|
||||
let k = self.read_ue()?;
|
||||
let magnitude = k.div_ceil(2);
|
||||
let magnitude =
|
||||
i32::try_from(magnitude).map_err(|_| "exp-Golomb value overflows i32".to_string())?;
|
||||
Ok(if k % 2 == 1 { magnitude } else { -magnitude })
|
||||
}
|
||||
}
|
||||
|
||||
#[cfg(test)]
|
||||
mod tests {
|
||||
use super::*;
|
||||
|
||||
#[test]
|
||||
fn a_minimal_recovery_point_message_parses_to_its_field_values() {
|
||||
// Message: type 6, size 1. Payload bits: ue(0)='1', exact=0, broken=0, csg=00,
|
||||
// then payload alignment '1' + zeros -> 0b1000_0100. NALU trailing 0x80.
|
||||
let sei = [0x06, 0x01, 0x84, 0x80];
|
||||
assert_eq!(
|
||||
parse_recovery_point(&sei).unwrap(),
|
||||
Some(RecoveryPoint {
|
||||
recovery_frame_cnt: 0,
|
||||
exact_match: false,
|
||||
broken_link: false
|
||||
})
|
||||
);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn recovery_frame_cnt_and_both_flags_round_trip_through_the_bit_reader() {
|
||||
// ue(5)='00110', exact=1, broken=1, csg=00, alignment -> 0b0011_0110 0b0100_0000.
|
||||
let sei = [0x06, 0x02, 0x36, 0x40, 0x80];
|
||||
assert_eq!(
|
||||
parse_recovery_point(&sei).unwrap(),
|
||||
Some(RecoveryPoint {
|
||||
recovery_frame_cnt: 5,
|
||||
exact_match: true,
|
||||
broken_link: true
|
||||
})
|
||||
);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn earlier_messages_and_ff_coded_types_are_skipped_to_reach_the_recovery_point() {
|
||||
// First message: ff-coded payload type 255 (0xFF 0x00), size 1, payload 0x55.
|
||||
// Second message: type 5 (user data), size 3. Third: the recovery point.
|
||||
let sei = [
|
||||
0xFF, 0x00, 0x01, 0x55, // type 255
|
||||
0x05, 0x03, 0xAA, 0xBB, 0xCC, // type 5
|
||||
0x06, 0x01, 0x84, // recovery point
|
||||
0x80,
|
||||
];
|
||||
assert_eq!(
|
||||
parse_recovery_point(&sei).unwrap(),
|
||||
Some(RecoveryPoint {
|
||||
recovery_frame_cnt: 0,
|
||||
exact_match: false,
|
||||
broken_link: false
|
||||
})
|
||||
);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn emulation_prevention_bytes_inside_the_payload_are_removed_before_reading() {
|
||||
// Unescaped payload (7 bytes): ue with a 22-zero prefix => recovery_frame_cnt
|
||||
// 2^22-1 = 4194303, exact=1, broken=0, csg=00, alignment. Its first bytes are
|
||||
// 00 00 02, which the escaper must have written as 00 00 03 02 on the wire.
|
||||
let sei = [
|
||||
0x06, 0x07, 0x00, 0x00, 0x03, 0x02, 0x00, 0x00, 0x04, 0x40, 0x80,
|
||||
];
|
||||
assert!(sei.windows(3).any(|w| w == [0x00, 0x00, 0x03]));
|
||||
assert_eq!(
|
||||
parse_recovery_point(&sei).unwrap(),
|
||||
Some(RecoveryPoint {
|
||||
recovery_frame_cnt: 4194303,
|
||||
exact_match: true,
|
||||
broken_link: false
|
||||
})
|
||||
);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn a_sei_nalu_without_a_recovery_point_yields_none_not_an_error() {
|
||||
let sei = [0x05, 0x01, 0x00, 0x80];
|
||||
assert_eq!(parse_recovery_point(&sei).unwrap(), None);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn a_payload_size_overrunning_the_nalu_is_a_parse_error() {
|
||||
let sei = [0x06, 0x0A, 0x00];
|
||||
assert!(parse_recovery_point(&sei).is_err());
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn the_hevc_recovery_point_parses_its_se_coded_poc_count() {
|
||||
// recovery_poc_cnt se(0) = '1', exact = 0, broken = 0, payload alignment:
|
||||
// 0b1001_0000.
|
||||
let sei = [0x06, 0x01, 0x90, 0x80];
|
||||
assert_eq!(
|
||||
parse_recovery_point_hevc(&sei).unwrap(),
|
||||
Some(RecoveryPointHevc {
|
||||
recovery_poc_cnt: 0,
|
||||
exact_match: false,
|
||||
broken_link: false
|
||||
})
|
||||
);
|
||||
|
||||
// se(-1) = '011' (ue code point 2), exact = 1, broken = 0, alignment:
|
||||
// 0b0111_0100 — the negative range H.264's ue(v) syntax cannot express.
|
||||
let sei = [0x06, 0x01, 0x74, 0x80];
|
||||
assert_eq!(
|
||||
parse_recovery_point_hevc(&sei).unwrap(),
|
||||
Some(RecoveryPointHevc {
|
||||
recovery_poc_cnt: -1,
|
||||
exact_match: true,
|
||||
broken_link: false
|
||||
})
|
||||
);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn the_hevc_parser_skips_earlier_messages_and_reports_absence_as_none() {
|
||||
// User-data message first, then the recovery point (poc_cnt se(3): ue code
|
||||
// point 5 = '00110', exact = 1, broken = 1, alignment: 0b0011_0111).
|
||||
let sei = [
|
||||
0x05, 0x02, 0xAA, 0xBB, // type 5
|
||||
0x06, 0x01, 0x37, // recovery point
|
||||
0x80,
|
||||
];
|
||||
assert_eq!(
|
||||
parse_recovery_point_hevc(&sei).unwrap(),
|
||||
Some(RecoveryPointHevc {
|
||||
recovery_poc_cnt: 3,
|
||||
exact_match: true,
|
||||
broken_link: true
|
||||
})
|
||||
);
|
||||
|
||||
let sei = [0x05, 0x01, 0x00, 0x80];
|
||||
assert_eq!(parse_recovery_point_hevc(&sei).unwrap(), None);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,232 @@
|
||||
//! Corpus replay: walk a captured real-host stream through the planners.
|
||||
//!
|
||||
//! The M0 capture hook (`PUNKTFUNK_DUMP_VIDEO=<dir>` on any desktop client) writes
|
||||
//! the exact decoder input of a live session — `au-<stamp>.<codec>` plus an `.idx`
|
||||
//! sidecar carrying `offset len flags complete` per AU. This harness feeds those AUs
|
||||
//! back through [`pf_bitstream::h264::H264Planner`] / [`pf_bitstream::h265::H265Planner`]
|
||||
//! and asserts the planner survives a REAL host stream: every AU plans (bar the
|
||||
//! deliberate skips), no panic, and the warnings are only the ones a clean capture may
|
||||
//! legitimately produce.
|
||||
//!
|
||||
//! Why this exists separately from the vendored conformance vectors: those prove we
|
||||
//! match the spec's own test streams, and the on-glass sessions prove the whole pipe —
|
||||
//! but between the two sits "does the planner handle what OUR five host encoder
|
||||
//! families actually emit", which is the question the corpus was captured to answer.
|
||||
//! For HEVC this is the ONLY pre-wiring validation against real host output (the
|
||||
//! client's HEVC rung is still being built), so it runs long before M3 finishes.
|
||||
//!
|
||||
//! Ignored by default: captures are hundreds of megabytes and live outside the repo.
|
||||
//! Run one explicitly —
|
||||
//!
|
||||
//! ```text
|
||||
//! PF_CORPUS=/path/to/au-1785970273.h265 \
|
||||
//! cargo test -p pf-bitstream --test corpus_replay -- --ignored --nocapture
|
||||
//! ```
|
||||
//!
|
||||
//! The `.idx` sidecar is found next to the data file (`<data>.idx`); the codec comes
|
||||
//! from the extension, matching the capture hook's own naming convention.
|
||||
|
||||
use std::path::Path;
|
||||
use std::path::PathBuf;
|
||||
|
||||
/// One captured access unit: its byte range in the data file, plus the wire bits the
|
||||
/// byte stream itself cannot carry.
|
||||
struct CapturedAu {
|
||||
offset: usize,
|
||||
len: usize,
|
||||
/// The wire `flags` byte (`USER_FLAG_*`) — kept for the RFI/intra-refresh legs,
|
||||
/// which discriminate on it.
|
||||
_flags: u32,
|
||||
complete: bool,
|
||||
}
|
||||
|
||||
/// Parse the `.idx` sidecar: one `offset len flags complete` line per AU, `#` comments
|
||||
/// and blank lines skipped (the hook writes none today, but a hand-trimmed corpus file
|
||||
/// is a thing a human will produce).
|
||||
///
|
||||
/// A malformed FINAL line is dropped with a note instead of failing: ending a capture
|
||||
/// means killing the client, so the last buffered line is routinely half-written (the
|
||||
/// hook's own docs call a truncated last AU acceptable). Anywhere else a malformed line
|
||||
/// means the sidecar is corrupt and the run must not quietly replay a subset.
|
||||
fn read_index(path: &Path) -> Vec<CapturedAu> {
|
||||
let text = std::fs::read_to_string(path)
|
||||
.unwrap_or_else(|e| panic!("cannot read the index sidecar {}: {e}", path.display()));
|
||||
let lines: Vec<&str> = text
|
||||
.lines()
|
||||
.filter(|line| !line.trim().is_empty() && !line.trim_start().starts_with('#'))
|
||||
.collect();
|
||||
let last = lines.len().saturating_sub(1);
|
||||
let mut out = Vec::with_capacity(lines.len());
|
||||
for (n, line) in lines.iter().enumerate() {
|
||||
match parse_index_line(line) {
|
||||
Some(au) => out.push(au),
|
||||
None if n == last => {
|
||||
println!("note: dropping a truncated final index line ({line:?})");
|
||||
}
|
||||
None => panic!("index line {n} is malformed: {line:?}"),
|
||||
}
|
||||
}
|
||||
out
|
||||
}
|
||||
|
||||
/// One `offset len flags complete` line, or `None` when it is not four parsable fields.
|
||||
fn parse_index_line(line: &str) -> Option<CapturedAu> {
|
||||
let mut it = line.split_whitespace();
|
||||
let num = |raw: &str| -> Option<u64> {
|
||||
match raw.strip_prefix("0x") {
|
||||
Some(hex) => u64::from_str_radix(hex, 16).ok(),
|
||||
None => raw.parse().ok(),
|
||||
}
|
||||
};
|
||||
let offset = num(it.next()?)?;
|
||||
let len = num(it.next()?)?;
|
||||
let flags = num(it.next()?)?;
|
||||
let complete = num(it.next()?)?;
|
||||
Some(CapturedAu {
|
||||
offset: offset as usize,
|
||||
len: len as usize,
|
||||
_flags: flags as u32,
|
||||
complete: complete != 0,
|
||||
})
|
||||
}
|
||||
|
||||
/// The capture named by `PF_CORPUS`, or `None` when the variable is unset.
|
||||
fn corpus_from_env() -> Option<(PathBuf, Vec<u8>, Vec<CapturedAu>)> {
|
||||
let path = PathBuf::from(std::env::var_os("PF_CORPUS")?);
|
||||
let data = std::fs::read(&path)
|
||||
.unwrap_or_else(|e| panic!("cannot read the capture {}: {e}", path.display()));
|
||||
let mut idx = path.clone().into_os_string();
|
||||
idx.push(".idx");
|
||||
let mut index = read_index(Path::new(&idx));
|
||||
// Same truncation story on the data side: the final AU's bytes may not all have
|
||||
// reached the file before the client died. Drop AUs the data cannot cover — but
|
||||
// only from the tail, so a short file can never silently hide a middle gap.
|
||||
let covered = index
|
||||
.iter()
|
||||
.take_while(|au| au.offset.saturating_add(au.len) <= data.len())
|
||||
.count();
|
||||
if covered < index.len() {
|
||||
println!(
|
||||
"note: dropping {} index entr{} past the end of the data file (truncated capture)",
|
||||
index.len() - covered,
|
||||
if index.len() - covered == 1 {
|
||||
"y"
|
||||
} else {
|
||||
"ies"
|
||||
},
|
||||
);
|
||||
index.truncate(covered);
|
||||
}
|
||||
assert!(!index.is_empty(), "the capture's index is empty");
|
||||
Some((path, data, index))
|
||||
}
|
||||
|
||||
/// Per-AU outcome tally — what the run reports and asserts on.
|
||||
#[derive(Default)]
|
||||
struct Tally {
|
||||
planned: usize,
|
||||
skipped: usize,
|
||||
errors: Vec<String>,
|
||||
warnings: Vec<String>,
|
||||
partial: usize,
|
||||
}
|
||||
|
||||
impl Tally {
|
||||
/// A clean capture of a healthy session must plan every complete AU. Errors are
|
||||
/// hard failures; warnings are printed and capped — `MissingReference` on a stream
|
||||
/// that never lost a packet would mean the planner invented a gap.
|
||||
fn assert_clean(&self, total: usize) {
|
||||
println!(
|
||||
"planned {} / skipped {} / partial-AUs-ignored {} / errors {} / warnings {} \
|
||||
(of {total} captured AUs)",
|
||||
self.planned,
|
||||
self.skipped,
|
||||
self.partial,
|
||||
self.errors.len(),
|
||||
self.warnings.len(),
|
||||
);
|
||||
for w in self.warnings.iter().take(20) {
|
||||
println!(" warning: {w}");
|
||||
}
|
||||
for e in self.errors.iter().take(20) {
|
||||
println!(" ERROR: {e}");
|
||||
}
|
||||
assert!(
|
||||
self.errors.is_empty(),
|
||||
"{} AUs failed to plan — first: {}",
|
||||
self.errors.len(),
|
||||
self.errors[0],
|
||||
);
|
||||
assert!(
|
||||
self.warnings.is_empty(),
|
||||
"{} planner warnings on a clean capture — first: {}",
|
||||
self.warnings.len(),
|
||||
self.warnings[0],
|
||||
);
|
||||
assert!(self.planned > 0, "no AU planned at all");
|
||||
}
|
||||
}
|
||||
|
||||
#[test]
|
||||
#[ignore = "needs a capture: PF_CORPUS=<au-file> (see the module docs)"]
|
||||
fn a_captured_host_stream_replays_through_the_planner() {
|
||||
let Some((path, data, index)) = corpus_from_env() else {
|
||||
panic!("PF_CORPUS is unset — see the module docs for the invocation");
|
||||
};
|
||||
let ext = path
|
||||
.extension()
|
||||
.and_then(|e| e.to_str())
|
||||
.unwrap_or_default()
|
||||
.to_owned();
|
||||
println!(
|
||||
"replaying {} ({} bytes, {} AUs, codec {ext})",
|
||||
path.display(),
|
||||
data.len(),
|
||||
index.len(),
|
||||
);
|
||||
|
||||
let mut tally = Tally::default();
|
||||
// The planners take one COMPLETE AU. A partial AU (the wire's shard split) is the
|
||||
// pump's business, not the planner's — count and skip rather than feed a fragment.
|
||||
let complete: Vec<&CapturedAu> = index.iter().filter(|au| au.complete).collect();
|
||||
tally.partial = index.len() - complete.len();
|
||||
|
||||
match ext.as_str() {
|
||||
"h265" => {
|
||||
let mut planner = pf_bitstream::h265::H265Planner::new();
|
||||
for (i, au) in complete.iter().enumerate() {
|
||||
let bytes = &data[au.offset..au.offset + au.len];
|
||||
match planner.plan_au(bytes) {
|
||||
Ok(plan) => {
|
||||
tally.planned += 1;
|
||||
for w in &plan.warnings {
|
||||
tally.warnings.push(format!("AU {i}: {w:?}"));
|
||||
}
|
||||
}
|
||||
// The spec's own skip (8.1.3): decode nothing, show nothing, the
|
||||
// stream is healthy — never an error (the WP-2 contract note).
|
||||
Err(pf_bitstream::h265::PlanError::RaslSkipped { .. }) => tally.skipped += 1,
|
||||
Err(e) => tally.errors.push(format!("AU {i}: {e}")),
|
||||
}
|
||||
}
|
||||
}
|
||||
"h264" => {
|
||||
let mut planner = pf_bitstream::h264::H264Planner::new();
|
||||
for (i, au) in complete.iter().enumerate() {
|
||||
let bytes = &data[au.offset..au.offset + au.len];
|
||||
match planner.plan_au(bytes) {
|
||||
Ok(plan) => {
|
||||
tally.planned += 1;
|
||||
for w in &plan.warnings {
|
||||
tally.warnings.push(format!("AU {i}: {w:?}"));
|
||||
}
|
||||
}
|
||||
Err(e) => tally.errors.push(format!("AU {i}: {e}")),
|
||||
}
|
||||
}
|
||||
}
|
||||
other => panic!("no planner for a .{other} capture (h264/h265 only today)"),
|
||||
}
|
||||
|
||||
tally.assert_clean(index.len());
|
||||
}
|
||||
@@ -0,0 +1,17 @@
|
||||
# Vendored snapshot — see PROVENANCE.md. Deliberately NOT opted into workspace lints
|
||||
# or workspace package inheritance: upstream code stays as close to pristine as the
|
||||
# trim allows, so re-syncing against the AOSP tree stays a diff, not an archaeology dig.
|
||||
[package]
|
||||
name = "cros-codecs"
|
||||
version = "0.0.5"
|
||||
license = "BSD-3-Clause"
|
||||
description = "Vendored cros-codecs parser layer (codec module only) for pf-bitstream"
|
||||
edition = "2021"
|
||||
|
||||
[dependencies]
|
||||
log = "0.4"
|
||||
|
||||
# Upstream's in-tree unit tests (kept — they are the conformance goldens) want these.
|
||||
[dev-dependencies]
|
||||
env_logger = "0.11"
|
||||
serde_json = "1"
|
||||
+26
@@ -0,0 +1,26 @@
|
||||
Copyright 2022 The ChromiumOS Authors
|
||||
|
||||
Redistribution and use in source and binary forms, with or without modification,
|
||||
are permitted provided that the following conditions are met:
|
||||
|
||||
1. Redistributions of source code must retain the above copyright notice, this
|
||||
list of conditions and the following disclaimer.
|
||||
|
||||
2. Redistributions in binary form must reproduce the above copyright notice,
|
||||
this list of conditions and the following disclaimer in the documentation
|
||||
and/or other materials provided with the distribution.
|
||||
|
||||
3. Neither the name of the copyright holder nor the names of its contributors
|
||||
may be used to endorse or promote products derived from this software without
|
||||
specific prior written permission.
|
||||
|
||||
THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND
|
||||
ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED
|
||||
WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
|
||||
DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE FOR
|
||||
ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES
|
||||
(INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES;
|
||||
LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON
|
||||
ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
|
||||
(INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS
|
||||
SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
|
||||
@@ -0,0 +1,72 @@
|
||||
# Vendored: cros-codecs (parser layer only)
|
||||
|
||||
- **Upstream:** <https://android.googlesource.com/platform/system/cros-codecs/> (the
|
||||
authoritative AOSP tree). Snapshot taken from the read-only GitHub mirror
|
||||
<https://github.com/chromeos/cros-codecs>, branch `main`,
|
||||
commit **`5ff6d693ffae0b36935b8fc13092c733b4c2646f`**, fetched 2026-08-05.
|
||||
- **License:** BSD-3-Clause (`LICENSE`, copied verbatim). Attribution headers retained
|
||||
in every source file.
|
||||
- **Why vendored, not a crates.io dependency:** the GitHub repo is a read-only mirror
|
||||
and the crates.io release lags it; a pinned, reviewed snapshot is the supply-chain
|
||||
posture punktfunk already uses elsewhere (`clients/android/native/vendor/ndk`,
|
||||
`punktfunk-host/vendor/usbip-sim`). Decision of record:
|
||||
punktfunk-planning `design/client-native-decode.md` §8.1.
|
||||
|
||||
## What was taken
|
||||
|
||||
`src/codec/{h264,h265,av1,vp9}` (parsers, DPBs, picture types, NALU/OBU machinery,
|
||||
their `test_data` vectors — they double as punktfunk's conformance corpus),
|
||||
`src/bitstream_utils.rs`, `LICENSE`. Upstream designed the `codec` module for exactly
|
||||
this extraction — its module doc: "There shall be no dependencies from other modules of
|
||||
this crate to this module, so that it can be turned into a crate of its own if needed
|
||||
in the future."
|
||||
|
||||
## What was left behind
|
||||
|
||||
- `decoder/`, `encoder/`, `backend/`, `c2_wrapper/`, `video_frame`, `image_processing`,
|
||||
`utils` — the Linux-only halves (libva/v4l2/gbm/nix). punktfunk's `pf-bitstream` +
|
||||
`pf-vkdecode` occupy that layer.
|
||||
- `codec/vp8` — VP9 has no dependency on it (verified) and no punktfunk host will ever
|
||||
emit VP8.
|
||||
|
||||
## Deviations from pristine upstream
|
||||
|
||||
1. `src/lib.rs` — rewritten: keeps only the module decls and `Resolution` /
|
||||
`ResolutionRoundMode` (the sole root items `codec` references), both copied verbatim;
|
||||
adds crate-level `#![allow(clippy::all, mismatched_lifetime_syntaxes)]` — vendored
|
||||
code is not held to the workspace lint bar (CI's `-D warnings` legs would fail on
|
||||
upstream style otherwise).
|
||||
2. `src/codec.rs` — one line removed (`pub mod vp8;`).
|
||||
3. `Cargo.toml` — rewritten: `log` is the only dependency the vendored subset needs,
|
||||
plus `env_logger`/`serde_json` dev-dependencies for upstream's in-tree tests.
|
||||
4. `cargo fmt` normalization under the workspace's rustfmt config (mechanical only).
|
||||
5. **Zero-unsafe, enforced**: `#![forbid(unsafe_code)]` added to lib.rs. Upstream's codec
|
||||
module had exactly one production `unsafe` (h264/dpb.rs `build_ref_pic_lists`: ref→index
|
||||
via pointer `offset_from`) — replaced with a safe `position(ptr::eq)` over the ≤16-entry
|
||||
DPB — and three test-only `mem::zeroed()` asserts, replaced with `Default::default()`
|
||||
(`PredWeightTable` derives `Default`; all-integer struct, identical value). The layer
|
||||
facing untrusted bytes is now compiler-verified free of unsafe — the property that
|
||||
motivates replacing libavcodec's C parsers in the first place.
|
||||
6. `src/codec/h264/picture.rs` — `PictureData::new_from_slice`: `display_resolution`
|
||||
computed as `visible_rect.max` instead of `max - min`. `Sps::visible_rectangle()`
|
||||
returns the crop offset in `min` and the visible *size* in `max` (see its
|
||||
definition: `max.x = width - crop_left - crop_right`); upstream's subtraction
|
||||
double-counts the left/top crop and, worse, panics on u32 underflow for a
|
||||
large-but-parser-valid `frame_crop_left_offset` (e.g. 100 crop units on a 320-wide
|
||||
SPS). Found by pf-bitstream's conformance-window tests; upstream never hits it
|
||||
because real encoders crop right/bottom only. **Reported upstream 2026-08-06:
|
||||
<https://github.com/chromeos/cros-codecs/issues/99>.**
|
||||
|
||||
7. `src/codec/h265/parser.rs` — `parse_slice_header`: reject
|
||||
`num_long_term_sps + num_long_term_pics > 16` before the long-term RPS loop.
|
||||
Upstream bounds the pair only by `MAX_LONG_TERM_REF_PIC_SETS` (32) combined, while
|
||||
every long-term array in `SliceHeader` (`poc_lsb_lt`, `used_by_curr_pic_lt`,
|
||||
`delta_poc_msb_present_flag`, `delta_poc_msb_cycle_lt`, `lt_idx_sps`) is `[_; 16]`
|
||||
— a hostile slice header with 17+ entries panics the parser with an
|
||||
index-out-of-bounds (bounds checks stay on in release). Found by pf-bitstream's
|
||||
H.265 planner review; regression-tested there
|
||||
(`a_hostile_long_term_count_is_a_parse_error_not_a_panic`). **Reported upstream
|
||||
2026-08-06: <https://github.com/chromeos/cros-codecs/issues/100>.**
|
||||
|
||||
Re-sync procedure: fetch the AOSP tree, re-apply this trim, diff `codec/` +
|
||||
`bitstream_utils.rs` (expect near-zero conflicts), update the commit pin above.
|
||||
@@ -0,0 +1,788 @@
|
||||
// Copyright 2024 The ChromiumOS Authors
|
||||
// Use of this source code is governed by a BSD-style license that can be
|
||||
// found in the LICENSE file.
|
||||
|
||||
use std::borrow::Cow;
|
||||
use std::fmt;
|
||||
use std::io::Cursor;
|
||||
use std::io::Read;
|
||||
use std::io::Seek;
|
||||
use std::io::SeekFrom;
|
||||
use std::io::Write;
|
||||
use std::marker::PhantomData;
|
||||
|
||||
use crate::codec::h264::parser::Nalu as H264Nalu;
|
||||
use crate::codec::h265::parser::Nalu as H265Nalu;
|
||||
|
||||
/// A bit reader for codec bitstreams. It properly handles emulation-prevention
|
||||
/// bytes and stop bits for H264.
|
||||
#[derive(Clone)]
|
||||
pub(crate) struct BitReader<'a> {
|
||||
/// A reference into the next unread byte in the stream.
|
||||
data: Cursor<&'a [u8]>,
|
||||
/// Contents of the current byte. First unread bit starting at position 8 -
|
||||
/// num_remaining_bits_in_curr_bytes.
|
||||
curr_byte: u8,
|
||||
/// Number of bits remaining in `curr_byte`
|
||||
num_remaining_bits_in_curr_byte: usize,
|
||||
/// Used in emulation prevention byte detection.
|
||||
prev_two_bytes: u16,
|
||||
/// Number of emulation prevention bytes (i.e. 0x000003) we found.
|
||||
num_epb: usize,
|
||||
/// Whether or not we need emulation prevention logic.
|
||||
needs_epb: bool,
|
||||
/// How many bits have been read so far.
|
||||
position: u64,
|
||||
}
|
||||
|
||||
#[derive(Debug)]
|
||||
pub(crate) enum GetByteError {
|
||||
OutOfBits,
|
||||
}
|
||||
|
||||
impl fmt::Display for GetByteError {
|
||||
fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
|
||||
write!(f, "reader ran out of bits")
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Debug)]
|
||||
pub(crate) enum ReadBitsError {
|
||||
TooManyBitsRequested(usize),
|
||||
GetByte(GetByteError),
|
||||
ConversionFailed,
|
||||
}
|
||||
|
||||
impl fmt::Display for ReadBitsError {
|
||||
fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
|
||||
match self {
|
||||
ReadBitsError::TooManyBitsRequested(bits) => {
|
||||
write!(f, "more than 31 ({}) bits were requested", bits)
|
||||
}
|
||||
ReadBitsError::GetByte(_) => write!(f, "failed to advance the current byte"),
|
||||
ReadBitsError::ConversionFailed => {
|
||||
write!(f, "failed to convert read input to target type")
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl From<GetByteError> for ReadBitsError {
|
||||
fn from(err: GetByteError) -> Self {
|
||||
ReadBitsError::GetByte(err)
|
||||
}
|
||||
}
|
||||
|
||||
impl<'a> BitReader<'a> {
|
||||
pub fn new(data: &'a [u8], needs_epb: bool) -> Self {
|
||||
Self {
|
||||
data: Cursor::new(data),
|
||||
curr_byte: Default::default(),
|
||||
num_remaining_bits_in_curr_byte: Default::default(),
|
||||
prev_two_bytes: 0xffff,
|
||||
num_epb: Default::default(),
|
||||
needs_epb: needs_epb,
|
||||
position: 0,
|
||||
}
|
||||
}
|
||||
|
||||
/// Read a single bit from the stream.
|
||||
pub fn read_bit(&mut self) -> Result<bool, String> {
|
||||
let bit = self.read_bits::<u32>(1)?;
|
||||
match bit {
|
||||
1 => Ok(true),
|
||||
0 => Ok(false),
|
||||
_ => panic!("Unexpected value {}", bit),
|
||||
}
|
||||
}
|
||||
|
||||
/// Read up to 31 bits from the stream. Note that we don't want to read 32
|
||||
/// bits even though we're returning a u32 because that would break the
|
||||
/// read_bits_signed() function. 31 bits should be overkill for compressed
|
||||
/// header parsing anyway.
|
||||
pub fn read_bits<U: TryFrom<u32>>(&mut self, num_bits: usize) -> Result<U, String> {
|
||||
if num_bits > 31 {
|
||||
return Err(ReadBitsError::TooManyBitsRequested(num_bits).to_string());
|
||||
}
|
||||
|
||||
let mut bits_left = num_bits;
|
||||
let mut out = 0u32;
|
||||
|
||||
while self.num_remaining_bits_in_curr_byte < bits_left {
|
||||
out |= (self.curr_byte as u32) << (bits_left - self.num_remaining_bits_in_curr_byte);
|
||||
bits_left -= self.num_remaining_bits_in_curr_byte;
|
||||
self.move_to_next_byte().map_err(|err| err.to_string())?;
|
||||
}
|
||||
|
||||
out |= (self.curr_byte >> (self.num_remaining_bits_in_curr_byte - bits_left)) as u32;
|
||||
out &= (1 << num_bits) - 1;
|
||||
self.num_remaining_bits_in_curr_byte -= bits_left;
|
||||
self.position += num_bits as u64;
|
||||
|
||||
U::try_from(out).map_err(|_| ReadBitsError::ConversionFailed.to_string())
|
||||
}
|
||||
|
||||
/// Reads a two's complement signed integer of length |num_bits|.
|
||||
pub fn read_bits_signed<U: TryFrom<i32>>(&mut self, num_bits: usize) -> Result<U, String> {
|
||||
let mut out: i32 = self
|
||||
.read_bits::<u32>(num_bits)?
|
||||
.try_into()
|
||||
.map_err(|_| ReadBitsError::ConversionFailed.to_string())?;
|
||||
if out >> (num_bits - 1) != 0 {
|
||||
out |= -1i32 ^ ((1 << num_bits) - 1);
|
||||
}
|
||||
|
||||
U::try_from(out).map_err(|_| ReadBitsError::ConversionFailed.to_string())
|
||||
}
|
||||
|
||||
/// Reads an unsigned integer from the stream and checks if the stream is byte aligned.
|
||||
pub fn read_bits_aligned<U: TryFrom<u32>>(&mut self, num_bits: usize) -> Result<U, String> {
|
||||
if self.num_remaining_bits_in_curr_byte % 8 != 0 {
|
||||
return Err("Attempted unaligned read_le()".into());
|
||||
}
|
||||
|
||||
Ok(self.read_bits(num_bits).map_err(|err| err.to_string())?)
|
||||
}
|
||||
|
||||
/// Skip `num_bits` bits from the stream.
|
||||
pub fn skip_bits(&mut self, mut num_bits: usize) -> Result<(), String> {
|
||||
while num_bits > 0 {
|
||||
let n = std::cmp::min(num_bits, 31);
|
||||
self.read_bits::<u32>(n)?;
|
||||
num_bits -= n;
|
||||
}
|
||||
|
||||
Ok(())
|
||||
}
|
||||
|
||||
/// Returns the amount of bits left in the stream
|
||||
pub fn num_bits_left(&mut self) -> usize {
|
||||
let cur_pos = self.data.position();
|
||||
// This should always be safe to unwrap.
|
||||
let end_pos = self.data.seek(SeekFrom::End(0)).unwrap();
|
||||
let _ = self.data.seek(SeekFrom::Start(cur_pos));
|
||||
((end_pos - cur_pos) as usize) * 8 + self.num_remaining_bits_in_curr_byte
|
||||
}
|
||||
|
||||
/// Returns the number of emulation-prevention bytes read so far.
|
||||
pub fn num_epb(&self) -> usize {
|
||||
self.num_epb
|
||||
}
|
||||
|
||||
/// Whether the stream still has RBSP data. Implements more_rbsp_data(). See
|
||||
/// the spec for more details.
|
||||
pub fn has_more_rsbp_data(&mut self) -> bool {
|
||||
if self.num_remaining_bits_in_curr_byte == 0 && self.move_to_next_byte().is_err() {
|
||||
// no more data at all in the rbsp
|
||||
return false;
|
||||
}
|
||||
|
||||
// If the next bit is the stop bit, then we should only see unset bits
|
||||
// until the end of the data.
|
||||
if (self.curr_byte & ((1 << (self.num_remaining_bits_in_curr_byte - 1)) - 1)) != 0 {
|
||||
return true;
|
||||
}
|
||||
|
||||
let mut buf = [0u8; 1];
|
||||
let orig_pos = self.data.position();
|
||||
while let Ok(_) = self.data.read_exact(&mut buf) {
|
||||
if buf[0] != 0 {
|
||||
self.data.set_position(orig_pos);
|
||||
return true;
|
||||
}
|
||||
}
|
||||
false
|
||||
}
|
||||
|
||||
/// Reads an Unsigned Exponential golomb coding number from the next bytes in the
|
||||
/// bitstream. This may advance the state of position within the bitstream even if the
|
||||
/// read operation is unsuccessful. See H264 Annex B specification 9.1 for details.
|
||||
pub fn read_ue<U: TryFrom<u32>>(&mut self) -> Result<U, String> {
|
||||
let mut num_bits = 0;
|
||||
|
||||
while self.read_bits::<u32>(1)? == 0 {
|
||||
num_bits += 1;
|
||||
if num_bits > 31 {
|
||||
return Err("invalid stream".into());
|
||||
}
|
||||
}
|
||||
|
||||
let value = ((1u32 << num_bits) - 1)
|
||||
.checked_add(self.read_bits::<u32>(num_bits)?)
|
||||
.ok_or::<String>("read number cannot fit in 32 bits".into())?;
|
||||
|
||||
U::try_from(value).map_err(|_| "conversion error".into())
|
||||
}
|
||||
|
||||
pub fn read_ue_bounded<U: TryFrom<u32>>(&mut self, min: u32, max: u32) -> Result<U, String> {
|
||||
let ue = self.read_ue()?;
|
||||
if ue > max || ue < min {
|
||||
Err(format!(
|
||||
"Value out of bounds: expected {} - {}, got {}",
|
||||
min, max, ue
|
||||
))
|
||||
} else {
|
||||
Ok(U::try_from(ue).map_err(|_| String::from("Conversion error"))?)
|
||||
}
|
||||
}
|
||||
|
||||
pub fn read_ue_max<U: TryFrom<u32>>(&mut self, max: u32) -> Result<U, String> {
|
||||
self.read_ue_bounded(0, max)
|
||||
}
|
||||
|
||||
/// Reads a signed exponential golomb coding number. Instead of using two's
|
||||
/// complement, this scheme maps even integers to positive numbers and odd
|
||||
/// integers to negative numbers. The least significant bit indicates the
|
||||
/// sign. See H264 Annex B specification 9.1.1 for details.
|
||||
pub fn read_se<U: TryFrom<i32>>(&mut self) -> Result<U, String> {
|
||||
let ue = self.read_ue::<u32>()? as i32;
|
||||
|
||||
if ue % 2 == 0 {
|
||||
Ok(U::try_from(-(ue / 2)).map_err(|_| String::from("Conversion error"))?)
|
||||
} else {
|
||||
Ok(U::try_from(ue / 2 + 1).map_err(|_| String::from("Conversion error"))?)
|
||||
}
|
||||
}
|
||||
|
||||
pub fn read_se_bounded<U: TryFrom<i32>>(&mut self, min: i32, max: i32) -> Result<U, String> {
|
||||
let se = self.read_se()?;
|
||||
if se < min || se > max {
|
||||
Err(format!(
|
||||
"Value out of bounds, expected between {}-{}, got {}",
|
||||
min, max, se
|
||||
))
|
||||
} else {
|
||||
Ok(U::try_from(se).map_err(|_| String::from("Conversion error"))?)
|
||||
}
|
||||
}
|
||||
|
||||
/// Read little endian multi-byte integer.
|
||||
pub fn read_le<U: TryFrom<u32>>(&mut self, num_bits: u8) -> Result<U, String> {
|
||||
let mut t = 0;
|
||||
|
||||
for i in 0..num_bits {
|
||||
let byte = self.read_bits_aligned::<u32>(8)?;
|
||||
t += byte << (i * 8)
|
||||
}
|
||||
|
||||
Ok(U::try_from(t).map_err(|_| String::from("Conversion error"))?)
|
||||
}
|
||||
|
||||
/// Return the position of this bitstream in bits.
|
||||
pub fn position(&self) -> u64 {
|
||||
self.position
|
||||
}
|
||||
|
||||
fn get_byte(&mut self) -> Result<u8, GetByteError> {
|
||||
let mut buf = [0u8; 1];
|
||||
self.data
|
||||
.read_exact(&mut buf)
|
||||
.map_err(|_| GetByteError::OutOfBits)?;
|
||||
Ok(buf[0])
|
||||
}
|
||||
|
||||
fn move_to_next_byte(&mut self) -> Result<(), GetByteError> {
|
||||
let mut byte = self.get_byte()?;
|
||||
|
||||
if self.needs_epb {
|
||||
if self.prev_two_bytes == 0 && byte == 0x03 {
|
||||
// We found an epb
|
||||
self.num_epb += 1;
|
||||
// Read another byte
|
||||
byte = self.get_byte()?;
|
||||
// We need another 3 bytes before another epb can happen.
|
||||
self.prev_two_bytes = 0xffff;
|
||||
}
|
||||
self.prev_two_bytes = (self.prev_two_bytes << 8) | u16::from(byte);
|
||||
}
|
||||
|
||||
self.num_remaining_bits_in_curr_byte = 8;
|
||||
self.curr_byte = byte;
|
||||
Ok(())
|
||||
}
|
||||
}
|
||||
|
||||
/// Iterator over IVF packets.
|
||||
pub struct IvfIterator<'a> {
|
||||
cursor: Cursor<&'a [u8]>,
|
||||
}
|
||||
|
||||
impl<'a> IvfIterator<'a> {
|
||||
pub fn new(data: &'a [u8]) -> Self {
|
||||
let mut cursor = Cursor::new(data);
|
||||
|
||||
// Skip the IVH header entirely.
|
||||
cursor.seek(std::io::SeekFrom::Start(32)).unwrap();
|
||||
|
||||
Self { cursor }
|
||||
}
|
||||
}
|
||||
|
||||
impl<'a> Iterator for IvfIterator<'a> {
|
||||
type Item = &'a [u8];
|
||||
|
||||
fn next(&mut self) -> Option<Self::Item> {
|
||||
// Make sure we have a header.
|
||||
let mut len_buf = [0u8; 4];
|
||||
self.cursor.read_exact(&mut len_buf).ok()?;
|
||||
let len = ((len_buf[3] as usize) << 24)
|
||||
| ((len_buf[2] as usize) << 16)
|
||||
| ((len_buf[1] as usize) << 8)
|
||||
| (len_buf[0] as usize);
|
||||
|
||||
// Skip PTS.
|
||||
self.cursor.seek(std::io::SeekFrom::Current(8)).ok()?;
|
||||
|
||||
let start = self.cursor.position() as usize;
|
||||
let _ = self
|
||||
.cursor
|
||||
.seek(std::io::SeekFrom::Current(len as i64))
|
||||
.ok()?;
|
||||
let end = self.cursor.position() as usize;
|
||||
|
||||
Some(&self.cursor.get_ref()[start..end])
|
||||
}
|
||||
}
|
||||
|
||||
/// Helper struct for synthesizing IVF file header
|
||||
pub struct IvfFileHeader {
|
||||
pub magic: [u8; 4],
|
||||
pub version: u16,
|
||||
pub header_size: u16,
|
||||
pub codec: [u8; 4],
|
||||
pub width: u16,
|
||||
pub height: u16,
|
||||
pub framerate: u32,
|
||||
pub timescale: u32,
|
||||
pub frame_count: u32,
|
||||
pub unused: u32,
|
||||
}
|
||||
|
||||
impl Default for IvfFileHeader {
|
||||
fn default() -> Self {
|
||||
Self {
|
||||
magic: Self::MAGIC,
|
||||
version: 0,
|
||||
header_size: 32,
|
||||
codec: Self::CODEC_VP9,
|
||||
width: 320,
|
||||
height: 240,
|
||||
framerate: 1,
|
||||
timescale: 1000,
|
||||
frame_count: 1,
|
||||
unused: Default::default(),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl IvfFileHeader {
|
||||
pub const MAGIC: [u8; 4] = *b"DKIF";
|
||||
pub const CODEC_VP8: [u8; 4] = *b"VP80";
|
||||
pub const CODEC_VP9: [u8; 4] = *b"VP90";
|
||||
pub const CODEC_AV1: [u8; 4] = *b"AV01";
|
||||
|
||||
pub fn new(codec: [u8; 4], width: u16, height: u16, framerate: u32, frame_count: u32) -> Self {
|
||||
let default = Self::default();
|
||||
|
||||
Self {
|
||||
codec,
|
||||
width,
|
||||
height,
|
||||
framerate: framerate * default.timescale,
|
||||
frame_count,
|
||||
..default
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl IvfFileHeader {
|
||||
/// Writes header into writer
|
||||
pub fn writo_into(&self, writer: &mut impl std::io::Write) -> std::io::Result<()> {
|
||||
writer.write_all(&self.magic)?;
|
||||
writer.write_all(&self.version.to_le_bytes())?;
|
||||
writer.write_all(&self.header_size.to_le_bytes())?;
|
||||
writer.write_all(&self.codec)?;
|
||||
writer.write_all(&self.width.to_le_bytes())?;
|
||||
writer.write_all(&self.height.to_le_bytes())?;
|
||||
writer.write_all(&self.framerate.to_le_bytes())?;
|
||||
writer.write_all(&self.timescale.to_le_bytes())?;
|
||||
writer.write_all(&self.frame_count.to_le_bytes())?;
|
||||
writer.write_all(&self.unused.to_le_bytes())?;
|
||||
|
||||
Ok(())
|
||||
}
|
||||
}
|
||||
|
||||
/// Helper struct for synthesizing IVF frame header
|
||||
pub struct IvfFrameHeader {
|
||||
pub frame_size: u32,
|
||||
pub timestamp: u64,
|
||||
}
|
||||
|
||||
impl IvfFrameHeader {
|
||||
/// Writes header into writer
|
||||
pub fn writo_into(&self, writer: &mut impl std::io::Write) -> std::io::Result<()> {
|
||||
writer.write_all(&self.frame_size.to_le_bytes())?;
|
||||
writer.write_all(&self.timestamp.to_le_bytes())?;
|
||||
Ok(())
|
||||
}
|
||||
}
|
||||
|
||||
/// Iterator NALUs in a bitstream.
|
||||
pub struct NalIterator<'a, Nalu>(Cursor<&'a [u8]>, PhantomData<Nalu>);
|
||||
|
||||
impl<'a, Nalu> NalIterator<'a, Nalu> {
|
||||
pub fn new(stream: &'a [u8]) -> Self {
|
||||
Self(Cursor::new(stream), PhantomData)
|
||||
}
|
||||
}
|
||||
|
||||
impl<'a> Iterator for NalIterator<'a, H264Nalu<'a>> {
|
||||
type Item = Cow<'a, [u8]>;
|
||||
|
||||
fn next(&mut self) -> Option<Self::Item> {
|
||||
H264Nalu::next(&mut self.0).map(|n| n.data).ok()
|
||||
}
|
||||
}
|
||||
|
||||
impl<'a> Iterator for NalIterator<'a, H265Nalu<'a>> {
|
||||
type Item = Cow<'a, [u8]>;
|
||||
|
||||
fn next(&mut self) -> Option<Self::Item> {
|
||||
H265Nalu::next(&mut self.0).map(|n| n.data).ok()
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Debug)]
|
||||
pub enum BitWriterError {
|
||||
InvalidBitCount,
|
||||
Io(std::io::Error),
|
||||
}
|
||||
|
||||
impl fmt::Display for BitWriterError {
|
||||
fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
|
||||
match self {
|
||||
BitWriterError::InvalidBitCount => write!(f, "invalid bit count"),
|
||||
BitWriterError::Io(x) => write!(f, "{}", x.to_string()),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl From<std::io::Error> for BitWriterError {
|
||||
fn from(err: std::io::Error) -> Self {
|
||||
BitWriterError::Io(err)
|
||||
}
|
||||
}
|
||||
|
||||
pub type BitWriterResult<T> = std::result::Result<T, BitWriterError>;
|
||||
|
||||
pub struct BitWriter<W: Write> {
|
||||
out: W,
|
||||
nth_bit: u8,
|
||||
curr_byte: u8,
|
||||
}
|
||||
|
||||
impl<W: Write> BitWriter<W> {
|
||||
pub fn new(writer: W) -> Self {
|
||||
Self {
|
||||
out: writer,
|
||||
curr_byte: 0,
|
||||
nth_bit: 0,
|
||||
}
|
||||
}
|
||||
|
||||
/// Writes fixed bit size integer (up to 32 bit)
|
||||
pub fn write_f<T: Into<u32>>(&mut self, bits: usize, value: T) -> BitWriterResult<usize> {
|
||||
let value = value.into();
|
||||
|
||||
if bits > 32 {
|
||||
return Err(BitWriterError::InvalidBitCount);
|
||||
}
|
||||
|
||||
let mut written = 0;
|
||||
for bit in (0..bits).rev() {
|
||||
let bit = (1 << bit) as u32;
|
||||
|
||||
self.write_bit((value & bit) == bit)?;
|
||||
written += 1;
|
||||
}
|
||||
|
||||
Ok(written)
|
||||
}
|
||||
|
||||
/// Takes a single bit that will be outputed to [`std::io::Write`]
|
||||
pub fn write_bit(&mut self, bit: bool) -> BitWriterResult<()> {
|
||||
self.curr_byte |= (bit as u8) << (7u8 - self.nth_bit);
|
||||
self.nth_bit += 1;
|
||||
|
||||
if self.nth_bit == 8 {
|
||||
self.out.write_all(&[self.curr_byte])?;
|
||||
self.nth_bit = 0;
|
||||
self.curr_byte = 0;
|
||||
}
|
||||
|
||||
Ok(())
|
||||
}
|
||||
|
||||
/// Immediately outputs any cached bits to [`std::io::Write`]
|
||||
pub fn flush(&mut self) -> BitWriterResult<()> {
|
||||
if self.nth_bit != 0 {
|
||||
self.out.write_all(&[self.curr_byte])?;
|
||||
self.nth_bit = 0;
|
||||
self.curr_byte = 0;
|
||||
}
|
||||
|
||||
self.out.flush()?;
|
||||
Ok(())
|
||||
}
|
||||
|
||||
/// Returns `true` if ['Self`] hold data that wasn't written to [`std::io::Write`]
|
||||
pub fn has_data_pending(&self) -> bool {
|
||||
self.nth_bit != 0
|
||||
}
|
||||
|
||||
pub(crate) fn inner(&self) -> &W {
|
||||
&self.out
|
||||
}
|
||||
|
||||
pub(crate) fn inner_mut(&mut self) -> &mut W {
|
||||
&mut self.out
|
||||
}
|
||||
}
|
||||
|
||||
impl<W: Write> Drop for BitWriter<W> {
|
||||
fn drop(&mut self) {
|
||||
if let Err(e) = self.flush() {
|
||||
log::error!("Unable to flush bits {e:?}");
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
#[cfg(test)]
|
||||
mod tests {
|
||||
use super::*;
|
||||
|
||||
#[test]
|
||||
fn test_ivf_file_header() {
|
||||
let mut hdr = IvfFileHeader {
|
||||
version: 0,
|
||||
codec: IvfFileHeader::CODEC_VP9,
|
||||
width: 256,
|
||||
height: 256,
|
||||
framerate: 30_000,
|
||||
timescale: 1_000,
|
||||
frame_count: 1,
|
||||
|
||||
..Default::default()
|
||||
};
|
||||
|
||||
let mut buf = Vec::new();
|
||||
hdr.writo_into(&mut buf).unwrap();
|
||||
|
||||
const EXPECTED: [u8; 32] = [
|
||||
0x44, 0x4b, 0x49, 0x46, 0x00, 0x00, 0x20, 0x00, 0x56, 0x50, 0x39, 0x30, 0x00, 0x01,
|
||||
0x00, 0x01, 0x30, 0x75, 0x00, 0x00, 0xe8, 0x03, 0x00, 0x00, 0x01, 0x00, 0x00, 0x00,
|
||||
0x00, 0x00, 0x00, 0x00,
|
||||
];
|
||||
|
||||
assert_eq!(&buf, &EXPECTED);
|
||||
|
||||
hdr.width = 1920;
|
||||
hdr.height = 800;
|
||||
hdr.framerate = 24;
|
||||
hdr.timescale = 1;
|
||||
hdr.frame_count = 100;
|
||||
|
||||
buf.clear();
|
||||
hdr.writo_into(&mut buf).unwrap();
|
||||
|
||||
const EXPECTED2: [u8; 32] = [
|
||||
0x44, 0x4b, 0x49, 0x46, 0x00, 0x00, 0x20, 0x00, 0x56, 0x50, 0x39, 0x30, 0x80, 0x07,
|
||||
0x20, 0x03, 0x18, 0x00, 0x00, 0x00, 0x01, 0x00, 0x00, 0x00, 0x64, 0x00, 0x00, 0x00,
|
||||
0x00, 0x00, 0x00, 0x00,
|
||||
];
|
||||
|
||||
assert_eq!(&buf, &EXPECTED2);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_ivf_frame_header() {
|
||||
let mut hdr = IvfFrameHeader {
|
||||
frame_size: 199249,
|
||||
timestamp: 0,
|
||||
};
|
||||
|
||||
let mut buf = Vec::new();
|
||||
hdr.writo_into(&mut buf).unwrap();
|
||||
|
||||
const EXPECTED: [u8; 12] = [
|
||||
0x51, 0x0a, 0x03, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
|
||||
];
|
||||
|
||||
assert_eq!(&buf, &EXPECTED);
|
||||
|
||||
hdr.timestamp = 1;
|
||||
hdr.frame_size = 52;
|
||||
|
||||
buf.clear();
|
||||
hdr.writo_into(&mut buf).unwrap();
|
||||
|
||||
const EXPECTED2: [u8; 12] = [
|
||||
0x34, 0x00, 0x00, 0x00, 0x01, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
|
||||
];
|
||||
|
||||
assert_eq!(&buf, &EXPECTED2);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_bitwriter_f1() {
|
||||
let mut buf = Vec::<u8>::new();
|
||||
{
|
||||
let mut writer = BitWriter::new(&mut buf);
|
||||
writer.write_f(1, true).unwrap();
|
||||
writer.write_f(1, false).unwrap();
|
||||
writer.write_f(1, false).unwrap();
|
||||
writer.write_f(1, false).unwrap();
|
||||
writer.write_f(1, true).unwrap();
|
||||
writer.write_f(1, true).unwrap();
|
||||
writer.write_f(1, true).unwrap();
|
||||
writer.write_f(1, true).unwrap();
|
||||
}
|
||||
assert_eq!(buf, vec![0b10001111u8]);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_bitwriter_f3() {
|
||||
let mut buf = Vec::<u8>::new();
|
||||
{
|
||||
let mut writer = BitWriter::new(&mut buf);
|
||||
writer.write_f(3, 0b100u8).unwrap();
|
||||
writer.write_f(3, 0b101u8).unwrap();
|
||||
writer.write_f(3, 0b011u8).unwrap();
|
||||
}
|
||||
assert_eq!(buf, vec![0b10010101u8, 0b10000000u8]);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_bitwriter_f4() {
|
||||
let mut buf = Vec::<u8>::new();
|
||||
{
|
||||
let mut writer = BitWriter::new(&mut buf);
|
||||
writer.write_f(4, 0b1000u8).unwrap();
|
||||
writer.write_f(4, 0b1011u8).unwrap();
|
||||
}
|
||||
assert_eq!(buf, vec![0b10001011u8]);
|
||||
}
|
||||
|
||||
// These tests are adapted from the chromium tests at media/video/h264_bit_reader_unitttest.cc
|
||||
|
||||
#[test]
|
||||
fn read_stream_without_escape_and_trailing_zero_bytes() {
|
||||
const RBSP: [u8; 6] = [0x01, 0x23, 0x45, 0x67, 0x89, 0xa0];
|
||||
|
||||
let mut reader = BitReader::new(&RBSP, true);
|
||||
assert_eq!(reader.read_bits::<u32>(1).unwrap(), 0);
|
||||
assert_eq!(reader.num_bits_left(), 47);
|
||||
assert!(reader.has_more_rsbp_data());
|
||||
|
||||
assert_eq!(reader.read_bits::<u32>(8).unwrap(), 0x02);
|
||||
assert_eq!(reader.num_bits_left(), 39);
|
||||
assert!(reader.has_more_rsbp_data());
|
||||
|
||||
assert_eq!(reader.read_bits::<u32>(31).unwrap(), 0x23456789);
|
||||
assert_eq!(reader.num_bits_left(), 8);
|
||||
assert!(reader.has_more_rsbp_data());
|
||||
|
||||
assert_eq!(reader.read_bits::<u32>(1).unwrap(), 1);
|
||||
assert_eq!(reader.num_bits_left(), 7);
|
||||
assert!(reader.has_more_rsbp_data());
|
||||
|
||||
assert_eq!(reader.read_bits::<u32>(1).unwrap(), 0);
|
||||
assert_eq!(reader.num_bits_left(), 6);
|
||||
assert!(!reader.has_more_rsbp_data());
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn single_byte_stream() {
|
||||
const RBSP: [u8; 1] = [0x18];
|
||||
|
||||
let mut reader = BitReader::new(&RBSP, true);
|
||||
assert_eq!(reader.num_bits_left(), 8);
|
||||
assert!(reader.has_more_rsbp_data());
|
||||
assert_eq!(reader.read_bits::<u32>(4).unwrap(), 1);
|
||||
assert!(!reader.has_more_rsbp_data());
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn stop_bit_occupy_full_byte() {
|
||||
const RBSP: [u8; 2] = [0xab, 0x80];
|
||||
|
||||
let mut reader = BitReader::new(&RBSP, true);
|
||||
assert_eq!(reader.num_bits_left(), 16);
|
||||
assert!(reader.has_more_rsbp_data());
|
||||
|
||||
assert_eq!(reader.read_bits::<u32>(8).unwrap(), 0xab);
|
||||
assert_eq!(reader.num_bits_left(), 8);
|
||||
|
||||
assert!(!reader.has_more_rsbp_data());
|
||||
}
|
||||
|
||||
// Check that read_ue behaves properly with input at the limits.
|
||||
#[test]
|
||||
fn read_ue() {
|
||||
// Regular value.
|
||||
let mut reader = BitReader::new(&[0b0001_1010], true);
|
||||
assert_eq!(reader.read_ue::<u32>().unwrap(), 12);
|
||||
assert_eq!(reader.data.position(), 1);
|
||||
assert_eq!(reader.num_remaining_bits_in_curr_byte, 1);
|
||||
|
||||
// 0 value.
|
||||
let mut reader = BitReader::new(&[0b1000_0000], true);
|
||||
assert_eq!(reader.read_ue::<u32>().unwrap(), 0);
|
||||
assert_eq!(reader.data.position(), 1);
|
||||
assert_eq!(reader.num_remaining_bits_in_curr_byte, 7);
|
||||
|
||||
// No prefix stop bit.
|
||||
let mut reader = BitReader::new(&[0b0000_0000], true);
|
||||
reader.read_ue::<u32>().unwrap_err();
|
||||
|
||||
// u32 max value: 31 0-bits, 1 bit marker, 31 bits 1-bits.
|
||||
let mut reader = BitReader::new(
|
||||
&[
|
||||
0b0000_0000,
|
||||
0b0000_0000,
|
||||
0b0000_0000,
|
||||
0b0000_0001,
|
||||
0b1111_1111,
|
||||
0b1111_1111,
|
||||
0b1111_1111,
|
||||
0b1111_1110,
|
||||
],
|
||||
true,
|
||||
);
|
||||
assert_eq!(reader.read_ue::<u32>().unwrap(), 0xffff_fffe);
|
||||
assert_eq!(reader.data.position(), 8);
|
||||
assert_eq!(reader.num_remaining_bits_in_curr_byte, 1);
|
||||
}
|
||||
|
||||
// Check that emulation prevention is being handled correctly.
|
||||
#[test]
|
||||
fn skip_epb_when_enabled() {
|
||||
let mut reader = BitReader::new(&[0x00, 0x00, 0x03, 0x01], false);
|
||||
assert_eq!(reader.read_bits::<u32>(8).unwrap(), 0x00);
|
||||
assert_eq!(reader.read_bits::<u32>(8).unwrap(), 0x00);
|
||||
assert_eq!(reader.read_bits::<u32>(8).unwrap(), 0x03);
|
||||
assert_eq!(reader.read_bits::<u32>(8).unwrap(), 0x01);
|
||||
|
||||
let mut reader = BitReader::new(&[0x00, 0x00, 0x03, 0x01], true);
|
||||
assert_eq!(reader.read_bits::<u32>(8).unwrap(), 0x00);
|
||||
assert_eq!(reader.read_bits::<u32>(8).unwrap(), 0x00);
|
||||
assert_eq!(reader.read_bits::<u32>(8).unwrap(), 0x01);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn read_signed_bits() {
|
||||
let mut reader = BitReader::new(&[0b1111_0000], false);
|
||||
assert_eq!(reader.read_bits_signed::<i32>(4).unwrap(), -1);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,17 @@
|
||||
// Copyright 2023 The ChromiumOS Authors
|
||||
// Use of this source code is governed by a BSD-style license that can be
|
||||
// found in the LICENSE file.
|
||||
|
||||
//! Parsers for various kinds of encoded streams.
|
||||
//!
|
||||
//! This module does not provide any actual decoding tools - that's the job of the
|
||||
//! [crate::decoder] module. However the parsers of this module are heavily used in order to
|
||||
//! implement stateless decoding.
|
||||
//!
|
||||
//! There shall be no dependencies from other modules of this crate to this module, so that it
|
||||
//! can be turned into a crate of its own if needed in the future.
|
||||
|
||||
pub mod av1;
|
||||
pub mod h264;
|
||||
pub mod h265;
|
||||
pub mod vp9;
|
||||
@@ -0,0 +1,9 @@
|
||||
// Copyright 2023 The ChromiumOS Authors
|
||||
// Use of this source code is governed by a BSD-style license that can be
|
||||
// found in the LICENSE file.
|
||||
|
||||
mod helpers;
|
||||
pub mod parser;
|
||||
pub mod reader;
|
||||
pub mod synthesizer;
|
||||
pub mod writer;
|
||||
@@ -0,0 +1,186 @@
|
||||
// Copyright 2023 The ChromiumOS Authors
|
||||
// Use of this source code is governed by a BSD-style license that can be
|
||||
// found in the LICENSE file.
|
||||
|
||||
use crate::codec::av1::parser::NUM_REF_FRAMES;
|
||||
|
||||
const DIV_LUT: [i32; 257] = [
|
||||
16384, 16320, 16257, 16194, 16132, 16070, 16009, 15948, 15888, 15828, 15768, 15709, 15650,
|
||||
15592, 15534, 15477, 15420, 15364, 15308, 15252, 15197, 15142, 15087, 15033, 14980, 14926,
|
||||
14873, 14821, 14769, 14717, 14665, 14614, 14564, 14513, 14463, 14413, 14364, 14315, 14266,
|
||||
14218, 14170, 14122, 14075, 14028, 13981, 13935, 13888, 13843, 13797, 13752, 13707, 13662,
|
||||
13618, 13574, 13530, 13487, 13443, 13400, 13358, 13315, 13273, 13231, 13190, 13148, 13107,
|
||||
13066, 13026, 12985, 12945, 12906, 12866, 12827, 12788, 12749, 12710, 12672, 12633, 12596,
|
||||
12558, 12520, 12483, 12446, 12409, 12373, 12336, 12300, 12264, 12228, 12193, 12157, 12122,
|
||||
12087, 12053, 12018, 11984, 11950, 11916, 11882, 11848, 11815, 11782, 11749, 11716, 11683,
|
||||
11651, 11619, 11586, 11555, 11523, 11491, 11460, 11429, 11398, 11367, 11336, 11305, 11275,
|
||||
11245, 11215, 11185, 11155, 11125, 11096, 11067, 11038, 11009, 10980, 10951, 10923, 10894,
|
||||
10866, 10838, 10810, 10782, 10755, 10727, 10700, 10673, 10645, 10618, 10592, 10565, 10538,
|
||||
10512, 10486, 10460, 10434, 10408, 10382, 10356, 10331, 10305, 10280, 10255, 10230, 10205,
|
||||
10180, 10156, 10131, 10107, 10082, 10058, 10034, 10010, 9986, 9963, 9939, 9916, 9892, 9869,
|
||||
9846, 9823, 9800, 9777, 9754, 9732, 9709, 9687, 9664, 9642, 9620, 9598, 9576, 9554, 9533, 9511,
|
||||
9489, 9468, 9447, 9425, 9404, 9383, 9362, 9341, 9321, 9300, 9279, 9259, 9239, 9218, 9198, 9178,
|
||||
9158, 9138, 9118, 9098, 9079, 9059, 9039, 9020, 9001, 8981, 8962, 8943, 8924, 8905, 8886, 8867,
|
||||
8849, 8830, 8812, 8793, 8775, 8756, 8738, 8720, 8702, 8684, 8666, 8648, 8630, 8613, 8595, 8577,
|
||||
8560, 8542, 8525, 8508, 8490, 8473, 8456, 8439, 8422, 8405, 8389, 8372, 8355, 8339, 8322, 8306,
|
||||
8289, 8273, 8257, 8240, 8224, 8208, 8192,
|
||||
];
|
||||
|
||||
const DIV_LUT_BITS: u32 = 8;
|
||||
const DIV_LUT_PREC_BITS: u32 = 14;
|
||||
|
||||
/// Implements FloorLog2(x), which is defined to be the floor of the base 2
|
||||
/// logarithm of the input x.
|
||||
///
|
||||
/// The input x will always be an integer, and will always be greater than or equal to 1.
|
||||
/// This function extracts the location of the most significant bit in x.
|
||||
pub fn floor_log2(mut x: u32) -> u32 {
|
||||
assert!(x > 0);
|
||||
let mut s = 0;
|
||||
|
||||
while x != 0 {
|
||||
x >>= 1;
|
||||
s += 1;
|
||||
}
|
||||
|
||||
s - 1
|
||||
}
|
||||
|
||||
/// Implements 5.9.3. Get relative distance function
|
||||
pub fn get_relative_dist(enable_order_hint: bool, order_hint_bits: i32, a: i32, b: i32) -> i32 {
|
||||
if !enable_order_hint {
|
||||
0
|
||||
} else {
|
||||
let diff = a - b;
|
||||
let m = 1 << (order_hint_bits - 1);
|
||||
(diff & (m - 1)) - (diff & m)
|
||||
}
|
||||
}
|
||||
|
||||
/// Implements find_latest_backward from section 7.8.
|
||||
pub fn find_latest_backward(
|
||||
shifted_order_hints: &[i32; NUM_REF_FRAMES],
|
||||
used_frame: &[bool; NUM_REF_FRAMES],
|
||||
cur_frame_hint: i32,
|
||||
latest_order_hint: &mut i32,
|
||||
) -> i32 {
|
||||
let mut _ref = -1;
|
||||
|
||||
for i in 0..NUM_REF_FRAMES {
|
||||
let hint = shifted_order_hints[i];
|
||||
if !used_frame[i] && hint >= cur_frame_hint && (_ref < 0 || hint >= *latest_order_hint) {
|
||||
_ref = i as i32;
|
||||
*latest_order_hint = hint;
|
||||
}
|
||||
}
|
||||
|
||||
_ref
|
||||
}
|
||||
|
||||
/// Implements find_earliest_backward from section 7.8.
|
||||
pub fn find_earliest_backward(
|
||||
shifted_order_hints: &[i32; NUM_REF_FRAMES],
|
||||
used_frame: &[bool; NUM_REF_FRAMES],
|
||||
cur_frame_hint: i32,
|
||||
earliest_order_hint: &mut i32,
|
||||
) -> i32 {
|
||||
let mut _ref = -1;
|
||||
|
||||
for i in 0..NUM_REF_FRAMES {
|
||||
let hint = shifted_order_hints[i];
|
||||
if !used_frame[i] && hint >= cur_frame_hint && (_ref < 0 || hint < *earliest_order_hint) {
|
||||
_ref = i as i32;
|
||||
*earliest_order_hint = hint;
|
||||
}
|
||||
}
|
||||
|
||||
_ref
|
||||
}
|
||||
|
||||
/// Implements find_latest_forward from section 7.8.
|
||||
pub fn find_latest_forward(
|
||||
shifted_order_hints: &[i32; NUM_REF_FRAMES],
|
||||
used_frame: &[bool; NUM_REF_FRAMES],
|
||||
cur_frame_hint: i32,
|
||||
latest_order_hint: &mut i32,
|
||||
) -> i32 {
|
||||
let mut _ref = -1;
|
||||
for i in 0..NUM_REF_FRAMES {
|
||||
let hint = shifted_order_hints[i];
|
||||
if !used_frame[i] && hint < cur_frame_hint && (_ref < 0 || hint >= *latest_order_hint) {
|
||||
_ref = i as i32;
|
||||
*latest_order_hint = hint;
|
||||
}
|
||||
}
|
||||
|
||||
_ref
|
||||
}
|
||||
|
||||
pub fn tile_log2(blk_size: u32, target: u32) -> u32 {
|
||||
let mut k = 0;
|
||||
|
||||
while (blk_size << k) < target {
|
||||
k += 1;
|
||||
}
|
||||
|
||||
k
|
||||
}
|
||||
|
||||
pub fn clip3(x: i32, y: i32, z: i32) -> i32 {
|
||||
if z < x {
|
||||
x
|
||||
} else if z > y {
|
||||
y
|
||||
} else {
|
||||
z
|
||||
}
|
||||
}
|
||||
|
||||
/// 5.9.29
|
||||
pub fn inverse_recenter(r: i32, v: i32) -> i32 {
|
||||
if v > 2 * r {
|
||||
v
|
||||
} else if v & 1 != 0 {
|
||||
r - ((v + 1) >> 1)
|
||||
} else {
|
||||
r + (v >> 1)
|
||||
}
|
||||
}
|
||||
|
||||
/// Implements Round2. See 4.7: mathematical functions.
|
||||
pub fn round2(x: u32, n: u32) -> u32 {
|
||||
(x + 2u32.pow(n - 1)) / 2u32.pow(n)
|
||||
}
|
||||
|
||||
/// Implements Round2Signed. See 4.7: mathematical functions.
|
||||
pub fn round2signed(x: i32, n: u32) -> Result<i32, String> {
|
||||
if x >= 0 {
|
||||
i32::try_from(round2(x as u32, n)).map_err(|e| e.to_string())
|
||||
} else {
|
||||
let x = x as i64;
|
||||
let val = i32::try_from(round2(-x as u32, n)).map_err(|e| e.to_string())?;
|
||||
Ok(-val)
|
||||
}
|
||||
}
|
||||
|
||||
/// Implements 7.11.3.7. Resolve divisor process
|
||||
pub fn resolve_divisor(d: i32) -> Result<(u32, i32), String> {
|
||||
let abs_d = u32::try_from(d.abs()).unwrap(); // abs cannot return a negative
|
||||
let n = floor_log2(abs_d);
|
||||
let e = abs_d - (1 << n);
|
||||
|
||||
let f = if n > DIV_LUT_BITS {
|
||||
round2(e, n - DIV_LUT_BITS)
|
||||
} else {
|
||||
e << (DIV_LUT_BITS - n)
|
||||
};
|
||||
|
||||
let div_shift = n + DIV_LUT_PREC_BITS;
|
||||
let div_factor = if d < 0 {
|
||||
-DIV_LUT[f as usize]
|
||||
} else {
|
||||
DIV_LUT[f as usize]
|
||||
};
|
||||
|
||||
Ok((div_shift, div_factor))
|
||||
}
|
||||
File diff suppressed because it is too large
Load Diff
@@ -0,0 +1,251 @@
|
||||
// Copyright 2023 The ChromiumOS Authors
|
||||
// Use of this source code is governed by a BSD-style license that can be
|
||||
// found in the LICENSE file.
|
||||
|
||||
use crate::bitstream_utils::BitReader;
|
||||
|
||||
use crate::codec::av1::helpers;
|
||||
|
||||
use super::parser::AnnexBState;
|
||||
|
||||
pub(crate) struct Reader<'a>(pub BitReader<'a>);
|
||||
|
||||
impl<'a> Reader<'a> {
|
||||
pub fn new(data: &'a [u8]) -> Self {
|
||||
Self(BitReader::new(data, false))
|
||||
}
|
||||
|
||||
/// Implements uvlc(): Variable length unsigned n-bit number appearing
|
||||
/// directly in the bitstream. See 4.10.3
|
||||
pub fn read_uvlc(&mut self) -> Result<u32, String> {
|
||||
let mut leading_zeroes = 0;
|
||||
loop {
|
||||
let done = self.0.read_bit()?;
|
||||
|
||||
if done {
|
||||
break;
|
||||
}
|
||||
|
||||
leading_zeroes += 1;
|
||||
}
|
||||
|
||||
if leading_zeroes >= 32 {
|
||||
return Ok(u32::MAX);
|
||||
}
|
||||
|
||||
let value = self.0.read_bits::<u32>(leading_zeroes)?;
|
||||
Ok(value + (1 << leading_zeroes) - 1)
|
||||
}
|
||||
|
||||
/// Implements leb128(): Unsigned integer represented by a variable number
|
||||
/// of little-endian bytes. See 4.10.5
|
||||
pub fn read_leb128(&mut self) -> Result<u32, String> {
|
||||
let mut value = 0u64;
|
||||
|
||||
for i in 0..8 {
|
||||
let byte = u64::from(self.0.read_bits_aligned::<u32>(8)?);
|
||||
value |= (byte & 0x7f) << (i * 7);
|
||||
|
||||
if byte & 0x80 == 0 {
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
Ok(value as u32)
|
||||
}
|
||||
|
||||
/// Implements su(n): Signed integer converted from an n bits unsigned
|
||||
/// integer in the bitstream. (The unsigned integer corresponds to the
|
||||
/// bottom n bits of the signed integer.). See 4.10.6
|
||||
pub fn read_su(&mut self, num_bits: usize) -> Result<i32, String> {
|
||||
let mut value: i32 = self
|
||||
.0
|
||||
.read_bits::<u32>(num_bits)?
|
||||
.try_into()
|
||||
.map_err(|_| String::from("Read more than 31 signed bits!"))?;
|
||||
let sign_mask = 1 << (num_bits - 1);
|
||||
|
||||
if (value & sign_mask) != 0 {
|
||||
value -= 2 * sign_mask;
|
||||
}
|
||||
|
||||
Ok(value)
|
||||
}
|
||||
|
||||
/// Implements ns(n): Unsigned encoded integer with maximum number of values
|
||||
/// n (i.e. output in range 0..n-1). See 4.10.7
|
||||
pub fn read_ns(&mut self, num_bits: usize) -> Result<u32, String> {
|
||||
let w = helpers::floor_log2(num_bits as u32) + 1;
|
||||
let m = (1 << w) - num_bits as u32;
|
||||
let v = self.0.read_bits::<u32>(
|
||||
usize::try_from(w).map_err(|_| String::from("Invalid num_bits"))? - 1,
|
||||
)?;
|
||||
|
||||
if v < m.into() {
|
||||
return Ok(v);
|
||||
}
|
||||
|
||||
let extra_bit = self.0.read_bit()?;
|
||||
Ok((v << 1) - u32::from(m) + u32::from(extra_bit))
|
||||
}
|
||||
|
||||
/// Implements 5.9.13: Delta quantizer syntax.
|
||||
pub fn read_delta_q(&mut self) -> Result<i32, String> {
|
||||
let delta_coded = self.0.read_bit()?;
|
||||
|
||||
if delta_coded {
|
||||
self.read_su(7)
|
||||
} else {
|
||||
Ok(0)
|
||||
}
|
||||
}
|
||||
|
||||
pub fn more_data_in_bitstream(&mut self) -> bool {
|
||||
self.0.num_bits_left() > 0
|
||||
}
|
||||
|
||||
pub(crate) fn consumed(&self, start_pos: u32) -> u32 {
|
||||
(self.0.position() / 8) as u32 - start_pos
|
||||
}
|
||||
|
||||
/// Get the length of the current OBU in AnnexB format.
|
||||
pub fn current_annexb_obu_length(
|
||||
&mut self,
|
||||
annexb_state: &mut AnnexBState,
|
||||
) -> Result<Option<usize>, String> {
|
||||
if !self.more_data_in_bitstream() {
|
||||
return Ok(None);
|
||||
}
|
||||
|
||||
#[allow(clippy::comparison_chain)]
|
||||
if annexb_state.temporal_unit_consumed == annexb_state.temporal_unit_size {
|
||||
annexb_state.temporal_unit_size = 0;
|
||||
} else if annexb_state.temporal_unit_consumed > annexb_state.temporal_unit_size {
|
||||
return Err(format!(
|
||||
"temporal_unit_size is {} but we consumed {} bytes",
|
||||
annexb_state.temporal_unit_size, annexb_state.temporal_unit_consumed,
|
||||
));
|
||||
}
|
||||
|
||||
if annexb_state.temporal_unit_size == 0 {
|
||||
annexb_state.temporal_unit_size = self.read_leb128()?;
|
||||
if annexb_state.temporal_unit_size == 0 {
|
||||
return Ok(None);
|
||||
}
|
||||
}
|
||||
|
||||
let start_pos = self.consumed(0);
|
||||
|
||||
#[allow(clippy::comparison_chain)]
|
||||
if annexb_state.frame_unit_consumed == annexb_state.frame_unit_size {
|
||||
annexb_state.frame_unit_size = 0;
|
||||
} else if annexb_state.frame_unit_consumed > annexb_state.frame_unit_size {
|
||||
return Err(format!(
|
||||
"frame_unit_size is {} but we consumed {} bytes",
|
||||
annexb_state.frame_unit_size, annexb_state.frame_unit_consumed,
|
||||
));
|
||||
}
|
||||
|
||||
if annexb_state.frame_unit_size == 0 {
|
||||
annexb_state.frame_unit_size = self.read_leb128()?;
|
||||
if annexb_state.frame_unit_size == 0 {
|
||||
return Ok(None);
|
||||
}
|
||||
annexb_state.temporal_unit_consumed += self.consumed(start_pos);
|
||||
}
|
||||
|
||||
let start_pos = self.consumed(0);
|
||||
let obu_length = self.read_leb128()?;
|
||||
let consumed = self.consumed(start_pos);
|
||||
|
||||
annexb_state.temporal_unit_consumed += consumed;
|
||||
annexb_state.frame_unit_consumed += consumed;
|
||||
|
||||
Ok(Some(obu_length.try_into().unwrap()))
|
||||
}
|
||||
|
||||
/// Implements 5.3.4.
|
||||
pub fn read_trailing_bits(&mut self, mut num_bits: u64) -> Result<(), String> {
|
||||
let trailing_one_bit = self.0.read_bit()?;
|
||||
num_bits -= 1;
|
||||
|
||||
if !trailing_one_bit {
|
||||
return Err("bad padding: trailing_one_bit is not set".into());
|
||||
}
|
||||
|
||||
while num_bits > 0 {
|
||||
let trailing_zero_bit = self.0.read_bit()?;
|
||||
if trailing_zero_bit {
|
||||
return Err("bad padding: trailing_zero_bit is set".into());
|
||||
}
|
||||
num_bits -= 1;
|
||||
}
|
||||
|
||||
Ok(())
|
||||
}
|
||||
|
||||
fn decode_subexp(&mut self, num_syms: i32) -> Result<u32, String> {
|
||||
let mut i = 0;
|
||||
let mut mk = 0;
|
||||
let k = 3;
|
||||
|
||||
loop {
|
||||
let b2 = if i != 0 { k + i - 1 } else { k };
|
||||
let a = 1 << b2;
|
||||
if num_syms <= mk + 3 * a {
|
||||
let num_bits = num_syms - mk;
|
||||
let subexp_final_bits = self.read_ns(num_bits as usize)?;
|
||||
return Ok(subexp_final_bits);
|
||||
} else {
|
||||
let subexp_more_bits = self.0.read_bit()?;
|
||||
if subexp_more_bits {
|
||||
i += 1;
|
||||
mk += a;
|
||||
} else {
|
||||
let num_bits = b2 as usize;
|
||||
let subexp_bits = self.0.read_bits::<u32>(num_bits)?;
|
||||
return Ok(subexp_bits + mk as u32);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/// Implements 5.9.27.
|
||||
pub fn decode_unsigned_subexp_with_ref(&mut self, mx: i32, r: i32) -> Result<u32, String> {
|
||||
let v = self.decode_subexp(mx)?;
|
||||
if (r << 1) <= mx {
|
||||
Ok(helpers::inverse_recenter(r, v.try_into().unwrap())
|
||||
.try_into()
|
||||
.unwrap())
|
||||
} else {
|
||||
let res = mx - 1 - helpers::inverse_recenter(mx - 1 - r, v.try_into().unwrap());
|
||||
Ok(res.try_into().unwrap())
|
||||
}
|
||||
}
|
||||
|
||||
/// Implements 5.9.26.
|
||||
pub fn decode_signed_subexp_with_ref(
|
||||
&mut self,
|
||||
low: i32,
|
||||
high: i32,
|
||||
r: i32,
|
||||
) -> Result<i32, String> {
|
||||
let x = self.decode_unsigned_subexp_with_ref(high - low, r - low)?;
|
||||
Ok(i32::try_from(x).unwrap() + low)
|
||||
}
|
||||
|
||||
/// Implements 5.3.5 Byte alignment syntax
|
||||
pub fn byte_alignment(&mut self) -> Result<(), String> {
|
||||
while (self.0.position() & 7) != 0 {
|
||||
self.0.read_bit()?;
|
||||
}
|
||||
|
||||
Ok(())
|
||||
}
|
||||
}
|
||||
|
||||
impl<'a> Clone for Reader<'a> {
|
||||
fn clone(&self) -> Self {
|
||||
Self(self.0.clone())
|
||||
}
|
||||
}
|
||||
File diff suppressed because it is too large
Load Diff
BIN
Binary file not shown.
+8
@@ -0,0 +1,8 @@
|
||||
#!/bin/bash
|
||||
|
||||
# Generates the CRCs for all .av1 files in the current directory using ffmpeg.
|
||||
|
||||
for f in `ls *.av1`; do
|
||||
ffmpeg -i $f -pix_fmt nv12 -f framehash -hash crc32 - |grep -v '^#' |awk '{print $6}' >$f.crc
|
||||
ffmpeg -i $f -pix_fmt nv12 -f framehash -hash md5 - |grep -v '^#' |awk '{print $6}' >$f.md5
|
||||
done
|
||||
BIN
Binary file not shown.
+260
@@ -0,0 +1,260 @@
|
||||
{
|
||||
"profile": "AV1PROFILE_PROFILE_MAIN",
|
||||
"width": 320,
|
||||
"height": 240,
|
||||
"frame_rate": 25,
|
||||
"num_frames": 250,
|
||||
"num_fragments": 250,
|
||||
"md5_checksums": [
|
||||
"83dab175e49c33a6e3ece5c3758d1bf6",
|
||||
"cedb4e25453dba430cb6ee830c1643f3",
|
||||
"ef14d142df162819eee800f3930a9e95",
|
||||
"11849ccb72cfdabc8f70e33271cd916f",
|
||||
"7c9856ed61566f399a1eda2243e199ca",
|
||||
"9394ed10354e986ecdfa2b753b921d2b",
|
||||
"573005df5ba8f982980c1f427dfb472e",
|
||||
"a96d9a913b1712f37f73b6c47b577cdf",
|
||||
"91fc52b076badd1e6cbddb9bca5c5cc3",
|
||||
"6253dde984bc5282f01005d7ebb55fe9",
|
||||
"ad88146fe374423e7c597a922049c76c",
|
||||
"ca7d1ddea7269e476b43a805bd5dd50b",
|
||||
"e3d1eeec3cbfa363a2222d274890be84",
|
||||
"fd7902a1b7352e04dc3940a010482a67",
|
||||
"cada3ee9e33c11c99f89c3f439e2723e",
|
||||
"1723ee290ae8be930f2904cb5aaf9de4",
|
||||
"bf48d536e70a6a0a6549e221a833ebe4",
|
||||
"f13b2e454420b1e0e5ab0adbbf2ce72f",
|
||||
"31c799b4bb0971b798970e23b906cd25",
|
||||
"3d925424bf645a225caf64757944a6e5",
|
||||
"ac55ba724f54dd068350fd2818064dd5",
|
||||
"a7125b8b0dc1e464e76ee64956897d22",
|
||||
"f2a00db38d83cc778fa9fbdb3f4515e3",
|
||||
"c804823f3401558a0283c4e77888af20",
|
||||
"662d71a06319faef70a0981b664481d2",
|
||||
"ad3a221a5f1f9d1a615733155a194385",
|
||||
"8b071335d6cee4b227782415b1cb8a13",
|
||||
"f3d98bfac8e5083233b88386b539b790",
|
||||
"739c73e71590e64db0629911039962b4",
|
||||
"0944990b0ada4012cc686b06833264ef",
|
||||
"77e306874ec1b0c91668f8df0953831b",
|
||||
"ca376820c5248cccb221ec8cb4b0eb9e",
|
||||
"86a72cec3aaf50e393880ac8d4139921",
|
||||
"b892ef1ff0c169b683b35cbaa9462ee3",
|
||||
"b5feafe6c294d29adb1b05138803be36",
|
||||
"3c3b150801f2dc48d300380c9a932890",
|
||||
"16b06dfb6e426a1b6d88e17de83f5e2c",
|
||||
"b1633b1c661a645bd1b04317b30fdbfa",
|
||||
"56d08d66ad8042ba295c3f86025d44cf",
|
||||
"78f6697fbb3af79dbd614f495c372031",
|
||||
"afb700399aacde4c8c895def06fd8594",
|
||||
"e7ae993d18af5c58047196d8369eee3e",
|
||||
"65d5d77181388229606972ea99f5191a",
|
||||
"bd336774b22d502d8fdca07d881d737e",
|
||||
"5cabd4479d94c040f86892fa41860ca9",
|
||||
"9ab949d5ede2f25889a9612b39f6f158",
|
||||
"bb54bb8d7782c4b2d63f201a8d338e7d",
|
||||
"9bf73fc10b4bb19021fd07586a0401d1",
|
||||
"a37621cda632ac5f58e03272d226e53b",
|
||||
"5c3ca20aa646d72b69f6e56592fd2f0b",
|
||||
"b8ff47ec622ce73e70cfc913a3f8116e",
|
||||
"1d020f74b9d3adf17b3f3da6992fcb52",
|
||||
"0236c4d4a9aa68ba59eeaeb6b010db26",
|
||||
"78a8e16aebbe5fc3d1e6051153031a51",
|
||||
"78a54c1182b8ce007a2c10d17915272d",
|
||||
"c7ac4f87168bbddd01155f54c79bc132",
|
||||
"e074be8ece629e08d6d93029b8d34ce2",
|
||||
"edfba91c624f4cdc558c693b33165542",
|
||||
"f3d005151909922bfed7a905693de7c0",
|
||||
"80118fa45af8b3e486928aae49d85b94",
|
||||
"e7663b97fc9b26348876d5fa64a54d5d",
|
||||
"69200a2a34bd4beccf4d473b4a4976f3",
|
||||
"82f6c63a1d87037c08fba3bf16ef9bf8",
|
||||
"6e3033f25eb56670a3c45c12822f340c",
|
||||
"6085ac2c582fd3048c240d3dc46e3455",
|
||||
"b4bbf1a7ae3f57b1d8f0ed65070e010b",
|
||||
"e7f0aad67af5c7719eb89e457cefe6b4",
|
||||
"6d5fdc1d68e136ce9095696be59b617b",
|
||||
"bf15759321de7b21ec3c1bd2cb98ca69",
|
||||
"fc4d67815a94c2e18e7d8b0b7d866641",
|
||||
"0d96425a800252d500fd25da7dccb94d",
|
||||
"5438c9e83cc90f030ee9b1876ec28bdc",
|
||||
"a88915fd11ad765299cef49c8e65e8ab",
|
||||
"4995a3442fa74849f3c45888037f9b42",
|
||||
"82693761e531127d2a98cdc5615cdb92",
|
||||
"07b9601bebc32dc33fd806f90cee586f",
|
||||
"d68df0d6b1dae0e4428a17717723dbe0",
|
||||
"2851422da5bcceebf57ae37a02a1fb57",
|
||||
"ae9ff5d20ea6ba6105fb5ed3e0109834",
|
||||
"a0f956663545e54acbaecf684d0841ff",
|
||||
"23589649e474e155583e6742fa79098c",
|
||||
"9031fbd94bd7a0508b48f8081018d447",
|
||||
"b72920bce007e8f0584d615529dda39f",
|
||||
"0638d4c62dbf43087b4831f2e39314fe",
|
||||
"7d34a23c9b78a61da7a60c520784e204",
|
||||
"0a3cf1af9095a7cb2787cf79ed257c59",
|
||||
"1c05ab5abe793c14ee53ea80e170d6cc",
|
||||
"02d0b03fe10b1b6675b86390fe317778",
|
||||
"7639df2f62c4f958a90337b19a591530",
|
||||
"6080ee3519bd5dd88fbb4050459c126b",
|
||||
"75ce7282839cda34f7ca6111d6353cdb",
|
||||
"2214093ad2bb512a223808ed78fe3369",
|
||||
"eb9bfad6ef08a5f28b531203db3fcb43",
|
||||
"cf4fd68dd57df907f35cc55fe5930fb8",
|
||||
"0aaf799fc8509acd1168cd667f789c9c",
|
||||
"e36ebe2332eb5a9a0ddd03469493d3cb",
|
||||
"49ec5c818b0fcc02fbe3f5636417a2da",
|
||||
"8430b81f1a90c325d116cec424d3c32c",
|
||||
"c67e1e1f7fec9f9ab688f1d54cf2f4d4",
|
||||
"fd98fabb3db5dc2d841dc50af7395130",
|
||||
"8af0c8f486057cbae86939fa248c8238",
|
||||
"e1888b1f32d2991682261f28f860cb27",
|
||||
"9c0455744df622675a6d5232d790f5b1",
|
||||
"acc73e6665f54bdaa98acead49be5f65",
|
||||
"5c6e6167e6c05411a22c348a2c807fc5",
|
||||
"89d09cb95dc2e212efed266b4b687b35",
|
||||
"9d35702693b699ac0ccb4bffc559e2e8",
|
||||
"afbeb04e4e00a1b0a4407b4a34815a63",
|
||||
"6f3515d4331d83abfd1dbdfb2de78189",
|
||||
"be6cb0da9149ad5d68269d0ed657cd38",
|
||||
"9e9f498bc71df3097caa266089a77fba",
|
||||
"40d9198beea17a254ee6b74544317a7a",
|
||||
"e2f04358338ae115635e621cf229df56",
|
||||
"5da22d2d7c5c304fc6198f809206de1d",
|
||||
"4951c1ad5ea945a098c6a4d7fad163fa",
|
||||
"5a25300d1ef28b60f9e137b47b2e4364",
|
||||
"5954dd49f459ea4c0984dba30076db89",
|
||||
"820a838b57e43271a55f2f17c3e0676f",
|
||||
"b486477fe004ed8ee213afe8ecdb35c6",
|
||||
"0715a96ada76fe2d713cdfc2b9abf11d",
|
||||
"c30544fc39df1c7876043115ceed355f",
|
||||
"1f7ce18debf0e339db20c68c1f0bf8b8",
|
||||
"e10229f1f8a95d2e59a086e5d6ac4faa",
|
||||
"8ad6c73b38d32fd1712a1fd67750d364",
|
||||
"30fdf5b06ae3fd2c0df9e631ea1e1048",
|
||||
"0026b1e7db5e25651ecaaf19685b2dc2",
|
||||
"0b53f3f2ca17d00138b4678580d63ab6",
|
||||
"cb08de2030a42826ef573082188d3614",
|
||||
"e14242c776b3a32da936d1f6484e29ba",
|
||||
"41a43d3ee9021bcda09efa1b218cc65a",
|
||||
"9b884d243e8b330edb8f569e416960f1",
|
||||
"5737c4f611cdc9871df094ba51a6a6d5",
|
||||
"9d47768c1a15c0b20d28545ad5e6ca53",
|
||||
"8c981af4e6c3432a330b6d8e330bc03a",
|
||||
"195910783821f51cb3dad54f651c1a75",
|
||||
"659833dc03dc5f929eb140e188f2b1f6",
|
||||
"f2f30eaa72c5a093d67cebfda15fac73",
|
||||
"3c521e3025340933cf826027a994114b",
|
||||
"469b72a8db2ac77c70a792c8035b0288",
|
||||
"e2cf1d7930d44341c5c363b0ceaa6c84",
|
||||
"469d4283cca04e78ac2cb9407531db67",
|
||||
"02416a6c77174796ecbd61adab5177b9",
|
||||
"f89077b3dfb54119157e04dff24b79bd",
|
||||
"2d1a8fc14b649e343288b5972abe1fdf",
|
||||
"3d059186dab49cd8d13584dc365862db",
|
||||
"765ef42f457dee16234df6992ec74cd2",
|
||||
"957589c357d82631477177e6250c713c",
|
||||
"2473af8395d08e9396216c082f854d6f",
|
||||
"6a117d16f8e8ffe87aebec05844d6a0a",
|
||||
"8e467cfcbd66ed80fca648248af56b3f",
|
||||
"71e39c4304e09688750aa85e9d040f4a",
|
||||
"3071ee85d87ef8029619d4777c799c2b",
|
||||
"cc86f09e3f228001e806fd3afe5e1271",
|
||||
"3828673cf67828240005f7bf9fe37412",
|
||||
"12700b04cc22fba7688ae1709e48b886",
|
||||
"a1045aa13b0007d23f22ad8e83c4bdb0",
|
||||
"a12e6c2d2805336c407417b7503670fe",
|
||||
"11dd1907c6785eccbedeba6a5708c516",
|
||||
"3fb8be1276b83db1ccd8206cc69ec736",
|
||||
"980ce03ae4e6f2eceb314f37290e0dd2",
|
||||
"c39395d9b6108f1bd4ab1f004c44a5f0",
|
||||
"a7244c89efd3b00611c92ebaf98793e8",
|
||||
"fbede5a2957c023216e79cc31ab32946",
|
||||
"bb40544a3adfe992e07a8eb3e5bf6966",
|
||||
"0f71f57100699e95fff51dabde86cb42",
|
||||
"6ba085efefacf385728423ca99bd4629",
|
||||
"4df2d956234cb2129ebefdd90f28185a",
|
||||
"d0a0d4263ef4f32d59b004752a5b94af",
|
||||
"a9deed8bf550f4ee370730e1d993d4dc",
|
||||
"eca65ba94930bf78854ce991c05e3d9c",
|
||||
"4eef9fa5bca7ee74e97a0ac6e12e5f53",
|
||||
"84e97d688ba1922f9ed71d72cea67259",
|
||||
"407dc974664f93be14c53a2b291cc422",
|
||||
"31eb9e589470cfbe4e72e1c441d56e3f",
|
||||
"ea4f696ad2ae150a1f0e622874f67fca",
|
||||
"7552fea18e053b4771c0490015488291",
|
||||
"6863cd478581244fff72f6a0021a9fb8",
|
||||
"9118deacdd49db27c317eeec868cd870",
|
||||
"50290c87c5ee1c558bd304fc0f4a15ea",
|
||||
"5aef6d78538151648450fd391c9deaec",
|
||||
"a7e3feb23e01e0b4556446e01b2d81eb",
|
||||
"6918645d2b0bd511247c6ed372bf213e",
|
||||
"2f9661df4d114ab9f3a98a255a8177f2",
|
||||
"baaea6f9f05a7a2bc10721f1afe6f57b",
|
||||
"366ab99c236a8e84e658bad69d82a1ba",
|
||||
"f950a6508c051ef32e4c04be56fcc719",
|
||||
"9e586c73c3b9daebc608600efbee33b0",
|
||||
"e9fb7690077e8cbac15d5564130a8d7c",
|
||||
"ef3c8d2b5376753c26a795bf403cdcc1",
|
||||
"be46eec37b67dfc8077705ce89588a7c",
|
||||
"dba1b08cfeddbcf4e8d79621ee343bd4",
|
||||
"a4a612bcb6c33c7799433c998c5f12d1",
|
||||
"5f45fa214bc860825510d034f1ff26de",
|
||||
"1766dd6fe6080c408a4baca135609f1c",
|
||||
"34dc16d50d7afeb93a874563f1f5e4fd",
|
||||
"0a5314f217972a9271c4792921f61153",
|
||||
"7c228f1f5ccc6f7307526531f79a307d",
|
||||
"188544479f5a44b6a90b6be62e33611a",
|
||||
"9cb9be5574c6a2c06862a3ed605c68b5",
|
||||
"845b033cc82472d8e647f2a3bb3eb653",
|
||||
"2c6b66c68107fd13395a1ed5f0639355",
|
||||
"0436424e24e562c52ab7e7063b04c129",
|
||||
"21248608746bb252faa347028f5343d7",
|
||||
"81607476ae05bbf7386dea9d8eac352b",
|
||||
"fadc83171e49e779ebe5d81769049cef",
|
||||
"e768c96238a8cf14caf8d03420981265",
|
||||
"c57aa4d8e2f7c9101868246de4de1c13",
|
||||
"b405a01f8eb1d4ffade0e49f593e5a8f",
|
||||
"76f02bab5b932bf4bcf4ce06bdf0e42c",
|
||||
"51b09ce7117e15cf654ad76e13263f0c",
|
||||
"45f650b845e31b87b522574cc7afde80",
|
||||
"858605e70124ef7130742b713b079ad9",
|
||||
"5d955329a007742ecab7153d2e69b262",
|
||||
"c4139450cb6ffcac62b764d62083bc31",
|
||||
"8b13de43cffa8d12dd17b8e749a375b2",
|
||||
"b000fbdb48be876302bc336aeb6deba1",
|
||||
"88e2b7abbc33f70231889113582f8f8c",
|
||||
"d9f31d4cfeee31da719b3567d5d11a19",
|
||||
"7c2a6cc9be9ce8cb96b5a1616909941b",
|
||||
"954c2be78496f0235ab137a98a5ae11a",
|
||||
"9efd13fe15e8900f67a6c76103c9ce78",
|
||||
"b04aedbee4993ff955ebba1dcb8a04e1",
|
||||
"83b045a2a43445f97d82c1faaf33b332",
|
||||
"1cf416bf7bf4a2124932d4b1bb0aca94",
|
||||
"ac17d329b587b065512269fa3def8279",
|
||||
"fcf2b7ab8b7d53741c737f75c3587a6f",
|
||||
"0aaaaca0aa165a6786e2161043aa1e72",
|
||||
"8d19a0848fad7b32a15522cc0b24b4d1",
|
||||
"be0a1be3f90ff47b80be31be021c28f1",
|
||||
"c9e423c59186d93100c56ad8f14c0fb4",
|
||||
"052dbb80693750c716acce06f3ad7266",
|
||||
"6a2c8002df49d3832b7e1abd70af542f",
|
||||
"92bec3aa91edfd2cc5dee7d1f66ac189",
|
||||
"579d29110e7a7a71152aedb9a462c79f",
|
||||
"b0bb435df9dd211dd5da3c547c8887b8",
|
||||
"fb7a3cf05afd06668287f5dcc9c20a55",
|
||||
"bbd27b9b555f361478f9516e839077a5",
|
||||
"a74dc95fe982709b25434bcc53a91973",
|
||||
"27f82418568440caea8df7bcdf56eae7",
|
||||
"d5ff54f75c8fa6d60428816945ee5e89",
|
||||
"4b6677c4cc9866f477ecda5618411c8d",
|
||||
"d56046f25113a82b090bf9a91291587c",
|
||||
"cb56aa0f4343c7da821c04e55e8af21a",
|
||||
"c633ea8a4c20ea4d70c8c241d0293e9b",
|
||||
"dd2b157ce8a010a61908170ecb3e31b0",
|
||||
"3c3509c92b03a702ac1f4a28b5abd3d5",
|
||||
"d1400e54b8b61241cabad5291d6e3a43",
|
||||
"c29470cd6afb0aee4ae4a8d0622a468d",
|
||||
"e35764f7bd48746d37478b2d6f3f2755",
|
||||
"3dcf1fd38fbcc1d45f98574b17ac5710"
|
||||
]
|
||||
}
|
||||
BIN
Binary file not shown.
+250
@@ -0,0 +1,250 @@
|
||||
6aea6152
|
||||
cb4a90b3
|
||||
fd83e35f
|
||||
074bd081
|
||||
216fcc04
|
||||
c73ca1e4
|
||||
fbfb2a30
|
||||
cd587935
|
||||
e8bc2912
|
||||
051517b7
|
||||
f3e9831d
|
||||
812e7bba
|
||||
3e2054e6
|
||||
7446385e
|
||||
8b75d043
|
||||
f930d9a7
|
||||
7bf6b591
|
||||
253c5389
|
||||
11a25f1f
|
||||
c5101d08
|
||||
ee1c0aae
|
||||
ce055a9f
|
||||
0ed4a046
|
||||
aa0a72c3
|
||||
87f7a598
|
||||
aa3a422e
|
||||
0ee0e533
|
||||
5ce3d683
|
||||
cc7e88e5
|
||||
3e1c7774
|
||||
bd6708d3
|
||||
0ba6a0ee
|
||||
ac00d2da
|
||||
f1222e65
|
||||
3c6aadc8
|
||||
a28e7327
|
||||
fd2d0d8c
|
||||
e0a791bf
|
||||
90a33d7e
|
||||
63ae28ce
|
||||
26230e31
|
||||
215e9021
|
||||
6ca1295b
|
||||
01604116
|
||||
15f8d3c9
|
||||
6f28a1d4
|
||||
8d54633d
|
||||
08cccd41
|
||||
0b082923
|
||||
0aaae5a1
|
||||
98310e98
|
||||
33f12578
|
||||
9fd072e0
|
||||
36a1220e
|
||||
a278c894
|
||||
828dcf3f
|
||||
3573505c
|
||||
861874b7
|
||||
dd78b1fd
|
||||
310ca9a0
|
||||
74d670f3
|
||||
93c48c50
|
||||
814991b1
|
||||
fca836a7
|
||||
9299e121
|
||||
a8d3b267
|
||||
661a1c05
|
||||
50038462
|
||||
bc866fe1
|
||||
46e2d826
|
||||
7035f78c
|
||||
00c2ed17
|
||||
6b64a0c6
|
||||
315f4761
|
||||
df4c3ef7
|
||||
7ad4996a
|
||||
3185972f
|
||||
19af5f17
|
||||
01c4e3a3
|
||||
3728bd7e
|
||||
16adc384
|
||||
5798878c
|
||||
d0ac34ee
|
||||
56f76f8a
|
||||
2b6f3f47
|
||||
4a5ec1a9
|
||||
75f0027c
|
||||
7a1dd1f6
|
||||
50c0f14f
|
||||
978d00e7
|
||||
a1c430b8
|
||||
7c67432a
|
||||
4615b4cb
|
||||
c53c7308
|
||||
876d9f98
|
||||
b0015dd1
|
||||
73288a1e
|
||||
d843f40a
|
||||
4127f309
|
||||
3adbe013
|
||||
9e84104c
|
||||
9f98680d
|
||||
380a36e2
|
||||
2202f414
|
||||
ca931e02
|
||||
182b20e1
|
||||
815a6be2
|
||||
59cc5d53
|
||||
0a05d420
|
||||
d53b5f2d
|
||||
98c613aa
|
||||
e4de4fc1
|
||||
2b8f6d29
|
||||
48ce9586
|
||||
d6e822dd
|
||||
c8674577
|
||||
75dfa0e9
|
||||
7ad6ed39
|
||||
f7725767
|
||||
a44e0f11
|
||||
bcd743cd
|
||||
1ccf1835
|
||||
8ceb6153
|
||||
0a43af09
|
||||
79bab263
|
||||
855dd65a
|
||||
a37d34fe
|
||||
be063b4d
|
||||
aec5df0c
|
||||
7b12399b
|
||||
c5ad7294
|
||||
2f609050
|
||||
8bb2dcba
|
||||
c19aa763
|
||||
700eb99b
|
||||
11bf5174
|
||||
e74d5eda
|
||||
f54e54ef
|
||||
0173e328
|
||||
162372d6
|
||||
3c8f18a3
|
||||
ce8579c7
|
||||
91bdbf86
|
||||
745edb27
|
||||
df783664
|
||||
e7462cfd
|
||||
30d8d761
|
||||
f15474e6
|
||||
4c5bb874
|
||||
27c36dd2
|
||||
c40981c0
|
||||
1cc4b7c0
|
||||
ebd96a40
|
||||
a5f4d31e
|
||||
cd1a957a
|
||||
0b0d8e1d
|
||||
c5d30b99
|
||||
d7d6b75e
|
||||
0ea512b8
|
||||
dd12fced
|
||||
29eab68d
|
||||
e4bb47d1
|
||||
e3060770
|
||||
0483e6ca
|
||||
296b084f
|
||||
d11abff6
|
||||
c7589394
|
||||
b187d62e
|
||||
2e217b3c
|
||||
b516603b
|
||||
287c8b71
|
||||
2f24a994
|
||||
7ea617d0
|
||||
7ba0024e
|
||||
7067ae3e
|
||||
95650cbd
|
||||
7b2e7deb
|
||||
d1b6da6d
|
||||
76f22a5a
|
||||
9ce6fac7
|
||||
1e5cf6c7
|
||||
bbe20b47
|
||||
2f2cc450
|
||||
3f0297f3
|
||||
a2084503
|
||||
06487daa
|
||||
5d06ae48
|
||||
02cbba1b
|
||||
b386b4f4
|
||||
47623370
|
||||
1acb6e9d
|
||||
5b55fd00
|
||||
70d43ae3
|
||||
8a0703c0
|
||||
4d62a79b
|
||||
e358a27d
|
||||
b89fcec0
|
||||
59508160
|
||||
f2c9f57d
|
||||
2b57ec09
|
||||
9427c163
|
||||
91169e76
|
||||
eda7cda1
|
||||
289c7bc0
|
||||
821dc9d5
|
||||
b003d6ce
|
||||
3d8dbc53
|
||||
d6c3324a
|
||||
76a7b9c9
|
||||
a4d4b21b
|
||||
51ce1f2c
|
||||
49cc8861
|
||||
b4fc2309
|
||||
c435862e
|
||||
3bceba0f
|
||||
7ee0c219
|
||||
cb04facd
|
||||
b2fad23b
|
||||
2b8cf4f1
|
||||
fcdcbad2
|
||||
f3c038c6
|
||||
71e639e9
|
||||
282325bf
|
||||
52c64dc8
|
||||
9ebe8705
|
||||
f8679dc3
|
||||
906116a2
|
||||
9f740960
|
||||
3cad2af8
|
||||
c7765bb4
|
||||
c8f38650
|
||||
1e5d7d36
|
||||
3374dc4c
|
||||
e83476e5
|
||||
6a9c0d00
|
||||
08b8cb58
|
||||
bd74ea9e
|
||||
399bce4c
|
||||
1577335d
|
||||
cdbbea48
|
||||
d1373c35
|
||||
cc62212a
|
||||
8a5e59e2
|
||||
75f5f297
|
||||
8c5f3776
|
||||
fce09105
|
||||
92b2d17e
|
||||
416e8530
|
||||
968d1450
|
||||
d20688a8
|
||||
+250
@@ -0,0 +1,250 @@
|
||||
ce5386b1beaa89f73a668d3720717fb4
|
||||
8f5d21bf3d78d9c242609bdd8df6cfd6
|
||||
7d75a95c82171c8cf4b09d694d4cbef9
|
||||
20d4bebdc51702ee7f40e187747696c7
|
||||
9c55dc575db2a189c5576537afca2c54
|
||||
68a15a8cdbc0aeaf10dff65db4d1a810
|
||||
dfd03584b10852eb16901d52578d8f8e
|
||||
a9a0cffed57bba344c9366ce91408e26
|
||||
9fdf7ac0fc07f2391da56462faf3e2e0
|
||||
b83d2da8f82e1a06d26bbb514b471f4b
|
||||
17c92a58fb68d0b3b02cb5b9105dbb37
|
||||
1e5bd3c05fb67f2aefc01611ed43d117
|
||||
eb287667a027f3e5325bfb27652021a6
|
||||
67e388816af98b99e4b9134516205c52
|
||||
35cba13cd8a6f23d6dfd95803af0886b
|
||||
45966e4a1b83dce6a2b0c259fea10cae
|
||||
416a95236e46a7bb0f11cf1147631979
|
||||
f23ab59d3c4efbb8eb2d80d67a2e5136
|
||||
ab7bce374a3468c03483418ccffb52de
|
||||
bdaaf22c6c4e58d393d7c051d4d0ae8a
|
||||
6f7de2de3e8d79c726f0f2c2fe52ee58
|
||||
d2fc7067797bbffe3765a238ff803b85
|
||||
769dbc781b43759287da0b2888e8338b
|
||||
2abd608af57978290e24c4cd62e4904f
|
||||
4fb54d7b0a71a4d3ca95b56d7b3d9a81
|
||||
262ba91321a0edac67c01e6ddcc2e4f7
|
||||
4c9c2d967fb853ecdc6042f223a7d138
|
||||
f21ccd6a4eb5f561b0a0eff9cb1956ec
|
||||
3c206ed277864249e7eaade75e71c815
|
||||
7167fed00621472662b19a0215dd00c8
|
||||
76e6f142e710ff5d7f6797d3fc1b8dfd
|
||||
4d814bb1fec0a2bca9d92140d5734ae0
|
||||
65b576bf469b182c8954591fd15dd21c
|
||||
abb7e8f11617aa0c9bf6870e537b008d
|
||||
02b86ea6b2f8699097d6899ab1f073c4
|
||||
610786c84b9fc118679afb09404c15ae
|
||||
7feb8fae65767f848ebbefa730f15d68
|
||||
12677768a4266571e85b750b51ef9fc8
|
||||
5b6541b178f6a885d0439b381e501967
|
||||
352cc90ecc1f3e26c19970f106c475fa
|
||||
9c5215a9fce00f0ec30615649171f8e4
|
||||
27e593a4b30c0b64939c62e5e0488e5a
|
||||
d8a5fd54958527ecefc642f6730e4164
|
||||
754dfeb4337108e534ba70bab429777a
|
||||
821ad11c37ffda3ad5438b57a9523e9f
|
||||
add12a889a5e08a5358e2e0e086fe522
|
||||
fa100278f49cbb7f9c9d7dc5e12fc0e1
|
||||
ef46a151df454084d0f0bc644adeb47d
|
||||
c02f8023a4be8a3249bcf6e1b223dffa
|
||||
41a11da85806e85fa86a9de41e1cd93f
|
||||
e792d89b5df357d1b03b1812a762832c
|
||||
90f72aead8124bc6ce51495b4b99da75
|
||||
af25c00b1990b778b50f529ae81efa30
|
||||
900f66f0e222a8d253153711a23a5ee9
|
||||
0850cad30d59e1b5e5e2943e6b916d0a
|
||||
03ca961e9876ee96b14711c44a9f2a69
|
||||
fa6bf9c30dd8618fdab499e9dd217582
|
||||
4e09e08e1f99bf210d4757281ab4c836
|
||||
dbf2bc5355a6846a028a7b3140fdef34
|
||||
b38e1973cabab58f8137cf66fc7fa3aa
|
||||
0ee4998cda155ca2823f5c9336a62cc2
|
||||
4f9918b4782a59a0361619389bd22d54
|
||||
c5c82a59505a9888b95e012a068b3254
|
||||
5eabc71dca6f57407ba306db416c5031
|
||||
41b8b1155bf6144aeb822acbff051e1a
|
||||
12e7d40ccd4298400cf3b5577370bff8
|
||||
2f22b7979183c9c3b26838c7e84a49a8
|
||||
68af2e17a487001d9374f6b38ed950af
|
||||
3a55bc482844ecc366e10544c38cc890
|
||||
6a08d19bbecb554837c9922b9818455a
|
||||
b2f95f48c1214a5dfb8f4528afcbf901
|
||||
c3c4fc391a1a040c71cb65a8c654a5b1
|
||||
86786c55d138bb9a4a4165522cd15253
|
||||
b329ece66a9dab6bebab9e3687629e7c
|
||||
7d6d818e7fe9a9b335c72ee6aedf9980
|
||||
317956c14cb119eda584de03dbf089cc
|
||||
d095660e8dd16899be9e916e0ff7946c
|
||||
12571880bd56bed1bbbd034094fabbaa
|
||||
7f702375e8c43036e36ddc07b543afb2
|
||||
e254d4c9854b28db694fb5477144551e
|
||||
24a4b250bc0461ad4894334838fbb645
|
||||
16b3dca0bfcad463498a62bf8a3f06a8
|
||||
77dbe27a373d87a6fc1012caa166a0a5
|
||||
1b304ea04baf90e1bf11d6296f64f933
|
||||
42c94efb8ab8ceb6ff98497ef0981c66
|
||||
1074398b6c210e833b4135c7d2caeb91
|
||||
3bce8712a7ba7b037a2cc85e4f72aea4
|
||||
42c1bc84350e82cbead374b41732d8d5
|
||||
76f8fc8e26d1b64e8afb8f263ec3f795
|
||||
ef9f8b042eb888f5408ccb500c9a157f
|
||||
d46505a331887bf49e157f8de4c17a98
|
||||
0be7df49394337bb277b63a6b9147520
|
||||
deb977415048b0b7fa1fd02878c1b10e
|
||||
a0395e32b4cc279f8545b791cee5d394
|
||||
c7dcd8741840799886a1f3b5208c0b36
|
||||
02b1297f58b9e699f232b9ed7e8bbcd0
|
||||
48852b209e405d7aaab50c5810448579
|
||||
d8a5598b2e7183d72cd3a81e54f301ce
|
||||
dfe2e4af7518f0d1f04fd825069d575b
|
||||
e1a70679c95173e6a1504b7c52dd0d3c
|
||||
16ae515dfb1268052bfe330a1cbbbffa
|
||||
f17c090f2140cfd3060558ceefe4d1bd
|
||||
58b1ba1fbbd76670c9218a846a8f6baf
|
||||
f543d8380c2d095f9b62d4ffaaee7467
|
||||
1b16d3e1d688a5a905ff22680ad803cb
|
||||
12e16d58422d5a5899379f11c6ca5b9a
|
||||
f1986c3e4a4545356656f76c27db0ae7
|
||||
324a3d45f631aed6c758888254ae07ad
|
||||
49532c06798f685e2a41dc683b081f79
|
||||
95934c5a6e2cc8de08c8b8342936cf38
|
||||
67d89790392f18908c24cc36bbe50362
|
||||
c4f963cd490f9241f4959cca6a6d35ce
|
||||
b21db693ba10898ec3205cb0cb343a02
|
||||
72e63227e74f2473a44da365fa9bc02f
|
||||
220d247b6c0f9512256dfcdf95582148
|
||||
86c290279ffa61ca615f1f8f8877e807
|
||||
6651d56c548fd4c5e8b2c86354c28ca3
|
||||
d863321ec53cd582d34a8ee5e88a5576
|
||||
f262bda775cf67026d8d226620c9d4aa
|
||||
15816d939647890021ac31aa1c3b2f75
|
||||
229b295a4bc44d8522580dd4383812d0
|
||||
833afe87641e34e60f2d127f44a7cbeb
|
||||
62cc76ec3a853878ebfff04ebbc8addc
|
||||
40fca93e2d6a976de641cdbfb56257b5
|
||||
65d654e9145527f8fcd24608d64f68f1
|
||||
1b2b0c68b6d9305f46642109b92a234b
|
||||
85aa859b29f2caeb60343cf53718e451
|
||||
3aa590c6c8a4dee128f1e7fcb2b82a2a
|
||||
699cb26687934746b722e2dfbc1d2c82
|
||||
9cc9b7f51cbdbaf15bc8eec9c1a1cf7c
|
||||
b1be900fe6a6eb21a563754420c5f38e
|
||||
47976686aff65b42661ae0fb2685f462
|
||||
6b3f96a1be97c4c2a371d5935672096a
|
||||
ac9b60c754c95ccbaf56f68544712dd6
|
||||
7dab91b68788d2ed61cd40d3073091a8
|
||||
35b367c591e71f11ddcd9dcc75be79e7
|
||||
d3321d793265630e81e6d52b20e58f44
|
||||
9d97047182efd2ba0594a569f6f2ce04
|
||||
14c772a342a28bd6785bb722e67fc767
|
||||
288802e107c373c9d28eeb3f8726214e
|
||||
1c9d8ff04c346a59ea4f3a349dc4b891
|
||||
2c857a65df4619d06f424ddcccbd2221
|
||||
10a0b3e4e7825d21fc22bcff34a3b26c
|
||||
78c8a5ee6e6880a3af222bfc27de62a1
|
||||
9e16c4346a69ff9127bb6c9287d98859
|
||||
1f1d95fa30c9c3a58f41d9657aa0e6ec
|
||||
5e8c6b3b4690da7f4175abe288d0c509
|
||||
0bf315da2ec1e135963cc4c9cf73ea1a
|
||||
2992fdf295438ec9f8dc7979b6d4e5ff
|
||||
a3d43afab0119aa3b6e38a81d244c388
|
||||
889d049dc64457637ad089e18e718a23
|
||||
46016c611b9323fe8f4206bd393a6741
|
||||
f555b79be75ba2a1a741f467192b693e
|
||||
242d4b4c323c7c1e9648de52ec8d7c2c
|
||||
383d2ee4d28919f193f28448ba28ddc6
|
||||
2c7cd221bd1025547fc654ca6717553d
|
||||
e16de72e05b548758832102ffb39d242
|
||||
5cecf3fc2823225f0f1748a1fedadf35
|
||||
4e709cb904317a43ffd94739281d32bc
|
||||
b6dfd4657668ed2c1ebaff9fa92702ab
|
||||
3c618cd95706cc25a0565fc0ed00e352
|
||||
dca8dbc91708a94525f16c064454a2ce
|
||||
fe3fbff45e30dcc3fa4f57ae78114f7f
|
||||
0f7bc1eab5cf9faeaa127c0243675253
|
||||
19b6fca6e3f0d039074753265436d2c5
|
||||
fce950fc2ec2526c0b29b6869ff58dee
|
||||
4c9c0b19668dc20a9fac84e13c590deb
|
||||
8fe06eb38ee870ffd7cb9f065476ac78
|
||||
515ce9eaa9a6970f809475e416325123
|
||||
6d58868e827dd9dca698c5b24380fea7
|
||||
072951a0e1d06aa829d716a86ae498fe
|
||||
07f23add152c44f754eac930093e6b59
|
||||
e98798eb29c162ddfff7ec72aa7b65dc
|
||||
62f4e1ca0294a3944224fb4a4280ed56
|
||||
28fc910d3b7a926f63e7bfaf18878cc1
|
||||
40065fe5c0e32a16622988bc1485a038
|
||||
b2623ec75309e28c6c0a4b179e4acd6d
|
||||
3ea6b991014bde2185be7a6c92745c28
|
||||
5fde25c7fe79a9c0e2b86eb1878de02c
|
||||
5bf42dc3006f4c601d01ee4820e44d38
|
||||
44500b37f18b2eb499894bc1ff1efdcc
|
||||
f5bfe3352c4281083083e51ef1fe0a72
|
||||
abf9c6ba639f68d9b8f8c0d9d97a19d3
|
||||
75f6dc9ccbc6a1381785bf9440a95744
|
||||
4db0fd27cac20f59718fe00c5dc13202
|
||||
9e0d611017698f2d1716f8d9d81a8bf7
|
||||
13c7015f10b37b3dbe1618cde5edc2ee
|
||||
70e2a0182471290a46961a0d62611053
|
||||
47ba942fc3396104b3731ea40eb97339
|
||||
bf10238458969233c0062b09a68f22a7
|
||||
115b7375a9aa156b8203dd88acdc5672
|
||||
1b39aa22702c993aa1e7606e9afad9c5
|
||||
c85e67c08e02538f151c6276662ba69b
|
||||
89dcf41c5adb944bcd46ca3f67666a75
|
||||
90fd81f9b572ede873313f4faa58da4b
|
||||
00badd56e7d5b2dddb10ef7de05be515
|
||||
a13c3404ac033c9fce8a6435f2a4a416
|
||||
af83e8f7af9785c96ff61c354417aab4
|
||||
5176b27945cc7686520b1425144dc4d3
|
||||
e5f248be2c0e83ad0a6bfd2eff759f6e
|
||||
cd693cce87898dafa5c68dbaeeb151fa
|
||||
2a47245fc3da1229e3dc288f216c4e20
|
||||
c6f8e88fbf5566000f8f86b0d4bf9020
|
||||
3e88c5f7b571a292c998cac33b41215b
|
||||
2a2e8271ecba2cec040ef343317e2ae9
|
||||
8e693282602bde3452061affde3ce514
|
||||
992acab07cf4d6aa5b95b4c4cc33cf0c
|
||||
6db1ad3494be9c3c9758b00d956bd2cb
|
||||
824d130a16d7bc2c6d6b26c31c2a0e05
|
||||
d286bd8f5cdfd65b695815ff68bb191f
|
||||
4c5bbeecda37bf4fb6dd79f266aac3d6
|
||||
c4f64e991e13dfc3913246f730c31e87
|
||||
459d8fd53975479aa977c61dc3ef1b9e
|
||||
ece59558539b52684433b54bf2c7e214
|
||||
4362122d7ed40dd5ad0886cdf901539e
|
||||
66ffe4fb31a5e4fa9462a3aa57224476
|
||||
86586a04e75ebde21903f407e6037e50
|
||||
93b2e3af91cb24ffafa6418c76cc1ce7
|
||||
41ca4598aac764fa944d8f578b2ab5ab
|
||||
6ef7ce05836d63520925817c220a5274
|
||||
c4e902503f3d5602ba2454907d7809f7
|
||||
8f3cb447682bebd3a230740e11d35dff
|
||||
4340ec4c3e62118b2749264393fc78ee
|
||||
8178fbeab83126f2111ab74c23d241c9
|
||||
db298bcb2a999b7ac2037991ef078d32
|
||||
f586895d6dca606e2d62dfdc92cc2021
|
||||
b5aaf4b91dd7b6cda3a2635c603c4500
|
||||
0f024b59afa957b59f5ebb9b63ff1b1a
|
||||
41acc378b08b5d9b7da2f644b79aebdf
|
||||
cd173062829e6e86eb094b8d40d16579
|
||||
ef55ddf0fa94dcb4f275033bb55e8466
|
||||
48a3efa1f9d8291d99ce434fc5474fb6
|
||||
731a649c764fa964d6e2ce640b34c25a
|
||||
5f9ac7b1dca56c8dab9ce1f92341094b
|
||||
e9b0ee7355299aaf55cce1e240050125
|
||||
8a285fc7c728cdfa1f4b651c64eab7e5
|
||||
05823b86e463ad514a4cd7eacd9c01bb
|
||||
0ce34318119eb0e8921b5e798789bea0
|
||||
e46f4c793b42136b1eefbece7b867052
|
||||
1331c7fcae627f290299b978299a9b48
|
||||
285c5783029a7b23374e91cce19df0de
|
||||
670fba4fad181308c15778f38e619646
|
||||
46a3350a39c21f45bafed75d339ed101
|
||||
b1d6f3230e58d2647d1aa4a5446061ed
|
||||
f6eb5936ec9bb3212b14716da8329839
|
||||
1ecb00343900a550ad33ba10777df23d
|
||||
4b9f70244adda8e0a42f4da7341b8d84
|
||||
6caafeb26793a011ebef352ef1d1aa34
|
||||
723ab0797281e7ee36109b53140a324d
|
||||
7a5e9be8c7c8307aa22c8b48708424a2
|
||||
@@ -0,0 +1,203 @@
|
||||
// Copyright 2024 The ChromiumOS Authors
|
||||
// Use of this source code is governed by a BSD-style license that can be
|
||||
// found in the LICENSE file.
|
||||
|
||||
use std::fmt;
|
||||
use std::io::Write;
|
||||
|
||||
use crate::bitstream_utils::BitWriter;
|
||||
use crate::bitstream_utils::BitWriterError;
|
||||
|
||||
#[derive(Debug)]
|
||||
pub enum ObuWriterError {
|
||||
BitWriterError(BitWriterError),
|
||||
UnalignedLeb128,
|
||||
}
|
||||
|
||||
impl fmt::Display for ObuWriterError {
|
||||
fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
|
||||
match self {
|
||||
ObuWriterError::BitWriterError(x) => write!(f, "{}", x.to_string()),
|
||||
ObuWriterError::UnalignedLeb128 => {
|
||||
write!(f, "attempted to write leb128 on unaligned position")
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl From<BitWriterError> for ObuWriterError {
|
||||
fn from(err: BitWriterError) -> Self {
|
||||
ObuWriterError::BitWriterError(err)
|
||||
}
|
||||
}
|
||||
|
||||
pub type ObuWriterResult<T> = std::result::Result<T, ObuWriterError>;
|
||||
|
||||
pub struct ObuWriter<W: Write>(BitWriter<W>);
|
||||
|
||||
impl<W: Write> ObuWriter<W> {
|
||||
pub fn new(writer: W) -> Self {
|
||||
Self(BitWriter::new(writer))
|
||||
}
|
||||
|
||||
/// Writes fixed bit size integer. Corresponds to `f(n)` in AV1 spec defined in 4.10.2.
|
||||
pub fn write_f<T: Into<u32>>(&mut self, bits: usize, value: T) -> ObuWriterResult<usize> {
|
||||
self.0
|
||||
.write_f(bits, value)
|
||||
.map_err(ObuWriterError::BitWriterError)
|
||||
}
|
||||
|
||||
/// Writes variable length unsigned n-bit number. Corresponds to `uvlc()` in AV1 spec
|
||||
/// defined in 4.10.3.
|
||||
pub fn write_uvlc<T: Into<u32>>(&mut self, value: T) -> ObuWriterResult<usize> {
|
||||
let value: u32 = value.into();
|
||||
if value == u32::MAX {
|
||||
return self.write_f(32, 0u32);
|
||||
}
|
||||
|
||||
let value = value + 1;
|
||||
let leading_zeros = (32 - value.leading_zeros()) as usize;
|
||||
|
||||
Ok(self.write_f(leading_zeros - 1, 0u32)? + self.write_f(leading_zeros, value)?)
|
||||
}
|
||||
|
||||
/// Writes unsigned little-endian n-byte integer. Corresponds to `le(n)` in AV1 spec
|
||||
/// defined in 4.10.4.
|
||||
pub fn write_le<T: Into<u32>>(&mut self, n: usize, value: T) -> ObuWriterResult<usize> {
|
||||
let value: u32 = value.into();
|
||||
let mut value = value.to_le();
|
||||
|
||||
for _ in 0..n {
|
||||
self.write_f(4, value & 0xff)?;
|
||||
value >>= 8;
|
||||
}
|
||||
|
||||
Ok(n)
|
||||
}
|
||||
|
||||
/// Writes unsigned integer represented by a variable number of little-endian bytes.
|
||||
/// Corresponds to `leb128()` in AV1 spec defined in 4.10.4.
|
||||
///
|
||||
/// Note: Despite the name, the AV1 4.10.4 limits the value to [`u32::MAX`] = (1 << 32) - 1.
|
||||
pub fn write_leb128<T: Into<u32>>(
|
||||
&mut self,
|
||||
value: T,
|
||||
min_bytes: usize,
|
||||
) -> ObuWriterResult<usize> {
|
||||
if !self.aligned() {
|
||||
return Err(ObuWriterError::UnalignedLeb128);
|
||||
}
|
||||
|
||||
let value: u32 = value.into();
|
||||
let mut value: u32 = value.to_le();
|
||||
let mut bytes = 0;
|
||||
|
||||
for _ in 0..8 {
|
||||
bytes += 1;
|
||||
|
||||
if value >= 0x7f || bytes < min_bytes {
|
||||
self.write_f(8, 0x80 | (value & 0x7f))?;
|
||||
value >>= 7;
|
||||
} else {
|
||||
self.write_f(8, value & 0x7f)?;
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
assert!(value < 0x7f);
|
||||
|
||||
Ok(bytes)
|
||||
}
|
||||
|
||||
pub fn write_su<T: Into<i32>>(&mut self, bits: usize, value: T) -> ObuWriterResult<usize> {
|
||||
let mut value: i32 = value.into();
|
||||
if value < 0 {
|
||||
value += 1 << bits;
|
||||
}
|
||||
|
||||
assert!(value >= 0);
|
||||
self.write_f(bits, value.unsigned_abs())
|
||||
}
|
||||
|
||||
pub fn aligned(&self) -> bool {
|
||||
!self.0.has_data_pending()
|
||||
}
|
||||
}
|
||||
|
||||
#[cfg(test)]
|
||||
mod tests {
|
||||
use super::*;
|
||||
use crate::codec::av1::reader::Reader;
|
||||
|
||||
const TEST_VECTOR: &[u32] = &[
|
||||
// some random test values
|
||||
u32::MAX,
|
||||
1,
|
||||
2,
|
||||
3,
|
||||
4,
|
||||
10,
|
||||
20,
|
||||
7312,
|
||||
8832,
|
||||
10123,
|
||||
47457,
|
||||
21390213,
|
||||
u32::MIN,
|
||||
u32::MAX - 1,
|
||||
];
|
||||
|
||||
#[test]
|
||||
fn test_uvlc() {
|
||||
for &value in TEST_VECTOR {
|
||||
let mut buf = Vec::<u8>::new();
|
||||
|
||||
ObuWriter::new(&mut buf).write_uvlc(value).unwrap();
|
||||
|
||||
if value == u32::MAX {
|
||||
// force stop uvlc
|
||||
buf.push(0x80);
|
||||
}
|
||||
|
||||
let read = Reader::new(&buf).read_uvlc().unwrap();
|
||||
|
||||
assert_eq!(read, value, "failed testing {}", value);
|
||||
}
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_leb128() {
|
||||
for &value in TEST_VECTOR {
|
||||
let mut buf = Vec::<u8>::new();
|
||||
|
||||
ObuWriter::new(&mut buf).write_leb128(value, 0).unwrap();
|
||||
let read = Reader::new(&buf).read_leb128().unwrap();
|
||||
|
||||
assert_eq!(read, value, "failed testing {}", value);
|
||||
}
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_su() {
|
||||
let vector = TEST_VECTOR
|
||||
.iter()
|
||||
.map(|e| *e as i32)
|
||||
.chain(TEST_VECTOR.iter().map(|e| -(*e as i32)));
|
||||
|
||||
for value in vector {
|
||||
let bits = 32 - value.abs().leading_zeros() as usize + 1; // For sign
|
||||
if bits >= 32 {
|
||||
// Skip too big nubmers
|
||||
continue;
|
||||
}
|
||||
|
||||
let mut buf = Vec::<u8>::new();
|
||||
|
||||
ObuWriter::new(&mut buf).write_su(bits, value).unwrap();
|
||||
|
||||
let read = Reader::new(&buf).read_su(bits as usize).unwrap();
|
||||
|
||||
assert_eq!(read, value, "failed testing {}", value);
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,10 @@
|
||||
// Copyright 2023 The ChromiumOS Authors
|
||||
// Use of this source code is governed by a BSD-style license that can be
|
||||
// found in the LICENSE file.
|
||||
|
||||
pub mod dpb;
|
||||
pub mod nalu;
|
||||
pub mod nalu_writer;
|
||||
pub mod parser;
|
||||
pub mod picture;
|
||||
pub mod synthesizer;
|
||||
File diff suppressed because it is too large
Load Diff
@@ -0,0 +1,124 @@
|
||||
// Copyright 2023 The ChromiumOS Authors
|
||||
// Use of this source code is governed by a BSD-style license that can be
|
||||
// found in the LICENSE file.
|
||||
|
||||
use std::borrow::Cow;
|
||||
use std::fmt::Debug;
|
||||
use std::io::Cursor;
|
||||
use std::io::Seek;
|
||||
use std::io::SeekFrom;
|
||||
|
||||
#[allow(clippy::len_without_is_empty)]
|
||||
pub trait Header: Sized {
|
||||
/// Parse the NALU header, returning it.
|
||||
fn parse<T: AsRef<[u8]>>(cursor: &mut Cursor<T>) -> Result<Self, String>;
|
||||
/// Whether this header type indicates EOS.
|
||||
fn is_end(&self) -> bool;
|
||||
/// The length of the header.
|
||||
fn len(&self) -> usize;
|
||||
}
|
||||
|
||||
#[derive(Debug)]
|
||||
pub struct Nalu<'a, U> {
|
||||
pub header: U,
|
||||
/// The mapping that backs this NALU. Possibly shared with the other NALUs
|
||||
/// in the Access Unit.
|
||||
pub data: Cow<'a, [u8]>,
|
||||
|
||||
pub size: usize,
|
||||
pub offset: usize,
|
||||
}
|
||||
|
||||
impl<'a, U> Nalu<'a, U>
|
||||
where
|
||||
U: Debug + Header,
|
||||
{
|
||||
/// Find the next Annex B encoded NAL unit.
|
||||
pub fn next(cursor: &mut Cursor<&'a [u8]>) -> Result<Nalu<'a, U>, String> {
|
||||
let bitstream = cursor.clone().into_inner();
|
||||
let pos = usize::try_from(cursor.position()).map_err(|err| err.to_string())?;
|
||||
|
||||
// Find the start code for this NALU
|
||||
let current_nalu_offset = match Nalu::<'a, U>::find_start_code(cursor, pos) {
|
||||
Some(offset) => offset,
|
||||
None => return Err("No NAL found".into()),
|
||||
};
|
||||
|
||||
let mut start_code_offset = pos + current_nalu_offset;
|
||||
|
||||
// If the preceding byte is 00, then we actually have a four byte SC,
|
||||
// i.e. 00 00 00 01 Where the first 00 is the "zero_byte()"
|
||||
if start_code_offset > 0 && cursor.get_ref()[start_code_offset - 1] == 00 {
|
||||
start_code_offset -= 1;
|
||||
}
|
||||
|
||||
// The NALU offset is its offset + 3 bytes to skip the start code.
|
||||
let nalu_offset = pos + current_nalu_offset + 3;
|
||||
|
||||
// Set the bitstream position to the start of the current NALU
|
||||
cursor.set_position(u64::try_from(nalu_offset).map_err(|err| err.to_string())?);
|
||||
|
||||
let hdr = U::parse(cursor)?;
|
||||
|
||||
// Find the start of the subsequent NALU.
|
||||
let mut next_nalu_offset = match Nalu::<'a, U>::find_start_code(cursor, nalu_offset) {
|
||||
Some(offset) => offset,
|
||||
None => {
|
||||
let cur_pos = cursor.position();
|
||||
let end_pos = cursor
|
||||
.seek(SeekFrom::End(0))
|
||||
.map_err(|err| err.to_string())?;
|
||||
let _ = cursor
|
||||
.seek(SeekFrom::Start(cur_pos))
|
||||
.map_err(|err| err.to_string())?;
|
||||
(end_pos - cur_pos) as usize
|
||||
} // Whatever data is left must be part of the current NALU
|
||||
};
|
||||
|
||||
while next_nalu_offset > 0 && cursor.get_ref()[nalu_offset + next_nalu_offset - 1] == 00 {
|
||||
// Discard trailing_zero_8bits
|
||||
next_nalu_offset -= 1;
|
||||
}
|
||||
|
||||
let nal_size = if hdr.is_end() {
|
||||
// the NALU is comprised of only the header
|
||||
hdr.len()
|
||||
} else {
|
||||
next_nalu_offset
|
||||
};
|
||||
|
||||
Ok(Nalu {
|
||||
header: hdr,
|
||||
data: Cow::from(&bitstream[start_code_offset..nalu_offset + nal_size]),
|
||||
size: nal_size,
|
||||
offset: nalu_offset - start_code_offset,
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
impl<'a, U> Nalu<'a, U>
|
||||
where
|
||||
U: Debug,
|
||||
{
|
||||
fn find_start_code(data: &mut Cursor<&'a [u8]>, offset: usize) -> Option<usize> {
|
||||
// discard all zeroes until the start code pattern is found
|
||||
data.get_ref()[offset..]
|
||||
.windows(3)
|
||||
.position(|window| window == [0x00, 0x00, 0x01])
|
||||
}
|
||||
|
||||
pub fn into_owned(self) -> Nalu<'static, U> {
|
||||
Nalu {
|
||||
header: self.header,
|
||||
size: self.size,
|
||||
offset: self.offset,
|
||||
data: Cow::Owned(self.data.into_owned()),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl<'a, U> AsRef<[u8]> for Nalu<'a, U> {
|
||||
fn as_ref(&self) -> &[u8] {
|
||||
&self.data[self.offset..self.offset + self.size]
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,311 @@
|
||||
// Copyright 2024 The ChromiumOS Authors
|
||||
// Use of this source code is governed by a BSD-style license that can be
|
||||
// found in the LICENSE file.
|
||||
use std::fmt;
|
||||
use std::io::Write;
|
||||
|
||||
use crate::bitstream_utils::BitWriter;
|
||||
use crate::bitstream_utils::BitWriterError;
|
||||
|
||||
/// Internal wrapper over [`std::io::Write`] for possible emulation prevention
|
||||
struct EmulationPrevention<W: Write> {
|
||||
out: W,
|
||||
prev_bytes: [Option<u8>; 2],
|
||||
|
||||
/// Emulation prevention enabled.
|
||||
ep_enabled: bool,
|
||||
}
|
||||
|
||||
impl<W: Write> EmulationPrevention<W> {
|
||||
fn new(writer: W, ep_enabled: bool) -> Self {
|
||||
Self {
|
||||
out: writer,
|
||||
prev_bytes: [None; 2],
|
||||
ep_enabled,
|
||||
}
|
||||
}
|
||||
|
||||
fn write_byte(&mut self, curr_byte: u8) -> std::io::Result<()> {
|
||||
if self.prev_bytes[1] == Some(0x00) && self.prev_bytes[0] == Some(0x00) && curr_byte <= 0x03
|
||||
{
|
||||
self.out.write_all(&[0x00, 0x00, 0x03, curr_byte])?;
|
||||
self.prev_bytes = [None; 2];
|
||||
} else {
|
||||
if let Some(byte) = self.prev_bytes[1] {
|
||||
self.out.write_all(&[byte])?;
|
||||
}
|
||||
|
||||
self.prev_bytes[1] = self.prev_bytes[0];
|
||||
self.prev_bytes[0] = Some(curr_byte);
|
||||
}
|
||||
|
||||
Ok(())
|
||||
}
|
||||
|
||||
/// Writes a H.264 NALU header.
|
||||
fn write_header(&mut self, idc: u8, type_: u8) -> NaluWriterResult<()> {
|
||||
self.out.write_all(&[
|
||||
0x00,
|
||||
0x00,
|
||||
0x00,
|
||||
0x01,
|
||||
(idc & 0b11) << 5 | (type_ & 0b11111),
|
||||
])?;
|
||||
|
||||
Ok(())
|
||||
}
|
||||
|
||||
fn has_data_pending(&self) -> bool {
|
||||
self.prev_bytes[0].is_some() || self.prev_bytes[1].is_some()
|
||||
}
|
||||
}
|
||||
|
||||
impl<W: Write> Write for EmulationPrevention<W> {
|
||||
fn write(&mut self, buf: &[u8]) -> std::io::Result<usize> {
|
||||
if !self.ep_enabled {
|
||||
self.out.write_all(buf)?;
|
||||
return Ok(buf.len());
|
||||
}
|
||||
|
||||
for byte in buf {
|
||||
self.write_byte(*byte)?;
|
||||
}
|
||||
|
||||
Ok(buf.len())
|
||||
}
|
||||
|
||||
fn flush(&mut self) -> std::io::Result<()> {
|
||||
if let Some(byte) = self.prev_bytes[1].take() {
|
||||
self.out.write_all(&[byte])?;
|
||||
}
|
||||
|
||||
if let Some(byte) = self.prev_bytes[0].take() {
|
||||
self.out.write_all(&[byte])?;
|
||||
}
|
||||
|
||||
self.out.flush()
|
||||
}
|
||||
}
|
||||
|
||||
impl<W: Write> Drop for EmulationPrevention<W> {
|
||||
fn drop(&mut self) {
|
||||
if let Err(e) = self.flush() {
|
||||
log::error!("Unable to flush pending bytes {e:?}");
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Debug)]
|
||||
pub enum NaluWriterError {
|
||||
Overflow,
|
||||
Io(std::io::Error),
|
||||
BitWriterError(BitWriterError),
|
||||
}
|
||||
|
||||
impl fmt::Display for NaluWriterError {
|
||||
fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
|
||||
match self {
|
||||
NaluWriterError::Overflow => write!(f, "value increment caused value overflow"),
|
||||
NaluWriterError::Io(x) => write!(f, "{}", x.to_string()),
|
||||
NaluWriterError::BitWriterError(x) => write!(f, "{}", x.to_string()),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl From<std::io::Error> for NaluWriterError {
|
||||
fn from(err: std::io::Error) -> Self {
|
||||
NaluWriterError::Io(err)
|
||||
}
|
||||
}
|
||||
|
||||
impl From<BitWriterError> for NaluWriterError {
|
||||
fn from(err: BitWriterError) -> Self {
|
||||
NaluWriterError::BitWriterError(err)
|
||||
}
|
||||
}
|
||||
|
||||
pub type NaluWriterResult<T> = std::result::Result<T, NaluWriterError>;
|
||||
|
||||
/// A writer for H.264 bitstream. It is capable of outputing bitstream with
|
||||
/// emulation-prevention.
|
||||
pub struct NaluWriter<W: Write>(BitWriter<EmulationPrevention<W>>);
|
||||
|
||||
impl<W: Write> NaluWriter<W> {
|
||||
pub fn new(writer: W, ep_enabled: bool) -> Self {
|
||||
Self(BitWriter::new(EmulationPrevention::new(writer, ep_enabled)))
|
||||
}
|
||||
|
||||
/// Writes fixed bit size integer (up to 32 bit) output with emulation
|
||||
/// prevention if enabled. Corresponds to `f(n)` in H.264 spec.
|
||||
pub fn write_f<T: Into<u32>>(&mut self, bits: usize, value: T) -> NaluWriterResult<usize> {
|
||||
self.0
|
||||
.write_f(bits, value)
|
||||
.map_err(NaluWriterError::BitWriterError)
|
||||
}
|
||||
|
||||
/// An alias to [`Self::write_f`] Corresponds to `n(n)` in H.264 spec.
|
||||
pub fn write_u<T: Into<u32>>(&mut self, bits: usize, value: T) -> NaluWriterResult<usize> {
|
||||
self.write_f(bits, value)
|
||||
}
|
||||
|
||||
/// Writes a number in exponential golumb format.
|
||||
pub fn write_exp_golumb(&mut self, value: u32) -> NaluWriterResult<()> {
|
||||
let value = value.checked_add(1).ok_or(NaluWriterError::Overflow)?;
|
||||
let bits = 32 - value.leading_zeros() as usize;
|
||||
let zeros = bits - 1;
|
||||
|
||||
self.write_f(zeros, 0u32)?;
|
||||
self.write_f(bits, value)?;
|
||||
|
||||
Ok(())
|
||||
}
|
||||
|
||||
/// Writes a unsigned integer in exponential golumb format.
|
||||
/// Coresponds to `ue(v)` in H.264 spec.
|
||||
pub fn write_ue<T: Into<u32>>(&mut self, value: T) -> NaluWriterResult<()> {
|
||||
let value = value.into();
|
||||
|
||||
self.write_exp_golumb(value)
|
||||
}
|
||||
|
||||
/// Writes a signed integer in exponential golumb format.
|
||||
/// Coresponds to `se(v)` in H.264 spec.
|
||||
pub fn write_se<T: Into<i32>>(&mut self, value: T) -> NaluWriterResult<()> {
|
||||
let value: i32 = value.into();
|
||||
let abs_value: u32 = value.unsigned_abs();
|
||||
|
||||
if value <= 0 {
|
||||
self.write_ue(2 * abs_value)
|
||||
} else {
|
||||
self.write_ue(2 * abs_value - 1)
|
||||
}
|
||||
}
|
||||
|
||||
/// Returns `true` if ['Self`] hold data that wasn't written to [`std::io::Write`]
|
||||
pub fn has_data_pending(&self) -> bool {
|
||||
self.0.has_data_pending() || self.0.inner().has_data_pending()
|
||||
}
|
||||
|
||||
/// Writes a H.264 NALU header.
|
||||
pub fn write_header(&mut self, idc: u8, _type: u8) -> NaluWriterResult<()> {
|
||||
self.0.flush()?;
|
||||
self.0.inner_mut().write_header(idc, _type)?;
|
||||
Ok(())
|
||||
}
|
||||
|
||||
/// Returns `true` if next bits will be aligned to 8
|
||||
pub fn aligned(&self) -> bool {
|
||||
!self.0.has_data_pending()
|
||||
}
|
||||
}
|
||||
|
||||
#[cfg(test)]
|
||||
mod tests {
|
||||
use super::*;
|
||||
use crate::bitstream_utils::BitReader;
|
||||
|
||||
#[test]
|
||||
fn simple_bits() {
|
||||
let mut buf = Vec::<u8>::new();
|
||||
{
|
||||
let mut writer = NaluWriter::new(&mut buf, false);
|
||||
writer.write_f(1, true).unwrap();
|
||||
writer.write_f(1, false).unwrap();
|
||||
writer.write_f(1, false).unwrap();
|
||||
writer.write_f(1, false).unwrap();
|
||||
writer.write_f(1, true).unwrap();
|
||||
writer.write_f(1, true).unwrap();
|
||||
writer.write_f(1, true).unwrap();
|
||||
writer.write_f(1, true).unwrap();
|
||||
}
|
||||
assert_eq!(buf, vec![0b10001111u8]);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn simple_first_few_ue() {
|
||||
fn single_ue(value: u32) -> Vec<u8> {
|
||||
let mut buf = Vec::<u8>::new();
|
||||
{
|
||||
let mut writer = NaluWriter::new(&mut buf, false);
|
||||
writer.write_ue(value).unwrap();
|
||||
}
|
||||
buf
|
||||
}
|
||||
|
||||
assert_eq!(single_ue(0), vec![0b10000000u8]);
|
||||
assert_eq!(single_ue(1), vec![0b01000000u8]);
|
||||
assert_eq!(single_ue(2), vec![0b01100000u8]);
|
||||
assert_eq!(single_ue(3), vec![0b00100000u8]);
|
||||
assert_eq!(single_ue(4), vec![0b00101000u8]);
|
||||
assert_eq!(single_ue(5), vec![0b00110000u8]);
|
||||
assert_eq!(single_ue(6), vec![0b00111000u8]);
|
||||
assert_eq!(single_ue(7), vec![0b00010000u8]);
|
||||
assert_eq!(single_ue(8), vec![0b00010010u8]);
|
||||
assert_eq!(single_ue(9), vec![0b00010100u8]);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn writer_reader() {
|
||||
let mut buf = Vec::<u8>::new();
|
||||
{
|
||||
let mut writer = NaluWriter::new(&mut buf, false);
|
||||
writer.write_ue(10u32).unwrap();
|
||||
writer.write_se(-42).unwrap();
|
||||
writer.write_se(3).unwrap();
|
||||
writer.write_ue(5u32).unwrap();
|
||||
}
|
||||
|
||||
let mut reader = BitReader::new(&buf, true);
|
||||
|
||||
assert_eq!(reader.read_ue::<u32>().unwrap(), 10);
|
||||
assert_eq!(reader.read_se::<i32>().unwrap(), -42);
|
||||
assert_eq!(reader.read_se::<i32>().unwrap(), 3);
|
||||
assert_eq!(reader.read_ue::<u32>().unwrap(), 5);
|
||||
|
||||
let mut buf = Vec::<u8>::new();
|
||||
{
|
||||
let mut writer = NaluWriter::new(&mut buf, false);
|
||||
writer.write_se(30).unwrap();
|
||||
writer.write_ue(100u32).unwrap();
|
||||
writer.write_se(-402).unwrap();
|
||||
writer.write_ue(50u32).unwrap();
|
||||
}
|
||||
|
||||
let mut reader = BitReader::new(&buf, true);
|
||||
|
||||
assert_eq!(reader.read_se::<i32>().unwrap(), 30);
|
||||
assert_eq!(reader.read_ue::<u32>().unwrap(), 100);
|
||||
assert_eq!(reader.read_se::<i32>().unwrap(), -402);
|
||||
assert_eq!(reader.read_ue::<u32>().unwrap(), 50);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn writer_emulation_prevention() {
|
||||
fn test(input: &[u8], bitstream: &[u8]) {
|
||||
let mut buf = Vec::<u8>::new();
|
||||
{
|
||||
let mut writer = NaluWriter::new(&mut buf, true);
|
||||
for byte in input {
|
||||
writer.write_f(8, *byte).unwrap();
|
||||
}
|
||||
}
|
||||
assert_eq!(buf, bitstream);
|
||||
{
|
||||
let mut reader = BitReader::new(&buf, true);
|
||||
for byte in input {
|
||||
assert_eq!(*byte, reader.read_bits::<u8>(8).unwrap());
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
test(&[0x00, 0x00, 0x00], &[0x00, 0x00, 0x03, 0x00]);
|
||||
test(&[0x00, 0x00, 0x01], &[0x00, 0x00, 0x03, 0x01]);
|
||||
test(&[0x00, 0x00, 0x02], &[0x00, 0x00, 0x03, 0x02]);
|
||||
test(&[0x00, 0x00, 0x03], &[0x00, 0x00, 0x03, 0x03]);
|
||||
|
||||
test(&[0x00, 0x00, 0x00, 0x00], &[0x00, 0x00, 0x03, 0x00, 0x00]);
|
||||
test(&[0x00, 0x00, 0x00, 0x01], &[0x00, 0x00, 0x03, 0x00, 0x01]);
|
||||
test(&[0x00, 0x00, 0x00, 0x02], &[0x00, 0x00, 0x03, 0x00, 0x02]);
|
||||
test(&[0x00, 0x00, 0x00, 0x03], &[0x00, 0x00, 0x03, 0x00, 0x03]);
|
||||
}
|
||||
}
|
||||
File diff suppressed because it is too large
Load Diff
@@ -0,0 +1,443 @@
|
||||
// Copyright 2022 The ChromiumOS Authors
|
||||
// Use of this source code is governed by a BSD-style license that can be
|
||||
// found in the LICENSE file.
|
||||
|
||||
use std::cell::RefCell;
|
||||
use std::ops::Deref;
|
||||
use std::rc::Rc;
|
||||
use std::rc::Weak;
|
||||
|
||||
use log::debug;
|
||||
|
||||
use crate::codec::h264::parser::MaxLongTermFrameIdx;
|
||||
use crate::codec::h264::parser::RefPicMarking;
|
||||
use crate::codec::h264::parser::Slice;
|
||||
use crate::codec::h264::parser::SliceType;
|
||||
use crate::codec::h264::parser::Sps;
|
||||
use crate::Resolution;
|
||||
|
||||
#[derive(Copy, Clone, Debug, Default, PartialEq, Eq)]
|
||||
pub enum Field {
|
||||
#[default]
|
||||
Frame,
|
||||
Top,
|
||||
Bottom,
|
||||
}
|
||||
|
||||
impl Field {
|
||||
/// Returns the field of opposite parity.
|
||||
pub fn opposite(&self) -> Self {
|
||||
match *self {
|
||||
Field::Frame => Field::Frame,
|
||||
Field::Top => Field::Bottom,
|
||||
Field::Bottom => Field::Top,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Copy, Clone, Debug, Default, PartialEq, Eq)]
|
||||
pub enum Reference {
|
||||
#[default]
|
||||
None,
|
||||
ShortTerm,
|
||||
LongTerm,
|
||||
}
|
||||
|
||||
#[derive(Copy, Clone, Debug, Default, PartialEq, Eq)]
|
||||
pub enum IsIdr {
|
||||
#[default]
|
||||
No,
|
||||
Yes {
|
||||
idr_pic_id: u16,
|
||||
},
|
||||
}
|
||||
|
||||
/// The rank of a field, i.e. whether it is the first or second one to be parsed from the stream.
|
||||
/// This is unrelated to the `Field` type, as the first field can be either `Top` or `Bottom`.
|
||||
#[derive(Default, Debug)]
|
||||
pub enum FieldRank {
|
||||
/// Frame has a single field.
|
||||
#[default]
|
||||
Single,
|
||||
/// Frame is interlaced, and this is the first field (with a reference to the second one).
|
||||
First(Weak<RefCell<PictureData>>),
|
||||
/// Frame is interlaced, and this is the second field (with a reference to the first one).
|
||||
Second(Rc<RefCell<PictureData>>),
|
||||
}
|
||||
|
||||
#[derive(Default)]
|
||||
pub struct PictureData {
|
||||
pub pic_order_cnt_type: u8,
|
||||
pub top_field_order_cnt: i32,
|
||||
pub bottom_field_order_cnt: i32,
|
||||
pub pic_order_cnt: i32,
|
||||
pub pic_order_cnt_msb: i32,
|
||||
pub pic_order_cnt_lsb: i32,
|
||||
pub delta_pic_order_cnt_bottom: i32,
|
||||
pub delta_pic_order_cnt0: i32,
|
||||
pub delta_pic_order_cnt1: i32,
|
||||
|
||||
pub pic_num: i32,
|
||||
pub long_term_pic_num: u32,
|
||||
pub frame_num: u32,
|
||||
pub frame_num_offset: u32,
|
||||
pub frame_num_wrap: i32,
|
||||
pub long_term_frame_idx: u32,
|
||||
|
||||
pub coded_resolution: Resolution,
|
||||
pub display_resolution: Resolution,
|
||||
|
||||
pub type_: SliceType,
|
||||
pub nal_ref_idc: u8,
|
||||
pub is_idr: IsIdr,
|
||||
reference: Reference,
|
||||
pub ref_pic_list_modification_flag_l0: i32,
|
||||
pub abs_diff_pic_num_minus1: i32,
|
||||
|
||||
// Does memory management op 5 needs to be executed after this
|
||||
// picture has finished decoding?
|
||||
pub has_mmco_5: bool,
|
||||
|
||||
// Created by the decoding process for gaps in frame_num.
|
||||
// Not for decode or output.
|
||||
pub nonexisting: bool,
|
||||
|
||||
pub field: Field,
|
||||
|
||||
// Values from slice_hdr to be used during reference marking and
|
||||
// memory management after finishing this picture.
|
||||
pub ref_pic_marking: RefPicMarking,
|
||||
|
||||
field_rank: FieldRank,
|
||||
|
||||
pub timestamp: u64,
|
||||
}
|
||||
|
||||
/// A `PictureData` within a `Rc<RefCell>` which field rank is guaranteed to be correct.
|
||||
///
|
||||
/// The field rank of `PictureData` is only final after both fields have been constructed - namely,
|
||||
/// the first field can only point to the second one after the latter is available as a Rc. Methods
|
||||
/// [`PictureData::into_rc`] and [`PictureData::split_frame`] take care of this, and is this only
|
||||
/// producer of this type, ensuring all instances are correct.
|
||||
#[derive(Default, Debug, Clone)]
|
||||
pub struct RcPictureData {
|
||||
pic: Rc<RefCell<PictureData>>,
|
||||
}
|
||||
|
||||
impl Deref for RcPictureData {
|
||||
type Target = Rc<RefCell<PictureData>>;
|
||||
|
||||
fn deref(&self) -> &Self::Target {
|
||||
&self.pic
|
||||
}
|
||||
}
|
||||
|
||||
impl PictureData {
|
||||
pub fn new_non_existing(frame_num: u32, timestamp: u64) -> Self {
|
||||
PictureData {
|
||||
frame_num,
|
||||
nonexisting: true,
|
||||
nal_ref_idc: 1,
|
||||
field: Field::Frame,
|
||||
pic_num: frame_num as i32,
|
||||
reference: Reference::ShortTerm,
|
||||
timestamp,
|
||||
..Default::default()
|
||||
}
|
||||
}
|
||||
|
||||
/// Create a new picture from a `slice`, `sps`, and `timestamp`.
|
||||
///
|
||||
/// `first_field` is set if this picture is the second field of a frame.
|
||||
pub fn new_from_slice(
|
||||
slice: &Slice,
|
||||
sps: &Sps,
|
||||
timestamp: u64,
|
||||
first_field: Option<&RcPictureData>,
|
||||
) -> Self {
|
||||
let hdr = &slice.header;
|
||||
let nalu_hdr = &slice.nalu.header;
|
||||
|
||||
let is_idr = if nalu_hdr.idr_pic_flag {
|
||||
IsIdr::Yes {
|
||||
idr_pic_id: hdr.idr_pic_id,
|
||||
}
|
||||
} else {
|
||||
IsIdr::No
|
||||
};
|
||||
|
||||
let field = if hdr.field_pic_flag {
|
||||
if hdr.bottom_field_flag {
|
||||
Field::Bottom
|
||||
} else {
|
||||
Field::Top
|
||||
}
|
||||
} else {
|
||||
Field::Frame
|
||||
};
|
||||
|
||||
let reference = if nalu_hdr.ref_idc != 0 {
|
||||
Reference::ShortTerm
|
||||
} else {
|
||||
Reference::None
|
||||
};
|
||||
|
||||
let pic_num = if !hdr.field_pic_flag {
|
||||
hdr.frame_num
|
||||
} else {
|
||||
2 * hdr.frame_num + 1
|
||||
};
|
||||
|
||||
let (
|
||||
pic_order_cnt_lsb,
|
||||
delta_pic_order_cnt_bottom,
|
||||
delta_pic_order_cnt0,
|
||||
delta_pic_order_cnt1,
|
||||
) = match sps.pic_order_cnt_type {
|
||||
0 => (
|
||||
hdr.pic_order_cnt_lsb,
|
||||
hdr.delta_pic_order_cnt_bottom,
|
||||
Default::default(),
|
||||
Default::default(),
|
||||
),
|
||||
1 => (
|
||||
Default::default(),
|
||||
Default::default(),
|
||||
hdr.delta_pic_order_cnt[0],
|
||||
hdr.delta_pic_order_cnt[1],
|
||||
),
|
||||
_ => (
|
||||
Default::default(),
|
||||
Default::default(),
|
||||
Default::default(),
|
||||
Default::default(),
|
||||
),
|
||||
};
|
||||
|
||||
let coded_resolution = Resolution::from((sps.width(), sps.height()));
|
||||
|
||||
let visible_rect = sps.visible_rectangle();
|
||||
|
||||
// punktfunk deviation (PROVENANCE.md #6): `Sps::visible_rectangle()` returns
|
||||
// the crop offset in `min` and the visible SIZE in `max` (not a corner);
|
||||
// upstream's `max - min` double-counts the left/top crop and panics on a u32
|
||||
// underflow for large-but-parser-valid left/top offsets.
|
||||
let display_resolution = Resolution {
|
||||
width: visible_rect.max.x,
|
||||
height: visible_rect.max.y,
|
||||
};
|
||||
|
||||
let mut pic = PictureData {
|
||||
pic_order_cnt_type: sps.pic_order_cnt_type,
|
||||
pic_order_cnt_lsb: i32::from(pic_order_cnt_lsb),
|
||||
delta_pic_order_cnt_bottom,
|
||||
delta_pic_order_cnt0,
|
||||
delta_pic_order_cnt1,
|
||||
pic_num: i32::from(pic_num),
|
||||
frame_num: u32::from(hdr.frame_num),
|
||||
nal_ref_idc: nalu_hdr.ref_idc,
|
||||
is_idr,
|
||||
reference,
|
||||
field,
|
||||
ref_pic_marking: hdr.dec_ref_pic_marking.clone(),
|
||||
coded_resolution,
|
||||
display_resolution,
|
||||
timestamp,
|
||||
..Default::default()
|
||||
};
|
||||
|
||||
if let Some(first_field) = first_field {
|
||||
pic.set_first_field_to(first_field);
|
||||
}
|
||||
|
||||
pic
|
||||
}
|
||||
|
||||
/// Whether the current picture is a reference, either ShortTerm or LongTerm.
|
||||
pub fn is_ref(&self) -> bool {
|
||||
!matches!(self.reference, Reference::None)
|
||||
}
|
||||
|
||||
/// Whether this picture is a second field.
|
||||
pub fn is_second_field(&self) -> bool {
|
||||
matches!(self.field_rank, FieldRank::Second(..))
|
||||
}
|
||||
|
||||
/// Returns the field rank of this picture, including a reference to its other field.
|
||||
pub fn field_rank(&self) -> &FieldRank {
|
||||
&self.field_rank
|
||||
}
|
||||
|
||||
/// Returns a reference to the picture's Reference
|
||||
pub fn reference(&self) -> &Reference {
|
||||
&self.reference
|
||||
}
|
||||
|
||||
/// Mark the picture as a reference picture.
|
||||
pub fn set_reference(&mut self, reference: Reference, apply_to_other_field: bool) {
|
||||
log::debug!("Set reference of {:#?} to {:?}", self, reference);
|
||||
self.reference = reference;
|
||||
|
||||
if apply_to_other_field {
|
||||
if let Some(other_field) = self.other_field() {
|
||||
log::debug!(
|
||||
"other_field: Set reference of {:#?} to {:?}",
|
||||
&other_field.borrow(),
|
||||
reference
|
||||
);
|
||||
other_field.borrow_mut().reference = reference;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/// Get a reference to the picture's other field, if there is any
|
||||
/// and its reference is still valid.
|
||||
pub fn other_field(&self) -> Option<Rc<RefCell<PictureData>>> {
|
||||
match &self.field_rank {
|
||||
FieldRank::Single => None,
|
||||
FieldRank::First(other_field) => other_field.upgrade(),
|
||||
FieldRank::Second(other_field) => Some(other_field.clone()),
|
||||
}
|
||||
}
|
||||
|
||||
/// Set this picture's second field.
|
||||
fn set_second_field_to(&mut self, other_field: &Rc<RefCell<Self>>) {
|
||||
self.field_rank = FieldRank::First(Rc::downgrade(other_field));
|
||||
}
|
||||
|
||||
/// Whether the current picture is the second field of a complementary ref pair.
|
||||
pub fn is_second_field_of_complementary_ref_pair(&self) -> bool {
|
||||
self.is_ref()
|
||||
&& matches!(self.field_rank(), FieldRank::Second(first_field) if first_field.borrow().is_ref())
|
||||
}
|
||||
|
||||
/// Set this picture's first field.
|
||||
fn set_first_field_to(&mut self, other_field: &Rc<RefCell<Self>>) {
|
||||
self.field_rank = FieldRank::Second(other_field.clone());
|
||||
}
|
||||
|
||||
pub fn pic_num_f(&self, max_pic_num: i32) -> i32 {
|
||||
if !matches!(self.reference(), Reference::LongTerm) {
|
||||
self.pic_num
|
||||
} else {
|
||||
max_pic_num
|
||||
}
|
||||
}
|
||||
|
||||
pub fn long_term_pic_num_f(&self, max_long_term_frame_idx: MaxLongTermFrameIdx) -> u32 {
|
||||
if matches!(self.reference(), Reference::LongTerm) {
|
||||
self.long_term_pic_num
|
||||
} else {
|
||||
2 * max_long_term_frame_idx.to_value_plus1()
|
||||
}
|
||||
}
|
||||
|
||||
/// Consume this picture and return a Rc'd version.
|
||||
///
|
||||
/// If the picture was a second field, adjust the field of the first field to point to this
|
||||
/// one.
|
||||
pub fn into_rc(self) -> RcPictureData {
|
||||
let self_rc = Rc::new(RefCell::new(self));
|
||||
|
||||
if let FieldRank::Second(first_field) = self_rc.borrow().field_rank() {
|
||||
first_field.borrow_mut().set_second_field_to(&self_rc);
|
||||
}
|
||||
|
||||
RcPictureData { pic: self_rc }
|
||||
}
|
||||
|
||||
/// Split a frame into two complementary fields that reference one another.
|
||||
pub fn split_frame(mut self) -> (RcPictureData, RcPictureData) {
|
||||
assert!(matches!(self.field, Field::Frame));
|
||||
assert!(matches!(self.field_rank, FieldRank::Single));
|
||||
|
||||
debug!(
|
||||
"Splitting picture (frame_num, POC) ({:?}, {:?})",
|
||||
self.frame_num, self.pic_order_cnt
|
||||
);
|
||||
|
||||
let second_pic_order_cnt = if self.top_field_order_cnt < self.bottom_field_order_cnt {
|
||||
self.field = Field::Top;
|
||||
self.pic_order_cnt = self.top_field_order_cnt;
|
||||
|
||||
self.bottom_field_order_cnt
|
||||
} else {
|
||||
self.field = Field::Bottom;
|
||||
self.pic_order_cnt = self.bottom_field_order_cnt;
|
||||
|
||||
self.top_field_order_cnt
|
||||
};
|
||||
|
||||
let second_field = PictureData {
|
||||
top_field_order_cnt: self.top_field_order_cnt,
|
||||
bottom_field_order_cnt: self.bottom_field_order_cnt,
|
||||
frame_num: self.frame_num,
|
||||
reference: self.reference,
|
||||
nonexisting: self.nonexisting,
|
||||
pic_order_cnt: second_pic_order_cnt,
|
||||
field: self.field.opposite(),
|
||||
..Default::default()
|
||||
};
|
||||
|
||||
debug!(
|
||||
"Split into picture (frame_num, POC) ({:?}, {:?}), field: {:?}",
|
||||
self.frame_num, self.pic_order_cnt, self.field
|
||||
);
|
||||
debug!(
|
||||
"Split into picture (frame_num, POC) ({:?}, {:?}), field {:?}",
|
||||
second_field.frame_num, second_field.pic_order_cnt, second_field.field
|
||||
);
|
||||
|
||||
let first_field = Rc::new(RefCell::new(self));
|
||||
let second_field = Rc::new(RefCell::new(second_field));
|
||||
|
||||
first_field.borrow_mut().set_second_field_to(&second_field);
|
||||
second_field.borrow_mut().set_first_field_to(&first_field);
|
||||
|
||||
(
|
||||
RcPictureData { pic: first_field },
|
||||
RcPictureData { pic: second_field },
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
impl std::fmt::Debug for PictureData {
|
||||
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
|
||||
f.debug_struct("PictureData")
|
||||
.field("pic_order_cnt_type", &self.pic_order_cnt_type)
|
||||
.field("top_field_order_cnt", &self.top_field_order_cnt)
|
||||
.field("bottom_field_order_cnt", &self.bottom_field_order_cnt)
|
||||
.field("pic_order_cnt", &self.pic_order_cnt)
|
||||
.field("pic_order_cnt_msb", &self.pic_order_cnt_msb)
|
||||
.field("pic_order_cnt_lsb", &self.pic_order_cnt_lsb)
|
||||
.field(
|
||||
"delta_pic_order_cnt_bottom",
|
||||
&self.delta_pic_order_cnt_bottom,
|
||||
)
|
||||
.field("delta_pic_order_cnt0", &self.delta_pic_order_cnt0)
|
||||
.field("delta_pic_order_cnt1", &self.delta_pic_order_cnt1)
|
||||
.field("pic_num", &self.pic_num)
|
||||
.field("long_term_pic_num", &self.long_term_pic_num)
|
||||
.field("frame_num", &self.frame_num)
|
||||
.field("frame_num_offset", &self.frame_num_offset)
|
||||
.field("frame_num_wrap", &self.frame_num_wrap)
|
||||
.field("long_term_frame_idx", &self.long_term_frame_idx)
|
||||
.field("coded_resolution", &self.coded_resolution)
|
||||
.field("display_resolution", &self.display_resolution)
|
||||
.field("type_", &self.type_)
|
||||
.field("nal_ref_idc", &self.nal_ref_idc)
|
||||
.field("is_idr", &self.is_idr)
|
||||
.field("reference", &self.reference)
|
||||
.field(
|
||||
"ref_pic_list_modification_flag_l0",
|
||||
&self.ref_pic_list_modification_flag_l0,
|
||||
)
|
||||
.field("abs_diff_pic_num_minus1", &self.abs_diff_pic_num_minus1)
|
||||
.field("has_mmco_5", &self.has_mmco_5)
|
||||
.field("nonexisting", &self.nonexisting)
|
||||
.field("field", &self.field)
|
||||
.field("ref_pic_marking", &self.ref_pic_marking)
|
||||
.field("field_rank", &self.field_rank)
|
||||
.finish()
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,593 @@
|
||||
// Copyright 2024 The ChromiumOS Authors
|
||||
// Use of this source code is governed by a BSD-style license that can be
|
||||
// found in the LICENSE file.
|
||||
use std::fmt;
|
||||
use std::io::Write;
|
||||
|
||||
use crate::codec::h264::nalu_writer::NaluWriter;
|
||||
use crate::codec::h264::nalu_writer::NaluWriterError;
|
||||
use crate::codec::h264::parser::HrdParams;
|
||||
use crate::codec::h264::parser::NaluType;
|
||||
use crate::codec::h264::parser::Pps;
|
||||
use crate::codec::h264::parser::Sps;
|
||||
use crate::codec::h264::parser::DEFAULT_4X4_INTER;
|
||||
use crate::codec::h264::parser::DEFAULT_4X4_INTRA;
|
||||
use crate::codec::h264::parser::DEFAULT_8X8_INTER;
|
||||
use crate::codec::h264::parser::DEFAULT_8X8_INTRA;
|
||||
|
||||
mod private {
|
||||
pub trait NaluStruct {}
|
||||
}
|
||||
|
||||
impl private::NaluStruct for Sps {}
|
||||
|
||||
impl private::NaluStruct for Pps {}
|
||||
|
||||
#[derive(Debug)]
|
||||
pub enum SynthesizerError {
|
||||
Unsupported,
|
||||
NaluWriter(NaluWriterError),
|
||||
}
|
||||
|
||||
impl fmt::Display for SynthesizerError {
|
||||
fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
|
||||
match self {
|
||||
SynthesizerError::Unsupported => write!(f, "tried to synthesize unsupported settings"),
|
||||
SynthesizerError::NaluWriter(x) => write!(f, "{}", x.to_string()),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl From<NaluWriterError> for SynthesizerError {
|
||||
fn from(err: NaluWriterError) -> Self {
|
||||
SynthesizerError::NaluWriter(err)
|
||||
}
|
||||
}
|
||||
|
||||
pub type SynthesizerResult<T> = Result<T, SynthesizerError>;
|
||||
|
||||
/// A helper to output typed NALUs to [`std::io::Write`] using [`NaluWriter`].
|
||||
pub struct Synthesizer<'n, N: private::NaluStruct, W: Write> {
|
||||
writer: NaluWriter<W>,
|
||||
nalu: &'n N,
|
||||
}
|
||||
|
||||
/// Extended Sample Aspect Ratio - H.264 Table E-1
|
||||
const EXTENDED_SAR: u8 = 255;
|
||||
|
||||
impl<N: private::NaluStruct, W: Write> Synthesizer<'_, N, W> {
|
||||
fn u<T: Into<u32>>(&mut self, bits: usize, value: T) -> SynthesizerResult<()> {
|
||||
self.writer.write_u(bits, value)?;
|
||||
Ok(())
|
||||
}
|
||||
|
||||
fn f<T: Into<u32>>(&mut self, bits: usize, value: T) -> SynthesizerResult<()> {
|
||||
self.writer.write_f(bits, value)?;
|
||||
Ok(())
|
||||
}
|
||||
|
||||
fn ue<T: Into<u32>>(&mut self, value: T) -> SynthesizerResult<()> {
|
||||
self.writer.write_ue(value)?;
|
||||
Ok(())
|
||||
}
|
||||
|
||||
fn se<T: Into<i32>>(&mut self, value: T) -> SynthesizerResult<()> {
|
||||
self.writer.write_se(value)?;
|
||||
Ok(())
|
||||
}
|
||||
|
||||
fn scaling_list(&mut self, list: &[u8], default: &[u8]) -> SynthesizerResult<()> {
|
||||
// H.264 7.3.2.1.1.1
|
||||
if list == default {
|
||||
self.se(-8)?;
|
||||
return Ok(());
|
||||
}
|
||||
|
||||
// The number of list values we want to encode.
|
||||
let mut run = list.len();
|
||||
|
||||
// Check how many values at the end of the matrix are the same,
|
||||
// so we can save on encoding those.
|
||||
for j in (1..list.len()).rev() {
|
||||
if list[j - 1] != list[j] {
|
||||
break;
|
||||
}
|
||||
run -= 1;
|
||||
}
|
||||
|
||||
// Encode deltas.
|
||||
let mut last_scale = 8;
|
||||
for scale in &list[0..run] {
|
||||
let delta_scale = *scale as i32 - last_scale;
|
||||
self.se(delta_scale)?;
|
||||
last_scale = *scale as i32;
|
||||
}
|
||||
|
||||
// Didn't encode all values, encode -|last_scale| to set decoder's
|
||||
// |next_scale| (H.264 7.3.2.1.1.1) to zero, i.e. decoder should repeat
|
||||
// last values in matrix.
|
||||
if run < list.len() {
|
||||
self.se(-last_scale)?;
|
||||
}
|
||||
|
||||
Ok(())
|
||||
}
|
||||
|
||||
fn default_scaling_list(i: usize) -> &'static [u8] {
|
||||
// H.264 Table 7-2
|
||||
match i {
|
||||
0 => &DEFAULT_4X4_INTRA[..],
|
||||
1 => &DEFAULT_4X4_INTRA[..],
|
||||
2 => &DEFAULT_4X4_INTRA[..],
|
||||
3 => &DEFAULT_4X4_INTER[..],
|
||||
4 => &DEFAULT_4X4_INTER[..],
|
||||
5 => &DEFAULT_4X4_INTER[..],
|
||||
6 => &DEFAULT_8X8_INTRA[..],
|
||||
7 => &DEFAULT_8X8_INTER[..],
|
||||
8 => &DEFAULT_8X8_INTRA[..],
|
||||
9 => &DEFAULT_8X8_INTER[..],
|
||||
10 => &DEFAULT_8X8_INTRA[..],
|
||||
11 => &DEFAULT_8X8_INTER[..],
|
||||
_ => unreachable!(),
|
||||
}
|
||||
}
|
||||
|
||||
fn rbsp_trailing_bits(&mut self) -> SynthesizerResult<()> {
|
||||
self.f(1, 1u32)?;
|
||||
|
||||
while !self.writer.aligned() {
|
||||
self.f(1, 0u32)?;
|
||||
}
|
||||
|
||||
Ok(())
|
||||
}
|
||||
}
|
||||
|
||||
impl<'n, W: Write> Synthesizer<'n, Sps, W> {
|
||||
pub fn synthesize(
|
||||
ref_idc: u8,
|
||||
sps: &'n Sps,
|
||||
writer: W,
|
||||
ep_enabled: bool,
|
||||
) -> SynthesizerResult<()> {
|
||||
let mut s = Self {
|
||||
writer: NaluWriter::<W>::new(writer, ep_enabled),
|
||||
nalu: sps,
|
||||
};
|
||||
|
||||
s.writer.write_header(ref_idc, NaluType::Sps as u8)?;
|
||||
s.seq_parameter_set_data()?;
|
||||
s.rbsp_trailing_bits()
|
||||
}
|
||||
|
||||
fn hrd_parameters(&mut self, hrd_params: &HrdParams) -> SynthesizerResult<()> {
|
||||
self.ue(hrd_params.cpb_cnt_minus1)?;
|
||||
self.u(4, hrd_params.bit_rate_scale)?;
|
||||
self.u(4, hrd_params.cpb_size_scale)?;
|
||||
|
||||
for i in 0..=(hrd_params.cpb_cnt_minus1 as usize) {
|
||||
self.ue(hrd_params.bit_rate_value_minus1[i])?;
|
||||
self.ue(hrd_params.cpb_size_value_minus1[i])?;
|
||||
self.u(1, hrd_params.cbr_flag[i])?;
|
||||
}
|
||||
|
||||
self.u(5, hrd_params.initial_cpb_removal_delay_length_minus1)?;
|
||||
self.u(5, hrd_params.cpb_removal_delay_length_minus1)?;
|
||||
self.u(5, hrd_params.dpb_output_delay_length_minus1)?;
|
||||
self.u(5, hrd_params.time_offset_length)?;
|
||||
|
||||
Ok(())
|
||||
}
|
||||
|
||||
fn vui_parameters(&mut self) -> SynthesizerResult<()> {
|
||||
// H.264 E.1.1
|
||||
let vui_params = &self.nalu.vui_parameters;
|
||||
|
||||
self.u(1, vui_params.aspect_ratio_info_present_flag)?;
|
||||
if vui_params.aspect_ratio_info_present_flag {
|
||||
self.u(8, vui_params.aspect_ratio_idc)?;
|
||||
if vui_params.aspect_ratio_idc == EXTENDED_SAR {
|
||||
self.u(16, vui_params.sar_width)?;
|
||||
self.u(16, vui_params.sar_height)?;
|
||||
}
|
||||
}
|
||||
|
||||
self.u(1, vui_params.overscan_info_present_flag)?;
|
||||
if vui_params.overscan_info_present_flag {
|
||||
self.u(1, vui_params.overscan_appropriate_flag)?;
|
||||
}
|
||||
|
||||
self.u(1, vui_params.video_signal_type_present_flag)?;
|
||||
if vui_params.video_signal_type_present_flag {
|
||||
self.u(3, vui_params.video_format)?;
|
||||
self.u(1, vui_params.video_full_range_flag)?;
|
||||
|
||||
self.u(1, vui_params.colour_description_present_flag)?;
|
||||
if vui_params.colour_description_present_flag {
|
||||
self.u(8, vui_params.colour_primaries)?;
|
||||
self.u(8, vui_params.transfer_characteristics)?;
|
||||
self.u(8, vui_params.matrix_coefficients)?;
|
||||
}
|
||||
}
|
||||
|
||||
self.u(1, vui_params.chroma_loc_info_present_flag)?;
|
||||
if vui_params.chroma_loc_info_present_flag {
|
||||
self.ue(vui_params.chroma_sample_loc_type_top_field)?;
|
||||
self.ue(self.nalu.vui_parameters.chroma_sample_loc_type_bottom_field)?;
|
||||
}
|
||||
|
||||
self.u(1, vui_params.timing_info_present_flag)?;
|
||||
if vui_params.timing_info_present_flag {
|
||||
self.u(32, vui_params.num_units_in_tick)?;
|
||||
self.u(32, vui_params.time_scale)?;
|
||||
self.u(1, vui_params.fixed_frame_rate_flag)?;
|
||||
}
|
||||
|
||||
self.u(1, vui_params.nal_hrd_parameters_present_flag)?;
|
||||
if vui_params.nal_hrd_parameters_present_flag {
|
||||
self.hrd_parameters(&vui_params.nal_hrd_parameters)?;
|
||||
}
|
||||
self.u(1, vui_params.vcl_hrd_parameters_present_flag)?;
|
||||
if vui_params.vcl_hrd_parameters_present_flag {
|
||||
self.hrd_parameters(&vui_params.vcl_hrd_parameters)?;
|
||||
}
|
||||
|
||||
if vui_params.nal_hrd_parameters_present_flag || vui_params.vcl_hrd_parameters_present_flag
|
||||
{
|
||||
self.u(1, vui_params.low_delay_hrd_flag)?;
|
||||
}
|
||||
|
||||
self.u(1, vui_params.pic_struct_present_flag)?;
|
||||
|
||||
self.u(1, vui_params.bitstream_restriction_flag)?;
|
||||
if vui_params.bitstream_restriction_flag {
|
||||
self.u(1, vui_params.motion_vectors_over_pic_boundaries_flag)?;
|
||||
self.ue(vui_params.max_bytes_per_pic_denom)?;
|
||||
self.ue(vui_params.max_bits_per_mb_denom)?;
|
||||
self.ue(vui_params.log2_max_mv_length_horizontal)?;
|
||||
self.ue(vui_params.log2_max_mv_length_vertical)?;
|
||||
self.ue(vui_params.max_num_reorder_frames)?;
|
||||
self.ue(vui_params.max_dec_frame_buffering)?;
|
||||
}
|
||||
|
||||
Ok(())
|
||||
}
|
||||
|
||||
fn seq_parameter_set_data(&mut self) -> SynthesizerResult<()> {
|
||||
// H.264 7.3.2.1.1
|
||||
self.u(8, self.nalu.profile_idc)?;
|
||||
self.u(1, self.nalu.constraint_set0_flag)?;
|
||||
self.u(1, self.nalu.constraint_set1_flag)?;
|
||||
self.u(1, self.nalu.constraint_set2_flag)?;
|
||||
self.u(1, self.nalu.constraint_set3_flag)?;
|
||||
self.u(1, self.nalu.constraint_set4_flag)?;
|
||||
self.u(1, self.nalu.constraint_set5_flag)?;
|
||||
self.u(2, /* reserved_zero_2bits */ 0u32)?;
|
||||
self.u(8, self.nalu.level_idc as u32)?;
|
||||
self.ue(self.nalu.seq_parameter_set_id)?;
|
||||
|
||||
if self.nalu.profile_idc == 100
|
||||
|| self.nalu.profile_idc == 110
|
||||
|| self.nalu.profile_idc == 122
|
||||
|| self.nalu.profile_idc == 244
|
||||
|| self.nalu.profile_idc == 44
|
||||
|| self.nalu.profile_idc == 83
|
||||
|| self.nalu.profile_idc == 86
|
||||
|| self.nalu.profile_idc == 118
|
||||
|| self.nalu.profile_idc == 128
|
||||
|| self.nalu.profile_idc == 138
|
||||
|| self.nalu.profile_idc == 139
|
||||
|| self.nalu.profile_idc == 134
|
||||
|| self.nalu.profile_idc == 135
|
||||
{
|
||||
self.ue(self.nalu.chroma_format_idc)?;
|
||||
|
||||
if self.nalu.chroma_format_idc == 3 {
|
||||
self.u(1, self.nalu.separate_colour_plane_flag)?;
|
||||
}
|
||||
|
||||
self.ue(self.nalu.bit_depth_luma_minus8)?;
|
||||
self.ue(self.nalu.bit_depth_chroma_minus8)?;
|
||||
self.u(1, self.nalu.qpprime_y_zero_transform_bypass_flag)?;
|
||||
self.u(1, self.nalu.seq_scaling_matrix_present_flag)?;
|
||||
|
||||
if self.nalu.seq_scaling_matrix_present_flag {
|
||||
let scaling_list_count = if self.nalu.chroma_format_idc != 3 {
|
||||
8
|
||||
} else {
|
||||
12
|
||||
};
|
||||
|
||||
for i in 0..scaling_list_count {
|
||||
// Assume if scaling lists are zeroed that they are not present.
|
||||
if i < 6 {
|
||||
if self.nalu.scaling_lists_4x4[i] == [0; 16] {
|
||||
self.u(1, /* seq_scaling_list_present_flag */ false)?;
|
||||
} else {
|
||||
self.u(1, /* seq_scaling_list_present_flag */ true)?;
|
||||
self.scaling_list(
|
||||
&self.nalu.scaling_lists_4x4[i],
|
||||
Self::default_scaling_list(i),
|
||||
)?;
|
||||
}
|
||||
} else if self.nalu.scaling_lists_8x8[i - 6] == [0; 64] {
|
||||
self.u(1, /* seq_scaling_list_present_flag */ false)?;
|
||||
} else {
|
||||
self.u(1, /* seq_scaling_list_present_flag */ true)?;
|
||||
self.scaling_list(
|
||||
&self.nalu.scaling_lists_8x8[i - 6],
|
||||
Self::default_scaling_list(i),
|
||||
)?;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
self.ue(self.nalu.log2_max_frame_num_minus4)?;
|
||||
self.ue(self.nalu.pic_order_cnt_type)?;
|
||||
|
||||
if self.nalu.pic_order_cnt_type == 0 {
|
||||
self.ue(self.nalu.log2_max_pic_order_cnt_lsb_minus4)?;
|
||||
} else if self.nalu.pic_order_cnt_type == 1 {
|
||||
self.u(1, self.nalu.delta_pic_order_always_zero_flag)?;
|
||||
self.se(self.nalu.offset_for_non_ref_pic)?;
|
||||
self.se(self.nalu.offset_for_top_to_bottom_field)?;
|
||||
self.ue(self.nalu.num_ref_frames_in_pic_order_cnt_cycle)?;
|
||||
|
||||
for offset_for_ref_frame in &self.nalu.offset_for_ref_frame {
|
||||
self.se(*offset_for_ref_frame)?;
|
||||
}
|
||||
}
|
||||
|
||||
self.ue(self.nalu.max_num_ref_frames)?;
|
||||
self.u(1, self.nalu.gaps_in_frame_num_value_allowed_flag)?;
|
||||
self.ue(self.nalu.pic_width_in_mbs_minus1)?;
|
||||
self.ue(self.nalu.pic_height_in_map_units_minus1)?;
|
||||
self.u(1, self.nalu.frame_mbs_only_flag)?;
|
||||
if !self.nalu.frame_mbs_only_flag {
|
||||
self.u(1, self.nalu.mb_adaptive_frame_field_flag)?;
|
||||
}
|
||||
self.u(1, self.nalu.direct_8x8_inference_flag)?;
|
||||
|
||||
self.u(1, self.nalu.frame_cropping_flag)?;
|
||||
if self.nalu.frame_cropping_flag {
|
||||
self.ue(self.nalu.frame_crop_left_offset)?;
|
||||
self.ue(self.nalu.frame_crop_right_offset)?;
|
||||
self.ue(self.nalu.frame_crop_top_offset)?;
|
||||
self.ue(self.nalu.frame_crop_bottom_offset)?;
|
||||
}
|
||||
|
||||
self.u(1, self.nalu.vui_parameters_present_flag)?;
|
||||
if self.nalu.vui_parameters_present_flag {
|
||||
self.vui_parameters()?;
|
||||
}
|
||||
|
||||
Ok(())
|
||||
}
|
||||
}
|
||||
|
||||
impl<'n, W: Write> Synthesizer<'n, Pps, W> {
|
||||
pub fn synthesize(
|
||||
ref_idc: u8,
|
||||
pps: &'n Pps,
|
||||
writer: W,
|
||||
ep_enabled: bool,
|
||||
) -> SynthesizerResult<()> {
|
||||
let mut s = Self {
|
||||
writer: NaluWriter::<W>::new(writer, ep_enabled),
|
||||
nalu: pps,
|
||||
};
|
||||
|
||||
s.writer.write_header(ref_idc, NaluType::Pps as u8)?;
|
||||
s.pic_parameter_set_rbsp()?;
|
||||
s.rbsp_trailing_bits()
|
||||
}
|
||||
|
||||
fn pic_parameter_set_rbsp(&mut self) -> SynthesizerResult<()> {
|
||||
self.ue(self.nalu.pic_parameter_set_id)?;
|
||||
self.ue(self.nalu.seq_parameter_set_id)?;
|
||||
self.u(1, self.nalu.entropy_coding_mode_flag)?;
|
||||
self.u(1, self.nalu.bottom_field_pic_order_in_frame_present_flag)?;
|
||||
|
||||
self.ue(self.nalu.num_slice_groups_minus1)?;
|
||||
if self.nalu.num_slice_groups_minus1 > 0 {
|
||||
return Err(SynthesizerError::Unsupported);
|
||||
}
|
||||
|
||||
self.ue(self.nalu.num_ref_idx_l0_default_active_minus1)?;
|
||||
self.ue(self.nalu.num_ref_idx_l1_default_active_minus1)?;
|
||||
self.u(1, self.nalu.weighted_pred_flag)?;
|
||||
self.u(2, self.nalu.weighted_bipred_idc)?;
|
||||
self.se(self.nalu.pic_init_qp_minus26)?;
|
||||
self.se(self.nalu.pic_init_qs_minus26)?;
|
||||
self.se(self.nalu.chroma_qp_index_offset)?;
|
||||
self.u(1, self.nalu.deblocking_filter_control_present_flag)?;
|
||||
self.u(1, self.nalu.constrained_intra_pred_flag)?;
|
||||
self.u(1, self.nalu.redundant_pic_cnt_present_flag)?;
|
||||
|
||||
if !(self.nalu.transform_8x8_mode_flag
|
||||
|| self.nalu.pic_scaling_matrix_present_flag
|
||||
|| self.nalu.second_chroma_qp_index_offset != 0)
|
||||
{
|
||||
return Ok(());
|
||||
}
|
||||
|
||||
self.u(1, self.nalu.transform_8x8_mode_flag)?;
|
||||
self.u(1, self.nalu.pic_scaling_matrix_present_flag)?;
|
||||
|
||||
if self.nalu.pic_scaling_matrix_present_flag {
|
||||
let mut scaling_list_count = 6;
|
||||
if self.nalu.transform_8x8_mode_flag {
|
||||
if self.nalu.sps.chroma_format_idc != 3 {
|
||||
scaling_list_count += 2;
|
||||
} else {
|
||||
scaling_list_count += 6;
|
||||
}
|
||||
}
|
||||
|
||||
for i in 0..scaling_list_count {
|
||||
// Assume if scaling lists are zeroed that they are not present.
|
||||
if i < 6 {
|
||||
if self.nalu.scaling_lists_4x4[i] == [0; 16] {
|
||||
self.u(1, /* seq_scaling_list_present_flag */ false)?;
|
||||
} else {
|
||||
self.u(1, /* seq_scaling_list_present_flag */ true)?;
|
||||
self.scaling_list(
|
||||
&self.nalu.scaling_lists_4x4[i],
|
||||
Self::default_scaling_list(i),
|
||||
)?;
|
||||
}
|
||||
} else if self.nalu.scaling_lists_8x8[i - 6] == [0; 64] {
|
||||
self.u(1, /* seq_scaling_list_present_flag */ false)?;
|
||||
} else {
|
||||
self.u(1, /* seq_scaling_list_present_flag */ true)?;
|
||||
self.scaling_list(
|
||||
&self.nalu.scaling_lists_8x8[i - 6],
|
||||
Self::default_scaling_list(i),
|
||||
)?;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
self.se(self.nalu.second_chroma_qp_index_offset)?;
|
||||
|
||||
Ok(())
|
||||
}
|
||||
}
|
||||
|
||||
#[cfg(test)]
|
||||
mod tests {
|
||||
use std::io::Cursor;
|
||||
|
||||
use super::*;
|
||||
use crate::codec::h264::parser::Nalu;
|
||||
use crate::codec::h264::parser::NaluType;
|
||||
use crate::codec::h264::parser::Parser;
|
||||
use crate::codec::h264::parser::Profile;
|
||||
|
||||
#[test]
|
||||
fn synthesize_sps() {
|
||||
let raw_sps_buf = [0x00, 0x00, 0x00, 0x01, 0x07, 0x00, 0x00, 0x0a, 0xfb, 0x88];
|
||||
let mut raw_sps = Cursor::new(&raw_sps_buf[..]);
|
||||
|
||||
let nalu = Nalu::next(&mut raw_sps).unwrap();
|
||||
assert_eq!(nalu.header.type_, NaluType::Sps);
|
||||
|
||||
let mut parser = Parser::default();
|
||||
let sps = parser.parse_sps(&nalu).unwrap();
|
||||
|
||||
let mut buf = Vec::<u8>::new();
|
||||
Synthesizer::<'_, Sps, _>::synthesize(0, sps, &mut buf, false).unwrap();
|
||||
|
||||
assert_eq!(buf, raw_sps_buf);
|
||||
|
||||
let write_to_file = std::option_env!("CROS_CODECS_TEST_WRITE_TO_FILE") == Some("true");
|
||||
if write_to_file {
|
||||
let mut out = std::fs::File::create("sps.h264").unwrap();
|
||||
out.write_all(&buf).unwrap();
|
||||
out.flush().unwrap();
|
||||
}
|
||||
|
||||
let mut cursor = Cursor::new(&buf[..]);
|
||||
let nalu = Nalu::next(&mut cursor).unwrap();
|
||||
|
||||
let mut parser = Parser::default();
|
||||
|
||||
let sps2 = parser.parse_sps(&nalu).unwrap();
|
||||
|
||||
assert_eq!(sps, sps2);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn synthesize_sps_scaling_lists() {
|
||||
let sps = Sps {
|
||||
profile_idc: Profile::High as u8,
|
||||
seq_scaling_matrix_present_flag: true,
|
||||
scaling_lists_4x4: [[
|
||||
11, 20, 10, 20, 10, 22, 10, 20, 10, 20, 13, 20, 10, 20, 10, 24,
|
||||
]; 6],
|
||||
scaling_lists_8x8: [
|
||||
[
|
||||
33, 20, 10, 21, 33, 20, 12, 20, 33, 23, 10, 20, 33, 20, 10, 20, 33, 24, 10, 20,
|
||||
33, 20, 15, 20, 33, 20, 10, 26, 33, 20, 17, 20, 33, 28, 10, 20, 33, 20, 10, 20,
|
||||
33, 29, 10, 20, 33, 20, 11, 20, 33, 20, 10, 20, 33, 20, 10, 20, 33, 20, 10, 20,
|
||||
33, 20, 10, 20,
|
||||
],
|
||||
[
|
||||
10, 77, 11, 20, 10, 77, 12, 20, 10, 77, 13, 20, 10, 77, 14, 20, 10, 77, 15, 20,
|
||||
10, 77, 16, 20, 10, 77, 17, 20, 10, 77, 18, 20, 10, 77, 19, 20, 10, 77, 10, 20,
|
||||
10, 77, 10, 21, 10, 77, 10, 22, 10, 77, 10, 23, 10, 77, 10, 24, 10, 77, 10, 26,
|
||||
10, 77, 10, 28,
|
||||
],
|
||||
[0; 64],
|
||||
[0; 64],
|
||||
[0; 64],
|
||||
[0; 64],
|
||||
],
|
||||
frame_mbs_only_flag: true,
|
||||
..Default::default()
|
||||
};
|
||||
|
||||
let mut buf = Vec::<u8>::new();
|
||||
Synthesizer::<'_, Sps, _>::synthesize(0, &sps, &mut buf, false).unwrap();
|
||||
|
||||
let write_to_file = std::option_env!("CROS_CODECS_TEST_WRITE_TO_FILE") == Some("true");
|
||||
if write_to_file {
|
||||
let mut out = std::fs::File::create("sps.h264").unwrap();
|
||||
out.write_all(&buf).unwrap();
|
||||
out.flush().unwrap();
|
||||
}
|
||||
|
||||
let mut cursor = Cursor::new(&buf[..]);
|
||||
let nalu = Nalu::next(&mut cursor).unwrap();
|
||||
|
||||
let mut parser = Parser::default();
|
||||
|
||||
let sps2 = parser.parse_sps(&nalu).unwrap();
|
||||
|
||||
assert_eq!(sps.scaling_lists_4x4, sps2.scaling_lists_4x4);
|
||||
assert_eq!(sps.scaling_lists_8x8, sps2.scaling_lists_8x8);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn synthesize_pps() {
|
||||
let raw_sps_pps = [
|
||||
0x00, 0x00, 0x00, 0x01, 0x07, 0x4d, 0x40, 0x0d, 0xa9, 0x18, 0x28, 0x3e, 0x60, 0x0d,
|
||||
0x41, 0x80, 0x41, 0xad, 0xb0, 0xad, 0x7b, 0xdf, 0x01, 0x00, 0x00, 0x00, 0x01, 0x08,
|
||||
0xde, 0x09, 0x88,
|
||||
];
|
||||
|
||||
let mut buf = Vec::<u8>::new();
|
||||
let mut out = Cursor::new(&mut buf);
|
||||
|
||||
let mut cursor = Cursor::new(&raw_sps_pps[..]);
|
||||
let mut parser: Parser = Default::default();
|
||||
|
||||
while let Ok(nalu) = Nalu::next(&mut cursor) {
|
||||
match nalu.header.type_ {
|
||||
NaluType::Sps => {
|
||||
let sps = parser.parse_sps(&nalu).unwrap();
|
||||
Synthesizer::<'_, Sps, _>::synthesize(0, sps, &mut out, false).unwrap();
|
||||
}
|
||||
NaluType::Pps => {
|
||||
let pps = parser.parse_pps(&nalu).unwrap();
|
||||
Synthesizer::<'_, Pps, _>::synthesize(0, pps, &mut out, false).unwrap();
|
||||
}
|
||||
_ => panic!(),
|
||||
}
|
||||
}
|
||||
|
||||
let write_to_file = std::option_env!("CROS_CODECS_TEST_WRITE_TO_FILE") == Some("true");
|
||||
if write_to_file {
|
||||
let mut out = std::fs::File::create("sps_pps.h264").unwrap();
|
||||
out.write_all(&buf).unwrap();
|
||||
out.flush().unwrap();
|
||||
|
||||
let mut out = std::fs::File::create("sps_pps_ref.h264").unwrap();
|
||||
out.write_all(&raw_sps_pps).unwrap();
|
||||
out.flush().unwrap();
|
||||
}
|
||||
|
||||
assert_eq!(buf, raw_sps_pps);
|
||||
}
|
||||
}
|
||||
BIN
Binary file not shown.
Vendored
+3
@@ -0,0 +1,3 @@
|
||||
43656b2f
|
||||
c9dd1361
|
||||
62d34555
|
||||
Vendored
+3
@@ -0,0 +1,3 @@
|
||||
45ba0c1a27d0ff82fd7a969631adffe6
|
||||
cee875ded4998c9810a14f9497a11f72
|
||||
d1e0b9347134ba7a07cbcf249066f022
|
||||
BIN
Binary file not shown.
+3
@@ -0,0 +1,3 @@
|
||||
ee936370
|
||||
0e5e577c
|
||||
bfe430af
|
||||
+3
@@ -0,0 +1,3 @@
|
||||
fe6701d54768bc37c76e630435e8fe02
|
||||
3298d47365ac1f9dc0942ece9e104246
|
||||
7cb9209603f53ba995f7eee20b2bbf4b
|
||||
BIN
Binary file not shown.
+2
@@ -0,0 +1,2 @@
|
||||
9fc67012
|
||||
7f0b441e
|
||||
+2
@@ -0,0 +1,2 @@
|
||||
115a8e6899b71c04e32a0254ba62b30a
|
||||
1fa3f5a930e08943e6d6bfbd5d43004e
|
||||
Binary file not shown.
+1
@@ -0,0 +1 @@
|
||||
7dd66ef1
|
||||
+1
@@ -0,0 +1 @@
|
||||
d2304abbf0349ec63324741bf723960d
|
||||
@@ -0,0 +1,70 @@
|
||||
# H.264 Test Data
|
||||
|
||||
This document lists the test data used by the H.264 decoder.
|
||||
|
||||
Unless otherwise noted, the CRCs were computed using GStreamer's VA-API decoder in
|
||||
`gst-plugins-bad`.
|
||||
|
||||
## 16x16-I.h264
|
||||
|
||||
A 16x16 progressive byte-stream encoded I-frame to make it easier to spot errors on the libva trace.
|
||||
Encoded with the following GStreamer pipeline:
|
||||
|
||||
```
|
||||
gst-launch-1.0 videotestsrc num-buffers=1 ! video/x-raw,format=I420,width=16,height=16 ! \
|
||||
x264enc ! video/x-h264,profile=constrained-baseline,stream-format=byte-stream ! \
|
||||
filesink location="/tmp/16x16-I.h264"
|
||||
```
|
||||
|
||||
## 16x16-I-P.h264
|
||||
|
||||
A 16x16 progressive byte-stream encoded I-frame and P-frame to make it easier to spot errors on the
|
||||
libva trace. Encoded with the following GStreamer pipeline:
|
||||
|
||||
```
|
||||
gst-launch-1.0 videotestsrc num-buffers=2 ! video/x-raw,format=I420,width=16,height=16 ! \
|
||||
x264enc b-adapt=false ! video/x-h264,profile=constrained-baseline,stream-format=byte-stream ! \
|
||||
filesink location="/tmp/16x16-I-P.h264"
|
||||
```
|
||||
|
||||
## 16x16-I-P-B-P.h264
|
||||
|
||||
A 16x16 progressive byte-stream encoded I-P-B-P sequence to make it easier to it easier to spot
|
||||
errors on the libva trace. Encoded with the following GStreamer pipeline:
|
||||
|
||||
```
|
||||
gst-launch-1.0 videotestsrc num-buffers=3 ! video/x-raw,format=I420,width=16,height=16 ! \
|
||||
x264enc b-adapt=false bframes=1 ! video/x-h264,profile=constrained-baseline,stream-format=byte-stream ! \
|
||||
filesink location="/tmp/16x16-I-B-P.h264"
|
||||
```
|
||||
|
||||
## 16x16-I-P-B-P-high.h264
|
||||
|
||||
A 16x16 progressive byte-stream encoded I-P-B-P sequence to make it easier to it easier to spot
|
||||
errors on the libva trace. Also tests whether the decoder supports the high profile. Encoded with
|
||||
the following GStreamer pipeline:
|
||||
|
||||
```
|
||||
gst-launch-1.0 videotestsrc num-buffers=3 ! video/x-raw,format=I420,width=16,height=16 ! \
|
||||
x264enc b-adapt=false bframes=1 ! video/x-h264,profile=high,stream-format=byte-stream ! \
|
||||
filesink location="/tmp/16x16-I-B-P-high.h264"
|
||||
```
|
||||
|
||||
## test-25fps.h264
|
||||
|
||||
Same as Chromium's `test-25fps.h264`. The slice data in `test-25fps-h264-slice-data-*.bin` was
|
||||
manually extracted from GStreamer using GDB.
|
||||
|
||||
## test-25fps-interlaced.h264
|
||||
|
||||
Adapted from Chromium's `test-25fps.h264`. Same file as above, but encoded as interlaced instead
|
||||
using the following ffmpeg command:
|
||||
|
||||
```
|
||||
ffmpeg -i \
|
||||
src/third_party/blink/web_tests/media/content/test-25fps.mp4 \
|
||||
-flags +ilme+ildct -vbsf h264_mp4toannexb -an test-25fps.h264
|
||||
```
|
||||
|
||||
This test makes sure that the interlaced logic in the decoder actually works, specially that "frame
|
||||
splitting" works, as the fields here were encoded as frames.
|
||||
+8
@@ -0,0 +1,8 @@
|
||||
#!/bin/bash
|
||||
|
||||
# Generates the CRCs for all .h264 files in the current directory using ffmpeg.
|
||||
|
||||
for f in `ls *.h264`; do
|
||||
ffmpeg -i $f -pix_fmt nv12 -f framehash -hash crc32 - |grep -v '^#' |awk '{print $6}' >$f.crc
|
||||
ffmpeg -i $f -pix_fmt nv12 -f framehash -hash md5 - |grep -v '^#' |awk '{print $6}' >$f.md5
|
||||
done
|
||||
Vendored
BIN
Binary file not shown.
Vendored
BIN
Binary file not shown.
Vendored
+1
@@ -0,0 +1 @@
|
||||
ЁЃ`_яяяяяяяяя_щЏ°`Џ}Ў��ЇяяяѓЌ%ђ\jЭaБfїЪwц”[bњѓћУJAПяь1ъШ?‹'ю4€Т_®3Iџ?Р`Wqaц– �6ЙFzzZшХ+ЯяГ0шI`‡Э–2рЛЄ}†-“~‘ґшн‚_яьC 88lи5фУг¶в0ЬшkЋЂ
|
||||
BIN
Binary file not shown.
Vendored
+250
@@ -0,0 +1,250 @@
|
||||
8e0a948b
|
||||
0d25f469
|
||||
612a0a5c
|
||||
0a452520
|
||||
6c83e97b
|
||||
d1598a6e
|
||||
26892325
|
||||
e7eb5f61
|
||||
e527446a
|
||||
f85a9d52
|
||||
16bfd3c5
|
||||
e837620a
|
||||
38484895
|
||||
050d070f
|
||||
b30cfcd9
|
||||
bec9e023
|
||||
9b272332
|
||||
f6a542ba
|
||||
bf978015
|
||||
1767b90a
|
||||
f3755595
|
||||
5ae99ff9
|
||||
5b8b86a6
|
||||
44cd3b51
|
||||
d99e1fab
|
||||
17761096
|
||||
271806c4
|
||||
b0d17f38
|
||||
01f033d7
|
||||
6ae775c1
|
||||
42933e3b
|
||||
11f61f13
|
||||
4ecab452
|
||||
8e50b509
|
||||
dfde6c00
|
||||
06703f5c
|
||||
92a02866
|
||||
c6f3a13b
|
||||
4371bed0
|
||||
5dd9e344
|
||||
cfd668a3
|
||||
abfc6c45
|
||||
ffbc45ac
|
||||
be1cbdf9
|
||||
a6dd2c68
|
||||
fc838af2
|
||||
d8f88c05
|
||||
e377a83d
|
||||
8acea967
|
||||
cfcae361
|
||||
47ec0343
|
||||
c5af87e3
|
||||
a4c1d94e
|
||||
080ca745
|
||||
74f48838
|
||||
00480284
|
||||
f2da9c1d
|
||||
8bb000aa
|
||||
32857438
|
||||
e7fcb9ae
|
||||
9bbb834f
|
||||
3889cf50
|
||||
a3ec330c
|
||||
6963e8b3
|
||||
8de3e2f3
|
||||
8e1d1dc1
|
||||
242fd47d
|
||||
e11cc789
|
||||
9e558667
|
||||
2ff16820
|
||||
d75fad55
|
||||
5f4a5907
|
||||
83182bc7
|
||||
450343a5
|
||||
dad31a6d
|
||||
bb365f64
|
||||
cdd2d57b
|
||||
99c0e687
|
||||
52eecf8a
|
||||
951cd566
|
||||
f29f0fef
|
||||
d1165eae
|
||||
0df626f8
|
||||
6fbdfc15
|
||||
7f5180af
|
||||
c1f6a321
|
||||
7d9b9418
|
||||
a0f25570
|
||||
c5af5562
|
||||
acc18caa
|
||||
0d3d93a1
|
||||
cce8f670
|
||||
7804b84a
|
||||
ed7999c0
|
||||
38dbf871
|
||||
09143151
|
||||
866c7c00
|
||||
c782f291
|
||||
65605fc3
|
||||
8f37e317
|
||||
aaf653bf
|
||||
2894b605
|
||||
7eeba8f6
|
||||
cad6e5a6
|
||||
bce25e79
|
||||
cffbc84e
|
||||
c29dc1b4
|
||||
99ac23e8
|
||||
7805efed
|
||||
78623121
|
||||
88767543
|
||||
a5e2df6f
|
||||
555355b0
|
||||
4e646cd0
|
||||
1fa3064f
|
||||
dc4f65f4
|
||||
77c52338
|
||||
41287dca
|
||||
0ea69260
|
||||
2fb243a8
|
||||
3de20c11
|
||||
ad7fd2c0
|
||||
79f73884
|
||||
cf89a598
|
||||
301e0a62
|
||||
3e26fed7
|
||||
8e8dacbe
|
||||
628cfc2b
|
||||
5c7fe45f
|
||||
06892492
|
||||
fb7d8b50
|
||||
ddf98de9
|
||||
5fb4c485
|
||||
501ccf38
|
||||
e5f40baf
|
||||
07ab7574
|
||||
8596934d
|
||||
f7066878
|
||||
27e166e6
|
||||
10a37320
|
||||
39dc9664
|
||||
ca9e1642
|
||||
5d23f171
|
||||
dafd915b
|
||||
ddcbc72c
|
||||
56b4b83d
|
||||
3bbde005
|
||||
41ed4060
|
||||
68caa834
|
||||
957b84b9
|
||||
5f6bbbfc
|
||||
98e64dae
|
||||
ebd05871
|
||||
c851d8e9
|
||||
f73fbbe4
|
||||
7451ac0b
|
||||
cbfd7a78
|
||||
901fe589
|
||||
9ed86bd9
|
||||
1e9fef87
|
||||
faca6981
|
||||
98b7bd7a
|
||||
e1d4bdf1
|
||||
b6a95dee
|
||||
3955fb57
|
||||
90d28016
|
||||
64472746
|
||||
5f2b76df
|
||||
478197b2
|
||||
c59fda8c
|
||||
169c2bfe
|
||||
74210735
|
||||
94662d44
|
||||
f16e4ef6
|
||||
f2134ee3
|
||||
f881db32
|
||||
0d927db1
|
||||
ab77556a
|
||||
12a65d29
|
||||
632965b4
|
||||
807d10f7
|
||||
339e5f7e
|
||||
be2c9336
|
||||
bc593f8b
|
||||
c9dfe52d
|
||||
fc738a40
|
||||
17699e28
|
||||
e8be4231
|
||||
b4264279
|
||||
895a00b1
|
||||
41eb9726
|
||||
e804873a
|
||||
c6a4b014
|
||||
bdc91323
|
||||
14b6934d
|
||||
dd31a422
|
||||
0d379528
|
||||
4e4b45cd
|
||||
9f6773f8
|
||||
326f3f46
|
||||
6180b23d
|
||||
61329916
|
||||
01a1ecf3
|
||||
8b77abcf
|
||||
4508213e
|
||||
dfc43ab1
|
||||
0937305a
|
||||
a9d22bba
|
||||
541a1ba4
|
||||
5eb3490a
|
||||
4702020a
|
||||
190299ca
|
||||
202ef749
|
||||
911daf20
|
||||
b7b63628
|
||||
3bbb965c
|
||||
25971699
|
||||
3cf16f5f
|
||||
1e95278a
|
||||
273193c1
|
||||
48794404
|
||||
a5a25d98
|
||||
7c85b782
|
||||
d17a5be3
|
||||
c218d70a
|
||||
8ce5be9b
|
||||
eed76688
|
||||
7fdd906e
|
||||
136a9e51
|
||||
87674a67
|
||||
0c1f44d8
|
||||
6e09307d
|
||||
f4f2d7f0
|
||||
855c52e4
|
||||
e63633cc
|
||||
8baecd74
|
||||
08371d8b
|
||||
18949a47
|
||||
c253b241
|
||||
c94aa045
|
||||
e272c69c
|
||||
f2b73158
|
||||
6b6d3a84
|
||||
76e9bf4d
|
||||
4c05b45e
|
||||
1a774816
|
||||
47e007d1
|
||||
37e715a7
|
||||
8d8bd4f6
|
||||
733862ca
|
||||
Vendored
+250
@@ -0,0 +1,250 @@
|
||||
b318f483b819b982a9756e53d7e15648
|
||||
f0257b3a3e42c43334b5114f7af458c2
|
||||
7e53dcfd409361c9747f2e15498bffec
|
||||
d149b038ca22572e33a1f395e5ecee08
|
||||
eb27dd6ebe6c63da3383569c0775a831
|
||||
2cbbf7c8d7519b6f5e8ac14363b4bfa5
|
||||
ca1a19e2110b2129f11e8b49127bdd85
|
||||
286936def7ad4df160b360d65ac9b46e
|
||||
b33f9984878150925a7967072e99d49b
|
||||
075757bf9b1875431fff1fef1f8d757b
|
||||
68d3706a9b387f97e6ddbfd4abc57ad0
|
||||
bdde980b77c72c36b4f090c9b5da5344
|
||||
2574458dd66165304420f6ea813d63e5
|
||||
35fcaf6785eb36fc903eb5279b6c29aa
|
||||
ec4f4e19d7e54e26d3dfcc30e46f8a57
|
||||
bffdfe52242097b034c1716a4f3c2c28
|
||||
5fb0282d92050c9a5c7b3c12dd5ced0f
|
||||
6167bc48f2e77dad220a3a6aa67b05bc
|
||||
d3cffb696153090eb014e6a88802bd4e
|
||||
9d175a5550156b1e8bca6099b4c09f61
|
||||
7fbd2aaeedd6f3d6cb9603910c7cf2c2
|
||||
56e21b1c18fff15a62e9933e442fe8ff
|
||||
546312d22a42fc9b67b08e18d6b82852
|
||||
2509014b171245b4a3b692527c2781f3
|
||||
fb31a14a29920145563197157f60b0cf
|
||||
64985fa4c405b139aa60202819bee68c
|
||||
7886fd15be015e09715906507fd7ee6b
|
||||
f86709eedd0ea8413c30e5417dab459a
|
||||
0d3b8df30ef8188a889dc7cdeb02b21f
|
||||
255db4b53038918896afe70d26f7a52c
|
||||
c35945881b96b536573f7eb5a35d9bd3
|
||||
809a7dcc7966d196b61e0645025e959b
|
||||
fe2130868d0026602d29492cfb77365b
|
||||
1f341d6cb526c6cb2b18a4919d13dd3f
|
||||
14a9658c25c52651b75b009cf505c268
|
||||
f1b76a3abadb11253729430c7f333fa8
|
||||
cd204999e6f7912ba9caa4f625314a7d
|
||||
3701e818d60637dfc56407d88b79feda
|
||||
819907f586f4c7515106b4083ddbad47
|
||||
c43789f2fcd506cd7ac5116c9ef4d15d
|
||||
19ae171488b106301ee54078b6f5b5df
|
||||
21ca7fe9e1ab4f13ef5a7f8a041d471d
|
||||
6d23d3b81f1c4c787e0ee9f62f5a9849
|
||||
3c9ea82d2bd157595b69138c327133a6
|
||||
506a237ea2ec916734d9511fbe79be10
|
||||
5001323d2e1b5410def30db2f59e4261
|
||||
f4e315814b0812511fbe50d5c061e324
|
||||
a687ca61e888fe0e36db694a870a62f5
|
||||
9f6f17e40f587c27ba7a24227f279285
|
||||
1e09cb2dcafb86be51648f2a4e14dc24
|
||||
db5fff370bd822000ca204fd64bac350
|
||||
0ad486dd80931116e0eedc3c2938a201
|
||||
c2a0ee00978debffcbd633efe9299d97
|
||||
21fe48e7b1666fedfeb8a6e7b67ed7e9
|
||||
bd2f4438022e4007207af7db7e488674
|
||||
f4c6654ae5325c06d7248b423d2dfff0
|
||||
f24ccbfb2bb7856c3a6a03570b01d1df
|
||||
253e90c855cb01f47c98b86c02ab5038
|
||||
9b1dc8a79e1d93d8756ed0cfcbfe5137
|
||||
aea93cf9423bc3dfc384faaca064397c
|
||||
fb030c4f06693f3610802851ceb11232
|
||||
9307c5df0bef220e2dac16807f7f237e
|
||||
1934c1a1a72b0646d0e9e71c15dc4987
|
||||
5ddc040a414fc5a141ff949e4b127d4a
|
||||
a1d5f1d432006062b5d6b9443c717ad2
|
||||
0589d0ebf3c8d2364237e80d41a2b475
|
||||
ddcebf0cefb4f874243424fac2e3b6de
|
||||
cdf1dfcfc39fa58d1f9092bf38ba7e7f
|
||||
167ce118cb7d087ea441c56fe4d35fb1
|
||||
20dc2343a99d4f973832173523c2f5b1
|
||||
3c54348b7b75234d86ee3dcf298b00dc
|
||||
187f13356155b3966596693b3ba7f6fc
|
||||
e64ec5b7d6554e8976f9d55620ae8411
|
||||
8e4d7d9d357d5146b824655e99284939
|
||||
221da170942678c1bb96f106bdacca65
|
||||
10397b2182e00d3c2f3521adb1125b1e
|
||||
820a2cbffd802eb8444d7d321903855d
|
||||
fb6543d46d8733d2fe8505f058b14e4b
|
||||
088bb42d269720bc1d023537f7b57213
|
||||
5d34e835cf4fe44f26e6ea7df3699323
|
||||
7525e57a778a4b298ab8a1f529dea231
|
||||
f6611d41cae1236372a61794515f3286
|
||||
fa807ac173b70892aa9fbbfe5cf097bf
|
||||
b30a12a5c07b96c5d2a7ef716a40e214
|
||||
b699c6d2b59396338c025cf72e28235e
|
||||
567c19149bb2089eae35fb58e6d3ca9f
|
||||
d3d47ec2f02bc7d564192a98fad66e34
|
||||
bd8293548cf345333c892503bc02762e
|
||||
84c3e877420d17c1536836f1b4b0cf42
|
||||
55c15bb8eb4cd81545314e0ab6535c73
|
||||
e47f3fd5c096033b0d1d3e9de8e05dfe
|
||||
63e69377f86e999221ce62dd101fb530
|
||||
8ca70fbe70b840ffc159f2ba5890b472
|
||||
e5da1d908351429ba3875de6e541a294
|
||||
0cd2b4b3d9dc052d4f3d82235a469809
|
||||
8c47d7a5e67175912811cf24ce1be9df
|
||||
b3a7c2128d325519e6e788ffa19b867b
|
||||
bf803115b52d1452e2f46355deba01a4
|
||||
5db69b7973fd9e6da6aaf7836219d495
|
||||
4466c1bf9d8cb075799e7259b06c3dfd
|
||||
0df28c89649a7ada81ed6b73995dacd3
|
||||
60adb4f96fdf22479e7d1edc583d532f
|
||||
af2802fee9230f40f7662f6eb6318518
|
||||
f1a5beadc8a5ba88b74d31e211b3da2b
|
||||
407d4028cf27cfe3cb265d932dd01ad8
|
||||
837ef722923ce9df10ead5332e2ad1b4
|
||||
d468d3075b95985c692d1182cf45c1f0
|
||||
cf750f31df41c4fd0f64797d7e776243
|
||||
bd0881cfb36014d96dbbd8d1872b449a
|
||||
b3fdd5a0235590e0606eb8223425c206
|
||||
c2130fcb9907554280a2f1024bf5b58e
|
||||
0b4cc296d9d7354718341a0b241d5417
|
||||
09be4cf122c20e92a8460571af96236d
|
||||
2c8763ee3dffb8a2ab123a0b98909531
|
||||
bc07e96aa9fd80daee6b6747f3eb10d9
|
||||
5191f574d1aed73c2fea26ba696ecd73
|
||||
cd863713a5588c5df97f1272bbbc8537
|
||||
1ed61be740e1c0dbca9e803537e913fe
|
||||
6ff899d76a0599bf67b4898078d10c8d
|
||||
ee1f3a668c150ed6dfae060e97dc46cc
|
||||
d73c55aa581d36c4f7f534f1e03c9418
|
||||
d9689cd24ae9b08375530f7ec41a574a
|
||||
08846be9ad2afa27ed851634d9cab4e5
|
||||
1668585e3d72b2bc736fd5b582b17ee8
|
||||
5d2d6df2d6c40044086e8faf5e20fa76
|
||||
53e50b205ac2de14ac79d2a658ef4327
|
||||
e73d59cfc81ec50e7aa5ca9087aaeae4
|
||||
2ca1fe1fc342c4c3dcb28089e82604a3
|
||||
b1cce1a87a2486705b59517c895e817f
|
||||
86143d602317e1302c8d1825bfb201d2
|
||||
7c962811ce295e6ab9c4650cae19319e
|
||||
be87fbaa40ac8989649700e9a82d0ce9
|
||||
d3fd645e47efd43950972463ffd74e3d
|
||||
6ed763a90aca846393467bd37bf6ecc2
|
||||
f888abbf25d47985669b2ecbbda04063
|
||||
1c64db945e018359d80762d0f1e82f85
|
||||
a5c8ff3c974ebbb722e9d6ef92de4c3e
|
||||
7611866a4cb7d346752bc4700621779e
|
||||
075ddd7b4bf5bcc6d308065b94c9e4e6
|
||||
0a59ab9ec9d8228b1b3d554bcbdafa26
|
||||
af6014ace2c40bada1bac0da4ca1855b
|
||||
86955b7f88078d167b381366229f8a42
|
||||
f8c66e921bf190453f6c64ba9dbeb3d5
|
||||
c353624e039f8f5a6ea83196d7559886
|
||||
bb418dee4a5aab8c9c1e5f4d32a4bc8a
|
||||
b56240f383319fc8ceddf6864f715a5b
|
||||
544ab0aebf0e6b5bac10f5c27329ef52
|
||||
dde30afce82bbb95752b39662d79648e
|
||||
ffe0f8f122b0b575c17a745d98af418a
|
||||
ba476f1704cc103f303dc88d6aa5fd31
|
||||
ddfc7326b1e137d4338da33585c94fc4
|
||||
32a313e1553677d00ac43cb8610186bc
|
||||
4fed1c0a1959a9dca55c1d126c704ce7
|
||||
cc073c0fcb57c2a0ac7785d3e36289cb
|
||||
7e91a82a927a14d0be7a4ccdce702222
|
||||
85a1e2124c2acb27c2eae947e371332d
|
||||
b8dad5132989583791ea3180ede734e3
|
||||
b749f5cfa51035c2c901c8f51fdae26f
|
||||
63c8d810da89f6d93895744cf6bc6092
|
||||
c4c32e4c185cf04da9e6f6cb4b228c52
|
||||
6d48d28f00b6cbd78b4ddfa056c4b24f
|
||||
a56ec3b1d282706257e9539d97708745
|
||||
3487a2b855eef272eca5c04ee2b826dd
|
||||
5d35907998c38c521a0bb08dd114900b
|
||||
f01a320fef7ed3cfa74fd5e7b7214491
|
||||
3655861d44a93225eab5a6f6aa4dbd9f
|
||||
adc67f4388f9e4aa42232f19db02690c
|
||||
096cebe6b60bc924def53b2d9413febb
|
||||
8200dc7f73ae39a7fbeaf6b7fdf11e2e
|
||||
ccc47ba3d01aaeb9859cd998d3472efe
|
||||
727e467f3789d4b3805318dd600e8d41
|
||||
ab74f71926d8e1b5d0fb89904c4f0e56
|
||||
2ef13831a4100e211309658d5c02a177
|
||||
796c34d7ccc6790fccabc9be3e42db9c
|
||||
b1fb2e39582d47ab8172702288b2fcbc
|
||||
ac5d037d7457af598c9b7fca0eadb384
|
||||
7a82d0ec070abcbb2d1ef16bc8af47e7
|
||||
ed68a5b6911d53f073b778294192e44f
|
||||
55b14258fc94575ccea51bc4bcf6cb61
|
||||
2502f7cd3fd06ad7994acd5ac8995508
|
||||
afbfe5035e3cdede39a1250b1d91eff4
|
||||
c7bd0e18288966e3fc902e401a1cf92a
|
||||
a3243a3f3ed1f32bf4e2d5ee06cf69ab
|
||||
3538beb96dff67a1d7b81a317950d038
|
||||
ff24aa585a23d93671add56e45ffdca3
|
||||
912bcec63bdabb5a4b472df4b4f557e1
|
||||
d59ad48f9b4378ce89b4bfffec9b916e
|
||||
ea4e4951dc51b56e07bf01c0d4171549
|
||||
7197584855f20ff4903c247939967dca
|
||||
9f7482cc0d499ab79f2eb5480807ec6e
|
||||
eaadda1f5ff577d617e6c4c479b5f563
|
||||
a4c9e70aebeaf8b4f986fbf8bd8aa902
|
||||
10f3080e8374ba0397f1e296fbb60350
|
||||
b021caa467751d617842f66bb4e62b47
|
||||
444ce58ff856af0ee2ba0e99ae37950b
|
||||
b61d88b82f582f32723ee5dfdb1d34de
|
||||
55f921778031bd848ebddb7dc0d63db7
|
||||
bb9ad6dd7d13d4e412bdfec31783992d
|
||||
a4d4f8ce07d3496017024897233b09dc
|
||||
ffd49b1bb1ebb2afa89ac73305762656
|
||||
69d843e05f072e3616e9fbeefe1feb9a
|
||||
4758c49357c312d6187a3cc7322d40a9
|
||||
0200eae7e77a5f7eb0ab6a52b5934c43
|
||||
6f6de279c5b1bf22064d6173e0f4c659
|
||||
dd1f5fc9e67d0f2375e72bfaefa72590
|
||||
bffd8a460d7ee4746ced0abd2e108562
|
||||
2d638249a3d755b2c778f6b384b862e8
|
||||
1e5322f8df0406fdbd2c99d5eeed0a68
|
||||
41e1a633a6adfaa70198f5872272013c
|
||||
9900e6ccab80f965f0f42f5f466e51f6
|
||||
0da72ec72eb2eae9f410267cee90d1b7
|
||||
c808dd41b542e64db7ee2b6770eb99b3
|
||||
2d8b514f3e2257521b2ab872ea7d4ea4
|
||||
be6a7befa9d91157e6a70bee2cdb9516
|
||||
ccc15622babeb66a984b81d80a713966
|
||||
caa22e1262b249b7e0dbac4bdd59e391
|
||||
d759cf8e65cb2b805a7101b32bfe64d2
|
||||
43da07daa63a0ad6c39e5d3b468b7df9
|
||||
2cf45923c9b52de4b23a34ae07616bfa
|
||||
1c5dd7b336a9cfcfa7772e4df2f62075
|
||||
8261e8791fc1d8a5097af6ed2c1487b8
|
||||
e97b262b8f2056851ba19b6b1b23a378
|
||||
567442abf4ee97d491572bd868d50daa
|
||||
cf25196079d433cdd802e87d4211ad3e
|
||||
e7295385f1842ede9e7e5e48602fac00
|
||||
6e44cb843be327f938e1558fd22d9aee
|
||||
e8efa873da70fb0c55e95ccb3b98dd0c
|
||||
1f520e477d527dfee845187c8bba3433
|
||||
da789f8b71e7618b6b614e7f38c6eb06
|
||||
4e9f640a8f317b0da5d3f24e87708866
|
||||
922134326b2d0223cbd3a679adc908e4
|
||||
272aa55d0137df113bee04f8c06e4d46
|
||||
6e5f5d3f6d2c88fdc80f26c3af4baf39
|
||||
2fec6a2b1154841837b77a456627de14
|
||||
71c562a6801157465fd5df54b87504b9
|
||||
eec76ebe71275d86b69a1ca42a8da947
|
||||
42e10d7a5b99affc5d9ed41006e701f2
|
||||
ae0aa8ce889e3ff30b7840b7a2b74737
|
||||
06bebb86bff1e08d071adf6284bbe3b1
|
||||
0fbe06a9de9f8195b03364748b6455e3
|
||||
46f2d55d237687a1b9d3b326c56da6c3
|
||||
3ffbbb180f1c3bf8303967a0840297af
|
||||
19cd3d6d0641fe34da63c78cfc94cb72
|
||||
953d1514d529804060f71d1430fdcf7e
|
||||
961279642b7ca4f0ddc329c2c0d7a819
|
||||
cc4ffd1fe4dab49b3a5ad06948fd3875
|
||||
32f0f8ff923031ac246925020c11868c
|
||||
ab56616053efde4cc28e379d689d380c
|
||||
253c3fa01be26ad95522f95bb3927132
|
||||
0b137261e168b892d7b6b2255198d545
|
||||
BIN
Binary file not shown.
+250
@@ -0,0 +1,250 @@
|
||||
3eae0fa4
|
||||
671432fb
|
||||
36f2633d
|
||||
0d4c5ed0
|
||||
5401b3b1
|
||||
132ea2eb
|
||||
638474a8
|
||||
ddaac73d
|
||||
55d506dd
|
||||
a895940a
|
||||
552aa819
|
||||
3df1c8cb
|
||||
4570f1a7
|
||||
e355e911
|
||||
29220d98
|
||||
e1d6c064
|
||||
44b2001e
|
||||
264b8f82
|
||||
d13e3c00
|
||||
36b2feb1
|
||||
dcee531a
|
||||
f8a44834
|
||||
a913018c
|
||||
90f486e7
|
||||
9a1101d0
|
||||
15332185
|
||||
842e0474
|
||||
bff7bbf9
|
||||
dc8cd701
|
||||
835bd3cd
|
||||
2a3ea506
|
||||
67e8e53b
|
||||
ef5b8038
|
||||
97c89f86
|
||||
ef8a9d7c
|
||||
792b083e
|
||||
388e20b0
|
||||
c9b83d6c
|
||||
868e8252
|
||||
d77091cc
|
||||
33a41580
|
||||
67912531
|
||||
d3a7e0cc
|
||||
fd5ac6f7
|
||||
ed96bee7
|
||||
8b4a495c
|
||||
1219c05f
|
||||
b8050ff4
|
||||
c9c27101
|
||||
465b4659
|
||||
01fb0b1e
|
||||
7a01e4d9
|
||||
1a6333b7
|
||||
bd4e9d8e
|
||||
fe812caa
|
||||
4bb09d4d
|
||||
64b15b3d
|
||||
cba3b242
|
||||
720daf5f
|
||||
44efaa8c
|
||||
14ae7ec5
|
||||
6c6c8c3d
|
||||
3bbaa125
|
||||
7b4872f6
|
||||
a59dd7f2
|
||||
83925660
|
||||
b0679228
|
||||
f52ef9fb
|
||||
2850560d
|
||||
01dfd505
|
||||
94a48963
|
||||
7fcc0cca
|
||||
47528c61
|
||||
032dc2f3
|
||||
bb8e0fb1
|
||||
63914082
|
||||
b9b4c36c
|
||||
c69f899d
|
||||
49a9c94e
|
||||
77af09d5
|
||||
07cdcc3d
|
||||
eabc68d5
|
||||
62421f8d
|
||||
2811bd16
|
||||
e3d5a2e5
|
||||
17d95adb
|
||||
55bcd9cb
|
||||
d785f280
|
||||
45e97423
|
||||
c7457a9c
|
||||
3e2b380f
|
||||
57b67280
|
||||
d02339e4
|
||||
065e3598
|
||||
d5977df8
|
||||
8ae372e3
|
||||
54e17f4c
|
||||
ba6e24c1
|
||||
a8a11da8
|
||||
ed23d1fd
|
||||
c1f3ed84
|
||||
4a46e699
|
||||
fea03186
|
||||
8bf1c40d
|
||||
7ee9d985
|
||||
fd3fafcc
|
||||
f2b62184
|
||||
aceda460
|
||||
97d31d6c
|
||||
54aa313e
|
||||
d95b8ea8
|
||||
70c3792a
|
||||
677ee6ad
|
||||
ca53aba4
|
||||
1351a9f5
|
||||
99a1bb43
|
||||
c01cff4c
|
||||
b1cb013c
|
||||
ef543a58
|
||||
f5c2ae90
|
||||
ca5387ad
|
||||
7b5ca533
|
||||
78d125a3
|
||||
5e04f1b8
|
||||
8f620bd6
|
||||
24ffe5c1
|
||||
5fda8ca9
|
||||
ae38f186
|
||||
2d86f080
|
||||
2b94cc5d
|
||||
697a09f7
|
||||
a6187634
|
||||
dae9cce8
|
||||
74bb20a4
|
||||
18a41fd8
|
||||
a4d92f33
|
||||
e902181c
|
||||
18b9f932
|
||||
e5ee4cda
|
||||
1ccb55bb
|
||||
6ae0fe2e
|
||||
05129a95
|
||||
66d634d7
|
||||
0dceffae
|
||||
00814ab6
|
||||
6370890a
|
||||
c6d35660
|
||||
8eb21a54
|
||||
9ccd4c7e
|
||||
b3a01706
|
||||
79d5382b
|
||||
d56f2dda
|
||||
b6bd04c6
|
||||
becb8b5d
|
||||
0860f480
|
||||
1febbb26
|
||||
c45e567d
|
||||
9b12f235
|
||||
916e5b2b
|
||||
7a0c2458
|
||||
cdfd327b
|
||||
666d700e
|
||||
d5743e0e
|
||||
a8ead497
|
||||
2de13dfe
|
||||
1ef94a75
|
||||
8a05fd88
|
||||
e8498198
|
||||
b3f388bf
|
||||
3ad2a8ed
|
||||
9a871a37
|
||||
1e994a52
|
||||
3c9c0f61
|
||||
9e67b705
|
||||
b37baee1
|
||||
c57c176a
|
||||
8cd83bd6
|
||||
ed5d5bef
|
||||
d933b481
|
||||
75882924
|
||||
662a5466
|
||||
956e24ed
|
||||
f55aa48e
|
||||
16273797
|
||||
1dbffc93
|
||||
dadd656e
|
||||
7bec0c75
|
||||
aabdd998
|
||||
136aa991
|
||||
72abfe71
|
||||
b2be52f9
|
||||
df04bac8
|
||||
fc14d7f9
|
||||
f34ae6f1
|
||||
1ea238aa
|
||||
d77bd8f7
|
||||
821bb618
|
||||
4c543487
|
||||
81bc3b15
|
||||
afd3f64e
|
||||
843d503a
|
||||
54bf670f
|
||||
f447048a
|
||||
51221d02
|
||||
f20efc3b
|
||||
e492c81f
|
||||
a2cff836
|
||||
41ffd6d6
|
||||
8e1877af
|
||||
a866b3ba
|
||||
c7751eaf
|
||||
6d80911d
|
||||
a8701556
|
||||
0185616d
|
||||
ce91cb8b
|
||||
a8b341b7
|
||||
49c4f47d
|
||||
d3001a6b
|
||||
6410d282
|
||||
ee71f778
|
||||
190d2d8f
|
||||
05c09706
|
||||
e25514d3
|
||||
7a8e74df
|
||||
543ac711
|
||||
4477777e
|
||||
40803303
|
||||
3cfae743
|
||||
bf691cc7
|
||||
c94b4dc2
|
||||
7127479d
|
||||
2aaaa442
|
||||
a36b21a2
|
||||
f8fcf82a
|
||||
e024d413
|
||||
7e080f9d
|
||||
937d6382
|
||||
3e0a998f
|
||||
f2af5462
|
||||
64df59a0
|
||||
9a9d533f
|
||||
9e2bd916
|
||||
58084d1b
|
||||
d638f9c7
|
||||
04d186be
|
||||
1b3b2b54
|
||||
48a8fa7d
|
||||
d20ab14c
|
||||
c4b87b7e
|
||||
0ec6efbf
|
||||
+260
@@ -0,0 +1,260 @@
|
||||
{
|
||||
"profile": "H264PROFILE_MAIN",
|
||||
"width": 320,
|
||||
"height": 240,
|
||||
"frame_rate": 25,
|
||||
"num_frames": 250,
|
||||
"num_fragments": 258,
|
||||
"md5_checksums": [
|
||||
"a5dad6170eb13fc5cbc6fe3511d44053",
|
||||
"e056362baaf13dd0f888e67a681ab381",
|
||||
"ee0c33d2b92e0443ca5770bd0c56911f",
|
||||
"0c8f0226fd484358b69e9fce6294a888",
|
||||
"a0809d811b6273bb63ecfdb74097e0df",
|
||||
"0cada76917c6dbca2093352b3beaa2e9",
|
||||
"63a5dae178cfa9e10fc6fdae7957f38d",
|
||||
"7e473898faa27f0372c30ccf7c1702e3",
|
||||
"6d3e896e1748f259207b2be30d24a0ad",
|
||||
"7a8a94778f723b6a4da79be367577dd0",
|
||||
"abbcff2c3d72fe2ccf8205c0e47142cb",
|
||||
"4790caa46b6c5998d627d53cf8a45ed0",
|
||||
"6d183cdb8d57e3c5cb5e5340c71214e0",
|
||||
"2594ab487dcd844860fe120e92e9513b",
|
||||
"1b365becbe416007e1fb269dfe2bf0d5",
|
||||
"ae6953f149e85d1170a27e22cf8fea89",
|
||||
"b6e8a55010fdd0a2a2f9b5aa26f24994",
|
||||
"0ea165fcde3b1a71bbb2e16c615c9e5a",
|
||||
"0dd539e2735c216bce229ada7e0e7722",
|
||||
"9d6b6e80b820a773a16060cd73d0b047",
|
||||
"f9375adb2bebacaed22305ba32af4583",
|
||||
"9185b111100dd0431b23ed799ca02b70",
|
||||
"bdbc5de6197178cc0ef70cdc07b5c34a",
|
||||
"505517fa76a772b8af27670302b5bb80",
|
||||
"df7a95b296257c8ec2c9fed399376631",
|
||||
"8d8d754f34f3118fb0d9ca69da2bf6b0",
|
||||
"f5d0ef57ef80fb4efffc2d4530f0d60e",
|
||||
"262eee5c2f052fcff8d330c5aa62353a",
|
||||
"90ff465117bfc3cc8b6363b204326a0d",
|
||||
"dc2445266fec9498194b185f4dd41fb2",
|
||||
"878f7dcb0df9ddeaa8470f73bbf0d1e6",
|
||||
"848d8910f7c95fbe27bf00d26ce3fc97",
|
||||
"e01547b08bd83922ccd677733ea21472",
|
||||
"fdc049f772de5fb88493d8ec58bf808e",
|
||||
"9f74486171798cbd0d4da6c6bf0ff00f",
|
||||
"557ff971fa63589cd4ae687abb778e15",
|
||||
"0fec7b663d136fcdab088178c7c240a2",
|
||||
"65b1f1ae4a015b9da106ba97baa929f1",
|
||||
"5e495f76502f2010c30c08d0ea7b6371",
|
||||
"96067b4e79207c0430f68ab0a4be612b",
|
||||
"5a5ff39ae498ba16009071e2b19a1047",
|
||||
"cdf81c7f4c94d199cb252cb0f880ba0f",
|
||||
"3e482b016234911fda0acb5a2b31b1e8",
|
||||
"0eba802cbbab73ab3ea6c1713bf38249",
|
||||
"4e3c939c00b2f439b9bb0e4f748cd693",
|
||||
"7da28a3926f11c1ad21ada44919326b7",
|
||||
"bb6ddd70a7c21190a9896deb89f0393f",
|
||||
"3bbc5a5d4855fdef5cff6a72e080db59",
|
||||
"8809185828397c4f188225a3a261319e",
|
||||
"0e6763c0673aad271cd13fc5c6c5b05f",
|
||||
"7dd4aa0e1a6ac5d308ca8437d3b33420",
|
||||
"4b38d5173b9e9f03df4c7d02ae380f7c",
|
||||
"a252d94720017908735d566322be9e25",
|
||||
"a547bf19701974e914d556b55e5cc876",
|
||||
"d9f65e78870600cd4b53cb19ecbde3e0",
|
||||
"22d740be86b5ae270b34a6a05f306169",
|
||||
"c7d9428660fdffccc6cff0a038a65c8f",
|
||||
"50dcb76ce04decbc97e17897a03120f5",
|
||||
"b39d19ae0f6a8dba61eb15f3a27512cf",
|
||||
"bd1dbca640bef318da29f3d6de5bafc4",
|
||||
"5eafb79ab0af0235020931cb2b411e97",
|
||||
"050393b1311b31b5d711cb3a84fa0398",
|
||||
"f8827e35815f52022a340129bc005860",
|
||||
"5c6f844ab5b0fab77e98ea3e5a2c6d30",
|
||||
"f29317ec99a14eeea363fa70d55dfe7a",
|
||||
"9fe58ebd66d22be9a7a443baa2733f83",
|
||||
"f7cdb42ccf66afa9cfe598fdd6243869",
|
||||
"ec68961bf9e81cedb36269a0bf94c851",
|
||||
"16b64cdafde9e955bf5930c4dbeb2f8e",
|
||||
"0c412adf668372e1676957e58e876518",
|
||||
"fb0179693e77dfa0e4873552f81415e0",
|
||||
"9d31a15e3fe050695d0a7a77387dc7b6",
|
||||
"737fa9ddc16c371672ab7c43645b69e5",
|
||||
"3c0723cf264055cdade0834c1fd0c503",
|
||||
"53b4f854484bab3f7fa8043729e14bf0",
|
||||
"6fa2c2fd8930dfc46e37ea8a2a153ea5",
|
||||
"a2d85585fa27d9f000aeed5da5d1722a",
|
||||
"7da150a3fbfad35c7c7e233147f97927",
|
||||
"f33db156c0c29a6125904477c7a52e0c",
|
||||
"8a4b651ac53d128e8bd530f947ecc393",
|
||||
"0ac0408a68e2fee3f359941c3c7664c4",
|
||||
"0af391a381b14b44c61e67baf5716869",
|
||||
"626d9be039035d65f3e6e40f3a1849b7",
|
||||
"388532467f13b64b84280fc2df75157c",
|
||||
"79ac74b267d1e31a9760e4797f3bd9af",
|
||||
"3cb436415cc056b2d2e0b56c5ec99b14",
|
||||
"66b2a9581b65a9f8381dab9b4e3cb107",
|
||||
"13f8261ab7bcc21ab46848b861cab446",
|
||||
"9fd2eb5e88ce9137ba774b8e5743f842",
|
||||
"531d9b50624f55446efe0ce2eb168cbc",
|
||||
"be08a1e2b214db9d3b8a7995bdf8c401",
|
||||
"fbb6f7ff4a4fec07e4609e6292846873",
|
||||
"70924eb573abb590615100d48f54ef92",
|
||||
"7dc5a37f2365231da2145dfbcbcf4a1b",
|
||||
"ca1ef1e6859945533b4afc4bf1a12ab3",
|
||||
"289ee446a3b431865d539ca2d3a50d59",
|
||||
"bc1d07902b4572ea615b6653c01f9b52",
|
||||
"fae9de5e08b65986e9e43df1e5e474e3",
|
||||
"ea4bb6045444a9c3085dcf546eb71016",
|
||||
"162392f165140997cf35e436c03366dd",
|
||||
"1a39e8d92602106ed428487c1e543541",
|
||||
"383cb9b29e98c471ff4de80a3abfe0cb",
|
||||
"cc70ac50536aa7b063375423dad34096",
|
||||
"e2e11c7ade1b414be1e51f626b9939a6",
|
||||
"2eb1b386c45d627ec119abbc84ea8dd9",
|
||||
"e788b25c134e1a0f258af9a88b1e1e2f",
|
||||
"10fb71dee1a7653cb6cc2d19e58b04cb",
|
||||
"8d20c4bbf93fe920a470f4dfb7f2d130",
|
||||
"9ff22a54b4589962f1d475a4d308f96f",
|
||||
"b5f27d12234ab57a97fd8d8595738aac",
|
||||
"6673fddc74d578fdf5e716218211b7ad",
|
||||
"e1cc1721e9c048e480214a8cb1369f08",
|
||||
"cc0d3ce9313fbc02005f4eacf08246f0",
|
||||
"7e275f2e832e8b4518e2aec5925323e6",
|
||||
"747af41fea71a7c443b58632ee06b6b7",
|
||||
"fe8a0cc71b908e241692e36284c9e2a6",
|
||||
"cdba33dbf114170b84ce01177b1eaa7d",
|
||||
"0eb286cac90b1e17f4e02c8625df85b9",
|
||||
"620252c40523dee3f0d3ed75d2d2bfb2",
|
||||
"ab8165d3282c27f4166c44a3f947154f",
|
||||
"25cb402b0681d921c819e00e5b8f77ad",
|
||||
"8ff4db1bb234ca8f2f6f8f4e45dc5088",
|
||||
"f30db0bbdee5bb63bdf04f5d944352a2",
|
||||
"985a0d075da06266437d3257e85e6d0f",
|
||||
"c53c0d7258484d806b908d2c69376688",
|
||||
"9390be2306908ee719ed6135ae3e0661",
|
||||
"3f74122552fa39da806b039fdcafe885",
|
||||
"2c7650ec0a56db5dce916508157d282f",
|
||||
"44f7fa9c7a71d3968830b2dc97005ed2",
|
||||
"0e5a240b5b64b1f86db4f603248d2d48",
|
||||
"dcc1268f74789b61f0120445d86dc3a7",
|
||||
"1df397a99a63248fe2a283d9398e3bf1",
|
||||
"72da4e7b2d0b9327bfc198ea86f259ff",
|
||||
"7152dd7b90eaf1699d5d03a29f41fa96",
|
||||
"ba8f38171542fd2de0e63fc4dd5ca4d1",
|
||||
"452e1a61ecef97d95eedbda97f27626d",
|
||||
"b5163a55f4d3da0cc563f2d8308aa8ea",
|
||||
"0594c34726be28517ff0180cc826031a",
|
||||
"0da52fffcf3ab88f9f1e9a0c5b6c277a",
|
||||
"787b744b305354ac0127af6f7ab6e5b4",
|
||||
"682d202d5b4b154c1e1d87fea635bfe9",
|
||||
"b2dbacace18349a8c4bc4af6d0109caf",
|
||||
"25fbe415ae8031b5f4260222520618f3",
|
||||
"bab2de7762b7e0db9c8b1583b2ca2fe6",
|
||||
"1d8b3418ff6170f7492f8b8098dfbe3c",
|
||||
"89cf144aac4e2932d2b30683ceb8a5df",
|
||||
"b952fe86a6275745f461e2f0243a8f2f",
|
||||
"1c5e35bb8d3f117ad22b80c3208ab36b",
|
||||
"8cba5bec1dbe1e9045cfc458fc3be807",
|
||||
"e3eb152d091b689b142fb52fde68acfe",
|
||||
"a90e523e20ef52a9708c27dd32a32e51",
|
||||
"276c2cb2e4dc2dee97014b913d20ceef",
|
||||
"bb1c76cafaed84030fbdf88cbd38bd10",
|
||||
"dd7155ad9cdb7914394f376585716ce4",
|
||||
"fd088933a0743c3f014c5cde815f8c0d",
|
||||
"cb41f67f3730d052129aa534e4cae33b",
|
||||
"349d01c4e208ae68fa5fbfa5c0b259c2",
|
||||
"44571e3c02368efe154dc942a86db41d",
|
||||
"9d50ad1788986f0e71aaeed49bfde214",
|
||||
"9e102fe1eb1cbfd67370cfe2ec676c42",
|
||||
"16eeab863b12b958d9da04ec16b76049",
|
||||
"697873e7878031ba7e5fb57fcf8bb3ba",
|
||||
"7d1c52765bd7dc87b01b96963085eca5",
|
||||
"dbc235422231280b8b314ac38bf03943",
|
||||
"bad7ce4deaf3ba172e30ee67770d5495",
|
||||
"6c7b0cf1bcb5da8ac7805d722d4c8e52",
|
||||
"92a923806b59470f83a1121c0a2b1282",
|
||||
"790f8f46429044540879beec078c2b9e",
|
||||
"fee37fef18b74dfc5fac61dd343297a4",
|
||||
"adeb2bf5bddd3ee4c3977570f7957eb2",
|
||||
"42da9b4d6561a9faa4646c1938665d8d",
|
||||
"96ef3aa375d97217ac52921f11f53393",
|
||||
"b4b9c2e6dccb82477037b237bc29beb7",
|
||||
"78f5063f7255796d6b954ffa63f171bc",
|
||||
"c48f05d7bd5865ba64bb670ff950528c",
|
||||
"737413a29e706d1ce3cb47476881d72b",
|
||||
"69da9015e20f16edccf22676850577b8",
|
||||
"9b76caf25e9566b9a507d2bc9d381d54",
|
||||
"c7820c8373e48a8219421ce9f01ab923",
|
||||
"1482abead594e3f5dcc4bc73e5cb3ccd",
|
||||
"ae8504362f1612eaf12fd4c99fb09849",
|
||||
"de613970f131a752cceef714c8cee331",
|
||||
"22a358e5278844f125e72d277397fc91",
|
||||
"a57f9588335e6afe9b346a674b8c4b02",
|
||||
"85e88cea5a43e5c7984df07a0b5bed61",
|
||||
"28ef730a1eade5e972beba4e46273451",
|
||||
"238dc0244e981538ef2c57c4667164a5",
|
||||
"7dbead8203f5037fd4ccaf9e662995dc",
|
||||
"a15b6b70fd457142da0841378b31b9e0",
|
||||
"e0e51fa5868886349846a60b105721f0",
|
||||
"afe444099e5c600d3ccaa8711c9099c0",
|
||||
"67eca422c9f2301e0f9f00674392ff31",
|
||||
"d21bc2ee616b28d5501334a56570b229",
|
||||
"93d9d78079d426c690b6c3f707fcc7ab",
|
||||
"50cc4faf4fa623f78f47865cfd638093",
|
||||
"89457f854433da6e28fe3e7d921d632a",
|
||||
"028a95319145051732f732422ad75473",
|
||||
"ef7124eb0ad577d2fb7d4fbd67146242",
|
||||
"d5f82be488a5bb797cbfe4449c96b13e",
|
||||
"06e79dfe8b046868ef7b80eeca6f213c",
|
||||
"52ecabe5b54e0bcdd9e61855b07403d9",
|
||||
"ad0f3e305e4c98d31a7aa7b38bd23feb",
|
||||
"fc8177e47267379c6d86cb66c70a9ecb",
|
||||
"78f8589a7a57c08808c16bf976e97eb7",
|
||||
"f32ef46b3a6364450a4c84dc59a3fdb1",
|
||||
"6dbda2170360e7f601c76ab2d8054ef1",
|
||||
"d7447d04d50b84be9fb685de827fb799",
|
||||
"bd678e5ecd23870c85f0d3ac3b304548",
|
||||
"caa25dceb0de2d4918b62bf290641b36",
|
||||
"5e231beb0c3c7f29770db53fa311efae",
|
||||
"dd68639cb5083b06a1ea1c850028cd6f",
|
||||
"fa1c9525569aba0ef23804b5f586e78b",
|
||||
"d75607ea7fe696715bf898aa537dc1b2",
|
||||
"bb0d9294e588699ce1cf9a3e6a477340",
|
||||
"ef2787371909de8fa59c9f45c41eca96",
|
||||
"7835e9094edcddfb16b147525cd8792e",
|
||||
"42dbe4b5aadf06ffca6f24b465b5ba3c",
|
||||
"36f6aa385cd30d73424113c1a6ff0aad",
|
||||
"cf2ac7f34a03a9e086858debebafb447",
|
||||
"9816f6620aafc8cad3a17e089c0bb3e8",
|
||||
"72db245f772dfcbd40489b96c8f2dd81",
|
||||
"555b6cb91b77a820359dc17e3a85c7c9",
|
||||
"88044cb9b0719ef254908df5694daf16",
|
||||
"82d5c83ae707acc6a24c5e6ea8369a53",
|
||||
"d6ab871f03fb9c3ebd2c16f7b1cd7778",
|
||||
"7eb6daaedc6863c8ca86605862cf55fc",
|
||||
"473e3dc83b70a0639735c211c6fa0fc1",
|
||||
"3b249d9a5747aaae5d21b54cea85f9f4",
|
||||
"1e270e073d634b47675716d555a285fc",
|
||||
"7b2123f7dba790ee1fa3c2f9de87fc19",
|
||||
"5e331a4678035837e9ed06957d13e05d",
|
||||
"fc79c2f6c81d68ea24b5f66373ba1b6f",
|
||||
"a29698a51f90cdeebe78133e9cbb741a",
|
||||
"e4b88aad03803aa0e4432545b2f4d851",
|
||||
"c214f459aa112b1d78bc37a599208891",
|
||||
"b429e6cc68732d804d94929f89c31158",
|
||||
"bb31ded86e464207d3b0b28b544806ad",
|
||||
"92bdd6f9949b8ce7e610345551fcfeec",
|
||||
"a0eccd9dd12fa381f36d0d877c9efa69",
|
||||
"f205c3e0255e4ff75bd86d3fd4ff0fbb",
|
||||
"950096d65b623715914809acf4c0a557",
|
||||
"b87ef8d2938e34894ed1bbf609fb99a2",
|
||||
"0a303937d3043c39e8904cc0ad181555",
|
||||
"05458b392f6555d1343949122653d6af",
|
||||
"d3dd90431ded20aae2f5ff0b65802fa9",
|
||||
"34df02751bd5cc9efe159cb85d0e22a2",
|
||||
"b32744532a99fc2657cdfbc85fd87be7",
|
||||
"160a29e8413328fde6623493a4da522f",
|
||||
"a2029dbf4304d50965dc2aad44b930c4",
|
||||
"1c082bc656c7752f21118f10b8677a6c"
|
||||
]
|
||||
}
|
||||
+250
@@ -0,0 +1,250 @@
|
||||
776f98580836e9d1f6de6cd5eaa26541
|
||||
b99c6281ace0eeaf0e5718808a568c2b
|
||||
133d598e8d3bc1ad6dad666799ef3b8c
|
||||
a0a05e3c48e4351dcf08934e55a53147
|
||||
79ecb0b46cf15db887390ee78403c27d
|
||||
b8ad982874240fee89b026eab547af09
|
||||
fb7de18f21faa1480d0f1c50b643b8a5
|
||||
a750adaf7e7a955744c377f3ba9bd99e
|
||||
3b5effc41ded56870353e19aeca14793
|
||||
adacf75ba5386d3442d30c8092fb4384
|
||||
0fb60fb5707a4b34c4a54f156c532423
|
||||
bcafabeb810e85570de61e4bd1b83466
|
||||
096823e6e69b2240cfc5f1c08a23d145
|
||||
d182e6428b339c9ec59081c4c196c4b5
|
||||
0c7edf230a9ca3112cf4895f2af7a835
|
||||
f6aca8639f2d838dba473658547a2928
|
||||
8eb680b4878e0c9e0bcf089b760b2267
|
||||
478b442172760bf7c3c7fe003ad73f21
|
||||
10e0f6e66e9739c36caae0cc2eaa97d3
|
||||
046a852108fc096d1d553a6583590d4a
|
||||
45d7284eafcbc5cccc618c12bc6af4c1
|
||||
8b5426beaac95a80df290220654c1dbf
|
||||
226d2eeae31131078f6d257e5ee89285
|
||||
2e1774cd1b8776a772b6dcb69773c5eb
|
||||
f31ec82bece107ec42c68a53b808e949
|
||||
8a9ba366d610b45d268848d3176ea179
|
||||
17a5556be0efb4a31949857a46cee27e
|
||||
f2077c849aebec971fa1cbc99d1ee8ee
|
||||
a067dd84f656e0367c4b3bad1862c533
|
||||
8be7cef3ee5b1edca2902e0afb28a116
|
||||
5adae2c59438976603e9b4d7da49e210
|
||||
44390208c61cda26906a26c85264ed6e
|
||||
222501ba177acc3aeda64927b0646d4e
|
||||
0d5fdc758b4632fd7792ce22bcfe717c
|
||||
144f01c1f347bb9f4dc27df962d37b4f
|
||||
c88762634ecfa06f1d12a4ce8a025af5
|
||||
2f5304392f987f4522e1ed7519475321
|
||||
1cbef99d860f038da6b8b2769d6c8e22
|
||||
8b5209b0fd0c84b96b9707c77778fe4c
|
||||
d78469cd4fba6d81b440d1fcb6ed1a71
|
||||
11d869672accf917e3f4f706f8f4a9d4
|
||||
ef985cbc882bac5bf8639f8d82680e8f
|
||||
96e113dc4f4d7526b49fb5ea0f8e901d
|
||||
ce418516be51f76d27509e2fd550f986
|
||||
7bd8928c90578e0d2fd8c9c1989946f9
|
||||
5a09fc3b51daba42beef52fce8e15881
|
||||
ab0430d72c7fd2288830fb99aae787e3
|
||||
7a5b68603fd986c7f6027f82fce47cdf
|
||||
305ec7419408437f8daeff01e3aad6cc
|
||||
0038fd9154185e9f0a5fded4e8dca4dd
|
||||
375019f32ed04e8484864f9fc51afd21
|
||||
6d8fb78c18042b55884d0a9e8c7ed7ea
|
||||
f83931a19f8de74dfb93082c228a73bb
|
||||
04a585694299a82dc862b108f1691dbf
|
||||
fbc073e705710480225f17d16ccd6527
|
||||
1d9e07daeb879c11029397f80513aec9
|
||||
3a1ba3f2da5307b17c68803bea449df1
|
||||
8d2f9d9a9d1188dbb90b987b321b9cb6
|
||||
57c52b47a13cc8aeab3ab005cceb45fd
|
||||
c52eee2498f6d567bd48e5a7a4046b51
|
||||
a401da6fb4349055cf343cd44a974497
|
||||
2def25c6377a6c1b069003f838f6ec31
|
||||
67331ed57c8a7f189727f84e9a5469ec
|
||||
8d279d4327fe50f2ff21ee53eb6cf015
|
||||
9e5e8223bdc1abbfba3cb65700452b70
|
||||
eb211e3d53c1fe49e89aa6ad729fc13c
|
||||
fc5a3e258868dfb2aedde494f8f475cd
|
||||
1fe91a15c43a4ad8f7007234de8ac222
|
||||
9d43fdbd942765778da18fa223f43cc6
|
||||
733b6b775a4a0421fc091216705ef652
|
||||
9ab696e0d1dac6105f84ea8ca9465756
|
||||
d5686e35b935d8990e5dee6d1bcf4723
|
||||
706aba57f814480fa8a17e078b6352f8
|
||||
0e5f8befc07ff3317ca174fd587e25bc
|
||||
f95f242b2e2eafea3f5a20cdf0c34d89
|
||||
0f1a8078ce533787c9cb30c9429dd450
|
||||
a226c15cc7e0c0b7e1c4de8c620c07b7
|
||||
36edee26f6f02c1576019034648cd4c8
|
||||
c79dea598375f06c3a24d75f5b77e8cb
|
||||
eb6fd06a0916ba6d60be7f4ad8561c0b
|
||||
d022fdb8aa0e7cdc1fd83680b9b8a0fd
|
||||
b5bb9c5a4cb36aa9c6b4b58076268d3d
|
||||
38416e5de6f0092bbf61efe8ed7f76cb
|
||||
a2ce2bf815fffc1fca8d99a4c97afb50
|
||||
6beb9dbfdc0edb74498a49c2d81106b3
|
||||
7b13ce77fc66031f036a8667076790f2
|
||||
569dcc3536bb44110d6b3a54bf0dee88
|
||||
6e08f74630a07154df6461ae25a955c9
|
||||
af8235bf965e972834a181f9210b9e09
|
||||
f9e4b7b0ffbbb24431e2ec6b6b337436
|
||||
d87cfba6630357b213316c6608583be5
|
||||
ec3382387784e715ea321fc05ce95372
|
||||
bc0fef2061b2e64ab5a40e0174a49ea7
|
||||
91d25f646606b2f9992688ef4edf4abb
|
||||
008e89f1070b0d8e2477d7e9fb9a86c9
|
||||
69bf6be50b0c1f9305f2522a1b01deef
|
||||
11db65d54b6c6b6c39062a3beaf82427
|
||||
efe6a4e563d48c7c86341a0f30e8fefe
|
||||
8df7408615732649f592c7830cb0f00e
|
||||
2fe4ec387ce34336dd8b968dac027b62
|
||||
0ae9182a07b24f27298e8eff4a74b541
|
||||
c0215167505dc21651dbef4565d0ae9c
|
||||
4453511aab349d4e9444949ce7b5c138
|
||||
29921ddff3b0ba42de2b36249f06a70a
|
||||
9e8fb2d42c7c90f64c346023186c1d46
|
||||
76e252269587a07138b016f57d09d5d4
|
||||
a3d3e21942d46be6f98cac225bac9fa1
|
||||
47ebd96106eb758c2ab1d7f6325a46ae
|
||||
fea2c0b6ff517b07aec90c975d3e929f
|
||||
a58220f49b95e399365f5a9c083923d6
|
||||
0ba65b271d8d95d5b1c4ba96a6281e48
|
||||
980589673748a3e2cfd37920e330d1e2
|
||||
5c800089bdd1626fb2d3b9b51520b2a1
|
||||
5aea26dea4436520f9643ed92409daae
|
||||
48499bbd0c06d1873249ddd2a5dc76a0
|
||||
6905e3dc0ee6930b3678d56898bfc5ef
|
||||
76c17247950a478a67b6adc5e4baa195
|
||||
18c123fbb6833207b27e7a400529a607
|
||||
9a41b6d8f3c52ebd3a1517fb822d09e1
|
||||
10cb9cde61fe48ed38bbe9d50755eaaf
|
||||
1899fe52ca9524948a641acba633c087
|
||||
03b254591b13d993575b960b8a5e5bfe
|
||||
2cbcffec5b410eb6ecdd05c48e7f0526
|
||||
69fd3b0f432bb70404a2a8deddd33856
|
||||
f0a8ad93606ae12b229aff1ec0e2c3ee
|
||||
580ae926ff57ac9ae54f35b000012b97
|
||||
87e3e0f6204e2ad502eb5153c65b2632
|
||||
d129b9408e42287088d7613d3bfbcd12
|
||||
dbc5c280fb9001496ed8b45602b4de1b
|
||||
f9c1efcff8549db819571d9a6cd55480
|
||||
9d0a444c988a2027cbc41c2163be63db
|
||||
1070020a0a6afb6c597d61b8524381ba
|
||||
a21ab68bc243e08536feb2624647f7db
|
||||
2e718c27e0d3266618907e40cc4a28a6
|
||||
4669c75778da3184aba250b6310b6d1b
|
||||
4e841515c3647e65a00b63dee84e67eb
|
||||
f867fae5ebf1ed714c5a70f3fe10adbc
|
||||
90cacccf6932ba8660e08b586f7597f7
|
||||
cb0774848fc16d1536418ab7e5ada88a
|
||||
c3e74fbbd564f993828e8cbeb876f31e
|
||||
90fe0f8363b9a3fb33b9b2bb4db254a4
|
||||
d38e11dcb17af7dd7e700355aa92e003
|
||||
9bb3f94f84033c4937adf84586e2be95
|
||||
4b4aedbc5dfa015b897b765e566feb96
|
||||
acd1ceb766b0a4e15f25870f0244f549
|
||||
32119d41be592c1f669ffd0b3a7888eb
|
||||
7e7775b06fa9b72f771ec6b8d481a6d0
|
||||
4415957d5a0d1526db1c19bc17b5d395
|
||||
334f9ae780437dca2a5fc3377d46f6e5
|
||||
35c3b43d748aad62f7410306ea3c3f99
|
||||
1ab4c3340132c3f5fa6ef55ba7b08127
|
||||
a61818d54edda2029762e87b9b431c34
|
||||
1af968ff49189f25e28b9d8afcd46a92
|
||||
80a8b78662dcd2aa4b1615b1325757a2
|
||||
10ad96ed6e46f5b054603f66ed6bf29b
|
||||
362457c91a605e04d3334b03b349ec78
|
||||
7704dc866db3238023317a0eac5b8249
|
||||
4231732950cb318403a45f38a46ad46b
|
||||
a8482c9229879f410b309fc77983e3d1
|
||||
45063084d5e5dfab462adaf5fcb43795
|
||||
5028d5d62a02bf9c6843602c9843f54d
|
||||
bc0d1e086e96d1347cb800a1d1aae611
|
||||
343d41fca235543df80e8ecee45f2248
|
||||
e68ed7bc5e4f4d5b92202011cdcda940
|
||||
d8735dcc4a5b6f28f8724616e256b759
|
||||
c261e7a13a9b507af279ed948acbe386
|
||||
4d5af215399a10792b02e6a372aadb02
|
||||
381abc836088178c39cc7da9b588b5a3
|
||||
406e3372afbd23924539b43f6fb9dc63
|
||||
032c29160e0529defc053d863b543926
|
||||
eb4b9c324770d1f900191833ccc4c603
|
||||
7540374c617d048db93719f2f74869e0
|
||||
8d851c4ee3ed3b42a8f8abc37dc037d9
|
||||
0cc60cb97509cd32f8de9a33248ec2a5
|
||||
bcb5a48c5816ecf1c701c1887fd72531
|
||||
2d0681c5f0c5daa293ac07c83ef0c852
|
||||
936265a67adf4d3bc271d0ef0513c4bf
|
||||
58e976105d9eb6e0715927143ddfad37
|
||||
b6a82350ee6b0f00fb5323b32e8b8dd4
|
||||
5cbc1b4da91aed315f7988ee9b4f6ae7
|
||||
446945e9f46db22cc480474572957f43
|
||||
8cdbbe21312c86754be28c1af186c85f
|
||||
c696ac596e16de134f9026c2fc89fcb9
|
||||
ac368b1a10748d0e18452527fdb67cd9
|
||||
f1eb8157957c7a2a84b5310c2825c5fd
|
||||
7fd5969b27e06110ee5eb52c0b7f895f
|
||||
93a4c8093abe3a4e37cb317584adec57
|
||||
3495c733a5c07428d7014799147f903b
|
||||
5fb650661b0891f52cc9d908e3425033
|
||||
399e846bf54ea08b854adc57b33bb940
|
||||
5377988dae4eb18ac1d50e0add7938a1
|
||||
f6f14c9520459a1bde9378e1faa59ca0
|
||||
93cce870e1a92b6ab841ccf2a27b2770
|
||||
7f79cd2127ac7c289414e993005828a6
|
||||
ed4d0faa60421d65cbefcc8ea9a83f00
|
||||
8c6b723120855441c69ef4ddd7e5194b
|
||||
9d675f33810dcac032f38e224e4bb65d
|
||||
a99ad6ea3c77cfb3dd94f8b39a909a34
|
||||
9d2f4e964ba17ef2c7d0496231b1c47b
|
||||
a6f1e6419f610d1f33888bcc7b16c6d1
|
||||
f7e92cd6ee5c8aa99f7be89fd4b9f79f
|
||||
9f95bf8beae3f90eeaa45f8a6003f0aa
|
||||
361311a47b6cefc08b8e8af37fd68038
|
||||
afc33b5877411da4349e013560a15e17
|
||||
3811debaef6b08f2e9338e9d04f6f02c
|
||||
7fd97c64677f845e98a4a0dc97a7711c
|
||||
7a7392b1f2525842393dba40e92077cb
|
||||
24ec025a0d2e7c2a57ff336f0a9baba1
|
||||
72f4ad6e07a788cd9356f01c92d94c8a
|
||||
3a801f8e16b8fc40b03be96b9db43e08
|
||||
c89e78f74a7dd4c649413129534b9367
|
||||
026609682e9ef750ea8e3f196c017c56
|
||||
2cc76327407153e015d1ae69f14f643f
|
||||
9aa38da2abb78d2b237f89ccf3fd052d
|
||||
e6f44e4485cef73ffa5a649aaeb92293
|
||||
ed8a7d1abce5a3f45d771ded4b8d0ddf
|
||||
b477e09272a812756950266e6d9d21a3
|
||||
7f7822c20694075ba7f74a1cc58ed326
|
||||
da4bd23e36c0cd2683ad3a08ccb92381
|
||||
a9e2a88720bfa413e5e3b2fd4c1306bc
|
||||
3e91bbfe96a7462186151f6e76e36233
|
||||
8fb00375e1d7595fa4544359f349eaed
|
||||
106d0cc1aad3fa1014b8a34ebe7a4a3c
|
||||
1692448922564e97d70d04cc12d55e81
|
||||
3537b9b91dcae2df5d3a09805a7ffc4d
|
||||
111ddd889dc595e18f97a93e36f7d3f2
|
||||
8e8875c00cfc5394e731e8c2d4846f07
|
||||
3f728c9ea9954a093405040047271fb4
|
||||
f93ef278c1c3dff430da16ee3596a83f
|
||||
51c0bd2ac650dd07df5b3630aee3705e
|
||||
f33a19dd85ef67cdf5a0d92f3f1fbecf
|
||||
0a6a7457190fa0fa9989dc71fee2152b
|
||||
f4b157a6ef24d5d2a1027120fc419a91
|
||||
dd69ac1443293ea2f753eda6e94101bc
|
||||
0f431525cb6ac56aa05484cf2a067303
|
||||
4aa02c74b3054a4ae3b13a5fbdeccd1f
|
||||
4f7c553a7830426c493b59685b371223
|
||||
738fc54e143b460de42f6279a9ef1672
|
||||
a437cbbd9abf89d9e8b1e69b4ef48b86
|
||||
afe6cb281c9ce601a1f4ab4d673f734f
|
||||
4756e9d1ebb100cdf77ff9e6f9d182f6
|
||||
a65853ac0ade5103a5840542e6e16c32
|
||||
f5abe519e7bf7078d4c581ed477b80bc
|
||||
d75c1f88e47baeef857fc43e8ad25ba0
|
||||
7200f686415f52d0d3ac0684eca69fb2
|
||||
31674be1dabca7c3fabef1c7481d6538
|
||||
4aa521f1811346604b61a29c658ab020
|
||||
fd049c4f80a58b6e2f4aea4d02b941d7
|
||||
ef6b331ba064b6637de7e821d5d6d935
|
||||
f23fa47c8cc237fa2f878b0bfc508986
|
||||
@@ -0,0 +1,7 @@
|
||||
// Copyright 2023 The ChromiumOS Authors
|
||||
// Use of this source code is governed by a BSD-style license that can be
|
||||
// found in the LICENSE file.
|
||||
|
||||
pub mod dpb;
|
||||
pub mod parser;
|
||||
pub mod picture;
|
||||
@@ -0,0 +1,297 @@
|
||||
// Copyright 2023 The ChromiumOS Authors
|
||||
// Use of this source code is governed by a BSD-style license that can be
|
||||
// found in the LICENSE file.
|
||||
|
||||
use std::cell::Ref;
|
||||
use std::cell::RefCell;
|
||||
use std::cell::RefMut;
|
||||
use std::rc::Rc;
|
||||
|
||||
use crate::codec::h265::parser::Sps;
|
||||
use crate::codec::h265::picture::PictureData;
|
||||
use crate::codec::h265::picture::Reference;
|
||||
|
||||
// Shortcut to refer to a DPB entry.
|
||||
//
|
||||
// The first member of the tuple is the `PictureData` for the frame.
|
||||
//
|
||||
// The second member is the backend handle of the frame.
|
||||
#[derive(Clone, Debug)]
|
||||
pub struct DpbEntry<T>(pub Rc<RefCell<PictureData>>, pub T);
|
||||
|
||||
pub struct Dpb<T> {
|
||||
/// List of `PictureData` and backend handles to decoded pictures.
|
||||
entries: Vec<DpbEntry<T>>,
|
||||
/// The maximum number of pictures that can be stored.
|
||||
max_num_pics: usize,
|
||||
}
|
||||
|
||||
impl<T: Clone> Dpb<T> {
|
||||
/// Returns an iterator over the underlying H265 pictures stored in the
|
||||
/// DPB.
|
||||
pub fn pictures(&self) -> impl Iterator<Item = Ref<'_, PictureData>> {
|
||||
self.entries.iter().map(|h| h.0.borrow())
|
||||
}
|
||||
|
||||
/// Returns a mutable iterator over the underlying H265 pictures stored in
|
||||
/// the DPB.
|
||||
pub fn pictures_mut(&mut self) -> impl Iterator<Item = RefMut<'_, PictureData>> {
|
||||
self.entries.iter().map(|h| h.0.borrow_mut())
|
||||
}
|
||||
|
||||
/// Returns the length of the DPB.
|
||||
pub fn len(&self) -> usize {
|
||||
self.entries.len()
|
||||
}
|
||||
|
||||
pub fn is_empty(&self) -> bool {
|
||||
self.len() == 0
|
||||
}
|
||||
|
||||
/// Get a reference to the whole DPB entries.
|
||||
pub fn entries(&self) -> &Vec<DpbEntry<T>> {
|
||||
&self.entries
|
||||
}
|
||||
|
||||
/// Set the dpb's max num pics.
|
||||
pub fn set_max_num_pics(&mut self, max_num_pics: usize) {
|
||||
self.max_num_pics = max_num_pics;
|
||||
}
|
||||
|
||||
/// Get a reference to the dpb's max num pics.
|
||||
pub fn max_num_pics(&self) -> usize {
|
||||
self.max_num_pics
|
||||
}
|
||||
|
||||
/// Mark all pictures in the DPB as unused for reference.
|
||||
pub fn mark_all_as_unused_for_ref(&mut self) {
|
||||
for mut picture in self.pictures_mut() {
|
||||
picture.set_reference(Reference::None);
|
||||
}
|
||||
}
|
||||
|
||||
/// Gets the position of `needle` in the DPB, if any.
|
||||
fn get_position(&self, needle: &Rc<RefCell<PictureData>>) -> Option<usize> {
|
||||
self.entries
|
||||
.iter()
|
||||
.position(|handle| Rc::ptr_eq(&handle.0, needle))
|
||||
}
|
||||
|
||||
/// Finds a reference picture in the DPB using `poc`.
|
||||
pub fn find_ref_by_poc(&self, poc: i32) -> Option<DpbEntry<T>> {
|
||||
let position = self
|
||||
.pictures()
|
||||
.position(|p| p.is_ref() && p.pic_order_cnt_val == poc);
|
||||
|
||||
log::debug!("find_ref_by_poc: {}, found position {:?}", poc, position);
|
||||
Some(self.entries[position?].clone())
|
||||
}
|
||||
|
||||
/// Finds a reference picture in the DPB using `poc` and `mask`.
|
||||
pub fn find_ref_by_poc_masked(&self, poc: i32, mask: i32) -> Option<DpbEntry<T>> {
|
||||
let position = self
|
||||
.pictures()
|
||||
.position(|p| p.is_ref() && p.pic_order_cnt_val & mask == poc);
|
||||
|
||||
log::debug!("find_ref_by_poc: {}, found position {:?}", poc, position);
|
||||
Some(self.entries[position?].clone())
|
||||
}
|
||||
|
||||
/// Finds a short term reference picture in the DPB using `poc`.
|
||||
pub fn find_short_term_ref_by_poc(&self, poc: i32) -> Option<DpbEntry<T>> {
|
||||
let position = self.pictures().position(|p| {
|
||||
matches!(p.reference(), Reference::ShortTerm) && p.pic_order_cnt_val == poc
|
||||
});
|
||||
|
||||
log::debug!(
|
||||
"find_short_term_ref_by_poc: {}, found position {:?}",
|
||||
poc,
|
||||
position
|
||||
);
|
||||
Some(self.entries[position?].clone())
|
||||
}
|
||||
|
||||
/// Drains the DPB by continuously invoking the bumping process.
|
||||
pub fn drain(&mut self) -> Vec<DpbEntry<T>> {
|
||||
log::debug!("Draining the DPB.");
|
||||
|
||||
let mut pics = vec![];
|
||||
while let Some(pic) = self.bump(true) {
|
||||
pics.push(pic);
|
||||
}
|
||||
|
||||
pics
|
||||
}
|
||||
|
||||
/// Whether the DPB needs bumping. See C.5.2.2.
|
||||
pub fn needs_bumping(&mut self, sps: &Sps) -> bool {
|
||||
let num_needed_for_output = self.pictures().filter(|pic| pic.needed_for_output).count();
|
||||
|
||||
let highest_tid = sps.max_sub_layers_minus1;
|
||||
let max_num_reorder_pics = sps.max_num_reorder_pics[usize::from(highest_tid)];
|
||||
let max_latency_increase_plus1 = sps.max_latency_increase_plus1[usize::from(highest_tid)];
|
||||
let pic_over_max_latency = self.pictures().find(|pic| {
|
||||
pic.needed_for_output && pic.pic_latency_cnt >= i32::from(max_latency_increase_plus1)
|
||||
});
|
||||
let max_dec_pic_buffering =
|
||||
usize::from(sps.max_dec_pic_buffering_minus1[usize::from(highest_tid)]) + 1;
|
||||
|
||||
num_needed_for_output > max_num_reorder_pics.into()
|
||||
|| (max_latency_increase_plus1 != 0 && pic_over_max_latency.is_some())
|
||||
|| self.entries().len() >= max_dec_pic_buffering
|
||||
}
|
||||
|
||||
/// Find the lowest POC in the DPB that can be bumped.
|
||||
fn find_lowest_poc_for_bumping(&self) -> Option<DpbEntry<T>> {
|
||||
let lowest = self
|
||||
.pictures()
|
||||
.filter(|pic| pic.needed_for_output)
|
||||
.min_by_key(|pic| pic.pic_order_cnt_val)?;
|
||||
|
||||
let position = self
|
||||
.entries
|
||||
.iter()
|
||||
.position(|handle| handle.0.borrow().pic_order_cnt_val == lowest.pic_order_cnt_val)
|
||||
.unwrap();
|
||||
|
||||
Some(self.entries[position].clone())
|
||||
}
|
||||
|
||||
/// See C.5.2.4 "Bumping process".
|
||||
pub fn bump(&mut self, flush: bool) -> Option<DpbEntry<T>> {
|
||||
let handle = self.find_lowest_poc_for_bumping()?;
|
||||
let mut pic = handle.0.borrow_mut();
|
||||
|
||||
pic.needed_for_output = false;
|
||||
log::debug!("Bumping POC {} from the dpb", pic.pic_order_cnt_val);
|
||||
log::trace!("{:#?}", pic);
|
||||
|
||||
if !pic.is_ref() || flush {
|
||||
let index = self.get_position(&handle.0).unwrap();
|
||||
|
||||
log::debug!(
|
||||
"Removed POC {} from the dpb: reference: {}, flush: {}",
|
||||
pic.pic_order_cnt_val,
|
||||
pic.is_ref(),
|
||||
flush
|
||||
);
|
||||
log::trace!("{:#?}", pic);
|
||||
|
||||
self.entries.remove(index);
|
||||
}
|
||||
|
||||
Some(handle.clone())
|
||||
}
|
||||
|
||||
/// See C.5.2.3. Happens when we are done decoding the picture.
|
||||
pub fn needs_additional_bumping(&mut self, sps: &Sps) -> bool {
|
||||
let num_needed_for_output = self.pictures().filter(|pic| pic.needed_for_output).count();
|
||||
let highest_tid = sps.max_sub_layers_minus1;
|
||||
|
||||
let max_num_reorder_pics = sps.max_num_reorder_pics[usize::from(highest_tid)];
|
||||
let max_latency_increase_plus1 = sps.max_latency_increase_plus1[usize::from(highest_tid)];
|
||||
|
||||
let pic_over_max_latency = self.pictures().find(|pic| {
|
||||
pic.needed_for_output && pic.pic_latency_cnt >= i32::from(max_latency_increase_plus1)
|
||||
});
|
||||
|
||||
num_needed_for_output > max_num_reorder_pics.into()
|
||||
|| (max_latency_increase_plus1 != 0 && pic_over_max_latency.is_some())
|
||||
}
|
||||
|
||||
/// Clears the DPB, dropping all the pictures.
|
||||
pub fn clear(&mut self) {
|
||||
log::debug!("Clearing the DPB");
|
||||
|
||||
let max_num_pics = self.max_num_pics;
|
||||
|
||||
*self = Default::default();
|
||||
self.max_num_pics = max_num_pics;
|
||||
}
|
||||
|
||||
/// Removes all pictures which are marked as "not needed for output" and
|
||||
/// "unused for reference". See C.5.2.2
|
||||
pub fn remove_unused(&mut self) {
|
||||
log::debug!("Removing unused pictures from DPB.");
|
||||
self.entries.retain(|e| {
|
||||
let pic = e.0.borrow();
|
||||
let retain = pic.needed_for_output || pic.is_ref();
|
||||
log::debug!("Retaining pic POC: {}: {}", pic.pic_order_cnt_val, retain);
|
||||
retain
|
||||
})
|
||||
}
|
||||
|
||||
/// Store a picture and its backend handle in the DPB.
|
||||
pub fn store_picture(
|
||||
&mut self,
|
||||
picture: Rc<RefCell<PictureData>>,
|
||||
handle: T,
|
||||
) -> Result<(), String> {
|
||||
if self.entries.len() >= self.max_num_pics {
|
||||
return Err("Can't add a picture to the DPB: DPB is full.".into());
|
||||
}
|
||||
|
||||
let mut pic = picture.borrow_mut();
|
||||
log::debug!(
|
||||
"Stored picture POC {:?}, the DPB length is {:?}",
|
||||
pic.pic_order_cnt_val,
|
||||
self.entries.len()
|
||||
);
|
||||
|
||||
if pic.pic_output_flag {
|
||||
pic.needed_for_output = true;
|
||||
pic.pic_latency_cnt = 0;
|
||||
} else {
|
||||
pic.needed_for_output = false;
|
||||
}
|
||||
|
||||
// C.3.4.
|
||||
// After all the slices of the current picture have been decoded, this
|
||||
// picture is marked as "used for short-term reference".
|
||||
pic.set_reference(Reference::ShortTerm);
|
||||
drop(pic);
|
||||
|
||||
for mut pic in self.pictures_mut() {
|
||||
pic.pic_latency_cnt += 1;
|
||||
}
|
||||
|
||||
self.entries.push(DpbEntry(picture, handle));
|
||||
|
||||
Ok(())
|
||||
}
|
||||
|
||||
/// Returns all the references in the DPB.
|
||||
pub fn get_all_references(&self) -> Vec<DpbEntry<T>> {
|
||||
self.entries
|
||||
.iter()
|
||||
.filter(|e| e.0.borrow().is_ref())
|
||||
.cloned()
|
||||
.collect()
|
||||
}
|
||||
}
|
||||
|
||||
impl<T: Clone> Default for Dpb<T> {
|
||||
fn default() -> Self {
|
||||
// See https://github.com/rust-lang/rust/issues/26925 on why this can't
|
||||
// be derived.
|
||||
Self {
|
||||
entries: Default::default(),
|
||||
max_num_pics: Default::default(),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl<T: Clone> std::fmt::Debug for Dpb<T> {
|
||||
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
|
||||
let pics = self
|
||||
.entries
|
||||
.iter()
|
||||
.map(|h| &h.0)
|
||||
.enumerate()
|
||||
.collect::<Vec<_>>();
|
||||
f.debug_struct("Dpb")
|
||||
.field("pictures", &pics)
|
||||
.field("max_num_pics", &self.max_num_pics)
|
||||
.finish()
|
||||
}
|
||||
}
|
||||
File diff suppressed because it is too large
Load Diff
@@ -0,0 +1,158 @@
|
||||
// Copyright 2023 The ChromiumOS Authors
|
||||
// Use of this source code is governed by a BSD-style license that can be
|
||||
// found in the LICENSE file.
|
||||
|
||||
use crate::codec::h265::parser::NaluType;
|
||||
use crate::codec::h265::parser::Slice;
|
||||
|
||||
#[derive(Copy, Clone, Debug, Default, PartialEq, Eq)]
|
||||
pub enum Reference {
|
||||
#[default]
|
||||
None,
|
||||
ShortTerm,
|
||||
LongTerm,
|
||||
}
|
||||
|
||||
/// Data associated with an h.265 picture. Most fields are extracted from the
|
||||
/// slice header and kept for future processing.
|
||||
#[derive(Debug, Default, Clone, Eq, PartialEq)]
|
||||
pub struct PictureData {
|
||||
// Fields extracted from the slice header. These are the CamelCase
|
||||
// variables, unless noted otherwise.
|
||||
pub nalu_type: NaluType,
|
||||
pub no_rasl_output_flag: bool,
|
||||
pub pic_output_flag: bool,
|
||||
pub valid_for_prev_tid0_pic: bool,
|
||||
pub slice_pic_order_cnt_lsb: i32,
|
||||
pub pic_order_cnt_msb: i32,
|
||||
pub pic_order_cnt_val: i32,
|
||||
pub no_output_of_prior_pics_flag: bool,
|
||||
|
||||
// Internal state.
|
||||
pub first_picture_after_eos: bool,
|
||||
reference: Reference,
|
||||
pub pic_latency_cnt: i32,
|
||||
pub needed_for_output: bool,
|
||||
pub short_term_ref_pic_set_size_bits: u32,
|
||||
}
|
||||
|
||||
impl PictureData {
|
||||
/// Instantiates a new `PictureData` from a slice.
|
||||
///
|
||||
/// See 8.1.3 Decoding process for a coded picture with nuh_layer_id equal
|
||||
/// to 0.
|
||||
///
|
||||
/// This will also call the picture order count process (clause 8.3.1) to
|
||||
/// correctly initialize the POC values.
|
||||
pub fn new_from_slice(
|
||||
slice: &Slice,
|
||||
first_picture_in_bitstream: bool,
|
||||
first_picture_after_eos: bool,
|
||||
prev_tid0_pic: Option<&PictureData>,
|
||||
max_pic_order_cnt_lsb: i32,
|
||||
) -> Self {
|
||||
let hdr = &slice.header;
|
||||
let nalu_type = slice.nalu.header.type_;
|
||||
|
||||
// We assume HandleCraAsBlafFLag == 0, as it is only set through
|
||||
// external means, which we do not provide.
|
||||
|
||||
let mut pic_order_cnt_msb = 0;
|
||||
let slice_pic_order_cnt_lsb: i32 = hdr.pic_order_cnt_lsb.into();
|
||||
|
||||
// Compute the output flags:
|
||||
// The value of NoRaslOutputFlag is equal to 1 for each IDR access
|
||||
// unit, each BLA access unit, and each CRA access unit that is the
|
||||
// first access unit in the bitstream in decoding order, is the first
|
||||
// access unit that follows an end of sequence NAL unit in decoding
|
||||
// order, or has HandleCraAsBlaFlag equal to 1.
|
||||
let no_rasl_output_flag = nalu_type.is_idr()
|
||||
|| nalu_type.is_bla()
|
||||
|| (nalu_type.is_cra() && first_picture_in_bitstream)
|
||||
|| first_picture_after_eos;
|
||||
|
||||
let pic_output_flag = if slice.nalu.header.type_.is_rasl() && no_rasl_output_flag {
|
||||
false
|
||||
} else {
|
||||
hdr.pic_output_flag
|
||||
};
|
||||
|
||||
// Compute the Picture Order Count. See 8.3.1 Decoding Process for
|
||||
// Picture Order Count
|
||||
if !(nalu_type.is_irap() && no_rasl_output_flag) {
|
||||
if let Some(prev_tid0_pic) = prev_tid0_pic {
|
||||
// Equation (8-1)
|
||||
let prev_pic_order_cnt_lsb = prev_tid0_pic.slice_pic_order_cnt_lsb;
|
||||
let prev_pic_order_cnt_msb = prev_tid0_pic.pic_order_cnt_msb;
|
||||
if (slice_pic_order_cnt_lsb < prev_pic_order_cnt_lsb)
|
||||
&& (prev_pic_order_cnt_lsb - slice_pic_order_cnt_lsb)
|
||||
>= (max_pic_order_cnt_lsb / 2)
|
||||
{
|
||||
pic_order_cnt_msb = prev_pic_order_cnt_msb + max_pic_order_cnt_lsb;
|
||||
} else if (slice_pic_order_cnt_lsb > prev_pic_order_cnt_lsb)
|
||||
&& (slice_pic_order_cnt_lsb - prev_pic_order_cnt_lsb)
|
||||
> (max_pic_order_cnt_lsb / 2)
|
||||
{
|
||||
pic_order_cnt_msb = prev_pic_order_cnt_msb - max_pic_order_cnt_lsb;
|
||||
} else {
|
||||
pic_order_cnt_msb = prev_pic_order_cnt_msb;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Compute whether this picture will be a valid prevTid0Pic, i.e.:
|
||||
//
|
||||
// Let prevTid0Pic be the previous picture in decoding order that has
|
||||
// TemporalId equal to 0 and that is not a RASL, RADL or SLNR picture.
|
||||
//
|
||||
// Use this flag to correctly set up the field in the decoder during
|
||||
// `finish_picture`.
|
||||
let valid_for_prev_tid0_pic = slice.nalu.header.nuh_temporal_id() == 0
|
||||
&& !nalu_type.is_radl()
|
||||
&& !nalu_type.is_rasl()
|
||||
&& !nalu_type.is_slnr();
|
||||
|
||||
let no_output_of_prior_pics_flag =
|
||||
if nalu_type.is_irap() && no_rasl_output_flag && !first_picture_in_bitstream {
|
||||
nalu_type.is_cra() || hdr.no_output_of_prior_pics_flag
|
||||
} else {
|
||||
false
|
||||
};
|
||||
|
||||
Self {
|
||||
nalu_type,
|
||||
no_rasl_output_flag,
|
||||
no_output_of_prior_pics_flag,
|
||||
pic_output_flag,
|
||||
valid_for_prev_tid0_pic,
|
||||
slice_pic_order_cnt_lsb,
|
||||
pic_order_cnt_msb,
|
||||
// Equation (8-2)
|
||||
pic_order_cnt_val: pic_order_cnt_msb + slice_pic_order_cnt_lsb,
|
||||
first_picture_after_eos,
|
||||
reference: Default::default(),
|
||||
pic_latency_cnt: 0,
|
||||
needed_for_output: false,
|
||||
short_term_ref_pic_set_size_bits: hdr.st_rps_bits,
|
||||
}
|
||||
}
|
||||
|
||||
/// Whether the current picture is a reference, either ShortTerm or LongTerm.
|
||||
pub fn is_ref(&self) -> bool {
|
||||
!matches!(self.reference, Reference::None)
|
||||
}
|
||||
|
||||
pub fn set_reference(&mut self, reference: Reference) {
|
||||
log::debug!(
|
||||
"Set reference of POC {} to {:?}",
|
||||
self.pic_order_cnt_val,
|
||||
reference
|
||||
);
|
||||
|
||||
self.reference = reference;
|
||||
}
|
||||
|
||||
pub fn reference(&self) -> &Reference {
|
||||
&self.reference
|
||||
}
|
||||
}
|
||||
BIN
Binary file not shown.
+3
@@ -0,0 +1,3 @@
|
||||
2904d4d2
|
||||
d8e11777
|
||||
a1108fed
|
||||
+3
@@ -0,0 +1,3 @@
|
||||
c45648e1d3dd68913e998bd6ddb0f633
|
||||
8a44f604b2518c6caa7bb422907c8d17
|
||||
fe6ac1f24e248f3dba54087245380dd8
|
||||
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user