Compare commits
23
Commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
db15c2615d | ||
|
|
5c6b09a5c5 | ||
|
|
9c10b97e31 | ||
|
|
31087697a9 | ||
|
|
2a57ee36f8 | ||
|
|
e4d8573475 | ||
|
|
6d8f3b45b5 | ||
|
|
c985438db1 | ||
|
|
55bc664eca | ||
|
|
a34f4051fc | ||
|
|
370b0ab494 | ||
|
|
cf5db2d485 | ||
|
|
dc0766b2f3 | ||
|
|
e6d6498a49 | ||
|
|
ca92dab6fd | ||
|
|
6331ae7fd9 | ||
|
|
d0659d2b61 | ||
|
|
540c0d3027 | ||
|
|
d24f7fc6ac | ||
|
|
0a359525a7 | ||
|
|
896bb47235 | ||
|
|
b5e54aea6e | ||
|
|
119ec0dd83 |
@@ -1,68 +0,0 @@
|
||||
#!/usr/bin/env bash
|
||||
# Assert that a builder image's :latest is the SAME manifest as its content key, and
|
||||
# re-point it when it isn't.
|
||||
#
|
||||
# This is what we do instead of pinning consumers by @sha256: digest
|
||||
# (security-review-2026-08-05, H-6 — see the reasoning at the top of docker.yml). The
|
||||
# content key is a hash of the ci/ tree, so "which image should :latest be?" has an
|
||||
# answer derivable from the commit alone. Checking it on every run turns :latest from a
|
||||
# tag someone remembered to move into a function of the tree.
|
||||
#
|
||||
# Two different things make them diverge and neither is distinguishable from here:
|
||||
#
|
||||
# - Someone overwrote :latest out of band. Post-fix that needs the push credential,
|
||||
# but it is exactly the H-6 attack and it must not pass silently.
|
||||
# - ci/ was reverted. The older key is already a cache hit, so nothing rebuilds and
|
||||
# nothing re-points :latest — it stays on the newer build forever while every
|
||||
# consumer pulls a builder that does not match the tree it is building. That bug
|
||||
# predates this script.
|
||||
#
|
||||
# Both are repaired identically, so: repair, and shout. Failing the build instead would
|
||||
# turn a legitimate revert into a red main with no way forward.
|
||||
#
|
||||
# Reads go to the anonymous port, the single write to the authenticated one.
|
||||
set -euo pipefail
|
||||
|
||||
IMAGE="${1:?usage: reconcile-latest.sh <image> <content-key>}"
|
||||
KEY="${2:?usage: reconcile-latest.sh <image> <content-key>}"
|
||||
: "${CI_REGISTRY:?CI_REGISTRY not set}"
|
||||
: "${CI_REGISTRY_PUSH:?CI_REGISTRY_PUSH not set}"
|
||||
: "${CI_REGISTRY_PASSWORD:?CI_REGISTRY_PASSWORD not set}"
|
||||
|
||||
ACCEPT='Accept: application/vnd.docker.distribution.manifest.v2+json, application/vnd.oci.image.manifest.v1+json, application/vnd.oci.image.index.v1+json, application/vnd.docker.distribution.manifest.list.v2+json'
|
||||
|
||||
# Digest of a tag, or empty if the tag does not exist. Never fails the script itself —
|
||||
# "missing" is a state this has to reason about, not an error to abort on.
|
||||
digest_of() {
|
||||
curl -sfI -H "$ACCEPT" "http://$CI_REGISTRY/v2/$IMAGE/manifests/$1" 2>/dev/null \
|
||||
| tr -d '\r' | sed -n 's/^[Dd]ocker-[Cc]ontent-[Dd]igest: //p' || true
|
||||
}
|
||||
|
||||
key_digest=$(digest_of "$KEY")
|
||||
latest_digest=$(digest_of latest)
|
||||
|
||||
if [ -z "$key_digest" ]; then
|
||||
echo "::error::$IMAGE:$KEY has no manifest — the build or push above did not land"
|
||||
exit 1
|
||||
fi
|
||||
|
||||
if [ "$key_digest" = "$latest_digest" ]; then
|
||||
echo "$IMAGE:latest == :$KEY ($key_digest)"
|
||||
exit 0
|
||||
fi
|
||||
|
||||
echo "::warning::$IMAGE:latest did not match its content key :$KEY — re-pointing it. If ci/ was not just reverted, someone overwrote this tag out of band: check the registry access log on home-ci-core."
|
||||
echo " was: ${latest_digest:-<no :latest tag>}"
|
||||
echo " wanted: $key_digest (:$KEY)"
|
||||
|
||||
tmp=$(mktemp)
|
||||
trap 'rm -f "$tmp"' EXIT
|
||||
media_type=$(curl -sfI -H "$ACCEPT" "http://$CI_REGISTRY/v2/$IMAGE/manifests/$KEY" \
|
||||
| tr -d '\r' | sed -n 's/^[Cc]ontent-[Tt]ype: //p')
|
||||
curl -sf -H "$ACCEPT" -o "$tmp" "http://$CI_REGISTRY/v2/$IMAGE/manifests/$KEY"
|
||||
curl -sf -u "ci:$CI_REGISTRY_PASSWORD" -X PUT -H "Content-Type: $media_type" \
|
||||
--data-binary @"$tmp" "http://$CI_REGISTRY_PUSH/v2/$IMAGE/manifests/latest"
|
||||
|
||||
now=$(digest_of latest)
|
||||
[ "$now" = "$key_digest" ] || { echo "::error::re-point failed: :latest is $now"; exit 1; }
|
||||
echo "$IMAGE:latest re-pointed to $key_digest"
|
||||
@@ -41,23 +41,9 @@ jobs:
|
||||
env:
|
||||
REGISTRY_TOKEN: ${{ secrets.REGISTRY_TOKEN }}
|
||||
UPDATE_MANIFEST_KEY: ${{ secrets.UPDATE_MANIFEST_KEY }}
|
||||
# Through the ENVIRONMENT, never interpolated into the script body. A `${{ }}` expansion
|
||||
# is a raw textual substitution performed BEFORE the shell sees the line, so a
|
||||
# workflow_dispatch input containing shell syntax executes as this step — and this is the
|
||||
# step holding UPDATE_MANIFEST_KEY, the Ed25519 key every host pins to decide whether an
|
||||
# update is real (2026-08-05 review H-6). As `$INPUT_TAG` it is only ever data.
|
||||
INPUT_TAG: ${{ inputs.tag }}
|
||||
run: |
|
||||
set -euo pipefail
|
||||
TAG="$INPUT_TAG"
|
||||
# Shape-check before the value reaches a URL or a filename: tags are `vX.Y.Z[-suffix]`.
|
||||
case "$TAG" in
|
||||
v[0-9]*) ;;
|
||||
*) echo "refusing to publish for a tag that is not vX.Y.Z: $TAG" >&2; exit 1 ;;
|
||||
esac
|
||||
case "$TAG" in
|
||||
*[!A-Za-z0-9.+_-]*) echo "tag has characters no release tag has: $TAG" >&2; exit 1 ;;
|
||||
esac
|
||||
TAG="${{ inputs.tag }}"
|
||||
case "$TAG" in
|
||||
*-*) echo "pre-release tag $TAG — not publishing to the stable update feed"; exit 0 ;;
|
||||
esac
|
||||
@@ -81,7 +67,4 @@ jobs:
|
||||
GITEA_TOKEN: ${{ secrets.REGISTRY_TOKEN }}
|
||||
DISCORD_RELEASE_WEBHOOK: ${{ secrets.DISCORD_RELEASE_WEBHOOK }}
|
||||
ALLOW_PRERELEASE: ${{ inputs.allow_prerelease }}
|
||||
# Same reasoning as the publish step above: the input is data in the environment, never
|
||||
# text spliced into the command line.
|
||||
INPUT_TAG: ${{ inputs.tag }}
|
||||
run: bash scripts/ci/discord-announce.sh "$INPUT_TAG"
|
||||
run: bash scripts/ci/discord-announce.sh "${{ inputs.tag }}"
|
||||
|
||||
@@ -29,9 +29,4 @@ jobs:
|
||||
steps:
|
||||
- uses: actions/checkout@v4
|
||||
- name: Tier-3 GPU stream benchmark
|
||||
# Through the environment, not interpolated into the command line: a `${{ }}` expansion is
|
||||
# substituted before the shell parses the line, so an input carrying shell syntax would run
|
||||
# as this step (2026-08-05 review H-6).
|
||||
env:
|
||||
BENCH_MODE: ${{ inputs.mode || '1920x1080x120' }}
|
||||
run: bash scripts/bench/gpu-stream.sh "$BENCH_MODE" 12
|
||||
run: bash scripts/bench/gpu-stream.sh "${{ inputs.mode || '1920x1080x120' }}" 12
|
||||
|
||||
@@ -46,10 +46,7 @@ env:
|
||||
REGISTRY: git.unom.io
|
||||
OWNER: unom
|
||||
PACKAGE: punktfunk-decky # generic-registry package name
|
||||
# The plugin's ON-DISK dir == the zip's top-level dir. Deliberately NOT plugin.json "name"
|
||||
# (that is the brand-cased label Decky lists, and it locates a plugin by matching it, not by
|
||||
# the folder) — see clients/decky/scripts/package.sh.
|
||||
PLUGIN: punktfunk
|
||||
PLUGIN: punktfunk # plugin.json "name" == zip top-level dir
|
||||
|
||||
jobs:
|
||||
build-publish:
|
||||
|
||||
+21
-107
@@ -3,18 +3,13 @@
|
||||
# Two very different image families now:
|
||||
#
|
||||
# BUILDER images (punktfunk-rust-ci{,-noble,-arm64cross}, punktfunk-fedora{,44}-rpm)
|
||||
# live on the LAN registry (home-ci-core — unom/infra runners/ci-core/) and are
|
||||
# CONTENT-KEYED: the tag is a hash of what they are built from (the ci/ tree, +
|
||||
# rust-toolchain.toml for the cross image), and a build only happens when that key
|
||||
# has no manifest yet. A push that doesn't touch ci/ costs one curl per image
|
||||
# (~seconds), pushes nothing over the WAN, and mints no per-SHA tag debris on the
|
||||
# runners — the failure mode that filled the fleet's disks. `:latest` is re-pushed
|
||||
# alongside every new key and is what the consuming workflows pin.
|
||||
#
|
||||
# READS come from :5010 and need no credential. WRITES go to :5011 and need
|
||||
# CI_REGISTRY_PASSWORD. Same store behind both — a registry keys by repository name,
|
||||
# not by the host:port the client used — so an image pushed to :5011 is the same
|
||||
# image every consumer pulls from :5010.
|
||||
# live on the LAN registry (home-ci-core, 192.168.1.58:5010 — unom/infra
|
||||
# runners/ci-core/) and are CONTENT-KEYED: the tag is a hash of what they are built
|
||||
# from (the ci/ tree, + rust-toolchain.toml for the cross image), and a build only
|
||||
# happens when that key has no manifest yet. A push that doesn't touch ci/ costs one
|
||||
# curl per image (~seconds), pushes nothing over the WAN, and mints no per-SHA tag
|
||||
# debris on the runners — the failure mode that filled the fleet's disks. `:latest`
|
||||
# is re-pushed alongside every new key and is what the consuming workflows pin.
|
||||
#
|
||||
# APP images (punktfunk-web, punktfunk-docs) are deployables: they keep going to the
|
||||
# Gitea registry (git.unom.io) with :latest + :sha-<8> (+ :vX.Y.Z on tags), because
|
||||
@@ -22,38 +17,8 @@
|
||||
#
|
||||
# Host and clients are intentionally NOT containerized (see CLAUDE.md "What's left").
|
||||
#
|
||||
# REGISTRY_TOKEN: repo Actions secret, a PAT with write:package scope (app images).
|
||||
# CI_REGISTRY_PASSWORD: repo Actions secret, the LAN registry's push credential for user
|
||||
# `ci`. Generated on ci-core into /srv/ci/stack/registry-secret; rotate in both places.
|
||||
#
|
||||
# --- security-review-2026-08-05 H-6, FIXED 2026-08-05 -------------------------------
|
||||
# The registry used to accept anonymous pushes from any LAN peer, and every
|
||||
# secret-bearing job in this repo runs INSIDE an image pulled from it. Attacker
|
||||
# position #1 of the project's own threat model did not need to break any signing
|
||||
# logic: push one tag, and the next android.yml run executes their code in the same job
|
||||
# that does `echo "$RELEASE_KEYSTORE_BASE64" | base64 -d > release.jks`. Same shape for
|
||||
# rpm.yml (RPM_GPG_PRIVATE_KEY) and android-promote.yml (SERVICE_ACCOUNT_JSON).
|
||||
#
|
||||
# The infra half is done (unom/infra runners/ci-core/): :5010 serves GET/HEAD only and
|
||||
# refuses everything else with 405, :5011 demands basic auth on every request. The half
|
||||
# in this file is done below: pushes and release-tag manifest PUTs authenticate.
|
||||
#
|
||||
# ⚠ On the second half as the review originally worded it — "pin consumers by @sha256:
|
||||
# digest". We deliberately do something else, because after authentication the digest
|
||||
# pin no longer buys what it was meant to buy. The set of people who can overwrite a tag
|
||||
# is now exactly the set who can push to main and edit a pinned digest in this very
|
||||
# file: a pin defends against nobody it did not already trust, while costing a
|
||||
# two-commit dance on every ci/ change (~3x a month) during which consumers silently run
|
||||
# a builder image that predates the ci/ change they are testing.
|
||||
#
|
||||
# What actually closes the residual gap — a tag quietly overwritten out of band — is
|
||||
# making :latest a CHECKED function of the tree instead of a tag someone remembered to
|
||||
# move. The "Reconcile :latest" step below asserts on every run that :latest and
|
||||
# :ck-$KEY are the same digest, repairs it when they are not, and says so loudly. That
|
||||
# catches an out-of-band overwrite on the next push to main, needs no churn, and fixes
|
||||
# a real pre-existing bug on the side: reverting ci/ used to leave :latest pointing at
|
||||
# the newer build forever. Revisit inline digest pins if the push credential ever leaves
|
||||
# the maintainer trust set.
|
||||
# REGISTRY_TOKEN: repo Actions secret, a PAT with write:package scope (app images only —
|
||||
# the LAN registry is unauthenticated inside the LAN).
|
||||
#
|
||||
# Bootstrap note: consuming workflows pull <LAN>/punktfunk-rust-ci:latest, so the LAN
|
||||
# registry must hold a seeded :latest once (done 2026-07-29 from the last Gitea-registry
|
||||
@@ -77,10 +42,7 @@ on:
|
||||
env:
|
||||
REGISTRY: git.unom.io
|
||||
OWNER: unom
|
||||
# Read port (anonymous, GET/HEAD only) and write port (basic auth). Two doors onto
|
||||
# one store; see the header.
|
||||
CI_REGISTRY: 192.168.1.58:5010
|
||||
CI_REGISTRY_PUSH: 192.168.1.58:5011
|
||||
|
||||
jobs:
|
||||
builders:
|
||||
@@ -136,40 +98,21 @@ jobs:
|
||||
echo "hit=false" >> "$GITHUB_OUTPUT"
|
||||
fi
|
||||
|
||||
# Tagged for the WRITE port: :5010 refuses a push outright, so a tag that names it
|
||||
# can only fail. Consumers still pull the identical image from :5010.
|
||||
- name: Build
|
||||
if: steps.exists.outputs.hit == 'false'
|
||||
# --pull is cheap now: base images come through the ci-core pull-through mirror.
|
||||
run: |
|
||||
docker build --pull ${{ matrix.buildargs }} \
|
||||
-f "${{ matrix.dockerfile }}" \
|
||||
-t "$CI_REGISTRY_PUSH/${{ matrix.image }}:$KEY" \
|
||||
-t "$CI_REGISTRY_PUSH/${{ matrix.image }}:latest" \
|
||||
-t "$CI_REGISTRY/${{ matrix.image }}:$KEY" \
|
||||
-t "$CI_REGISTRY/${{ matrix.image }}:latest" \
|
||||
ci
|
||||
|
||||
- name: Log in to the LAN registry
|
||||
run: |
|
||||
echo "$CI_REGISTRY_PASSWORD" | docker login "$CI_REGISTRY_PUSH" -u ci --password-stdin
|
||||
env:
|
||||
CI_REGISTRY_PASSWORD: ${{ secrets.CI_REGISTRY_PASSWORD }}
|
||||
|
||||
- name: Push
|
||||
if: steps.exists.outputs.hit == 'false'
|
||||
run: |
|
||||
docker push "$CI_REGISTRY_PUSH/${{ matrix.image }}:$KEY"
|
||||
docker push "$CI_REGISTRY_PUSH/${{ matrix.image }}:latest"
|
||||
|
||||
# :latest must be whatever ci/ says it is, on every run — not only on the runs that
|
||||
# happened to build. Two things break that: an out-of-band overwrite (the H-6
|
||||
# attack, now only reachable by someone holding the push credential), and a plain
|
||||
# revert of ci/, which leaves :latest on the newer build because the older key is
|
||||
# already a cache hit and nothing re-points it. Both look identical from here and
|
||||
# both are repaired the same way, so repair and shout rather than fail the build.
|
||||
- name: Reconcile :latest with the content key
|
||||
run: .gitea/scripts/reconcile-latest.sh "${{ matrix.image }}" "$KEY"
|
||||
env:
|
||||
CI_REGISTRY_PASSWORD: ${{ secrets.CI_REGISTRY_PASSWORD }}
|
||||
docker push "$CI_REGISTRY/${{ matrix.image }}:$KEY"
|
||||
docker push "$CI_REGISTRY/${{ matrix.image }}:latest"
|
||||
|
||||
# A release pins reproducible builder images without any rebuild: copy the key's
|
||||
# manifest to a vX.Y.Z tag via the registry API (no image bytes move).
|
||||
@@ -181,19 +124,8 @@ jobs:
|
||||
| tr -d '\r' | sed -n 's/^[Cc]ontent-[Tt]ype: //p')
|
||||
curl -sf -H "$ACCEPT" -o /tmp/manifest.json \
|
||||
"http://$CI_REGISTRY/v2/${{ matrix.image }}/manifests/$KEY"
|
||||
curl -sf -u "ci:$CI_REGISTRY_PASSWORD" -X PUT -H "Content-Type: $MT" \
|
||||
--data-binary @/tmp/manifest.json \
|
||||
"http://$CI_REGISTRY_PUSH/v2/${{ matrix.image }}/manifests/$GITHUB_REF_NAME"
|
||||
env:
|
||||
CI_REGISTRY_PASSWORD: ${{ secrets.CI_REGISTRY_PASSWORD }}
|
||||
|
||||
# Today the job container is ephemeral (the ubuntu-24.04 label is a docker://
|
||||
# image), so the credential docker login wrote would die with it anyway. Don't
|
||||
# make that a load-bearing assumption about a runner label somebody may change to
|
||||
# a host runner later.
|
||||
- name: Log out of the LAN registry
|
||||
if: always()
|
||||
run: docker logout "$CI_REGISTRY_PUSH" || true
|
||||
curl -sf -X PUT -H "Content-Type: $MT" --data-binary @/tmp/manifest.json \
|
||||
"http://$CI_REGISTRY/v2/${{ matrix.image }}/manifests/$GITHUB_REF_NAME"
|
||||
|
||||
# The aarch64 CROSS builder — a SEPARATE job because it is `FROM punktfunk-rust-ci:latest`
|
||||
# (the LAN copy) and so must not race the matrix entry that publishes that base. Consumed
|
||||
@@ -232,26 +164,15 @@ jobs:
|
||||
run: |
|
||||
docker build --pull \
|
||||
-f ci/rust-ci-arm64cross.Dockerfile \
|
||||
-t "$CI_REGISTRY_PUSH/$IMAGE:$KEY" \
|
||||
-t "$CI_REGISTRY_PUSH/$IMAGE:latest" \
|
||||
-t "$CI_REGISTRY/$IMAGE:$KEY" \
|
||||
-t "$CI_REGISTRY/$IMAGE:latest" \
|
||||
.
|
||||
|
||||
- name: Log in to the LAN registry
|
||||
run: |
|
||||
echo "$CI_REGISTRY_PASSWORD" | docker login "$CI_REGISTRY_PUSH" -u ci --password-stdin
|
||||
env:
|
||||
CI_REGISTRY_PASSWORD: ${{ secrets.CI_REGISTRY_PASSWORD }}
|
||||
|
||||
- name: Push
|
||||
if: steps.exists.outputs.hit == 'false'
|
||||
run: |
|
||||
docker push "$CI_REGISTRY_PUSH/$IMAGE:$KEY"
|
||||
docker push "$CI_REGISTRY_PUSH/$IMAGE:latest"
|
||||
|
||||
- name: Reconcile :latest with the content key
|
||||
run: .gitea/scripts/reconcile-latest.sh "$IMAGE" "$KEY"
|
||||
env:
|
||||
CI_REGISTRY_PASSWORD: ${{ secrets.CI_REGISTRY_PASSWORD }}
|
||||
docker push "$CI_REGISTRY/$IMAGE:$KEY"
|
||||
docker push "$CI_REGISTRY/$IMAGE:latest"
|
||||
|
||||
- name: Tag for release
|
||||
if: startsWith(github.ref, 'refs/tags/v')
|
||||
@@ -261,15 +182,8 @@ jobs:
|
||||
| tr -d '\r' | sed -n 's/^[Cc]ontent-[Tt]ype: //p')
|
||||
curl -sf -H "$ACCEPT" -o /tmp/manifest.json \
|
||||
"http://$CI_REGISTRY/v2/$IMAGE/manifests/$KEY"
|
||||
curl -sf -u "ci:$CI_REGISTRY_PASSWORD" -X PUT -H "Content-Type: $MT" \
|
||||
--data-binary @/tmp/manifest.json \
|
||||
"http://$CI_REGISTRY_PUSH/v2/$IMAGE/manifests/$GITHUB_REF_NAME"
|
||||
env:
|
||||
CI_REGISTRY_PASSWORD: ${{ secrets.CI_REGISTRY_PASSWORD }}
|
||||
|
||||
- name: Log out of the LAN registry
|
||||
if: always()
|
||||
run: docker logout "$CI_REGISTRY_PUSH" || true
|
||||
curl -sf -X PUT -H "Content-Type: $MT" --data-binary @/tmp/manifest.json \
|
||||
"http://$CI_REGISTRY/v2/$IMAGE/manifests/$GITHUB_REF_NAME"
|
||||
|
||||
# Deployable app images — unchanged flow, Gitea registry, per-SHA + release tags.
|
||||
apps:
|
||||
|
||||
@@ -38,18 +38,10 @@ jobs:
|
||||
with:
|
||||
fetch-depth: 0
|
||||
# Pinned syft (keep in sync with the version validated against this repo; bump deliberately).
|
||||
#
|
||||
# The BINARY version was pinned; the INSTALLER was not — it was fetched from `main` and piped
|
||||
# into a shell, so whatever that branch happened to say at job time ran here, with the job's
|
||||
# environment (2026-08-05 review H-6). Pinning the script to the same tag as the binary makes
|
||||
# the whole step reproducible: bump the tag in both places together.
|
||||
- name: Install syft
|
||||
env:
|
||||
SYFT_VERSION: v1.49.0
|
||||
run: |
|
||||
set -euo pipefail
|
||||
curl -sSfL "https://raw.githubusercontent.com/anchore/syft/${SYFT_VERSION}/install.sh" \
|
||||
| sh -s -- -b /usr/local/bin "$SYFT_VERSION"
|
||||
curl -sSfL https://raw.githubusercontent.com/anchore/syft/main/install.sh \
|
||||
| sh -s -- -b /usr/local/bin v1.49.0
|
||||
- name: Generate SBOM
|
||||
run: |
|
||||
git config --global --add safe.directory "$PWD"
|
||||
|
||||
Generated
+175
-24
@@ -65,7 +65,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "05b07e8e73d720a1f2e4b6014766e6039fd2e96a4fa44e2a78d0e1fa2ff49826"
|
||||
dependencies = [
|
||||
"android_log-sys",
|
||||
"env_filter",
|
||||
"env_filter 0.1.4",
|
||||
"log",
|
||||
]
|
||||
|
||||
@@ -446,7 +446,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "993776b509cfb49c750f11b8f07a46fa23e0a1386ffc01fb1e7d343efc387895"
|
||||
dependencies = [
|
||||
"annotate-snippets",
|
||||
"bitflags",
|
||||
"bitflags 2.13.0",
|
||||
"cexpr",
|
||||
"clang-sys",
|
||||
"itertools 0.13.0",
|
||||
@@ -475,6 +475,12 @@ version = "0.8.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "5e764a1d40d510daf35e07be9eb06e75770908c27d411ee6c92109c9840eaaf7"
|
||||
|
||||
[[package]]
|
||||
name = "bitflags"
|
||||
version = "1.3.2"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "bef38d45163c2f1dde094a7dfd33ccf595c92905c8f8f4fdc18d06fb1037718a"
|
||||
|
||||
[[package]]
|
||||
name = "bitflags"
|
||||
version = "2.13.0"
|
||||
@@ -556,7 +562,7 @@ version = "0.22.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "5cc8d9aa793480744cd9a0524fef1a2e197d9eaa0f739cde19d16aba530dcb95"
|
||||
dependencies = [
|
||||
"bitflags",
|
||||
"bitflags 2.13.0",
|
||||
"cairo-sys-rs",
|
||||
"glib",
|
||||
"libc",
|
||||
@@ -885,6 +891,15 @@ dependencies = [
|
||||
"itertools 0.10.5",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "cros-codecs"
|
||||
version = "0.0.5"
|
||||
dependencies = [
|
||||
"env_logger",
|
||||
"log",
|
||||
"serde_json",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "crossbeam-deque"
|
||||
version = "0.8.6"
|
||||
@@ -991,6 +1006,37 @@ version = "2.11.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "a4ae5f15dda3c708c0ade84bfee31ccab44a3da4f88015ed22f63732abe300c8"
|
||||
|
||||
[[package]]
|
||||
name = "defmt"
|
||||
version = "1.1.1"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "e2953bfe4f93bbd20cc71198842756f77d161884c99ebbabc41d80231ded88d1"
|
||||
dependencies = [
|
||||
"bitflags 1.3.2",
|
||||
"defmt-macros",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "defmt-macros"
|
||||
version = "1.1.1"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "bad9c72e7ca2137e0dc3813245a0d282fd6daad32fd800af018306a9169b5fe8"
|
||||
dependencies = [
|
||||
"defmt-parser",
|
||||
"proc-macro2",
|
||||
"quote",
|
||||
"syn",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "defmt-parser"
|
||||
version = "1.0.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "10d60334b3b2e7c9d91ef8150abfb6fa4c1c39ebbcf4a81c2e346aad939fee3e"
|
||||
dependencies = [
|
||||
"thiserror 2.0.18",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "der"
|
||||
version = "0.7.10"
|
||||
@@ -1101,6 +1147,29 @@ dependencies = [
|
||||
"regex",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "env_filter"
|
||||
version = "2.0.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "900d271a03799a1ee8d1ca9b19893b48ca674a9284fefcfb85f05e74ed314217"
|
||||
dependencies = [
|
||||
"log",
|
||||
"regex",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "env_logger"
|
||||
version = "0.11.11"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "de671bd27a75a797dc9ae289ba1e77276e75e2026408aab65185384e2d5cd3f6"
|
||||
dependencies = [
|
||||
"anstream",
|
||||
"anstyle",
|
||||
"env_filter 2.0.0",
|
||||
"jiff",
|
||||
"log",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "equivalent"
|
||||
version = "1.0.2"
|
||||
@@ -1193,7 +1262,7 @@ version = "8.1.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "f7c4bd5ab1ac61f29c634df1175d350ded29cf74c3c6d4f7030431a5ae3c7d5d"
|
||||
dependencies = [
|
||||
"bitflags",
|
||||
"bitflags 2.13.0",
|
||||
"ffmpeg-sys-next",
|
||||
"libc",
|
||||
]
|
||||
@@ -1584,7 +1653,7 @@ version = "0.22.7"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "c207e04e51605dcf7b2924c41591b3a10e1438eaac5bcf448fb91f325381104a"
|
||||
dependencies = [
|
||||
"bitflags",
|
||||
"bitflags 2.13.0",
|
||||
"futures-channel",
|
||||
"futures-core",
|
||||
"futures-executor",
|
||||
@@ -2127,6 +2196,42 @@ version = "1.0.18"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "8f42a60cbdf9a97f5d2305f08a87dc4e09308d1276d28c869c684d7777685682"
|
||||
|
||||
[[package]]
|
||||
name = "jiff"
|
||||
version = "0.2.35"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "668b7183bd07af9a4885f5c35b0cc5c83c4607a913c16b7e17291832910d2dcc"
|
||||
dependencies = [
|
||||
"defmt",
|
||||
"jiff-core",
|
||||
"jiff-static",
|
||||
"log",
|
||||
"portable-atomic",
|
||||
"portable-atomic-util",
|
||||
"serde_core",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "jiff-core"
|
||||
version = "0.1.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "7feca88439efe53da3754500c1851dedf3cb36c524dd5cf8225cc0794de95d09"
|
||||
dependencies = [
|
||||
"defmt",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "jiff-static"
|
||||
version = "0.2.35"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "3a69dcb3a21cfb32ce1cd056169337ca284af0766dd766e7878819b251a49204"
|
||||
dependencies = [
|
||||
"jiff-core",
|
||||
"proc-macro2",
|
||||
"quote",
|
||||
"syn",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "jni"
|
||||
version = "0.21.1"
|
||||
@@ -2291,7 +2396,7 @@ version = "0.9.2"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "b6b8cfa2a7656627b4c92c6b9ef929433acd673d5ab3708cda1b18478ac00df4"
|
||||
dependencies = [
|
||||
"bitflags",
|
||||
"bitflags 2.13.0",
|
||||
"cc",
|
||||
"convert_case",
|
||||
"cookie-factory",
|
||||
@@ -2493,7 +2598,7 @@ dependencies = [
|
||||
name = "ndk"
|
||||
version = "0.9.0"
|
||||
dependencies = [
|
||||
"bitflags",
|
||||
"bitflags 2.13.0",
|
||||
"jni-sys 0.3.1",
|
||||
"log",
|
||||
"ndk-sys",
|
||||
@@ -2517,7 +2622,7 @@ version = "0.29.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "71e2746dc3a24dd78b3cfcb7be93368c6de9963d30f43a6a73998a9cf4b17b46"
|
||||
dependencies = [
|
||||
"bitflags",
|
||||
"bitflags 2.13.0",
|
||||
"cfg-if",
|
||||
"cfg_aliases",
|
||||
"libc",
|
||||
@@ -2530,7 +2635,7 @@ version = "0.30.1"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "74523f3a35e05aba87a1d978330aef40f67b0304ac79c1c00b294c9830543db6"
|
||||
dependencies = [
|
||||
"bitflags",
|
||||
"bitflags 2.13.0",
|
||||
"cfg-if",
|
||||
"cfg_aliases",
|
||||
"libc",
|
||||
@@ -2848,6 +2953,14 @@ version = "2.3.2"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "9b4f627cb1b25917193a259e49bdad08f671f8d9708acfd5fe0a8c1455d87220"
|
||||
|
||||
[[package]]
|
||||
name = "pf-bitstream"
|
||||
version = "0.24.0"
|
||||
dependencies = [
|
||||
"cros-codecs",
|
||||
"tracing",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "pf-capture"
|
||||
version = "0.24.0"
|
||||
@@ -2879,8 +2992,10 @@ dependencies = [
|
||||
"ffmpeg-next",
|
||||
"mdns-sd",
|
||||
"opus",
|
||||
"pf-dxvadec",
|
||||
"pf-ffvk",
|
||||
"pf-update-check",
|
||||
"pf-vkdecode",
|
||||
"pipewire",
|
||||
"punktfunk-core",
|
||||
"pyrowave-sys",
|
||||
@@ -2935,6 +3050,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"
|
||||
@@ -3075,7 +3200,7 @@ version = "0.24.0"
|
||||
dependencies = [
|
||||
"anyhow",
|
||||
"ashpd",
|
||||
"bitflags",
|
||||
"bitflags 2.13.0",
|
||||
"bytemuck",
|
||||
"futures-util",
|
||||
"hex",
|
||||
@@ -3102,6 +3227,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 +3289,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 +3343,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 +3387,21 @@ dependencies = [
|
||||
"universal-hash",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "portable-atomic"
|
||||
version = "1.14.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "3d20d5497ef88037a52ff98267d066e7f11fcc5e99bbfbd58a42336193aacec3"
|
||||
|
||||
[[package]]
|
||||
name = "portable-atomic-util"
|
||||
version = "0.2.7"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "c2a106d1259c23fac8e543272398ae0e3c0b8d33c88ed73d0cc71b0f1d902618"
|
||||
dependencies = [
|
||||
"portable-atomic",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "potential_utf"
|
||||
version = "0.1.5"
|
||||
@@ -3311,7 +3462,7 @@ checksum = "4b45fcc2344c680f5025fe57779faef368840d0bd1f42f216291f0dc4ace4744"
|
||||
dependencies = [
|
||||
"bit-set",
|
||||
"bit-vec",
|
||||
"bitflags",
|
||||
"bitflags 2.13.0",
|
||||
"num-traits",
|
||||
"rand 0.9.4",
|
||||
"rand_chacha 0.9.0",
|
||||
@@ -3777,7 +3928,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 +4085,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 +4124,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 +4278,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 +4373,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 +4579,7 @@ version = "0.87.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "0f7d94f3e7537c71ad4cf132eb26e3be8c8a886ed3649c4525c089041fc312b2"
|
||||
dependencies = [
|
||||
"bitflags",
|
||||
"bitflags 2.13.0",
|
||||
"lazy_static",
|
||||
"skia-bindings",
|
||||
]
|
||||
@@ -5301,7 +5452,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 +5464,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 +5476,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 +5489,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 +5841,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",
|
||||
]
|
||||
|
||||
@@ -5,6 +5,8 @@ members = [
|
||||
"crates/punktfunk-host",
|
||||
"crates/punktfunk-host/vendor/usbip-sim",
|
||||
"crates/punktfunk-tray",
|
||||
"crates/pf-bitstream",
|
||||
"crates/pf-bitstream/vendor/cros-codecs",
|
||||
"crates/pf-client-core",
|
||||
"crates/pf-clipboard",
|
||||
"crates/pf-presenter",
|
||||
@@ -23,6 +25,8 @@ members = [
|
||||
"crates/pf-capture",
|
||||
"crates/pf-inject",
|
||||
"crates/pf-vdisplay",
|
||||
"crates/pf-vkdecode",
|
||||
"crates/pf-dxvadec",
|
||||
"crates/pyrowave-sys",
|
||||
"crates/libvpl-sys",
|
||||
"clients/probe",
|
||||
|
||||
+9
-157
@@ -10,7 +10,7 @@
|
||||
"name": "MIT OR Apache-2.0",
|
||||
"identifier": "MIT OR Apache-2.0"
|
||||
},
|
||||
"version": "0.24.0"
|
||||
"version": "0.23.0"
|
||||
},
|
||||
"paths": {
|
||||
"/api/v1/clients": {
|
||||
@@ -1052,7 +1052,7 @@
|
||||
"library"
|
||||
],
|
||||
"summary": "Fetch one cover-art image for a library entry",
|
||||
"description": "Resolves `kind` (`portrait` | `hero` | `logo` | `header`) for the given library id and streams\nthe image bytes. Any id stored in the host's catalog (manual entries, provider-synced entries,\nand a library plugin's claimed-store entries) serves its local art file. A Steam title falls back\nto the in-host scanner's resolver: the host's own local Steam cache first (exact — it's what the\nuser's Steam client already shows for it), the public Steam CDN's flat URL convention second\n(newer titles' CDN assets can live at a per-asset-hash path the host can't predict, in which case\nthis 404s and the client falls through to its next art candidate).",
|
||||
"description": "Resolves `kind` (`portrait` | `hero` | `logo` | `header`) for the given library id and streams\nthe image bytes. For a Steam title, the host's own local Steam cache is tried first (exact —\nit's what the user's Steam client already shows for it), the public Steam CDN's flat URL\nconvention as a fallback (newer titles' CDN assets can live at a per-asset-hash path the host\ncan't predict, in which case this 404s and the client falls through to its next art candidate).\nOnly Steam ids are backed today; any other store 404s.",
|
||||
"operationId": "getLibraryArt",
|
||||
"parameters": [
|
||||
{
|
||||
@@ -1307,7 +1307,7 @@
|
||||
"library"
|
||||
],
|
||||
"summary": "Replace a provider's library entries (declarative reconcile)",
|
||||
"description": "Atomically replaces the full entry set owned by `{provider}` (RFC §8): the payload is the\nprovider's desired list, keyed by its own stable `external_id` — the host diffs, keeps each\nsurviving title's host id stable across reconciles, drops orphans, and never touches manual\nentries or other providers'. An empty array removes everything the provider owns. Emits\n`library.changed` with the provider as `source`.\n\n`?store=` additionally **claims** that store for the provider: its entries then surface with\ndeterministic `<store>:<external_id>` ids and the store's own badge, instead of opaque\n`custom:<id>` ones — which is what lets a library plugin reproduce the entries an in-host scanner\nused to produce, right down to the GameStream app ids and client-side art caches. One provider\nper store; a second claimant gets 409. While a claim is held the matching built-in scanner is\nsuppressed, so the two never double-list. The claim is released by `DELETE`, not by an empty\nreconcile (a store can legitimately have zero installed titles).",
|
||||
"description": "Atomically replaces the full entry set owned by `{provider}` (RFC §8): the payload is the\nprovider's desired list, keyed by its own stable `external_id` — the host diffs, keeps each\nsurviving title's host id stable across reconciles, drops orphans, and never touches manual\nentries or other providers'. An empty array removes everything the provider owns. Emits\n`library.changed` with the provider as `source`.",
|
||||
"operationId": "reconcileProviderEntries",
|
||||
"parameters": [
|
||||
{
|
||||
@@ -1318,15 +1318,6 @@
|
||||
"schema": {
|
||||
"type": "string"
|
||||
}
|
||||
},
|
||||
{
|
||||
"name": "store",
|
||||
"in": "query",
|
||||
"description": "Claim this store for the provider ([a-z0-9_-], `custom`/`manual` reserved)",
|
||||
"required": false,
|
||||
"schema": {
|
||||
"type": "string"
|
||||
}
|
||||
}
|
||||
],
|
||||
"requestBody": {
|
||||
@@ -1357,7 +1348,7 @@
|
||||
}
|
||||
},
|
||||
"400": {
|
||||
"description": "Invalid provider id, store id, or payload",
|
||||
"description": "Invalid provider id or payload",
|
||||
"content": {
|
||||
"application/json": {
|
||||
"schema": {
|
||||
@@ -1376,16 +1367,6 @@
|
||||
}
|
||||
}
|
||||
},
|
||||
"409": {
|
||||
"description": "That store is already claimed by another provider",
|
||||
"content": {
|
||||
"application/json": {
|
||||
"schema": {
|
||||
"$ref": "#/components/schemas/ApiError"
|
||||
}
|
||||
}
|
||||
}
|
||||
},
|
||||
"500": {
|
||||
"description": "Could not persist the catalog",
|
||||
"content": {
|
||||
@@ -4178,8 +4159,7 @@
|
||||
"tier",
|
||||
"platforms",
|
||||
"compatible",
|
||||
"update_available",
|
||||
"categories"
|
||||
"update_available"
|
||||
],
|
||||
"properties": {
|
||||
"author": {
|
||||
@@ -4192,13 +4172,6 @@
|
||||
],
|
||||
"description": "A revocation covering the catalogued version — do not offer this without shouting."
|
||||
},
|
||||
"categories": {
|
||||
"type": "array",
|
||||
"items": {
|
||||
"type": "string"
|
||||
},
|
||||
"description": "What kind of plugin this is — the console filters Browse by these, and the Game sources\nsurface's \"Add a source\" rail shows exactly the `library` ones (design D5/D6)."
|
||||
},
|
||||
"compatible": {
|
||||
"type": "boolean",
|
||||
"description": "Can this host install it?"
|
||||
@@ -4206,13 +4179,6 @@
|
||||
"description": {
|
||||
"type": "string"
|
||||
},
|
||||
"detected": {
|
||||
"type": [
|
||||
"boolean",
|
||||
"null"
|
||||
],
|
||||
"description": "Whether the launcher this plugin scans looks **installed on this host** (design D8), from the\nindex's own existence probes. `null` = the entry declares no probes for this platform, which\nthe console renders as \"unknown\" rather than \"not installed\"."
|
||||
},
|
||||
"homepage": {
|
||||
"type": [
|
||||
"string",
|
||||
@@ -4399,17 +4365,6 @@
|
||||
],
|
||||
"description": "The external provider owning this entry (RFC §8), set ONLY by the provider reconcile\nAPI — `None` = a manual entry, which no provider operation ever touches, and which the\nmanual CRUD alone may edit (the converse holds too: manual CRUD refuses provider-owned\nentries, so ownership is never ambiguous)."
|
||||
},
|
||||
"role": {
|
||||
"$ref": "#/components/schemas/GameRole",
|
||||
"description": "Whether this entry is a game or the launcher itself — see [`GameRole`]."
|
||||
},
|
||||
"store": {
|
||||
"type": [
|
||||
"string",
|
||||
"null"
|
||||
],
|
||||
"description": "The **store this entry was claimed under** (D2), stamped by a `?store=`-qualified reconcile.\n`None` = an unclaimed provider entry or a manual one, both of which surface as `custom`.\n\nMaterialized onto the entry rather than looked up in [`Catalog::claims`] on every read so an\nentry is self-describing: its id and its `store` badge derive from the entry alone, and stay\ncorrect even while the claim map is being rewritten."
|
||||
},
|
||||
"title": {
|
||||
"type": "string"
|
||||
}
|
||||
@@ -4454,10 +4409,6 @@
|
||||
},
|
||||
"description": "Per-title prep/undo steps — commands run as the host user; operator-privileged config."
|
||||
},
|
||||
"role": {
|
||||
"$ref": "#/components/schemas/GameRole",
|
||||
"description": "Whether this entry is a game or the launcher itself — see [`GameRole`]. A hand-added launcher\nentry is legal (an operator may want a \"Steam\" tile without installing the steam plugin)."
|
||||
},
|
||||
"title": {
|
||||
"type": "string"
|
||||
}
|
||||
@@ -4516,17 +4467,6 @@
|
||||
"type": "object",
|
||||
"description": "What an operator (or a provider plugin) can tell the host about recognizing a title — the wire\nhalf of [`DetectSpec`], and the only part of it that is ever accepted from outside.\n\nDeliberately a **subset**: the store-derived signals (a Steam appid, a launcher's environment\nmarker) are things the host discovers for itself and would be meaningless — or dangerous — to take\non someone's word. What is left is what a provider genuinely knows and the host cannot guess: where\nthe title is installed, which executable is the game, what the process is called. All three are\noptional; supplying none is the same as supplying no hint at all.\n\nNever returned by the catalog API — see the module docs on why detect data does not cross the wire\noutbound.",
|
||||
"properties": {
|
||||
"env_marker": {
|
||||
"oneOf": [
|
||||
{
|
||||
"type": "null"
|
||||
},
|
||||
{
|
||||
"$ref": "#/components/schemas/EnvMarker",
|
||||
"description": "A launcher-stamped environment marker (D3) — see [`EnvMarker`]."
|
||||
}
|
||||
]
|
||||
},
|
||||
"exe": {
|
||||
"type": [
|
||||
"string",
|
||||
@@ -4547,15 +4487,6 @@
|
||||
"null"
|
||||
],
|
||||
"description": "The executable's file name (`Hades.exe`), when its location isn't fixed. Weakest of the three\n— see [`DetectSpec::process_name`]."
|
||||
},
|
||||
"steam_appid": {
|
||||
"type": [
|
||||
"integer",
|
||||
"null"
|
||||
],
|
||||
"format": "int32",
|
||||
"description": "The Steam appid, for a title Steam itself installed (D3). On Linux this is the **sharpest**\nsignal that exists — Steam wraps every launch, native or Proton, in\n`reaper SteamLaunch AppId=<appid>`, whose lifetime is exactly the game's — so without it a\nsteam plugin's lease tracking would degrade from reaper-exact to install-dir prefix matching.",
|
||||
"minimum": 0
|
||||
}
|
||||
}
|
||||
},
|
||||
@@ -4784,27 +4715,6 @@
|
||||
}
|
||||
}
|
||||
},
|
||||
"EnvMarker": {
|
||||
"type": "object",
|
||||
"description": "An environment variable a launcher stamps onto the game's process, identifying it.\n\nSerializable because it is now half of the inbound [`DetectHint`] too (D3) — a library plugin\nthat knows its launcher's marker (Heroic's `HEROIC_APP_NAME`, load-bearing under Proton) has to\nbe able to say so, since after extraction the host no longer reads that launcher's files itself.",
|
||||
"required": [
|
||||
"key"
|
||||
],
|
||||
"properties": {
|
||||
"key": {
|
||||
"type": "string",
|
||||
"description": "The variable name (e.g. `HEROIC_GAME_ID`).",
|
||||
"example": "HEROIC_APP_NAME"
|
||||
},
|
||||
"value": {
|
||||
"type": [
|
||||
"string",
|
||||
"null"
|
||||
],
|
||||
"description": "The exact value to require, when the launcher's value identifies *this* title. `None` matches\nthe key's mere presence — only safe for launchers that run one game at a time."
|
||||
}
|
||||
}
|
||||
},
|
||||
"EventKind": {
|
||||
"oneOf": [
|
||||
{
|
||||
@@ -5255,10 +5165,6 @@
|
||||
],
|
||||
"description": "The external provider owning this entry (custom-store entries synced by a provider\nplugin, RFC §8) — `None` for installed-store titles and manual custom entries. The\nconsole uses it for attribution; `GET /library?provider=` filters on it."
|
||||
},
|
||||
"role": {
|
||||
"$ref": "#/components/schemas/GameRole",
|
||||
"description": "Whether this entry is a game or the launcher itself — see [`GameRole`]."
|
||||
},
|
||||
"store": {
|
||||
"type": "string",
|
||||
"description": "Which store surfaced it: `\"steam\"` or `\"custom\"`.",
|
||||
@@ -5390,14 +5296,6 @@
|
||||
}
|
||||
}
|
||||
},
|
||||
"GameRole": {
|
||||
"type": "string",
|
||||
"description": "What a library entry *is* — an ordinary title, or the launcher application itself (Steam Big\nPicture, Heroic, Playnite fullscreen). Purely a presentation hint: a launcher entry launches,\nleases and lists exactly like a game (design D4), and clients that don't know the field render it\nas a plain tile. Serde-default `game` and skip-serialized when default, so the wire is unchanged\nfor every entry that doesn't opt in.",
|
||||
"enum": [
|
||||
"game",
|
||||
"launcher"
|
||||
]
|
||||
},
|
||||
"GameSession": {
|
||||
"type": "string",
|
||||
"description": "How a session that **launches a game** (a library id on the Hello / apps.json / Decky pin) is\nserved (`design/gamemode-and-dedicated-sessions.md` §5.2). Orthogonal to the preset/lifecycle axes\n— a top-level [`DisplayPolicy`] field, NOT part of [`EffectivePolicy`], so a preset never clobbers\nit. Linux-only in effect (a launching Windows session opens into the one desktop).",
|
||||
@@ -6436,13 +6334,6 @@
|
||||
"title"
|
||||
],
|
||||
"properties": {
|
||||
"category": {
|
||||
"type": [
|
||||
"string",
|
||||
"null"
|
||||
],
|
||||
"description": "What KIND of plugin this is (`^[a-z][a-z0-9-]{0,31}$`), top-level rather than under `ui`\nbecause it describes the plugin, not its surface. The console knows one value today —\n`library` — which it filters **out of the nav**: six installed scanner plugins would otherwise\nflood the sidebar, and their real entry point is the Game sources surface (design D5). A\nlibrary plugin that genuinely wants its own page (rom-manager, which is much more than a\nscanner) simply omits the category."
|
||||
},
|
||||
"title": {
|
||||
"type": "string",
|
||||
"description": "Human-readable title for the console nav entry (1–64 chars; control chars stripped)."
|
||||
@@ -6475,13 +6366,6 @@
|
||||
"title"
|
||||
],
|
||||
"properties": {
|
||||
"category": {
|
||||
"type": [
|
||||
"string",
|
||||
"null"
|
||||
],
|
||||
"description": "The plugin's kind — see [`PluginRegistration::category`]."
|
||||
},
|
||||
"id": {
|
||||
"type": "string"
|
||||
},
|
||||
@@ -6720,10 +6604,6 @@
|
||||
},
|
||||
"description": "Per-title prep/undo steps — commands run as the host user; operator-privileged config."
|
||||
},
|
||||
"role": {
|
||||
"$ref": "#/components/schemas/GameRole",
|
||||
"description": "Whether this entry is a game or the launcher itself — see [`GameRole`]. A library plugin\nemits its `launchers(cfg)` entries with `role: \"launcher\"`."
|
||||
},
|
||||
"title": {
|
||||
"type": "string"
|
||||
}
|
||||
@@ -6900,46 +6780,26 @@
|
||||
},
|
||||
"ScannerInfo": {
|
||||
"type": "object",
|
||||
"description": "One **game source** on this host, with its enable state — the unit the console renders a toggle\nfor. A source is either a scanner compiled into this build or a plugin that reconciles entries in\n(WP2.6); the console treats them identically, which is what makes the extraction invisible.",
|
||||
"description": "One installed-store scanner this host build supports, with its enable state — the unit the\nconsole renders a toggle for. The list is platform-gated at compile time (the scanners are),\nso the console never shows a toggle that cannot do anything on this host.",
|
||||
"required": [
|
||||
"id",
|
||||
"label",
|
||||
"enabled",
|
||||
"origin"
|
||||
"enabled"
|
||||
],
|
||||
"properties": {
|
||||
"enabled": {
|
||||
"type": "boolean",
|
||||
"description": "Whether this host runs the source (default true)."
|
||||
},
|
||||
"entries": {
|
||||
"type": [
|
||||
"integer",
|
||||
"null"
|
||||
],
|
||||
"description": "How many entries this source currently contributes. `None` for a built-in scanner, whose\ncount would mean walking every launcher's files just to render a toggle.",
|
||||
"minimum": 0
|
||||
"description": "Whether this host runs the scanner (default true)."
|
||||
},
|
||||
"id": {
|
||||
"type": "string",
|
||||
"description": "Stable source id — the same string this source's entries carry in their `store` field. For a\nplugin source it is also its provider id and its store claim: one string, by construction, so\na user's disabled state survives a built-in scanner being replaced by its plugin.",
|
||||
"description": "Stable scanner id — the same string the scanner's entries carry in their `store` field.",
|
||||
"example": "steam"
|
||||
},
|
||||
"label": {
|
||||
"type": "string",
|
||||
"description": "Human-facing name for the console toggle.",
|
||||
"example": "Steam"
|
||||
},
|
||||
"origin": {
|
||||
"$ref": "#/components/schemas/SourceOrigin",
|
||||
"description": "Where the source comes from: `builtin` (a scanner in this host build) or `plugin`."
|
||||
},
|
||||
"provider": {
|
||||
"type": [
|
||||
"string",
|
||||
"null"
|
||||
],
|
||||
"description": "The provider id backing a `plugin` source — absent for a built-in scanner."
|
||||
}
|
||||
}
|
||||
},
|
||||
@@ -7102,14 +6962,6 @@
|
||||
}
|
||||
}
|
||||
},
|
||||
"SourceOrigin": {
|
||||
"type": "string",
|
||||
"description": "Where a [`ScannerInfo`] comes from.",
|
||||
"enum": [
|
||||
"builtin",
|
||||
"plugin"
|
||||
]
|
||||
},
|
||||
"SourceView": {
|
||||
"type": "object",
|
||||
"description": "A configured catalog source and how its last refresh went.",
|
||||
|
||||
@@ -26,25 +26,13 @@ import kotlin.math.roundToInt
|
||||
* presentsWindow, presenterActive, feedP50Ms, codecP50Ms, skippedOverflowWindow]`. Every read
|
||||
* is length-guarded, so an older native lib simply omits the lines it can't feed.
|
||||
*
|
||||
* The shown `display` and `end-to-end` numbers EXCLUDE the OS present floor (see [osFloorMs]) at
|
||||
* every tier, and the detailed tier names what was excluded on its own line. The principle is the
|
||||
* Apple client's: metrics report what Punktfunk controls, so the compositor's own latch and scanout
|
||||
* — which no client can pace under — is reported rather than charged. It also stops the HUD reading
|
||||
* worse than it is: the usual Android streaming overlays stop measuring at decode-complete, so a
|
||||
* headline that carried the compositor's wait was compared against numbers that never contained it.
|
||||
*
|
||||
* The RAW figures are not lost — the native 1 Hz `pf.present` logcat line keeps `paceMs`, `latchMs`
|
||||
* and `e2eMs` unshaved, so a HUD-off A/B and any cross-session comparison still work off the
|
||||
* untouched numbers.
|
||||
*
|
||||
* [verbosity] selects how many lines render (each tier a superset of the last — see
|
||||
* [StatsVerbosity]):
|
||||
* - [StatsVerbosity.COMPACT] — one line, `fps · end-to-end ms · Mb/s` (+ a loss flag).
|
||||
* - [StatsVerbosity.NORMAL] — the res/fps/Mb·s line, the end-to-end p50/p95 headline, and the
|
||||
* reliability counters (18–21) when nonzero.
|
||||
* - [StatsVerbosity.DETAILED] — also the decoder label, the video-feed descriptor (10–13), the
|
||||
* stage equation (14/15, split into `host + network` when the Phase-2 terms at 16/17 are nonzero),
|
||||
* and the excluded-floor line when one was measured.
|
||||
* - [StatsVerbosity.DETAILED] — also the decoder label, the video-feed descriptor (10–13), and the
|
||||
* stage equation (14/15, split into `host + network` when the Phase-2 terms at 16/17 are nonzero).
|
||||
* [StatsVerbosity.OFF] renders nothing. Older native layouts simply omit the lines they lack (the
|
||||
* counter line falls back to the cumulative `lostTotal` at index 9 on a pre-window lib).
|
||||
*/
|
||||
@@ -107,15 +95,9 @@ internal fun StatsOverlay(
|
||||
// equation gains its `display` term; otherwise (older lib / no callbacks) the endpoint
|
||||
// honestly stays capture→decoded — the equation always tiles the headline interval.
|
||||
val dispValid = s.size >= 26 && s[22] != 0.0
|
||||
// The OS present floor this window (see [osFloorMs]) is excluded from every shown
|
||||
// display / end-to-end number, at every tier — it is pipeline depth no client can pace
|
||||
// under, so charging it to Punktfunk made our HUD read worse than clients that simply
|
||||
// never measure it. 0.0 when unmeasured, which leaves the numbers exactly as raw as
|
||||
// they were.
|
||||
val floorMs = osFloorMs(s)
|
||||
val tag = if (skew) "" else " (same-host clock)"
|
||||
val (p50, p95, endpoint) = if (dispValid) {
|
||||
Triple(shave(s[24], floorMs), shave(s[25], floorMs), "capture→displayed")
|
||||
Triple(s[24], s[25], "capture→displayed")
|
||||
} else {
|
||||
Triple(s[2], s[3], "capture→decoded")
|
||||
}
|
||||
@@ -138,11 +120,6 @@ internal fun StatsOverlay(
|
||||
// dropping/serializing, an fps deficit is upstream.
|
||||
val split = s.size >= 30 && s[29] != 0.0 && (s[26] > 0 || s[27] > 0)
|
||||
val displayTerm = when {
|
||||
// Floor excluded: what remains of the `display` term is the half Punktfunk
|
||||
// owns (the presenter's pace wait), and the excluded line below carries the
|
||||
// latch — printing the split too would report the same milliseconds twice.
|
||||
dispValid && floorMs > 0 ->
|
||||
" + display ${"%.1f".format(shave(s[23], floorMs))}"
|
||||
dispValid && split ->
|
||||
" + display ${"%.1f".format(s[23])} " +
|
||||
"(pace ${"%.1f".format(s[26])} + latch ${"%.1f".format(s[27])})"
|
||||
@@ -166,14 +143,16 @@ internal fun StatsOverlay(
|
||||
"= $hostTerms + $decodeTerm$displayTerm$presents",
|
||||
Color.White,
|
||||
)
|
||||
// What the numbers above leave out, named — the Apple client's
|
||||
// `os present +N excluded` line, same wording so the two HUDs read alike.
|
||||
// (This replaces the old "≈ Apple-HUD equiv" twin: both clients now shave, and
|
||||
// Android's shave is measured rather than assumed at 2 refresh periods.)
|
||||
if (floorMs > 0) {
|
||||
// Metric fairness: the Apple client's HUD shaves ~2 refresh periods of OS
|
||||
// pipeline floor off its shown display/end-to-end; Android shows raw. This twin
|
||||
// applies the same shave so iPhone↔Android HUD numbers compare directly.
|
||||
if (dispValid && hz > 0) {
|
||||
val shave = 2000.0 / hz
|
||||
statLine(
|
||||
"os present +${"%.1f".format(floorMs)} excluded (display pipeline minimum)",
|
||||
Color(0xFF9AA6B8),
|
||||
"≈ Apple-HUD equiv: end-to-end " +
|
||||
"${"%.1f".format((s[24] - shave).coerceAtLeast(0.0))} · display " +
|
||||
"${"%.1f".format((s[23] - shave).coerceAtLeast(0.0))} (−2 refresh)",
|
||||
Color(0xFFA8D8B8),
|
||||
)
|
||||
}
|
||||
}
|
||||
@@ -188,37 +167,6 @@ private fun statLine(text: String, color: Color) {
|
||||
Text(text, color = color, fontFamily = FontFamily.Monospace, fontSize = 12.sp)
|
||||
}
|
||||
|
||||
/**
|
||||
* The OS present floor to exclude from the shown `display` / `end-to-end` numbers, ms — the
|
||||
* measured `latch` p50 at index 27, i.e. release→`OnFrameRendered`: SurfaceFlinger's own latch and
|
||||
* scanout. That is compositor pipeline depth no client can pace under, so it is reported as
|
||||
* excluded rather than charged to Punktfunk — the Apple client's policy since its presentation
|
||||
* rebuild, where the same floor is measured from the display link's vend lead.
|
||||
*
|
||||
* Measured, not assumed: the previous Android treatment used a fixed `2000/hz` twin, but the latch
|
||||
* varies with panel rate, tunnelled playback and the vendor's low-latency mode (~21 ms p50 observed
|
||||
* where the ~2-interval model predicts less), and this term self-adapts to all three. It is also
|
||||
* available on every render path — the presenter's and both legacy release-immediately ones — since
|
||||
* the release stamp it starts from is parked on every render, so it does not depend on
|
||||
* `presenterActive` (29).
|
||||
*
|
||||
* `0.0` means unmeasured — no display stage this window (an older native lib, API < 33, or a
|
||||
* platform that refused the callback), or no latch sample paired — and every caller then leaves its
|
||||
* number raw, which is the honest fallback: we exclude only what we actually measured.
|
||||
*/
|
||||
private fun osFloorMs(s: DoubleArray): Double {
|
||||
val dispValid = s.size >= 26 && s[22] != 0.0
|
||||
if (!dispValid || s.size < 28) return 0.0
|
||||
return s[27].coerceAtLeast(0.0)
|
||||
}
|
||||
|
||||
/**
|
||||
* Subtract the excluded [floorMs] from a shown latency [ms], clamped at zero — the percentiles are
|
||||
* drawn from different sample sets (a p50 latch against a p50/p95 end-to-end), so the difference can
|
||||
* legitimately go slightly negative on a well-paced window without anything being wrong.
|
||||
*/
|
||||
private fun shave(ms: Double, floorMs: Double): Double = (ms - floorMs).coerceAtLeast(0.0)
|
||||
|
||||
/**
|
||||
* The single [StatsVerbosity.COMPACT] line: `238 fps · 1.3 ms · 921 Mb/s`. The end-to-end p50 term
|
||||
* is dropped when no in-range latency sample landed (`latValid` false), and a loss flag
|
||||
@@ -226,9 +174,8 @@ private fun shave(ms: Double, floorMs: Double): Double = (ms - floorMs).coerceAt
|
||||
* one reliability signal worth surfacing even at the tersest tier.
|
||||
*/
|
||||
private fun compactLine(s: DoubleArray, latValid: Boolean): String {
|
||||
// Prefer the capture→displayed end-to-end (s[24]) when a render timestamp landed this window,
|
||||
// less the excluded OS present floor — the same number the richer tiers headline.
|
||||
val e2eP50 = if (s.size >= 26 && s[22] != 0.0) shave(s[24], osFloorMs(s)) else s[2]
|
||||
// Prefer the capture→displayed end-to-end (s[24]) when a render timestamp landed this window.
|
||||
val e2eP50 = if (s.size >= 26 && s[22] != 0.0) s[24] else s[2]
|
||||
val parts = buildList {
|
||||
add("${s[0].roundToInt()} fps")
|
||||
if (latValid) add("${"%.1f".format(e2eP50)} ms")
|
||||
|
||||
@@ -355,11 +355,9 @@ internal fun StreamScene(verbosity: StatsVerbosity = StatsVerbosity.DETAILED) {
|
||||
// dispValid, displayP50, e2eDispP50, e2eDispP95].
|
||||
// 10/9/16/1 = a 10-bit BT.2020 PQ (HDR) 4:2:0 feed so the DETAILED HUD renders its
|
||||
// video-feed line; the display stage is valid (dispValid 1) so the headline is the
|
||||
// directly-measured capture→displayed pair, less the excluded OS present floor (the 0.3
|
||||
// latch p50) — 1.5/2.3 shown from 1.8/2.6 raw — and the Phase-2 stage terms
|
||||
// (host 0.6 + network 0.3 + decode 0.4 + display 0.2) tile the shaved headline, with the
|
||||
// `os present +0.3 excluded` line naming what came off; the decoder label shows the ranked
|
||||
// low-latency decoder. Light per-window loss
|
||||
// directly-measured capture→displayed pair (1.8/2.6) and the Phase-2 stage terms
|
||||
// (host 0.6 + network 0.3 + decode 0.4 + display 0.5) tile it, rendering the full split
|
||||
// equation; the decoder label shows the ranked low-latency decoder. Light per-window loss
|
||||
// (lost 2 · skipped 1 · FEC 5 of 238) so the reliability line (NORMAL/DETAILED) and the
|
||||
// compact loss flag both render.
|
||||
StatsOverlay(
|
||||
|
||||
@@ -127,14 +127,8 @@ object LibraryClient {
|
||||
* An OkHttpClient that presents the paired client cert and pins the host's self-signed cert by
|
||||
* SHA-256(DER) — reused for BOTH the library fetch and the cover-art loads (so a paired client
|
||||
* reaches the host's own art proxy). The pinning trust manager trusts the host by fingerprint and
|
||||
* defers to normal public trust for any other origin (an external CDN URL).
|
||||
*
|
||||
* The two checks are only sound TOGETHER, and the composition is the point: the trust manager
|
||||
* cannot fail closed on its own (it has no hostname, so it must let a CDN chain through), so the
|
||||
* hostname verifier is what makes the pinned host pin-only. Loosen either and a publicly-trusted
|
||||
* certificate for any name is accepted for the host — which is exactly what 2026-08-05 review M-2
|
||||
* found. The host's own cert is self-signed with no matching SAN, so it can never satisfy the
|
||||
* default verifier; the pin is its only credential, on purpose.
|
||||
* defers to normal public trust for any other origin (an external CDN URL); the hostname verifier
|
||||
* accepts the pinned host (whose self-signed cert has no matching SAN) and defers otherwise.
|
||||
*/
|
||||
fun mtlsHttpClient(certPem: String, keyPem: String, host: String, fpHex: String): OkHttpClient {
|
||||
val clientCert = CertificateFactory.getInstance("X.509")
|
||||
@@ -168,26 +162,7 @@ fun mtlsHttpClient(certPem: String, keyPem: String, host: String, fpHex: String)
|
||||
|
||||
val defaultVerifier = HttpsURLConnection.getDefaultHostnameVerifier()
|
||||
val verifier = HostnameVerifier { hostname, session ->
|
||||
if (hostname == host) {
|
||||
// The PINNED host fails closed: only the pinned leaf is acceptable for this name.
|
||||
//
|
||||
// This used to be a bare `hostname == host`, which composed with the trust manager's
|
||||
// system-CA fall-through into "any publicly-trusted certificate, for any name, is
|
||||
// accepted for the pinned host" — the pin was decorative (2026-08-05 review M-2). A
|
||||
// MITM with any free CA-issued cert intercepted the connection, received the client's
|
||||
// mTLS IDENTITY certificate, and served attacker-chosen library JSON and art URLs.
|
||||
// The Rust (`pf-client-core`) and Apple (`ClientTLS`) paths already fail closed here;
|
||||
// only Android did not.
|
||||
try {
|
||||
sha256Hex((session.peerCertificates.firstOrNull() as? X509Certificate)?.encoded ?: return@HostnameVerifier false) == pinned
|
||||
} catch (_: Exception) {
|
||||
false
|
||||
}
|
||||
} else {
|
||||
// Any other origin (an external CDN art URL) is ordinary public trust: the system
|
||||
// trust manager validated the chain, and this checks the name against it.
|
||||
defaultVerifier.verify(hostname, session)
|
||||
}
|
||||
hostname == host || defaultVerifier.verify(hostname, session)
|
||||
}
|
||||
|
||||
return OkHttpClient.Builder()
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
{
|
||||
"name": "Punktfunk",
|
||||
"name": "punktfunk",
|
||||
"author": "enrico",
|
||||
"flags": ["debug"],
|
||||
"api_version": 1,
|
||||
|
||||
@@ -12,9 +12,7 @@
|
||||
set -euo pipefail
|
||||
HERE="$(cd "$(dirname "$0")/.." && pwd)"
|
||||
DECK="${DECK:?set DECK=deck@<ip>}"
|
||||
# The on-disk plugin DIR (what scripts/package.sh staged into out/), not plugin.json "name" —
|
||||
# that field is the brand-cased label Decky shows in its plugin list. See package.sh's header.
|
||||
NAME=punktfunk
|
||||
NAME="$(python3 -c 'import json;print(json.load(open("'"$HERE"'/plugin.json"))["name"])')"
|
||||
STAGE_LOCAL="$HERE/out/$NAME"
|
||||
[ -d "$STAGE_LOCAL" ] || { echo "$STAGE_LOCAL missing — run scripts/package.sh first" >&2; exit 1; }
|
||||
|
||||
|
||||
@@ -5,13 +5,9 @@
|
||||
# package.json,decky.pyi,LICENSE,README.md}
|
||||
# out/punktfunk/ (the same tree, unzipped — rsync this with scripts/deploy.sh)
|
||||
#
|
||||
# The single top-level dir is the plugin's ON-DISK folder name (Decky extracts the zip as-is,
|
||||
# so the dir in the zip becomes ~/homebrew/plugins/<dir>). It is deliberately NOT read from
|
||||
# plugin.json "name": that field is the user-visible label ("Punktfunk", brand-cased, shown in
|
||||
# Decky's plugin list) and Decky locates an installed plugin by MATCHING it, never by the folder
|
||||
# name. Keeping the folder lowercase means a rename of the label can't strand the old directory
|
||||
# next to a new one (which would show up as two plugins).
|
||||
# Run after `pnpm build` (or use `pnpm run package`). Host-agnostic: needs only bash, python3 and zip.
|
||||
# Decky extracts the zip with --strip-components=1, so the single top-level dir MUST equal
|
||||
# plugin.json "name". Run after `pnpm build` (or use `pnpm run package`). Host-agnostic: needs
|
||||
# only bash, python3 and zip.
|
||||
set -euo pipefail
|
||||
HERE="$(cd "$(dirname "$0")/.." && pwd)"
|
||||
cd "$HERE"
|
||||
@@ -19,7 +15,7 @@ cd "$HERE"
|
||||
[ -f dist/index.js ] || { echo "dist/index.js missing — run 'pnpm build' first" >&2; exit 1; }
|
||||
[ -f LICENSE ] || { echo "LICENSE missing (required by the Decky store)" >&2; exit 1; }
|
||||
|
||||
NAME=punktfunk # the on-disk plugin dir (see the header) — NOT plugin.json "name"
|
||||
NAME="$(python3 -c 'import json;print(json.load(open("plugin.json"))["name"])')"
|
||||
VER="$(python3 -c 'import json;print(json.load(open("package.json"))["version"])')"
|
||||
|
||||
STAGE="$(mktemp -d)"
|
||||
|
||||
@@ -122,25 +122,6 @@ function advertMatchesSaved(a: DiscoveredHost, s: SavedHost): boolean {
|
||||
);
|
||||
}
|
||||
|
||||
/**
|
||||
* The label a saved row shows.
|
||||
*
|
||||
* A saved record whose name IS its own address is a PLACEHOLDER, not a choice: `hosts add`
|
||||
* falls back to the address when the pairing path had nothing better, so the row ends up
|
||||
* captioned with the same string it already prints underneath. When the box is on the air it
|
||||
* is advertising its actual hostname — prefer that, and the row reads "home-worker-5" instead
|
||||
* of "192.168.1.21".
|
||||
*
|
||||
* A real saved name always wins over the advert, even a stale one: it may be a name the user
|
||||
* chose, and a live advert must never quietly overwrite that. Compared against the SAVED
|
||||
* address, so a host that moved DHCP lease still recognises its old address as a placeholder.
|
||||
*/
|
||||
function hostLabel(s: SavedHost, advert?: DiscoveredHost): string {
|
||||
const placeholder = !s.name || s.name === s.addr || s.name === `${s.addr}:${s.port}`;
|
||||
if (!placeholder) return s.name;
|
||||
return advert?.name || s.name || s.addr;
|
||||
}
|
||||
|
||||
/**
|
||||
* Join the saved store and the live browse into the rows the panel draws.
|
||||
*
|
||||
@@ -153,7 +134,7 @@ export function mergeHosts(saved: SavedHost[], discovered: DiscoveredHost[]): Ho
|
||||
// Prefer a live advert's address: the host may have moved since it was last saved.
|
||||
const advert = discovered.find((a) => advertMatchesSaved(a, s));
|
||||
return {
|
||||
name: hostLabel(s, advert),
|
||||
name: s.name || s.addr,
|
||||
addr: advert?.addr ?? s.addr,
|
||||
port: advert?.port ?? s.port,
|
||||
fp: s.fp_hex,
|
||||
@@ -406,10 +387,7 @@ export async function applyUpdate(
|
||||
// before any result could arrive — so never await it. Decky shows its own confirm prompt.
|
||||
void backend.callable("utilities/install_plugin")(
|
||||
info.artifact,
|
||||
// The name Decky uninstalls before extracting the new zip — it locates the folder by
|
||||
// matching plugin.json "name", so this must equal THIS build's plugin.json name (the
|
||||
// brand-cased one), not the lowercase on-disk dir.
|
||||
"Punktfunk",
|
||||
"punktfunk",
|
||||
info.latest,
|
||||
info.hash,
|
||||
INSTALL_TYPE_UPDATE,
|
||||
|
||||
@@ -337,11 +337,9 @@ export default definePlugin(() => {
|
||||
// controller config. Fire-and-forget: cosmetic library upkeep must never block plugin load.
|
||||
void ensureGamepadUiShortcut();
|
||||
return {
|
||||
// `name` must stay in sync with plugin.json (the loader keys plugins by it) — and it is
|
||||
// USER-VISIBLE: Decky labels the entry in its plugin list with it, so it carries the brand
|
||||
// case. Decky finds an installed plugin by matching plugin.json "name" (never the folder
|
||||
// name), so this is independent of the on-disk dir, which stays lowercase `punktfunk`.
|
||||
name: "Punktfunk",
|
||||
// `name` is the plugin's INTERNAL id — it must stay in sync with plugin.json (the loader
|
||||
// keys plugins by it), so it stays lowercase; user-facing strings say "Punktfunk".
|
||||
name: "punktfunk",
|
||||
// `staticClasses?.Title` is guarded so a future client that drops the export can't throw
|
||||
// at plugin-load time (an error boundary only catches render-time, not load-time, errors).
|
||||
titleView: <div className={staticClasses?.Title}>Punktfunk</div>,
|
||||
|
||||
@@ -70,18 +70,9 @@ declare const appStore:
|
||||
* entry from a false "missing". A confident null means the shortcut was deleted → recreate. */
|
||||
function shortcutStillExists(appId: number): boolean {
|
||||
try {
|
||||
// Call it as a METHOD on appStore — NEVER as an extracted function. Its implementation
|
||||
// reads the store's own state (`this.m_mapApps`), so `const get = appStore.GetAppOverview…;
|
||||
// get(id)` throws on the lost `this`, and the catch below turns that into a permanent
|
||||
// "true". That is not a stale-data bug but a total one: the guard then answers "still
|
||||
// exists" for EVERY appId, so a dangling id is never dropped, the reuse path repoints a
|
||||
// dead shortcut (silent no-ops), and "recreate" reports success having done nothing.
|
||||
// `typeof` first: `appStore` is a Steam-injected global, and a bare reference to a missing
|
||||
// one is a ReferenceError that optional chaining does NOT prevent.
|
||||
if (typeof appStore === "undefined" || !appStore?.GetAppOverviewByAppID) {
|
||||
return true; // no way to verify — preserve the reuse path
|
||||
}
|
||||
return appStore.GetAppOverviewByAppID(appId) != null;
|
||||
const get = appStore?.GetAppOverviewByAppID;
|
||||
if (!get) return true; // no way to verify — preserve the reuse path
|
||||
return get(appId) != null;
|
||||
} catch {
|
||||
return true;
|
||||
}
|
||||
|
||||
@@ -773,7 +773,6 @@ fn mock_library() -> (
|
||||
title: title.to_string(),
|
||||
art: crate::library::Artwork::default(),
|
||||
platform: None,
|
||||
role: None,
|
||||
};
|
||||
let games = vec![
|
||||
game("steam:570", "steam", "Dota 2"),
|
||||
|
||||
@@ -49,19 +49,40 @@ 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: hardware Vulkan Video →
|
||||
VAAPI → software on Linux, hardware Vulkan Video → D3D11VA → software on Windows, with
|
||||
VAAPI/D3D11VA first on Intel; on H.264 and HEVC the native pf-vkdecode Vulkan decoder
|
||||
is tried immediately before FFmpeg-Vulkan): the Vulkan decoders run 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
|
||||
native, FFmpeg-Vulkan, VAAPI/D3D11VA and software paths alike, 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=
|
||||
Debug/bisect knobs: `PUNKTFUNK_DECODER=native-vulkan|vulkan|vaapi|d3d11va|software`, `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` or on any FFmpeg lane. 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).
|
||||
|
||||
@@ -1911,35 +1911,10 @@ pub(crate) fn settings_page(
|
||||
} else {
|
||||
border(vstack(Vec::<Element>::new())).into()
|
||||
};
|
||||
// Every save on this page is fire-and-forget by design — a failed settings write must
|
||||
// never take a stream down — so a client whose config store rejects writes looks entirely
|
||||
// normal: toggles move, profiles appear, and NOTHING survives a restart. That is exactly
|
||||
// how it reached us from the field ("it's in read-only mode"), with no log file to send
|
||||
// either. When the store is refusing writes, say so, name the path, and stop pretending.
|
||||
//
|
||||
// Same always-mounted-slot discipline as `sheet_slot`: one child in both states, and the
|
||||
// SAME KIND in both (a Border wrapping the bar, versus an empty background-less Border —
|
||||
// which per style.rs is not hit-testable, so it swallows no clicks). Neither a grid child
|
||||
// nor a vstack child is ever added or removed, which is where this reconciler's phantom
|
||||
// bookkeeping breaks.
|
||||
let store_slot: Element = match pf_client_core::trust::store_health::last_error() {
|
||||
Some(err) => border(
|
||||
InfoBar::new("Your changes aren\u{2019}t being saved")
|
||||
.message(format!(
|
||||
"Punktfunk can\u{2019}t write to its settings folder, so nothing on this \
|
||||
page will survive a restart. {err}"
|
||||
))
|
||||
.error()
|
||||
.is_closable(false),
|
||||
)
|
||||
.margin(edges(24.0, 12.0, 28.0, 0.0))
|
||||
.into(),
|
||||
None => border(vstack(Vec::<Element>::new())).into(),
|
||||
};
|
||||
// The bar rides an Auto row above the nav's Star row, so the nav (and the sheet's scrim
|
||||
// over it) still fills the rest of the window.
|
||||
grid(vec![
|
||||
Element::from(vstack(vec![store_slot, scope_bar])).grid_row(0),
|
||||
scope_bar.grid_row(0),
|
||||
Element::from(grid(vec![nav.into(), sheet_slot, confirm])).grid_row(1),
|
||||
])
|
||||
.rows([GridLength::Auto, GridLength::STAR])
|
||||
|
||||
@@ -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
@@ -0,0 +1,156 @@
|
||||
//! 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 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
|
||||
BIN
Binary file not shown.
+2
@@ -0,0 +1,2 @@
|
||||
2407c115
|
||||
396fe8d4
|
||||
+2
@@ -0,0 +1,2 @@
|
||||
afcabbf0be007e76b1f496e199eb07fa
|
||||
cebc9437bfc8501523c432d3fee3621d
|
||||
Binary file not shown.
+1
@@ -0,0 +1 @@
|
||||
a5a83a48
|
||||
+1
@@ -0,0 +1 @@
|
||||
15caee73c93560200b9e240c6f5d7d0f
|
||||
@@ -0,0 +1,17 @@
|
||||
# H.265 Test Data
|
||||
|
||||
This document lists the test data used by the H.265 parser.
|
||||
|
||||
## bear.hevc
|
||||
|
||||
Same as Chromium's `bbb.hevc`.
|
||||
|
||||
## bear.hevc
|
||||
|
||||
Same as Chromium's `bear.hevc`.
|
||||
|
||||
## test-25fps.hevc
|
||||
|
||||
Same as Chromium's `test-25fps.hevc`.
|
||||
|
||||
The slice data for the first two slices in this stream was extracted manually from GStreamer using GDB.
|
||||
Binary file not shown.
@@ -0,0 +1,60 @@
|
||||
97255112
|
||||
c317718d
|
||||
bd113d45
|
||||
186209fe
|
||||
75726ee3
|
||||
e4a90dbd
|
||||
1da8aec0
|
||||
f537bcee
|
||||
bdcead02
|
||||
edd12fd6
|
||||
9814391e
|
||||
ea813192
|
||||
8e3f606c
|
||||
7298a718
|
||||
c25ed8f9
|
||||
54585aa5
|
||||
c5d6811c
|
||||
83ffc178
|
||||
ad9b6189
|
||||
20e3b289
|
||||
ea852c44
|
||||
38af63d3
|
||||
222e116b
|
||||
cbf5b144
|
||||
5cc11d3f
|
||||
561aba19
|
||||
c1b0021e
|
||||
97465654
|
||||
3f69533a
|
||||
95724dac
|
||||
4fd1865b
|
||||
00fc6666
|
||||
262e9ecc
|
||||
753e21c5
|
||||
73820a63
|
||||
6023ad07
|
||||
6baf0154
|
||||
6b658956
|
||||
c7f3d4d0
|
||||
b7fe7729
|
||||
652e5667
|
||||
76b64fad
|
||||
98f934ef
|
||||
64a737be
|
||||
e561fe00
|
||||
2bd0799f
|
||||
53d86edb
|
||||
07cad162
|
||||
c4857692
|
||||
9ab13929
|
||||
c445eb2a
|
||||
f0e890de
|
||||
1db971fc
|
||||
d03cc3fa
|
||||
69d2cdba
|
||||
172562d2
|
||||
356c55d0
|
||||
515bfbd6
|
||||
5d8dfbc2
|
||||
ec0874fb
|
||||
@@ -0,0 +1,60 @@
|
||||
e4519fce4b5a50a8ae38d2b42864e550
|
||||
c7f6707ea7b2aaa3a9c027e00bf2064b
|
||||
21c016eb7a57804d8637b03bc7910b31
|
||||
f744d8b67d4836629beac1ca4efdad9b
|
||||
8e593571ad75b9b19c3b14b44033bc03
|
||||
3f76c8397f7829d948ddb50c7050b4bb
|
||||
d91e343dbdb64dfd15f09abc4c460b06
|
||||
62d5c1de7a7ff8b4cdfb51cb33746bcd
|
||||
01a4c4d5f770d3cdc5429264f6a1a742
|
||||
760de93c5a25439a89378fbe50c26f3d
|
||||
d56f52f9252745321c723121824e5d21
|
||||
be626892c70d6fd412d7bff504852529
|
||||
ae49266f3b90dd1238f7ad83ae86886e
|
||||
7a9fe15547cbcb51557bfd0b8370e0ee
|
||||
a5b81f4e52fe54f67d109caa200486ec
|
||||
34395343b832af13dbe37ebe745506e0
|
||||
36d6e24c8a000b4f8d5c1aabba85c185
|
||||
9be3b6e5b0b77c136b5abe8a22bded59
|
||||
5122057efd33ed5634b755a7b0581382
|
||||
abaf35e215001a6abe0dbc63f5b4ace1
|
||||
62ba3a52072f80993c24cf9a3abda06f
|
||||
21b225675443d0142e104ec88f7e9569
|
||||
b561e6555b65ba6cebf0d73eefe4f319
|
||||
ab2b1a568bed6f9bafe79cf15dfd85b5
|
||||
e8c3645830f80e082e8b02e3cc599cc2
|
||||
82d655fb233fea3fc3377b4d3dfc11ff
|
||||
4a7c2a51149c1939cffc8cfc87fdb953
|
||||
32295652a5ff998c90fa18fbfc066fdc
|
||||
ce8974e2519a2babc43fb29106459cd2
|
||||
589fa92d80565556776c2108f676c439
|
||||
2bc1878534bf66caa6d686d0dad5a936
|
||||
b3d81309bb62ed349bed2c0fb17a5593
|
||||
c941ae94ab205aa1c27b9b06df4b57de
|
||||
e782fa5f5a7302b8246c29880e9c2f9d
|
||||
eeabb3a6c5fa1ea0f17a9fb911f5b612
|
||||
26f27eafea985de929a48287162fc641
|
||||
da4f4da7a16a4326c0c458f3dba12ef6
|
||||
7697150fa654713af6a6ecc07ad7d3dc
|
||||
67696b8c27169cbdbc18d093f794b328
|
||||
96ba8f5d17e7b35e78f5ac78a816a364
|
||||
1ab2c0d535d26c72d730e101600f42af
|
||||
f8b9d8fc231ccd64c551c829ef25089a
|
||||
eb004174e79dc8dbb533922639a725e2
|
||||
fd519318eb8c47e5fac6fc767246bfed
|
||||
471dbd17c2a374ab7dcaf64c345c1884
|
||||
fc28a8f484573893f6f501a4ade8ac37
|
||||
1e2dd38a14d5c13ef76e89d142f73951
|
||||
0fd6a338f4ecb3df053d105bc95e6ee1
|
||||
a42fd1e8bb069ab5c501fbfd655fc1b0
|
||||
f252d07215ddd6687fb3019f2710c939
|
||||
c8df7b9cbc16a26d782494b8cd312be4
|
||||
966a2f3fcb066179feef80e68f13e212
|
||||
b99b1100b6950144e70fa47310451340
|
||||
9b3502c7dc1a4ca2579a29f6b17af84d
|
||||
9a81be9720c64ce3b343fd20bc17bb14
|
||||
e5a67048a9c1ae74b8a97c5a27d6d2b2
|
||||
1527a719633c75b67ae6c36ebe550812
|
||||
86b1329ec84332fec1a75330e7d7a174
|
||||
6fa5b5faf93d70e96ffa215e4628ad9a
|
||||
ed6279f7b90727abc8a193745170a318
|
||||
Binary file not shown.
+30
@@ -0,0 +1,30 @@
|
||||
0a6ac0dd
|
||||
350b4669
|
||||
61a8b9ad
|
||||
f566d7c6
|
||||
8d8ab332
|
||||
52468dae
|
||||
44bdbf5f
|
||||
935e3db3
|
||||
814109bc
|
||||
68dc2234
|
||||
d5b47d8b
|
||||
852f3e1f
|
||||
3f54c08b
|
||||
9e33e4ae
|
||||
047c4f0d
|
||||
10cebb43
|
||||
87774650
|
||||
08adc4be
|
||||
837797a6
|
||||
17f80256
|
||||
221e943f
|
||||
e06ff206
|
||||
7fb43061
|
||||
e5dc2425
|
||||
37928778
|
||||
3a787cf0
|
||||
14ea1afd
|
||||
0331b84f
|
||||
bdfa1606
|
||||
28216ab7
|
||||
+30
@@ -0,0 +1,30 @@
|
||||
037e7ff0ab754f16dc254edbecaf5bc0
|
||||
47d9c4d6427726b74dd8586aeea147e4
|
||||
1fae73f309640f704f8358fc0bae4c39
|
||||
579c2fe2b1fa83ab3082e30daebba728
|
||||
a4e01659d6bc12626d445a02fa5f977e
|
||||
3bcadc5d9985e44338edca617fd84541
|
||||
ac92b5ee1f79b5bda8f662eefab40085
|
||||
03d24eba6675f51496b9338d97c39308
|
||||
77f9d5f1e2e5bd40164336d9fe7970ef
|
||||
355f2e0f8e8c4702ad45c790415004df
|
||||
1d9563a8ccdf00b2116edb508b665e7e
|
||||
44eb70aeba2c5eba6b0ce931d6fff3e5
|
||||
c5fab1da55bd8177342d4e174bdc1474
|
||||
30f0d9a37231141722ba65c61bd59d09
|
||||
a1e8fdc8b33af447e4486c17d028b706
|
||||
11cd37f438399a595745dff608965aa2
|
||||
8025ff7ac2d9c9cda4039900834c1182
|
||||
e934c10259100457b44bd13a8630a3e0
|
||||
9f745c40aae4b6c31d57a0a43b39a4d2
|
||||
041ea32d187a011baafda69c1f079b2e
|
||||
d8a172fed23b9f0b4213bc7f8f1375a9
|
||||
22e9e048f8e04410307068b6bc2e79d7
|
||||
3c9d59aa49756060f8e78a5d608db8e6
|
||||
5482d7c76a6ff60c9c32fffc27c39873
|
||||
44a4d11b3ecdc827cc21ff732e1c7c7d
|
||||
aeaaff7c5b23b4f03d939de6c12e5006
|
||||
afa6ece7a21db66eece97e2e0713f29c
|
||||
d84b7794f4a4968ea7fadf5cba7333d0
|
||||
f9644eca3981a123b917938c7a4963fb
|
||||
e02c049b9e516e10c05ce4469856e37d
|
||||
+8
@@ -0,0 +1,8 @@
|
||||
#!/bin/bash
|
||||
|
||||
# Generates the CRCs for all .h265 files in the current directory using ffmpeg.
|
||||
|
||||
for f in `ls *.h265`; 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.
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user