Compare commits

...

89 Commits

Author SHA1 Message Date
enricobuehler 4d2632a845 fix(client/deps): ring-only rustls in pf-client-core — finish dropping aws-lc-sys
audit / cargo-audit (push) Successful in 2m30s
deb / build-publish (push) Successful in 13m42s
web-screenshots / screenshots (push) Successful in 2m47s
ci / bench (push) Successful in 6m1s
android-screenshots / screenshots (push) Successful in 3m59s
apple / screenshots (push) Successful in 6m27s
decky / build-publish (push) Successful in 24s
rpm / build-publish (43, bazzite, punktfunk-fedora-rpm) (push) Successful in 15m46s
docker / deploy-docs (push) Successful in 11s
docker / build-push (--build-arg FEDORA_VERSION=44, ci, ci/fedora-rpm.Dockerfile, punktfunk-fedora44-rpm) (push) Successful in 13s
docker / build-push (ci, ci/fedora-rpm.Dockerfile, punktfunk-fedora-rpm) (push) Successful in 12s
docker / build-push (., web/Dockerfile, punktfunk-web) (push) Successful in 12s
docker / build-push (docs-site, docs-site/Dockerfile, punktfunk-docs) (push) Successful in 11s
docker / build-push (ci, ci/rust-ci.Dockerfile, punktfunk-rust-ci) (push) Successful in 12s
rpm / build-publish (44, fedora-44, punktfunk-fedora44-rpm) (push) Successful in 15m2s
windows / build (aarch64-pc-windows-msvc) (push) Successful in 8m21s
windows / build (x86_64-pc-windows-msvc) (push) Successful in 9m18s
ci / rust (push) Failing after 17m36s
windows-host / package (push) Successful in 20m17s
flatpak / build-publish (push) Failing after 8m3s
windows-msix / package (arm64, C:\Users\Public\ffmpeg-arm64, --no-default-features, aarch64-pc-windows-msvc, C:\t-a64) (push) Successful in 7m5s
linux-client-screenshots / screenshots (push) Successful in 7m54s
arch / build-publish (push) Successful in 13m5s
android / android (push) Successful in 13m44s
release / apple (push) Successful in 10m22s
windows-msix / package (x64, C:\Users\Public\ffmpeg, , x86_64-pc-windows-msvc, C:\t) (push) Successful in 7m17s
apple / swift (push) Successful in 1m25s
audit / bun-audit (push) Successful in 16s
ci / web (push) Successful in 48s
ci / docs-site (push) Successful in 57s
Companion to the host ring fix: pf-client-core declared rustls with
features=["ring"] but WITHOUT default-features=false, so rustls's default
aws-lc-rs backend came along too (feature tree: aws-lc-rs <- rustls default <-
pf-client-core default) — pulling aws-lc-sys 0.41.0 into the Windows client +
session + presenter builds, the same C dep that fails on the CI runner. Pin
default-features=false + [ring, logging, std, tls12] (matching the tray/host/core
pattern). aws-lc-sys now gone from EVERY windows-built crate (host, client-windows,
client-session, tray — all 'cargo tree -i aws-lc-sys' => no match). Code already
used rustls::crypto::ring::default_provider (library.rs); no behaviour change.

Validated: cargo tree (all 4 windows crates aws-lc-free) + .21 clippy -D warnings
(pf-client-core, pf-presenter, punktfunk-client-session) clean.
2026-07-17 22:26:04 +02:00
enricobuehler 17a262aace fix(host/deps): ring-only crypto — drop aws-lc-sys (fails to build on the Windows CI)
android / android (push) Has been cancelled
audit / bun-audit (push) Successful in 14s
apple / screenshots (push) Has been cancelled
apple / swift (push) Has been cancelled
arch / build-publish (push) Has been cancelled
audit / cargo-audit (push) Has been cancelled
deb / build-publish (push) Has been cancelled
ci / rust (push) Has been cancelled
ci / web (push) Has been cancelled
ci / docs-site (push) Has been cancelled
ci / bench (push) Has been cancelled
docker / build-push (., web/Dockerfile, punktfunk-web) (push) Successful in 21s
decky / build-publish (push) Successful in 32s
docker / build-push (ci, ci/fedora-rpm.Dockerfile, punktfunk-fedora-rpm) (push) Successful in 21s
docker / build-push (ci, ci/rust-ci.Dockerfile, punktfunk-rust-ci) (push) Successful in 19s
docker / build-push (docs-site, docs-site/Dockerfile, punktfunk-docs) (push) Successful in 20s
docker / deploy-docs (push) Has been cancelled
docker / build-push (--build-arg FEDORA_VERSION=44, ci, ci/fedora-rpm.Dockerfile, punktfunk-fedora44-rpm) (push) Has been cancelled
flatpak / build-publish (push) Has been cancelled
release / apple (push) Has been cancelled
windows / build (aarch64-pc-windows-msvc) (push) Has been cancelled
windows / build (x86_64-pc-windows-msvc) (push) Has been cancelled
windows-msix / package (arm64, C:\Users\Public\ffmpeg-arm64, --no-default-features, aarch64-pc-windows-msvc, C:\t-a64) (push) Has been cancelled
windows-msix / package (x64, C:\Users\Public\ffmpeg, , x86_64-pc-windows-msvc, C:\t) (push) Has been cancelled
windows-host / package (push) Has been cancelled
rpm / build-publish (43, bazzite, punktfunk-fedora-rpm) (push) Has been cancelled
rpm / build-publish (44, fedora-44, punktfunk-fedora44-rpm) (push) Has been cancelled
The Windows host clippy CI broke building aws-lc-sys 0.41.0 (a heavy C dep that
cl.exe fails to compile on the runner). The host was the ONLY thing pulling it:
its rcgen used features=["aws_lc_rs"], and bare `rustls`/`tokio-rustls` take
the default (aws-lc-rs) backend, and gamestream/{mod,tls}.rs installed the
aws_lc_rs crypto provider explicitly — a pre-existing deviation from the
project's documented ring-only design (punktfunk-core + the client are ring).

Align the host to ring: rcgen/rustls/tokio-rustls pinned to the `ring` feature
(keeping tls12 for Moonlight/GameStream TLS-1.2 clients) and the two GameStream
provider installs switched to `rustls::crypto::ring::default_provider()`. Both
providers offer the same standard TLS 1.2/1.3 suites, so it's transparent to the
TLS paths; aws-lc-sys/aws-lc-rs leave the tree entirely (`cargo tree -i
aws-lc-sys` => no match). Not caused by the preceding log fix (that touched no
crypto deps / the lock).

Validated: cargo tree (aws-lc-sys gone, windows target, amf-qsv,qsv features) +
.21 host clippy -D warnings (--features nvenc) clean. Owed: GameStream on-glass
re-check with a real Moonlight client (low risk — same TLS suites, client is
already ring).
2026-07-17 22:21:23 +02:00
enricobuehler 205e5c5a59 fix(apple): recover dropped macOS client features lost in the W7/W8 refactor
apple / swift (push) Successful in 1m20s
android / android (push) Has been cancelled
apple / screenshots (push) Has been cancelled
arch / build-publish (push) Has been cancelled
ci / web (push) Successful in 52s
ci / docs-site (push) Successful in 1m17s
ci / rust (push) Has been cancelled
ci / bench (push) Has been cancelled
deb / build-publish (push) Has been cancelled
decky / build-publish (push) Successful in 24s
docker / build-push (., web/Dockerfile, punktfunk-web) (push) Successful in 12s
docker / build-push (ci, ci/fedora-rpm.Dockerfile, punktfunk-fedora-rpm) (push) Successful in 17s
docker / build-push (ci, ci/rust-ci.Dockerfile, punktfunk-rust-ci) (push) Successful in 18s
docker / build-push (docs-site, docs-site/Dockerfile, punktfunk-docs) (push) Successful in 18s
docker / deploy-docs (push) Has been cancelled
docker / build-push (--build-arg FEDORA_VERSION=44, ci, ci/fedora-rpm.Dockerfile, punktfunk-fedora44-rpm) (push) Has been cancelled
release / apple (push) Has been cancelled
rpm / build-publish (44, fedora-44, punktfunk-fedora44-rpm) (push) Has been cancelled
rpm / build-publish (43, bazzite, punktfunk-fedora-rpm) (push) Has been cancelled
The W7/W8 client refactor (extracting FullscreenController/ApprovalRequest out of
ContentView, splitting Settings) landed the DefaultsKeys + scaffolding but dropped
the feature WIRING, which was uncommitted WIP shelved in a stash during the
16:28 clipboard/v4 reconcile. Recovered from that stash (+ its untracked-files
parent for the whole new ModifierLayout.swift) and re-ported onto the refactored
tree:

- invertScroll: applied in InputCapture.sendScroll (the one scroll sink) + Settings toggle
- modifierLayout (Cmd/Option switch): restored ModifierLayout.swift enum + KeyMaps
  .applyModifierLayout + InputCapture.emitKey wire-boundary + Settings picker
- fullscreen shortcut (Ctrl+Cmd+F): InputCapture ⌃⌘F interception + onToggleFullscreen
  + StreamView wiring + Stream-menu item (the .punktfunkToggleFullscreen sink +
  FullscreenController observer already survived on main)
- render scale: Settings picker + MatchWindowFollower renderScale/maxDimension
  application + StreamView/StreamViewIOS plumbing (RenderScale.swift already on main)

StreamCommands re-ported by hand (the stash hunk deleted the since-landed clipboard
item — kept it, added the fullscreen item alongside). Recovered ModifierLayout +
RenderScale tests. swift build green; 15 tests pass.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
2026-07-17 22:20:46 +02:00
enricobuehler 3584b47fb8 fix(host/log): drop the wasapi crate's once-a-second DEBUG device-poll from the logs
apple / swift (push) Successful in 1m18s
apple / screenshots (push) Successful in 5m16s
ci / web (push) Successful in 58s
ci / docs-site (push) Successful in 1m15s
arch / build-publish (push) Has been cancelled
ci / rust (push) Has been cancelled
ci / bench (push) Has been cancelled
deb / build-publish (push) Has been cancelled
decky / build-publish (push) Has been cancelled
docker / build-push (ci, ci/rust-ci.Dockerfile, punktfunk-rust-ci) (push) Has been cancelled
docker / build-push (docs-site, docs-site/Dockerfile, punktfunk-docs) (push) Has been cancelled
docker / deploy-docs (push) Has been cancelled
docker / build-push (ci, ci/fedora-rpm.Dockerfile, punktfunk-fedora-rpm) (push) Has been cancelled
docker / build-push (--build-arg FEDORA_VERSION=44, ci, ci/fedora-rpm.Dockerfile, punktfunk-fedora44-rpm) (push) Has been cancelled
docker / build-push (., web/Dockerfile, punktfunk-web) (push) Has been cancelled
rpm / build-publish (43, bazzite, punktfunk-fedora-rpm) (push) Has been cancelled
rpm / build-publish (44, fedora-44, punktfunk-fedora44-rpm) (push) Has been cancelled
windows-host / package (push) Has been cancelled
android / android (push) Successful in 16m4s
The `wasapi` audio crate polls the default output device ~1×/s and
`log::debug!`s it (`wasapi::api default device Ok(...)`), flooding host.log
even at RUST_LOG=info: those `log`-crate records reach tracing through the
tracing-log bridge, and at *filter* time they carry the bridge's "log" shim
target, so neither a bare-info EnvFilter nor a `wasapi=warn` directive on the
file layer matches them (the ring already drops them post-hoc via
normalized_metadata + NOISY_DEBUG_TARGETS — the fmt layer filters pre-event and
can't).

Fix at the source: a shared `log_capture::install_global` replaces
SubscriberInitExt::init in both init paths (service file log + interactive
stderr) and inits the tracing-log bridge with `ignore_crate("wasapi")`, so the
poll records are dropped before they ever become tracing events — both sinks go
quiet, while every other log-crate dependency still reaches the console ring
(bridge max-level stays DEBUG). `wasapi` also added to NOISY_DEBUG_TARGETS as
belt-and-suspenders, and `log` moved dev-dep -> dep for LevelFilter.

Validated: .21 clippy (host --features nvenc) clean.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
2026-07-17 22:06:14 +02:00
enricobuehler de651c0f1a chore(gitignore): ignore Gradle build output in the vendored pyrowave Granite Android platform
apple / swift (push) Successful in 1m24s
apple / screenshots (push) Successful in 6m21s
ci / web (push) Successful in 59s
ci / docs-site (push) Successful in 1m5s
ci / bench (push) Successful in 5m14s
release / apple (push) Successful in 11m7s
windows-host / package (push) Failing after 12m38s
android-screenshots / screenshots (push) Successful in 3m23s
decky / build-publish (push) Successful in 23s
windows-msix / package (arm64, C:\Users\Public\ffmpeg-arm64, --no-default-features, aarch64-pc-windows-msvc, C:\t-a64) (push) Successful in 7m22s
ci / rust (push) Successful in 29m2s
windows-msix / package (x64, C:\Users\Public\ffmpeg, , x86_64-pc-windows-msvc, C:\t) (push) Successful in 7m41s
linux-client-screenshots / screenshots (push) Successful in 7m50s
android / android (push) Successful in 13m43s
web-screenshots / screenshots (push) Successful in 3m16s
docker / build-push (--build-arg FEDORA_VERSION=44, ci, ci/fedora-rpm.Dockerfile, punktfunk-fedora44-rpm) (push) Successful in 20s
docker / build-push (., web/Dockerfile, punktfunk-web) (push) Successful in 11s
docker / build-push (ci, ci/fedora-rpm.Dockerfile, punktfunk-fedora-rpm) (push) Successful in 9s
docker / build-push (ci, ci/rust-ci.Dockerfile, punktfunk-rust-ci) (push) Successful in 9s
docker / build-push (docs-site, docs-site/Dockerfile, punktfunk-docs) (push) Successful in 16s
docker / deploy-docs (push) Successful in 30s
arch / build-publish (push) Successful in 14m35s
deb / build-publish (push) Successful in 15m31s
rpm / build-publish (44, fedora-44, punktfunk-fedora44-rpm) (push) Successful in 16m18s
flatpak / build-publish (push) Has been cancelled
rpm / build-publish (43, bazzite, punktfunk-fedora-rpm) (push) Successful in 19m56s
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
2026-07-17 21:36:49 +02:00
enricobuehler e7a8cf2bba feat(packaging): nix web console + plugin/script runner everywhere; fix tray zbus/tokio crash
ci / web (push) Successful in 58s
apple / swift (push) Successful in 1m24s
ci / docs-site (push) Successful in 59s
docker / build-push (--build-arg FEDORA_VERSION=44, ci, ci/fedora-rpm.Dockerfile, punktfunk-fedora44-rpm) (push) Successful in 17s
decky / build-publish (push) Successful in 25s
windows-host / package (push) Successful in 19m46s
docker / build-push (., web/Dockerfile, punktfunk-web) (push) Successful in 17s
docker / build-push (ci, ci/fedora-rpm.Dockerfile, punktfunk-fedora-rpm) (push) Successful in 17s
docker / build-push (ci, ci/rust-ci.Dockerfile, punktfunk-rust-ci) (push) Successful in 18s
docker / build-push (docs-site, docs-site/Dockerfile, punktfunk-docs) (push) Successful in 17s
android / android (push) Successful in 16m12s
arch / build-publish (push) Successful in 12m14s
ci / bench (push) Successful in 5m27s
apple / screenshots (push) Successful in 6m32s
ci / rust (push) Successful in 19m26s
deb / build-publish (push) Successful in 12m58s
docker / deploy-docs (push) Successful in 24s
rpm / build-publish (43, bazzite, punktfunk-fedora-rpm) (push) Successful in 24m51s
rpm / build-publish (44, fedora-44, punktfunk-fedora44-rpm) (push) Successful in 24m51s
Ship the management web console and the plugin/script runner across the packaging
matrix, and fix a Linux tray startup crash.

- nix: add punktfunk-web (Nitro SSR on bun) and punktfunk-scripting (Effect runner),
  with services.punktfunk.{web,scripting} modules. Both bun node_modules come from
  fixed-output derivations pinned to real deps hashes; patchShebangs the web
  node_modules so orval/paraglide/vite run in the sandbox. Validated on x86_64-linux
  (nix build .#punktfunk-{web,scripting,tray,host} all green).

- punktfunk-scripting now ships for deb/rpm/arch/nix/windows, mirroring the web-console
  mechanics (vendored bun on deb/rpm/arch, pkgs.bun on nix; one self-contained
  `bun build --target=bun` bundle). Opt-in: host Recommends it, but the systemd --user
  unit ships un-started (Windows registers its scheduled task disabled).

- fix(tray): build punktfunk-tray in its OWN cargo invocation on nix/rpm/arch. Cargo
  unifies features across one `cargo build`, so co-building the tray with the host pulls
  the host's ashpd->zbus/tokio onto the tray's shared zbus and it panics at startup
  ("there is no reactor running, must be called from the context of a Tokio 1.x
  runtime"). Isolated, the tray's zbus stays on async-io. The .deb already did this;
  runtime-verified on nix.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
2026-07-17 21:16:34 +02:00
enricobuehler fb92dcd1c2 fix(release): regen managed dev profiles at archive (App Groups + widgets); widgets iOS target 27→17
ci / web (push) Successful in 53s
ci / docs-site (push) Successful in 58s
docker / build-push (--build-arg FEDORA_VERSION=44, ci, ci/fedora-rpm.Dockerfile, punktfunk-fedora44-rpm) (push) Successful in 10s
apple / swift (push) Successful in 1m15s
decky / build-publish (push) Successful in 22s
docker / build-push (., web/Dockerfile, punktfunk-web) (push) Successful in 11s
docker / build-push (ci, ci/fedora-rpm.Dockerfile, punktfunk-fedora-rpm) (push) Successful in 14s
docker / build-push (ci, ci/rust-ci.Dockerfile, punktfunk-rust-ci) (push) Successful in 15s
docker / build-push (docs-site, docs-site/Dockerfile, punktfunk-docs) (push) Successful in 12s
ci / bench (push) Successful in 6m36s
docker / deploy-docs (push) Successful in 26s
release / apple (push) Successful in 9m13s
arch / build-publish (push) Successful in 12m10s
android / android (push) Successful in 14m2s
apple / screenshots (push) Successful in 6m27s
deb / build-publish (push) Successful in 17m9s
ci / rust (push) Successful in 20m44s
rpm / build-publish (43, bazzite, punktfunk-fedora-rpm) (push) Successful in 20m1s
rpm / build-publish (44, fedora-44, punktfunk-fedora44-rpm) (push) Successful in 20m3s
The iOS/tvOS/macOS-AppStore archive steps sign development-automatic OFFLINE
(no -allowProvisioningUpdates), so they depend on Xcode's cached managed dev
profiles. Adding the App Groups capability (group.io.unom.punktfunk, shared with
the Widget/Live-Activity extension) invalidated the cached "iOS Team Provisioning
Profile: io.unom.punktfunk" (it carries no application-groups entitlement) and
there is no managed dev profile for the embedded io.unom.punktfunk.widgets at all
— so the archive fails with "doesn't include the App Groups capability" and
"No profiles for io.unom.punktfunk.widgets".

Add -allowProvisioningUpdates + the ASC auth key (asc.p8 already written to
RUNNER_TEMP) to all three archive commands so Xcode syncs the App ID capabilities
and regenerates/creates the managed *development* profiles. This is development
signing against the Apple Development cert already in the keychain — no cert
creation, so the App-Manager ASC key suffices; the manual distribution export
steps are untouched.

Also drop the widgets IPHONEOS_DEPLOYMENT_TARGET from 27.0 (an iOS-27-beta-era
value, out of range on the runner's iPhoneOS26.5 SDK) to 17.0, matching the app.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
2026-07-17 21:14:23 +02:00
enricobuehler 8e75f3d8bf perf(latency): adaptive IDD pipeline depth — depth-1 default, escalate on contention
ci / web (push) Successful in 55s
ci / docs-site (push) Successful in 53s
docker / build-push (--build-arg FEDORA_VERSION=44, ci, ci/fedora-rpm.Dockerfile, punktfunk-fedora44-rpm) (push) Successful in 11s
apple / swift (push) Successful in 1m17s
decky / build-publish (push) Successful in 25s
docker / build-push (., web/Dockerfile, punktfunk-web) (push) Successful in 14s
docker / build-push (ci, ci/fedora-rpm.Dockerfile, punktfunk-fedora-rpm) (push) Successful in 16s
docker / build-push (ci, ci/rust-ci.Dockerfile, punktfunk-rust-ci) (push) Successful in 14s
docker / build-push (docs-site, docs-site/Dockerfile, punktfunk-docs) (push) Successful in 13s
ci / bench (push) Successful in 6m27s
docker / deploy-docs (push) Successful in 25s
apple / screenshots (push) Successful in 6m22s
arch / build-publish (push) Successful in 11m59s
android / android (push) Successful in 14m53s
deb / build-publish (push) Successful in 16m23s
windows-host / package (push) Successful in 19m49s
rpm / build-publish (44, fedora-44, punktfunk-fedora44-rpm) (push) Successful in 18m11s
rpm / build-publish (43, bazzite, punktfunk-fedora-rpm) (push) Successful in 18m33s
ci / rust (push) Successful in 21m0s
From the 2026-07-17 on-glass finding (.173 RTX 4090): the IDD-push capturer's
depth-2 pipeline measured ~13ms of glass-to-glass latency over depth-1 at
60fps (17ms → 4ms) — the AU is ready in µs but depth-2 holds it a whole frame
interval unpolled while N+1 is submitted. Depth-2 only earns its keep under
GPU contention (it overlaps the convert of N+1 with the encode of N, avoiding
the depth-1 ~50fps collapse). So the native encode loop now runs depth-1 by
default and escalates to the capturer's max ONLY when it can't hold cadence at
depth-1 (a leaky-bucket over 'the frame's work overran its pacing deadline',
with a startup warmup skip), then holds there for the session (no oscillation;
de-escalation is a v2 item). PUNKTFUNK_IDD_ADAPTIVE=0 pins the capturer's full
depth (pre-adaptive behaviour); no effect where the capturer's max is already
1 (every non-IDD backend). GameStream plane untouched.

Pairs with the shipped REALTIME auto-gate (1bc156aa): uncontended sessions get
the ~13ms latency cut, genuinely GPU-bound ones keep the depth-2 overlap AND
the REALTIME priority. Validated: .21 clippy -D warnings (host --features nvenc).

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
2026-07-17 20:48:55 +02:00
enricobuehler 5e1e64e50b perf(latency): T2.5b — NV12 compute CSC on the LINEAR/gamescope zero-copy path
apple / swift (push) Successful in 1m21s
apple / screenshots (push) Successful in 6m23s
ci / web (push) Successful in 54s
arch / build-publish (push) Successful in 10m52s
ci / docs-site (push) Successful in 1m7s
decky / build-publish (push) Successful in 35s
android / android (push) Successful in 13m2s
docker / build-push (., web/Dockerfile, punktfunk-web) (push) Successful in 16s
docker / build-push (ci, ci/fedora-rpm.Dockerfile, punktfunk-fedora-rpm) (push) Successful in 16s
docker / build-push (docs-site, docs-site/Dockerfile, punktfunk-docs) (push) Successful in 11s
ci / bench (push) Successful in 6m8s
docker / build-push (ci, ci/rust-ci.Dockerfile, punktfunk-rust-ci) (push) Successful in 7m56s
docker / build-push (--build-arg FEDORA_VERSION=44, ci, ci/fedora-rpm.Dockerfile, punktfunk-fedora44-rpm) (push) Successful in 9m44s
docker / deploy-docs (push) Successful in 24s
deb / build-publish (push) Successful in 11m52s
ci / rust (push) Successful in 27m4s
rpm / build-publish (43, bazzite, punktfunk-fedora-rpm) (push) Successful in 20m26s
rpm / build-publish (44, fedora-44, punktfunk-fedora44-rpm) (push) Successful in 20m20s
design/latency-reduction-2026-07.md T2.5's Linux half: the LINEAR dmabuf path
(gamescope's only offer) fed NVENC RGB, paying its internal RGB->YUV CSC on
the SM the game is saturating — the exact contention §5.A removed everywhere
else. The Vulkan bridge now carries a buffer-to-buffer RGB->NV12 compute
shader (rgb2nv12_buf.comp, BT.709 limited, coefficient-identical to
pf-encode's rgb2yuv.comp; whole-word writes so no 8-bit-storage feature is
needed): import dmabuf -> dispatch CSC into the exportable buffer -> CUDA
de-strides both planes into a pooled two-plane NV12 buffer. PUNKTFUNK_NV12
(default-on) now covers LINEAR; a CSC failure latches RGB for the stream
(mid-frame fallback, no dropped frame); 4:4:4 LINEAR sessions stay RGB (never
silently subsample). New ImportKind::LinearNv12 rides the existing worker IPC
(appended last per the wire-tag rule); cursor stays downstream (blend_nv12).

Validated: .21 clippy -D warnings (pf-zerocopy/pf-capture/host+nvenc) + 17
zero-copy tests. Owed: on-glass gamescope session (visual + dmon sm% check).

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
2026-07-17 20:14:20 +02:00
enricobuehler fbe1e62ef2 Merge commit '0c9d3ee3' into HEAD
android / android (push) Has been cancelled
apple / screenshots (push) Has been cancelled
apple / swift (push) Has been cancelled
arch / build-publish (push) Has been cancelled
ci / rust (push) Has been cancelled
ci / web (push) Has been cancelled
ci / docs-site (push) Has been cancelled
ci / bench (push) Has been cancelled
deb / build-publish (push) Has been cancelled
decky / build-publish (push) Has been cancelled
docker / build-push (ci, ci/fedora-rpm.Dockerfile, punktfunk-fedora-rpm) (push) Has been cancelled
docker / build-push (ci, ci/rust-ci.Dockerfile, punktfunk-rust-ci) (push) Has been cancelled
docker / build-push (docs-site, docs-site/Dockerfile, punktfunk-docs) (push) Has been cancelled
docker / deploy-docs (push) Has been cancelled
docker / build-push (., web/Dockerfile, punktfunk-web) (push) Has been cancelled
docker / build-push (--build-arg FEDORA_VERSION=44, ci, ci/fedora-rpm.Dockerfile, punktfunk-fedora44-rpm) (push) Has been cancelled
rpm / build-publish (43, bazzite, punktfunk-fedora-rpm) (push) Has been cancelled
rpm / build-publish (44, fedora-44, punktfunk-fedora44-rpm) (push) Has been cancelled
2026-07-17 20:11:08 +02:00
enricobuehler 0c9d3ee33f fix(windows-clippy): pf-frame SAFETY comments + checked_div, pf-clipboard SAFETY placement + Gdi feature
Surfaced by giving windows-host CI its missing pf-encode/pf-frame path triggers:
the T2.3 auto-gate (dxgi.rs) and the clipboard host (windows.rs) landed with
Windows clippy owed — three undocumented unsafe blocks, one manual checked
division, one comment orphaned off its statement, and a latent E0432 in
standalone pf-clipboard builds (WNDCLASSW needs Win32_Graphics_Gdi; the host
graph only compiled via feature unification).

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
2026-07-17 20:08:44 +02:00
enricobuehler df04c2c54f Merge commit '9da38b8d' into HEAD
apple / swift (push) Successful in 1m21s
ci / web (push) Successful in 50s
ci / docs-site (push) Successful in 52s
docker / build-push (--build-arg FEDORA_VERSION=44, ci, ci/fedora-rpm.Dockerfile, punktfunk-fedora44-rpm) (push) Successful in 11s
decky / build-publish (push) Successful in 20s
docker / build-push (., web/Dockerfile, punktfunk-web) (push) Successful in 10s
docker / build-push (ci, ci/fedora-rpm.Dockerfile, punktfunk-fedora-rpm) (push) Successful in 9s
docker / build-push (ci, ci/rust-ci.Dockerfile, punktfunk-rust-ci) (push) Successful in 14s
docker / build-push (docs-site, docs-site/Dockerfile, punktfunk-docs) (push) Successful in 11s
ci / bench (push) Successful in 5m14s
windows-host / package (push) Failing after 8m11s
apple / screenshots (push) Successful in 5m22s
android / android (push) Has been cancelled
docker / deploy-docs (push) Successful in 30s
arch / build-publish (push) Successful in 16m33s
ci / rust (push) Successful in 19m17s
deb / build-publish (push) Successful in 18m38s
rpm / build-publish (44, fedora-44, punktfunk-fedora44-rpm) (push) Successful in 21m5s
rpm / build-publish (43, bazzite, punktfunk-fedora-rpm) (push) Successful in 21m9s
2026-07-17 20:01:13 +02:00
enricobuehler 9da38b8d9c ci(windows-host): build + clippy with the native QSV feature; encode-crate path triggers; CMake 4 policy floor
--features nvenc,amf-qsv,qsv on build and clippy (static VPL dispatcher, no new
DLLs — ffmpeg *_qsv stays as the fallback until Phase 4). Adds pf-encode/** +
libvpl-sys/** to the paths trigger (drift from the W6 split: encoder changes
only reached this workflow via Cargo.lock luck). Exports
CMAKE_POLICY_VERSION_MINIMUM=3.5: audiopus_sys' vendored opus fails a FRESH
CMake-4 configure (observed on this runner on a clean target dir), so green
runs were one cache purge from breaking.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
2026-07-17 20:01:05 +02:00
enricobuehler 1bc156aab0 perf(latency): T2.2 Linux NVENC two-thread retrieve + T2.3 REALTIME auto-gate
android / android (push) Has been cancelled
arch / build-publish (push) Has been cancelled
apple / screenshots (push) Has been cancelled
apple / swift (push) Has been cancelled
ci / rust (push) Has been cancelled
ci / web (push) Has been cancelled
ci / docs-site (push) Has been cancelled
ci / bench (push) Has been cancelled
deb / build-publish (push) Has been cancelled
decky / build-publish (push) Has been cancelled
docker / build-push (--build-arg FEDORA_VERSION=44, ci, ci/fedora-rpm.Dockerfile, punktfunk-fedora44-rpm) (push) Has been cancelled
docker / build-push (., web/Dockerfile, punktfunk-web) (push) Has been cancelled
docker / build-push (ci, ci/fedora-rpm.Dockerfile, punktfunk-fedora-rpm) (push) Has been cancelled
docker / build-push (ci, ci/rust-ci.Dockerfile, punktfunk-rust-ci) (push) Has been cancelled
docker / build-push (docs-site, docs-site/Dockerfile, punktfunk-docs) (push) Has been cancelled
docker / deploy-docs (push) Has been cancelled
rpm / build-publish (43, bazzite, punktfunk-fedora-rpm) (push) Has been cancelled
rpm / build-publish (44, fedora-44, punktfunk-fedora44-rpm) (push) Has been cancelled
design/latency-reduction-2026-07.md tier 2, the two code-side halves:

- T2.2: the Linux direct-NVENC backend gains the two-thread retrieve
  (PUNKTFUNK_NVENC_ASYNC, the same opt-in knob as Windows): the session stays
  sync-mode (async events are Windows-only) but the blocking lock_bitstream
  moves to a dedicated pf-nvenc-out thread — the NVENC guide's sanctioned
  submit-thread/output-thread split. poll() drains completions non-blocking,
  submit() backpressures at PUNKTFUNK_NVENC_ASYNC_DEPTH (default 4) in-flight;
  map/unmap and every other session call stay on the encode thread; teardown
  joins the thread before destroying the session. Under a GPU-saturating game
  completed frames queue instead of serializing capture on the encode wait.
- T2.3: PUNKTFUNK_GPU_PRIORITY_CLASS gains 'auto' AND IT IS THE NEW DEFAULT
  (gpu-contention §5.C): HIGH immediately, then REALTIME where the documented
  NVIDIA+HAGS+near-full-VRAM NVENC hang cannot bite — HAGS probed once via
  D3DKMT WDDM_2_7_CAPS (off => REALTIME outright); HAGS on => a pf-gpu-prio
  monitor flips REALTIME<->HIGH on LOCAL-segment VRAM headroom (downgrade
  >92% of budget, restore <=85% for 3x2s polls). 'high' restores the old
  static default; 'realtime' pins it (operator owns the hazard).

Validated: .21 clippy -D warnings (punktfunk-host --features nvenc) against
the QSV-merged main; .133 Windows cargo check of pf-frame + punktfunk-host.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
2026-07-17 19:57:38 +02:00
enricobuehler 399ea9a0f1 merge(qsv): on-glass Arc validation round — MT-protect-before-SetHandle + live test matrix
android / android (push) Has been cancelled
apple / swift (push) Has been cancelled
apple / screenshots (push) Has been cancelled
arch / build-publish (push) Has been cancelled
ci / web (push) Has been cancelled
ci / bench (push) Has been cancelled
ci / rust (push) Has been cancelled
ci / docs-site (push) Has been cancelled
deb / build-publish (push) Has been cancelled
decky / build-publish (push) Has been cancelled
docker / build-push (ci, ci/fedora-rpm.Dockerfile, punktfunk-fedora-rpm) (push) Has been cancelled
docker / build-push (--build-arg FEDORA_VERSION=44, ci, ci/fedora-rpm.Dockerfile, punktfunk-fedora44-rpm) (push) Has been cancelled
docker / build-push (ci, ci/rust-ci.Dockerfile, punktfunk-rust-ci) (push) Has been cancelled
docker / build-push (docs-site, docs-site/Dockerfile, punktfunk-docs) (push) Has been cancelled
docker / deploy-docs (push) Has been cancelled
docker / build-push (., web/Dockerfile, punktfunk-web) (push) Has been cancelled
rpm / build-publish (44, fedora-44, punktfunk-fedora44-rpm) (push) Has been cancelled
rpm / build-publish (43, bazzite, punktfunk-fedora-rpm) (push) Has been cancelled
8/8 live tests green on the Arc Pro laptop (VPL 2.16): H264/HEVC SDR, HEVC
Main10+HDR metadata, AV1 10-bit, LTR-RFI clean recovery (no IDR; driver honors
mfxExtRefListCtrl on H264/HEVC/AV1), no-IDR bitrate retarget. Zero-copy
GetNativeHandle path confirmed (single textures, no array-slice hazard).

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
2026-07-17 19:56:51 +02:00
enricobuehler df70bf00f9 test(qsv): live on-glass matrix — HEVC, Main10+HDR, AV1 10-bit, LTR-RFI, no-IDR retarget
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
2026-07-17 19:55:43 +02:00
enricobuehler 9e6ab9b94d fix(qsv): enable D3D11 multithread protection before SetHandle (on-glass -16 on Arc)
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
2026-07-17 19:52:44 +02:00
enricobuehler 6f409caac7 merge(qsv): land the native QSV backend onto main
apple / swift (push) Successful in 1m22s
android / android (push) Has been cancelled
apple / screenshots (push) Has been cancelled
arch / build-publish (push) Has been cancelled
audit / bun-audit (push) Has been cancelled
audit / cargo-audit (push) Has been cancelled
ci / docs-site (push) Has been cancelled
ci / bench (push) Has been cancelled
ci / web (push) Has been cancelled
ci / rust (push) Has been cancelled
deb / build-publish (push) Has been cancelled
decky / build-publish (push) Has been cancelled
docker / build-push (ci, ci/rust-ci.Dockerfile, punktfunk-rust-ci) (push) Has been cancelled
docker / build-push (docs-site, docs-site/Dockerfile, punktfunk-docs) (push) Has been cancelled
docker / deploy-docs (push) Has been cancelled
docker / build-push (ci, ci/fedora-rpm.Dockerfile, punktfunk-fedora-rpm) (push) Has been cancelled
docker / build-push (--build-arg FEDORA_VERSION=44, ci, ci/fedora-rpm.Dockerfile, punktfunk-fedora44-rpm) (push) Has been cancelled
docker / build-push (., web/Dockerfile, punktfunk-web) (push) Has been cancelled
flatpak / build-publish (push) Has been cancelled
release / apple (push) Has been cancelled
rpm / build-publish (43, bazzite, punktfunk-fedora-rpm) (push) Has been cancelled
rpm / build-publish (44, fedora-44, punktfunk-fedora44-rpm) (push) Has been cancelled
windows-host / package (push) Has been cancelled
windows-msix / package (arm64, C:\Users\Public\ffmpeg-arm64, --no-default-features, aarch64-pc-windows-msvc, C:\t-a64) (push) Has been cancelled
windows-msix / package (x64, C:\Users\Public\ffmpeg, , x86_64-pc-windows-msvc, C:\t) (push) Has been cancelled
windows / build (aarch64-pc-windows-msvc) (push) Has been cancelled
windows / build (x86_64-pc-windows-msvc) (push) Has been cancelled
Reconciles the qsv line (libvpl-sys + qsv.rs + dispatch/probe wiring, verified
on the .133 Windows box: check + clippy + dispatcher link test + unit tests)
with the concurrent clipboard JPEG/GIF pass-through commits; Cargo.lock union
re-adds the libvpl-sys entry.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
2026-07-17 19:49:12 +02:00
enricobuehler 5977b4b9f1 chore(libvpl-sys): silence clippy across the generated bindings
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
2026-07-17 19:48:17 +02:00
enricobuehler 0a617e7779 fix(qsv): clippy — per-block SAFETY comments, div_ceil, clamp, vec_box allow
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
2026-07-17 19:44:16 +02:00
enricobuehler 75d1322b8a fix(qsv): end the inner borrow before the retarget param rebuild
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
2026-07-17 19:39:08 +02:00
enricobuehler 55e7f3fca9 feat(encode): native QSV backend — libvpl-sys + qsv.rs (Phases 0-3 of design/native-qsv-encoder.md)
Vendored MIT VPL dispatcher (static, trimmed tree, pin 674d015b/v2.17.0) built
via cmake+bindgen behind new feature 'qsv' (pf-encode + punktfunk-host forward).
qsv.rs: dispatcher session on the capture adapter (LUID-matched), SetHandle
D3D11, AsyncDepth=1/GopRefDist=1/VDEnc/CBR + HRD-off low-latency config,
GetSurfaceForEncode + GPU CopySubresourceRegion input (zero-copy, no readback
path), bounded sync-point poll, in-place reset with teardown escalation, no-IDR
bitrate retarget (Reset + StartNewSequence=OFF), 10-bit P010 HEVC-Main10/AV1,
HDR mastering/CLL SEI-OBU at IDR + BT.2020/PQ VSI, LTR-RFI via mfxExtRefListCtrl
(AMF slot policy port, Query-gated per codec, wire-index FrameOrder pinning).
Dispatch: native-first with ffmpeg fallback + PUNKTFUNK_QSV_FFMPEG hatch;
probes (can_encode_10bit / windows_codec_support / windows_backend_is_probed)
now answer natively for QSV.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
2026-07-17 19:34:18 +02:00
enricobuehler 055b95a181 fix(pf-clipboard): winfmt layout + wayland map test for the JPEG/GIF pass-through
ci / web (push) Successful in 49s
ci / docs-site (push) Successful in 58s
apple / swift (push) Successful in 1m23s
decky / build-publish (push) Successful in 22s
docker / build-push (--build-arg FEDORA_VERSION=44, ci, ci/fedora-rpm.Dockerfile, punktfunk-fedora44-rpm) (push) Successful in 11s
docker / build-push (., web/Dockerfile, punktfunk-web) (push) Successful in 10s
docker / build-push (ci, ci/fedora-rpm.Dockerfile, punktfunk-fedora-rpm) (push) Successful in 10s
docker / build-push (ci, ci/rust-ci.Dockerfile, punktfunk-rust-ci) (push) Successful in 11s
docker / build-push (docs-site, docs-site/Dockerfile, punktfunk-docs) (push) Successful in 15s
ci / bench (push) Successful in 6m25s
arch / build-publish (push) Successful in 11m18s
android / android (push) Successful in 13m57s
ci / rust (push) Has been cancelled
deb / build-publish (push) Has been cancelled
rpm / build-publish (43, bazzite, punktfunk-fedora-rpm) (push) Has been cancelled
rpm / build-publish (44, fedora-44, punktfunk-fedora44-rpm) (push) Has been cancelled
apple / screenshots (push) Has been cancelled
docker / deploy-docs (push) Has been cancelled
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
2026-07-17 19:33:51 +02:00
enricobuehler f4b52d0bf5 feat(clipboard): pass original JPEG/GIF through verbatim beside the PNG floor
android / android (push) Has been cancelled
audit / bun-audit (push) Successful in 14s
audit / cargo-audit (push) Successful in 2m29s
ci / web (push) Successful in 50s
ci / docs-site (push) Successful in 54s
decky / build-publish (push) Successful in 30s
docker / build-push (--build-arg FEDORA_VERSION=44, ci, ci/fedora-rpm.Dockerfile, punktfunk-fedora44-rpm) (push) Successful in 18s
apple / swift (push) Successful in 1m23s
docker / build-push (., web/Dockerfile, punktfunk-web) (push) Successful in 12s
docker / build-push (ci, ci/fedora-rpm.Dockerfile, punktfunk-fedora-rpm) (push) Successful in 13s
docker / build-push (ci, ci/rust-ci.Dockerfile, punktfunk-rust-ci) (push) Successful in 12s
docker / build-push (docs-site, docs-site/Dockerfile, punktfunk-docs) (push) Successful in 11s
ci / bench (push) Successful in 5m58s
ci / rust (push) Failing after 6m54s
release / apple (push) Successful in 5m52s
arch / build-publish (push) Failing after 11m7s
flatpak / build-publish (push) Failing after 8m7s
deb / build-publish (push) Successful in 13m52s
rpm / build-publish (43, bazzite, punktfunk-fedora-rpm) (push) Successful in 14m31s
docker / deploy-docs (push) Successful in 24s
apple / screenshots (push) Successful in 6m28s
rpm / build-publish (44, fedora-44, punktfunk-fedora44-rpm) (push) Successful in 18m5s
windows-host / package (push) Has been cancelled
windows-msix / package (arm64, C:\Users\Public\ffmpeg-arm64, --no-default-features, aarch64-pc-windows-msvc, C:\t-a64) (push) Has been cancelled
windows-msix / package (x64, C:\Users\Public\ffmpeg, , x86_64-pc-windows-msvc, C:\t) (push) Has been cancelled
windows / build (aarch64-pc-windows-msvc) (push) Has been cancelled
windows / build (x86_64-pc-windows-msvc) (push) Has been cancelled
Transcoding every image to PNG was the Phase-1 floor, but it bloats lossy
originals (a copied JPEG re-encoded lossless is 5-10x the bytes for zero
quality gain — feeding the render-timeout on constrained links) and flattens
GIFs to one frame. The wire already carries kind LISTS, so offer the original
format beside the portable fallback and let the destination pick:

- New wire kinds image/jpeg + image/gif (§3.5 extension; strings only, no
  protocol change). image/png stays the universal floor every peer accepts.
- macOS: public.jpeg / com.compuserve.gif rows pass through verbatim both
  directions; the PNG floor is announced whenever any image is present.
- Windows: registered JFIF/GIF formats read + promised verbatim; every image
  offer still promises CF_DIB, whose delayed render now fetches the richest
  offered kind (png > jpeg > gif) through the generalized image_to_dib.
- Linux maps gain the matching rows (native MIME pass-through).

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
2026-07-17 19:32:04 +02:00
enricobuehler bbe4380b41 perf(latency): T1.1 frame-driven encode trigger + T1.4 time-based flush thresholds
ci / web (push) Successful in 1m11s
ci / docs-site (push) Successful in 1m14s
apple / swift (push) Successful in 1m24s
docker / build-push (--build-arg FEDORA_VERSION=44, ci, ci/fedora-rpm.Dockerfile, punktfunk-fedora44-rpm) (push) Successful in 12s
decky / build-publish (push) Successful in 22s
docker / build-push (., web/Dockerfile, punktfunk-web) (push) Successful in 10s
docker / build-push (ci, ci/fedora-rpm.Dockerfile, punktfunk-fedora-rpm) (push) Successful in 9s
docker / build-push (ci, ci/rust-ci.Dockerfile, punktfunk-rust-ci) (push) Successful in 25s
docker / build-push (docs-site, docs-site/Dockerfile, punktfunk-docs) (push) Successful in 25s
docker / deploy-docs (push) Has been cancelled
flatpak / build-publish (push) Has been cancelled
release / apple (push) Has been cancelled
rpm / build-publish (43, bazzite, punktfunk-fedora-rpm) (push) Has been cancelled
rpm / build-publish (44, fedora-44, punktfunk-fedora44-rpm) (push) Has been cancelled
windows-host / package (push) Has been cancelled
windows-msix / package (arm64, C:\Users\Public\ffmpeg-arm64, --no-default-features, aarch64-pc-windows-msvc, C:\t-a64) (push) Has been cancelled
windows-msix / package (x64, C:\Users\Public\ffmpeg, , x86_64-pc-windows-msvc, C:\t) (push) Has been cancelled
windows / build (aarch64-pc-windows-msvc) (push) Has been cancelled
windows / build (x86_64-pc-windows-msvc) (push) Has been cancelled
deb / build-publish (push) Failing after 4m39s
arch / build-publish (push) Failing after 4m46s
ci / rust (push) Failing after 4m53s
ci / bench (push) Successful in 6m8s
android / android (push) Successful in 13m40s
apple / screenshots (push) Successful in 6m26s
design/latency-reduction-2026-07.md tier 1, remaining halves:

- T1.1: the native encode loop wakes on the capture's ACTUAL arrival instead of
  sampling at a free-running tick — deletes the sample-and-hold (~half a frame
  interval on average, a full one worst-case: ~8ms avg @60fps). New
  Capturer::supports_arrival_wait/wait_arrival pair (IDD-push waits its
  frame-ready event against the shared-header token; the PipeWire portal blocks
  its channel with a pending stash); backends without an arrival signal — and
  PUNKTFUNK_FRAME_DRIVEN=0 — keep the legacy tick bit-identically. A
  0.9x-interval rate floor caps encode at ~1.11x target when the compositor
  outruns the session; a +0.5x-interval keepalive keeps static desktops
  re-encoding at 1.5x-interval cadence. Pacing deadlines re-anchor to the
  actual submit so they can't drift against the arrival clock. GameStream
  plane untouched.
- T1.4: the jump-to-live detectors run on WALL-CLOCK now (STANDING_TIME /
  FLUSH_AFTER = 250ms) instead of 30-frame counts whose meaning scaled with
  fps (500ms @60 but 125ms @240 — and stretching further under T1.1's slower
  static-scene repeats). The queue trip also requires depth still >= high, so
  a hysteresis-band hover can't fire on elapsed time alone.

Validated: .21 Linux 185 core + 177 host + pf-capture tests, clippy
-D warnings; .133 Windows cargo check of pf-capture + punktfunk-host green.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
2026-07-17 19:31:49 +02:00
enricobuehler aedee2a4e3 perf(latency): tier-0 attribution + tier-1 send-path levers from the latency plan
ci / web (push) Successful in 49s
ci / docs-site (push) Successful in 54s
decky / build-publish (push) Successful in 18s
docker / build-push (--build-arg FEDORA_VERSION=44, ci, ci/fedora-rpm.Dockerfile, punktfunk-fedora44-rpm) (push) Successful in 10s
docker / build-push (., web/Dockerfile, punktfunk-web) (push) Successful in 9s
docker / build-push (ci, ci/fedora-rpm.Dockerfile, punktfunk-fedora-rpm) (push) Successful in 7s
docker / build-push (ci, ci/rust-ci.Dockerfile, punktfunk-rust-ci) (push) Successful in 9s
docker / build-push (docs-site, docs-site/Dockerfile, punktfunk-docs) (push) Successful in 8s
apple / swift (push) Successful in 1m23s
ci / rust (push) Failing after 4m15s
arch / build-publish (push) Failing after 4m35s
deb / build-publish (push) Failing after 4m4s
docker / deploy-docs (push) Successful in 24s
ci / bench (push) Successful in 5m35s
rpm / build-publish (43, bazzite, punktfunk-fedora-rpm) (push) Failing after 3m45s
windows-host / package (push) Failing after 9m8s
release / apple (push) Successful in 5m49s
rpm / build-publish (44, fedora-44, punktfunk-fedora44-rpm) (push) Failing after 5m25s
flatpak / build-publish (push) Failing after 8m3s
windows-msix / package (arm64, C:\Users\Public\ffmpeg-arm64, --no-default-features, aarch64-pc-windows-msvc, C:\t-a64) (push) Successful in 3m58s
android / android (push) Successful in 14m9s
apple / screenshots (push) Successful in 6m28s
windows-msix / package (x64, C:\Users\Public\ffmpeg, , x86_64-pc-windows-msvc, C:\t) (push) Successful in 7m0s
windows / build (aarch64-pc-windows-msvc) (push) Successful in 9m28s
windows / build (x86_64-pc-windows-msvc) (push) Successful in 12m26s
design/latency-reduction-2026-07.md T0.1/T0.2/T1.2/T1.3:

- T1.2 rate-capped front-loaded pacing: the paced overflow's budget is now
  min(0.9x slack, overflow wire time at ~3x the live encoder bitrate)
  (PUNKTFUNK_PACE_FACTOR, 0 = legacy deadline-only spread). A 300 KB-1 MB
  frame's tail leaves in ~2-5 ms instead of smearing across ~15 ms at 60 fps;
  GameStream schedule byte-identical (pins unchanged).
- T1.3 data-first wire order: packetize emits every block's data shards before
  any parity (per-block parity pools keep all blocks' parity alive for the
  second pass), so lossless completion stops waiting behind the parity tail.
  EOF = last emitted packet; receiver already order-agnostic.
- T0.1 staged 0xCF: HostTiming gains an append-extensible per-stage tail
  (queue/encode/pace us; seal+channel-wait derived as residual) - no cap bit
  needed, old peers read the 13-byte prefix. Joined client-side into
  Stats::host_{queue,encode,xfer,pace}_ms, the OSD detailed tier, and the
  probe's report.
- T0.2 true on-glass present timing: VK_KHR_present_id/present_wait enabled
  when supported; a PresentTimer waiter thread resolves each present id to
  real visibility, replacing the submit-time display stamp (which undercounts
  by up to a refresh and hides a silent-FIFO standing queue).

Validated on .21: core 185 + host 185 tests, pf-presenter 19, clippy
-D warnings across all five touched crates; loss-harness recovery curve
unchanged; C ABI harness round-trips.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
2026-07-17 19:13:08 +02:00
enricobuehler c28b10a5b9 fix(apple/clipboard): announce + serve image/png from ANY pasteboard image type
apple / swift (push) Successful in 1m20s
ci / web (push) Successful in 1m31s
ci / docs-site (push) Successful in 1m37s
docker / build-push (--build-arg FEDORA_VERSION=44, ci, ci/fedora-rpm.Dockerfile, punktfunk-fedora44-rpm) (push) Successful in 10s
decky / build-publish (push) Successful in 23s
docker / build-push (ci, ci/fedora-rpm.Dockerfile, punktfunk-fedora-rpm) (push) Successful in 11s
docker / build-push (ci, ci/rust-ci.Dockerfile, punktfunk-rust-ci) (push) Successful in 12s
docker / build-push (., web/Dockerfile, punktfunk-web) (push) Successful in 56s
docker / build-push (docs-site, docs-site/Dockerfile, punktfunk-docs) (push) Successful in 53s
ci / rust (push) Failing after 4m14s
arch / build-publish (push) Failing after 4m39s
docker / deploy-docs (push) Successful in 26s
deb / build-publish (push) Failing after 4m52s
ci / bench (push) Successful in 6m24s
rpm / build-publish (43, bazzite, punktfunk-fedora-rpm) (push) Failing after 4m11s
rpm / build-publish (44, fedora-44, punktfunk-fedora44-rpm) (push) Failing after 4m4s
apple / screenshots (push) Successful in 6m16s
android / android (push) Successful in 12m40s
macOS image copies rarely carry public.png — screenshots/Preview put TIFF on
the pasteboard, browsers add WebP/AVIF/GIF (observed live: TIFF+RTFD+WebP+AVIF+
8BPS+GIF, no PNG) — so the literal .png announce never fired and images
silently didn't sync. Announce image/png whenever a convertible image is
present (TIFF/HEIC alongside the native PNG check) and convert at serve time
via NSImage -> NSBitmapImageRep PNG (lazy, per design §3.5 — bytes still cross
only on a host paste).

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
2026-07-17 19:07:33 +02:00
enricobuehler 78d018ae2f fix(apple/clipboard): announce + serve image/png from ANY pasteboard image type
apple / swift (push) Successful in 1m20s
ci / web (push) Successful in 49s
ci / docs-site (push) Successful in 1m4s
decky / build-publish (push) Successful in 19s
docker / build-push (--build-arg FEDORA_VERSION=44, ci, ci/fedora-rpm.Dockerfile, punktfunk-fedora44-rpm) (push) Successful in 11s
docker / build-push (., web/Dockerfile, punktfunk-web) (push) Successful in 10s
docker / build-push (ci, ci/fedora-rpm.Dockerfile, punktfunk-fedora-rpm) (push) Successful in 9s
docker / build-push (ci, ci/rust-ci.Dockerfile, punktfunk-rust-ci) (push) Successful in 10s
docker / build-push (docs-site, docs-site/Dockerfile, punktfunk-docs) (push) Successful in 9s
ci / bench (push) Successful in 5m43s
release / apple (push) Successful in 5m48s
docker / deploy-docs (push) Successful in 26s
apple / screenshots (push) Successful in 6m26s
android / android (push) Successful in 13m58s
arch / build-publish (push) Successful in 14m37s
ci / rust (push) Failing after 15m36s
deb / build-publish (push) Successful in 14m40s
rpm / build-publish (43, bazzite, punktfunk-fedora-rpm) (push) Successful in 14m24s
rpm / build-publish (44, fedora-44, punktfunk-fedora44-rpm) (push) Successful in 14m45s
macOS image copies rarely carry public.png — screenshots/Preview put TIFF on
the pasteboard, browsers add WebP/AVIF/GIF (observed live: TIFF+RTFD+WebP+AVIF+
8BPS+GIF, no PNG) — so the literal .png announce never fired and images
silently didn't sync. Announce image/png whenever a convertible image is
present (TIFF/HEIC alongside the native PNG check) and convert at serve time
via NSImage -> NSBitmapImageRep PNG (lazy, per design §3.5 — bytes still cross
only on a host paste).

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
2026-07-17 17:43:00 +02:00
enricobuehler 1b58130e68 merge(release): reconcile the 0.13.0 bump with today's landings
windows-drivers / probe-and-proto (push) Successful in 34s
ci / web (push) Successful in 51s
ci / docs-site (push) Successful in 1m9s
decky / build-publish (push) Successful in 20s
apple / swift (push) Successful in 1m16s
docker / build-push (--build-arg FEDORA_VERSION=44, ci, ci/fedora-rpm.Dockerfile, punktfunk-fedora44-rpm) (push) Successful in 11s
docker / build-push (ci, ci/fedora-rpm.Dockerfile, punktfunk-fedora-rpm) (push) Successful in 9s
docker / build-push (ci, ci/rust-ci.Dockerfile, punktfunk-rust-ci) (push) Successful in 9s
docker / build-push (., web/Dockerfile, punktfunk-web) (push) Successful in 33s
docker / build-push (docs-site, docs-site/Dockerfile, punktfunk-docs) (push) Successful in 10s
windows-drivers / driver-build (push) Successful in 1m42s
ci / bench (push) Successful in 5m36s
release / apple (push) Successful in 5m36s
flatpak / build-publish (push) Successful in 7m1s
docker / deploy-docs (push) Successful in 24s
apple / screenshots (push) Successful in 5m41s
ci / rust (push) Failing after 14m16s
arch / build-publish (push) Successful in 14m37s
deb / build-publish (push) Successful in 16m50s
rpm / build-publish (43, bazzite, punktfunk-fedora-rpm) (push) Successful in 15m6s
android / android (push) Successful in 17m4s
windows-host / package (push) Successful in 15m23s
windows-msix / package (arm64, C:\Users\Public\ffmpeg-arm64, --no-default-features, aarch64-pc-windows-msvc, C:\t-a64) (push) Successful in 4m25s
rpm / build-publish (44, fedora-44, punktfunk-fedora44-rpm) (push) Successful in 17m10s
windows-msix / package (x64, C:\Users\Public\ffmpeg, , x86_64-pc-windows-msvc, C:\t) (push) Successful in 4m2s
windows / build (aarch64-pc-windows-msvc) (push) Successful in 5m4s
windows / build (x86_64-pc-windows-msvc) (push) Successful in 5m57s
Brings origin's release prep (widgets App Store profile mapping + workspace
version 0.13.0) into the local line that landed the Linux stream sink, the
Windows client-only audio rework, the July security-audit fixes, cross-client
render-scale, and the widgets scheme. release.yml was byte-identical on both
sides; no other file overlap.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
2026-07-17 17:26:59 +02:00
enricobuehler 12d4b025f7 ci(release): widgets-extension App Store profile mapping + shared Xcode scheme
Mirrors origin's 15233a68 (byte-identical release.yml, so it dedupes at the
next reconcile) and adds the shared PunktfunkWidgetsExtension scheme the
archive step builds with.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
2026-07-17 17:20:15 +02:00
enricobuehler 871ebb31ce feat(clients): render-scale setting on every client — shared punktfunk_core::render_scale
Client-side supersampling/downscaling: the client asks the host to render
and encode at chosen-resolution × scale (the host does no scaling) and the
presenter rescales the decoded frame to the display. >1 supersamples for
sharpness; <1 lightens the host GPU and the link. Default 1.0 = Native, the
prior behavior.

The geometry lives once in punktfunk_core::render_scale (multiply, preserve
aspect ratio, floor to even, clamp to the codec's per-axis ceiling — 4096
for H.264, 8192 otherwise), the Rust twin of the Apple client's
RenderScale.swift, consumed by the native session client, the presenter's
match-window path, the Windows/Linux settings UIs, Decky, and Android
(settings + host connect + unit test).

Implemented and platform-verified by the Apple-client-features session
(Linux+Android+Apple green there); the punktfunk-core wiring
(pub mod render_scale) is restored here after being lost in a working-tree
reconciliation.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
2026-07-17 17:20:15 +02:00
enricobuehler 600693914f fix(security): land the 2026-07 audit fixes — SSRF guards, roster lane, SYSTEM path hygiene
The low/medium findings from the July host+Windows security review, as
implemented in the audit session's working tree:

- Webhooks and library-art fetches refuse loopback/link-local/metadata
  targets and no longer follow redirects (SSRF pivots from the privileged
  host process).
- The paired-client rosters (/clients, /native/clients) move off the
  streaming-client auth lane — one paired device could enumerate every other
  device's name + fingerprint; only the bearer/loopback console keeps them.
- Device-name sanitizing extends to bidi/format control characters
  (spoofable rendering) via the shared native_pairing::is_spoofy_char;
  stream-marker quoting uses the same set.
- The SYSTEM service resolves powershell by its full System32 path —
  CreateProcess checks the launching EXE's own directory first, so a planted
  powershell.exe beside the host binary would have run as SYSTEM.
- The pf-vdisplay driver's opt-in file log moves from world-writable
  C:\Users\Public to WUDFHost's own temp dir.
- GameStream pairing sessions are single-use (removed whatever the outcome).
- Uninstall also removes the pf_mouse driver-store entry (rider from the
  virtual-HID-mouse work).
- openapi.json regenerated (hardened-config-dir doc wording).

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
2026-07-17 17:20:15 +02:00
enricobuehler 4d89dcd3d7 fix(audio/windows): explicit-endpoint capture, client-only playback, self-healing watchdog
Root cause of the field report (Android client, Windows host: no audio until
the user manually cycled Sound-output devices, then audio on BOTH PC and
phone): the WASAPI loopback captured whatever the default render endpoint
was at open time — not the wiring plan's chosen endpoint — the plan's
IPolicyConfig default-set is warn-only and racy, and nothing reacted to
mid-stream default-device changes.

- Explicit-endpoint capture: the capture thread opens the plan's
  loopback_render by id, never "the default" (KEEP_DEFAULT preserves the old
  default-capturing behavior with the echo guard).
- Client-only playback default: wiring_plan::plan(..., host_audio) prefers a
  silent sink (Steam Streaming Microphone render side — loopback-validated,
  silent on host) over real hardware, so stream audio plays on the client
  only; PUNKTFUNK_HOST_AUDIO=1 restores real-hw-first (audible on the host).
  The capture side auto-installs the Steam pair once per process when no
  silent sink exists; open() handshake timeout 3s -> 30s to cover it, and a
  handshake timeout now stops the detached thread (it used to run for the
  process lifetime with the default still parked).
- Self-healing capture thread (wasapi_cap): outer capture_once loop
  (Assert|Follow) with a ~1s watchdog on the default render id. A user
  switch to a capturable endpoint is followed (their choice wins, audio on
  both); a switch to a dud (cable/SSS/mic target) re-asserts the plan;
  IPolicyConfig-denied converges to Follow instead of churning. Device
  errors reopen with 2s backoff; only the FIRST open failure is fatal.
  Zero-packets breadcrumb after 30s distinguishes broken-loopback from
  quiet-desktop.
- Park/restore of the default playback device (audio_control):
  wire_now(set_playback) parks the default on the loopback sink only for the
  capture's lifetime (the mic pump passes false — it runs while the host is
  idle); crash marker audio-default.prev + recover_orphaned_default() at
  first wire; restore is skipped if the operator changed the default
  themselves. A mic-default hygiene pass keeps VB-Cable installs audible and
  never records the mic target as the restore target.
- Session-end park_audio_capture(): Windows DROPS the capturer (thread join
  restores the default) instead of caching it; Linux keeps the parked
  PipeWire thread. Composes with the stream-sink idle() hook at all three
  park sites (idle is a no-op on Windows).

Verified: Linux (.21) clippy -D warnings + 176 punktfunk-host tests green
(incl. the new wiring-plan preference tests); Windows (.173) clippy with
nvenc,amf-qsv --all-targets -D warnings green at this exact tree.
On-glass winbox/Android validation still owed.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
2026-07-17 17:05:02 +02:00
enricobuehler 97c5778a36 merge(audio): land feat/linux-stream-sink — Linux host-owned stream sink
Merges 44b71e74 (see its message for the full design: the "Punktfunk Stream
Speaker" Audio/Sink claimed as the session's default output — the crackle
root fix for hardware-sink churn — plus the core-error liveness fix and true
5.1/7.1 capture) across the W1–W8 refactor that landed since the branch
forked:

- punktfunk1.rs was split on main: the audio_thread idle()/drain() hunks are
  hand-ported into native/audio.rs (same three sites: reuse-drain comment,
  encoder-fail park, end-of-thread park).
- pwinit moved to pf-capture (W6.2): the branch's crate::pwinit calls in
  pw_thread and stream_sink.rs become pf_capture::pwinit.
- Kept main's log-tier demotions (stream-state + setup lines at debug)
  inside the restructured pw_thread.

Resolution verified on Linux (home-worker-5): clippy -D warnings clean +
173 punktfunk-host tests green at the 691c064a-based equivalent ba5973a2;
re-verified at this base before landing.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
2026-07-17 16:35:02 +02:00
enricobuehler 22b352c1da chore(release): bump workspace version to 0.13.0
apple / swift (push) Successful in 1m19s
audit / bun-audit (push) Successful in 13s
audit / cargo-audit (push) Successful in 2m8s
ci / web (push) Successful in 53s
release / apple (push) Successful in 5m45s
ci / docs-site (push) Successful in 1m3s
decky / build-publish (push) Successful in 23s
docker / build-push (--build-arg FEDORA_VERSION=44, ci, ci/fedora-rpm.Dockerfile, punktfunk-fedora44-rpm) (push) Successful in 10s
docker / build-push (., web/Dockerfile, punktfunk-web) (push) Successful in 40s
docker / build-push (ci, ci/fedora-rpm.Dockerfile, punktfunk-fedora-rpm) (push) Successful in 9s
ci / bench (push) Successful in 5m26s
docker / build-push (ci, ci/rust-ci.Dockerfile, punktfunk-rust-ci) (push) Successful in 10s
apple / screenshots (push) Successful in 6m27s
windows-host / package (push) Successful in 15m6s
arch / build-publish (push) Successful in 10m50s
docker / build-push (docs-site, docs-site/Dockerfile, punktfunk-docs) (push) Successful in 1m1s
windows-msix / package (arm64, C:\Users\Public\ffmpeg-arm64, --no-default-features, aarch64-pc-windows-msvc, C:\t-a64) (push) Successful in 3m58s
android / android (push) Successful in 16m0s
docker / deploy-docs (push) Successful in 11s
flatpak / build-publish (push) Failing after 8m9s
windows-msix / package (x64, C:\Users\Public\ffmpeg, , x86_64-pc-windows-msvc, C:\t) (push) Successful in 4m11s
deb / build-publish (push) Successful in 13m4s
ci / rust (push) Successful in 19m37s
windows / build (aarch64-pc-windows-msvc) (push) Successful in 5m6s
rpm / build-publish (44, fedora-44, punktfunk-fedora44-rpm) (push) Successful in 14m10s
rpm / build-publish (43, bazzite, punktfunk-fedora-rpm) (push) Successful in 14m26s
windows / build (x86_64-pc-windows-msvc) (push) Successful in 5m51s
The eight W6 leaf crates hardcoded 0.12.0 instead of inheriting the
workspace version — switched to version.workspace = true so the next bump
is one line again.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
2026-07-17 16:32:35 +02:00
enricobuehler 15233a68cf ci(release): map the widgets extension to its own App Store provisioning profile
The .ipa now embeds PunktfunkWidgetsExtension (io.unom.punktfunk.widgets), a
second distribution artifact, so the manual-signing exportArchive must map its
App ID to its own App Store profile — without it the export fails with "no
profile for io.unom.punktfunk.widgets". Requires the "Punktfunk iOS Widgets
App Store Distribution" profile on the runner.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
2026-07-17 16:32:35 +02:00
enricobuehler 2a637eaf3f Merge origin/main: shared clipboard Phase 1 + first-frame-latency v4 into the local SDK line
apple / screenshots (push) Has been cancelled
arch / build-publish (push) Has been cancelled
android / android (push) Has been cancelled
apple / swift (push) Has been cancelled
ci / rust (push) Has been cancelled
ci / web (push) Has been cancelled
ci / docs-site (push) Has been cancelled
ci / bench (push) Has been cancelled
deb / build-publish (push) Has been cancelled
decky / build-publish (push) Has been cancelled
docker / build-push (--build-arg FEDORA_VERSION=44, ci, ci/fedora-rpm.Dockerfile, punktfunk-fedora44-rpm) (push) Has been cancelled
docker / build-push (., web/Dockerfile, punktfunk-web) (push) Has been cancelled
docker / build-push (ci, ci/fedora-rpm.Dockerfile, punktfunk-fedora-rpm) (push) Has been cancelled
docker / build-push (ci, ci/rust-ci.Dockerfile, punktfunk-rust-ci) (push) Has been cancelled
docker / build-push (docs-site, docs-site/Dockerfile, punktfunk-docs) (push) Has been cancelled
docker / deploy-docs (push) Has been cancelled
rpm / build-publish (43, bazzite, punktfunk-fedora-rpm) (push) Has been cancelled
rpm / build-publish (44, fedora-44, punktfunk-fedora44-rpm) (push) Has been cancelled
Brings the day's landed work — clipboard (pf-clipboard + macOS client, ABI v8),
the perf/first-frame-latency merge (driver proto v4, Welcome-time display prep,
in-place resize), and the W7/W8 reconciliation — into the local main that had
diverged with the SDK-publish commits.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
2026-07-17 16:29:10 +02:00
enricobuehler 6c976e9dc5 fix(apple): land the RenderScale/DefaultsKeys definitions the client refactor references
apple / swift (push) Successful in 1m14s
ci / web (push) Successful in 1m42s
ci / docs-site (push) Successful in 2m0s
release / apple (push) Successful in 5m32s
rpm / build-publish (43, bazzite, punktfunk-fedora-rpm) (push) Has been cancelled
rpm / build-publish (44, fedora-44, punktfunk-fedora44-rpm) (push) Has been cancelled
apple / screenshots (push) Successful in 6m33s
ci / bench (push) Successful in 8m15s
decky / build-publish (push) Successful in 29s
docker / build-push (--build-arg FEDORA_VERSION=44, ci, ci/fedora-rpm.Dockerfile, punktfunk-fedora44-rpm) (push) Successful in 12s
docker / build-push (., web/Dockerfile, punktfunk-web) (push) Successful in 54s
docker / build-push (ci, ci/fedora-rpm.Dockerfile, punktfunk-fedora-rpm) (push) Successful in 11s
docker / build-push (ci, ci/rust-ci.Dockerfile, punktfunk-rust-ci) (push) Successful in 11s
android / android (push) Successful in 14m14s
docker / build-push (docs-site, docs-site/Dockerfile, punktfunk-docs) (push) Successful in 11s
arch / build-publish (push) Successful in 18m11s
deb / build-publish (push) Successful in 18m29s
docker / deploy-docs (push) Successful in 28s
ci / rust (push) Successful in 28m37s
The W7/W8 client reconciliation committed the CONSUMERS of the render-scale
setting and the toggle-fullscreen notification (ContentView/FullscreenController
et al.) while their definitions were still uncommitted working-tree state from
the Apple-features effort — apple.yml red on main (run 10657). Lands the two
definition files (PunktfunkShared/RenderScale.swift + the DefaultsKeys additions)
so main builds; the rest of that effort's WIP stays in its working tree.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
2026-07-17 16:20:39 +02:00
enricobuehler c8ee4b9902 fix(pf-vdisplay,pf-capture,pf-win-display): pre-split paths in the auto-merged v4 code
windows-drivers / probe-and-proto (push) Successful in 31s
ci / web (push) Successful in 43s
apple / swift (push) Failing after 1m1s
ci / docs-site (push) Successful in 1m0s
apple / screenshots (push) Has been skipped
docker / build-push (--build-arg FEDORA_VERSION=44, ci, ci/fedora-rpm.Dockerfile, punktfunk-fedora44-rpm) (push) Successful in 10s
decky / build-publish (push) Successful in 19s
docker / build-push (ci, ci/fedora-rpm.Dockerfile, punktfunk-fedora-rpm) (push) Successful in 10s
docker / build-push (., web/Dockerfile, punktfunk-web) (push) Successful in 44s
windows-drivers / driver-build (push) Successful in 1m38s
docker / build-push (docs-site, docs-site/Dockerfile, punktfunk-docs) (push) Successful in 1m1s
ci / bench (push) Successful in 5m34s
docker / build-push (ci, ci/rust-ci.Dockerfile, punktfunk-rust-ci) (push) Successful in 5m53s
android / android (push) Has been cancelled
arch / build-publish (push) Has been cancelled
ci / rust (push) Has been cancelled
deb / build-publish (push) Has been cancelled
windows-host / package (push) Successful in 15m6s
rpm / build-publish (43, bazzite, punktfunk-fedora-rpm) (push) Successful in 18m48s
docker / deploy-docs (push) Successful in 23s
rpm / build-publish (44, fedora-44, punktfunk-fedora44-rpm) (push) Successful in 18m28s
The rename-followed perf hunks still said crate::win_display:: (the pre-W6
layout) — point them at pf_win_display::win_display:: and widen the four
helpers they call cross-crate.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
2026-07-17 16:13:15 +02:00
enricobuehler 1197415216 fix(pf-vdisplay,pf-win-display): v4 trait surface on the extracted driver.rs + cross-crate visibility
The W-refactor extracted VdisplayDriver into manager/driver.rs (the merge
resolution assumed it deleted) — carry the v4 changes there: open() returns the
driver's protocol version, update_modes() default-errs to the re-arrival
fallback. wait_target_departed goes pub for the manager's cross-crate call.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
2026-07-17 16:11:34 +02:00
enricobuehler 18a5d93ae3 fix(host): allow too_many_arguments on the two fns the v4 merge grew
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
2026-07-17 16:09:57 +02:00
enricobuehler 09849906e9 Merge perf/first-frame-latency: driver proto v4 + first-frame/resize latency (P0-P2)
Brings the first-frame-latency branch (P0.1 transition tracing, P1.1/P1.2
Welcome-time display prep, P2 in-place resize; pf-driver-proto v3 -> v4 with
IOCTL_UPDATE_MODES) onto current main. The branch predates the W6.2/W7 splits,
so git's rename detection carried most of it into the moved crates
(pf-capture idd_push, pf-vdisplay manager/pf_vdisplay, pf-win-display,
pf-driver-proto, the driver workspace) and the punktfunk1.rs remainder was
re-homed by hand:

- native/handshake.rs: welcome/start trace marks + the Welcome-time display
  prep spawn (the prep thread BECOMES the stream thread; hand-off via a
  SyncSender<SessionContext>). negotiate() gains bringup/quit/stop and returns
  the PrepHandle.
- native.rs: bringup/resize_ms creation + the stop/quit flags hoisted BEFORE
  the handshake (the close watcher splits: flags pre-handshake, lifecycle
  events post-handshake where `hello` exists); punch_done stamp; the data
  plane adopts the prep thread's result or builds inline.
- native/stream.rs: SessionContext/SendStats carry the trace; send_loop
  finishes it on the first video packet; the resize path gains the in-place
  fast path (try_inplace_resize) with the full rebuild as fallback, restructured
  so both share the post-rebuild bookkeeping; prepare_display/PreparedDisplay/
  PrepHandle; build_pipeline(+retry) thread the stage marks.
- session_status/mgmt: ttff_ms + last_resize_ms per session (union with the
  lifecycle-events fields main added to the same spots).
- pf-capture: Capturer gains capture_target_id() + resize_output() defaults.
- pf-vdisplay manager: perf's faster activation poll (60x50ms) + the settle
  floor before the PnP sweep, on main's knobs/no-trait shape.

Also: packaging/windows/build-gamepad-drivers.ps1 is ASCII again (an em-dash
from the pf-mouse work tripped windows-host.yml's locale-safety gate on main).

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
2026-07-17 16:08:16 +02:00
enricobuehler 691c064a37 build(sdk): publish @punktfunk/host to the Gitea npm registry
- package.json: drop private; point main/types/exports/bin at a tsc-built dist/;
  add publishConfig (unom/npm registry), files, repo metadata, and the
  MIT OR Apache-2.0 license; effect becomes a peerDependency (shared instance).
- tsconfig.build.json: emit dist/ JS + .d.ts (bun shebang preserved on the bin).
- .npmrc: map the @punktfunk scope to the registry (no token committed).
- sdk-publish.yml: publish on sdk-v* tags or manual dispatch, reusing the
  REGISTRY_TOKEN secret; typecheck/test/build/tag-matches-version gate.
- README: Install section for consumers.

Verified: build green, frozen lockfile stable, bun publish --dry-run packs
@punktfunk/host@0.1.0 (dist + README only) to the unom registry.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
2026-07-17 15:54:36 +02:00
enricobuehler 34519566ba feat(sdk): VirtualHere DualSense passthrough example + automation recipe
Add sdk/examples/virtualhere-dualsense.ts — bind a real USB DualSense (shared
from the couch via VirtualHere USB-over-IP) to the host for the length of each
connection and release it after, for full gyro/touchpad/adaptive-trigger/USB-
rumble passthrough instead of the emulated pad. Brackets on client.connected/
disconnected and releases the pad on SIGTERM for a clean runner stop.

Document it in the Events & hooks page with a zero-code hooks.json variant.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
2026-07-17 15:54:36 +02:00
enricobuehler 86d9f49473 style(pf-vdisplay): rustfmt under the pinned 1.96.0 toolchain
audit / bun-audit (push) Successful in 12s
windows-drivers / probe-and-proto (push) Successful in 31s
ci / web (push) Successful in 48s
apple / swift (push) Failing after 1m1s
ci / docs-site (push) Successful in 1m2s
decky / build-publish (push) Successful in 21s
windows-drivers / driver-build (push) Successful in 1m35s
windows-host / package (push) Failing after 16s
audit / cargo-audit (push) Successful in 2m46s
docker / build-push (., web/Dockerfile, punktfunk-web) (push) Successful in 9s
docker / build-push (ci, ci/fedora-rpm.Dockerfile, punktfunk-fedora-rpm) (push) Successful in 9s
docker / build-push (ci, ci/rust-ci.Dockerfile, punktfunk-rust-ci) (push) Successful in 10s
docker / build-push (docs-site, docs-site/Dockerfile, punktfunk-docs) (push) Successful in 8s
release / apple (push) Successful in 3m56s
apple / screenshots (push) Has been skipped
ci / bench (push) Successful in 6m26s
windows-msix / package (arm64, C:\Users\Public\ffmpeg-arm64, --no-default-features, aarch64-pc-windows-msvc, C:\t-a64) (push) Successful in 4m19s
docker / build-push (--build-arg FEDORA_VERSION=44, ci, ci/fedora-rpm.Dockerfile, punktfunk-fedora44-rpm) (push) Successful in 7m0s
windows-msix / package (x64, C:\Users\Public\ffmpeg, , x86_64-pc-windows-msvc, C:\t) (push) Successful in 4m16s
flatpak / build-publish (push) Failing after 8m9s
docker / deploy-docs (push) Successful in 24s
arch / build-publish (push) Successful in 13m9s
android / android (push) Successful in 13m11s
deb / build-publish (push) Successful in 14m12s
windows / build (aarch64-pc-windows-msvc) (push) Successful in 5m8s
ci / rust (push) Successful in 19m29s
rpm / build-publish (43, bazzite, punktfunk-fedora-rpm) (push) Successful in 14m23s
rpm / build-publish (44, fedora-44, punktfunk-fedora44-rpm) (push) Successful in 13m10s
windows / build (x86_64-pc-windows-msvc) (push) Successful in 6m1s
The W6.2 pf-vdisplay extraction (27a5d8da) committed six spots that the pinned
rustfmt (rust-toolchain.toml = 1.96.0) reformats — a short closure collapse and
chain-call wrapping. No semantic change; makes cargo fmt --all --check green so
the combined push does not land CI red on pre-existing format drift.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
2026-07-17 15:38:12 +02:00
enricobuehler 2064c0780c merge(core): reconcile the W7/W8 client refactor with origin's shared-clipboard feature
origin/main landed the shared clipboard (design/clipboard-and-file-transfer.md) while
this branch split quic/msgs.rs -> quic/{caps,control,...} and client.rs ->
client/{mod,control,worker,pump,planes,...} (W7) and deleted the two monoliths. The
feature had modified both deleted files, so its delta is re-applied onto the split
instead of resurrecting the monoliths:

  - HOST_CAP_CLIPBOARD                         -> quic/caps.rs
  - MSG_CLIP_* / CLIP_* consts, the six Clip*
    structs, and their encode/decode impls     -> quic/control.rs (beside the clock codecs)
  - CtrlRequest::{ClipControl,ClipOffer} +
    Negotiated.host_caps                        -> client/control.rs
  - WorkerArgs.{clip_event_tx,clip_cmd_rx}      -> client/worker.rs
  - CLIP_EVENT_QUEUE                            -> client/planes.rs
  - NativeClient clip fields, the 7 clip_* /
    host_caps / next_clip methods, connect()
    channel wiring                              -> client/mod.rs
  - the control-task encode/decode arms and
    the clipboard-task spawn                     -> client/pump.rs

Cargo.lock reconciled (adds pf-clipboard), punktfunk-host/Cargo.toml unions the W6
pf-* subsystem deps with pf-clipboard, and include/punktfunk_core.h is the cbindgen
union (clipboard + rumble C-ABI). punktfunk-core builds --all-features and its 174
lib tests pass, including quic::tests::clip_loopback.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
2026-07-17 15:38:11 +02:00
enricobuehler f439b69451 refactor(android/W8): split decode.rs into decode/ directory module
Break the 1747-line clients/android/native/src/decode.rs into a decode/ directory
module (mod.rs + 5 concern submodules):
  - decode/setup.rs      : codec creation + low-latency config + thread/frame-rate
                           tuning + HDR static-info encode
  - decode/display.rs    : DisplayTracker + render-callback registration + HDR dataspace
  - decode/latency.rs    : realtime clock + decoded-pts / user-flags stat recording
  - decode/sync_loop.rs  : the synchronous poll decode loop (+ feed/drain) — moved WHOLE
  - decode/async_loop.rs : the event-driven async decode loop (+ helpers) — moved WHOLE
decode/mod.rs keeps the consts, DecodeOptions, and the `run` entry point + the
`codec_mime`/`codec_label` re-export, so every crate::decode::X path stays byte-stable.
The module has no decoder struct (free functions + small types), so both decode loops
move byte-for-byte and their separately-inlined received-stat recording is NOT unified.
16 helper fns/types became pub(super) for sibling access; zero field bumps. lib.rs
unchanged (`#[cfg(target_os="android")] mod decode;` resolves to decode/mod.rs).

Verified: cargo-ndk check (aarch64-linux-android, clean) + the gradle cargoNdkDebug
build (arm64-v8a / armeabi-v7a / x86_64). On-device runtime re-verification still owed
per the plan (the two decode loops are a hot path).

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
2026-07-17 15:06:24 +02:00
enricobuehler 1eef55016d refactor(apple/W8): extract FullscreenController + ApprovalRequest from ContentView
Move the macOS `FullscreenController` (NSViewRepresentable that drives native
fullscreen) into its own FullscreenController.swift, and `ApprovalRequest` (the
pending-trust-decision value type) into ApprovalRequest.swift, out of the
1041-line ContentView.swift. Both were file-`private`; dropped to internal so
ContentView (same module) still references them across files. StreamView*.swift
and the connection body are untouched. Pure move; no behavior change.

Verified: `swift build` (macOS) — Build complete.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
2026-07-17 15:02:34 +02:00
enricobuehler 61118cbdd4 refactor(presenter/W8): split vk.rs into vk/ directory module
Break the 2513-line pf-presenter/src/vk.rs into a vk/ directory module (mod.rs +
6 concern submodules), keeping ALL type definitions in vk/mod.rs so every submodule
(a descendant of vk) sees the Presenter/OverlayPipe/etc. private fields with no
field bumps:
  - vk/setup.rs      : Presenter::new + device/format/present-mode selection
  - vk/present.rs    : the per-frame present path (present + CSC record + AVVkFrame
                       sync) — HOT PATH, moved whole
  - vk/reconfig.rs   : swapchain recreate/resize + HDR reconfiguration
  - vk/resources.rs  : video-image/staging (re)build + Retired-frame destruction
  - vk/overlay_pipe.rs: the presenter-side overlay composite pipeline
  - vk/gpu.rs        : memory allocation, image barriers, geometry helpers (+ tests)
vk/mod.rs keeps FrameInput/Presenter/OverlayPipe/VideoImage/Staging/Retired/HwCtx*
+ the public accessors + Drop. Methods/free-fns a sibling submodule calls became
pub(super) (~18); zero field bumps, zero re-exports (Presenter/FrameInput never
leave mod.rs). lib.rs unchanged (`pub mod vk;` resolves to vk/mod.rs). The moved
overlay shader include_bytes! gained one `../` for the deeper dir. Pure move; no
behavior change; the hot present path keeps only static pub(super) calls (inlinable).

Verified both platforms: Linux (home-worker-5) clippy -p pf-presenter
(--all-targets -D warnings) + test; Windows (winbox, ASCII CARGO_HOME) clippy.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
2026-07-17 14:39:36 +02:00
enricobuehler 22a61e0b48 refactor(console-ui/W8): split shell.rs into shell/ facade + render/overlays/tests
Break the 1212-line pf-console-ui/src/shell.rs into a facade + shell/ subdir
(shell.rs stays the parent; `mod render;` resolves to shell/render.rs):
  - shell/render.rs   : the per-frame screen compose/transition path (Shell::render
                        + the LayerEnv paint helper)
  - shell/overlays.rs : the modal overlays (Shell::draw_overlays + draw_takeover)
  - shell/tests.rs    : the inline #[cfg(test)] module, extracted verbatim
The Shell struct + its public API + draw_aurora() stay in shell.rs (both children
reach draw_aurora + the private fields as descendants — no bumps). Sole visibility
change: draw_overlays -> pub(in crate::shell) (its caller Shell::render is now a
sibling). Zero re-exports needed (Shell/ConsoleOptions never leave the root). Pure
move; no behavior change.

Verified both platforms: Linux (home-worker-5) clippy -p pf-console-ui
(--all-targets -D warnings) + test; Windows (winbox, ASCII CARGO_HOME) clippy.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
2026-07-17 14:29:07 +02:00
enricobuehler 13b1f36d4a feat(core,clients): one rumble policy engine for every platform (rumble root fix D)
punktfunk-core client/rumble.rs: a per-connection policy engine consumes seq-gated wire
updates and emits EFFECTIVE actuator commands — re-emits on renewals (duration APIs stay
re-armed), self-silences at the v2 lease, a UNIFORM 1 s legacy-host staleness replacing the
per-platform zoo (Apple 1.6 s / Android 60 s / SDL 1.5 s / Deck 1 s), quirk-declared
actuator keepalives (Deck 40 ms + LSB dedupe-defeat jitter), and one stop per buzzing pad
on connection close. Per-pad mailbox semantics: a stalled embedder wakes to ONE current
command, and a stop can structurally never be the update an overflowing queue drops.

New API/ABI: NativeClient::{next_rumble_command,set_rumble_quirks} +
punktfunk_connection_next_rumble_cmd/_set_rumble_quirks (next_rumble/next_rumble2 stay for
un-migrated embedders; both consumers are fed). Migrations DELETE the platform forks:
pf-client-core loses RumbleState + the Deck keepalive loop + LEGACY_RUMBLE_CEILING_MS and
physically silences a slot at close; Android loses the 60 s legacy one-shot (backstop
repack, cancel-on-zero); Apple loses envelopeDeadline + sessionStaleSeconds + both tick
watchdogs (CoreHaptics realization untouched; mac xcframework rebuilt locally).

design/rumble-root-fix.md par. D. Engine 10/10 unit tests; core tests 176 Linux / 175
Windows + clippy -D warnings; swift build + RumbleTuningTests; Kotlin + android-native
compile green.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
2026-07-17 14:07:32 +02:00
enricobuehler 9e6fc6e071 fix(host/inject,drivers): rumble root fixes A-C — lossless report ring + rumble-keyed idle watchdogs
B: PadFeedback.game_drove -> rumble_drove, keyed on vibration-asserting reports — an
LED/adaptive-trigger stream can no longer feed the abandoned-rumble force-off while a
coalesced stop never re-asserts (the confirmed unbounded stuck-ON path). C: Linux parity —
every UHID backend now arms the shared watchdog (Steam Input drives these pads over hidraw
with Windows abandonment semantics) and the uinput mixer force-stops abandoned
infinite-replay FF effects (FfState, unit-tested). Shared PUNKTFUNK_RUMBLE_IDLE_MS hatch
(0 = off; non-zero floored above SDL's ~2 s rumble resend).

A: PadShm v2.1 — a 1024 B tail extension carrying an 8-slot lossless output-report ring,
feature-negotiated via zeroed reserved fields (out_ring_ver; deliberately NO
GAMEPAD_PROTO_VERSION bump — mixed generations degrade to the legacy latest-report slot
instead of failing closed). The pf-dualsense driver dual-writes both planes
(publish_output); the host's shared OutputDrain drains oldest->newest with a torn-read
recheck and an overflow->resync path (PadFeedback.resync force-stops + re-arms dedups).
pf-umdf-util grows a min_data_size map fallback. Ds*Feedback.fresh removed (dead).

design/rumble-root-fix.md par. A-C. Verified: pf-inject tests+clippy Linux+Windows (53/53
on winbox incl. the stop-coalesce repro); drivers ws check+clippy on the CI runner.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
2026-07-17 14:07:32 +02:00
enricobuehler 570ff504ad refactor(client-core/W8): split video.rs into flat decoder-backend siblings
Break the 1974-line pf-client-core/src/video.rs into flat sibling modules
(matching the crate's video_d3d11.rs / video_pyrowave.rs convention), leaving
video.rs as the contract + Decoder dispatch facade:
  - video_color.rs   : ColorDesc + csc_rows (the Y'CbCr->RGB matrix)
  - video_software.rs : the libavcodec/swscale SoftwareDecoder
  - video_vaapi.rs   : the Linux-only VAAPI/DRM-PRIME backend (mod is cfg(linux))
  - video_vulkan.rs  : the FFmpeg Vulkan Video backend
Every crate::video::X / video::X path stays byte-stable (ColorDesc + csc_rows
re-exported from video.rs; frame POD, VulkanDecodeDevice, QueueLock, Decoder,
decodable_codecs*, ffmpeg_codec_id, fourcc/drm_fourcc_for all stay in video.rs).
Code-driven placements: averr, AVERROR_EAGAIN, frame_is_keyframe stay in video.rs
(shared by all three decoders); DrmFrameGuard's field + drm_fourcc_for +
Software/Vaapi/VulkanDecoder ctors/decode became pub(crate) (sibling access);
the test module split three ways (software tests need private decoder internals).
Pure move; no behavior change.

Verified on Linux (home-worker-5): cargo clippy -p pf-client-core (default
[pyrowave] + --no-default-features, --all-targets -D warnings) + cargo test.
Windows verify BLOCKED environmentally: pf-client-core -> sdl3 build-from-source
-> CMake/CL.exe fails on winbox's non-ASCII home path (fails the baseline too,
independent of this split); the split's Windows surface (facade cfg(windows) bits
+ video_d3d11) is verbatim-preserved.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
2026-07-17 14:06:57 +02:00
enricobuehler e8b64ffe43 Merge branch 'feat/shared-clipboard-v2': shared clipboard Phase 1 (wire + pf-clipboard host backends + macOS client)
audit / bun-audit (push) Successful in 13s
apple / swift (push) Successful in 1m19s
ci / web (push) Successful in 1m28s
audit / cargo-audit (push) Successful in 2m18s
ci / docs-site (push) Successful in 1m43s
docker / build-push (--build-arg FEDORA_VERSION=44, ci, ci/fedora-rpm.Dockerfile, punktfunk-fedora44-rpm) (push) Successful in 21s
decky / build-publish (push) Successful in 38s
docker / build-push (., web/Dockerfile, punktfunk-web) (push) Successful in 1m10s
docker / build-push (ci, ci/rust-ci.Dockerfile, punktfunk-rust-ci) (push) Successful in 15s
docker / build-push (docs-site, docs-site/Dockerfile, punktfunk-docs) (push) Successful in 1m15s
ci / bench (push) Successful in 5m25s
release / apple (push) Successful in 6m4s
docker / build-push (ci, ci/fedora-rpm.Dockerfile, punktfunk-fedora-rpm) (push) Successful in 6m46s
arch / build-publish (push) Successful in 11m22s
docker / deploy-docs (push) Successful in 31s
apple / screenshots (push) Successful in 4m47s
flatpak / build-publish (push) Failing after 8m1s
windows-host / package (push) Successful in 15m58s
android / android (push) Successful in 16m40s
deb / build-publish (push) Successful in 15m58s
windows-msix / package (arm64, C:\Users\Public\ffmpeg-arm64, --no-default-features, aarch64-pc-windows-msvc, C:\t-a64) (push) Successful in 4m15s
rpm / build-publish (43, bazzite, punktfunk-fedora-rpm) (push) Successful in 13m57s
ci / rust (push) Successful in 23m13s
rpm / build-publish (44, fedora-44, punktfunk-fedora44-rpm) (push) Successful in 13m53s
windows / build (aarch64-pc-windows-msvc) (push) Has been cancelled
windows / build (x86_64-pc-windows-msvc) (push) Has been cancelled
windows-msix / package (x64, C:\Users\Public\ffmpeg, , x86_64-pc-windows-msvc, C:\t) (push) Successful in 4m26s
2026-07-17 11:43:52 +00:00
enricobuehler ffa63a74f2 refactor(core/W7): split client.rs into client/ facade + submodules
Turn the 2674-line client.rs into a client/ directory module (mod.rs facade +
8 submodules) behind glob/`use self::` re-exports, so crate::client::X paths
(NativeClient, ProbeOutcome, AudioPacket, display_hdr_env_override) stay
byte-stable. Leaf lifts: frame_channel.rs (the FIFO hand-off + jump-to-live
consts + DecodeLatAcc), recovery.rs (RfiRecovery loss-range detector),
probe.rs (ProbeState/ProbeOutcome), planes.rs (side-plane queues + AudioPacket),
control.rs (CtrlRequest/Negotiated), worker.rs (WorkerArgs + reject_from_close),
pairing.rs (NativeClient::pair). The per-frame pump moves WHOLE as a plain
`pub(super) async fn run_pump` (was worker_main) — the only edit is the
signature line: no trait object, no Box, no per-frame allocation or indirection.
NativeClient + its public impl + Drop + the cfg-gated thread-pin/hot-tid helpers
stay in the facade. Visibility bumps are pub(crate) (struct + each field for
WorkerArgs/Negotiated/ProbeState; FrameChannel + each method); reject_from_close
is pub(crate) (sibling access). No behavior change.

Verified: Linux clippy (quic + no-default, -D warnings) + full cargo test;
Windows clippy (both) + test --lib; macOS clippy (apple thread-pin variant) +
165 lib tests. On-glass jump-to-live + ABR smoke still owed (pump is a pure
relocation, so this is a formality) per the plan's pump gate.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
2026-07-17 13:29:16 +02:00
enricobuehler 716875dd09 refactor(core/W7): split quic/msgs.rs into handshake/caps/control/pairing
Break the 1302-line quic/msgs.rs into four flat sibling modules behind the
quic facade's glob re-exports, so every crate::quic::X path stays byte-stable:
handshake.rs (Hello/Welcome/Start + codecs), caps.rs (video-cap bits, codec &
chroma negotiation, ColorInfo), control.rs (typed CTL_MAGIC messages + frame),
pairing.rs (SPAKE2 ceremony messages). msgs.rs is deleted; quic/mod.rs gains the
four `mod`/`pub use` lines and the `pub use crate::reject::*` hoist (moved up from
msgs.rs). Pure move; no wire-format or behavior change. Private helpers
(truncate_to, put_bytes, get_bytes) stay with their sole callers; no visibility
changes.

Verified both platforms from clean HEAD snapshots: Linux clippy (quic +
no-default, -D warnings) + full cargo test (157 lib + integration); Windows
clippy (both) + test --lib (156).

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
2026-07-17 13:08:45 +02:00
enricobuehler ef736cb9d7 refactor(core/W7): split transport/udp.rs into udp/ facade + per-OS backends
Turn transport/udp.rs into a udp/ directory module: the cross-platform core
(UdpTransport, is_transient_io, spawn_data_punch, the Transport trait impl) stays
in mod.rs; the platform batched-I/O backends move to udp/{linux,windows,apple}.rs.
The trait impl is kept whole -- its per-OS send_batch/send_gso/recv_batch methods
become cfg-gated one-line delegators to pub(super) free fns that take &UdpTransport
(byte-identical bodies, self -> t). transport/mod.rs is unchanged (re-exports still
resolve; udp/mod.rs re-exports windows::send_uso_all). No behavior change.

Module gates: linux = any(linux, android) (Android uses sendmmsg/recvmmsg via its
bionic binding); windows = windows (USO); apple = all(unix, not(any(linux,android)))
(recvmsg_x on Darwin, recv-loop on BSD). GSO stays linux-only.

Verified on all four target families from clean HEAD snapshots: Linux clippy
(quic + no-default, -D warnings) + full test; Windows clippy (both) + test --lib
(156); macOS check (apple recvmsg_x path); aarch64-linux-android check (android_mmsg).

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
2026-07-17 13:01:53 +02:00
enricobuehler 93c8dc4712 refactor(core/W7): split packet.rs into packet/ facade + submodules
Turn the 1446-line packet.rs into a packet/ directory module (mod.rs facade
+ header/packetize/reassemble/tests) behind glob re-exports, so every
crate::packet::X path stays byte-stable. Pure move: the header consts +
PacketHeader -> header.rs; Packetizer -> packetize.rs; the Reassembler cluster
(kept WHOLE -- disjoint-borrow hot path) + loss-window consts -> reassemble.rs;
the inline #[cfg(test)] block -> tests.rs. Sole visibility change:
LOSS_WINDOW_NS -> pub(super) (a test imports it). No behavior change.

Verified on both platforms from a clean HEAD snapshot: Linux clippy
(--features quic and --no-default-features, --all-targets -D warnings) + full
cargo test; Windows clippy (both feature sets) + cargo test --lib (156 pass).

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
2026-07-17 12:48:48 +02:00
enricobuehler f012ebbcba feat(sdk): Effect v4 + @effect/openapi-generator; typed pf.api & example ladder
Drop Orval for the first-party @effect/openapi-generator (OpenAPI 3.1 ->
Effect Schema + a typed HttpClient client) and bump effect 3.19 ->
4.0.0-beta.98. Port the hand-written surfaces to the v4 API (Result over
Either, Context.Service, Codec, Literals/Union arrays, Stream/Schedule/
Effect renames). Transport (CA-pinning fetch) and the reconnecting SSE
source are kept intact.

Make the SDK approachable for non-Effect users:
- Add pf.api.* on the Promise facade: the generated client surfaced as
  typed, Promise-native methods (await pf.api.listPairedClients()), so REST
  calls are autocompleted and checked instead of stringly-typed
  pf.request(method, path, body) + `as` casts. Zero-drift veneer over
  make(httpClient), backed by the same pinning fetch. pf.request stays as
  the untyped escape hatch.
- Re-tier examples into a 1-4 complexity ladder, rewritten onto pf.api.*
  (the typed payloads caught a wrong `launch` shape in provider-sync);
  the Effect example is labelled advanced. Add examples/ to tsconfig so
  they are typechecked (stops rot).

typecheck + 19 tests green.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
2026-07-17 12:40:04 +02:00
enricobuehler 27a5d8daac refactor(host/W6.2): extract virtual-display orchestration into the pf-vdisplay crate
vdisplay.rs + vdisplay/* (the per-compositor Linux backends — KWin zkde-screencast,
wlroots swaymsg, Mutter RemoteDesktop, Hyprland — and the Windows IddCx/pf-vdisplay
driver backend, behind one VirtualDisplay trait; the mode-conflict admission
registry, the display policy/identity/custom-preset state, and the session-env /
gamescope routing) move into crates/pf-vdisplay (plan §W6). The DDC/CI panel-power
control (used only here) and the KWin zkde protocol XML move with it. This
completes the host-crate decomposition: capture, encode, inject, and vdisplay are
now four subsystem crates over the shared leaves, and punktfunk-host is the
orchestrator (serve/supervisor + native + gamestream + mgmt).

Coupling breaks (all down-only, cargo-tree acyclic):
- capture::dxgi identity -> pf_frame::dxgi; win_display/monitor_devnode/
  console_session_mismatch -> pf-win-display leaf; can_open_another_session ->
  pf-encode (the NVENC session-budget admission gate — acyclic peer edge).
- The registry's DisplayCreated/DisplayReleased emits into the host SSE event bus
  invert to a leaf hook: pf-vdisplay emits a neutral DisplayEvent to a
  host-registered DISPLAY_EVENT_SINK, so it never reaches the orchestrator's
  events module.
- The IddCx driver module is renamed pf_vdisplay -> driver (its old name collided
  with the crate name through the host's `mod vdisplay` shim glob).

The host keeps `mod vdisplay { pub use pf_vdisplay::* }` so every crate::vdisplay::*
path (serve/mgmt/native/the capture FrameChannelSender seam) is unchanged; the
heavy deps (wayland/ashpd/tokio + the zkde protocol) moved with the crate.
Co-authored: a fail-closed IOCTL-reply-length security fix (reject short/zeroed
pf-vdisplay driver replies before trusting protocol_version/target_id/wudf_pid/luid,
security-review 2026-07-17) rides this commit in the moved driver module.

Verified: Linux clippy -D warnings (pf-vdisplay + host nvenc,vulkan-encode,pyrowave
--all-targets) + pf-vdisplay 63/63 + host 167/167 tests; Windows clippy -D warnings
(pf-vdisplay --all-targets + host nvenc,amf-qsv --all-targets) Finished exit 0.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
2026-07-17 12:14:08 +02:00
enricobuehler f6c6e4e594 refactor(host/W6.2): extract the input-injection backends into the pf-inject crate
inject.rs + inject/* (the per-OS injectors — wlroots virtual-input, KWin
fake_input, libei/reis, gamescope-EI on Linux; SendInput on Windows — plus the
virtual-gamepad HID stack: DualSense/DualShock4/Switch Pro/Steam Controller/Deck
over uhid/usbip and the Windows UMDF drivers, the proto codecs, the injector
service, and the uhid manager) move into crates/pf-inject behind the
InputInjector trait (plan §W6). It consumes punktfunk_core::input (the neutral
GamepadEvent/InputEvent vocabulary, moved to core in W5) + the pf-driver-proto
wire contract, and reaches pf-capture only for the Windows gamepad-channel
WUDFHost check + the resident-mouse compose-kick hook.

The one inject->vdisplay coupling (the libei gamescope-EI backend needs the EIS
relay socket path) is broken via a leaf: gamescope_ei_socket_file moves to
pf-paths as the shared contract — the gamescope producer (host vdisplay) keeps
its session-env-lock wrapper around it, the libei consumer (pf-inject) reads it
directly post-retarget. The host keeps a `mod inject { pub use pf_inject::* }`
shim so every crate::inject::* path (the native/gamestream input planes + devtest)
is unchanged; the heavy input deps (wayland/reis/xkbcommon/usbip + the KWin
fake-input protocol XML) moved with the crate.

Verified: Linux clippy -D warnings (pf-inject + host nvenc,vulkan-encode,pyrowave
--all-targets) + pf-inject 69/69 + host 230/230 tests; Windows clippy -D warnings
(pf-inject --all-targets + host nvenc,amf-qsv --all-targets) Finished exit 0.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
2026-07-17 11:52:02 +02:00
enricobuehler 0992548de7 feat(host/windows): HID compose kick — wake and dirty the display through the virtual mouse (lid-closed first-frame fix)
The final piece of the lid-closed field report (fixes 1-3: 3d9b3290;
prerequisites: 85dd2bb0 pf-mouse, 845a9760 leaf primitives, 94ca4041
pf-capture hook): the IDD first-frame gate can only pass if DWM composes
at least one frame, and a lid-closed/locked/idle machine is exactly the
state Windows has decided not to compose in. The SendInput compose kick
is conditional on this process's context — wrong session → wrong input
queue; secure desktop → blocked; display powered off → no wake. A report
from the resident pf-mouse HID device is REAL input to win32k:
session-independent, secure-desktop-proof, wakes a powered-off display,
counts as user presence. Parsec-class mechanism, and semantically honest
— a remote user starting a stream IS a user arriving at this machine.

- hid_kick(rect, bounds): newest-wins kick slot + condvar, serviced by
  the keeper thread that owns the ONE process-wide VirtualMouse (a
  second open() would squat the bootstrap mailbox). Not-ready (opt-out,
  driver missing, not yet attached) returns false → the capture crate
  falls back to SendInput.
- perform_kick: park the pointer at the target display's center, dwell
  35 ms (Stage-W3: DWM samples cursor position at the next vsync tick;
  the gaps also keep reports from coalescing in the driver's 8 ms
  timer), wiggle ~2 px, restore the saved position. Desktop→HID
  coordinates normalize against pf_win_display::desktop_bounds() (CCD
  union — correct from any session, unlike per-session GDI metrics).
- ensure_resident registers the hook (pf_capture::HID_COMPOSE_KICK) —
  the one-way-edge inversion: pf-capture never reaches into inject.
- keeper loop: condvar wait (250 ms tick) so a kick executes
  immediately, not at the next tick; publishes MOUSE_READY from
  driver_proto.

Paired with pf-frame's DisplayWakeRequest (held by the capturer from
before the first-frame gate): the power request keeps the display from
going dark mid-session, the HID kick wakes it when it already is.

Verified on winbox: combined-tree cargo check + clippy for
punktfunk-host, pf-capture, pf-frame, pf-win-display all EXIT 0.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
2026-07-17 11:30:32 +02:00
enricobuehler 94ca4041ca refactor(host/W6.2): extract the frame-capture backends into the pf-capture crate
capture/linux (PipeWire portal) + capture/windows (IDD direct-push: dxgi
mechanics, idd_push + submodules, synthetic_nv12) + pwinit move into
crates/pf-capture behind the Capturer trait + synthetic sources (plan §W6).
The crate speaks pf-frame (CapturedFrame/PixelFormat + the DXGI identity),
pf-zerocopy (CUDA import), and the pf-win-display leaves, and NEVER pf-encode —
the capture->encode edge is one-way. This completes the deliberate capture/encode
crate split (the invasive path the plan had merged into one pf-media): capture
and encode are now separate subsystem crates sharing only pf-frame.

Four seams keep the capturer off the orchestrator:
- VirtualOutput is EXPLODED into primitives (remote_fd/node_id/preferred_mode/
  keepalive) by the host facade, so pf-capture never depends on the vdisplay type;
- FrameChannelSender: the sealed-channel delivery is a Send+Sync closure the host
  facade builds from the pf-vdisplay control device + send_frame_channel IOCTL and
  hands in; ChannelBroker holds the closure instead of the control HANDLE (the
  whole-desktop handle-duplication security boundary is byte-for-byte unchanged);
- console_session_mismatch + desktop_bounds live in pf-win-display (leaf peers);
- pwinit moves here (audio caller -> pf_capture::pwinit).

The host keeps capture.rs as a thin BRIDGE: it re-exports the vocabulary + capturer
types (every crate::capture::* path is unchanged) and keeps open_portal_monitor /
capture_virtual_output, which resolve the ZeroCopyPolicy + FrameChannelSender and
call into pf-capture. verify_is_wudfhost + install_gpu_pref_hook are re-exported
(the gamepad-channel bootstrap + the main.rs subcommand consume them).

Co-developed: the resident-HID-mouse compose-kick hook (HID_COMPOSE_KICK + the
HID-first cursor kick + _display_wake) rides this commit into pf-capture; the host
mouse_windows registration side lands separately on top.

Verified: Linux clippy -D warnings (pf-capture + host nvenc,vulkan-encode,pyrowave
--all-targets) + host tests 299/299; Windows clippy -D warnings (pf-capture
--all-targets + host nvenc,amf-qsv --all-targets) Finished exit 0.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
2026-07-17 11:28:56 +02:00
enricobuehler 845a97601d feat(pf-frame,pf-win-display): leaf primitives for the lid-closed first-frame fix
Three leaf-crate additions the IDD-push capturer (pf-capture, plan §W6 C6)
builds on — committed ahead so the capture-crate extraction and the HID
compose kick can land on top:

- pf-frame session_tuning::DisplayWakeRequest — RAII PowerCreateRequest/
  PowerSetRequest(PowerRequestDisplayRequired + SystemRequired), the
  service-grade 'someone is watching this screen' assertion (visible in
  powercfg /requests), held for a capture session so the console cannot
  drop into display-off mid-stream. Object-lifetime, unlike the
  thread-bound ES_* flags in on_hot_thread. Prevention only: no power
  request turns an already-off display back on — that wake is input's
  job (the virtual-mouse compose kick).

- pf-win-display win_display::desktop_bounds() — the virtual-desktop
  bounds as the union of every ACTIVE CCD path's source rect. From the
  CCD database (global), NOT GetSystemMetrics (a per-session view), so
  a non-console-session host still aims HID absolute coordinates at the
  console's real layout.

- pf-win-display console_session_mismatch() — the session guard from
  3d9b3290, copied into the leaf so pf-capture reads it as a peer
  instead of reaching into the orchestrator (relocation authored by the
  W6 extraction session).

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
2026-07-17 11:17:29 +02:00
enricobuehler 85dd2bb077 feat(host/windows): resident virtual HID mouse (pf-mouse UMDF minidriver)
Headless Windows hosts (no dongle) stream an INVISIBLE cursor: with no
pointing device present win32k reports SM_MOUSEPRESENT=0 and DWM never
composites a pointer into the pf-vdisplay frame, even though SendInput
moves it. Keep ONE virtual HID mouse devnode alive for the host's
lifetime — the Sunshine/Parsec-class fix, zero client changes.

- pf-mouse: UMDF2 HID minidriver, one fixed identity (PF:MO 5046:4D4F,
  obviously virtual, nothing fingerprints it), one 8-byte input report
  (5 buttons + absolute 15-bit X/Y + wheel + AC-pan). Transport is the
  sealed pad channel verbatim (Global\pfmouse-boot-0 mailbox + unnamed
  MouseShm DATA section) so pf-umdf-util's audited layer serves it
  unchanged; report delivery is event-driven (idle = no HID traffic).
- host: inject::mouse_windows — VirtualMouse (SwDeviceCreate'd devnode +
  channel), ensure_resident() keeper thread started by every
  InjectorService (process-wide, PUNKTFUNK_NO_VIRTUAL_MOUSE opts out),
  vmouse-spike on-glass validation (cursor sweep via HID reports).
- proto: mouse module (magic, boot-name, identity, report layout,
  unit-tested input_report packing).
- SwDeviceProfile grows container_tag so the mouse's ContainerId family
  (PFMO) never groups with a pad's (PFDS) in the Devices UI.
- packaging: pf-mouse rides the gamepad-driver build + install pipeline
  (build-gamepad-drivers.ps1, windows-drivers.yml, driver install
  --gamepad picks up every staged .inf).

On-glass validated on winbox: devnode + HID child bind, SM_MOUSEPRESENT=1
with no physical mouse, cursor sweeps via HID reports (vmouse-spike).

This work was implemented in a parallel session; committed here as the
build prerequisite for the HID compose kick that follows.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
2026-07-17 11:02:10 +02:00
enricobuehler 3d9b329084 fix(host): name the lid-closed/no-frames failure — display-write decode, console-session guard, driver-truth attach diagnostics
Field report (Windows laptop, lid closed, Tailscale): v0.12.0's activation
fix works — the pf-vdisplay target activates in ~200ms — but the session
still dies at the first-frame gate: 'driver_status=1 but no frame published
within 4s'. Triage showed three independent blind spots; this names all of
them at their source instead of guessing downstream:

- pf-win-display: decode ChangeDisplaySettingsExW failures (-1 FAILED — a
  display write rejected, the wrong/remote-session signature — vs -2 BADMODE,
  which the old 'mode not advertised?' text conflated), and WARN on every
  non-zero SetDisplayConfig rc in the CCD isolate even when verification
  passes vacuously (the lid-closed case: nothing else active, so the INFO
  swallowed rc=0x5 ERROR_ACCESS_DENIED while the load-bearing COMMIT_MODES →
  ASSIGN_SWAPCHAIN re-commit silently never applied). Access-denied rcs get
  the remedy appended (console session / installed service).

- host: console-session guard (interactive::console_session_mismatch) — a
  host outside the active console session (a hand-launched host after an RDP
  round-trip) fails every display write, reads the wrong session's GDI view,
  and its SendInput compose kicks go nowhere. Named ERROR at vdisplay
  acquire + appended to the first-frame timeout, instead of the misleading
  generic failure. (The idd_push diagnosis half of this landed in 9a36ea21;
  this commit adds the proto helpers + session guard it references, healing
  the windows-cfg build.)

- proto + driver: while OPENED, driver_status_detail now carries a live
  packed word (bit31 live-marker | offered 15-bit | mismatch-dropped 16-bit)
  maintained by the publisher, so the host's first-frame timeout can tell
  apart: never-attached (no swap-chain worker ran), attached-but-DWM-composed-
  zero-frames (undamaged/powered-off desktop, kicks blocked on the secure
  desktop), and composed-but-every-frame-mismatched (ring sized from a stale/
  foreign-session GDI mode). Zero layout change, old drivers read as 'no
  detail'; unit-tested pack/unpack in pf-driver-proto.

Verified on winbox: cargo check + clippy -p punktfunk-host -p pf-win-display
-p pf-driver-proto EXIT 0, drivers ws cargo check -p pf-vdisplay EXIT 0
(Version_Number=10.0.26100.0), cargo fmt --all --check clean; pf-driver-proto
tests 13/13 pass locally.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
2026-07-17 10:47:47 +02:00
enricobuehler 9a36ea2132 refactor(host/W6.2): extract the video encode backends into the pf-encode crate
encode.rs + encode/* (NVENC, VAAPI, native AMF, AMF/QSV ffmpeg, direct-SDK
NVENC/CUDA, raw Vulkan-Video, PyroWave, openh264) move into crates/pf-encode
behind one Encoder trait + open_video selector (plan §W6). The crate speaks the
shared frame vocabulary (pf-frame: CapturedFrame/PixelFormat + the DXGI identity
D3d11Frame/make_device) and pf-zerocopy (CUDA context/buffers), and NEVER
pf-capture — the capture→encode edge is one-way (ZeroCopyPolicy, prior commit).

Dep moves: the heavy encoder deps (ffmpeg-next, the NVENC SDK, openh264,
pyrowave-sys) move from the host to pf-encode; the host's
nvenc/amf-qsv/vulkan-encode/pyrowave features now FORWARD to pf-encode/*. The
host keeps a mod-encode shim (pub use pf_encode) so every crate::encode::* path
(negotiator + GameStream/native/mgmt planes) is unchanged.

resolve_render_adapter_luid moves from the host's windows/win_adapter.rs into
pf-gpu (both pf-encode and pf-capture need it as a peer of GPU selection); its 5
call sites (encode amf/nvenc, capture idd_push/synthetic_nv12, vdisplay manager)
rewire to pf_gpu::resolve_render_adapter_luid and win_adapter.rs is deleted.
pf-frame's make_device gains a # Safety section (public-unsafe-fn lint, latent
since the pf-frame carve — a full-workspace -D warnings clippy catches it).

Verified: Linux clippy -D warnings (pf-encode + host nvenc,vulkan-encode,pyrowave
--all-targets) + 13/13 pf-encode + 299/299 host tests; Windows clippy -D warnings
(pf-encode nvenc,amf-qsv --all-targets + host nvenc,amf-qsv --all-targets)
Finished exit 0.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
2026-07-17 10:42:51 +02:00
enricobuehler 1de83ba51d refactor(host/W6.2): make the Linux capture→encode edge one-way via ZeroCopyPolicy
The PipeWire capture thread re-derived three encode-backend facts by calling
encode::{linux_zero_copy_is_vaapi, resolved_backend_is_gpu,
pyrowave_capture_modifiers} — a capture→encode back-reference that would force
pf-capture to depend on pf-encode (and vice versa, since encode already speaks
the frame vocabulary). Resolve them ONCE in the host capture facade (which may
reach crate::encode) as a ZeroCopyPolicy { backend_is_vaapi, backend_is_gpu,
pyrowave_modifiers } and thread it into PortalCapturer::open /
from_virtual_output → spawn_pipewire → pipewire_thread.

capture/linux/mod.rs now makes ZERO crate::encode calls — the edge is one-way
(plan §2.4 / §W6), so pf-capture can be extracted depending only on pf-frame
(not pf-encode). pyrowave_modifiers is computed by the facade whenever the
encoder pref is pyrowave (which implies the VAAPI backend); the thread still
consumes them only inside its existing vaapi_passthrough guard, so behavior is
unchanged.

Verified: Linux clippy -D warnings (host nvenc,vulkan-encode,pyrowave
--all-targets); Windows clippy nvenc,amf-qsv --all-targets Finished exit 0.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
2026-07-17 10:20:17 +02:00
enricobuehler ccc4b08d45 refactor(host/W6.2): extract the Windows display-topology cluster into the pf-win-display leaf crate
windows/{win_display,monitor_devnode,display_events}.rs move into
crates/pf-win-display: the CCD/GDI path-activation + mode-set + HDR
advanced-colour + source-rect helpers, the PnP monitor-devnode enable/disable
lever, and the WM_DISPLAYCHANGE / device-arrival watch. The coming pf-capture
crate's IDD-push capturer consumes all three; the host's pf-vdisplay backend
consumes win_display + monitor_devnode. A leaf lets both depend on them as a
PEER instead of the capturer reaching back into the orchestrator (plan §W6).

win_display's one external tie (crate::vdisplay::Mode) becomes the underlying
punktfunk_core::Mode; the cluster is otherwise self-contained (pf-paths for the
state file, serde_json for it, windows). pub(crate) items bump to pub at the
boundary; win_display carries a module-level allow(missing_safety_doc) to keep
the pre-carve behavior (the FFI helpers were pub(crate) unsafe fn with prose
safety docs — the lint only fires once they're pub, and this is an internal
publish=false leaf). The host imports the three modules at its crate root, so
every crate::{win_display,monitor_devnode,display_events}::* path is unchanged.

Verified: Linux clippy -D warnings (leaf empty + host
nvenc,vulkan-encode,pyrowave --all-targets); Windows clippy -D warnings
(pf-win-display --all-targets + host nvenc,amf-qsv --all-targets) Finished exit 0.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
2026-07-17 10:14:17 +02:00
enricobuehler b168790e0a refactor(host/W6.2): extract the shared frame/format vocabulary into the pf-frame leaf crate
The captured-frame types both capture (producer) and encode (consumer) speak —
PixelFormat, OutputFormat, CursorOverlay, CapturedFrame, FramePayload,
DmabufFrame, drm_fourcc — move into crates/pf-frame, alongside the small pure
helpers that ride the same seam: hdr (HDR static metadata / in-band SEI),
metronome (the metronomic-stall detector), thread_qos (per-thread scheduling
QoS), session_tuning (Windows process tuning), and the Windows DXGI capture
IDENTITY (WinCaptureTarget, D3d11Frame, pack_luid, make_device + the GPU
scheduling-priority hardening it applies) (plan §W6).

This is the crate that breaks the capture<->encode cycle: FramePayload's GPU
variants own their backends from BELOW (Cuda -> pf_zerocopy::DeviceBuffer,
D3d11 -> dxgi::D3d11Frame), so encode can speak the vocabulary without a path to
capture, and vice versa. The Windows DXGI identity moving here lets capture,
encode, and pf-vdisplay share ONE WinCaptureTarget/device factory instead of the
old capture<->encode<->vdisplay reach-in.

The host keeps thin facades: capture.rs re-exports the vocabulary
(crate::capture::{PixelFormat,…} unchanged); capture/windows/dxgi.rs keeps the
win32u GPU-preference hook + HDR/video-engine converters + self-test and
re-exports the identity; native.rs re-exports boost_thread_priority from
pf_frame. crate::hdr/metronome/session_tuning callers rewired to pf_frame::*.
metronome's Metronome::new gained a Default impl (new_without_default fires once
the type is public across the crate boundary).

Verified: Linux clippy -D warnings (pf-frame --all-targets + host
nvenc,vulkan-encode,pyrowave --all-targets) + 9/9 pf-frame tests; Windows clippy
nvenc,amf-qsv --all-targets Finished exit 0.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
2026-07-17 10:03:56 +02:00
enricobuehler 6824c1cc0c feat(core): Automatic bitrate climbs only through validated territory — utilization gate + proven-throughput cap
The controller's target is only a promise: on calm content the encoder
emits a fraction of it, every window looks clean while proving nothing,
and the climb drifts the target into rates the decoder has never seen.
The first motion spike is then the first real test — it fails, and the
decoder is overloaded for the two-window backoff latency (the reported
settle-calm-then-spike stutter).

Three changes, all client-side (no wire/ABI impact, old hosts unaffected):

- Climb gate: a clean window authorizes a climb only when its ACTUAL
  delivered throughput reached 3/4 of the current target — the target was
  genuinely tested. Calm windows still bank clean credit; the first
  loaded window after a clean run climbs immediately.
- Proven-throughput cap: climbs step at most x1.5 past the session's
  high-water mark of delivered-and-digested (decode-latency-flat)
  throughput, so slow start becomes a bounded experiment instead of a
  blind doubling. High-water never decays: calm periods keep a validated
  target, so returning motion gets the full rate instantly; shrinking
  capacity (thermals) stays the reactive decode signal's job.
- Severe decode excursion: a >45 ms-over-baseline decode spike backs off
  after ONE window instead of two — the overload is already on screen.

The pump feeds the window's wire-byte throughput; the byte baseline is
rebased when the startup capacity probe completes so FLAG_PROBE filler
can't poison the proven mark with the link rate.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
2026-07-17 09:55:09 +02:00
enricobuehler 85bc5b9a3f refactor(host/W6.2): extract the Linux zero-copy GPU plumbing into the pf-zerocopy leaf crate
linux/zerocopy/* (CUDA context/buffers + EGL/Vulkan dmabuf import + the isolated
import worker) and linux/dmabuf_fence.rs move wholesale into crates/pf-zerocopy,
so the coming pf-frame vocabulary crate (FramePayload::Cuda owns a DeviceBuffer)
and the pf-encode/pf-capture subsystem crates can reach the GPU plumbing without
the host orchestrator in between (plan §W6). Content stays Linux-only; the crate
compiles to an empty lib elsewhere, so dependents carry a plain dependency.

drm_fourcc deliberately does NOT move: it consumes the frame vocabulary
(PixelFormat), which sits ABOVE pf-zerocopy — it lives with capture for now and
moves into pf-frame next. cuda's ffi re-export bumps pub(crate)->pub (the raw
CUdeviceptr vocabulary is consumed across the crate boundary by the encode
backends). A crate::zerocopy shim module keeps every existing path valid until
capture/encode themselves move out.

Verified: Linux clippy -D warnings (pf-zerocopy --all-targets + host
nvenc,vulkan-encode,pyrowave --all-targets) + 17/17 pf-zerocopy tests + 321/321
host tests; Windows clippy nvenc,amf-qsv --all-targets Finished exit 0.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
2026-07-17 09:41:15 +02:00
enricobuehler 6ea036766a refactor(host/W6.1): extract GPU vendor/adapter detection into the pf-gpu leaf crate
Fourth de-coupling for the host crate carve (plan §W6.1 leaf). gpu.rs (inventory,
selection preference, active-session accounting — deps only pf-host-config + pf-paths, no
subsystem refs) moves to a new pf-gpu leaf so pf-encode/pf-capture/pf-vdisplay can consult
the selected GPU without an orchestrator edge. ~50 crate::gpu:: sites repoint to pf_gpu::;
the ~30 pub(crate) items become pub (crate API). assign_ids gets a macOS-only
allow(dead_code) (used only by the Linux/Windows enumerate arms).

Verified: Linux (home-worker-5) clippy -p pf-gpu -p punktfunk-host --all-targets
-D warnings + pf-gpu tests (12 pass); Windows (192.168.1.158) clippy --features
nvenc,amf-qsv --all-targets green.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
2026-07-17 09:00:53 +02:00
enricobuehler 3495d189e1 refactor(host/W6.1): extract the config() global into the pf-host-config leaf crate
Third de-coupling for the host crate carve (plan §W6.1 leaf). HostConfig + the config()
OnceLock (config.rs, pure std, zero deps) move to a new pf-host-config leaf so every
subsystem crate (pf-encode/pf-capture/pf-vdisplay/pf-gpu) can read process config WITHOUT
depending on the orchestrator. 34 crate::config::config() call sites across 19 files
repoint to pf_host_config::config(). thread_qos stays in the host for now (it calls
session_tuning::on_hot_thread — its own leaf-ification rides the encode carve).

Granular-crate decision (supersedes the plan's single pf-media): split capture/encode/
vdisplay into separate crates rather than one broad crate — the capture↔encode cycle is
broken by a shared frame-types leaf, and vdisplay→encode (can_open_another_session) is a
legal one-way edge since encode never references vdisplay.

Verified: Linux (home-worker-5) clippy -p pf-host-config -p punktfunk-host --all-targets
-D warnings; Windows (192.168.1.158) clippy --features nvenc,amf-qsv --all-targets green.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
2026-07-17 08:54:47 +02:00
enricobuehler c42ce88921 refactor(host/W6.1): extract secret/config-dir helpers into the pf-paths leaf crate
Second de-coupling for the host crate carve (plan §W6.1 leaf). config_dir /
create_private_dir / write_secret_file (+ the Windows DACL helpers) were pub(crate) in the
gamestream junk drawer, yet consumed by vdisplay, stats, gpu, library, mgmt_token,
native_pairing and the Windows service — many of which become pf-media / pf-vdisplay, for
which crate::gamestream would be an illegal upward edge. New leaf crate pf-paths (pure std
+ tracing) owns them; ~40 call sites across 14 files repoint to pf_paths::. gamestream
keeps only its own concerns.

Verified: Linux (home-worker-5) clippy -p pf-paths -p punktfunk-host --all-targets
-D warnings + tests (347 pass, incl. secrets_are_written_owner_only); Windows
(192.168.1.158) clippy --features nvenc,amf-qsv --all-targets green.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
2026-07-17 01:07:26 +02:00
enricobuehler 2e3208f75e refactor(host/W6.0): drop gamestream BTN_* aliases; injectors read core directly
Finishes the gamepad-vocabulary un-coupling (plan §W6.0): the Linux uinput button map now
names BTN_* straight from punktfunk_core::input::gamepad instead of the crate::gamestream
re-export aliases, so pf-inject will carry no edge into the gamestream junk drawer for
gamepad constants. Removes the now-dead alias block; the wire-bit pinning test
(gamepad_wire_bits_are_pinned) pins core directly (equally strong — core is the single
source). gamestream keeps only the decode path, which imports the types from core.

Verified: Linux (home-worker-5) clippy --all-targets -D warnings + gamepad tests green;
Windows (192.168.1.158) clippy -p punktfunk-host --features nvenc,amf-qsv --all-targets green.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
2026-07-17 00:55:44 +02:00
enricobuehler 6a0a97b702 Merge main into perf/first-frame-latency (controller fixes + Apple M0)
Keeps the latency branch current with 1a7e3a6e/b45323c0/4cae1b8b so the
eventual landing on main is a clean fast-forward-style merge.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
2026-07-16 18:35:20 +02:00
enricobuehler 45c29a99d5 perf(host+driver): in-place resize = advertised-mode fast path + mode-history union
On-glass round 2 settled the mechanism: after UpdateModes2 the OS re-parses
our description AND re-queries target modes (driver log — both callbacks
served the fresh list) yet the SETTABLE set stays pruned to the modes known
at monitor ARRIVAL; the monitor source-mode set is pinned then, below
anything the driver can refresh. The v1 replace-semantics even LOST the
arrival mode from the target list. Consequences:

- driver: UPDATE_MODES now UNIONs (new mode first, previous list kept,
  deduped by resolution, cap 12), and a re-created same-id monitor inherits
  its departed predecessor's list (MODE_HISTORY) — every size an identity
  ever served is settable at the next arrival, so returning to a
  previously-used size (windowed<->fullscreen, drag back) is IN-PLACE.
- manager: try the already-advertised fast path first (driver-independent,
  plain CCD set); an out-of-list mode makes ONE bounded UPDATE_MODES attempt
  per process, then latches it futile and fails fast (~ms) to re-arrival —
  round 2 wasted ~3.1 s per arbitrary resize on the doomed wait. Fallback
  log demoted warn->info (expected-normal for first-seen sizes).

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
2026-07-16 18:01:36 +02:00
enricobuehler a738de6cd8 fix(host): force a CCD mode re-enumeration after UPDATE_MODES (in-place resize)
First on-glass run: the driver accepted every UpdateModes2 (0x0 in the driver
log) but the OS never re-enumerated the target's settable modes on its own —
'OS did not advertise 800x1050 within 2s' → re-arrival fallback every time.
Re-commit the current config with SDC_FORCE_MODE_ENUMERATION (the same nudge
the isolate/layout paths already rely on) before the advertised-wait, re-kick
up to 3x, and log the actually-offered resolutions when it still misses.
Driver: dbglog the *2 mode-query/parse callbacks so the re-enumeration story
is visible in pfvd-driver.log.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
2026-07-16 17:48:44 +02:00
enricobuehler 55e59458a2 test(host): instrument the live resize spike (tracing + CCD-visibility probe)
On-glass finding: from an ssh/schtasks session-0 context QueryDisplayConfig
returns nothing at all — the activation ladder is blind there, so the live
tests can only run from an INTERACTIVE (desktop) admin prompt on the box;
the probe line makes that precondition self-diagnosing. Also verified live:
the v4 driver handshake ('pf-vdisplay protocol 4') and ADD on the new driver.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
2026-07-16 17:36:16 +02:00
enricobuehler f910d23fb2 fix(proto): drop the constant assertion clippy rejects (CI parity)
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
2026-07-16 17:18:06 +02:00
enricobuehler c95e9125b9 test(host): live in-place resize spike (PUNKTFUNK_PF_VDISPLAY_LIVE)
Answers the P2 open questions on real glass with no streaming client: a
second same-slot acquire at a different (never-advertised) mode drives the
manager's resize branch; in-place success = same OS target id + the new
active resolution, with the elapsed ms printed.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
2026-07-16 17:17:34 +02:00
enricobuehler c2b9b32904 perf(host): in-place mid-stream resize — mode-set the live monitor, keep the capturer
Latency plan P2.2/P2.3: against a v4 driver the manager's resize branch now
runs UPDATE_MODES -> wait-mode-advertised (the OS re-enumerates async) ->
set_active_mode -> verified-state settle (P0.2) on the SAME monitor — no
REMOVE->ADD hotplug, no departure settle, no activation ladder, no re-isolate;
Windows keeps the per-monitor DPI (identity preserved). Any failure (v3
driver, mode never advertised, settle miss) falls back to the proven
re-arrival path unchanged.

On top of that the session's resize handler keeps the WHOLE capture pipeline:
the IDD-push capturer re-sizes its ring immediately (Capturer::resize_output —
no DescriptorPoller two-strike debounce, which stays for EXTERNAL changes),
the driver re-attaches and the mode-set full redraw provides the first frame;
only the encoder is swapped once the first new-size frame arrives
(open_video is ms-scale — P2.4 deliberately skipped). The capturer, send
thread and session transport all survive; every decline routes to the full
rebuild. Resize-trace stages (display_resized, ring_recreated,
first_new_frame, encoder_open) extend the P0.1 timeline.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
2026-07-16 17:12:14 +02:00
enricobuehler 0899e53903 feat(driver): pf-vdisplay IOCTL_UPDATE_MODES — live monitor mode-list refresh (proto v4)
Latency plan P2.1 (design/first-frame-and-resize-latency.md): a new additive
control-plane op lets the host refresh a LIVE monitor's advertised target-mode
list to lead with an arbitrary new mode (IddCxMonitorUpdateModes2 — the same
IddCx 1.10 *2 family this driver already requires, so no new OS floor). This
removes the 'mode list frozen at ADD' constraint that forced the mid-stream
resize through a REMOVE->ADD monitor hotplug: the monitor's OS identity, its
swap-chain worker and the retained FrameStash all survive an in-place mode set.

Protocol v4 is ADDITIVE over v3: the host's handshake floor stays at v3
(MIN_DRIVER_PROTOCOL_VERSION) and gates the in-place path on the reported
version, keeping re-arrival as the permanent fallback. The driver's stored
mode list is swapped before the DDI and reverted if it fails, so the OS and
the mode-DDI callbacks always agree.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
2026-07-16 17:12:13 +02:00
enricobuehler 32ffe7d634 chore(api): regenerate openapi.json (transition-latency fields + held drift)
Adds StreamInfo.time_to_first_frame_ms / last_resize_ms (latency plan P0.1)
and folds in the drift the spec already owed from the held working-tree
consolidation (version 0.12.0, pnp_disable_monitors description, the
conflicting-host 'conflicts' summary field) — the drift test was already
red before this branch; it is green at this commit.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
2026-07-16 16:48:35 +02:00
enricobuehler 8374dfedf3 perf(host): session-transition trace + Welcome-time display prep (native path)
Latency plan P0.1 + P1.1/P1.2 (design/first-frame-and-resize-latency.md):

P0.1 — every native session runs a bringup::Trace (hello -> welcome -> start
-> punch_done -> display_acquired -> capture_attached -> first_frame ->
encoder_open -> first_au -> first_packet), one summary info! line when the
first video packet leaves; each accepted resize runs its own trace
(reconfigure -> pipeline_rebuilt). Totals surface per session as
time_to_first_frame_ms / last_resize_ms in session_status -> mgmt /status,
so every subsequent latency change is measured, not vibed. (The Windows
manager logs its own activation/settle deltas — correlate by wall clock.)

P1.1/P1.2 — on the Windows native path the display bring-up no longer
serializes behind the Start round-trip and the up-to-2.5 s hole-punch wait:
a prep thread kicks off at Welcome (mode is final there) and runs monitor
create -> activation -> verified settle -> capture attach -> first frame ->
encoder open while the network waits are in flight; the data plane hands it
the post-punch SessionContext and it becomes the stream thread on a warm
pipeline. Abort between Welcome and Start drops the hand-off channel and the
prep result releases into the keep-alive machinery (stop/quit + watcher are
created pre-handshake so a vanished client also aborts the build retries).
Same slot-scoped begin_idd_setup serialization as the inline path. Linux
keeps the inline bring-up (launch semantics bind before create); GameStream
untouched.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
2026-07-16 16:45:34 +02:00
enricobuehler e62cd5448e perf(host): IDD-push open — poll the HDR-enable settle, wait on the frame event
Latency plan P0.4/P0.6: the fixed 250 ms advanced-color settle becomes a
25 ms poll of the CCD state (ceiling 250 ms, ring still sized FP16 from the
successful enable either way), and wait_for_attach waits on the driver's
frame-ready event (20 ms cap for the status-code polls) instead of a blind
20 ms sleep, which also sharpens the P0.1 stage stamps.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
2026-07-16 16:45:16 +02:00
enricobuehler 4ed5b88407 perf(host): replace the Windows bring-up/resize fixed sleeps with verified-state waits
Latency plan P0.2/P0.3/P0.5 (design/first-frame-and-resize-latency.md):
- topology settle: the unconditional 1500 ms sleeps after create_monitor's
  group-topology apply and re_add's reisolate become a 25 ms poll for the
  committed state (active path + active mode == requested), ceiling 1500 ms —
  worst case identical, typical case saves ~1.2-1.4 s on every fresh create
  AND every mid-stream resize. The experimental pnp_disable_monitors sweep
  keeps the full settle as its floor (it reads OTHER displays' active flags,
  which the target-scoped wait doesn't verify).
- monitor departure: the fixed 400 ms REMOVE settles (re_add + both preempt
  paths) become a 25 ms poll until the target leaves the active CCD set
  (2 consecutive absent samples), ceiling 400 ms; the driver-side ghost-reap
  ADD retry stays the backstop.
- activation ladder: 200 ms -> 50 ms sampling, same 3 s per-stage ceilings
  and the same 3-stage structure.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
2026-07-16 16:44:57 +02:00
enricobuehler 44b71e7460 feat(audio/linux): host-owned stream sink decouples capture from hardware-sink churn
The Linux desktop-audio capture stream now registers itself as an Audio/Sink
node ("Punktfunk Stream Speaker", default-on, PUNKTFUNK_STREAM_SINK=0 =
legacy escape hatch) and claims the configured default sink for the duration
of a session (saved and restored around it, refcounted across concurrent
sessions, crash-stale claims degrade to automatic election). Host apps play
directly into the capture stream, so the capture link no longer depends on
any hardware sink.

Root cause this fixes (live-diagnosed on a bazzite/LG-TV host): gamescope
modesets drop the NVIDIA HDMI audio endpoint, WirePlumber ping-pongs the
default sink HDMI<->auto_null ~8x/s, and the old monitor-follower relinked
its capture on every flip - Paused/renegotiate/Streaming storms (~1300 log
lines/min) heard as crackle on the client.

Also fixes a latent liveness bug in both modes: the capture thread had no
core-error listener, so a PipeWire daemon restart mid-session left a zombie
thread returning quiet-sink empty chunks forever (same class as the historic
virtual-mic death bug). Now the thread exits and sessions reopen with backoff.

Bonus: the sink advertises the session's true channel count, so games can
produce real 5.1/7.1 even when local hardware is stereo. New AudioCapturer::
idle() hook releases the routing claim when a capturer is parked between
sessions; drain() re-claims on reuse.

Verified: cargo clippy -D warnings + 295 tests green on Linux (.21).
On-glass validation on the bazzite host pending.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
2026-07-14 09:34:36 +02:00
433 changed files with 58987 additions and 18278 deletions
+5 -4
View File
@@ -1,4 +1,4 @@
# Build the punktfunk-host / punktfunk-client / punktfunk-web pacman packages from
# Build the punktfunk-host / punktfunk-client / punktfunk-web / punktfunk-scripting pacman packages from
# packaging/arch/PKGBUILD and publish them to Gitea's Arch package registry, so Arch boxes
# get new builds via `pacman -Syu`. Counterpart to deb.yml (apt) and rpm.yml (dnf/rpm-ostree).
# Arch is rolling, so the packages build against whatever the archlinux:base-devel container
@@ -45,8 +45,9 @@ jobs:
git nodejs rust clang cmake nasm pkgconf python vulkan-headers \
gtk4 libadwaita sdl3 ffmpeg pipewire wayland libxkbcommon opus libei \
mesa libglvnd unzip libarchive
# bun builds the punktfunk-web console AND is vendored as its runtime (PF_WITH_WEB=1);
# it's AUR-only on Arch, so bootstrap the official binary.
# bun builds the punktfunk-web console + the punktfunk-scripting runner AND is vendored as
# their runtime (PF_WITH_WEB=1 / PF_WITH_SCRIPTING=1); it's AUR-only on Arch, so bootstrap
# the official binary.
command -v bun >/dev/null || {
curl -fsSL https://bun.sh/install | bash
install -m0755 "$HOME/.bun/bin/bun" /usr/local/bin/bun
@@ -105,7 +106,7 @@ jobs:
sudo -u builder git config --global --add safe.directory "$PWD"
mkdir -p dist && chown builder: dist
cd packaging/arch
sudo -u builder env PF_SRCDIR="$GITHUB_WORKSPACE" PF_WITH_WEB=1 \
sudo -u builder env PF_SRCDIR="$GITHUB_WORKSPACE" PF_WITH_WEB=1 PF_WITH_SCRIPTING=1 \
PF_PKGVER="$PF_PKGVER" PF_PKGREL="$PF_PKGREL" \
CARGO_HOME="$CARGO_HOME" PKGDEST="$GITHUB_WORKSPACE/dist" \
makepkg -f -d --holdver
+2
View File
@@ -132,6 +132,8 @@ jobs:
VERSION="$VERSION" bash packaging/debian/build-client-deb.sh
# Reuse CI's bun for the vendored runtime (matches the amd64 runner) instead of downloading.
VERSION="$VERSION" BUN_BIN="$(command -v bun || true)" bash packaging/debian/build-web-deb.sh
# The plugin/script runner (bun-bundled Effect SDK) — same vendored-bun mechanics.
VERSION="$VERSION" BUN_BIN="$(command -v bun || true)" bash packaging/debian/build-scripting-deb.sh
- name: Publish to the Gitea apt registry
env:
+41 -12
View File
@@ -258,9 +258,12 @@ jobs:
# sdk-scoped one (iOS/tvOS) lands on it and fails the archive ("does not support
# provisioning profiles"). Automatic signing assigns a profile only to the app and leaves
# the resource bundle (and the macOS-host macro plugins) alone, and bakes the sandbox
# entitlements in. No -allowProvisioningUpdates → it stays OFFLINE and never cloud-signs
# (the App-Manager ASC key can't), so the runner must have a macOS *development* profile
# for io.unom.punktfunk installed. DISTRIBUTION signing happens in the export step below
# entitlements in. -allowProvisioningUpdates lets Xcode sync the App ID capabilities and
# regenerate the managed *development* profile — needed because the App Groups capability
# (group.io.unom.punktfunk, in Config/Punktfunk-macOS.entitlements) invalidated the cached
# one. This is DEVELOPMENT signing against the Apple Development cert already in the
# keychain, so the App-Manager ASC key suffices. DISTRIBUTION signing happens in the export
# step below
# (manual, via the plist). Quit Xcode so it can't prune the manually-installed App Store
# distribution profile that export needs.
osascript -e 'tell application "Xcode" to quit' >/dev/null 2>&1 || true
@@ -271,6 +274,10 @@ jobs:
-destination 'generic/platform=macOS' \
-archivePath "$RUNNER_TEMP/Punktfunk-macos-appstore.xcarchive" \
-skipMacroValidation -skipPackagePluginValidation \
-allowProvisioningUpdates \
-authenticationKeyPath "$RUNNER_TEMP/asc.p8" \
-authenticationKeyID "${{ secrets.ASC_API_KEY_ID }}" \
-authenticationKeyIssuerID "${{ secrets.ASC_API_ISSUER_ID }}" \
MARKETING_VERSION="$VERSION" CURRENT_PROJECT_VERSION="$BUILD_NUM" \
CODE_SIGN_STYLE=Automatic \
DEVELOPMENT_TEAM="$TEAM_ID"
@@ -308,19 +315,32 @@ jobs:
# license screens) builds for iphoneos, so even the sdk-scoped PROVISIONING_PROFILE_SPECIFIER
# this step used to set matched it and failed the archive ("does not support provisioning
# profiles"). Automatic signing profiles only the app and leaves the resource bundle (and
# the macOS-host macro plugins) alone. No -allowProvisioningUpdates → OFFLINE, never
# cloud-signs (the App-Manager ASC key can't), so the runner needs an iOS *development*
# profile for io.unom.punktfunk installed. DISTRIBUTION signing is the export step below
# (manual, via the plist). A running Xcode.app prunes unrecognized profiles — quit it so the
# manually-installed App Store distribution profile survives for export.
# the macOS-host macro plugins) alone. -allowProvisioningUpdates lets Xcode sync the App ID
# capabilities and regenerate the managed *development* profiles for both io.unom.punktfunk
# AND the embedded io.unom.punktfunk.widgets — needed because adding the App Groups
# capability (group.io.unom.punktfunk, shared with the Widget/Live-Activity extension)
# invalidated the cached managed dev profile, which had no widgets profile at all. This is
# DEVELOPMENT signing against the Apple Development cert already in the keychain — no cert
# creation, so the App-Manager ASC key is sufficient (it only manages App IDs/dev profiles).
# DISTRIBUTION signing is the export step below (manual, via the plist) and is unaffected.
# A running Xcode.app prunes unrecognized profiles — quit it so the manually-installed
# App Store distribution profile survives for export.
osascript -e 'tell application "Xcode" to quit' >/dev/null 2>&1 || true
pkill -x Xcode 2>/dev/null || true
PROFILE="Punktfunk iOS App Store Distribution"
# The embedded PunktfunkWidgetsExtension (bundle io.unom.punktfunk.widgets) is a second
# distribution artifact in the .ipa, so manual signing must map its App ID to its own
# App Store profile too — else exportArchive fails ("no profile for io.unom.punktfunk.widgets").
WIDGET_PROFILE="Punktfunk iOS Widgets App Store Distribution"
DEVELOPER_DIR="$XCODE_DEV_DIR" xcodebuild archive \
-project "$PROJECT" -scheme Punktfunk-iOS \
-destination 'generic/platform=iOS' \
-archivePath "$RUNNER_TEMP/Punktfunk-ios.xcarchive" \
-skipMacroValidation -skipPackagePluginValidation \
-allowProvisioningUpdates \
-authenticationKeyPath "$RUNNER_TEMP/asc.p8" \
-authenticationKeyID "${{ secrets.ASC_API_KEY_ID }}" \
-authenticationKeyIssuerID "${{ secrets.ASC_API_ISSUER_ID }}" \
MARKETING_VERSION="$VERSION" CURRENT_PROJECT_VERSION="$BUILD_NUM" \
CODE_SIGN_STYLE=Automatic \
DEVELOPMENT_TEAM="$TEAM_ID"
@@ -335,7 +355,10 @@ jobs:
<key>signingStyle</key><string>manual</string>
<key>signingCertificate</key><string>Apple Distribution</string>
<key>provisioningProfiles</key>
<dict><key>io.unom.punktfunk</key><string>$PROFILE</string></dict>
<dict>
<key>io.unom.punktfunk</key><string>$PROFILE</string>
<key>io.unom.punktfunk.widgets</key><string>$WIDGET_PROFILE</string>
</dict>
</dict>
</plist>
EOF
@@ -359,9 +382,11 @@ jobs:
# resource bundle (PunktfunkKit_PunktfunkKit) builds for appletvos and rejected the
# sdk-scoped profile this step used to set; Automatic signing profiles only the app and
# leaves the resource bundle + the macOS-host macro plugins (OnceMacro/SwizzlingMacro/
# AssociationMacro) alone. No -allowProvisioningUpdates → OFFLINE, never cloud-signs (the
# App-Manager ASC key can't), so the runner needs a tvOS *development* profile for
# io.unom.punktfunk installed. DISTRIBUTION signing is the export step below (manual, plist).
# AssociationMacro) alone. -allowProvisioningUpdates lets Xcode sync the App ID capabilities
# and regenerate the managed *development* profile — the tvOS app carries the App Groups key
# (group.io.unom.punktfunk) too, which invalidated the cached one. DEVELOPMENT signing against
# the Apple Development cert already in the keychain, so the App-Manager ASC key suffices.
# DISTRIBUTION signing is the export step below (manual, plist).
osascript -e 'tell application "Xcode" to quit' >/dev/null 2>&1 || true
pkill -x Xcode 2>/dev/null || true
PROFILE="Punktfunk tvOS App Store Distribution"
@@ -370,6 +395,10 @@ jobs:
-destination 'generic/platform=tvOS' \
-archivePath "$RUNNER_TEMP/Punktfunk-tvos.xcarchive" \
-skipMacroValidation -skipPackagePluginValidation \
-allowProvisioningUpdates \
-authenticationKeyPath "$RUNNER_TEMP/asc.p8" \
-authenticationKeyID "${{ secrets.ASC_API_KEY_ID }}" \
-authenticationKeyIssuerID "${{ secrets.ASC_API_ISSUER_ID }}" \
MARKETING_VERSION="$VERSION" CURRENT_PROJECT_VERSION="$BUILD_NUM" \
CODE_SIGN_STYLE=Automatic \
DEVELOPMENT_TEAM="$TEAM_ID"
+6 -4
View File
@@ -92,9 +92,10 @@ jobs:
echo "rpm $V-$R -> group '$GROUP'"
- name: Build RPM
# PF_WITH_WEB=1 → also build the noarch punktfunk-web subpackage (the publish loop below
# globs it in; the host RPM Recommends it). Needs bun (ensured in Prep).
run: PF_VERSION="$PF_VERSION" PF_RELEASE="$PF_RELEASE" PF_WITH_WEB=1 bash packaging/rpm/build-rpm.sh
# PF_WITH_WEB=1 / PF_WITH_SCRIPTING=1 → also build the punktfunk-web console + the
# punktfunk-scripting runner subpackages (the publish loop globs them in; the host RPM
# Recommends both). Both need bun (ensured in Prep).
run: PF_VERSION="$PF_VERSION" PF_RELEASE="$PF_RELEASE" PF_WITH_WEB=1 PF_WITH_SCRIPTING=1 bash packaging/rpm/build-rpm.sh
- name: Sign RPMs (dormant until RPM_GPG_PRIVATE_KEY is set — see packaging/rpm/README.md)
env:
@@ -131,7 +132,8 @@ jobs:
bash packaging/bazzite/build-sysext.sh --version-id "${{ matrix.fedver }}" \
--out "dist-sysext/punktfunk-${PF_VERSION}-${PF_RELEASE}-x86-64.raw" \
dist/punktfunk-"${PF_VERSION}-${PF_RELEASE}"*.rpm \
dist/punktfunk-web-"${PF_VERSION}-${PF_RELEASE}"*.rpm
dist/punktfunk-web-"${PF_VERSION}-${PF_RELEASE}"*.rpm \
dist/punktfunk-scripting-"${PF_VERSION}-${PF_RELEASE}"*.rpm
- name: Publish the sysext feed
env:
+60
View File
@@ -0,0 +1,60 @@
# Publish the TypeScript SDK (@punktfunk/host) to the Gitea npm registry
# (https://git.unom.io/api/packages/unom/npm/).
#
# Trigger: push a tag `sdk-vX.Y.Z` (must equal sdk/package.json "version"), or run manually.
# The SDK versions independently of the app's `v*` tags, so bumping the host doesn't republish it.
#
# Auth: REGISTRY_TOKEN — the same repo Actions secret docker.yml uses (a Gitea PAT with
# write:package scope). No new secret needed.
name: sdk-publish
on:
push:
tags: ['sdk-v*']
workflow_dispatch:
jobs:
publish:
runs-on: ubuntu-24.04
container:
image: oven/bun:1
timeout-minutes: 15
defaults:
run:
working-directory: sdk
steps:
# oven/bun's slim base ships neither git, a CA bundle, nor node — actions/checkout's HTTPS
# fetch needs git + ca-certificates, and the version-guard step below uses node.
- name: Install git + node + CA certs
working-directory: /
run: apt-get update && apt-get install -y --no-install-recommends ca-certificates git nodejs
- uses: actions/checkout@v4
- name: Install dependencies
run: bun install --frozen-lockfile --ignore-scripts
- name: Typecheck
run: bun run typecheck
- name: Test
run: bun test
- name: Build (dist/ JS + .d.ts)
run: bun run build
- name: Tag matches package version
if: startsWith(github.ref, 'refs/tags/')
run: |
TAG="${GITHUB_REF_NAME#sdk-v}"
PKG="$(node -p "require('./package.json').version")"
test "$TAG" = "$PKG" || { echo "tag $GITHUB_REF_NAME does not match package version $PKG"; exit 1; }
- name: Publish to Gitea registry
env:
NODE_AUTH_TOKEN: ${{ secrets.REGISTRY_TOKEN }}
run: |
test -n "$NODE_AUTH_TOKEN" || { echo "REGISTRY_TOKEN secret is empty"; exit 1; }
# .npmrc already maps the @punktfunk scope to the registry; append the auth line.
printf '//git.unom.io/api/packages/unom/npm/:_authToken=%s\n' "$NODE_AUTH_TOKEN" >> .npmrc
bun publish
+3 -3
View File
@@ -153,9 +153,9 @@ jobs:
# `// SAFETY:` proof. Both invariants are lint-gated (`unsafe_op_in_unsafe_fn` +
# `undocumented_unsafe_blocks`); this step keeps them from regressing. (wdk-probe is a
# toolchain-only probe crate and is excluded.)
run: cargo clippy -p pf-umdf-util -p pf-xusb -p pf-dualsense -p wdk-iddcx -p pf-vdisplay --all-targets -- -D warnings
- name: cargo fmt --check the safe-layer + gamepad drivers
run: cargo fmt -p pf-umdf-util -p pf-xusb -p pf-dualsense --check
run: cargo clippy -p pf-umdf-util -p pf-xusb -p pf-dualsense -p pf-mouse -p wdk-iddcx -p pf-vdisplay --all-targets -- -D warnings
- name: cargo fmt --check the safe-layer + gamepad/mouse drivers
run: cargo fmt -p pf-umdf-util -p pf-xusb -p pf-dualsense -p pf-mouse --check
- name: Inspect /INTEGRITYCHECK (before) — expect FORCE_INTEGRITY set by wdk-build
run: |
# explicit --target (.cargo/config.toml) -> output under the triple subdir.
+43 -7
View File
@@ -1,7 +1,8 @@
# Build the punktfunk Windows HOST as a signed Inno Setup installer and publish it to Gitea's generic
# package registry, so a Windows GPU box can install the streaming host (SYSTEM service + bundled
# pf-vdisplay virtual-display driver + the web management console, run by a scheduled task on a bundled
# bun) from one signed setup.exe. Runs on a self-hosted windows-amd64 runner
# pf-vdisplay virtual-display driver + the web management console + the opt-in plugin/script runner,
# run by scheduled tasks on a bundled bun) from one signed setup.exe. Runs on a self-hosted
# windows-amd64 runner
# (host mode; same MSVC/Windows-SDK/LLVM env as windows.yml — generic from unom/infra's
# windows-runner/, FFmpeg/Inno Setup self-provision via the "Ensure Windows toolchain" step below).
#
@@ -23,10 +24,15 @@
# an ephemeral self-signed cert is generated and its public .cer published next to the installer
# (import once to LocalMachine\TrustedPublisher). See packaging/windows/pack-host-installer.ps1.
#
# GPU backends: the host builds with --features nvenc,amf-qsv = all three vendors in one installer.
# GPU backends: the host builds with --features nvenc,amf-qsv,qsv = all three vendors in one installer.
# - NVENC (NVIDIA, direct SDK): nothing needed at build time — the entry points are resolved at
# RUNTIME from the driver's nvEncodeAPI64.dll (a link-time import would kill the binary on
# AMD/Intel-only boxes before main).
# - QSV native (Intel, VPL — design/native-qsv-encoder.md): the MIT dispatcher is built from the
# vendored tree (libvpl-sys, cmake+bindgen — LIBCLANG_PATH already in the runner env for
# pyrowave-sys) and statically linked; the GPU runtime comes from the Intel driver store at run
# time, so no new DLL ships and non-Intel boxes are unaffected. This is the Intel dispatch;
# the ffmpeg *_qsv path below stays as its open-failure fallback until Phase 4 deletes it.
# - AMF/QSV (AMD/Intel, libavcodec): link-imports the FFmpeg libs from FFMPEG_DIR (the BtbN lgpl-shared
# tree the client uses; includes the *_amf/*_qsv encoders) and bundles its DLLs into the installer.
# lgpl-shared (not gpl-shared) keeps those bundled DLLs LGPL (we never use the GPL-only x264/x265).
@@ -40,9 +46,14 @@ on:
- 'crates/punktfunk-host/**'
- 'crates/punktfunk-core/**'
- 'crates/punktfunk-tray/**'
# The encode subsystem (split out in W6) + the vendored VPL dispatcher the `qsv` feature
# builds — without these, encoder changes only reached this workflow via Cargo.lock luck.
- 'crates/pf-encode/**'
- 'crates/libvpl-sys/**'
- 'packaging/windows/**'
- 'scripts/windows/**'
- 'web/**'
- 'sdk/**'
- 'Cargo.lock'
- 'Cargo.toml'
- '.gitea/workflows/windows-host.yml'
@@ -89,6 +100,12 @@ jobs:
# (pwsh Out-File utf8 = no BOM, unlike Windows PowerShell 5.1 — keeps the first line clean).
"CARGO_TARGET_DIR=C:\t" | Out-File -FilePath $env:GITHUB_ENV -Append -Encoding utf8
"CARGO_WORKSPACE_DIR=$env:GITHUB_WORKSPACE" | Out-File -FilePath $env:GITHUB_ENV -Append -Encoding utf8
# audiopus_sys' vendored opus declares cmake_minimum_required < 3.5, which CMake 4.x
# refuses outright. Green runs today only survive on the cached configure output — a
# target-dir purge (the runner's disk-cleanup task) would fail the fresh configure, as
# observed on a clean build on this very runner (2026-07-17). No-op for compliant
# projects (libvpl-sys pins 3.13+).
"CMAKE_POLICY_VERSION_MINIMUM=3.5" | Out-File -FilePath $env:GITHUB_ENV -Append -Encoding utf8
# FFMPEG_DIR: the same BtbN lgpl-shared x64 tree the Windows CLIENT links against (provisioned
# by scripts/ci/provision-windows-punktfunk-extras.ps1). The host's AMD/Intel AMF/QSV encode backend
# (--features amf-qsv) link-imports avcodec/avutil/swscale from it; pack-host-installer.ps1
@@ -114,10 +131,11 @@ jobs:
"PUNKTFUNK_BUILD_VERSION=$v" | Out-File -FilePath $env:GITHUB_ENV -Append -Encoding utf8
Write-Output "host version $v"
- name: Build (release, nvenc + amf-qsv)
- name: Build (release, nvenc + amf-qsv + qsv)
shell: pwsh
# All-vendor host: NVENC (NVIDIA, direct SDK) + AMF/QSV (AMD/Intel, libavcodec via FFMPEG_DIR).
run: cargo build --release -p punktfunk-host --features nvenc,amf-qsv
# All-vendor host: NVENC (NVIDIA, direct SDK) + native QSV (Intel, static VPL dispatcher)
# + AMF/QSV (AMD + the Intel ffmpeg fallback, libavcodec via FFMPEG_DIR).
run: cargo build --release -p punktfunk-host --features nvenc,amf-qsv,qsv
- name: Build (release, status tray)
shell: pwsh
@@ -128,7 +146,7 @@ jobs:
shell: pwsh
# First-ever Windows lint coverage for the host (Linux CI never lints the windows-cfg code).
run: |
cargo clippy -p punktfunk-host --features nvenc,amf-qsv -- -D warnings; if ($LASTEXITCODE) { throw "host clippy" }
cargo clippy -p punktfunk-host --features nvenc,amf-qsv,qsv -- -D warnings; if ($LASTEXITCODE) { throw "host clippy" }
cargo clippy -p punktfunk-tray -- -D warnings; if ($LASTEXITCODE) { throw "tray clippy" }
- name: Build + lint the HDR Vulkan layer (pf-vkhdr-layer)
@@ -196,6 +214,24 @@ jobs:
if ($code -ne 200) { throw "web console failed to boot under bun" }
"WEB_OUTPUT_DIR=$((Resolve-Path 'web\.output').Path)" | Out-File -FilePath $env:GITHUB_ENV -Append -Encoding utf8
- name: Build plugin/script runner bundle (bun)
shell: pwsh
# `bun build --target=bun` bundles the SDK's runner CLI to ONE self-contained JS (effect + the
# SDK inlined; the dynamic plugin import stays a runtime import). pack-host-installer.ps1 ships
# it (+ the shared bun) and registers its scheduled task DISABLED (opt-in). The SDK's deps are
# public npm (effect), so no @unom token is needed here.
run: |
$bun = $env:BUN_EXE
Push-Location sdk
& $bun install --frozen-lockfile --ignore-scripts; if ($LASTEXITCODE) { throw "sdk bun install failed ($LASTEXITCODE)" }
New-Item -ItemType Directory -Force -Path C:\t\scripting | Out-Null
& $bun build src/runner-cli.ts --target=bun --outfile=C:\t\scripting\runner-cli.js; if ($LASTEXITCODE) { throw "runner bundle build failed ($LASTEXITCODE)" }
Pop-Location
if (-not (Select-String -Path C:\t\scripting\runner-cli.js -Pattern 'attempt=' -Quiet)) {
throw "runner bundle missing the dynamic plugin import - wrong build"
}
"SCRIPTING_BUNDLE=C:\t\scripting\runner-cli.js" | Out-File -FilePath $env:GITHUB_ENV -Append -Encoding utf8
- name: Pack + sign installer
shell: pwsh
env:
+4
View File
@@ -43,3 +43,7 @@ CLAUDE.md
/result
/result-*
.direnv/
# Gradle build output inside the vendored pyrowave Granite Android platform (regenerated, never ours to commit)
/crates/pyrowave-sys/vendor/pyrowave/Granite/application/platforms/android/**/.gradle/
/crates/pyrowave-sys/vendor/pyrowave/Granite/application/platforms/android/**/build/
Generated
+296 -56
View File
@@ -358,28 +358,6 @@ version = "1.5.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "f2032f911046de80f0a198e0901378627c33f59ea0ac00e363d481118bd70a53"
[[package]]
name = "aws-lc-rs"
version = "1.17.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "5ec2f1fc3ec205783a5da9a7e6c1509cc69dedf09a1949e412c1e18469326d00"
dependencies = [
"aws-lc-sys",
"zeroize",
]
[[package]]
name = "aws-lc-sys"
version = "0.41.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "1a2f9779ce85b93ab6170dd940ad0169b5766ff848247aff13bb788b832fe3f4"
dependencies = [
"cc",
"cmake",
"dunce",
"fs_extra",
]
[[package]]
name = "axum"
version = "0.8.9"
@@ -560,6 +538,12 @@ dependencies = [
"syn",
]
[[package]]
name = "byteorder-lite"
version = "0.1.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "8f1fe948ff07f4bd06c30984e69f5b4899c516a3ef74f34df92a2df2ab535495"
[[package]]
name = "bytes"
version = "1.12.0"
@@ -756,6 +740,12 @@ dependencies = [
"cc",
]
[[package]]
name = "color_quant"
version = "1.1.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "3d7b894f5411737b7867f4827955924d7c254fc9f4d91a6aad6b097804b1018b"
[[package]]
name = "colorchoice"
version = "1.0.5"
@@ -1022,12 +1012,6 @@ version = "1.2.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "75b325c5dbd37f80359721ad39aca5a29fb04c89279657cffdda8736d0c0b9d2"
[[package]]
name = "dunce"
version = "1.0.5"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "92773504d58c093f6de2459af4af33faa518c13451eb8f2b5698ed3d36e7c813"
[[package]]
name = "either"
version = "1.16.0"
@@ -1141,6 +1125,15 @@ dependencies = [
"getrandom 0.3.4",
]
[[package]]
name = "fdeflate"
version = "0.3.7"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "1e6853b52649d4ac5c0bd02320cddc5ba956bdb407c4b75a2c6b75bf51500f8c"
dependencies = [
"simd-adler32",
]
[[package]]
name = "fec-rs"
version = "0.1.0"
@@ -1273,12 +1266,6 @@ dependencies = [
"tokio",
]
[[package]]
name = "fs_extra"
version = "1.3.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "42703706b716c37f96a77aea830392ad231f44c9e9a67872fa5548707e11b11c"
[[package]]
name = "futures"
version = "0.3.32"
@@ -1495,6 +1482,16 @@ dependencies = [
"polyval",
]
[[package]]
name = "gif"
version = "0.14.2"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "ee8cfcc411d9adbbaba82fb72661cc1bcca13e8bba98b364e62b2dba8f960159"
dependencies = [
"color_quant",
"weezl",
]
[[package]]
name = "gio"
version = "0.22.6"
@@ -1988,6 +1985,23 @@ dependencies = [
"windows-sys 0.61.2",
]
[[package]]
name = "image"
version = "0.25.10"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "85ab80394333c02fe689eaf900ab500fbd0c2213da414687ebf995a65d5a6104"
dependencies = [
"bytemuck",
"byteorder-lite",
"color_quant",
"gif",
"moxcms",
"num-traits",
"png",
"zune-core",
"zune-jpeg",
]
[[package]]
name = "indexmap"
version = "2.14.0"
@@ -2145,7 +2159,7 @@ dependencies = [
[[package]]
name = "latency-probe"
version = "0.12.0"
version = "0.13.0"
[[package]]
name = "lazy_static"
@@ -2248,6 +2262,14 @@ dependencies = [
"vcpkg",
]
[[package]]
name = "libvpl-sys"
version = "0.13.0"
dependencies = [
"bindgen",
"cmake",
]
[[package]]
name = "linux-raw-sys"
version = "0.12.1"
@@ -2277,7 +2299,7 @@ checksum = "0ceec5bc11778974d1bcb055b18002eba7f4b3518b6a0081b3af5f21666da9ad"
[[package]]
name = "loss-harness"
version = "0.12.0"
version = "0.13.0"
dependencies = [
"punktfunk-core",
]
@@ -2386,6 +2408,16 @@ dependencies = [
"windows-sys 0.61.2",
]
[[package]]
name = "moxcms"
version = "0.8.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "bb85c154ba489f01b25c0d36ae69a87e4a1c73a72631fc6c0eb6dde34a73e44b"
dependencies = [
"num-traits",
"pxfm",
]
[[package]]
name = "nasm-rs"
version = "0.3.2"
@@ -2754,9 +2786,29 @@ version = "2.3.2"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "9b4f627cb1b25917193a259e49bdad08f671f8d9708acfd5fe0a8c1455d87220"
[[package]]
name = "pf-capture"
version = "0.13.0"
dependencies = [
"anyhow",
"ashpd",
"libc",
"pf-driver-proto",
"pf-frame",
"pf-gpu",
"pf-host-config",
"pf-win-display",
"pf-zerocopy",
"pipewire",
"punktfunk-core",
"tokio",
"tracing",
"windows 0.62.2 (registry+https://github.com/rust-lang/crates.io-index)",
]
[[package]]
name = "pf-client-core"
version = "0.12.0"
version = "0.13.0"
dependencies = [
"anyhow",
"ash",
@@ -2785,6 +2837,7 @@ dependencies = [
"anyhow",
"ashpd",
"futures-util",
"image",
"libc",
"punktfunk-core",
"quinn",
@@ -2797,7 +2850,7 @@ dependencies = [
[[package]]
name = "pf-console-ui"
version = "0.12.0"
version = "0.13.0"
dependencies = [
"anyhow",
"ash",
@@ -2816,9 +2869,32 @@ dependencies = [
"bytemuck",
]
[[package]]
name = "pf-encode"
version = "0.13.0"
dependencies = [
"anyhow",
"ash",
"ffmpeg-next",
"libc",
"libloading",
"libvpl-sys",
"nvidia-video-codec-sdk",
"openh264",
"pf-frame",
"pf-gpu",
"pf-host-config",
"pf-zerocopy",
"punktfunk-core",
"pyrowave-sys",
"tracing",
"tracing-subscriber",
"windows 0.62.2 (registry+https://github.com/rust-lang/crates.io-index)",
]
[[package]]
name = "pf-ffvk"
version = "0.12.0"
version = "0.13.0"
dependencies = [
"ash",
"bindgen",
@@ -2826,8 +2902,73 @@ dependencies = [
]
[[package]]
name = "pf-presenter"
name = "pf-frame"
version = "0.13.0"
dependencies = [
"anyhow",
"libc",
"pf-zerocopy",
"punktfunk-core",
"tracing",
"windows 0.62.2 (registry+https://github.com/rust-lang/crates.io-index)",
]
[[package]]
name = "pf-gpu"
version = "0.13.0"
dependencies = [
"anyhow",
"pf-host-config",
"pf-paths",
"serde",
"serde_json",
"tempfile",
"tracing",
"windows 0.62.2 (registry+https://github.com/rust-lang/crates.io-index)",
]
[[package]]
name = "pf-host-config"
version = "0.13.0"
[[package]]
name = "pf-inject"
version = "0.12.0"
dependencies = [
"anyhow",
"ashpd",
"futures-util",
"libc",
"parking_lot",
"pf-capture",
"pf-driver-proto",
"pf-host-config",
"pf-paths",
"punktfunk-core",
"reis",
"tokio",
"tracing",
"usbip-sim",
"wayland-backend",
"wayland-client",
"wayland-protocols",
"wayland-protocols-misc",
"wayland-protocols-wlr",
"wayland-scanner",
"windows 0.62.2 (registry+https://github.com/rust-lang/crates.io-index)",
"xkbcommon",
]
[[package]]
name = "pf-paths"
version = "0.13.0"
dependencies = [
"tracing",
]
[[package]]
name = "pf-presenter"
version = "0.13.0"
dependencies = [
"anyhow",
"ash",
@@ -2840,6 +2981,62 @@ dependencies = [
"windows-sys 0.61.2",
]
[[package]]
name = "pf-vdisplay"
version = "0.12.0"
dependencies = [
"anyhow",
"ashpd",
"bytemuck",
"futures-util",
"hex",
"libc",
"pf-driver-proto",
"pf-encode",
"pf-frame",
"pf-gpu",
"pf-host-config",
"pf-paths",
"pf-win-display",
"punktfunk-core",
"serde",
"serde_json",
"sha2",
"tokio",
"tracing",
"utoipa",
"wayland-backend",
"wayland-client",
"wayland-scanner",
"windows 0.62.2 (registry+https://github.com/rust-lang/crates.io-index)",
]
[[package]]
name = "pf-win-display"
version = "0.13.0"
dependencies = [
"anyhow",
"pf-paths",
"punktfunk-core",
"serde_json",
"tracing",
"windows 0.62.2 (registry+https://github.com/rust-lang/crates.io-index)",
]
[[package]]
name = "pf-zerocopy"
version = "0.13.0"
dependencies = [
"anyhow",
"ash",
"khronos-egl",
"libc",
"libloading",
"serde",
"serde_json",
"tracing",
]
[[package]]
name = "pin-project-lite"
version = "0.2.17"
@@ -2912,6 +3109,19 @@ version = "0.3.33"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "19f132c84eca552bf34cab8ec81f1c1dcc229b811638f9d283dceabe58c5569e"
[[package]]
name = "png"
version = "0.18.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "60769b8b31b2a9f263dae2776c37b1b28ae246943cf719eb6946a1db05128a61"
dependencies = [
"bitflags",
"crc32fast",
"fdeflate",
"flate2",
"miniz_oxide",
]
[[package]]
name = "polling"
version = "3.11.0"
@@ -3011,7 +3221,7 @@ dependencies = [
[[package]]
name = "punktfunk-client-android"
version = "0.12.0"
version = "0.13.0"
dependencies = [
"android_logger",
"jni",
@@ -3027,7 +3237,7 @@ dependencies = [
[[package]]
name = "punktfunk-client-linux"
version = "0.12.0"
version = "0.13.0"
dependencies = [
"anyhow",
"async-channel",
@@ -3043,7 +3253,7 @@ dependencies = [
[[package]]
name = "punktfunk-client-session"
version = "0.12.0"
version = "0.13.0"
dependencies = [
"anyhow",
"pf-client-core",
@@ -3058,7 +3268,7 @@ dependencies = [
[[package]]
name = "punktfunk-client-windows"
version = "0.12.0"
version = "0.13.0"
dependencies = [
"async-channel",
"ffmpeg-next",
@@ -3077,7 +3287,7 @@ dependencies = [
[[package]]
name = "punktfunk-core"
version = "0.12.0"
version = "0.13.0"
dependencies = [
"aes-gcm",
"bytes",
@@ -3108,7 +3318,7 @@ dependencies = [
[[package]]
name = "punktfunk-host"
version = "0.12.0"
version = "0.13.0"
dependencies = [
"aes",
"aes-gcm",
@@ -3120,7 +3330,6 @@ dependencies = [
"base64",
"bytemuck",
"cbc",
"ffmpeg-next",
"futures-util",
"hex",
"hmac",
@@ -3134,15 +3343,22 @@ dependencies = [
"log",
"mac_address",
"mdns-sd",
"nvidia-video-codec-sdk",
"openh264",
"opus",
"parking_lot",
"pf-capture",
"pf-clipboard",
"pf-driver-proto",
"pf-encode",
"pf-frame",
"pf-gpu",
"pf-host-config",
"pf-inject",
"pf-paths",
"pf-vdisplay",
"pf-win-display",
"pf-zerocopy",
"pipewire",
"punktfunk-core",
"pyrowave-sys",
"quinn",
"rand 0.8.6",
"rcgen",
@@ -3184,7 +3400,7 @@ dependencies = [
[[package]]
name = "punktfunk-probe"
version = "0.12.0"
version = "0.13.0"
dependencies = [
"anyhow",
"mdns-sd",
@@ -3198,7 +3414,7 @@ dependencies = [
[[package]]
name = "punktfunk-tray"
version = "0.12.0"
version = "0.13.0"
dependencies = [
"anyhow",
"ksni",
@@ -3213,9 +3429,15 @@ dependencies = [
"winresource",
]
[[package]]
name = "pxfm"
version = "0.1.30"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "d55d956fa96f5ec02be2e13af0e20391a5aa83d6a074e3ad368959d0fab299ea"
[[package]]
name = "pyrowave-sys"
version = "0.12.0"
version = "0.13.0"
dependencies = [
"bindgen",
"cmake",
@@ -3414,7 +3636,6 @@ version = "0.13.2"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "75e669e5202259b5314d1ea5397316ad400819437857b90861765f24c4cf80a2"
dependencies = [
"aws-lc-rs",
"pem",
"ring",
"rustls-pki-types",
@@ -3643,7 +3864,6 @@ version = "0.23.41"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "6b92b125634d9b795e7beca796cc790df15a7fb38323bf3196fda83292d06b1f"
dependencies = [
"aws-lc-rs",
"log",
"once_cell",
"ring",
@@ -3708,7 +3928,6 @@ version = "0.103.13"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "61c429a8649f110dddef65e2a5ad240f747e85f7758a6bccc7e5777bd33f756e"
dependencies = [
"aws-lc-rs",
"ring",
"rustls-pki-types",
"untrusted",
@@ -5033,6 +5252,12 @@ dependencies = [
"rustls-pki-types",
]
[[package]]
name = "weezl"
version = "0.1.12"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "a28ac98ddc8b9274cb41bb4d9d4d5c425b6020c50c46f25559911905610b4a88"
[[package]]
name = "wide"
version = "0.7.33"
@@ -5845,6 +6070,21 @@ version = "1.0.21"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "b8848ee67ecc8aedbaf3e4122217aff892639231befc6a1b58d29fff4c2cabaa"
[[package]]
name = "zune-core"
version = "0.5.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "cb8a0807f7c01457d0379ba880ba6322660448ddebc890ce29bb64da71fb40f9"
[[package]]
name = "zune-jpeg"
version = "0.5.15"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "27bc9d5b815bc103f142aa054f561d9187d191692ec7c2d1e2b4737f8dbd7296"
dependencies = [
"zune-core",
]
[[package]]
name = "zvariant"
version = "5.12.0"
+12 -1
View File
@@ -11,7 +11,18 @@ members = [
"crates/pf-console-ui",
"crates/pf-ffvk",
"crates/pf-driver-proto",
"crates/pf-paths",
"crates/pf-host-config",
"crates/pf-gpu",
"crates/pf-zerocopy",
"crates/pf-frame",
"crates/pf-win-display",
"crates/pf-encode",
"crates/pf-capture",
"crates/pf-inject",
"crates/pf-vdisplay",
"crates/pyrowave-sys",
"crates/libvpl-sys",
"clients/probe",
"clients/linux",
"clients/session",
@@ -37,7 +48,7 @@ exclude = [
ndk = { path = "clients/android/native/vendor/ndk" }
[workspace.package]
version = "0.12.0"
version = "0.13.0"
edition = "2021"
rust-version = "1.82"
license = "MIT OR Apache-2.0"
+20 -2
View File
@@ -10,7 +10,7 @@
"name": "MIT OR Apache-2.0",
"identifier": "MIT OR Apache-2.0"
},
"version": "0.12.0"
"version": "0.13.0"
},
"paths": {
"/api/v1/clients": {
@@ -2744,7 +2744,7 @@
},
"CustomEntry": {
"type": "object",
"description": "A user-added title, persisted in `~/.config/punktfunk/library.json`. Same shape the API\nreturns and the web console edits.",
"description": "A user-added title, persisted in the hardened host config dir's `library.json` (see\n[`custom_path`]). Same shape the API returns and the web console edits.",
"required": [
"id",
"title"
@@ -4617,6 +4617,15 @@
"format": "int32",
"minimum": 0
},
"last_resize_ms": {
"type": [
"integer",
"null"
],
"format": "int32",
"description": "Most recent mid-stream resize total, reconfigure → pipeline rebuilt, in ms (native sessions;\n`null` when no resize happened / GameStream).",
"minimum": 0
},
"min_fec": {
"type": "integer",
"format": "int32",
@@ -4629,6 +4638,15 @@
"description": "Video payload size per packet (bytes).",
"minimum": 0
},
"time_to_first_frame_ms": {
"type": [
"integer",
"null"
],
"format": "int32",
"description": "Session bring-up total, hello → first video packet, in ms (native sessions; `null` on the\nGameStream plane or while the session is still bringing up).",
"minimum": 0
},
"width": {
"type": "integer",
"format": "int32",
@@ -30,7 +30,13 @@ suspend fun connectToHost(
): Long {
// Advertise HDR only when the user enabled it AND this device's display can present it (else the
// host sends a proper SDR stream rather than PQ the panel would mis-tone-map).
val (w, h, hz) = settings.effectiveMode(context)
val (baseW, baseH, hz) = settings.effectiveMode(context)
// Render scale: ask the host for `chosen mode × scale` (even + codec-clamped) — > 1 supersamples
// (the compositor downscales the larger decoded frame to the SurfaceView), < 1 renders under
// native. 1.0 leaves the resolved mode untouched.
val (w, h) = RenderScale.apply(
baseW, baseH, settings.renderScale, RenderScale.maxDimension(settings.codec)
)
val hdrEnabled = settings.hdrEnabled && displaySupportsHdr(context)
// "Automatic" resolves to a concrete pad type from the connected controller's VID/PID.
val gamepadPref = Gamepad.resolvePref(settings.gamepad)
@@ -16,6 +16,14 @@ data class Settings(
val height: Int = 0,
val hz: Int = 0,
val bitrateKbps: Int = 0,
/**
* Render-resolution multiplier: the client asks the host to render/encode at `chosen mode ×
* renderScale` and the compositor downscales the larger decoded frame to the SurfaceView
* (`> 1` supersamples for sharpness, at more bandwidth AND decode; `< 1` renders under native
* for a lighter host/link). `1.0` = Native. Applied at connect via [RenderScale.apply], clamped
* even + to the codec's max dimension. Mirrors the Apple/Linux clients' render scale.
*/
val renderScale: Double = 1.0,
/**
* Advertise HDR (10-bit BT.2020 PQ) to the host. Default on, but only *effective* on a panel that
* can actually present HDR10 (see [displaySupportsHdr]) — on an SDR display HDR is never
@@ -137,6 +145,7 @@ class SettingsStore(context: Context) {
height = prefs.getInt(K_H, 0),
hz = prefs.getInt(K_HZ, 0),
bitrateKbps = prefs.getInt(K_BITRATE, 0),
renderScale = prefs.getFloat(K_RENDER_SCALE, 1.0f).toDouble(),
hdrEnabled = prefs.getBoolean(K_HDR, true),
compositor = prefs.getInt(K_COMPOSITOR, 0),
gamepad = prefs.getInt(K_GAMEPAD, 0),
@@ -171,6 +180,7 @@ class SettingsStore(context: Context) {
.putInt(K_H, s.height)
.putInt(K_HZ, s.hz)
.putInt(K_BITRATE, s.bitrateKbps)
.putFloat(K_RENDER_SCALE, s.renderScale.toFloat())
.putBoolean(K_HDR, s.hdrEnabled)
.putInt(K_COMPOSITOR, s.compositor)
.putInt(K_GAMEPAD, s.gamepad)
@@ -193,6 +203,7 @@ class SettingsStore(context: Context) {
const val K_H = "height"
const val K_HZ = "hz"
const val K_BITRATE = "bitrate_kbps"
const val K_RENDER_SCALE = "render_scale"
const val K_HDR = "hdr_enabled"
const val K_COMPOSITOR = "compositor"
const val K_GAMEPAD = "gamepad"
@@ -281,6 +292,54 @@ fun Settings.effectiveMode(context: Context): Triple<Int, Int, Int> {
return Triple(w, h, hz)
}
/**
* Client-side render-scale geometry — the Kotlin twin of `punktfunk-core`'s `render_scale` module
* (and the Apple client's `RenderScale`). Multiply a base size, preserve aspect, even-floor (the
* host rejects odd sizes), and clamp uniformly to the codec's per-axis ceiling so a connect can't
* ask for a size the encoder rejects. `1.0` = Native. Pure + covered by [RenderScaleTest].
*/
object RenderScale {
val PRESETS = listOf(0.5, 0.67, 0.75, 1.0, 1.25, 1.5, 2.0, 3.0, 4.0)
/** H.264 tops out at 4096 px/axis; HEVC/AV1/auto at 8192 — the host's `codec.rs` walls. */
fun maxDimension(codec: String): Int = if (codec == "h264") 4096 else 8192
/** Clamp a raw multiplier into [0.5, 4.0]; a missing / non-positive / NaN value → 1.0. */
fun sanitize(raw: Double): Double = if (raw > 0.0) raw.coerceIn(0.5, 4.0) else 1.0
/** "Native (1×)" / "1.5×" / "2× · supersample" — the picker label. */
fun label(scale: Double): String = when {
scale == 1.0 -> "Native (1×)"
scale > 1.0 -> "${trim(scale)}× · supersample"
else -> "${trim(scale)}×"
}
private fun trim(s: Double): String =
if (s == s.toLong().toDouble()) s.toLong().toString() else s.toString()
/** Apply [scale] to a base size → a host-valid even, aspect-preserved, codec-clamped (w, h). */
fun apply(baseW: Int, baseH: Int, scale: Double, maxDim: Int): Pair<Int, Int> {
val s = sanitize(scale)
var w = maxOf(baseW, 1) * s
var h = maxOf(baseH, 1) * s
val cap = maxDim.toDouble()
val over = maxOf(w / cap, h / cap)
if (over > 1.0) {
w /= over
h /= over
}
return Pair(evenFloor(w, 320), evenFloor(h, 200))
}
private fun evenFloor(value: Double, minimum: Int): Int {
val v = maxOf(kotlin.math.floor(value).toInt(), minimum).coerceAtLeast(0)
return v / 2 * 2
}
}
/** (scale, label) for the render-scale picker. `1.0` = Native. */
val RENDER_SCALE_OPTIONS = RenderScale.PRESETS.map { it to RenderScale.label(it) }
// ---- UI option tables (value, label). The first entry is always the "auto/native" default. ----
/** (width, height, label). `(0,0)` = native display. */
@@ -333,6 +333,15 @@ private fun DisplaySettings(s: Settings, update: (Settings) -> Unit, context: an
update(s.copy(bitrateKbps = kbps))
}
SettingDropdown(
label = "Render scale",
options = RENDER_SCALE_OPTIONS,
// Snap the stored value (a Float round-tripped to Double) to the nearest preset so the
// exact Double keys match. > 1 supersamples for sharpness (more bandwidth AND decode);
// < 1 renders under native for a lighter host — this device resamples to the display.
selected = RenderScale.PRESETS.minByOrNull { kotlin.math.abs(it - s.renderScale) } ?: 1.0,
) { scale -> update(s.copy(renderScale = scale)) }
// AV1 is only offered when the device has a real AV1 decoder (it's never advertised to the
// host otherwise, so preferring it would be a dead setting). A stored "av1" from a capable
// device stays visible so the selection is always representable.
@@ -0,0 +1,73 @@
package io.unom.punktfunk
import org.junit.Assert.assertEquals
import org.junit.Assert.assertTrue
import org.junit.Test
/**
* Pure JVM test of the client-side render-scale geometry ([RenderScale]) — the Kotlin twin of
* `punktfunk-core`'s `render_scale` module. Run: `./gradlew :app:testDebugUnitTest`.
*/
class RenderScaleTest {
@Test
fun sanitizeClampsAndDefaults() {
assertEquals(1.0, RenderScale.sanitize(0.0), 0.0) // absent / zero → Native
assertEquals(1.0, RenderScale.sanitize(-2.0), 0.0)
assertEquals(1.0, RenderScale.sanitize(Double.NaN), 0.0)
assertEquals(0.5, RenderScale.sanitize(0.1), 0.0) // below the floor
assertEquals(4.0, RenderScale.sanitize(9.0), 0.0) // above the ceiling
assertEquals(1.5, RenderScale.sanitize(1.5), 0.0)
}
@Test
fun maxDimensionIsCodecAware() {
assertEquals(4096, RenderScale.maxDimension("h264"))
assertEquals(8192, RenderScale.maxDimension("hevc"))
assertEquals(8192, RenderScale.maxDimension("av1"))
assertEquals(8192, RenderScale.maxDimension("auto"))
}
@Test
fun nativeIsIdentity() {
assertEquals(1920 to 1080, RenderScale.apply(1920, 1080, 1.0, 8192))
}
@Test
fun supersampleDoubles() {
assertEquals(3840 to 2160, RenderScale.apply(1920, 1080, 2.0, 8192))
}
@Test
fun underRenderHalves() {
assertEquals(960 to 540, RenderScale.apply(1920, 1080, 0.5, 8192))
}
@Test
fun resultsAreEven() {
// 1366×768 × 1.5 = 2049×1152 → even-floored to 2048×1152.
val (w, h) = RenderScale.apply(1366, 768, 1.5, 8192)
assertEquals(0, w % 2)
assertEquals(0, h % 2)
assertEquals(2048 to 1152, w to h)
}
@Test
fun overCeilingClampsUniformly() {
// 4K × 4 = 15360×8640; both exceed 8192 → width lands on cap, 16:9 kept (8192×4608).
val (w, h) = RenderScale.apply(3840, 2160, 4.0, 8192)
assertTrue(w <= 8192 && h <= 8192)
assertEquals(8192 to 4608, w to h)
}
@Test
fun h264CeilingIsTighter() {
// 1080p × 4 = 7680×4320; under H.264's 4096 wall → 4096×2304.
assertEquals(4096 to 2304, RenderScale.apply(1920, 1080, 4.0, 4096))
}
@Test
fun minimumFloorHonoured() {
val (w, h) = RenderScale.apply(400, 300, 0.5, 8192)
assertTrue(w >= 320 && h >= 200)
}
}
@@ -52,9 +52,6 @@ class GamepadFeedback(
const val TAG_PLAYER_LEDS: Byte = 0x02
const val TAG_TRIGGER: Byte = 0x03
const val TAG_HID_RAW: Byte = 0x05
// Fallback one-shot duration against a legacy host (no v2 TTL lease): the prior fixed value.
// A new host renews far below this, so it never actually holds this long there.
const val LEGACY_RUMBLE_MS = 60_000L
}
/** One controller's rumble binding — VibratorManager (API 31+) OR the legacy single Vibrator (API 2830). */
@@ -95,19 +92,19 @@ class GamepadFeedback(
while (running) {
val ev = NativeBridge.nativeNextRumble(handle)
if (ev < 0L) continue // timeout / closed
// ev bits 49..52 = wire pad index; bit 48 = has a v2 lease; bits 32..47 = ttl_ms;
// 16..31 = low; 0..15 = high. The lease flag is out-of-band, so any ttl_ms (incl.
// 0xFFFF) is a real lease — no in-band sentinel. No lease (legacy host) → the prior
// long one-shot.
// ev bits 49..52 = wire pad index; bits 32..47 = backstop duration (ms);
// 16..31 = low; 0..15 = high. These are EFFECTIVE commands from the core's shared
// rumble policy engine — it owns every lease/staleness/close decision (uniform
// across all clients; the old 60 s legacy-host exposure is gone) and emits
// explicit zeros, so apply verbatim: (0, 0) = cancel, non-zero = one-shot for
// the backstop (the hardware net under a stalled poll thread).
val pad = ((ev ushr 49) and 0xFL).toInt()
val hasLease = ((ev ushr 48) and 0x1L) == 0x1L
val ttl = ((ev ushr 32) and 0xFFFF).toInt()
val durationMs = if (hasLease) ttl.toLong() else LEGACY_RUMBLE_MS
val backstopMs = ((ev ushr 32) and 0xFFFF)
renderRumble(
pad,
((ev ushr 16) and 0xFFFF).toInt(),
(ev and 0xFFFF).toInt(),
durationMs,
backstopMs,
)
}
}, "pf-rumble").apply { isDaemon = true; start() }
@@ -212,12 +209,13 @@ class GamepadFeedback(
/**
* low = heavy/left motor, high = light/right motor; both 0..0xFFFF (the host's u16 amplitudes),
* addressed to wire pad [pad]. `durationMs` is the host's v2 envelope TTL the one-shot self-
* terminates after it unless the host renews, so a lost stop (or a dead host) silences at the
* lease instead of the old fixed 60 s. Against a legacy host it is [LEGACY_RUMBLE_MS].
* addressed to wire pad [pad]. `durationMs` is the engine command's backstop the one-shot's
* self-termination net under a stalled poll thread; the engine emits explicit zero commands at
* every policy stop (lease expiry, legacy staleness, session close), so cancel-on-zero is the
* real stop mechanism.
*/
private fun renderRumble(pad: Int, low: Int, high: Int, durationMs: Long) {
Log.i(TAG, "rumble pad=$pad low=$low high=$high ttlMs=$durationMs") // verification line — BEFORE any no-op return
Log.i(TAG, "rumble pad=$pad low=$low high=$high backstopMs=$durationMs") // verification line — BEFORE any no-op return
// Opt-in phone mirror, BEFORE the controller-bind early-return: the exact pads this
// serves have no vibrator of their own, so their bind below is null. It follows
// controller 1 unconditionally rather than only motor-less pads — capability probing
File diff suppressed because it is too large Load Diff
@@ -0,0 +1,626 @@
//! The event-driven async MediaCodec decode loop (default) + its feeder/dispatch/present helpers.
use ndk::data_space::DataSpace;
use ndk::media::media_codec::{AsyncNotifyCallback, MediaCodec, MediaCodecDirection};
use ndk::media::media_format::MediaFormat;
use ndk::native_window::NativeWindow;
use punktfunk_core::client::NativeClient;
use punktfunk_core::error::PunktfunkError;
use punktfunk_core::reanchor::{GateVerdict, ReanchorGate};
use punktfunk_core::session::Frame;
use std::collections::VecDeque;
use std::sync::atomic::{AtomicBool, AtomicI64, Ordering};
use std::sync::{mpsc, Arc, Mutex};
use std::time::{Duration, Instant};
use super::display::{
apply_hdr_dataspace, install_render_callback, release_render_callback, DisplayTracker,
};
use super::latency::{note_decoded_pts, now_realtime_ns, take_flags};
use super::setup::{
android_hdr_static_info, boost_hot_threads, boost_thread_priority, codec_mime,
configure_low_latency, create_codec, try_set_frame_rate,
};
use super::{DecodeOptions, FRAME_PARK_CAP, IN_FLIGHT_CAP, PENDING_SPLIT_CAP};
/// One decoded output buffer ready to release: its codec buffer index + the pts the codec echoed
/// (from the output callback's `BufferInfo`), used to pair the `decode` HUD stat, and the
/// wall-clock instant the output callback fired — the spec's `decoded` point ("decoder output
/// frame available"), stamped at the callback so the event-channel hop + coalescing wait in the
/// loop never inflates the decode stage.
struct OutputReady {
index: usize,
pts_us: u64,
decoded_ns: i128,
}
/// Events the async decode loop reacts to. The codec's async-notify callbacks (which run on its
/// internal looper thread) push the codec ones; the feeder thread pushes `Au`. Each carries only
/// owned/`Copy` data so the callback closures satisfy the `Send` bound and never touch the codec.
enum DecodeEvent {
/// A received access unit from the feeder, ready to queue into the decoder. The `bool` is the
/// feeder's [`NativeClient::note_frame_index`] verdict — `true` when this AU revealed a forward
/// frame-index gap, so the loop arms the freeze gate (the feeder already fired the RFI request).
Au(Frame, bool),
/// An input buffer slot freed (index) — we can queue an AU into it.
InputAvailable(usize),
/// A decoded frame is ready (buffer index + echoed pts + the callback-time `decoded` stamp).
OutputAvailable {
index: usize,
pts_us: u64,
decoded_ns: i128,
},
/// The output format changed — re-check the stream's colour signalling (HDR DataSpace).
FormatChanged,
/// The codec reported an error; `fatal` when neither recoverable nor transient.
Error { fatal: bool },
}
/// The event-driven async decode loop (default; see [`run`]/[`USE_ASYNC_DECODE`]). The codec drives
/// us: an async-notify callback fires the instant an input buffer frees or a frame finishes
/// decoding, so a decoded frame is presented immediately instead of waiting out a poll interval (the
/// latency the sync loop left on the table). The callbacks run on the codec's internal looper thread
/// and only *push events* — every `AMediaCodec` buffer op stays on this thread, which owns the codec,
/// sidestepping the self-reference that would arise from a callback calling back into the codec it's
/// stored in. A small `pf-decode-feed` thread blocks on the network so this loop never does.
pub(super) fn run_async(
client: Arc<NativeClient>,
window: NativeWindow,
shutdown: Arc<AtomicBool>,
stats: Arc<crate::stats::VideoStats>,
opts: DecodeOptions,
) {
let DecodeOptions {
decoder_name,
ll_feature,
low_latency_mode,
is_tv,
} = opts;
boost_thread_priority();
let mode = client.mode();
let mime = codec_mime(client.codec);
let mut codec = match create_codec(mime, decoder_name.as_deref()) {
Some(c) => c,
None => {
log::error!("decode: no {mime} decoder on this device");
return;
}
};
let codec_name = codec.name().unwrap_or_default();
stats.set_decoder(&codec_name, ll_feature);
log::info!(
"decode: codec mime = {mime}, decoder = {codec_name} (async, low-latency feature: {ll_feature})"
);
// The event channel: the callbacks + feeder push, this loop pulls. `Sender` is `Send`, so the
// callback closures (each capturing a clone) satisfy the async-notify `Send` bound.
let (ev_tx, ev_rx) = mpsc::channel::<DecodeEvent>();
// Install the callbacks BEFORE configure()/start() so we're in async mode from the first buffer.
// Each just forwards an index/flag — no codec access here (the codec owns these closures).
{
let out_tx = ev_tx.clone();
let in_tx = ev_tx.clone();
let fmt_tx = ev_tx.clone();
let err_tx = ev_tx.clone();
let cb = AsyncNotifyCallback {
on_input_available: Some(Box::new(move |idx| {
let _ = in_tx.send(DecodeEvent::InputAvailable(idx));
})),
on_output_available: Some(Box::new(move |idx, info| {
let _ = out_tx.send(DecodeEvent::OutputAvailable {
index: idx,
pts_us: info.presentation_time_us().max(0) as u64,
// The `decoded` HUD point: stamp HERE, on the codec's looper thread, so the
// decode stage ends when the frame actually became available — not after the
// channel hop + whatever work the loop coalesces in front of presenting it.
decoded_ns: now_realtime_ns(),
});
})),
on_format_changed: Some(Box::new(move |_fmt| {
let _ = fmt_tx.send(DecodeEvent::FormatChanged);
})),
on_error: Some(Box::new(move |e, code, _detail| {
let fatal = !code.is_recoverable() && !code.is_transient();
if fatal {
log::error!("decode: fatal codec error — stream will stop: {e:?}");
} else {
log::warn!("decode: codec error {e:?} (recoverable)");
}
let _ = err_tx.send(DecodeEvent::Error { fatal });
})),
};
if let Err(e) = codec.set_async_notify_callback(Some(cb)) {
log::error!("decode: set_async_notify_callback failed: {e}");
return;
}
}
// Build the low-latency format (identical keys to the sync path).
let mut format = MediaFormat::new();
format.set_str("mime", mime);
format.set_i32("width", mode.width as i32);
format.set_i32("height", mode.height as i32);
format.set_i32(
"max-input-size",
(mode.width * mode.height).max(2_000_000) as i32,
);
configure_low_latency(&mut format, &codec_name, low_latency_mode);
if client.color.is_hdr() {
match client.next_hdr_meta(Duration::from_millis(250)) {
Ok(meta) => {
format.set_buffer("hdr-static-info", &android_hdr_static_info(&meta));
log::info!("decode: HDR static metadata applied (KEY_HDR_STATIC_INFO)");
}
Err(_) => {
log::info!("decode: HDR session but no mastering metadata yet — DataSpace only")
}
}
}
if let Err(e) = codec.configure(&format, Some(&window), MediaCodecDirection::Decoder) {
log::error!("decode: configure failed: {e}");
return;
}
if let Err(e) = codec.start() {
log::error!("decode: start failed: {e}");
return;
}
log::info!(
"decode: decoder started (async) at {}x{}",
mode.width,
mode.height
);
// The forced TV mode switch (`is_tv` ⇒ ALWAYS strategy) is part of the experimental stack;
// off, every form factor gets the original soft seamless hint.
if mode.refresh_hz > 0
&& !try_set_frame_rate(&window, mode.refresh_hz as f32, is_tv && low_latency_mode)
{
log::debug!(
"decode: set_frame_rate({} Hz) unavailable/declined (non-fatal)",
mode.refresh_hz
);
}
// Skew-corrected latency stats (spec: design/stats-unification.md). Receipt stamps (keyed by the
// pts we queue) live in a shared map: the feeder writes them at receipt, this loop pairs decoded
// output back to them. Behind a `Mutex` since two threads touch it — only ever locked while the
// HUD is visible.
let clock_offset = client.clock_offset_shared();
// Whether the adaptive-bitrate controller wants the `decode` stage as its decoder-backlog
// signal (Automatic, non-PyroWave): then `in_flight` is fed regardless of the HUD.
let measure_decode = client.wants_decode_latency();
let in_flight = Arc::new(Mutex::new(VecDeque::<(u64, i128)>::new()));
// Display stage (spec `display` + the capture→displayed headline): the rendered frame is
// parked in the tracker at release; the OnFrameRendered callback pairs it with
// SurfaceFlinger's render timestamp. `render_cb` is the callback's leaked Arc refcount,
// reclaimed after the codec is dropped below.
let tracker = DisplayTracker::new(stats.clone(), clock_offset.clone());
let render_cb = install_render_callback(&codec, &tracker);
// Feeder thread: block on the network so this loop doesn't (an AU's arrival becomes an event that
// wakes us immediately, with no input-side poll latency). It also records the `received` HUD stat.
let feeder = {
let client = client.clone();
let stats = stats.clone();
let in_flight = in_flight.clone();
let clock_offset = clock_offset.clone();
let shutdown = shutdown.clone();
let ev_tx = ev_tx.clone();
std::thread::Builder::new()
.name("pf-decode-feed".into())
.spawn(move || {
feeder_loop(
client,
stats,
measure_decode,
in_flight,
clock_offset,
shutdown,
ev_tx,
);
})
.ok()
};
drop(ev_tx); // only the feeder + callbacks keep the channel alive now
// ADPF: same as the sync path — register this thread now, create the session lazily on the first
// presented frame (by when the pump + audio + feeder threads have registered their tids too).
let frame_period_ns = if mode.refresh_hz > 0 {
1_000_000_000i64 / mode.refresh_hz as i64
} else {
0
};
client.register_hot_thread();
let mut hint: Option<crate::adpf::HintSession> = None;
let mut hint_tried = false;
let mut free_inputs: VecDeque<usize> = VecDeque::new();
let mut pending_aus: VecDeque<Frame> = VecDeque::new();
let mut ready: Vec<OutputReady> = Vec::new();
let mut applied_ds: Option<DataSpace> = None;
let mut fed: u64 = 0;
let mut rendered: u64 = 0;
let mut discarded: u64 = 0;
// AUs larger than the codec input buffer, dropped whole (see `feed`/`feed_ready`).
let mut oversized_dropped: u64 = 0;
// Freeze-until-reanchor gate (see the sync loop for the rationale). Armed on a frame-index gap
// (the feeder's Au verdict), a parked-AU overflow drop, a dropped-count climb, or a recoverable
// codec error; `recovery_flags` carries each AU's user_flags from `dispatch_event` (feed) to
// `present_ready` (present), keyed by the codec-echoed pts.
let mut gate = ReanchorGate::new(client.frames_dropped());
let mut recovery_flags: VecDeque<(u64, u32)> = VecDeque::new();
let mut last_kf_req: Option<Instant> = None;
// Productive (dispatch+feed+present) time between displayed frames; reported to ADPF once one is
// presented. The blocking event wait is excluded (idle, not work) — same accounting as the sync loop.
let mut work_accum_ns: i64 = 0;
let mut fatal = false;
while !shutdown.load(Ordering::Relaxed) && !fatal {
// Block for the next event (idle wait — excluded from the work tally). The short timeout
// drives loss-recovery housekeeping when the pipeline is momentarily quiet.
let ev0 = match ev_rx.recv_timeout(Duration::from_millis(5)) {
Ok(ev) => Some(ev),
Err(mpsc::RecvTimeoutError::Timeout) => None,
Err(mpsc::RecvTimeoutError::Disconnected) => break,
};
let work_t0 = Instant::now();
let mut fmt_dirty = false;
let mut aus_dropped: u64 = 0;
if let Some(ev) = ev0 {
aus_dropped += u64::from(dispatch_event(
ev,
&mut pending_aus,
&mut free_inputs,
&mut ready,
&mut fmt_dirty,
&mut fatal,
&mut gate,
&mut recovery_flags,
));
}
// Coalesce every other event already queued into this one work pass — correct newest-only
// presentation across a decode burst, and batched feeding.
while let Ok(ev) = ev_rx.try_recv() {
aus_dropped += u64::from(dispatch_event(
ev,
&mut pending_aus,
&mut free_inputs,
&mut ready,
&mut fmt_dirty,
&mut fatal,
&mut gate,
&mut recovery_flags,
));
}
stats.note_skipped(aus_dropped); // parked-AU overflow drops are client-side skips too
if fmt_dirty {
apply_hdr_dataspace(&codec, &window, &mut applied_ds);
}
feed_ready(
&codec,
&client,
&mut pending_aus,
&mut free_inputs,
&mut fed,
&mut oversized_dropped,
);
let had_output = !ready.is_empty();
present_ready(
&codec,
&client,
measure_decode,
&mut ready,
&stats,
&in_flight,
clock_offset.load(Ordering::Relaxed),
&tracker,
&mut rendered,
&mut discarded,
&mut gate,
&mut recovery_flags,
);
work_accum_ns += work_t0.elapsed().as_nanos() as i64;
if had_output {
if !hint_tried {
hint_tried = true;
let tids = client.hot_thread_ids();
// The pump/audio priority boost is part of the experimental low-latency stack; the
// ADPF session itself predates it and always runs (max-performance bias gated inside).
if low_latency_mode {
boost_hot_threads(&tids);
}
hint = crate::adpf::HintSession::create(frame_period_ns, &tids, low_latency_mode);
log::info!(
"decode: ADPF hint session {} — {} hot thread(s), target {frame_period_ns} ns",
if hint.is_some() {
"active"
} else {
"unavailable"
},
tids.len(),
);
}
if let Some(h) = &hint {
h.report_actual(work_accum_ns);
}
work_accum_ns = 0;
if rendered > 0 && rendered % 300 == 0 {
log::info!("decode: fed={fed} rendered={rendered} discarded={discarded}");
}
}
// Loss recovery + overdue backstop, folded through the gate. A parked-AU overflow drop is itself
// a loss, so it arms the freeze directly; the gate's `poll` then arms on a dropped-count climb
// and re-asks on an overdue freeze. All keyframe intents route through the shared 100 ms
// throttle so a multi-frame recovery gap can't flood the control stream.
let now = Instant::now();
if aus_dropped > 0 {
gate.arm(now);
}
if (gate.poll(client.frames_dropped(), now) || aus_dropped > 0)
&& last_kf_req.is_none_or(|t| now.duration_since(t) >= Duration::from_millis(100))
{
last_kf_req = Some(now);
let _ = client.request_keyframe();
}
}
let _ = codec.stop();
shutdown.store(true, Ordering::SeqCst); // ensure the feeder wakes and exits, then join it
if let Some(j) = feeder {
let _ = j.join();
}
drop(codec); // AMediaCodec_delete — after this no render callback can fire
if let Some(ud) = render_cb {
// SAFETY: the codec was dropped above; this registration's single reclaim.
unsafe { release_render_callback(ud) };
}
log::info!("decode: stopped (async, fed={fed} rendered={rendered} discarded={discarded})");
}
/// The `pf-decode-feed` thread: block on the connector for the next access unit so the async loop
/// never has to. Records the `received` HUD stat (receipt point) — including the Phase-2 host/network
/// split from any matching 0xCF host timings — then hands the AU to the loop via the event channel.
/// Exits when `shutdown` is set, the session closes, or the loop's receiver is gone.
fn feeder_loop(
client: Arc<NativeClient>,
stats: Arc<crate::stats::VideoStats>,
measure_decode: bool,
in_flight: Arc<Mutex<VecDeque<(u64, i128)>>>,
clock_offset: Arc<AtomicI64>,
shutdown: Arc<AtomicBool>,
ev_tx: mpsc::Sender<DecodeEvent>,
) {
// Received AUs awaiting their 0xCF host timing (Phase-2 split), as (pts_ns, capture→received µs).
let mut pending_split: VecDeque<(u64, u64)> = VecDeque::new();
while !shutdown.load(Ordering::Relaxed) {
match client.next_frame(Duration::from_millis(5)) {
Ok(frame) => {
// Loss recovery (RFI): a forward frame-index gap fires a throttled reference-frame-
// invalidation request so an RFI-capable host recovers with a cheap clean P-frame
// instead of a full IDR (the frames_dropped keyframe path is the backstop). The gap
// verdict rides the Au event so the decode loop arms its freeze gate on the same signal.
let gap = client.note_frame_index(frame.frame_index);
// Park the receipt stamp (keyed by the pts the codec echoes) whenever the `decode`
// stage is consumed: the HUD, or the ABR decode signal (`measure_decode`). The
// HUD-only `received` point + host/network split stay gated on the overlay.
if stats.enabled() || measure_decode {
let received_ns = now_realtime_ns();
{
let mut g = in_flight
.lock()
.unwrap_or_else(std::sync::PoisonError::into_inner);
g.push_back((frame.pts_ns / 1000, received_ns));
if g.len() > IN_FLIGHT_CAP {
g.pop_front(); // stale — codec never echoed it back
}
}
if stats.enabled() {
let clock_offset = clock_offset.load(Ordering::Relaxed) as i128;
let lat_ns = received_ns + clock_offset - frame.pts_ns as i128;
let lat_us = (lat_ns > 0 && lat_ns < 10_000_000_000)
.then_some((lat_ns / 1000) as u64);
stats.note_received(frame.data.len(), lat_us, clock_offset != 0);
if let Some(hostnet_us) = lat_us {
pending_split.push_back((frame.pts_ns, hostnet_us));
if pending_split.len() > PENDING_SPLIT_CAP {
pending_split.pop_front();
}
}
while let Ok(t) = client.next_host_timing(Duration::ZERO) {
if let Some(i) = pending_split.iter().position(|&(p, _)| p == t.pts_ns)
{
let (_, hostnet_us) = pending_split.remove(i).unwrap();
stats.note_host_split(
t.host_us as u64,
hostnet_us.saturating_sub(t.host_us as u64),
);
}
}
}
}
if ev_tx.send(DecodeEvent::Au(frame, gap)).is_err() {
break; // the decode loop is gone
}
}
Err(PunktfunkError::NoFrame) => {} // timeout — re-check shutdown and poll again
Err(_) => break, // session closed
}
}
}
/// Route one [`DecodeEvent`] into the loop's working sets. Returns `true` only when a parked AU was
/// dropped on overflow (the caller then requests a keyframe).
#[allow(clippy::too_many_arguments)] // two call sites; the freeze gate + flag map are threaded in
fn dispatch_event(
ev: DecodeEvent,
pending_aus: &mut VecDeque<Frame>,
free_inputs: &mut VecDeque<usize>,
ready: &mut Vec<OutputReady>,
fmt_dirty: &mut bool,
fatal: &mut bool,
gate: &mut ReanchorGate,
recovery_flags: &mut VecDeque<(u64, u32)>,
) -> bool {
match ev {
DecodeEvent::Au(f, gap) => {
// A forward frame-index gap arms the freeze; park this AU's flags for the present side to
// fold `on_decoded` (keyed by the pts the codec will echo).
if gap {
gate.arm(Instant::now());
}
recovery_flags.push_back((f.pts_ns / 1000, f.flags));
if recovery_flags.len() > IN_FLIGHT_CAP {
recovery_flags.pop_front();
}
pending_aus.push_back(f);
if pending_aus.len() > FRAME_PARK_CAP {
pending_aus.pop_front(); // sustained overflow — drop oldest, signal a keyframe request
return true;
}
}
DecodeEvent::InputAvailable(i) => free_inputs.push_back(i),
DecodeEvent::OutputAvailable {
index,
pts_us,
decoded_ns,
} => ready.push(OutputReady {
index,
pts_us,
decoded_ns,
}),
DecodeEvent::FormatChanged => *fmt_dirty = true,
DecodeEvent::Error { fatal: f } => {
if f {
*fatal = true;
} else {
// A recoverable/transient codec error is a decode hiccup on a broken reference chain —
// arm the freeze so the concealed output it recovers into is held off the screen.
gate.arm(Instant::now());
}
}
}
false
}
/// Queue as many parked AUs as there are free input buffer slots (async mode: the indices come from
/// `InputAvailable` callbacks, not a dequeue). Each AU is copied into its codec input buffer and
/// submitted; an AU larger than the buffer is DROPPED (+ a recovery keyframe requested) — a
/// truncated AU is corrupt input the decoder chews on silently, poisoning the reference chain.
fn feed_ready(
codec: &MediaCodec,
client: &NativeClient,
pending_aus: &mut VecDeque<Frame>,
free_inputs: &mut VecDeque<usize>,
fed: &mut u64,
oversized_dropped: &mut u64,
) {
while !pending_aus.is_empty() && !free_inputs.is_empty() {
let idx = free_inputs.pop_front().unwrap();
let frame = pending_aus.pop_front().unwrap();
let pts_us = frame.pts_ns / 1000;
let Some(dst) = codec.input_buffer(idx) else {
log::warn!("decode: input_buffer({idx}) returned None — dropping AU");
continue;
};
let au = &frame.data;
if au.len() > dst.len() {
// The slot was never queued, so it stays ours — recycle it for the next AU.
free_inputs.push_front(idx);
*oversized_dropped += 1;
log::warn!(
"decode: AU {} > input buffer {} — dropped ({} so far), requesting keyframe",
au.len(),
dst.len(),
*oversized_dropped
);
let _ = client.request_keyframe();
continue;
}
let n = au.len();
// SAFETY: `au` (wire AU) and `dst` (codec input buffer) are distinct allocations, both valid
// for `n` bytes; `MaybeUninit<u8>` is layout-identical to `u8`, so this initializes dst[..n].
unsafe {
std::ptr::copy_nonoverlapping(au.as_ptr(), dst.as_mut_ptr().cast::<u8>(), n);
}
if let Err(e) = codec.queue_input_buffer_by_index(idx, 0, n, pts_us, 0) {
log::warn!("decode: queue_input_buffer_by_index: {e}");
} else {
*fed += 1;
}
}
}
/// Present only the NEWEST ready output (render = true) and release the rest without rendering — a
/// burst of stale frames on glass is worse than skipping to the freshest (the sync loop's newest-ready
/// policy, callback-driven). Every dequeued buffer, rendered or not, is the HUD's `decoded`
/// measurement point (it finished decoding either way); samples are recorded in pts order so the
/// receipt-map eviction stays monotonic. The presented frame's `(pts, decoded stamp)` is parked in
/// `tracker` for the OnFrameRendered callback — the `display` stage's other endpoint. `ready` is
/// drained.
#[allow(clippy::too_many_arguments)] // one call site; mirrors the sync loop's drain
fn present_ready(
codec: &MediaCodec,
client: &NativeClient,
measure_decode: bool,
ready: &mut Vec<OutputReady>,
stats: &crate::stats::VideoStats,
in_flight: &Mutex<VecDeque<(u64, i128)>>,
clock_offset: i64,
tracker: &DisplayTracker,
rendered: &mut u64,
discarded: &mut u64,
gate: &mut ReanchorGate,
recovery_flags: &mut VecDeque<(u64, u32)>,
) {
if ready.is_empty() {
return;
}
// Pair each output's decode stage (feeds the ABR decode signal always; the HUD histogram only
// while visible) — both consume the receipt map, so enter for either.
if stats.enabled() || measure_decode {
let mut g = in_flight
.lock()
.unwrap_or_else(std::sync::PoisonError::into_inner);
for o in ready.iter() {
note_decoded_pts(
client,
measure_decode,
stats,
&mut g,
clock_offset,
o.pts_us,
o.decoded_ns,
);
}
}
// Fold EVERY output through the gate in pts (== decode) order — even the ones newest-wins discards —
// so the two-mark re-anchor count stays correct; the newest's verdict decides whether it reaches
// glass (`false` = withheld concealment; the SurfaceView keeps the last rendered frame frozen on).
let now = Instant::now();
let last = ready.len() - 1;
let mut skipped: u64 = 0;
for (i, o) in ready.drain(..).enumerate() {
let flags = take_flags(recovery_flags, o.pts_us);
let present = gate.on_decoded(flags, false, now) == GateVerdict::Present;
let render = i == last && present;
match codec.release_output_buffer_by_index(o.index, render) {
Ok(()) if render => {
*rendered += 1;
if stats.enabled() {
tracker.note_rendered(o.pts_us, o.decoded_ns);
}
}
Ok(()) => {
*discarded += 1;
skipped += 1;
}
Err(e) => {
log::warn!(
"decode: release_output_buffer_by_index({}, {render}): {e}",
o.index
)
}
}
}
stats.note_skipped(skipped); // HUD `skipped` counter (newest-wins + held-off drops); no-op hidden
}
@@ -0,0 +1,224 @@
//! Display/frame-rendered tracking, render-callback registration, HDR dataspace mapping.
use ndk::data_space::DataSpace;
use ndk::media::media_codec::MediaCodec;
use ndk::native_window::NativeWindow;
use std::collections::VecDeque;
use std::ffi::c_void;
use std::sync::atomic::{AtomicI64, Ordering};
use std::sync::{Arc, Mutex};
use super::latency::now_realtime_ns;
use super::RENDERED_CAP;
/// `CLOCK_MONOTONIC` now in nanoseconds — the base of the `systemNano` render timestamp the
/// `OnFrameRendered` callback reports (Android's `System.nanoTime`), read only to re-base that
/// stamp onto `CLOCK_REALTIME` (see [`on_frame_rendered`]).
fn now_monotonic_ns() -> i128 {
let mut ts = libc::timespec {
tv_sec: 0,
tv_nsec: 0,
};
// SAFETY: `clock_gettime` with a valid out-pointer is an always-safe syscall.
unsafe { libc::clock_gettime(libc::CLOCK_MONOTONIC, &mut ts) };
ts.tv_sec as i128 * 1_000_000_000 + ts.tv_nsec as i128
}
/// State shared between the decode loop and the `AMediaCodec` `OnFrameRendered` callback (which
/// fires on a codec-internal thread): rendered frames awaiting their render timestamp, so the HUD
/// gets the spec's `display` stage (decoded→displayed) and the `capture→displayed` end-to-end
/// headline (`design/stats-unification.md` — this replaces Android's v1 `capture→decoded`
/// endpoint whenever the platform delivers render callbacks).
pub(super) struct DisplayTracker {
stats: Arc<crate::stats::VideoStats>,
/// Live host-minus-client clock offset (ns) for the skew-corrected end-to-end sample —
/// loaded per callback so mid-stream re-syncs apply. Holding the handle (not the client)
/// keeps the leaked render-callback refcount from pinning the whole session alive.
clock_offset: Arc<AtomicI64>,
/// `(pts_us, decoded_real_ns)` of frames released with `render = true`, in release order,
/// awaiting their callback. Pushes are HUD-gated by the caller, so this stays empty (and the
/// callback early-outs) while the overlay is hidden.
rendered: Mutex<VecDeque<(u64, i128)>>,
}
impl DisplayTracker {
pub(super) fn new(
stats: Arc<crate::stats::VideoStats>,
clock_offset: Arc<AtomicI64>,
) -> Arc<DisplayTracker> {
Arc::new(DisplayTracker {
stats,
clock_offset,
rendered: Mutex::new(VecDeque::new()),
})
}
/// Park one just-rendered frame's `(pts, decoded stamp)` for the render callback to pair.
/// Caller gates on the HUD being visible.
pub(super) fn note_rendered(&self, pts_us: u64, decoded_ns: i128) {
let mut g = self
.rendered
.lock()
.unwrap_or_else(std::sync::PoisonError::into_inner);
g.push_back((pts_us, decoded_ns));
if g.len() > RENDERED_CAP {
g.pop_front(); // render callbacks stopped coming (allowed under load) — evict
}
}
}
/// Register [`on_frame_rendered`] on the codec (`AMediaCodec_setOnFrameRenderedCallback`,
/// **API 33** — "Available since Android T" per the NDK header; only the *Java* listener dates
/// back further). That sits above the API-28 floor, so the entry point is dlsym-resolved at
/// runtime like [`try_set_frame_rate`] — hard-linking it (as 0.9.0 shipped) made
/// `System.loadLibrary` fail on every pre-Android-13 device, taking down all of `NativeBridge`.
/// The `ndk` wrapper has no binding and the call needs the raw codec pointer, which is what the
/// vendored crate's public `as_ptr` patch is for. Returns the userdata pointer holding a leaked
/// `Arc<DisplayTracker>` refcount; the caller MUST reclaim it with [`release_render_callback`]
/// AFTER dropping the codec (`AMediaCodec_delete` is what guarantees no further callback can
/// fire). `None` (nothing to reclaim) if the symbol is absent (API < 33) or the platform refused —
/// the HUD then simply has no `display` stage, exactly the pre-callback behaviour.
pub(super) fn install_render_callback(
codec: &MediaCodec,
tracker: &Arc<DisplayTracker>,
) -> Option<*const DisplayTracker> {
// media_status_t AMediaCodec_setOnFrameRenderedCallback(
// AMediaCodec*, AMediaCodecOnFrameRendered, void*) (API 33)
type SetOnFrameRenderedFn = unsafe extern "C" fn(
*mut ndk_sys::AMediaCodec,
ndk_sys::AMediaCodecOnFrameRendered,
*mut c_void,
) -> ndk_sys::media_status_t;
// SAFETY: `dlopen` of `libmediandk.so`, which the `ndk` media wrapper already links — always
// mapped, so this only bumps its refcount (never closed — process-lifetime handle). `dlsym`
// returns null when the symbol is absent (device below API 33), checked before transmuting the
// non-null pointer to its fn-pointer type.
let set_on_frame_rendered = unsafe {
let lib = libc::dlopen(c"libmediandk.so".as_ptr(), libc::RTLD_NOW);
if lib.is_null() {
return None;
}
let sym = libc::dlsym(lib, c"AMediaCodec_setOnFrameRenderedCallback".as_ptr());
if sym.is_null() {
log::info!("decode: no render callback on this API level (<33) — no display stage");
return None;
}
std::mem::transmute::<*mut c_void, SetOnFrameRenderedFn>(sym)
};
let ud = Arc::into_raw(tracker.clone());
// SAFETY: `codec.as_ptr()` is the live codec this thread owns; `ud` outlives the registration
// (reclaimed only after the codec is deleted, per this function's contract).
let status = unsafe {
set_on_frame_rendered(codec.as_ptr(), Some(on_frame_rendered), ud as *mut c_void)
};
if status == ndk_sys::media_status_t::AMEDIA_OK {
Some(ud)
} else {
log::warn!("decode: setOnFrameRenderedCallback failed ({status:?}) — no display stage");
// SAFETY: registration failed, so the codec never took the reference — reclaim it now.
unsafe { drop(Arc::from_raw(ud)) };
None
}
}
/// Reclaim [`install_render_callback`]'s leaked `Arc` refcount.
///
/// # Safety
/// Call exactly once, and only after the codec the callback was registered on has been dropped —
/// deleting the codec stops its internal threads, so no callback can still be running (or run
/// later) against this pointer.
pub(super) unsafe fn release_render_callback(ud: *const DisplayTracker) {
drop(Arc::from_raw(ud));
}
/// The `AMediaCodecOnFrameRendered` trampoline: fires (possibly batched) on a codec-internal
/// thread once per output frame actually placed on the output surface, with SurfaceFlinger's
/// render timestamp. That timestamp (`system_nano`) is on `CLOCK_MONOTONIC`, so it is re-based
/// onto `CLOCK_REALTIME` here — against monotonic-now at callback time, which also cancels any lag
/// between the frame rendering and the (batchable) callback delivery — to subtract against the
/// receipt/decode stamps and the host capture pts. Records the HUD's `displayed` point:
/// `end-to-end` = capture→displayed (skew-corrected) and `display` = decoded→displayed
/// (single-clock local). Panic-free by construction (poison-proof lock, saturating math) — an
/// unwind out of an `extern "C"` fn would abort the process.
unsafe extern "C" fn on_frame_rendered(
_codec: *mut ndk_sys::AMediaCodec,
userdata: *mut c_void,
media_time_us: i64,
system_nano: i64,
) {
let t = &*(userdata as *const DisplayTracker);
if !t.stats.enabled() {
return; // HUD hidden — the ring is empty too (pushes are caller-gated)
}
let displayed_ns = now_realtime_ns() - (now_monotonic_ns() - system_nano as i128);
let pts_us = media_time_us.max(0) as u64;
// Pair the frame back to its release record, evicting older entries (their callbacks were
// dropped by the platform, or the entry predates a HUD toggle) — same monotonic-eviction
// discipline as `note_decoded_pts`.
let mut decoded_ns = None;
{
let mut g = t
.rendered
.lock()
.unwrap_or_else(std::sync::PoisonError::into_inner);
while let Some(&(p, d)) = g.front() {
if p > pts_us {
break; // future frame — leave it for its own callback
}
g.pop_front();
if p == pts_us {
decoded_ns = Some(d);
break;
}
}
}
let e2e_ns =
displayed_ns + t.clock_offset.load(Ordering::Relaxed) as i128 - pts_us as i128 * 1000;
let e2e_us = (e2e_ns > 0 && e2e_ns < 10_000_000_000).then_some((e2e_ns / 1000) as u64);
let display_us = decoded_ns.map(|d| ((displayed_ns - d).max(0) / 1000) as u64);
t.stats.note_displayed(e2e_us, display_us);
}
/// React to an output-format change by signalling the stream's HDR dataspace on the Surface (SDR
/// streams leave the default alone). The AMediaCodec analogue of the sync loop's `OutputFormatChanged`
/// handling; safe to call repeatedly (`applied_ds` dedups).
pub(super) fn apply_hdr_dataspace(
codec: &MediaCodec,
window: &NativeWindow,
applied_ds: &mut Option<DataSpace>,
) {
if let Some(ds) = hdr_dataspace(codec) {
if *applied_ds != Some(ds) {
match window.set_buffers_data_space(ds) {
Ok(()) => {
*applied_ds = Some(ds);
log::info!("decode: HDR stream → Surface dataspace {ds}");
}
Err(e) => {
log::warn!("decode: set_buffers_data_space({ds}) failed (non-fatal): {e}")
}
}
}
}
}
/// Map the decoder's reported output colour to a BT.2020 HDR dataspace, or `None` for SDR. The
/// integer values are the Android MediaFormat colour constants the NDK shares: COLOR_TRANSFER
/// ST2084 = 6 (PQ/HDR10), HLG = 7; COLOR_RANGE FULL = 1, LIMITED = 2 (the host encodes limited).
pub(super) fn hdr_dataspace(codec: &MediaCodec) -> Option<DataSpace> {
let fmt = codec.output_format();
let full_range = fmt.i32("color-range") == Some(1);
match fmt.i32("color-transfer") {
Some(6) => Some(if full_range {
DataSpace::Bt2020Pq
} else {
DataSpace::Bt2020ItuPq
}),
Some(7) => Some(if full_range {
DataSpace::Bt2020Hlg
} else {
DataSpace::Bt2020ItuHlg
}),
_ => None, // SDR (BT.709 / SDR_VIDEO) or unspecified
}
}
@@ -0,0 +1,83 @@
//! Decode-latency bookkeeping: realtime clock + decoded-pts / user-flags stat recording.
use punktfunk_core::client::NativeClient;
use std::collections::VecDeque;
/// Wall-clock now in nanoseconds (CLOCK_REALTIME basis), to compare against the host-stamped
/// capture `pts_ns` after the skew offset is applied.
pub(super) fn now_realtime_ns() -> i128 {
use std::time::{SystemTime, UNIX_EPOCH};
SystemTime::now()
.duration_since(UNIX_EPOCH)
.map(|d| d.as_nanos() as i128)
.unwrap_or(0)
}
/// HUD `decoded` point for one dequeued output frame, keyed by the echoed `presentationTimeUs`:
/// build the end-to-end (capture→decoded, skew-corrected, clamped to (0, 10 s)) and `decode`
/// (received→decoded, single-clock local, ≥ 0) samples and hand them to
/// [`crate::stats::VideoStats::note_decoded`]. The pts keys the receipt stamp in `in_flight`;
/// entries older than it are evicted (decode order == input order here — low-latency, no
/// B-frames — so anything before it was dropped inside the codec or stamped before a flush).
/// `decoded_ns` is the availability instant: the dequeue (sync loop) or the output callback's
/// stamp (async loop).
pub(super) fn note_decoded_pts(
client: &NativeClient,
measure_decode: bool,
stats: &crate::stats::VideoStats,
in_flight: &mut VecDeque<(u64, i128)>,
clock_offset: i64,
pts_us: u64,
decoded_ns: i128,
) {
// Pair the echoed pts back to its receipt stamp, evicting stale (older) entries as we go.
let mut received_ns = None;
while let Some(&(p, r)) = in_flight.front() {
if p > pts_us {
break; // future frame — leave it for its own output buffer
}
in_flight.pop_front();
if p == pts_us {
received_ns = Some(r);
break;
}
}
let decode_us = received_ns.map(|r| ((decoded_ns - r).max(0) / 1000) as u64);
// Adaptive bitrate: the `decode` stage (received→decoded, single-clock local) IS the decoder-
// backlog signal — the only bottleneck the host-side network signals can't see (a fast LAN
// feeding a slower mobile decoder). Report it whenever the controller is armed, regardless of
// the HUD; `report_decode_us` is a cheap accumulate the pump windows.
if measure_decode {
if let Some(us) = decode_us {
client.report_decode_us(us.min(u32::MAX as u64) as u32);
}
}
// HUD histogram: only while the overlay is visible (a measure-only caller enters here for the
// ABR report alone). `end-to-end` = capture→decoded (skew-corrected) tiles the `decode` stage.
// pts_us is the truncated frame.pts_ns/1000 we queued, so ×1000 re-approximates capture time to
// < 1 µs — negligible against the ms-scale figures shown.
if stats.enabled() {
let e2e_ns = decoded_ns + clock_offset as i128 - pts_us as i128 * 1000;
let e2e_us = (e2e_ns > 0 && e2e_ns < 10_000_000_000).then_some((e2e_ns / 1000) as u64);
stats.note_decoded(e2e_us, decode_us);
}
}
/// The AU `user_flags` for a decoded output, keyed by the echoed `presentationTimeUs`. Recovery
/// signalling (FLAG_SOF IDR marker / RECOVERY_ANCHOR / RECOVERY_POINT) rides the AU's flags, which are
/// only in scope at feed time — so the feed side parks `(pts_us, flags)` here and the present side
/// looks them up to fold [`ReanchorGate::on_decoded`]. Decode order == input order (low-latency, no
/// B-frames), so this evicts entries older than `pts_us` as it goes; a miss (probe filler, or an entry
/// aged past the cap) reads `0` — no recovery flags, decoded normally.
pub(super) fn take_flags(map: &mut VecDeque<(u64, u32)>, pts_us: u64) -> u32 {
while let Some(&(p, f)) = map.front() {
if p > pts_us {
break; // future frame — leave it for its own output buffer
}
map.pop_front();
if p == pts_us {
return f;
}
}
0
}
+84
View File
@@ -0,0 +1,84 @@
//! Android video decode (android-only): pull HEVC access units from the connector and render them
//! to the SurfaceView via NDK `AMediaCodec` — hardware decode, zero per-frame JNI.
//!
//! One-in/one-out: the host opens every stream with an IDR carrying VPS/SPS/PPS **in-band**, so the
//! decoder needs no out-of-band codec-specific data — we configure with mime + the negotiated
//! WxH (from [`NativeClient::mode`]) and feed each access unit as it arrives. The decode thread owns
//! the codec + window for its whole life; [`crate::session`] signals it to stop via the shared flag.
mod async_loop;
mod display;
mod latency;
mod setup;
mod sync_loop;
use async_loop::run_async;
pub(crate) use setup::{codec_label, codec_mime};
use sync_loop::run_sync;
use ndk::native_window::NativeWindow;
use punktfunk_core::client::NativeClient;
use std::sync::atomic::AtomicBool;
use std::sync::Arc;
/// Cap on AUs parked in the async loop awaiting a free codec input slot. Matches the connector's
/// own frame-channel depth; on sustained overflow the oldest is dropped and a keyframe requested
/// (same recovery as a reassembler drop). In steady state this stays near-empty.
const FRAME_PARK_CAP: usize = 16;
/// Cap on the pts→received-timestamp map below: MediaCodec holds only a handful of frames in
/// flight, so anything beyond this is stale (codec flushed / HUD toggled) and gets evicted.
const IN_FLIGHT_CAP: usize = 64;
/// Cap on received AUs awaiting their 0xCF host timing (Phase 2 host/network split): the timing
/// datagram trails its AU by at most the wire, so a match lands within a frame or two — anything
/// this deep is a lost datagram (or an old host that never sends any) and gets evicted.
const PENDING_SPLIT_CAP: usize = 256;
/// Cap on rendered frames parked in [`DisplayTracker`] awaiting their `OnFrameRendered` render
/// timestamp: the callback trails its release by at most a vsync or two, so anything this deep
/// means the platform stopped delivering render callbacks (allowed under load, per the docs) and
/// gets evicted.
const RENDERED_CAP: usize = 64;
/// Whether low-latency mode uses the event-driven async decode loop (default) or the synchronous
/// poll loop. Flip to `false` to A/B the two on the HUD (`design/…`); the async loop presents a
/// decoded frame the instant it's ready instead of waiting out a poll interval. Only consulted when
/// the user's "Low-latency mode" toggle is ON (now the default) — off, the sync loop always runs (the
/// original pipeline, kept as the per-device escape hatch).
const USE_ASYNC_DECODE: bool = true;
/// Per-session decode configuration, resolved by the JNI layer (`nativeStartVideo`) and passed to
/// the decode loop. Bundled so the loop entry points don't sprout a wide argument list.
pub(crate) struct DecodeOptions {
/// The decoder Kotlin ranked from `MediaCodecList` (`VideoDecoders.pickDecoder`). `None`/empty ⇒
/// let the platform resolve the default decoder for the MIME.
pub decoder_name: Option<String>,
/// Whether Kotlin found the chosen decoder advertises `FEATURE_LowLatency` (queryable only via
/// the Java `CodecCapabilities` API) — surfaced on the HUD next to the decoder name.
pub ll_feature: bool,
/// The user's "Low-latency mode" master toggle. On (default) ⇒ the full fast pipeline: async
/// decode loop, per-SoC vendor keys, pipeline thread boosts, ADPF max-performance, forced TV
/// mode switch. Off ⇒ the original synchronous pre-overhaul pipeline, kept as the per-device
/// escape hatch.
pub low_latency_mode: bool,
/// TV form factor (Kotlin's `UiModeManager`): actively drive the HDMI output into the stream's
/// refresh mode, vs. the softer seamless hint on a phone/tablet.
pub is_tv: bool,
}
/// The decode entry point on the `pf-decode` thread: dispatches to the async or synchronous loop.
/// Both run until `shutdown` is set or the session closes.
pub fn run(
client: Arc<NativeClient>,
window: NativeWindow,
shutdown: Arc<AtomicBool>,
stats: Arc<crate::stats::VideoStats>,
opts: DecodeOptions,
) {
if opts.low_latency_mode && USE_ASYNC_DECODE {
run_async(client, window, shutdown, stats, opts);
} else {
run_sync(client, window, shutdown, stats, opts);
}
}
+254
View File
@@ -0,0 +1,254 @@
//! Codec creation, low-latency config, thread/frame-rate tuning, HDR static-info encode.
use ndk::media::media_codec::MediaCodec;
use ndk::media::media_format::MediaFormat;
use ndk::native_window::NativeWindow;
use std::ffi::c_void;
/// The MediaCodec MIME for the codec the host resolved (`Welcome.codec`). Shared by the decode
/// thread and `nativeVideoMime` (which tells Kotlin what to rank decoders for). AV1 uses the
/// AOSP `video/av01` type; anything not H.264/AV1 is treated as HEVC (every pre-negotiation host
/// emitted HEVC).
pub(crate) fn codec_mime(codec: u8) -> &'static str {
match codec {
punktfunk_core::quic::CODEC_H264 => "video/avc",
punktfunk_core::quic::CODEC_AV1 => "video/av01",
_ => "video/hevc",
}
}
/// A short human label for the codec the host resolved, for the stats HUD's video-feed line
/// (`"H.264"` / `"HEVC"` / `"AV1"` / `"PyroWave"`). Mirrors [`codec_mime`]'s fallback: anything
/// not H.264/AV1/PyroWave is reported as HEVC (every pre-negotiation host emitted HEVC). Kept
/// beside [`codec_mime`] because the MIME collapses PyroWave onto `video/hevc` and so can't name it.
pub(crate) fn codec_label(codec: u8) -> &'static str {
match codec {
punktfunk_core::quic::CODEC_H264 => "H.264",
punktfunk_core::quic::CODEC_AV1 => "AV1",
punktfunk_core::quic::CODEC_PYROWAVE => "PyroWave",
_ => "HEVC",
}
}
/// Create the decoder: prefer the specific codec Kotlin ranked from `MediaCodecList`
/// (`from_codec_name`), falling back to the platform's default decoder for the MIME
/// (`from_decoder_type`) if that name can't be created (codec busy / renamed across an OS update).
pub(super) fn create_codec(mime: &str, preferred: Option<&str>) -> Option<MediaCodec> {
if let Some(name) = preferred.filter(|n| !n.is_empty()) {
if let Some(c) = MediaCodec::from_codec_name(name) {
return Some(c);
}
log::warn!(
"decode: from_codec_name({name}) failed — falling back to default {mime} decoder"
);
}
MediaCodec::from_decoder_type(mime)
}
/// Apply the low-latency MediaFormat keys for `codec_name`.
///
/// `aggressive` = the "Low-latency mode" master toggle. **Off** ⇒ the pre-overhaul key set,
/// byte-for-byte — the standard `low-latency` key, the blind Qualcomm vendor twin, `priority = 0` AND
/// `operating-rate = MAX` set together — kept as the per-device escape hatch (the profile every device
/// streamed with before the overhaul). **On** (default) ⇒ the Moonlight-parity
/// profile: MediaTek's `vdec-lowlatency` (unconditionally — ignored off MediaTek), the per-SoC
/// vendor extension keys (gated on the decoder-name prefix the way Moonlight-Android does, since a
/// key one vendor honours is meaningless on another), and one *mutually exclusive* clock hint.
///
/// Vendor keys mirror Moonlight's `MediaCodecHelper` (verified against current source): Qualcomm
/// picture-order + low-latency, Exynos (also Google Tensor), Amlogic, HiSilicon, MediaTek. NVIDIA
/// Tegra / Rockchip / Realtek expose no such key (nor does Moonlight) — they're covered by the
/// standard key + clock hint + being ranked first in `VideoDecoders`.
pub(super) fn configure_low_latency(format: &mut MediaFormat, codec_name: &str, aggressive: bool) {
// Standard key: request the no-reorder low-latency path where the platform decoder supports it.
format.set_i32("low-latency", 1);
if !aggressive {
// The original profile: the Qualcomm vendor twin set blind (unknown keys are ignored by
// other vendors' codecs), realtime priority, and the AOSP "unbounded" operating-rate
// sentinel — decode each frame at max clocks rather than pacing to the frame rate.
format.set_i32("vendor.qti-ext-dec-low-latency.enable", 1);
format.set_i32("priority", 0); // 0 = realtime
format.set_i32("operating-rate", i16::MAX as i32); // 32767 = "as fast as possible"
return;
}
// MediaTek's low-latency key — very common (mid/budget phones + many Google TV / Fire TV boxes).
// Set unconditionally like the standard key: MediaTek decoders honour it, others ignore it, so it
// covers MediaTek whatever the exact decoder name (omx.mtk / c2.mtk / an OEM rename). Moonlight
// does the same, and also relies on it for Amazon's Amlogic fork.
format.set_i32("vdec-lowlatency", 1);
let name = codec_name.to_ascii_lowercase();
let is = |prefix: &str| name.starts_with(prefix);
// Qualcomm Snapdragon (the most common phone SoC): picture-order forces decode-order output
// (kills the reorder buffer on decoders that predate the standard key); low-latency is the older
// vendor twin.
if is("omx.qcom") || is("c2.qti") {
format.set_i32("vendor.qti-ext-dec-picture-order.enable", 1);
format.set_i32("vendor.qti-ext-dec-low-latency.enable", 1);
}
// Samsung Exynos — also covers Google Tensor (Pixel 6+), whose hardware decoder is `c2.exynos.*`.
if is("omx.exynos") || is("c2.exynos") {
format.set_i32("vendor.rtc-ext-dec-low-latency.enable", 1);
}
// Amlogic — the Android TV boxes (onn 4K, Chromecast w/ Google TV, Homatics).
if is("omx.amlogic") || is("c2.amlogic") {
format.set_i32("vendor.low-latency.enable", 1);
}
// HiSilicon / Kirin (older Huawei; paired req/rdy keys).
if is("omx.hisi") || is("c2.hisi") {
format.set_i32(
"vendor.hisi-ext-low-latency-video-dec.video-scene-for-low-latency-req",
1,
);
format.set_i32(
"vendor.hisi-ext-low-latency-video-dec.video-scene-for-low-latency-rdy",
-1,
);
}
// NVIDIA Tegra (Shield TV) and Rockchip/Realtek (budget TV boxes / smart TVs) expose no
// low-latency vendor key (Moonlight has none either) — their decoders are already low-latency
// oriented, so the standard `low-latency` key + the clock hint below + being ranked first
// (see `VideoDecoders`) is their treatment.
//
// Clock hint, mutually exclusive (matching Moonlight): the AOSP "unbounded" operating-rate
// sentinel (Short.MAX) tells the decoder to run each frame at max clocks and finish ASAP rather
// than pace to the frame rate — shaving per-frame decode latency at a power/heat cost. Only
// Qualcomm is known to handle the sentinel; every other vendor mis-paces on it, so they get the
// plain realtime `priority` hint instead.
if decoder_supports_max_operating_rate(&name) {
format.set_i32("operating-rate", i16::MAX as i32); // 32767 = "as fast as possible"
} else {
format.set_i32("priority", 0); // 0 = realtime
}
}
/// Whether a decoder tolerates `operating-rate = Short.MAX` rather than regressing on it. Follows
/// Moonlight's allowlist: Qualcomm decoders honour the sentinel (the Adreno 620 generation is the
/// known exception Moonlight excludes by GPU model — undetectable from native code here, so it
/// rides the master toggle as its escape hatch). Other vendors fall back to the plain `priority`
/// hint above.
fn decoder_supports_max_operating_rate(name_lower: &str) -> bool {
name_lower.starts_with("omx.qcom") || name_lower.starts_with("c2.qti")
}
/// Raise the pipeline's OTHER hot threads — the core's data-plane pump (UDP receive + FEC
/// reassembly) and the audio decode thread — toward the display band, matching this decode thread's
/// own boost. `setpriority(PRIO_PROCESS, tid)` targets any task in the process, so we do it from
/// here once their tids are known (the same set ADPF hints), without a per-platform priority hook
/// in the shared core. Slightly below the decode thread's -10 so the display path still wins.
/// Best-effort; skips this thread (already boosted) and is non-fatal if the platform refuses.
pub(super) fn boost_hot_threads(tids: &[i32]) {
// SAFETY: `gettid` is an always-safe syscall on the calling thread.
let self_tid = unsafe { libc::gettid() };
for &tid in tids {
if tid == self_tid {
continue;
}
// SAFETY: `setpriority` with PRIO_PROCESS + a live tid in our own process is an always-safe
// syscall; a refusal is reported via the return value, not UB.
unsafe {
if libc::setpriority(libc::PRIO_PROCESS, tid as libc::id_t, -8) != 0 {
log::debug!("decode: setpriority(-8) on hot tid {tid} failed (non-fatal)");
}
}
}
}
/// Best-effort: raise the decode thread toward Android's URGENT_DISPLAY band so background work
/// can't preempt it under load (which shows up as late/dropped frames). Non-fatal if the platform
/// refuses (foreground apps may set their own threads; the exact floor is policy-dependent).
pub(super) fn boost_thread_priority() {
// SAFETY: `gettid`/`setpriority` on the calling thread are always-safe syscalls. PRIO_PROCESS
// with a TID targets that one task on Linux — the same idiom `Process.setThreadPriority` uses.
unsafe {
let tid = libc::gettid();
if libc::setpriority(libc::PRIO_PROCESS, tid as libc::id_t, -10) != 0 {
log::warn!(
"decode: setpriority(-10) failed (non-fatal): {}",
std::io::Error::last_os_error()
);
}
}
}
/// Set the surface's frame-rate hint to the stream's refresh so SurfaceFlinger picks a matching
/// display mode and aligns vsync (no 60-in-120 judder). Both NDK entry points sit above our API-28
/// floor, so both are dlsym-resolved at runtime (a hard import of a >floor symbol makes
/// `dlopen`/`System.load` fail on every API-28/29 device, even where this path is never hit —
/// mirrors [`crate::adpf`]):
/// - On a **TV** (`is_tv`): `ANativeWindow_setFrameRateWithChangeStrategy` (**API 31**) with
/// `changeFrameRateStrategy = ALWAYS`, which actively drives the HDMI output into the matching
/// mode (e.g. 60↔120) instead of leaving the panel at its default and judder-matching. The
/// forced switch may blank the panel briefly — acceptable once at stream start, not wanted on a
/// phone. Falls through to the 2-arg hint on API 30.
/// - Otherwise: `ANativeWindow_setFrameRate` (**API 30**) with `compatibility = DEFAULT` — the
/// softer, seamless-preferred hint for phones/tablets and the universal fallback.
///
/// Returns `true` when the platform accepted a hint; `false` on API < 30 (symbols absent) or a
/// decline.
pub(super) fn try_set_frame_rate(window: &NativeWindow, frame_rate: f32, is_tv: bool) -> bool {
// int32_t ANativeWindow_setFrameRate(ANativeWindow*, float frameRate, int8_t compatibility)
type SetFrameRateFn = unsafe extern "C" fn(*mut c_void, f32, i8) -> i32;
// int32_t ANativeWindow_setFrameRateWithChangeStrategy(
// ANativeWindow*, float frameRate, int8_t compatibility, int8_t changeFrameRateStrategy)
type SetFrameRateStrategyFn = unsafe extern "C" fn(*mut c_void, f32, i8, i8) -> i32;
// SAFETY: `dlopen` of the always-mapped `libandroid.so` (only bumps its refcount; never closed —
// process-lifetime handle). Each `dlsym` returns null when the symbol is absent (device below the
// symbol's API level), checked before transmuting the non-null pointer to its fn-pointer type.
// `window.ptr()` is the live `ANativeWindow` this `NativeWindow` owns for the call's duration.
unsafe {
let lib = libc::dlopen(c"libandroid.so".as_ptr(), libc::RTLD_NOW);
if lib.is_null() {
return false;
}
// TV: prefer the API-31 change-strategy form to force the mode switch (strategy 1 = ALWAYS,
// compatibility 0 = DEFAULT). Absent on API 30 ⇒ fall through to the 2-arg hint below.
if is_tv {
let sym = libc::dlsym(
lib,
c"ANativeWindow_setFrameRateWithChangeStrategy".as_ptr(),
);
if !sym.is_null() {
let set = std::mem::transmute::<*mut c_void, SetFrameRateStrategyFn>(sym);
return set(window.ptr().as_ptr().cast(), frame_rate, 0, 1) == 0;
}
}
let sym = libc::dlsym(lib, c"ANativeWindow_setFrameRate".as_ptr());
if sym.is_null() {
return false; // device API < 30 — no per-surface frame-rate hint
}
let set_frame_rate = std::mem::transmute::<*mut c_void, SetFrameRateFn>(sym);
set_frame_rate(window.ptr().as_ptr().cast(), frame_rate, 0) == 0
}
}
/// Serialize [`HdrMeta`](punktfunk_core::quic::HdrMeta) into Android's `KEY_HDR_STATIC_INFO`
/// (`hdr-static-info`) layout: a 25-byte CTA-861.3 / `HDRStaticInfo.Type1` blob — descriptor id 0,
/// then primaries in **R, G, B** order, white point, max/min display luminance, MaxCLL, MaxFALL, all
/// **little-endian** `u16`. Two conversions vs our wire form: HdrMeta stores primaries in ST.2086
/// **G, B, R** order (reorder to R, G, B), and `max_display_mastering_luminance` is in 0.0001-cd/m²
/// units while Android wants **whole nits** (min stays 0.0001-nit). Chromaticities (1/50000) and
/// MaxCLL/MaxFALL (nits) match 1:1.
pub(super) fn android_hdr_static_info(m: &punktfunk_core::quic::HdrMeta) -> [u8; 25] {
let [g, b_, r] = m.display_primaries; // ST.2086 G, B, R
let max_nits = (m.max_display_mastering_luminance / 10_000).min(u16::MAX as u32) as u16;
let min_units = m.min_display_mastering_luminance.min(u16::MAX as u32) as u16;
let fields: [u16; 12] = [
r[0],
r[1],
g[0],
g[1],
b_[0],
b_[1], // R, G, B primaries
m.white_point[0],
m.white_point[1], // white point
max_nits,
min_units, // max (nits) / min (0.0001-nit) display luminance
m.max_cll,
m.max_fall, // MaxCLL / MaxFALL (nits)
];
let mut out = [0u8; 25]; // out[0] = 0 (Type 1 descriptor id), already zero
for (i, v) in fields.iter().enumerate() {
out[1 + i * 2..3 + i * 2].copy_from_slice(&v.to_le_bytes());
}
out
}
@@ -0,0 +1,547 @@
//! The synchronous MediaCodec decode loop (the original poll path) + its feed/drain helpers.
use ndk::data_space::DataSpace;
use ndk::media::media_codec::{
DequeuedInputBufferResult, DequeuedOutputBufferInfoResult, MediaCodec, MediaCodecDirection,
OutputBuffer,
};
use ndk::media::media_format::MediaFormat;
use ndk::native_window::NativeWindow;
use punktfunk_core::client::NativeClient;
use punktfunk_core::error::PunktfunkError;
use punktfunk_core::reanchor::{GateVerdict, ReanchorGate};
use punktfunk_core::session::Frame;
use std::collections::VecDeque;
use std::sync::atomic::{AtomicBool, Ordering};
use std::sync::Arc;
use std::time::{Duration, Instant};
use super::display::{
hdr_dataspace, install_render_callback, release_render_callback, DisplayTracker,
};
use super::latency::{note_decoded_pts, now_realtime_ns, take_flags};
use super::setup::{
android_hdr_static_info, boost_hot_threads, boost_thread_priority, codec_mime,
configure_low_latency, create_codec, try_set_frame_rate,
};
use super::{DecodeOptions, IN_FLIGHT_CAP, PENDING_SPLIT_CAP};
/// The synchronous poll loop — the original decode path: the only one when low-latency mode is off,
/// and the [`USE_ASYNC_DECODE`] A/B fallback when it's on. Feeds and drains on this one thread; the
/// only blocking wait is a short output dequeue while input is backed up.
pub(super) fn run_sync(
client: Arc<NativeClient>,
window: NativeWindow,
shutdown: Arc<AtomicBool>,
stats: Arc<crate::stats::VideoStats>,
opts: DecodeOptions,
) {
let DecodeOptions {
decoder_name,
ll_feature,
low_latency_mode,
is_tv,
} = opts;
boost_thread_priority();
let mode = client.mode();
// The MediaCodec MIME for the codec the host resolved (`Welcome.codec`). AMediaCodec needs no
// out-of-band extradata — the in-band VPS/SPS/PPS on every IDR configure it either way.
let mime = codec_mime(client.codec);
let codec = match create_codec(mime, decoder_name.as_deref()) {
Some(c) => c,
None => {
log::error!("decode: no {mime} decoder on this device");
return;
}
};
// The decoder's *actual* resolved name (Kotlin's pick, or the platform default when it fell
// back) drives both the HUD label and which vendor low-latency keys apply below.
let codec_name = codec.name().unwrap_or_default();
stats.set_decoder(&codec_name, ll_feature);
log::info!(
"decode: codec mime = {mime}, decoder = {codec_name} (low-latency feature: {ll_feature})"
);
let mut format = MediaFormat::new();
format.set_str("mime", mime);
format.set_i32("width", mode.width as i32);
format.set_i32("height", mode.height as i32);
// Generous input buffer so a large keyframe AU is never truncated.
format.set_i32(
"max-input-size",
(mode.width * mode.height).max(2_000_000) as i32,
);
// Standard + per-SoC vendor low-latency keys and the clock hints, gated on the resolved decoder
// name and the master toggle (see `configure_low_latency`).
configure_low_latency(&mut format, &codec_name, low_latency_mode);
// HDR static metadata (ST.2086 mastering + content light level): when an HDR session was
// negotiated, set KEY_HDR_STATIC_INFO so the display tone-maps from the source's real grade.
// MediaCodec wants it BEFORE configure(), and the host sends a 0xCE right after the handshake,
// so it's typically already queued; wait briefly otherwise. The Surface DataSpace (applied on
// OutputFormatChanged below) carries transfer/primaries regardless — this adds the luminance the
// tone-mapper needs. A non-HDR display still gets sensible SurfaceFlinger tone-mapping.
if client.color.is_hdr() {
match client.next_hdr_meta(Duration::from_millis(250)) {
Ok(meta) => {
format.set_buffer("hdr-static-info", &android_hdr_static_info(&meta));
log::info!("decode: HDR static metadata applied (KEY_HDR_STATIC_INFO)");
}
Err(_) => {
log::info!("decode: HDR session but no mastering metadata yet — DataSpace only")
}
}
}
if let Err(e) = codec.configure(&format, Some(&window), MediaCodecDirection::Decoder) {
log::error!("decode: configure failed: {e}");
return;
}
if let Err(e) = codec.start() {
log::error!("decode: start failed: {e}");
return;
}
log::info!(
"decode: {mime} decoder started at {}x{}",
mode.width,
mode.height
);
// Tell the display the stream's refresh so Android can pick a matching display mode and align
// vsync (no 60-in-120 judder on high-refresh panels). `ANativeWindow_setFrameRate` is NDK API 30,
// above our API-28 floor, so we resolve it at runtime (see `try_set_frame_rate`) rather than link
// it — a hard import would stop `libpunktfunk_android.so` loading at all on API 28/29. Absent
// there ⇒ we simply skip the hint (non-fatal; the stream renders fine without it).
// The forced TV mode switch (`is_tv` ⇒ ALWAYS strategy) is part of the experimental stack;
// off, every form factor gets the original soft seamless hint.
if mode.refresh_hz > 0
&& !try_set_frame_rate(&window, mode.refresh_hz as f32, is_tv && low_latency_mode)
{
log::debug!(
"decode: set_frame_rate({} Hz) unavailable/declined (non-fatal)",
mode.refresh_hz
);
}
// ADPF: hint the platform that the whole video pipeline — this pf-decode feed/drain/present
// loop, the core's data-plane pump (UDP receive + FEC reassembly), and the audio thread — runs a
// per-frame real-time workload, so the CPU governor keeps those threads on fast cores at high
// clocks instead of down-clocking between frames or parking them on a little core. Snapdragon's
// ADPF backend responds well to this. We register this thread now but create the session lazily
// on the first presented frame: by then the pump + audio threads have registered their ids too,
// and ADPF `createSession` rejects a set with any not-yet-live/dead tid. No-op below API 33.
let frame_period_ns = if mode.refresh_hz > 0 {
1_000_000_000i64 / mode.refresh_hz as i64
} else {
0
};
client.register_hot_thread(); // this decode thread → the pipeline's hot-thread set
let mut hint: Option<crate::adpf::HintSession> = None;
let mut hint_tried = false;
// Accumulates the loop's productive (feed+drain) time between displayed frames; reported to ADPF
// once per rendered frame against the frame-period target.
let mut work_accum_ns: i64 = 0;
let mut fed: u64 = 0;
let mut rendered: u64 = 0;
let mut discarded: u64 = 0;
// AUs larger than the codec input buffer, dropped whole (see `feed`/`feed_ready`).
let mut oversized_dropped: u64 = 0;
// The AU waiting for a free codec input buffer. `feed` is non-blocking; on transient input
// pressure the AU stays parked here instead of being dropped (a drop forces a keyframe
// round-trip) and we only pop the next one once it's queued.
let mut pending: Option<Frame> = None;
// Freeze-until-reanchor: the shared post-loss gate ([`punktfunk_core::reanchor::ReanchorGate`]).
// Armed on a frame-index gap or a dropped-count climb, it withholds the decoder's concealed output
// (released WITHOUT rendering — the SurfaceView keeps the last rendered frame on glass) until a
// proven clean re-anchor lifts it: an IDR (wire FLAG_SOF), an RFI anchor, or the 2nd recovery mark.
// `last_kf_req` throttles the keyframe intents it emits; `recovery_flags` carries each AU's
// user_flags from feed to present (keyed by the codec-echoed pts) so `on_decoded` reads the
// re-anchor signalling the platform decoder doesn't expose.
let mut gate = ReanchorGate::new(client.frames_dropped());
let mut recovery_flags: VecDeque<(u64, u32)> = VecDeque::new();
let mut last_kf_req: Option<Instant> = None;
// Skew-corrected latency stats (spec: design/stats-unification.md) use the negotiated
// host-minus-client clock offset (0 if the host didn't answer the skew handshake — then the
// HUD flags it "(same-host clock)").
let clock_offset = client.clock_offset_shared();
// Display stage (spec `display` + the capture→displayed headline): frames released with
// render = true are parked in the tracker; the OnFrameRendered callback pairs them with
// SurfaceFlinger's render timestamp. `render_cb` is the callback's leaked Arc refcount,
// reclaimed after the codec is dropped below.
let tracker = DisplayTracker::new(stats.clone(), clock_offset.clone());
let render_cb = install_render_callback(&codec, &tracker);
// Receipt timestamps keyed by the pts we queue into the codec, so the decoded point (output-
// buffer dequeue — MediaCodec round-trips presentationTimeUs) can be paired back to its receipt
// for the `decode` stage. Fed while the HUD is visible OR the adaptive-bitrate controller wants
// the decode signal (`measure_decode`) — the decoder-backlog bottleneck the network can't see.
let measure_decode = client.wants_decode_latency();
let mut in_flight: VecDeque<(u64, i128)> = VecDeque::new();
// Phase-2 host/network split (design/stats-unification.md): received AUs awaiting their 0xCF
// host timing, as (pts_ns, capture→received µs). The timings are drained non-blockingly right
// where receipts are recorded and matched by pts; `network = hostnet host` (saturating).
// Only fed while the HUD is visible; an old host never sends a 0xCF, so entries just age out.
let mut pending_split: VecDeque<(u64, u64)> = VecDeque::new();
// The dataspace we've signalled on the Surface so far (None = default/SDR). Set reactively once
// the decoder reports an HDR stream (see `drain`); avoids re-applying every format event.
let mut applied_ds: Option<DataSpace> = None;
// One thread feeds AND drains: the NDK AMediaCodec wrapper isn't documented thread-safe for
// cross-thread feed/drain, so instead of splitting threads the loop decouples the two — input
// dequeue is non-blocking (never stalls presentation of already-decoded frames) and the only
// blocking wait is a short output dequeue while input is backed up (decoder progress is exactly
// what frees the next input buffer).
while !shutdown.load(Ordering::Relaxed) {
if pending.is_none() {
match client.next_frame(Duration::from_millis(5)) {
Ok(frame) => {
// Loss recovery (RFI): feed the frame index so a forward gap fires a throttled
// reference-frame-invalidation request — an RFI-capable host (AMD LTR / NVENC)
// recovers with a cheap clean P-frame instead of a full IDR. The same forward gap
// arms the freeze gate so the decoder's concealment is held off the screen until the
// recovery re-anchors. The frames_dropped keyframe path below stays the backstop.
if client.note_frame_index(frame.frame_index) {
gate.arm(Instant::now());
}
// Park this AU's re-anchor flags for the present side (keyed by the pts the codec
// echoes on the output buffer) — unconditional, unlike the HUD's `in_flight` map.
recovery_flags.push_back((frame.pts_ns / 1000, frame.flags));
if recovery_flags.len() > IN_FLIGHT_CAP {
recovery_flags.pop_front();
}
if fed == 0 {
let p = &frame.data;
log::info!(
"decode: first AU {} bytes, head {:02x?}",
p.len(),
&p[..p.len().min(6)]
);
}
// Receipt stamp for the `decode` stage pairing, parked in `in_flight` (keyed by
// the pts the codec echoes on its output buffer) whenever it's needed: the HUD
// being visible, or the ABR decode signal (`measure_decode`). The HUD-only
// samplers (`received` point, host/network split) stay gated on the overlay so
// the hidden steady state adds only a wall-clock read + the receipt push.
if stats.enabled() || measure_decode {
let received_ns = now_realtime_ns();
in_flight.push_back((frame.pts_ns / 1000, received_ns));
if in_flight.len() > IN_FLIGHT_CAP {
in_flight.pop_front(); // stale — codec never echoed it back
}
// HUD stat, `received` point: host+network = client_now + (hostclient)
// capture_pts.
if stats.enabled() {
let clock_offset = clock_offset.load(Ordering::Relaxed);
let lat_ns = received_ns + clock_offset as i128 - frame.pts_ns as i128;
let lat_us = (lat_ns > 0 && lat_ns < 10_000_000_000)
.then_some((lat_ns / 1000) as u64);
stats.note_received(frame.data.len(), lat_us, clock_offset != 0);
// Phase-2 split: park this AU's capture→received sample, then match any
// 0xCF host timings that have arrived — host = the host's own
// capture→sent, network = our capture→received minus it (per-frame
// tiling; saturating in case of clock jitter).
if let Some(hostnet_us) = lat_us {
pending_split.push_back((frame.pts_ns, hostnet_us));
if pending_split.len() > PENDING_SPLIT_CAP {
pending_split.pop_front(); // 0xCF lost / old host — evict
}
}
while let Ok(t) = client.next_host_timing(Duration::ZERO) {
if let Some(i) =
pending_split.iter().position(|&(p, _)| p == t.pts_ns)
{
let (_, hostnet_us) = pending_split.remove(i).unwrap();
stats.note_host_split(
t.host_us as u64,
hostnet_us.saturating_sub(t.host_us as u64),
);
}
}
}
}
pending = Some(frame);
}
Err(PunktfunkError::NoFrame) => {} // timeout — still drain output below
Err(_) => break, // session closed
}
}
// Time the productive work (feed + drain) only — the `next_frame` poll wait above is idle
// and excluded, so ADPF sees this thread's real per-frame CPU cost, not the poll timeout.
let work_t0 = Instant::now();
if let Some(frame) = pending.take() {
if feed(
&codec,
&client,
&frame.data,
frame.pts_ns / 1000,
&mut oversized_dropped,
) {
fed += 1;
if fed % 300 == 0 {
log::info!("decode: fed={fed} rendered={rendered} discarded={discarded}");
}
} else {
// No input buffer free — transient back-pressure. Keep the AU and let `drain` block
// briefly below; a released output buffer is what recycles an input slot.
pending = Some(frame);
}
}
// Drain every iteration. When input is blocked, wait ~2 ms on output so the loop rides
// decoder progress instead of busy-spinning against a full input queue.
let wait = if pending.is_some() {
Duration::from_millis(2)
} else {
Duration::ZERO
};
let (r, d) = drain(
&codec,
&client,
measure_decode,
&window,
&mut applied_ds,
wait,
&stats,
&mut in_flight,
clock_offset.load(Ordering::Relaxed),
&tracker,
&mut gate,
&mut recovery_flags,
);
rendered += r;
discarded += d;
// ADPF: attribute this iteration's feed+drain time to the frame being produced, and report
// the accumulated per-frame work once one is actually presented (r > 0). Under back-pressure
// the short output-dequeue wait is included in the tally — for a latency-first client,
// biasing the governor toward "boost" is the desired behaviour. Cheap when `hint` is None
// (one `Instant` diff, no report).
work_accum_ns += work_t0.elapsed().as_nanos() as i64;
if r > 0 {
if !hint_tried {
// First presented frame: the pump + audio threads have registered their ids by now.
// Build one ADPF session over the whole pipeline's thread set (empty below API 33,
// or where the platform declines → `None`, and the loop runs unhinted).
hint_tried = true;
let tids = client.hot_thread_ids();
// The pump/audio priority boost is part of the experimental low-latency stack; the
// ADPF session itself predates it and always runs (max-performance bias gated inside).
if low_latency_mode {
boost_hot_threads(&tids);
}
hint = crate::adpf::HintSession::create(frame_period_ns, &tids, low_latency_mode);
log::info!(
"decode: ADPF hint session {} — {} hot thread(s), target {frame_period_ns} ns",
if hint.is_some() {
"active"
} else {
"unavailable"
},
tids.len(),
);
}
if let Some(h) = &hint {
h.report_actual(work_accum_ns);
}
work_accum_ns = 0;
}
// Loss recovery + overdue backstop, folded through the gate. Under infinite GOP the only
// recovery keyframe is one we request; the reassembler drops unrecoverable AUs (frames_dropped)
// and the decoder then conceals the reference-missing deltas and renders them without error, so
// a decode-error trigger rarely fires — the gate arms the freeze on the drop-count climb
// instead. An overdue freeze (held REANCHOR_FREEZE_MAX with no clean re-anchor) re-asks while it
// keeps holding: never resume to gray — a dead stream is the QUIC idle-timeout watchdog's job.
let now = Instant::now();
if gate.poll(client.frames_dropped(), now)
&& last_kf_req.is_none_or(|t| now.duration_since(t) >= Duration::from_millis(100))
{
last_kf_req = Some(now);
let _ = client.request_keyframe();
log::debug!("decode: requested keyframe (loss recovery / overdue re-anchor)");
}
}
let _ = codec.stop();
drop(codec); // AMediaCodec_delete — after this no render callback can fire
if let Some(ud) = render_cb {
// SAFETY: the codec was dropped above; this registration's single reclaim.
unsafe { release_render_callback(ud) };
}
log::info!("decode: stopped (fed={fed} rendered={rendered} discarded={discarded})");
}
/// Try to copy one access unit into a codec input buffer and queue it, without blocking. Returns
/// `false` only on `TryAgainLater` (no input buffer free) — the caller keeps the AU pending and
/// retries; a hard dequeue/queue error counts as consumed (retrying can't salvage the AU, and
/// parking it forever would wedge the loop on a broken codec). An AU larger than the input
/// buffer is DROPPED (+ a recovery keyframe requested), never truncated — a truncated AU is
/// corrupt input the decoder chews on silently, poisoning the reference chain.
fn feed(
codec: &MediaCodec,
client: &NativeClient,
au: &[u8],
pts_us: u64,
oversized_dropped: &mut u64,
) -> bool {
match codec.dequeue_input_buffer(Duration::ZERO) {
Ok(DequeuedInputBufferResult::Buffer(mut buf)) => {
let n = {
let dst = buf.buffer_mut();
if au.len() > dst.len() {
*oversized_dropped += 1;
log::warn!(
"decode: AU {} > input buffer {} — dropped ({} so far), requesting keyframe",
au.len(),
dst.len(),
*oversized_dropped
);
let _ = client.request_keyframe();
0 // return the slot with zero valid bytes — a no-op input, not corrupt data
} else {
let n = au.len();
// SAFETY: `au` and `dst` are distinct allocations (wire AU vs. codec buffer),
// both valid for `n` bytes; `MaybeUninit<u8>` is layout-identical to `u8`, so
// the cast write initializes exactly `dst[..n]`.
unsafe {
std::ptr::copy_nonoverlapping(
au.as_ptr(),
dst.as_mut_ptr().cast::<u8>(),
n,
);
}
n
}
};
if let Err(e) = codec.queue_input_buffer(buf, 0, n, pts_us, 0) {
log::warn!("decode: queue_input_buffer: {e}");
}
true
}
Ok(DequeuedInputBufferResult::TryAgainLater) => false, // caller keeps the AU pending
Err(e) => {
log::warn!("decode: dequeue_input_buffer: {e}");
true
}
}
}
/// Dequeue every ready output buffer and present only the NEWEST (render = true), discarding the
/// rest (render = false) — when decode falls behind, a back-to-back burst of stale frames on glass
/// is worse than skipping straight to the freshest one (the Apple client's 1-slot newest-ready
/// ring, ported). `first_wait` is the timeout for the first dequeue only: zero normally, ~2 ms when
/// the caller's input is blocked so the loop waits on decoder progress instead of busy-spinning.
/// Returns `(rendered, discarded)`. Also reacts to `OutputFormatChanged` (which can interleave
/// between buffers — handled without losing the held buffer) to signal HDR on the Surface.
///
/// Each dequeued buffer is also the HUD's `decoded` measurement point (rendered or not — the frame
/// finished decoding either way): end-to-end = decoded + clock_offset capture pts, and the
/// `decode` stage pairs the buffer's echoed presentationTimeUs back to the receipt stamp in
/// `in_flight` (single-clock local difference, no skew involved). The presented frame's
/// `(pts, decoded stamp)` is additionally parked in `tracker` for the OnFrameRendered callback —
/// the `display` stage's other endpoint.
#[allow(clippy::too_many_arguments)] // one call site; mirrors the async loop's present_ready
fn drain(
codec: &MediaCodec,
client: &NativeClient,
measure_decode: bool,
window: &NativeWindow,
applied_ds: &mut Option<DataSpace>,
first_wait: Duration,
stats: &crate::stats::VideoStats,
in_flight: &mut VecDeque<(u64, i128)>,
clock_offset: i64,
tracker: &DisplayTracker,
gate: &mut ReanchorGate,
recovery_flags: &mut VecDeque<(u64, u32)>,
) -> (u64, u64) {
// Newest ready buffer so far (presented after the loop) with its HUD metadata —
// `Some((pts_us, decoded_ns))` only while the HUD is visible. `held_present` is the freeze gate's
// verdict for that newest buffer (`false` = a post-loss concealment to withhold).
let mut held: Option<(OutputBuffer<'_>, Option<(u64, i128)>)> = None;
let mut held_present = true;
let mut discarded: u64 = 0;
let mut wait = first_wait;
loop {
match codec.dequeue_output_buffer(wait) {
Ok(DequeuedOutputBufferInfoResult::Buffer(buf)) => {
// Only the first dequeue may block; later ones poll (wait == ZERO).
wait = Duration::ZERO;
// Fold every dequeued frame through the gate in pts (== decode) order — even the ones
// the newest-wins policy discards — so the two-mark re-anchor count stays correct; the
// verdict of the newest (last folded) buffer decides whether it reaches glass.
let pts_us = buf.info().presentation_time_us().max(0) as u64;
let flags = take_flags(recovery_flags, pts_us);
held_present =
gate.on_decoded(flags, false, Instant::now()) == GateVerdict::Present;
let meta = if stats.enabled() || measure_decode {
// The dequeue IS the sync loop's decoded-availability instant.
let decoded_ns = now_realtime_ns();
note_decoded_pts(
client,
measure_decode,
stats,
in_flight,
clock_offset,
pts_us,
decoded_ns,
);
// The tracker's `display` stage is a HUD concern — park only when visible.
stats.enabled().then_some((pts_us, decoded_ns))
} else {
None
};
if let Some((stale, _)) = held.replace((buf, meta)) {
// A newer frame is ready — drop the held one without rendering.
if let Err(e) = codec.release_output_buffer(stale, false) {
log::warn!("decode: release_output_buffer(discard): {e}");
}
discarded += 1;
stats.note_skipped(1); // HUD `skipped` counter; no-op while hidden
}
}
Ok(DequeuedOutputBufferInfoResult::OutputFormatChanged) => {
// The decoder has parsed the SPS and now reports the stream's real colour signalling
// (the AMediaCodec analogue of VideoToolbox's format description on the Apple client).
// If it's HDR (BT.2020 PQ/HLG), tell the Surface so the compositor/display switch to
// HDR; SDR streams leave the default dataspace alone. The decoder itself picks a
// Main10 path from the SPS — no profile override needed. Keep looping (buffers
// follow, and any held buffer stays held across this event).
wait = Duration::ZERO;
if let Some(ds) = hdr_dataspace(codec) {
if *applied_ds != Some(ds) {
match window.set_buffers_data_space(ds) {
Ok(()) => {
*applied_ds = Some(ds);
log::info!("decode: HDR stream → Surface dataspace {ds}");
}
Err(e) => log::warn!(
"decode: set_buffers_data_space({ds}) failed (non-fatal): {e}"
),
}
}
}
}
// TryAgainLater / OutputBuffersChanged — nothing more to dequeue now.
Ok(_) => break,
Err(e) => {
log::warn!("decode: dequeue_output_buffer: {e}");
break;
}
}
}
// Present the newest ready frame — UNLESS the gate is withholding it as a post-loss concealment,
// in which case release it without rendering (the SurfaceView keeps the last rendered frame frozen
// on glass) and count it as a discard rather than a display.
let mut rendered = 0;
if let Some((buf, meta)) = held {
match codec.release_output_buffer(buf, held_present) {
Ok(()) if held_present => {
rendered = 1;
if let Some((pts_us, decoded_ns)) = meta {
tracker.note_rendered(pts_us, decoded_ns);
}
}
Ok(()) => discarded += 1, // held off the screen — awaiting a clean re-anchor
Err(e) => log::warn!("decode: release_output_buffer: {e}"),
}
}
(rendered, discarded)
}
+23 -25
View File
@@ -24,14 +24,19 @@ const TAG_PLAYER_LEDS: u8 = 0x02;
const TAG_TRIGGER: u8 = 0x03;
const TAG_HID_RAW: u8 = 0x05;
/// `NativeBridge.nativeNextRumble(handle): Long` — block up to ~100 ms for the next rumble update.
/// Returns a packed positive long: bits 49..52 = wire `pad` index (0..15), bit 48 = "has a v2 lease",
/// bits 32..47 = `ttl_ms`, bits 16..31 = `low`, bits 0..15 = `high` (`low`/`high` 0..=0xFFFF, `0/0` =
/// stop). The lease flag is out-of-band so ANY 16-bit `ttl_ms` — including 0xFFFF — is unambiguous (no
/// in-band sentinel to collide with a real 65535 ms lease). No lease (legacy host) → bit 48 clear, and
/// Kotlin falls back to its long one-shot. `-1` on timeout / session closed (all packed values are
/// positive, so `-1` stays unambiguous). Kotlin routes the update back to the controller holding that
/// wire `pad` index (multi-pad rumble). Run from a Kotlin poll thread.
/// `NativeBridge.nativeNextRumble(handle): Long` — block up to ~100 ms for the next EFFECTIVE
/// rumble command from the core's shared policy engine (`design/rumble-root-fix.md` §D). The
/// engine owns ALL rumble policy — v2 lease expiry, legacy-host staleness (a uniform 1 s, ending
/// the old 60 s Android exposure), connection-close drain zeros — so Kotlin applies commands
/// verbatim: `(0, 0)` = cancel now, non-zero = one-shot at this level.
///
/// Returns a packed positive long: bits 49..52 = wire `pad` index (0..15), bits 32..47 = the
/// command's `backstop_ms` (≤ 5000 — the one-shot duration, i.e. the hardware net under a stalled
/// poll thread; the engine emits explicit zeros at every policy stop, so it is never the stop
/// mechanism), bits 16..31 = `low`, bits 0..15 = `high` (0..=0xFFFF). `-1` on timeout / session
/// closed (all packed values are positive, so `-1` stays unambiguous). Kotlin routes the command
/// back to the controller holding that wire `pad` index (multi-pad rumble). Run from a Kotlin
/// poll thread.
#[no_mangle]
pub extern "system" fn Java_io_unom_punktfunk_kit_NativeBridge_nativeNextRumble(
_env: JNIEnv,
@@ -43,24 +48,17 @@ pub extern "system" fn Java_io_unom_punktfunk_kit_NativeBridge_nativeNextRumble(
if handle == 0 {
return -1;
}
// SAFETY: live handle per the nativeConnect/nativeClose contract; next_rumble_ttl is &self on
// the Sync connector — safe alongside the decode/audio/input threads. Kotlin stops these poll
// threads (and joins them — unbounded) before nativeClose frees the handle.
// SAFETY: live handle per the nativeConnect/nativeClose contract; next_rumble_command is
// &self on the Sync connector — safe alongside the decode/audio/input threads. Kotlin
// stops these poll threads (and joins them — unbounded) before nativeClose frees the
// handle.
let h = unsafe { &*(handle as *const SessionHandle) };
match h.client.next_rumble_ttl(PULL_TIMEOUT) {
Ok((pad, low, high, ttl)) => {
// The reorder gate already ran in the core, so this update is fresh. Encode the
// Option out-of-band: a real lease sets bit 48 and carries ttl_ms verbatim. The pad
// index rides above the lease flag (bits 49..52), keeping the whole word positive.
let (lease_flag, ttl_bits) = match ttl {
Some(ms) => (1i64 << 48, jlong::from(ms) << 32),
None => (0, 0),
};
(jlong::from(pad & 0xF) << 49)
| lease_flag
| ttl_bits
| (jlong::from(low) << 16)
| jlong::from(high)
match h.client.next_rumble_command(PULL_TIMEOUT) {
Ok(cmd) => {
(jlong::from(cmd.pad & 0xF) << 49)
| (jlong::from(cmd.backstop_ms.min(0xFFFF) as u16) << 32)
| (jlong::from(cmd.low) << 16)
| jlong::from(cmd.high)
}
Err(_) => -1, // NoFrame (timeout) or Closed — Kotlin loops on its running flag
}
@@ -712,7 +712,7 @@
INFOPLIST_FILE = PunktfunkWidgets/Info.plist;
INFOPLIST_KEY_CFBundleDisplayName = PunktfunkWidgets;
INFOPLIST_KEY_NSHumanReadableCopyright = "";
IPHONEOS_DEPLOYMENT_TARGET = 27.0;
IPHONEOS_DEPLOYMENT_TARGET = 17.0;
LD_RUNPATH_SEARCH_PATHS = (
"$(inherited)",
"@executable_path/Frameworks",
@@ -757,7 +757,7 @@
INFOPLIST_FILE = PunktfunkWidgets/Info.plist;
INFOPLIST_KEY_CFBundleDisplayName = PunktfunkWidgets;
INFOPLIST_KEY_NSHumanReadableCopyright = "";
IPHONEOS_DEPLOYMENT_TARGET = 27.0;
IPHONEOS_DEPLOYMENT_TARGET = 17.0;
LD_RUNPATH_SEARCH_PATHS = (
"$(inherited)",
"@executable_path/Frameworks",
@@ -0,0 +1,120 @@
<?xml version="1.0" encoding="UTF-8"?>
<Scheme
LastUpgradeVersion = "2700"
wasCreatedForAppExtension = "YES"
version = "2.0">
<BuildAction
parallelizeBuildables = "YES"
buildImplicitDependencies = "YES"
buildArchitectures = "Automatic">
<BuildActionEntries>
<BuildActionEntry
buildForTesting = "YES"
buildForRunning = "YES"
buildForProfiling = "YES"
buildForArchiving = "YES"
buildForAnalyzing = "YES">
<BuildableReference
BuildableIdentifier = "primary"
BlueprintIdentifier = "E2955696300948B9009F939C"
BuildableName = "PunktfunkWidgetsExtension.appex"
ReferencedContainer = "container:Punktfunk.xcodeproj">
</BuildableReference>
</BuildActionEntry>
<BuildActionEntry
buildForTesting = "YES"
buildForRunning = "YES"
buildForProfiling = "YES"
buildForArchiving = "YES"
buildForAnalyzing = "YES">
<BuildableReference
BuildableIdentifier = "primary"
BlueprintIdentifier = "BB0000000000000000000009"
BuildableName = "Punktfunk-iOS.app"
ReferencedContainer = "container:Punktfunk.xcodeproj">
</BuildableReference>
</BuildActionEntry>
</BuildActionEntries>
</BuildAction>
<TestAction
buildConfiguration = "Debug"
selectedDebuggerIdentifier = "Xcode.DebuggerFoundation.Debugger.LLDB"
selectedLauncherIdentifier = "Xcode.DebuggerFoundation.Launcher.LLDB"
shouldUseLaunchSchemeArgsEnv = "YES"
shouldAutocreateTestPlan = "YES">
</TestAction>
<LaunchAction
buildConfiguration = "Debug"
selectedDebuggerIdentifier = ""
selectedLauncherIdentifier = "Xcode.IDEFoundation.Launcher.PosixSpawn"
launchStyle = "0"
askForAppToLaunch = "Yes"
useCustomWorkingDirectory = "NO"
ignoresPersistentStateOnLaunch = "NO"
debugDocumentVersioning = "YES"
debugServiceExtension = "internal"
allowLocationSimulation = "YES"
launchAutomaticallySubstyle = "2"
queueDebuggingEnabled = "No">
<RemoteRunnable
runnableDebuggingMode = "2"
BundleIdentifier = "com.apple.springboard">
<BuildableReference
BuildableIdentifier = "primary"
BlueprintIdentifier = "E2955696300948B9009F939C"
BuildableName = "PunktfunkWidgetsExtension.appex"
ReferencedContainer = "container:Punktfunk.xcodeproj">
</BuildableReference>
</RemoteRunnable>
<MacroExpansion>
<BuildableReference
BuildableIdentifier = "primary"
BlueprintIdentifier = "BB0000000000000000000009"
BuildableName = "Punktfunk-iOS.app"
ReferencedContainer = "container:Punktfunk.xcodeproj">
</BuildableReference>
</MacroExpansion>
<EnvironmentVariables>
<EnvironmentVariable
key = "_XCWidgetKind"
value = ""
isEnabled = "YES">
</EnvironmentVariable>
<EnvironmentVariable
key = "_XCWidgetDefaultView"
value = "timeline"
isEnabled = "YES">
</EnvironmentVariable>
<EnvironmentVariable
key = "_XCWidgetFamily"
value = "systemMedium"
isEnabled = "YES">
</EnvironmentVariable>
</EnvironmentVariables>
</LaunchAction>
<ProfileAction
buildConfiguration = "Release"
shouldUseLaunchSchemeArgsEnv = "YES"
savedToolIdentifier = ""
useCustomWorkingDirectory = "NO"
debugDocumentVersioning = "YES"
askForAppToLaunch = "Yes"
launchAutomaticallySubstyle = "2">
<BuildableProductRunnable
runnableDebuggingMode = "0">
<BuildableReference
BuildableIdentifier = "primary"
BlueprintIdentifier = "BB0000000000000000000009"
BuildableName = "Punktfunk-iOS.app"
ReferencedContainer = "container:Punktfunk.xcodeproj">
</BuildableReference>
</BuildableProductRunnable>
</ProfileAction>
<AnalyzeAction
buildConfiguration = "Debug">
</AnalyzeAction>
<ArchiveAction
buildConfiguration = "Release"
revealArchiveInOrganizer = "YES">
</ArchiveAction>
</Scheme>
@@ -0,0 +1,10 @@
import Foundation
import PunktfunkKit
/// A fresh `pair=required`/unknown host pending a trust decision: drives both the "request access
/// vs. pair with PIN" choice and the subsequent approval wait. `advertisedFingerprint` is the
/// discovered host's advertised cert (nil for a manually-typed host trust-on-first-use).
struct ApprovalRequest {
let host: StoredHost
let advertisedFingerprint: Data?
}
@@ -24,6 +24,7 @@ struct ContentView: View {
@AppStorage(DefaultsKey.streamWidth) private var width = 1920
@AppStorage(DefaultsKey.streamHeight) private var height = 1080
@AppStorage(DefaultsKey.streamHz) private var hz = 60
@AppStorage(DefaultsKey.renderScale) private var renderScale = 1.0
@AppStorage(DefaultsKey.compositor) private var compositor = 0
@AppStorage(DefaultsKey.gamepadType) private var gamepadType = 0
@AppStorage(DefaultsKey.bitrateKbps) private var bitrateKbps = 0
@@ -338,18 +339,20 @@ struct ContentView: View {
+ "approve it — no need to reconnect.")
}
// Informational deep-link outcome (unknown host / already streaming). Not an error.
.alert(
"Can't open",
isPresented: Binding(
get: { deepLinkNotice != nil },
set: { if !$0 { deepLinkNotice = nil } })
) {
.alert("Can't open", isPresented: deepLinkNoticePresented) {
Button("OK", role: .cancel) {}
} message: {
Text(deepLinkNotice ?? "")
}
}
/// Presentation flag for the informational deep-link alert. Extracted from the `.alert` call so
/// the manual get/set Binding type-checks on its own instead of inflating the body chain's
/// budget (adding it inline tips SwiftUI's per-expression limit see the split sections idiom).
private var deepLinkNoticePresented: Binding<Bool> {
Binding(get: { deepLinkNotice != nil }, set: { if !$0 { deepLinkNotice = nil } })
}
#if os(iOS)
/// The Live Activity mode line, e.g. "2560×1440 @120 · HEVC · HDR", from the live connection.
private func currentModeLine() -> String {
@@ -736,6 +739,17 @@ struct ContentView: View {
/// host is back online. `prepareWake` still runs here to LEARN/refresh the MAC now that the host
/// is advertising (and is a harmless no-op otherwise). `onUnreachable` hands a plain connect
/// failure back to the caller (the wake-wait fallback) instead of the error alert.
/// The stream mode to request = the chosen resolution × the render scale, aspect-preserved,
/// even, and clamped to the codec's max dimension. > 1 supersamples for sharpness (the presenter
/// downscales the larger decoded frame to this display); < 1 renders under native and upscales.
/// The match-window path applies the SAME scale to the live window size in `MatchWindowFollower`.
private func scaledMode() -> (width: UInt32, height: UInt32) {
RenderScale.apply(
baseWidth: width, baseHeight: height,
scale: renderScale,
maxDimension: RenderScale.maxDimension(codec: codec))
}
private func startSessionDirect(
_ host: StoredHost, launchID: String? = nil,
allowTofu: Bool, requestAccess: Bool = false, approvalReq: ApprovalRequest? = nil,
@@ -747,7 +761,7 @@ struct ContentView: View {
if let approvalReq { awaitingApproval = approvalReq }
model.connect(
to: host,
width: UInt32(clamping: width), height: UInt32(clamping: height),
width: scaledMode().width, height: scaledMode().height,
hz: UInt32(clamping: hz),
compositor: PunktfunkConnection.Compositor(
rawValue: UInt32(clamping: compositor)) ?? .auto,
@@ -930,7 +944,7 @@ struct ContentView: View {
}
model.connect(
to: host,
width: UInt32(clamping: width), height: UInt32(clamping: height),
width: scaledMode().width, height: scaledMode().height,
hz: UInt32(clamping: hz),
compositor: pref,
gamepad: pad,
@@ -941,71 +955,3 @@ struct ContentView: View {
autoTrust: true)
}
}
#if os(macOS)
/// Drives the hosting window in/out of native fullscreen from SwiftUI state, and mirrors the
/// window's ACTUAL fullscreen state back into `isFullscreen` (the user can also toggle it with the
/// green button / F ContentView keys the session view's safe-area handling off the real state,
/// not the setting). Mounted invisibly in the view tree; on each `active` change it captures the
/// window and toggles fullscreen only when the current state differs (so it never fights a toggle
/// already in flight, and never touches a window the user fullscreened manually unless `active`
/// says otherwise).
private struct FullscreenController: NSViewRepresentable {
let active: Bool
@Binding var isFullscreen: Bool
/// Holds the window's fullscreen-transition observers so they're rebound on a window change
/// and removed on dismantle.
final class Coordinator {
var observers: [NSObjectProtocol] = []
weak var observedWindow: NSWindow?
deinit { observers.forEach(NotificationCenter.default.removeObserver(_:)) }
}
func makeCoordinator() -> Coordinator { Coordinator() }
func makeNSView(context: Context) -> NSView { NSView() }
func updateNSView(_ view: NSView, context: Context) {
let want = active
let isFullscreen = $isFullscreen
let coordinator = context.coordinator
DispatchQueue.main.async {
guard let window = view.window else { return }
observeTransitions(of: window, coordinator: coordinator)
let isFull = window.styleMask.contains(.fullScreen)
if isFullscreen.wrappedValue != isFull { isFullscreen.wrappedValue = isFull }
if want != isFull { window.toggleFullScreen(nil) }
}
}
/// `willEnter` (not did) so the video goes edge-to-edge while the title bar is already
/// animating away; `didExit` so the top inset returns only once the title bar is back
/// no black gap in either direction.
private func observeTransitions(of window: NSWindow, coordinator: Coordinator) {
guard coordinator.observedWindow !== window else { return }
coordinator.observers.forEach(NotificationCenter.default.removeObserver(_:))
coordinator.observers.removeAll()
coordinator.observedWindow = window
let isFullscreen = $isFullscreen
for (name, value) in [
(NSWindow.willEnterFullScreenNotification, true),
(NSWindow.didExitFullScreenNotification, false),
] {
coordinator.observers.append(NotificationCenter.default.addObserver(
forName: name, object: window, queue: .main
) { _ in
isFullscreen.wrappedValue = value
})
}
}
}
#endif
/// A fresh `pair=required`/unknown host pending a trust decision: drives both the "request access
/// vs. pair with PIN" choice and the subsequent approval wait. `advertisedFingerprint` is the
/// discovered host's advertised cert (nil for a manually-typed host trust-on-first-use).
private struct ApprovalRequest {
let host: StoredHost
let advertisedFingerprint: Data?
}
@@ -0,0 +1,83 @@
import PunktfunkKit
import SwiftUI
#if os(macOS)
import AppKit
/// Drives the hosting window in/out of native fullscreen from SwiftUI state, and mirrors the
/// window's ACTUAL fullscreen state back into `isFullscreen` (the user can also toggle it with the
/// green button / F ContentView keys the session view's safe-area handling off the real state,
/// not the setting). Mounted invisibly in the view tree; on each `active` change it captures the
/// window and toggles fullscreen only when the current state differs (so it never fights a toggle
/// already in flight, and never touches a window the user fullscreened manually unless `active`
/// says otherwise).
struct FullscreenController: NSViewRepresentable {
let active: Bool
@Binding var isFullscreen: Bool
/// Holds the window's fullscreen-transition observers so they're rebound on a window change
/// and removed on dismantle.
final class Coordinator {
var observers: [NSObjectProtocol] = []
weak var observedWindow: NSWindow?
/// The last `active` value we DROVE the window to. We toggle only when `active` itself
/// changes (stream start/end) never to correct a mismatch so a deliberate mid-session
/// toggle (F / the green button) isn't snapped back on the next SwiftUI update.
var lastActive: Bool?
deinit { observers.forEach(NotificationCenter.default.removeObserver(_:)) }
}
func makeCoordinator() -> Coordinator { Coordinator() }
func makeNSView(context: Context) -> NSView { NSView() }
func updateNSView(_ view: NSView, context: Context) {
let want = active
let isFullscreen = $isFullscreen
let coordinator = context.coordinator
DispatchQueue.main.async {
guard let window = view.window else { return }
observeTransitions(of: window, coordinator: coordinator)
let isFull = window.styleMask.contains(.fullScreen)
if isFullscreen.wrappedValue != isFull { isFullscreen.wrappedValue = isFull }
// Drive the window only on an `active` EDGE (stream start/end), not to close a mismatch
// so a user's F / green-button toggle stays put. First pass (lastActive == nil) just
// records the state without toggling, so mounting never yanks a window into fullscreen.
if coordinator.lastActive != want {
coordinator.lastActive = want
if want != isFull { window.toggleFullScreen(nil) }
}
}
}
/// `willEnter` (not did) so the video goes edge-to-edge while the title bar is already
/// animating away; `didExit` so the top inset returns only once the title bar is back
/// no black gap in either direction.
private func observeTransitions(of window: NSWindow, coordinator: Coordinator) {
guard coordinator.observedWindow !== window else { return }
coordinator.observers.forEach(NotificationCenter.default.removeObserver(_:))
coordinator.observers.removeAll()
coordinator.observedWindow = window
let isFullscreen = $isFullscreen
for (name, value) in [
(NSWindow.willEnterFullScreenNotification, true),
(NSWindow.didExitFullScreenNotification, false),
] {
coordinator.observers.append(NotificationCenter.default.addObserver(
forName: name, object: window, queue: .main
) { _ in
isFullscreen.wrappedValue = value
})
}
// The Stream menu's "Toggle Fullscreen" (F) and InputCapture's captured-state interception
// both post this; flip the KEY window only (posted app-wide, object nil). The transition
// observers above then mirror the real state back into the binding.
coordinator.observers.append(NotificationCenter.default.addObserver(
forName: .punktfunkToggleFullscreen, object: nil, queue: .main
) { [weak window] _ in
guard let window, window.isKeyWindow else { return }
window.toggleFullScreen(nil)
})
}
}
#endif
@@ -72,6 +72,14 @@ struct StreamCommands: Commands {
}
.keyboardShortcut("c", modifiers: [.control, .option, .shift])
.disabled(session?.isStreaming != true || session?.clipboardAvailable != true)
// Toggle the window's fullscreen. F is the macOS-standard fullscreen combo; here it's
// explicit so it's discoverable AND survives capture while streaming the stream view
// swallows keys, so InputCapture's monitor detects the same combo and posts the same
// notification the key window's FullscreenController observes.
Button("Toggle Fullscreen") {
NotificationCenter.default.post(name: .punktfunkToggleFullscreen, object: nil)
}
.keyboardShortcut("f", modifiers: [.control, .command])
#endif
Divider()
Button("Disconnect") { session?.disconnect() }
@@ -38,6 +38,7 @@ extension SettingsView {
}
#endif
#if !os(tvOS)
renderScaleRow
bitrateRows
#endif
} header: {
@@ -54,6 +55,50 @@ extension SettingsView {
}
}
#if !os(tvOS)
/// Render-scale picker + the resulting host resolution. > 1 supersamples (sharper, at more
/// bandwidth AND client decode); < 1 renders under native (lighter). The presenter resamples the
/// decoded frame to this display, so the multiplier is where the sharpness/cost trade-off lives.
@ViewBuilder var renderScaleRow: some View {
Picker("Render scale", selection: $renderScale) {
ForEach(RenderScale.presets, id: \.self) { scale in
Text(RenderScale.label(scale)).tag(scale)
}
}
// The concrete host resolution makes the cost legible. Only meaningful for the explicit mode
// (match-window derives the base from the live window, not these fields).
if renderScale != 1.0, !matchWindow {
let mode = RenderScale.apply(
baseWidth: width, baseHeight: height,
scale: renderScale,
maxDimension: RenderScale.maxDimension(codec: codec))
Text("Host renders \(Int(mode.width))×\(Int(mode.height)); this device downscales it to your display.")
.font(.geist(12, relativeTo: .caption))
.foregroundStyle(.secondary)
}
}
/// Keyboard & mouse forwarding applies wherever a hardware keyboard/mouse drives the stream
/// (always on macOS; an attached keyboard/mouse on iPad). Absent on tvOS (no such input path).
@ViewBuilder var inputSection: some View {
Section {
Picker("Modifier keys", selection: $modifierLayout) {
ForEach(ModifierLayout.allCases, id: \.self) { layout in
Text(layout.label).tag(layout.rawValue)
}
}
Toggle("Invert scroll direction", isOn: $invertScroll)
} header: {
Text("Keyboard & mouse")
} footer: {
Text((ModifierLayout(rawValue: modifierLayout) ?? .mac).detail
+ " Invert scroll reverses the wheel/trackpad scroll direction sent to the host.")
.font(.geist(12, relativeTo: .caption))
.foregroundStyle(.secondary)
}
}
#endif
#if os(iOS)
// MARK: - Stream mode (iOS wheel)
@@ -1,5 +1,6 @@
// App settings. The host creates a native virtual output at exactly the chosen size/refresh
// there is no scaling anywhere in the pipeline.
// App settings. The host creates a virtual output at exactly the chosen size/refresh; the only
// deliberate resample is the opt-in Render Scale (the host renders at size × scale and this device
// downscales supersampling for sharpness, or under-rendering for a lighter host/link).
//
// Navigation differs per platform, but all three group the same categories (General, Display,
// Audio, Controllers, Advanced, About): macOS uses a tabbed preferences window; iOS/iPadOS uses
@@ -25,6 +26,10 @@ struct SettingsView: View {
// windowed session instead streams at the window's native pixels (1:1, no scaling) so it stays
// pixel-exact rather than the presenter resampling a fixed-mode frame into the window.
@AppStorage(DefaultsKey.matchWindow) var matchWindow = false
// Render-resolution multiplier: the host renders/encodes at chosen-resolution × this, and the
// presenter downscales (> 1 = supersampling for sharpness) or upscales (< 1 = a lighter host /
// link). 1.0 = Native (the prior behaviour).
@AppStorage(DefaultsKey.renderScale) var renderScale = 1.0
@AppStorage(DefaultsKey.compositor) var compositor = 0
@AppStorage(DefaultsKey.gamepadType) var gamepadType = 0
@AppStorage(DefaultsKey.bitrateKbps) var bitrateKbps = 0
@@ -51,6 +56,12 @@ struct SettingsView: View {
@AppStorage(DefaultsKey.autoWake) var autoWakeEnabled = true
@AppStorage(DefaultsKey.backgroundKeepAlive) var backgroundKeepAlive = false
@AppStorage(DefaultsKey.backgroundTimeoutMinutes) var backgroundTimeoutMinutes = 10
#if !os(tvOS)
// Keyboard & mouse forwarding (macOS + a hardware keyboard/mouse on iPad). Invert-scroll flips
// both wheel axes; modifier-layout relocates the / Alt/Super roles by physical position.
@AppStorage(DefaultsKey.invertScroll) var invertScroll = false
@AppStorage(DefaultsKey.modifierLayout) var modifierLayout = ModifierLayout.mac.rawValue
#endif
#if DEBUG && !os(tvOS)
@State var showControllerTest = false
#endif
@@ -112,6 +123,7 @@ struct SettingsView: View {
TabView {
Form {
streamModeSection
inputSection
compositorSection
wakeSection
}
@@ -242,6 +254,7 @@ struct SettingsView: View {
Form {
streamModeSection
pointerSection
inputSection
compositorSection
wakeSection
keepAliveSection // iOS-only content; empty on tvOS
@@ -334,6 +347,10 @@ struct SettingsView: View {
return ScrollView {
VStack(spacing: 16) {
TVSelectionRow(title: "Stream mode", options: options, selection: modeTag)
TVSelectionRow(
title: "Render scale",
options: RenderScale.presets.map { (label: RenderScale.label($0), tag: $0) },
selection: $renderScale)
TVSelectionRow(
title: "Bitrate",
options: SettingsOptions.bitrateOptions(current: bitrateKbps),
@@ -29,6 +29,11 @@ public final class ClipboardSync: NSObject {
("text/rtf", .rtf),
("text/html", .html),
("image/png", .png),
// Original image formats pass through VERBATIM beside the PNG floor a copied JPEG
// never balloons into PNG, a GIF keeps its animation; the destination picks the richest
// kind it can place.
("image/jpeg", NSPasteboard.PasteboardType("public.jpeg")),
("image/gif", NSPasteboard.PasteboardType("com.compuserve.gif")),
]
/// Pasteboard marker types that must never cross the wire (password managers mark secrets
/// with these see nspasteboard.org).
@@ -202,9 +207,20 @@ public final class ClipboardSync: NSObject {
let types = pb.types ?? []
if types.contains(Self.concealed) || types.contains(Self.transient) { return }
offerSeq &+= 1
let kinds = Self.wireToPasteboard
var kinds = Self.wireToPasteboard
.filter { types.contains($0.type) }
.map { PunktfunkConnection.ClipKind(mime: $0.wire) }
// PNG floor: announce the portable `image/png` whenever ANY convertible image is present
// native PNG, TIFF/HEIC (screenshots, Preview), or a JPEG/GIF original already being
// offered verbatim above. `readWireData` converts at fetch time (lazy, §3.5), so the
// fallback costs nothing unless a peer actually pastes it.
if !kinds.contains(where: { $0.mime == "image/png" }),
types.contains(.tiff)
|| types.contains(NSPasteboard.PasteboardType("public.heic"))
|| kinds.contains(where: { $0.mime.hasPrefix("image/") })
{
kinds.append(PunktfunkConnection.ClipKind(mime: "image/png"))
}
// Empty = the pasteboard holds nothing we sync (or was cleared) clears the host side.
connection.clipOffer(seq: offerSeq, kinds: kinds)
}
@@ -305,8 +321,7 @@ public final class ClipboardSync: NSObject {
private func serveFetch(reqId: UInt32, seq: UInt32, mime: String) {
let pb = NSPasteboard.general
guard seq == offerSeq, pb.changeCount == lastSeenChangeCount,
let type = Self.wireToPasteboard.first(where: { $0.wire == mime })?.type,
let data = pb.data(forType: type)
let data = Self.readWireData(pb, mime)
else {
connection.clipCancel(id: reqId)
return
@@ -322,6 +337,30 @@ public final class ClipboardSync: NSObject {
connection.clipServe(reqId: reqId, data: Data(), last: true)
}
}
/// Read one wire format from the pasteboard, converting where macOS stores a different
/// native type: `image/png` is served from a real `.png` entry when present, else converted
/// from whatever image representation the pasteboard holds (TIFF from screenshots/Preview,
/// WebP/AVIF/GIF from browsers `NSImage` decodes them all) into PNG at fetch time.
private static func readWireData(_ pb: NSPasteboard, _ mime: String) -> Data? {
guard mime == "image/png" else {
guard let type = wireToPasteboard.first(where: { $0.wire == mime })?.type else {
return nil
}
return pb.data(forType: type)
}
if let png = pb.data(forType: .png) {
return png
}
// No native PNG: decode whatever image the pasteboard carries and re-encode.
guard let img = NSImage(pasteboard: pb),
let tiff = img.tiffRepresentation,
let rep = NSBitmapImageRep(data: tiff)
else {
return nil
}
return rep.representation(using: .png, properties: [:])
}
}
/// The lazy paste hook: AppKit calls `provideDataForType` only when a Mac app actually pastes;
@@ -354,6 +393,8 @@ private final class RemoteOfferProvider: NSObject, NSPasteboardItemDataProvider
case .rtf: return "text/rtf"
case .html: return "text/html"
case .png: return "image/png"
case NSPasteboard.PasteboardType("public.jpeg"): return "image/jpeg"
case NSPasteboard.PasteboardType("com.compuserve.gif"): return "image/gif"
default: return nil
}
}
@@ -794,6 +794,34 @@ public final class PunktfunkConnection {
}
}
/// Pull the next EFFECTIVE rumble command from the core's shared rumble policy engine the
/// uniform replacement for per-platform rumble policy. The engine owns every decision
/// (v2 lease expiry, legacy-host staleness at a uniform 1 s, connection-close drain zeros),
/// so apply commands verbatim: `(0, 0)` = stop now, non-zero = run at this level.
/// `backstopMs` is a safety-net duration for duration-parameterized platform APIs the
/// CoreHaptics renderer ignores it (its finite segment ceiling is the equivalent net).
/// Drain from the (single) feedback thread, alongside `nextHidOutput`.
public func nextRumbleCommand(timeoutMs: UInt32 = 0) throws
-> (pad: UInt16, low: UInt16, high: UInt16, backstopMs: UInt32)?
{
feedbackLock.lock()
defer { feedbackLock.unlock() }
guard let h = liveHandle() else { throw PunktfunkClientError.closed }
var pad: UInt16 = 0, low: UInt16 = 0, high: UInt16 = 0, backstop: UInt32 = 0
let rc = punktfunk_connection_next_rumble_cmd(h, &pad, &low, &high, &backstop, timeoutMs)
switch rc {
case statusOK:
return (pad, low, high, backstop)
case statusNoFrame:
return nil
case statusClosed:
throw PunktfunkClientError.closed
default:
throw PunktfunkClientError.status(rc)
}
}
/// One DualSense feedback event a game wrote to the host's virtual pad replay it on
/// the real controller (GCDeviceLight, GCControllerPlayerIndex,
/// GCDualSenseAdaptiveTrigger). Only a `.dualSense` session emits these.
@@ -155,21 +155,18 @@ public final class GamepadFeedback {
// meta, was unaffected). Pacing with a short sleep OUTSIDE the lock (below) keeps
// rumble/HID latency low while leaving the lock free between polls.
//
// Rumble is idempotent state, so drain the plane DRY and apply only the newest
// level PER PAD. The old one-datagram-per-cycle shape let a burst outpace the
// ~125 Hz drain: levels rendered up to ~130 ms late through the core's 16-deep
// queue, and its drop-newest overflow could shed a stop while stale nonzero
// states queued ahead of it buzzing until the host's next 500 ms refresh.
var newestByPad: [UInt8: (low: UInt16, high: UInt16, ttl: UInt32)] = [:]
// Rumble arrives as EFFECTIVE commands from the core's shared policy engine
// (design/rumble-root-fix.md §D): the engine owns leases, legacy staleness,
// and close-drain zeros, and its per-pad mailbox already coalesces a
// stalled drain wakes to ONE current-level command per pad, and a stop can
// never be shed by a queue. Apply verbatim, in order.
var rumbleBurst = 0
while rumbleBurst < 64, !flag.isStopped,
let r = try connection.nextRumble2(timeoutMs: 0) {
newestByPad[UInt8(truncatingIfNeeded: r.pad)] = (r.low, r.high, r.ttlMs)
let c = try connection.nextRumbleCommand(timeoutMs: 0) {
self?.routeRumble(
pad: UInt8(truncatingIfNeeded: c.pad), low: c.low, high: c.high)
rumbleBurst += 1
}
for (pad, n) in newestByPad {
self?.routeRumble(pad: pad, low: n.low, high: n.high, ttlMs: n.ttl)
}
// Drain a BOUNDED burst of hidout events so sustained 0xCD traffic (a game writing
// per-frame LED/trigger reports) can't spin here or block stop() past one cycle.
var burst = 0
@@ -218,15 +215,15 @@ public final class GamepadFeedback {
}
}
/// Route one rumble envelope to its pad's renderer (drain thread). An update for a pad with no
/// Route one engine command to its pad's renderer (drain thread). A command for a pad with no
/// live renderer one that just left the forwarded set is dropped.
private func routeRumble(pad: UInt8, low: UInt16, high: UInt16, ttlMs: UInt32) {
private func routeRumble(pad: UInt8, low: UInt16, high: UInt16) {
let renderer = withRouting { rumbleByPad[pad] }
renderer?.apply(low: low, high: high, ttlMs: ttlMs)
renderer?.apply(low: low, high: high)
// The opt-in device mirror follows controller 1 unconditionally the pads it exists for
// have no motors (their renderer above no-ops), and mirroring deliberately isn't gated on
// that: capability probing can't see a motor-less MFi pad, and the user opted in.
if pad == 0 { deviceRumble?.apply(low: low, high: high, ttlMs: ttlMs) }
if pad == 0 { deviceRumble?.apply(low: low, high: high) }
}
private func withRouting<R>(_ body: () -> R) -> R {
@@ -23,23 +23,6 @@ enum RumbleTuning {
/// the churn that lost stops inside CoreHaptics. Newest level wins when the window opens;
/// zero is never throttled.
static let minRebakeSeconds: TimeInterval = 0.025
/// Session watchdog: silence the motors when no wire command arrived for this long. This is
/// the **legacy-host fallback only** an old host sends no self-termination lease, so its
/// periodic re-send (every 500 ms) is the sole liveness signal and 3 vanished refreshes means
/// the channel or host died while audible. A v2 host instead supplies a per-command TTL (see
/// [`leaseSeconds`]); that deadline supersedes this watchdog.
static let sessionStaleSeconds: TimeInterval = 1.6
/// The legacy no-lease sentinel a v2 `ttl_ms` carries for an old host (mirrors the C ABI's
/// `PUNKTFUNK_RUMBLE_NO_TTL`). `UInt32.max` by construction.
static let noTTL: UInt32 = .max
/// Interpret a wire TTL (ms) from a rumble update: `nil` for the legacy no-lease sentinel
/// ([`noTTL`]) the renderer falls back to [`sessionStaleSeconds`] else the self-termination
/// lease in seconds (render the level for at most this long unless the host renews it).
static func leaseSeconds(ttlMs: UInt32) -> TimeInterval? {
ttlMs == noTTL ? nil : TimeInterval(ttlMs) / 1000
}
/// Levels closer than this (0.4 % of full scale) are the same level an identical host
/// refresh must never rebuild a player.
static let levelEpsilon: Float = 1.0 / 256.0
@@ -110,13 +93,15 @@ enum RumbleTuning {
/// `@unchecked Sendable` is sound because every property is read and written only inside
/// `queue` closures the serial queue is the synchronization.
final class RumbleRenderer: @unchecked Sendable {
/// What an un-refreshed nonzero target means. A live session ties motor life to wire
/// liveness (the host refreshes state every 500 ms); the controller test panel holds a
/// slider level indefinitely.
/// Who ends an un-refreshed nonzero target. Session mode applies the core policy engine's
/// commands verbatim the engine (punktfunk-core `client/rumble.rs`) owns every lease,
/// staleness, and close decision and emits explicit zeros, so the renderer keeps NO
/// staleness policy of its own anymore. The controller test panel (`manual`) holds a slider
/// level indefinitely; both are identical renderer-side today, the distinction is kept for
/// the call sites' intent.
struct Policy {
let staleAfter: TimeInterval?
static let session = Policy(staleAfter: RumbleTuning.sessionStaleSeconds)
static let manual = Policy(staleAfter: nil)
static let session = Policy()
static let manual = Policy()
}
/// Which physical actuator this renderer drives: the forwarded controller's haptics engine
@@ -160,13 +145,9 @@ final class RumbleRenderer: @unchecked Sendable {
private var controller: GCController?
private var low: Motor?
private var high: Motor?
/// Wire-truth target (raw wire units) and when it was last confirmed by any command.
/// Wire-truth target (raw wire units) the engine command's level, applied verbatim; the
/// core policy engine owns when it ends (explicit zero commands), so no deadline lives here.
private var target: (low: UInt16, high: UInt16) = (0, 0)
private var lastCommand = DispatchTime(uptimeNanoseconds: 0)
/// The v2 envelope lease: the active level is authorized until here unless the host renews it
/// (`tick` silences at the deadline). `nil` against a legacy host (no lease the
/// `sessionStaleSeconds` watchdog is the backstop) and while silent.
private var envelopeDeadline: DispatchTime?
/// Runs while anything is (or should be) audible: staleness watchdog, segment re-arm,
/// throttled-level catch-up, engine rebuild after a reset, HID keepalive. Nil while silent,
/// so an idle controller costs no timer wakeups and no radio traffic.
@@ -247,17 +228,9 @@ final class RumbleRenderer: @unchecked Sendable {
/// against a legacy host (no lease the staleness watchdog is the backstop). Renewals at an
/// unchanged level extend the deadline before the idempotence guard, so a held rumble never
/// lapses mid-effect.
func apply(low lowAmp: UInt16, high highAmp: UInt16, ttlMs: UInt32 = RumbleTuning.noTTL) {
func apply(low lowAmp: UInt16, high highAmp: UInt16) {
queue.async {
self.lastCommand = .now()
let active = lowAmp != 0 || highAmp != 0
// v2 lease: a nonzero level gets an explicit deadline; a stop or a legacy update clears
// it. Set BEFORE the idempotence guard so an identical renewal still extends the lease.
if let lease = RumbleTuning.leaseSeconds(ttlMs: ttlMs), active {
self.envelopeDeadline = .now() + lease
} else {
self.envelopeDeadline = nil
}
if active != self.wasActive {
self.wasActive = active
log.debug(
@@ -275,7 +248,6 @@ final class RumbleRenderer: @unchecked Sendable {
self.ticker?.cancel()
self.ticker = nil
self.target = (0, 0)
self.envelopeDeadline = nil
self.wasActive = false
self.teardown()
self.closeHID()
@@ -331,25 +303,11 @@ final class RumbleRenderer: @unchecked Sendable {
healthSink?(problem)
}
/// Watchdog + housekeeping heartbeat while audible.
/// Housekeeping heartbeat while audible: segment re-arm, HID keepalive, backoff retries.
/// Every liveness decision (lease expiry, legacy-host staleness, session close) lives in the
/// core policy engine now it emits explicit zero commands, so the renderer never guesses
/// when a level should end.
private func tick() {
if let deadline = envelopeDeadline {
// v2 host lease: silence the moment it lapses unrenewed. This firing in the wild is the
// observable signature of a host that stopped renewing (a dropped stop, or a dead host)
// the whole point of the envelope model: the motor can't outlive the host's intent.
if target != (0, 0), DispatchTime.now() >= deadline {
log.warning("rumble: envelope expired unrenewed — silencing")
target = (0, 0)
envelopeDeadline = nil
}
} else if let after = policy.staleAfter, target != (0, 0), seconds(since: lastCommand) > after {
// Legacy host (no lease): it re-sends state every 500 ms, so this much silence means the
// channel (or host) died while a motor was on. A direct-connected pad would have been
// stopped by its game long ago force the same outcome.
log.warning(
"rumble: no wire refresh for \(after, format: .fixed(precision: 1), privacy: .public)s — auto-silencing")
target = (0, 0)
}
render()
}
@@ -41,6 +41,7 @@ import UIKit
import Foundation
import GameController
import PunktfunkCore
import PunktfunkShared
import os
/// Diagnostic logging for the input path. Off by default (input is high-rate); set
@@ -125,6 +126,12 @@ public final class InputCapture {
public var onDisconnect: (() -> Void)?
public var onCycleStats: (() -> Void)?
/// Fired on F (macOS) toggle the streaming window in/out of fullscreen. Detected in the
/// monitor only WHILE FORWARDING, for the same reason as the combos: a captured stream view
/// swallows keys, so the Stream menu's identical F equivalent never reaches it; released, the
/// menu handles it. Main queue.
public var onToggleFullscreen: (() -> Void)?
#if os(iOS)
/// Windows VKs of the three modifier classes in the Q release chord, both L/R sides:
/// control (0xA2/0xA3), option (0xA4/0xA5), shift (0xA0/0xA1). Used to sift the HID key stream.
@@ -273,6 +280,14 @@ public final class InputCapture {
break
}
}
// F toggles the streaming window's fullscreen. Intercepted only while forwarding (the
// captured stream view swallows the menu's identical equivalent); the F is latched so its
// keyUp can't type into the host. keyCode 3 = kVK_ANSI_F (layout-independent).
if self.forwarding, flags == [.control, .command], event.keyCode == 3 /* F */ {
self.suppressedVK = 0x46 // VK_F the same physical F is en route via GC
self.onToggleFullscreen?()
return nil
}
return event
}
#endif
@@ -318,7 +333,7 @@ public final class InputCapture {
chordModifiersDown.removeAll()
suppressedVK = nil
for vk in pressedVKs {
connection.send(.key(vk, down: false))
emitKey(vk, down: false)
}
for button in pressedButtons {
connection.send(.mouseButton(button, down: false))
@@ -331,6 +346,15 @@ public final class InputCapture {
residualScrollY = 0
}
/// The single wire boundary for a key event. Every `.key` send funnels through here so the
/// active location-based modifier layout is applied in exactly one place while all internal
/// press/release bookkeeping (`pressedVKs`, `cmdKeysDown`, `resolveModifier`'s `isDown`) stays on
/// the physical VK. Read live from the setting so a mid-session change (rare) takes on the next
/// key without re-arming capture. Non-modifier VKs pass through untouched.
private func emitKey(_ vk: UInt32, down: Bool) {
connection.send(.key(Self.applyModifierLayout(vk, ModifierLayout.current), down: down))
}
/// Release any held MOUSE buttons host-side, leaving keyboard state untouched. Used when
/// the iPad pointer lock drops while a GCMouse button is held: by then the GCMouse release
/// handler is gated off (`gcMouseForwarding` is false), so it can't deliver the release
@@ -399,7 +423,7 @@ public final class InputCapture {
inputLog.debug(
"key \(vk, privacy: .public) \(down ? "down" : "up", privacy: .public) sent")
}
connection.send(.key(vk, down: down))
emitKey(vk, down: down)
}
/// NSEvent modifier path (macOS): modifier keys never fire keyDown/keyUp they arrive
@@ -566,8 +590,15 @@ public final class InputCapture {
/// Moonlight's convention). Fed by StreamLayerView.scrollWheel the only delivery
/// path that covers trackpad/Magic Mouse gestures (GCMouse never reports them).
/// Fractional remainders accumulate so slow two-finger scrolling isn't truncated away.
public func sendScroll(dx: Float, dy: Float) {
public func sendScroll(dx rawDx: Float, dy rawDy: Float) {
guard forwarding else { return }
// Optionally invert both axes (read live). This is the ONE scroll sink for every platform
// the macOS wheel, the iOS trackpad pan, and a GCMouse wheel all land here so the toggle
// flips them consistently. Residuals are accumulated AFTER inversion so a direction change
// between events doesn't strand a fractional remainder of the old sign.
let invert = UserDefaults.standard.bool(forKey: DefaultsKey.invertScroll)
let dx = invert ? -rawDx : rawDx
let dy = invert ? -rawDy : rawDy
let fy = dy + residualScrollY
let fx = dx + residualScrollX
let iy = fy.rounded(.towardZero)
@@ -643,7 +674,7 @@ public final class InputCapture {
} else {
self.pressedVKs.remove(vk)
}
self.connection.send(.key(vk, down: pressed))
self.emitKey(vk, down: pressed)
}
#endif
}
@@ -1,7 +1,28 @@
// InputCapture's static keymap tables: HID usage Windows VK (the GCKeyboard path on all
// platforms) and, on macOS, NSEvent.keyCode Windows VK (the NSEvent key path).
import PunktfunkShared
extension InputCapture {
/// Remap one modifier VK for the active location-based [`ModifierLayout`] just before it goes on
/// the wire. `.mac` (and every non-modifier VK) is the identity. `.windows` swaps the Alt vs
/// Super/Windows ROLE between the Option and Command keys while KEEPING the side, so a Windows
/// user's ` = Alt, = Win` muscle memory lands correctly:
/// L Command 0x5B L Alt 0xA4 · R Command 0x5C R Alt 0xA5.
/// It's applied ONLY at the send boundary (`InputCapture.emitKey`) all press/release
/// bookkeeping stays on the physical VK, so modifier direction tracking and the client-local
/// -based shortcuts are untouched. The swap is its own inverse: a key that went down remapped
/// goes up remapped, so the host never sees a stuck modifier.
static func applyModifierLayout(_ vk: UInt32, _ layout: ModifierLayout) -> UInt32 {
guard layout == .windows else { return vk }
switch vk {
case 0x5B: return 0xA4 // L Command L Alt (VK_LWIN VK_LMENU)
case 0x5C: return 0xA5 // R Command R Alt (VK_RWIN VK_RMENU)
case 0xA4: return 0x5B // L Option L Win (VK_LMENU VK_LWIN)
case 0xA5: return 0x5C // R Option R Win (VK_RMENU VK_RWIN)
default: return vk
}
}
/// HID usage (GCKeyCode raw) Windows VK (the host maps VK evdev; every VK emitted
/// here exists in punktfunk-host/src/inject.rs::vk_to_evdev extend the two together).
static let hidToVK: [Int: UInt32] = {
@@ -15,6 +15,7 @@
// from looping request rollback request.
import Foundation
import PunktfunkShared
/// The pure, side-effect-free core of the Match-window trigger so the normalize/skip discipline
/// is unit-tested without a live connection or a UI (`MatchWindowTests`).
@@ -51,6 +52,11 @@ public final class MatchWindowFollower {
private weak var connection: PunktfunkConnection?
private let debounce: TimeInterval
private let minSpacing: TimeInterval
/// Render-scale multiplier applied to the live window size before it's requested, so the
/// match-window path supersamples/undersamples exactly like the fixed-mode connect. 1.0 = the
/// window's native pixels (the prior behaviour). `maxDimension` is the codec's per-axis ceiling.
private let renderScale: Double
private let maxDimension: Int
private var enabled: Bool
private var work: DispatchWorkItem?
@@ -74,15 +80,28 @@ public final class MatchWindowFollower {
public init(
connection: PunktfunkConnection,
enabled: Bool,
renderScale: Double = 1.0,
maxDimension: Int = 8192,
debounce: TimeInterval = 0.4,
minSpacing: TimeInterval = 1.0
) {
self.connection = connection
self.enabled = enabled
self.renderScale = RenderScale.sanitize(renderScale)
self.maxDimension = maxDimension
self.debounce = debounce
self.minSpacing = minSpacing
}
/// The host-valid mode for a live window size: the window's physical pixels × the render scale,
/// aspect-preserved, even, and clamped to the codec ceiling the match-window twin of
/// ContentView's `scaledMode()`. Reduces to `MatchWindow.normalize` when the scale is 1.0.
private func targetMode(widthPx: Int, heightPx: Int) -> (width: UInt32, height: UInt32) {
RenderScale.apply(
baseWidth: widthPx, baseHeight: heightPx,
scale: renderScale, maxDimension: maxDimension)
}
/// Turn following on/off live (a mid-session settings change; off cancels a pending request).
public func setEnabled(_ on: Bool) {
enabled = on
@@ -109,7 +128,7 @@ public final class MatchWindowFollower {
/// mode yet nothing to compare against, skip.
private func reportSteering(widthPx: Int, heightPx: Int) {
guard let connection else { return }
let target = MatchWindow.normalize(widthPx: widthPx, heightPx: heightPx)
let target = targetMode(widthPx: widthPx, heightPx: heightPx)
let mode = connection.currentMode()
guard mode.width > 0, mode.height > 0 else { return }
if target.width == mode.width, target.height == mode.height {
@@ -137,7 +156,7 @@ public final class MatchWindowFollower {
schedule()
return
}
let target = MatchWindow.normalize(widthPx: size.width, heightPx: size.height)
let target = targetMode(widthPx: size.width, heightPx: size.height)
let mode = connection.currentMode()
pendingSize = nil
guard let req = MatchWindow.request(
@@ -621,6 +621,12 @@ public final class StreamLayerView: NSView {
guard let self, self.window?.isKeyWindow == true else { return }
self.onDisconnectRequest?()
}
capture.onToggleFullscreen = { [weak self] in
// App-level window action: post to the key window's FullscreenController (same routing as
// the Stream menu's F item, so captured and released states hit one code path).
guard self?.window?.isKeyWindow == true else { return }
NotificationCenter.default.post(name: .punktfunkToggleFullscreen, object: nil)
}
capture.onCycleStats = { [weak self] in
guard self?.window?.isKeyWindow == true else { return }
// Advance the shared tier setting directly every @AppStorage reader (the HUD's
@@ -671,7 +677,10 @@ public final class StreamLayerView: NSView {
// default keeps the explicit mode.
let follower = MatchWindowFollower(
connection: connection,
enabled: UserDefaults.standard.object(forKey: DefaultsKey.matchWindow) as? Bool ?? false)
enabled: UserDefaults.standard.object(forKey: DefaultsKey.matchWindow) as? Bool ?? false,
renderScale: UserDefaults.standard.object(forKey: DefaultsKey.renderScale) as? Double ?? 1.0,
maxDimension: RenderScale.maxDimension(
codec: UserDefaults.standard.string(forKey: DefaultsKey.codec) ?? "auto"))
follower.onResizeTarget = onResizeTarget // resize overlay START signal (instant, on the follower)
matchFollower = follower
layoutPresenter()
@@ -385,7 +385,10 @@ public final class StreamViewController: StreamViewControllerBase {
// default keeps the explicit mode.
let follower = MatchWindowFollower(
connection: connection,
enabled: UserDefaults.standard.object(forKey: DefaultsKey.matchWindow) as? Bool ?? false)
enabled: UserDefaults.standard.object(forKey: DefaultsKey.matchWindow) as? Bool ?? false,
renderScale: UserDefaults.standard.object(forKey: DefaultsKey.renderScale) as? Double ?? 1.0,
maxDimension: RenderScale.maxDimension(
codec: UserDefaults.standard.string(forKey: DefaultsKey.codec) ?? "auto"))
follower.onResizeTarget = onResizeTarget
matchFollower = follower
#endif
@@ -21,6 +21,14 @@ public enum DefaultsKey {
/// is native either way, so this degenerates to Auto-native there). Read per session by the
/// stream views' `MatchWindowFollower`.
public static let matchWindow = "punktfunk.matchWindow"
/// Render-resolution multiplier (a `RenderScale` value, default 1.0): the client asks the host
/// to render/encode at `chosen resolution × scale`, then the presenter downscales the larger
/// decoded frame to this display in one Catmull-Rom pass. > 1 supersamples (sharper, at the cost
/// of more bandwidth AND client decode both grow scale²); < 1 renders below native for a
/// weak host GPU / constrained link (the presenter upscales). Purely client-side the host just
/// sees a normal (larger/smaller) `Mode`, and Automatic bitrate scales with it. Clamped even +
/// to the codec's max dimension at connect. Applies to the fixed mode and the match-window path.
public static let renderScale = "punktfunk.renderScale"
public static let compositor = "punktfunk.compositor"
public static let gamepadType = "punktfunk.gamepadType"
public static let gamepadID = "punktfunk.gamepadID"
@@ -69,6 +77,21 @@ public enum DefaultsKey {
public static let hosts = "punktfunk.hosts"
/// Client-side cursor mode: "auto" (shown only in gamescope sessions), "always", "never".
public static let cursorMode = "punktfunk.cursorMode"
/// Invert the scroll-wheel / two-finger-scroll direction sent to the host (both axes). Off by
/// default: the local (natural-scrolling) sign passes through untouched. When on, the sign is
/// negated at the single scroll sink (`InputCapture.sendScroll`), so it flips consistently across
/// the macOS wheel, the iOS trackpad pan, and a GCMouse wheel. For users whose host expects the
/// opposite convention from their local OS preference.
public static let invertScroll = "punktfunk.invertScroll"
/// Location-based modifier mapping (a `ModifierLayout` value, default `.mac`): which Windows VK
/// each PHYSICAL modifier position forwards to the host. `.mac` keeps Option Alt and
/// Command Super/Win (the Apple positions). `.windows` swaps the Alt/Super ROLE between the
/// Option and Command keys preserving side (L/R) so the key nearest the space bar acts as
/// Alt and the next one as the Windows key, matching a Windows keyboard's `Ctrl / / Alt` row.
/// Only what's FORWARDED changes; client-local shortcuts ( &co.) stay on the physical key.
/// Read live at the wire boundary by `InputCapture`. Control/Shift never move (same position on
/// both keyboards).
public static let modifierLayout = "punktfunk.modifierLayout"
/// iPad: capture the mouse/trackpad pointer (pointer lock relative movement) for games,
/// rather than forwarding an absolute cursor position. On by default. Only meaningful on iPad
/// with a hardware mouse/trackpad; the system grants the lock only to a full-screen, frontmost
@@ -132,6 +155,12 @@ extension Notification.Name {
/// discoverable menu-bar surface.
public static let punktfunkReleaseCapture = Notification.Name("io.unom.punktfunk.release-capture")
/// Posted by the app's Stream menu ("Toggle Fullscreen", F) and by InputCapture's monitor
/// when the same combo fires while input is captured (the menu key-equivalent never reaches a
/// captured stream view). The key window's `FullscreenController` flips the window's fullscreen
/// state. macOS only.
public static let punktfunkToggleFullscreen = Notification.Name("io.unom.punktfunk.toggle-fullscreen")
/// Posted by the Live Activity's / Shortcuts' End-stream intent (`EndStreamIntent.perform`,
/// which runs in the app's process): the app tears the active session down deliberately
/// (quit-close the host). Same cross-process-signal pattern as `punktfunkReleaseCapture`
@@ -0,0 +1,52 @@
// Location-based modifier mapping which Windows VK each PHYSICAL modifier position forwards to
// the host. A Mac keyboard's bottom row is ` Control / Option / Command / space`; a Windows
// keyboard's is `Ctrl / Super / Alt / space`. So the key NEAREST the space bar is Command on a
// Mac but Alt on Windows, and the next one out is Option on a Mac but the Windows key. A user who
// grew up on Windows reaches for Alt where the Mac has Command; this setting lets them get that
// muscle memory back WITHOUT relabelling keycaps it remaps by physical position, not by a blunt
// "swap these two VKs" that would also drag Control/Shift or lose the left/right distinction.
//
// The model: keep the physical detection (which side, which key) exactly as the OS reports it, and
// swap only the Alt-vs-Super ROLE between the Option and Command keys, per side. So under `.windows`
// the position emits Alt (VK_L/RMENU) and the position emits the Windows key (VK_L/RWIN), while
// left stays left and right stays right. Control and Shift are in the same place on both keyboards,
// so they never move. Lives in PunktfunkShared because both the Settings UI (PunktfunkClient) and
// the wire-boundary remap (`InputCapture.applyModifierLayout`, PunktfunkKit) resolve it.
import Foundation
/// How the physical Option / Command keys map to host modifiers. The raw values are stable on
/// disk rename the cases freely, never the strings.
public enum ModifierLayout: String, CaseIterable, Sendable {
/// Apple positions (default): Option Alt, Command Super/Windows. The current behaviour.
case mac
/// Windows positions: the key nearest the space bar ( Command) Alt, the next one ( Option)
/// the Windows/Super key. Side (left/right) is preserved.
case windows
/// User-facing label (Settings picker).
public var label: String {
switch self {
case .mac: return "Mac (⌥ Alt · ⌘ Super)"
case .windows: return "Windows (⌘ Alt · ⌥ Super)"
}
}
/// A one-line explanation for the setting's footer.
public var detail: String {
switch self {
case .mac:
return "The ⌥ Option key sends Alt and the ⌘ Command key sends the Windows key — the Apple layout."
case .windows:
return "The key nearest the space bar sends Alt and the next one sends the Windows key, matching a PC keyboard. Client shortcuts (⌘⎋ and friends) still use the physical ⌘ key."
}
}
/// The persisted layout (default `.mac` when unset).
public static var current: ModifierLayout {
guard let raw = UserDefaults.standard.string(forKey: DefaultsKey.modifierLayout) else {
return .mac
}
return ModifierLayout(rawValue: raw) ?? .mac
}
}
@@ -0,0 +1,72 @@
// Render-resolution scaling the pure geometry behind `DefaultsKey.renderScale`. The client asks
// the host to render/encode at `chosen resolution × scale` and lets the presenter downscale the
// larger decoded frame to the display (a Catmull-Rom minification, > 1 = supersampling for
// sharpness) or upscale a smaller one (< 1 = a performance mode for a weak host GPU / thin link).
//
// This is where the multiplier is turned into a host-valid `Mode` dimension: multiply, preserve the
// aspect ratio, floor to even (the host's `validate_dimensions` rejects odd sizes), and clamp to the
// codec's per-axis ceiling so the connect can't ask for something the encoder will reject. Kept
// dependency-free + side-effect-free so it's unit-tested (`RenderScaleTests`) and reused by both the
// fixed-mode connect and the match-window follower.
import Foundation
public enum RenderScale {
/// The supported multiplier range. Below 1 renders under native (upscaled on present); above 1
/// supersamples. The UI clamps its slider to this and the connect clamps the raw stored value.
public static let range: ClosedRange<Double> = 0.5...4.0
/// The multipliers the picker offers. 1.0 (Native) is the default; the rest are the round stops
/// users reason about.
public static let presets: [Double] = [0.5, 0.67, 0.75, 1.0, 1.25, 1.5, 2.0, 3.0, 4.0]
/// The encoder/host per-axis ceiling for a codec preference string (`DefaultsKey.codec`). H.264
/// tops out at 4096 px/axis; HEVC / AV1 / PyroWave (and "auto", which negotiates one of those in
/// practice) at 8192. The host enforces the same walls in `codec.rs::validate_dimensions`.
public static func maxDimension(codec: String) -> Int {
codec == "h264" ? 4096 : 8192
}
/// A compact user-facing label for a multiplier: "Native (1×)", "1.5×", "2× · supersample".
/// Shared by every platform's picker so the wording stays identical.
public static func label(_ scale: Double) -> String {
if scale == 1.0 { return "Native (1×)" }
let magnitude = String(format: "%g×", scale)
return scale > 1 ? "\(magnitude) · supersample" : magnitude
}
/// Clamp a raw stored multiplier into `range`, treating a missing/zero value as 1.0 (Native).
public static func sanitize(_ raw: Double) -> Double {
guard raw > 0 else { return 1.0 }
return min(max(raw, range.lowerBound), range.upperBound)
}
/// Apply `scale` to a base pixel size, preserving aspect, even-flooring each axis, and clamping
/// uniformly so neither axis exceeds `maxDimension` (the larger axis lands on the cap, the ratio
/// is kept). Also floors each axis at `minWidth`/`minHeight` (the host never accepts < 320×200).
/// The result is a directly host-valid `Mode` width/height.
public static func apply(
baseWidth: Int,
baseHeight: Int,
scale rawScale: Double,
maxDimension: Int,
minWidth: Int = 320,
minHeight: Int = 200
) -> (width: UInt32, height: UInt32) {
let scale = sanitize(rawScale)
var w = Double(max(baseWidth, 1)) * scale
var h = Double(max(baseHeight, 1)) * scale
// Uniform down-clamp if either axis blew past the ceiling keep the aspect ratio intact.
let cap = Double(maxDimension)
let over = max(w / cap, h / cap)
if over > 1 {
w /= over
h /= over
}
let evenFloor: (Double, Int) -> UInt32 = { value, minimum in
let clamped = max(Int(value.rounded(.down)), minimum)
return UInt32(clamped / 2 * 2)
}
return (evenFloor(w, minWidth), evenFloor(h, minHeight))
}
}
@@ -0,0 +1,51 @@
import XCTest
import PunktfunkShared
@testable import PunktfunkKit
/// Pins the location-based modifier remap (`InputCapture.applyModifierLayout`) the wire-boundary
/// swap that relocates the Alt/Super role between the physical / keys per side, without touching
/// Control/Shift or the physical detection upstream.
final class ModifierLayoutMappingTests: XCTestCase {
// The four physical modifier VKs the remap can touch, and everything else it must not.
private let lWin: UInt32 = 0x5B, rWin: UInt32 = 0x5C
private let lAlt: UInt32 = 0xA4, rAlt: UInt32 = 0xA5
func testMacLayoutIsIdentity() {
for vk: UInt32 in [lWin, rWin, lAlt, rAlt, 0xA0, 0xA2, 0x41, 0x1B] {
XCTAssertEqual(InputCapture.applyModifierLayout(vk, .mac), vk)
}
}
func testWindowsLayoutSwapsAltAndSuperPerSide() {
XCTAssertEqual(InputCapture.applyModifierLayout(lWin, .windows), lAlt) // L L Alt
XCTAssertEqual(InputCapture.applyModifierLayout(rWin, .windows), rAlt) // R R Alt
XCTAssertEqual(InputCapture.applyModifierLayout(lAlt, .windows), lWin) // L L Win
XCTAssertEqual(InputCapture.applyModifierLayout(rAlt, .windows), rWin) // R R Win
}
func testWindowsLayoutKeepsSideNeverCrossesLeftRight() {
// A left key never becomes a right VK or vice-versa.
XCTAssertNotEqual(InputCapture.applyModifierLayout(lWin, .windows), rAlt)
XCTAssertNotEqual(InputCapture.applyModifierLayout(rWin, .windows), lAlt)
}
func testControlShiftAndRegularKeysNeverMove() {
for vk: UInt32 in [
0xA0, 0xA1, // L/R Shift
0xA2, 0xA3, // L/R Control
0x41, 0x5A, // A, Z
0x1B, 0x0D, // Esc, Return
] {
XCTAssertEqual(InputCapture.applyModifierLayout(vk, .windows), vk)
}
}
func testWindowsRemapIsItsOwnInverse() {
// Down-remapped then the same key up-remapped land on the same host VK no stuck modifier.
for vk: UInt32 in [lWin, rWin, lAlt, rAlt] {
let once = InputCapture.applyModifierLayout(vk, .windows)
XCTAssertEqual(InputCapture.applyModifierLayout(once, .windows), vk)
}
}
}
@@ -0,0 +1,82 @@
import XCTest
import PunktfunkShared
/// Pins the render-scale geometry (`RenderScale`) the multiply aspect-preserve even-floor
/// codec-clamp that turns the `renderScale` setting into a host-valid `Mode`, plus the label/clamp
/// helpers the UI and the connect share.
final class RenderScaleTests: XCTestCase {
func testSanitizeClampsToRangeAndDefaultsMissing() {
XCTAssertEqual(RenderScale.sanitize(0), 1.0) // absent / zero Native
XCTAssertEqual(RenderScale.sanitize(-2), 1.0) // nonsense Native
XCTAssertEqual(RenderScale.sanitize(0.1), 0.5) // below the floor
XCTAssertEqual(RenderScale.sanitize(9), 4.0) // above the ceiling
XCTAssertEqual(RenderScale.sanitize(1.5), 1.5) // in range, untouched
}
func testMaxDimensionIsCodecAware() {
XCTAssertEqual(RenderScale.maxDimension(codec: "h264"), 4096)
XCTAssertEqual(RenderScale.maxDimension(codec: "hevc"), 8192)
XCTAssertEqual(RenderScale.maxDimension(codec: "av1"), 8192)
XCTAssertEqual(RenderScale.maxDimension(codec: "auto"), 8192)
}
func testNativeScaleIsIdentity() {
let m = RenderScale.apply(baseWidth: 1920, baseHeight: 1080, scale: 1.0, maxDimension: 8192)
XCTAssertEqual(m.width, 1920)
XCTAssertEqual(m.height, 1080)
}
func testSupersampleDoubles() {
let m = RenderScale.apply(baseWidth: 1920, baseHeight: 1080, scale: 2.0, maxDimension: 8192)
XCTAssertEqual(m.width, 3840)
XCTAssertEqual(m.height, 2160)
}
func testUnderRenderHalves() {
let m = RenderScale.apply(baseWidth: 1920, baseHeight: 1080, scale: 0.5, maxDimension: 8192)
XCTAssertEqual(m.width, 960)
XCTAssertEqual(m.height, 540)
}
func testResultsAreAlwaysEven() {
// 1280×720 × 1.5 = 1920×1080 (already even); 1366×768 × 1.5 = 2049×1152 2048×1152.
let m = RenderScale.apply(baseWidth: 1366, baseHeight: 768, scale: 1.5, maxDimension: 8192)
XCTAssertEqual(m.width % 2, 0)
XCTAssertEqual(m.height % 2, 0)
XCTAssertEqual(m.width, 2048)
XCTAssertEqual(m.height, 1152)
}
func testOverCeilingClampsUniformlyPreservingAspect() {
// 4K × 4 would be 15360×8640; both axes exceed 8192, so the LARGER (width) lands on the cap
// and the ratio is kept (16:9 8192×4608, even).
let m = RenderScale.apply(baseWidth: 3840, baseHeight: 2160, scale: 4.0, maxDimension: 8192)
XCTAssertLessThanOrEqual(m.width, 8192)
XCTAssertLessThanOrEqual(m.height, 8192)
XCTAssertEqual(m.width, 8192)
XCTAssertEqual(m.height, 4608)
// 16:9 preserved to within the even-floor rounding.
XCTAssertEqual(Double(m.width) / Double(m.height), 16.0 / 9.0, accuracy: 0.01)
}
func testH264CeilingIsTighter() {
// 1080p × 4 = 7680×4320; under H.264's 4096 wall the width clamps to 4096, aspect kept.
let m = RenderScale.apply(baseWidth: 1920, baseHeight: 1080, scale: 4.0, maxDimension: 4096)
XCTAssertEqual(m.width, 4096)
XCTAssertEqual(m.height, 2304)
}
func testMinimumFloorHonoured() {
// A tiny base under a small scale can't fall below 320×200.
let m = RenderScale.apply(baseWidth: 400, baseHeight: 300, scale: 0.5, maxDimension: 8192)
XCTAssertGreaterThanOrEqual(m.width, 320)
XCTAssertGreaterThanOrEqual(m.height, 200)
}
func testLabels() {
XCTAssertEqual(RenderScale.label(1.0), "Native (1×)")
XCTAssertEqual(RenderScale.label(2.0), "2× · supersample")
XCTAssertEqual(RenderScale.label(0.5), "0.5×")
}
}
@@ -52,13 +52,6 @@ final class RumbleTuningTests: XCTestCase {
XCTAssertEqual(RumbleTuning.handoffStart(endsAt: 100, now: 100.5), 100.5)
}
func testPolicies() {
// The session policy ties motor life to wire liveness; the manual (test-panel) policy
// holds a level indefinitely.
XCTAssertNotNil(RumbleRenderer.Policy.session.staleAfter)
XCTAssertNil(RumbleRenderer.Policy.manual.staleAfter)
}
/// Exercise the renderer's queue/ticker machinery without a physical pad: a wire-rate call
/// storm, an audible target left to the ticker (watchdog path), then `stop()` which runs
/// `queue.sync` against the same serial queue the ticker fires on and must not deadlock.
@@ -75,45 +68,22 @@ final class RumbleTuningTests: XCTestCase {
renderer.stop()
}
func testLeaseSecondsInterpretsWireTTL() {
// The legacy no-lease sentinel nil (fall back to the staleness watchdog).
XCTAssertNil(RumbleTuning.leaseSeconds(ttlMs: RumbleTuning.noTTL))
XCTAssertEqual(RumbleTuning.noTTL, UInt32.max)
// A real lease its duration in seconds (non-nil for any ttl != noTTL).
XCTAssertEqual(RumbleTuning.leaseSeconds(ttlMs: 400) ?? .nan, 0.4, accuracy: 1e-9)
XCTAssertEqual(RumbleTuning.leaseSeconds(ttlMs: 0) ?? .nan, 0, accuracy: 1e-9)
XCTAssertEqual(RumbleTuning.leaseSeconds(ttlMs: 150) ?? .nan, 0.15, accuracy: 1e-9)
}
func testEnvelopeLeaseBoundsMotorLifeTighterThanTheLegacyWatchdog() {
// The whole point of v2: a host-supplied lease silences the motor faster than the
// legacy staleness watchdog ever could (which needs sessionStaleSeconds of silence). The
// default 400 ms TTL is well under that, on every platform.
let defaultTTL = RumbleTuning.leaseSeconds(ttlMs: 400)
XCTAssertNotNil(defaultTTL)
XCTAssertLessThan(defaultTTL!, RumbleTuning.sessionStaleSeconds)
// The ticker must be able to observe an expired lease promptly (well within one TTL).
XCTAssertLessThan(RumbleTuning.tickSeconds, defaultTTL!)
}
/// A v2 envelope with a short TTL, left unrenewed, must self-silence the renderer's core
/// promise. Drive the real queue/ticker (no physical pad) and confirm it doesn't wedge.
func testEnvelopeExpiresWhenUnrenewed() {
/// A zero command must silence promptly the engine (punktfunk-core) emits explicit zeros at
/// every policy stop (lease expiry, legacy staleness, session close), and the renderer's only
/// job is to apply them. Drive the real queue/ticker (no physical pad) and confirm no wedge.
func testZeroCommandSilencesAndTeardownDoesNotDeadlock() {
let renderer = RumbleRenderer(policy: .session)
renderer.retarget(nil)
// A 100 ms lease, then no renewal the ticker (50 ms) must silence it on its own.
renderer.apply(low: 0x8000, high: 0x8000, ttlMs: 100)
Thread.sleep(forTimeInterval: 0.3)
// No assertion on private state; this exercises the expiry path + serial-queue teardown
renderer.apply(low: 0x8000, high: 0x8000)
Thread.sleep(forTimeInterval: 0.1)
renderer.apply(low: 0, high: 0)
Thread.sleep(forTimeInterval: 0.1)
// No assertion on private state; this exercises the stop path + serial-queue teardown
// without deadlock (the ticker fires on the same queue stop() sync-hops onto).
renderer.stop()
}
func testTuningRelationsTheDesignDependsOn() {
// The watchdog must tolerate a couple of lost 500 ms host refreshes (heals, not gaps)
// but trip well before a stuck rumble reads as "still going".
XCTAssertGreaterThan(RumbleTuning.sessionStaleSeconds, 2 * 0.5)
XCTAssertLessThanOrEqual(RumbleTuning.sessionStaleSeconds, 2.5)
// Re-arm headroom must clear several ticker periods, or a steady rumble could miss the
// segment boundary and gap.
XCTAssertGreaterThanOrEqual(
@@ -123,9 +93,8 @@ final class RumbleTuningTests: XCTestCase {
// The rebake throttle must be far under the host refresh period, or refreshed level
// changes would queue behind it; and under a frame at 30 fps so ramps stay smooth.
XCTAssertLessThan(RumbleTuning.minRebakeSeconds, 1.0 / 30)
// The ticker (which lands throttled levels) must outpace the HID keepalive and the
// watchdog, or those deadlines could be overshot by a full period.
// The ticker (which lands throttled levels) must outpace the HID keepalive, or its
// deadline could be overshot by a full period.
XCTAssertLessThan(RumbleTuning.tickSeconds, RumbleTuning.hidKeepaliveSeconds)
XCTAssertLessThan(RumbleTuning.tickSeconds, RumbleTuning.sessionStaleSeconds)
}
}
+2 -2
View File
@@ -896,8 +896,8 @@ class Plugin:
except (OSError, json.JSONDecodeError):
# The client's own defaults (native display, host-default bitrate, auto pad).
return {
"width": 0, "height": 0, "refresh_hz": 0, "bitrate_kbps": 0,
"codec": "auto", "gamepad": "auto", "compositor": "auto",
"width": 0, "height": 0, "refresh_hz": 0, "render_scale": 1.0,
"bitrate_kbps": 0, "codec": "auto", "gamepad": "auto", "compositor": "auto",
"inhibit_shortcuts": True, "mic_enabled": False,
}
+1
View File
@@ -99,6 +99,7 @@ export interface StreamSettings {
width: number; // 0 = native
height: number; // 0 = native
refresh_hz: number; // 0 = native
render_scale?: number; // render-resolution multiplier; 1.0 = native (absent in pre-scale files)
bitrate_kbps: number; // 0 = host default
codec?: string; // "auto" | "hevc" | "h264" | "av1" — soft preference (absent in pre-codec files)
gamepad: string; // "auto" | "xbox360" | "xboxone" | "dualsense" | "dualshock4" | "steamdeck"
+22
View File
@@ -25,6 +25,10 @@ const RESOLUTIONS: [number, number, string][] = [
[2560, 1440, "2560 × 1440"],
];
const REFRESH = [0, 30, 60, 90, 120];
// Render-resolution multipliers (mirrors punktfunk_core::render_scale::PRESETS). 1.0 = native.
const RENDER_SCALES = [0.5, 0.67, 0.75, 1.0, 1.25, 1.5, 2.0, 3.0, 4.0];
const renderScaleLabel = (x: number): string =>
x === 1 ? "Native (1×)" : x > 1 ? `${x}× · supersample` : `${x}×`;
const GAMEPADS = ["auto", "xbox360", "xboxone", "dualsense", "dualshock4", "steamdeck"];
const GAMEPAD_LABELS: Record<string, string> = {
auto: "Automatic",
@@ -106,6 +110,24 @@ export const SettingsSection: FC = () => {
</div>
</RowActions>
</Field>
<Field
label="Render scale"
description="Supersample for sharpness (> 1×, more bandwidth) or render below native (< 1×) — the Deck resamples to its screen"
childrenContainerWidth="max"
>
<RowActions>
<div style={selectShell}>
<Dropdown
rgOptions={RENDER_SCALES.map((x) => ({ data: x, label: renderScaleLabel(x) }))}
// Snap the stored value to the nearest preset so the dropdown always shows a match.
selectedOption={RENDER_SCALES.reduce((best, x) =>
Math.abs(x - (s.render_scale ?? 1)) < Math.abs(best - (s.render_scale ?? 1)) ? x : best,
)}
onChange={(o) => patch({ render_scale: o.data as number })}
/>
</div>
</RowActions>
</Field>
<SliderField
label="Bitrate"
description="Mbit/s · 0 = host default"
+34
View File
@@ -17,6 +17,20 @@ const RESOLUTIONS: &[(u32, u32)] = &[
];
/// `0` = the monitor's native refresh, resolved at connect.
const REFRESH: &[u32] = &[0, 30, 60, 90, 120, 144, 165, 240];
/// Render-scale multipliers (persisted as f64; mirrors [`punktfunk_core::render_scale::PRESETS`]).
/// `1.0` = Native. Applied at connect and each match-window resize.
const RENDER_SCALES: &[f64] = &[0.5, 0.67, 0.75, 1.0, 1.25, 1.5, 2.0, 3.0, 4.0];
/// A compact label for a render-scale multiplier: "Native" / "1.5×" / "2× (supersample)".
fn render_scale_label(scale: f64) -> String {
if scale == 1.0 {
"Native".to_string()
} else if scale > 1.0 {
format!("{scale}× (supersample)")
} else {
format!("{scale}×")
}
}
const GAMEPADS: &[&str] = &[
"auto",
"xbox360",
@@ -304,6 +318,18 @@ pub fn show(
"",
&hz_names.iter().map(String::as_str).collect::<Vec<_>>(),
);
let scale_names: Vec<String> = RENDER_SCALES
.iter()
.map(|&s| render_scale_label(s))
.collect();
let scale_row = ChoiceRow::new(
&dialog,
inline,
"Render scale",
"Supersample for sharpness (> 1×, more bandwidth and decode) or render below native \
(< 1×) for a lighter host this device resamples to the window",
&scale_names.iter().map(String::as_str).collect::<Vec<_>>(),
);
let bitrate_row = adw::SpinRow::with_range(0.0, 3000.0, 5.0);
bitrate_row.set_title("Bitrate");
bitrate_row.set_subtitle("Mbit/s · 0 = host default · run a speed test before going high");
@@ -346,6 +372,7 @@ pub fn show(
.build();
stream.add(res_row.widget());
stream.add(hz_row.widget());
stream.add(scale_row.widget());
stream.add(&bitrate_row);
stream.add(compositor_row.widget());
stream.add(decoder_row.widget());
@@ -500,6 +527,11 @@ pub fn show(
res_row.set_selected(res_i as u32);
let hz_i = REFRESH.iter().position(|&r| r == s.refresh_hz).unwrap_or(0);
hz_row.set_selected(hz_i as u32);
let scale_i = RENDER_SCALES
.iter()
.position(|&x| (x - s.render_scale).abs() < 1e-6)
.unwrap_or_else(|| RENDER_SCALES.iter().position(|&x| x == 1.0).unwrap());
scale_row.set_selected(scale_i as u32);
bitrate_row.set_value(f64::from(s.bitrate_kbps) / 1000.0);
let pad_i = GAMEPADS.iter().position(|&g| g == s.gamepad).unwrap_or(0);
pad_row.set_selected(pad_i as u32);
@@ -545,6 +577,8 @@ pub fn show(
RESOLUTIONS[res_i - 1]
};
s.refresh_hz = REFRESH[(hz_row.selected() as usize).min(REFRESH.len() - 1)];
s.render_scale =
RENDER_SCALES[(scale_row.selected() as usize).min(RENDER_SCALES.len() - 1)];
s.bitrate_kbps = (bitrate_row.value() * 1000.0) as u32;
s.gamepad = GAMEPADS[(pad_row.selected() as usize).min(GAMEPADS.len() - 1)].to_string();
s.touch_mode =
+31
View File
@@ -1241,6 +1241,12 @@ async fn session(args: Args) -> Result<()> {
std::collections::VecDeque::new();
let mut host_us_v: Vec<u64> = Vec::new();
let mut net_us_v: Vec<u64> = Vec::new();
// T0.1 host-stage split (extended 0xCF): queue/encode/pace + the derived seal/xfer
// residual. Empty against a host that predates the stage tail.
let mut queue_us_v: Vec<u64> = Vec::new();
let mut enc_us_v: Vec<u64> = Vec::new();
let mut xfer_us_v: Vec<u64> = Vec::new();
let mut pace_us_v: Vec<u64> = Vec::new();
let mut last_rx = std::time::Instant::now();
let started = std::time::Instant::now();
// Stream-duration cap: `--seconds N`, else the 120s default. Ending the loop here reaches the
@@ -1316,6 +1322,14 @@ async fn session(args: Args) -> Result<()> {
let (_, hostnet_us) = pending_split.remove(i).unwrap();
host_us_v.push(t.host_us as u64);
net_us_v.push(hostnet_us.saturating_sub(t.host_us as u64));
if let Some(s) = t.stages {
queue_us_v.push(s.queue_us as u64);
enc_us_v.push(s.encode_us as u64);
pace_us_v.push(s.pace_us as u64);
xfer_us_v.push((t.host_us as u64).saturating_sub(
s.queue_us as u64 + s.encode_us as u64 + s.pace_us as u64,
));
}
}
}
if expected > 0 {
@@ -1399,6 +1413,23 @@ async fn session(args: Args) -> Result<()> {
"host/network latency split (host = capture→sent on the host; network = wire + \
reassembly)"
);
if !queue_us_v.is_empty() {
// The T0.1 per-stage host attribution: queue (capture→submit) → encode
// (submit→bitstream) → xfer (seal/FEC + send-channel wait, derived residual)
// → pace (the microburst spread). The four tile host_us per frame.
tracing::info!(
stage_samples = queue_us_v.len(),
queue_p50_us = pcts(&mut queue_us_v, 0.50),
queue_p95_us = pcts(&mut queue_us_v, 0.95),
encode_p50_us = pcts(&mut enc_us_v, 0.50),
encode_p95_us = pcts(&mut enc_us_v, 0.95),
xfer_p50_us = pcts(&mut xfer_us_v, 0.50),
xfer_p95_us = pcts(&mut xfer_us_v, 0.95),
pace_p50_us = pcts(&mut pace_us_v, 0.50),
pace_p95_us = pcts(&mut pace_us_v, 0.95),
"host stage split (queue → encode → xfer → pace tile the host figure)"
);
}
} else {
tracing::info!("no host timing datagrams (0xCF) — old host; host+network unsplit");
}
+2
View File
@@ -175,6 +175,8 @@ pub fn run(target: Option<&str>) -> u8 {
// Latched at console start (like the stats tier above): toggling Match window in
// the console's settings screen applies from the next console launch.
match_window: crate::session_main::match_window(&settings_at_start),
render_scale: settings_at_start.render_scale,
render_scale_max_dim: punktfunk_core::render_scale::max_dimension(&settings_at_start.codec),
};
let result =
+16
View File
@@ -127,6 +127,20 @@ mod session_main {
settings.refresh_hz
},
};
// Render scale: multiply the resolved mode (even + codec-clamped) so the host renders
// larger/smaller and the presenter resamples to the window. 1.0 = Native. Applied after the
// Native/explicit resolution so it composes uniformly with both.
let (sw, sh) = punktfunk_core::render_scale::apply(
mode.width,
mode.height,
settings.render_scale,
punktfunk_core::render_scale::max_dimension(&settings.codec),
);
let mode = Mode {
width: sw,
height: sh,
..mode
};
SessionParams {
host: addr,
port,
@@ -372,6 +386,8 @@ mod session_main {
overlay: None,
window_size: window_size(&settings),
match_window: match_window(&settings),
render_scale: settings.render_scale,
render_scale_max_dim: punktfunk_core::render_scale::max_dimension(&settings.codec),
};
let outcome =
+33
View File
@@ -19,6 +19,20 @@ const RESOLUTIONS: &[(u32, u32)] = &[
];
/// `0` = the display's native refresh, resolved at connect.
const REFRESH: &[u32] = &[0, 30, 60, 90, 120, 144, 165, 240];
/// Render-scale multipliers (persisted as f64; mirrors [`punktfunk_core::render_scale::PRESETS`]).
/// `1.0` = Native. Applied at connect and each match-window resize.
const RENDER_SCALES: &[f64] = &[0.5, 0.67, 0.75, 1.0, 1.25, 1.5, 2.0, 3.0, 4.0];
/// A compact label for a render-scale multiplier: "Native" / "1.5×" / "2× (supersample)".
fn render_scale_label(scale: f64) -> String {
if scale == 1.0 {
"Native".to_string()
} else if scale > 1.0 {
format!("{scale}\u{00D7} (supersample)")
} else {
format!("{scale}\u{00D7}")
}
}
/// Decode backend presets: `(stored value, display label)`.
// A stored legacy "hardware" (the D3D11VA era) matches no preset, so the combo shows
// Automatic — which is exactly how the session's decoder chain reads that value.
@@ -193,6 +207,24 @@ pub(crate) fn settings_page(
s.refresh_hz = REFRESH[i];
})
.tooltip("\u{201C}Native\u{201D} resolves to this display's refresh rate at connect.");
let (scale_names, scale_i) = {
let names: Vec<String> = RENDER_SCALES
.iter()
.map(|&x| render_scale_label(x))
.collect();
let i = RENDER_SCALES
.iter()
.position(|&x| (x - s.render_scale).abs() < 1e-6)
.unwrap_or_else(|| RENDER_SCALES.iter().position(|&x| x == 1.0).unwrap());
(names, i)
};
let scale_combo = setting_combo(ctx, "Render scale", scale_names, scale_i, |s, i| {
s.render_scale = RENDER_SCALES[i];
})
.tooltip(
"Supersample for sharpness (above 1\u{00D7}, more bandwidth and decode) or render below \
native (below 1\u{00D7}) for a lighter host \u{2014} this device resamples to the window.",
);
let (comp_names, comp_i) = presets(COMPOSITORS, |v| *v == s.compositor);
let comp_combo = setting_combo(ctx, "Host compositor", comp_names, comp_i, |s, i| {
s.compositor = COMPOSITORS[i].0.to_string();
@@ -441,6 +473,7 @@ pub(crate) fn settings_page(
settings_card(vec![
res_combo.into(),
hz_combo.into(),
scale_combo.into(),
fullscreen_toggle.into(),
comp_combo.into(),
]),
+30
View File
@@ -0,0 +1,30 @@
# Thin driver over the vendored Intel VPL tree (vendor/libvpl): build the
# dispatcher as a static archive, nothing else. Replaces upstream's top-level
# CMakeLists.txt (which also wires examples/env/install machinery).
cmake_minimum_required(VERSION 3.13.0)
if(MSVC)
cmake_policy(SET CMP0091 NEW)
endif()
file(STRINGS "vendor/libvpl/version.txt" version_txt)
project(vpl VERSION ${version_txt})
set(BUILD_SHARED_LIBS OFF)
set(BUILD_TESTS OFF)
set(INSTALL_DEV OFF)
set(INSTALL_LIB OFF)
set(INSTALL_EXAMPLES OFF)
# Defines ONEVPL_EXPERIMENTAL inside the dispatcher must match the bindgen
# flag in build.rs (the D3D11 import API lives behind it).
set(BUILD_EXPERIMENTAL ON)
set(CMAKE_POSITION_INDEPENDENT_CODE true)
# One predictable place for the archive; MSVC multi-config generators still
# append a per-config subdir (Release/) build.rs searches both.
set(CMAKE_ARCHIVE_OUTPUT_DIRECTORY ${PROJECT_BINARY_DIR})
# api/ first: it exports API_VERSION_MAJOR/MINOR + the vpl-api interface
# target into this scope, which libvpl/ then consumes (same ordering contract
# as upstream's top-level).
add_subdirectory(vendor/libvpl/api)
add_subdirectory(vendor/libvpl/libvpl)
+17
View File
@@ -0,0 +1,17 @@
[package]
name = "libvpl-sys"
description = "Vendored Intel VPL (libvpl) dispatcher built from source as a static archive + bindgen over the C API — substrate for the native QSV encoder backend (design/native-qsv-encoder.md). Upstream pin recorded in vendor/libvpl/PUNKTFUNK-VENDOR.txt; the tree builds with ONEVPL_EXPERIMENTAL, so bumping the pin re-opens the experimental-ABI check (§3.1)."
version.workspace = true
edition.workspace = true
rust-version.workspace = true
license.workspace = true
authors.workspace = true
repository.workspace = true
links = "vpl"
[build-dependencies]
# Same CMake-from-vendored-source model as pyrowave-sys/opus: reproducible
# offline builds (CI, MSVC, flatpak) — no network, no system libvpl.
cmake = "0.1"
# Same bindgen configuration as pyrowave-sys (runtime = dlopen libclang).
bindgen = { version = "0.72", features = ["runtime"], default-features = false }
+80
View File
@@ -0,0 +1,80 @@
//! Build the vendored Intel VPL dispatcher (`vendor/libvpl`) as a static
//! archive via CMake and generate bindings over the C API (`vpl/mfx.h`).
//!
//! Windows only — the native QSV backend is a Windows-host feature
//! (design/native-qsv-encoder.md); Linux Intel is served by VAAPI/Vulkan
//! Video. Other targets get an empty bindings file so the workspace builds
//! everywhere.
//!
//! The dispatcher is MIT and statically linked: no new runtime DLL. At run
//! time it locates the Intel GPU runtimes (`libmfx64-gen.dll`, legacy
//! `libmfxhw64.dll`) in the driver store; a box without an Intel driver just
//! fails MFXCreateSession and the encoder open falls through — the same
//! degrade contract as the NVENC/AMF runtime loaders.
//!
//! Everything compiles from the committed vendor tree: no network, no system
//! libvpl, no pkg-config. Only cmake + a libclang for bindgen are required on
//! the build machine (both already in the build closure via pyrowave-sys).
use std::env;
use std::path::PathBuf;
fn main() {
println!("cargo:rerun-if-changed=wrapper.h");
println!("cargo:rerun-if-changed=CMakeLists.txt");
println!("cargo:rerun-if-changed=vendor/libvpl");
let out = PathBuf::from(env::var("OUT_DIR").unwrap());
let bindings_path = out.join("bindings.rs");
let target_os = env::var("CARGO_CFG_TARGET_OS").unwrap_or_default();
if target_os != "windows" {
std::fs::write(
&bindings_path,
"// libvpl-sys: Windows-only, empty on this target\n",
)
.unwrap();
return;
}
let manifest_dir = PathBuf::from(env::var("CARGO_MANIFEST_DIR").unwrap());
let api_include = manifest_dir.join("vendor/libvpl/api");
// Always Release: mirrors pyrowave-sys — a debug dispatcher buys nothing
// and the MSVC debug CRT would clash with Rust's release CRT.
let dst = cmake::Config::new(&manifest_dir)
.profile("Release")
.build_target("VPL")
.build();
let build = dst.join("build");
println!("cargo:rustc-link-search=native={}", build.display());
// MSVC multi-config generators put the archive in a Release/ subdir.
println!(
"cargo:rustc-link-search=native={}",
build.join("Release").display()
);
println!("cargo:rustc-link-lib=static=vpl");
// The dispatcher's Win32 import closure (registry, DXGI adapter probing
// COM plumbing). d3d9.dll/dxgi.dll themselves are LoadLibrary'd at
// runtime — no static import.
for lib in ["advapi32", "ole32", "user32", "uuid", "gdi32"] {
println!("cargo:rustc-link-lib=dylib={lib}");
}
// ONEVPL_EXPERIMENTAL must match the CMake side (BUILD_EXPERIMENTAL=ON):
// it gates the D3D11 surface-import API in both the headers and the
// dispatcher object code.
let bindings = bindgen::Builder::default()
.header("wrapper.h")
.clang_arg(format!("-I{}", api_include.display()))
.clang_arg("-DONEVPL_EXPERIMENTAL")
// Plain MFX_* constants instead of EnumName_MFX_* — the C API is used
// by FourCC/flag value, never by enum type.
.prepend_enum_name(false)
.derive_default(true)
.generate()
.expect("bindgen failed for vpl/mfx.h");
bindings
.write_to_file(&bindings_path)
.expect("failed to write libvpl bindings");
}
+36
View File
@@ -0,0 +1,36 @@
//! Raw FFI bindings to the vendored Intel VPL C API (`vpl/mfx.h`) plus the
//! statically linked MIT dispatcher.
//!
//! Empty on targets other than Windows — see build.rs. The safe wrapper lives
//! with its consumer (`pf-encode`'s `enc/windows/qsv.rs`); this crate is
//! bindings only.
#![allow(non_upper_case_globals)]
#![allow(non_camel_case_types)]
#![allow(non_snake_case)]
// Bindgen output for a C API: u128 layout warnings and the like are upstream's concern.
#![allow(improper_ctypes)]
// Generated code — clippy findings in it (missing safety docs on generated unsafe fns, style
// nits across 14k lines) are bindgen's shape, not ours; the safe wrapper in pf-encode is the
// linted surface.
#![allow(clippy::all)]
#[cfg(target_os = "windows")]
include!(concat!(env!("OUT_DIR"), "/bindings.rs"));
#[cfg(all(test, target_os = "windows"))]
mod tests {
use super::*;
/// Link sanity: the static dispatcher resolves and its loader spins up
/// without an Intel driver present (enumeration may find zero
/// implementations — that's fine, MFXLoad itself must still succeed).
#[test]
fn dispatcher_links_and_loads() {
unsafe {
let loader = MFXLoad();
assert!(!loader.is_null(), "MFXLoad returned NULL");
MFXUnload(loader);
}
}
}
+21
View File
@@ -0,0 +1,21 @@
MIT License
Copyright (c) 2020 Intel Corporation
Permission is hereby granted, free of charge, to any person obtaining a copy
of this software and associated documentation files (the "Software"), to deal
in the Software without restriction, including without limitation the rights
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
copies of the Software, and to permit persons to whom the Software is
furnished to do so, subject to the following conditions:
The above copyright notice and this permission notice shall be included in all
copies or substantial portions of the Software.
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
SOFTWARE.
+14
View File
@@ -0,0 +1,14 @@
Upstream: https://github.com/intel/libvpl
Pin: 674d015bcb294bc39fa276e99a652ea045423e82 (v2.17.0 per version.txt, 2026-07)
License: MIT (LICENSE; third-party notices in third-party-programs.txt)
Trimmed vendor tree: only what the static dispatcher build + bindgen need.
Dropped from upstream: examples/, doc/, env/, script/, tools, api/strings/,
api/tests/, libvpl/test/, top-level CMakeLists.txt (replaced by the crate-root
CMakeLists.txt one level up, which pins BUILD_SHARED_LIBS=OFF, all INSTALL_*
off, BUILD_EXPERIMENTAL=ON).
BUILD_EXPERIMENTAL=ON defines ONEVPL_EXPERIMENTAL: the D3D11 surface-import
API (mfxMemoryInterface / mfxSurfaceD3D11Tex2D) is experimental ABI that is
release-coupled — bumping this pin requires re-checking every ONEVPL_EXPERIMENTAL
symbol qsv.rs touches (design/native-qsv-encoder.md §3.1/§3.4).
+41
View File
@@ -0,0 +1,41 @@
# ##############################################################################
# Copyright (C) Intel Corporation
#
# SPDX-License-Identifier: MIT
# ##############################################################################
cmake_minimum_required(VERSION 3.13.0)
set(VPL_API_INCLUDE_DIR ${CMAKE_CURRENT_SOURCE_DIR})
if(DEFINED ENV{ONEVPL_API_HEADER_DIRECTORY})
set(VPL_API_INCLUDE_DIR $ENV{ONEVPL_API_HEADER_DIRECTORY})
message(STATUS "Using custom API header directory: ${VPL_API_INCLUDE_DIR}")
endif()
# get API version
file(READ ${VPL_API_INCLUDE_DIR}/vpl/mfxdefs.h mfxdefs)
string(REGEX MATCH "MFX_VERSION_MAJOR ([0-9]*)" _ ${mfxdefs})
set(API_VERSION_MAJOR ${CMAKE_MATCH_1})
set(API_VERSION_MAJOR
${API_VERSION_MAJOR}
PARENT_SCOPE)
string(REGEX MATCH "MFX_VERSION_MINOR ([0-9]*)" _ ${mfxdefs})
set(API_VERSION_MINOR ${CMAKE_MATCH_1})
set(API_VERSION_MINOR
${API_VERSION_MINOR}
PARENT_SCOPE)
message(STATUS "API version: ${API_VERSION_MAJOR}.${API_VERSION_MINOR}")
add_library(vpl-api INTERFACE)
target_include_directories(vpl-api INTERFACE ${VPL_API_INCLUDE_DIR})
if(INSTALL_DEV)
install(
DIRECTORY ${VPL_API_INCLUDE_DIR}/vpl
DESTINATION ${CMAKE_INSTALL_INCLUDEDIR}
COMPONENT ${VPL_COMPONENT_DEV})
endif()
+31
View File
@@ -0,0 +1,31 @@
/*############################################################################
# Copyright Intel Corporation
#
# SPDX-License-Identifier: MIT
############################################################################*/
#ifndef __MFX_H__
#define __MFX_H__
#include "mfxdefs.h"
#include "mfxcommon.h"
#include "mfxstructures.h"
#include "mfxdispatcher.h"
#include "mfximplcaps.h"
#include "mfxsession.h"
#include "mfxvideo.h"
#include "mfxadapter.h"
#include "mfxbrc.h"
#include "mfxmvc.h"
#include "mfxpcp.h"
#include "mfxvp8.h"
#include "mfxjpeg.h"
#include "mfxsurfacepool.h"
#ifdef ONEVPL_EXPERIMENTAL
#include "mfxencodestats.h"
#endif
#endif /* __MFXDEFS_H__ */
+85
View File
@@ -0,0 +1,85 @@
/*############################################################################
# Copyright Intel Corporation
#
# SPDX-License-Identifier: MIT
############################################################################*/
#include "mfxdefs.h"
#ifndef __MFXADAPTER_H__
#define __MFXADAPTER_H__
#include "mfxstructures.h"
#ifdef __cplusplus
extern "C"
{
#endif
/*!
@brief
Returns a list of adapters that are suitable to handle workload @p input_info. The list is sorted in priority order, with iGPU given the highest precedence.
This rule may change in the future. If the @p input_info pointer is NULL, the list of all available adapters will be returned.
@param[in] input_info Pointer to workload description. See mfxComponentInfo description for details.
@param[out] adapters Pointer to output description of all suitable adapters for input workload. See mfxAdaptersInfo description for details.
@return
MFX_ERR_NONE The function completed successfully. \n
MFX_ERR_NULL_PTR
@p input_info or adapters pointer is NULL. \n
MFX_ERR_NOT_FOUND No suitable adapters found. \n
MFX_WRN_OUT_OF_RANGE Not enough memory to report back entire list of adapters. In this case as many adapters as possible will be returned.
@since This function is available since API version 1.31.
@deprecated Deprecated in API version 2.9. Use MFXEnumImplementations and MFXSetConfigFilterProperty to query adapter capabilities and
to select a suitable adapter for the input workload.
Use MFX_DEPRECATED_OFF macro to turn off the deprecation message visualization.
*/
MFX_DEPRECATED mfxStatus MFX_CDECL MFXQueryAdapters(mfxComponentInfo* input_info, mfxAdaptersInfo* adapters);
/*!
@brief
Returns list of adapters that are suitable to decode the input bitstream. The list is sorted in priority order, with iGPU given the highest precedence. This rule may change in the future. This function is a simplification of MFXQueryAdapters, because bitstream is a description of the workload itself.
@param[in] bitstream Pointer to bitstream with input data.
@param[in] codec_id Codec ID to determine the type of codec for the input bitstream.
@param[out] adapters Pointer to the output list of adapters. Memory should be allocated by user. See mfxAdaptersInfo description for details.
@return
MFX_ERR_NONE The function completed successfully. \n
MFX_ERR_NULL_PTR bitstream or @p adapters pointer is NULL. \n
MFX_ERR_NOT_FOUND No suitable adapters found. \n
MFX_WRN_OUT_OF_RANGE Not enough memory to report back entire list of adapters. In this case as many adapters as possible will be returned.
@since This function is available since API version 1.31.
@deprecated Deprecated in API version 2.9. Use MFXEnumImplementations and MFXSetConfigFilterProperty to query adapter capabilities and
to select a suitable adapter for the input workload.
Use MFX_DEPRECATED_OFF macro to turn off the deprecation message visualization.
*/
MFX_DEPRECATED mfxStatus MFX_CDECL MFXQueryAdaptersDecode(mfxBitstream* bitstream, mfxU32 codec_id, mfxAdaptersInfo* adapters);
/*!
@brief
Returns the number of detected graphics adapters. It can be used before calling MFXQueryAdapters to determine the size of input data that the user will need to allocate.
@param[out] num_adapters Pointer for the output number of detected graphics adapters.
@return
MFX_ERR_NONE The function completed successfully. \n
MFX_ERR_NULL_PTR num_adapters pointer is NULL.
@since This function is available since API version 1.31.
@deprecated Deprecated in API version 2.9. Use MFXEnumImplementations and MFXSetConfigFilterProperty to query adapter capabilities and
to select a suitable adapter for the input workload.
Use MFX_DEPRECATED_OFF macro to turn off the deprecation message visualization.
*/
MFX_DEPRECATED mfxStatus MFX_CDECL MFXQueryAdaptersNumber(mfxU32* num_adapters);
#ifdef __cplusplus
} // extern "C"
#endif
#endif // __MFXADAPTER_H__
+175
View File
@@ -0,0 +1,175 @@
/*############################################################################
# Copyright Intel Corporation
#
# SPDX-License-Identifier: MIT
############################################################################*/
#ifndef __MFXBRC_H__
#define __MFXBRC_H__
#include "mfxstructures.h"
#ifdef __cplusplus
extern "C"
{
#endif /* __cplusplus */
/*! See the mfxExtBRC structure for details. */
enum {
MFX_EXTBUFF_BRC = MFX_MAKEFOURCC('E','B','R','C')
};
MFX_PACK_BEGIN_STRUCT_W_PTR()
/*!
Describes frame parameters required for external BRC functions.
*/
typedef struct {
mfxU32 reserved[23];
mfxU16 SceneChange; /*!< Frame belongs to a new scene if non zero. */
mfxU16 LongTerm; /*!< Frame is a Long Term Reference frame if non zero. */
mfxU32 FrameCmplx; /*!< Frame Complexity Frame spatial complexity if non zero. Zero if complexity is not available. */
mfxU32 EncodedOrder; /*!< The frame number in a sequence of reordered frames starting from encoder Init. */
mfxU32 DisplayOrder; /*!< The frame number in a sequence of frames in display order starting from last IDR. */
mfxU32 CodedFrameSize; /*!< Size of the frame in bytes after encoding. */
mfxU16 FrameType; /*!< Frame type. See FrameType enumerator for possible values. */
mfxU16 PyramidLayer; /*!< B-pyramid or P-pyramid layer that the frame belongs to. */
mfxU16 NumRecode; /*!< Number of recodings performed for this frame. */
mfxU16 NumExtParam; /*!< Reserved for future use. */
mfxExtBuffer** ExtParam;/*!< Reserved for future use. */
} mfxBRCFrameParam;
MFX_PACK_END()
MFX_PACK_BEGIN_STRUCT_W_PTR()
/*!
Specifies controls for next frame encoding provided by external BRC functions.
*/
typedef struct {
mfxI32 QpY; /*!< Frame-level Luma QP. */
mfxU32 InitialCpbRemovalDelay; /*!< See initial_cpb_removal_delay in codec standard. Ignored if no HRD control:
mfxExtCodingOption::VuiNalHrdParameters = MFX_CODINGOPTION_OFF. Calculated by encoder if
initial_cpb_removal_delay==0 && initial_cpb_removal_offset == 0 && HRD control is switched on. */
mfxU32 InitialCpbRemovalOffset; /*!< See initial_cpb_removal_offset in codec standard. Ignored if no HRD control:
mfxExtCodingOption::VuiNalHrdParameters = MFX_CODINGOPTION_OFF. Calculated by encoder if
initial_cpb_removal_delay==0 && initial_cpb_removal_offset == 0 && HRD control is switched on. */
mfxU32 reserved1[7];
mfxU32 MaxFrameSize; /*!< Max frame size in bytes. Option for repack feature. Driver calls PAK until current frame size is
less than or equal to MaxFrameSize, or number of repacking for this frame is equal to MaxNumRePak. Repack is available
if there is driver support, MaxFrameSize !=0, and MaxNumRePak != 0. Ignored if MaxNumRePak == 0. */
mfxU8 DeltaQP[8]; /*!< Option for repack feature. Ignored if MaxNumRePak == 0 or MaxNumRePak==0. If current
frame size > MaxFrameSize and/or number of repacking (nRepack) for this frame <= MaxNumRePak,
PAK is called with QP = mfxBRCFrameCtrl::QpY + Sum(DeltaQP[i]), where i = [0,nRepack].
Non zero DeltaQP[nRepack] are ignored if nRepack > MaxNumRePak.
If repacking feature is on ( MaxFrameSize & MaxNumRePak are not zero), it is calculated by the encoder. */
mfxU16 MaxNumRepak; /*!< Number of possible repacks in driver if current frame size > MaxFrameSize. Ignored if MaxFrameSize==0.
See MaxFrameSize description. Possible values are in the range of 0 to 8. */
mfxU16 NumExtParam; /*!< Reserved for future use. */
mfxExtBuffer** ExtParam; /*!< Reserved for future use. */
} mfxBRCFrameCtrl;
MFX_PACK_END()
/*! The BRCStatus enumerator itemizes instructions to the encoder by mfxExtBrc::Update. */
enum {
MFX_BRC_OK = 0, /*!< CodedFrameSize is acceptable, no further recoding/padding/skip required, proceed to next frame. */
MFX_BRC_BIG_FRAME = 1, /*!< Coded frame is too big, recoding required. */
MFX_BRC_SMALL_FRAME = 2, /*!< Coded frame is too small, recoding required. */
MFX_BRC_PANIC_BIG_FRAME = 3, /*!< Coded frame is too big, no further recoding possible - skip frame. */
MFX_BRC_PANIC_SMALL_FRAME = 4 /*!< Coded frame is too small, no further recoding possible - required padding to mfxBRCFrameStatus::MinFrameSize. */
};
MFX_PACK_BEGIN_STRUCT_W_PTR()
/*!
Specifies instructions for the encoder provided by external BRC after each frame encoding. See the BRCStatus enumerator for details.
*/
typedef struct {
mfxU32 MinFrameSize; /*!< Size in bytes, coded frame must be padded to when Status = MFX_BRC_PANIC_SMALL_FRAME. */
mfxU16 BRCStatus; /*!< BRC status. See the BRCStatus enumerator for possible values. */
mfxU16 reserved[25];
mfxHDL reserved1;
} mfxBRCFrameStatus;
MFX_PACK_END()
MFX_PACK_BEGIN_STRUCT_W_PTR()
/*!
Contains a set of callbacks to perform external bitrate control. Can be attached to the mfxVideoParam structure during
encoder initialization. Set the mfxExtCodingOption2::ExtBRC option to ON to make the encoder use the external BRC instead of the native one.
*/
typedef struct {
mfxExtBuffer Header; /*!< Extension buffer header. Header.BufferId must be equal to MFX_EXTBUFF_BRC. */
mfxU32 reserved[14];
mfxHDL pthis; /*!< Pointer to the BRC object. */
/*!
@brief Initializes the BRC session according to parameters from input mfxVideoParam and attached structures. It does not modify the input mfxVideoParam and attached structures. Invoked during MFXVideoENCODE_Init.
@param[in] pthis Pointer to the BRC object.
@param[in] par Pointer to the mfxVideoParam structure that was used for the encoder initialization.
@return
MFX_ERR_NONE The function completed successfully. \n
MFX_ERR_UNSUPPORTED The function detected unsupported video parameters.
*/
mfxStatus (MFX_CDECL *Init) (mfxHDL pthis, mfxVideoParam* par);
/*!
@brief Resets BRC session according to new parameters. It does not modify the input mfxVideoParam and attached structures. Invoked during MFXVideoENCODE_Reset.
@param[in] pthis Pointer to the BRC object.
@param[in] par Pointer to the mfxVideoParam structure that was used for the encoder initialization.
@return
MFX_ERR_NONE The function completed successfully. \n
MFX_ERR_UNSUPPORTED The function detected unsupported video parameters. \n
MFX_ERR_INCOMPATIBLE_VIDEO_PARAM The function detected that the video parameters provided by the application are incompatible with
initialization parameters. Reset requires additional memory allocation and cannot be executed.
*/
mfxStatus (MFX_CDECL *Reset) (mfxHDL pthis, mfxVideoParam* par);
/*!
@brief Deallocates any internal resources acquired in Init for this BRC session. Invoked during MFXVideoENCODE_Close.
@param[in] pthis Pointer to the BRC object.
@return
MFX_ERR_NONE The function completed successfully.
*/
mfxStatus (MFX_CDECL *Close) (mfxHDL pthis);
/*! @brief Returns controls (@p ctrl) to encode next frame based on info from input mfxBRCFrameParam structure (@p par) and
internal BRC state. Invoked asynchronously before each frame encoding or recoding.
@param[in] pthis Pointer to the BRC object.
@param[in] par Pointer to the mfxVideoParam structure that was used for the encoder initialization.
@param[out] ctrl Pointer to the output mfxBRCFrameCtrl structure.
@return
MFX_ERR_NONE The function completed successfully.
*/
mfxStatus (MFX_CDECL* GetFrameCtrl) (mfxHDL pthis, mfxBRCFrameParam* par, mfxBRCFrameCtrl* ctrl);
/*!
@brief Updates internal BRC state and returns status to instruct encoder whether it should recode the previous frame,
skip the previous frame, do padding, or proceed to next frame based on info from input mfxBRCFrameParam and mfxBRCFrameCtrl structures.
Invoked asynchronously after each frame encoding or recoding.
@param[in] pthis Pointer to the BRC object.
@param[in] par Pointer to the mfxVideoParam structure that was used for the encoder initialization.
@param[in] ctrl Pointer to the output mfxBRCFrameCtrl structure.
@param[in] status Pointer to the output mfxBRCFrameStatus structure.
@return
MFX_ERR_NONE The function completed successfully.
*/
mfxStatus (MFX_CDECL* Update) (mfxHDL pthis, mfxBRCFrameParam* par, mfxBRCFrameCtrl* ctrl, mfxBRCFrameStatus* status);
mfxHDL reserved1[10];
} mfxExtBRC;
MFX_PACK_END()
#ifdef __cplusplus
} // extern "C"
#endif /* __cplusplus */
#endif
+375
View File
@@ -0,0 +1,375 @@
/*############################################################################
# Copyright Intel Corporation
#
# SPDX-License-Identifier: MIT
############################################################################*/
#ifndef __MFXCAMERA_H__
#define __MFXCAMERA_H__
#include "mfxcommon.h"
#if !defined(__GNUC__)
#pragma warning(disable : 4201)
#endif
#ifdef __cplusplus
extern "C" {
#endif /* __cplusplus */
/*!
The ExtendedBufferID enumerator itemizes and defines identifiers (BufferId) for extended buffers in camera processing.
The application should attach these extended buffers to the mfxVideoParam structure to configure camera processing through VideoVPP functions.
And Implementation capabilities of camera processing features can be delivered by the function MFXQueryImplsDescription via VPP configuration
mfxVPPDescription.
*/
enum {
/*!
This extended buffer is mandatory for camera raw accelerator initialization. See the mfxExtCamPipeControl structure for details.
The application should attach this extended buffer to the mfxVideoParam structure to configure camera processing initialization.
*/
MFX_EXTBUF_CAM_PIPECONTROL = MFX_MAKEFOURCC('C', 'P', 'P', 'C'),
/*!
This extended buffer defines control parameters for the Camera White Balance filter algorithm. See mfxExtCamWhiteBalance structure for details.
The application should attach this extended buffer to the mfxVideoParam structure to configure camera processing initialization.
*/
MFX_EXTBUF_CAM_WHITE_BALANCE = MFX_MAKEFOURCC('C', 'W', 'B', 'L'),
/*!
This extended buffer defines control parameters for the Camera Hot Pixel Removal filter algorithm. See mfxExtCamHotPixelRemoval structure for details.
The application should attach this extended buffer to the mfxVideoParam structure to configure camera processing initialization.
*/
MFX_EXTBUF_CAM_HOT_PIXEL_REMOVAL = MFX_MAKEFOURCC('C', 'H', 'P', 'R'),
/*!
This extended buffer defines control parameters for the Camera Black Level Correction filter algorithm. See mfxExtCamBlackLevelCorrection structure for details.
The application should attach this extended buffer to the mfxVideoParam structure to configure camera processing initialization.
*/
MFX_EXTBUF_CAM_BLACK_LEVEL_CORRECTION = MFX_MAKEFOURCC('C', 'B', 'L', 'C'),
/*!
This extended buffer defines control parameters for the Camera Vignette Correction filter algorithm. See mfxCamVignetteCorrectionParam structure for details.
The application should attach this extended buffer to the mfxVideoParam structure to configure camera processing initialization.
*/
MFX_EXTBUF_CAM_VIGNETTE_CORRECTION = MFX_MAKEFOURCC('C', 'V', 'G', 'T'),
/*!
This extended buffer defines control parameters for the Camera Bayer Denoise filter algorithm. See mfxExtCamBayerDenoise structure for details.
The application should attach this extended buffer to the mfxVideoParam structure to configure camera processing initialization.
*/
MFX_EXTBUF_CAM_BAYER_DENOISE = MFX_MAKEFOURCC('C', 'D', 'N', 'S'),
/*!
This extended buffer defines control parameters for the Camera Color Correction filter algorithm. See mfxExtCamColorCorrection3x3 structure for details.
The application should attach this extended buffer to the mfxVideoParam structure to configure camera processing initialization.
*/
MFX_EXTBUF_CAM_COLOR_CORRECTION_3X3 = MFX_MAKEFOURCC('C', 'C', '3', '3'),
/*!
This extended buffer defines control parameters for the Camera Padding. See mfxExtCamPadding structure for details.
The application should attach this extended buffer to the mfxVideoParam structure to configure camera processing initialization.
*/
MFX_EXTBUF_CAM_PADDING = MFX_MAKEFOURCC('C', 'P', 'A', 'D'),
/*!
This extended buffer defines control parameters for the Camera Forward Gamma Correction filter algorithm. See mfxExtCamFwdGamma structure for details.
The application should attach this extended buffer to the mfxVideoParam structure to configure camera processing initialization.
*/
MFX_EXTBUF_CAM_FORWARD_GAMMA_CORRECTION =
MFX_MAKEFOURCC('C', 'F', 'G', 'C'),
/*!
This extended buffer defines control parameters for the Camera Lens Geometry Distortion and Chroma Aberration Correction filter algorithm. See mfxExtCamLensGeomDistCorrection structure for details.
The application should attach this extended buffer to the mfxVideoParam structure to configure camera processing initialization.
*/
MFX_EXTBUF_CAM_LENS_GEOM_DIST_CORRECTION =
MFX_MAKEFOURCC('C', 'L', 'G', 'D'),
/*!
This extended buffer defines control parameters for the Camera 3DLUT filter algorithm. See mfxExtCam3DLut structure for details.
The application should attach this extended buffer to the mfxVideoParam structure to configure camera processing initialization.
*/
MFX_EXTBUF_CAM_3DLUT = MFX_MAKEFOURCC('C', 'L', 'U', 'T'),
/*!
This extended buffer defines control parameters for the Camera Total Color Control algorithm. See mfxExtCamTotalColorControl structure for details.
The application should attach this extended buffer to the mfxVideoParam structure to configure camera processing initialization.
*/
MFX_EXTBUF_CAM_TOTAL_COLOR_CONTROL = MFX_MAKEFOURCC('C', 'T', 'C', 'C'),
/*!
This extended buffer defines control parameters for the Camera YUV to RGB conversion algorithm. See mfxExtCamCscYuvRgb structure for details.
The application should attach this extended buffer to the mfxVideoParam structure to configure camera processing initialization.
*/
MFX_EXTBUF_CAM_CSC_YUV_RGB = MFX_MAKEFOURCC('C', 'C', 'Y', 'R')
};
/*!
A enumeration that defines white balance mode.
*/
typedef enum {
MFX_CAM_WHITE_BALANCE_MANUAL = 0x0001, /*!< White balance manual mode.*/
MFX_CAM_WHITE_BALANCE_AUTO = 0x0002 /*!< White balance auto mode.*/
} mfxCamWhiteBalanceMode;
MFX_PACK_BEGIN_USUAL_STRUCT()
/*!
A hint structure that configures Camera White Balance filter.
*/
typedef struct {
mfxExtBuffer Header; /*!< Extension buffer header. Header.BufferId must be equal to MFX_EXTBUF_CAM_WHITE_BALANCE. */
mfxU32 Mode; /*!< Specifies one of White Balance operation modes defined in enumeration mfxCamWhiteBalanceMode. */
mfxF64 R; /*!< White Balance Red correction.*/
mfxF64 G0; /*!< White Balance Green Top correction.*/
mfxF64 B; /*!< White Balance Blue correction.*/
mfxF64 G1; /*!< White Balance Green Bottom correction. */
mfxU32 reserved[8]; /*!< Reserved for future extension. */
} mfxExtCamWhiteBalance;
MFX_PACK_END()
MFX_PACK_BEGIN_USUAL_STRUCT()
/*!
A hint structure that configures Camera Total Color Control filter.
*/
typedef struct {
mfxExtBuffer Header; /*!< Extension buffer header. Header.BufferId must be equal to MFX_EXTBUF_CAM_TOTAL_COLOR_CONTROL. */
mfxU16 R; /*!< Red element.*/
mfxU16 G; /*!< Green element.*/
mfxU16 B; /*!< Blue element.*/
mfxU16 C; /*!< Cyan element.*/
mfxU16 M; /*!< Magenta element.*/
mfxU16 Y; /*!< Yellow element.*/
mfxU16 reserved[6]; /*!< Reserved for future extension.*/
} mfxExtCamTotalColorControl;
MFX_PACK_END()
MFX_PACK_BEGIN_USUAL_STRUCT()
/*!
A hint structure that configures Camera YUV to RGB format conversion.
*/
typedef struct {
mfxExtBuffer Header; /*!< Extension buffer header. Header.BufferId must be equal to MFX_EXTBUF_CAM_CSC_YUV_RGB. */
mfxF32 PreOffset[3]; /*!< Specifies offset for conversion from full range RGB input to limited range YUV for input color coordinate.*/
mfxF32 Matrix[3][3]; /*!< Specifies conversion matrix with CSC coefficients.*/
mfxF32 PostOffset[3]; /*!< Specifies offset for conversion from full range RGB input to limited range YUV for output color coordinate.*/
mfxU16 reserved[30]; /*!< Reserved for future extension.*/
} mfxExtCamCscYuvRgb;
MFX_PACK_END()
MFX_PACK_BEGIN_USUAL_STRUCT()
/*!
A hint structure that configures Camera Hot Pixel Removal filter.
*/
typedef struct {
mfxExtBuffer Header; /*!< Extension buffer header. Header.BufferId must be equal to MFX_EXTBUF_CAM_HOT_PIXEL_REMOVAL. */
mfxU16 PixelThresholdDifference; /*!< Threshold for Hot Pixel difference. */
mfxU16 PixelCountThreshold; /*!< Count pixel detection.*/
mfxU16 reserved[32]; /*!< Reserved for future extension.*/
} mfxExtCamHotPixelRemoval;
MFX_PACK_END()
MFX_PACK_BEGIN_USUAL_STRUCT()
typedef struct {
/*!
A hint structure that configures Camera black level correction.
*/
mfxExtBuffer Header; /*!< Extension buffer header. Header.BufferId must be equal to MFX_EXTBUF_CAM_BLACK_LEVEL_CORRECTION. */
mfxU16 R; /*!< Black Level Red correction.*/
mfxU16 G0; /*!< Black Level Green Top correction.*/
mfxU16 B; /*!< Black Level Blue correction.*/
mfxU16 G1; /*!< Black Level Green Bottom correction.*/
mfxU32 reserved[4]; /*!< Reserved for future extension.*/
} mfxExtCamBlackLevelCorrection;
MFX_PACK_END()
MFX_PACK_BEGIN_USUAL_STRUCT()
/*!
A structure that defines Camera Vignette Correction Element.
*/
typedef struct {
mfxU8 integer; /*!< Integer part of correction element.*/
mfxU8 mantissa; /*!< Fractional part of correction element.*/
mfxU8 reserved[6]; /*!< Reserved for future extension.*/
} mfxCamVignetteCorrectionElement;
MFX_PACK_END()
MFX_PACK_BEGIN_USUAL_STRUCT()
/*!
A structure that defines Camera Vignette Correction Parameters.
*/
typedef struct {
mfxCamVignetteCorrectionElement R; /*!< Red correction element.*/
mfxCamVignetteCorrectionElement G0; /*!< Green top correction element.*/
mfxCamVignetteCorrectionElement B; /*!< Blue Correction element.*/
mfxCamVignetteCorrectionElement G1; /*!< Green bottom correction element.*/
mfxU32 reserved[4]; /*!< Reserved for future extension.*/
} mfxCamVignetteCorrectionParam;
MFX_PACK_END()
MFX_PACK_BEGIN_STRUCT_W_PTR()
/*!
A hint structure that configures Camera Vignette Correction filter.
*/
typedef struct {
mfxExtBuffer Header; /*!< Extension buffer header. Header.BufferId must be equal to MFX_EXTBUF_CAM_VIGNETTE_CORRECTION. */
mfxU32 Width; /*!< Width of Correction Map 2D buffer in mfxCamVignetteCorrectionParam elements. */
mfxU32 Height; /*!< Height of Correction Map 2D buffer in mfxCamVignetteCorrectionParam elements. */
mfxU32 Pitch; /*!< Pitch of Correction Map 2D buffer in mfxCamVignetteCorrectionParam elements. */
mfxU32 reserved[7]; /*!< Reserved for future extension.*/
union {
mfxCamVignetteCorrectionParam* CorrectionMap; /*!< 2D buffer of mfxCamVignetteCorrectionParam elements.*/
mfxU64 reserved1; /*!< Reserved for alignment on 32bit and 64bit.*/
};
} mfxExtCamVignetteCorrection;
MFX_PACK_END()
MFX_PACK_BEGIN_USUAL_STRUCT()
/*!
A hint structure that configures Camera Bayer denoise filter.
*/
typedef struct {
mfxExtBuffer Header; /*!< Extension buffer header. Header.BufferId must be equal to MFX_EXTBUF_CAM_BAYER_DENOISE. */
mfxU16 Threshold; /*!< Level of denoise, legal values: [0:63].*/
mfxU16 reserved[27]; /*!< Reserved for future extension.*/
} mfxExtCamBayerDenoise;
MFX_PACK_END()
MFX_PACK_BEGIN_STRUCT_W_L_TYPE()
/*!
A hint structure that configures Camera Color correction filter.
*/
typedef struct {
mfxExtBuffer Header; /*!< Extension buffer header. Header.BufferId must be equal to MFX_EXTBUF_CAM_COLOR_CORRECTION_3X3. */
mfxF32 CCM[3][3]; /*!< 3x3 dimension matrix providing RGB Color Correction coefficients.*/
mfxU32 reserved[32]; /*!< Reserved for future extension.*/
} mfxExtCamColorCorrection3x3;
MFX_PACK_END()
MFX_PACK_BEGIN_USUAL_STRUCT()
/*!
A hint structure that configures Camera Padding.
*/
typedef struct {
mfxExtBuffer Header; /*!< Extension buffer header. Header.BufferId must be equal to MFX_EXTBUF_CAM_PADDING. */
mfxU16 Top; /*!< Specify number of padded columns respectively. Currently only 8 pixels supported for all dimensions. */
mfxU16 Bottom; /*!< Specify number of padded columns respectively. Currently only 8 pixels supported for all dimensions. */
mfxU16 Left; /*!< Specify number of padded rows respectively. Currently only 8 pixels supported for all dimensions. */
mfxU16 Right; /*!< Specify number of padded rows respectively. Currently only 8 pixels supported for all dimensions. */
mfxU32 reserved[4]; /*!< Reserved for future extension.*/
} mfxExtCamPadding;
MFX_PACK_END()
/*!
A enumeration that defines Bayer mode.
*/
typedef enum {
/*!
Pixel Representation BG
GR.
*/
MFX_CAM_BAYER_BGGR = 0x0000,
/*!
Pixel Representation RG
GB.
*/
MFX_CAM_BAYER_RGGB = 0x0001,
/*!
Pixel Representation GB
RG.
*/
MFX_CAM_BAYER_GBRG = 0x0002,
/*!
Pixel Representation GR
BG.
*/
MFX_CAM_BAYER_GRBG = 0x0003
} mfxCamBayerFormat;
MFX_PACK_BEGIN_USUAL_STRUCT()
/*!
A hint structure that configures camera pipe control.
*/
typedef struct {
mfxExtBuffer Header; /*!< Extension buffer header. Header.BufferId must be equal to MFX_EXTBUF_CAM_PIPECONTROL. */
mfxU16 RawFormat; /*!< Specifies one of the four Bayer patterns defined in mfxCamBayerFormat enumeration. */
mfxU16 reserved1; /*!< Reserved for future extension.*/
mfxU32 reserved[5]; /*!< Reserved for future extension.*/
} mfxExtCamPipeControl;
MFX_PACK_END()
MFX_PACK_BEGIN_USUAL_STRUCT()
/*!
A structure that specifies forward gamma segment.
*/
typedef struct {
mfxU16 Pixel; /*!< Pixel value.*/
mfxU16 Red; /*!< Corrected Red value.*/
mfxU16 Green; /*!< Corrected Green value.*/
mfxU16 Blue; /*!< Corrected Blue value.*/
} mfxCamFwdGammaSegment;
MFX_PACK_END()
MFX_PACK_BEGIN_STRUCT_W_L_TYPE()
/*!
A hint structure that configures Camera Forward Gamma Correction filter.
*/
typedef struct {
mfxExtBuffer Header; /*!< Extension buffer header. Header.BufferId must be equal to MFX_EXTBUF_CAM_FORWARD_GAMMA_CORRECTION. */
mfxU16 reserved[19]; /*!< Reserved for future extension.*/
mfxU16 NumSegments; /*!< Number of Gamma segments.*/
union {
mfxCamFwdGammaSegment* Segment; /*!< Pointer to Gamma segments array.*/
mfxU64 reserved1; /*!< Reserved for future extension.*/
};
} mfxExtCamFwdGamma;
MFX_PACK_END()
MFX_PACK_BEGIN_USUAL_STRUCT()
/*!
A hint structure that configures Camera Lens Geometry Distortion and Chroma Aberration Correction filter.
*/
typedef struct {
mfxExtBuffer
Header; /*!< Extension buffer header. Header.BufferId must be equal to MFX_EXTBUF_CAM_LENS_GEOM_DIST_CORRECTION. */
mfxF32 a[3]; /*!< Polynomial coefficients a for R/G/B*/
mfxF32 b[3]; /*!< Polynomial coefficients b for R/G/B*/
mfxF32 c[3]; /*!< Polynomial coefficients c for R/G/B*/
mfxF32 d[3]; /*!< Polynomial coefficients d for R/G/B*/
mfxU16 reserved[36]; /*!< Reserved for future extension.*/
} mfxExtCamLensGeomDistCorrection;
MFX_PACK_END()
/*!
A enumeration that defines 3DLUT size.
*/
enum {
MFX_CAM_3DLUT17_SIZE = (17 * 17 * 17), /*!< 17^3 LUT size*/
MFX_CAM_3DLUT33_SIZE = (33 * 33 * 33), /*!< 33^3 LUT size*/
MFX_CAM_3DLUT65_SIZE = (65 * 65 * 65) /*!< 65^3 LUT size*/
};
MFX_PACK_BEGIN_USUAL_STRUCT()
/*!
A structure that defines 3DLUT entry.
*/
typedef struct {
mfxU16 R; /*!< R channel*/
mfxU16 G; /*!< G channel*/
mfxU16 B; /*!< B channel*/
mfxU16 Reserved; /*!< Reserved for future extension.*/
} mfxCam3DLutEntry;
MFX_PACK_END()
MFX_PACK_BEGIN_STRUCT_W_L_TYPE()
/*!
A hint structure that configures Camera 3DLUT filter.
*/
typedef struct {
mfxExtBuffer Header; /*!< Extension buffer header. Header.BufferId must be equal to MFX_EXTBUF_CAM_3DLUT. */
mfxU16 reserved[10]; /*!< Reserved for future extension.*/
mfxU32 Size; /*!< LUT size, defined in MFX_CAM_3DLUT17/33/65_SIZE enumeration.*/
union {
mfxCam3DLutEntry* Table; /*!< Pointer to mfxCam3DLutEntry, size of each dimension depends on LUT size, e.g. LUT[17][17][17] for 17x17x17 look up table.*/
mfxU64 reserved1; /*!< Reserved for future extension.*/
};
} mfxExtCam3DLut;
MFX_PACK_END()
#ifdef __cplusplus
} // extern "C"
#endif
#endif // __MFXCAMERA_H__
+740
View File
@@ -0,0 +1,740 @@
/*############################################################################
# Copyright Intel Corporation
#
# SPDX-License-Identifier: MIT
############################################################################*/
#ifndef __MFXCOMMON_H__
#define __MFXCOMMON_H__
#include "mfxdefs.h"
#if !defined (__GNUC__)
#pragma warning(disable: 4201)
#endif
#ifdef __cplusplus
extern "C"
{
#endif /* __cplusplus */
#define MFX_MAKEFOURCC(A,B,C,D) ((((int)A))+(((int)B)<<8)+(((int)C)<<16)+(((int)D)<<24))
/* Extended Configuration Header Structure */
MFX_PACK_BEGIN_USUAL_STRUCT()
/*! The common header definition for external buffers and video
processing hints. */
typedef struct {
mfxU32 BufferId; /*!< Identifier of the buffer content. See the ExtendedBufferID enumerator for a complete list of extended buffers. */
mfxU32 BufferSz; /*!< Size of the buffer. */
} mfxExtBuffer;
MFX_PACK_END()
#define MFX_REFINTERFACE_VERSION MFX_STRUCT_VERSION(1, 0)
MFX_PACK_BEGIN_STRUCT_W_PTR()
/*! The structure represents reference counted interface structure.
The memory is allocated and released by the implementation.
*/
typedef struct mfxRefInterface {
mfxHDL Context; /*!< The context of the container interface. User should not touch (change, set, null) this pointer. */
mfxStructVersion Version; /*!< The version of the structure. */
/*! @brief
Increments the internal reference counter of the container. The container is not destroyed until the container
is released using the mfxRefInterface::Release function.
mfxRefInterface::AddRef should be used each time a new link to the container is created
(for example, copy structure) for proper management.
@param[in] ref_interface Valid interface.
@return
MFX_ERR_NONE If no error. \n
MFX_ERR_NULL_PTR If interface is NULL. \n
MFX_ERR_INVALID_HANDLE If mfxRefInterface->Context is invalid (for example NULL). \n
MFX_ERR_UNKNOWN Any internal error.
*/
mfxStatus (MFX_CDECL *AddRef)(struct mfxRefInterface* ref_interface);
/*! @brief
Decrements the internal reference counter of the container. mfxRefInterface::Release should be called after using the
mfxRefInterface::AddRef function to add a container or when allocation logic requires it.
@param[in] ref_interface Valid interface.
@return
MFX_ERR_NONE If no error. \n
MFX_ERR_NULL_PTR If interface is NULL. \n
MFX_ERR_INVALID_HANDLE If mfxRefInterface->Context is invalid (for example NULL). \n
MFX_ERR_UNDEFINED_BEHAVIOR If Reference Counter of container is zero before call. \n
MFX_ERR_UNKNOWN Any internal error.
*/
mfxStatus (MFX_CDECL *Release)(struct mfxRefInterface* ref_interface);
/*! @brief
Returns current reference counter of mfxRefInterface structure.
@param[in] ref_interface Valid interface.
@param[out] counter Sets counter to the current reference counter value.
@return
MFX_ERR_NONE If no error. \n
MFX_ERR_NULL_PTR If interface or counter is NULL. \n
MFX_ERR_INVALID_HANDLE If mfxRefInterface->Context is invalid (for example NULL). \n
MFX_ERR_UNKNOWN Any internal error.
*/
mfxStatus (MFX_CDECL *GetRefCounter)(struct mfxRefInterface* ref_interface, mfxU32* counter);
mfxHDL reserved[4];
}mfxRefInterface;
MFX_PACK_END()
/* Library initialization and deinitialization */
/*!
This enumerator itemizes implementation types.
The implementation type is a bit OR'ed value of the base type and any decorative flags.
@note This enumerator is for legacy dispatcher compatibility only. The new dispatcher does not use it.
*/
typedef mfxI32 mfxIMPL;
/*!
The application can use the macro MFX_IMPL_BASETYPE(x) to obtain the base implementation type.
*/
#define MFX_IMPL_BASETYPE(x) (0x00ff & (x))
enum {
MFX_IMPL_AUTO = 0x0000, /*!< Auto Selection/In or Not Supported/Out. */
MFX_IMPL_SOFTWARE = 0x0001, /*!< Pure software implementation. */
MFX_IMPL_HARDWARE = 0x0002, /*!< Hardware accelerated implementation (default device). */
MFX_IMPL_AUTO_ANY = 0x0003, /*!< Auto selection of any hardware/software implementation. */
MFX_IMPL_HARDWARE_ANY = 0x0004, /*!< Auto selection of any hardware implementation. */
MFX_IMPL_HARDWARE2 = 0x0005, /*!< Hardware accelerated implementation (2nd device). */
MFX_IMPL_HARDWARE3 = 0x0006, /*!< Hardware accelerated implementation (3rd device). */
MFX_IMPL_HARDWARE4 = 0x0007, /*!< Hardware accelerated implementation (4th device). */
MFX_IMPL_RUNTIME = 0x0008, /*!< This value cannot be used for session initialization. It may be returned by the MFXQueryIMPL
function to show that the session has been initialized in run-time mode. */
MFX_IMPL_VIA_ANY = 0x0100, /*!< Hardware acceleration can go through any supported OS infrastructure. This is the default value. The default value
is used by the legacy Intel(r) Media SDK if none of the MFX_IMPL_VIA_xxx flags are specified by the application. */
MFX_IMPL_VIA_D3D9 = 0x0200, /*!< Hardware acceleration goes through the Microsoft* Direct3D* 9 infrastructure. */
MFX_IMPL_VIA_D3D11 = 0x0300, /*!< Hardware acceleration goes through the Microsoft* Direct3D* 11 infrastructure. */
MFX_IMPL_VIA_VAAPI = 0x0400, /*!< Hardware acceleration goes through the Linux* VA-API infrastructure. */
MFX_IMPL_VIA_HDDLUNITE = 0x0500, /*!< Hardware acceleration goes through the HDDL* Unite*. */
MFX_IMPL_UNSUPPORTED = 0x0000 /*!< One of the MFXQueryIMPL returns. */
};
/* Version Info */
MFX_PACK_BEGIN_USUAL_STRUCT()
/*! The mfxVersion union describes the version of the implementation.*/
typedef union {
/*! @brief Structure with Major and Minor fields. */
/*! @struct Anonymous */
struct {
/*! @{
@name Major and Minor fields
Anonymous structure with Major and Minor fields.
*/
mfxU16 Minor; /*!< Minor number of the implementation. */
mfxU16 Major; /*!< Major number of the implementation. */
/*! @} */
};
mfxU32 Version; /*!< Implementation version number. */
} mfxVersion;
MFX_PACK_END()
/*! The mfxPriority enumerator describes the session priority. */
typedef enum
{
MFX_PRIORITY_LOW = 0, /*!< Low priority: the session operation halts when high priority tasks are executing and more than 75% of the CPU is being used for normal priority tasks.*/
MFX_PRIORITY_NORMAL = 1, /*!< Normal priority: the session operation is halted if there are high priority tasks.*/
MFX_PRIORITY_HIGH = 2 /*!< High priority: the session operation blocks other lower priority session operations.*/
} mfxPriority;
typedef struct _mfxEncryptedData mfxEncryptedData;
MFX_PACK_BEGIN_STRUCT_W_L_TYPE()
/*! Defines the buffer that holds compressed video data. */
typedef struct {
/*! @internal :unnamed(union) @endinternal */
union {
struct {
mfxEncryptedData* EncryptedData; /*!< Reserved and must be zero. */
mfxExtBuffer **ExtParam; /*!< Array of extended buffers for additional bitstream configuration. See the ExtendedBufferID enumerator for a complete list of extended buffers. */
mfxU16 NumExtParam; /*!< The number of extended buffers attached to this structure. */
mfxU16 reserved1; /*!< Reserved for future use. */
mfxU32 CodecId; /*!< Specifies the codec format identifier in the FourCC code. See the CodecFormatFourCC enumerator for details. This optional parameter is required for the simplified decode initialization. */
};
mfxU32 reserved[6];
};
/*! Decode time stamp of the compressed bitstream in units of 90KHz. A value of MFX_TIMESTAMP_UNKNOWN indicates that there is no time stamp.
This value is calculated by the encoder from the presentation time stamp provided by the application in the mfxFrameSurface1 structure and
from the frame rate provided by the application during the encoder initialization. */
mfxI64 DecodeTimeStamp;
mfxU64 TimeStamp; /*!< Time stamp of the compressed bitstream in units of 90KHz. A value of MFX_TIMESTAMP_UNKNOWN indicates that there is no time stamp. */
mfxU8* Data; /*!< Bitstream buffer pointer, 32-bytes aligned. */
mfxU32 DataOffset; /*!< Next reading or writing position in the bitstream buffer. */
mfxU32 DataLength; /*!< Size of the actual bitstream data in bytes. */
mfxU32 MaxLength; /*!< Allocated bitstream buffer size in bytes. */
mfxU16 PicStruct; /*!< Type of the picture in the bitstream. Output parameter. */
mfxU16 FrameType; /*!< Frame type of the picture in the bitstream. Output parameter. */
mfxU16 DataFlag; /*!< Indicates additional bitstream properties. See the BitstreamDataFlag enumerator for details. */
mfxU16 reserved2; /*!< Reserved for future use. */
} mfxBitstream;
MFX_PACK_END()
/*! Synchronization point object handle. */
typedef struct _mfxSyncPoint *mfxSyncPoint;
/*! The GPUCopy enumerator controls usage of GPU accelerated copying between video and system memory in the legacy Intel(r) Media SDK components. */
enum {
MFX_GPUCOPY_DEFAULT = 0, /*!< Use default mode for the legacy Intel(r) Media SDK implementation. */
MFX_GPUCOPY_ON = 1, /*!< The hint to enable GPU accelerated copying when it is supported by the library.
If the library doesn't support GPU accelerated copy the operation will be made by CPU.
Buffer caching usage decision is up to runtime to decide, for explicit hints please use MFX_GPUCOPY_SAFE or MFX_GPUCOPY_FAST */
MFX_GPUCOPY_OFF = 2, /*!< Disable GPU accelerated copying. */
MFX_GPUCOPY_SAFE = 3, /*!< The hint to disable buffer caching for GPU accelerated copying. Actual when GPU accelerated copying is supported by the library. */
#ifdef ONEVPL_EXPERIMENTAL
MFX_GPUCOPY_FAST = 4 /*!< The hint to enable buffer caching for GPU accelerated copying. Actual when GPU accelerated copying is supported by the library. */
#endif
};
MFX_PACK_BEGIN_STRUCT_W_PTR()
/*! Specifies advanced initialization parameters.
A zero value in any of the fields indicates that the corresponding field
is not explicitly specified.
*/
typedef struct {
mfxIMPL Implementation; /*!< Enumerator that indicates the desired legacy Intel(r) Media SDK implementation. */
mfxVersion Version; /*!< Structure which specifies minimum library version or zero, if not specified. */
mfxU16 ExternalThreads; /*!< Desired threading mode. Value 0 means internal threading, 1 - external. */
/*! @internal :unnamed(union) @endinternal */
union {
struct {
mfxExtBuffer **ExtParam; /*!< Points to an array of pointers to the extra configuration structures; see the ExtendedBufferID enumerator for a list of extended configurations. */
mfxU16 NumExtParam; /*!< The number of extra configuration structures attached to this structure. */
};
mfxU16 reserved2[5];
};
mfxU16 GPUCopy; /*!< Enables or disables GPU accelerated copying between video and system memory in legacy Intel(r) Media SDK components. See the GPUCopy enumerator for a list of valid values. */
mfxU16 reserved[21];
} mfxInitParam;
MFX_PACK_END()
enum {
MFX_EXTBUFF_THREADS_PARAM = MFX_MAKEFOURCC('T','H','D','P') /*!< mfxExtThreadsParam buffer ID. */
};
MFX_PACK_BEGIN_USUAL_STRUCT()
/*! Specifies options for threads created by this session. Attached to the
mfxInitParam structure during legacy Intel(r) Media SDK session initialization
or to mfxInitializationParam by the dispatcher in MFXCreateSession function. */
typedef struct {
mfxExtBuffer Header; /*!< Extension buffer header. Header.BufferId must be equal to MFX_EXTBUFF_THREADS_PARAM. */
mfxU16 NumThread; /*!< The number of threads. */
mfxI32 SchedulingType; /*!< Scheduling policy for all threads.*/
mfxI32 Priority; /*!< Priority for all threads. */
mfxU16 reserved[55]; /*!< Reserved for future use. */
} mfxExtThreadsParam;
MFX_PACK_END()
/*! Deprecated. */
enum {
MFX_DEPRECATED_ENUM_FIELD_INSIDE(MFX_PLATFORM_UNKNOWN) = 0, /*!< Unknown platform. */
MFX_DEPRECATED_ENUM_FIELD_INSIDE(MFX_PLATFORM_SANDYBRIDGE) = 1, /*!< Intel(r) microarchitecture code name Sandy Bridge. */
MFX_DEPRECATED_ENUM_FIELD_INSIDE(MFX_PLATFORM_IVYBRIDGE) = 2, /*!< Intel(r) microarchitecture code name Ivy Bridge. */
MFX_DEPRECATED_ENUM_FIELD_INSIDE(MFX_PLATFORM_HASWELL) = 3, /*!< Code name Haswell. */
MFX_DEPRECATED_ENUM_FIELD_INSIDE(MFX_PLATFORM_BAYTRAIL) = 4, /*!< Code name Bay Trail. */
MFX_DEPRECATED_ENUM_FIELD_INSIDE(MFX_PLATFORM_BROADWELL) = 5, /*!< Intel(r) microarchitecture code name Broadwell. */
MFX_DEPRECATED_ENUM_FIELD_INSIDE(MFX_PLATFORM_CHERRYTRAIL) = 6, /*!< Code name Cherry Trail. */
MFX_DEPRECATED_ENUM_FIELD_INSIDE(MFX_PLATFORM_SKYLAKE) = 7, /*!< Intel(r) microarchitecture code name Skylake. */
MFX_DEPRECATED_ENUM_FIELD_INSIDE(MFX_PLATFORM_APOLLOLAKE) = 8, /*!< Code name Apollo Lake. */
MFX_DEPRECATED_ENUM_FIELD_INSIDE(MFX_PLATFORM_KABYLAKE) = 9, /*!< Code name Kaby Lake. */
MFX_DEPRECATED_ENUM_FIELD_INSIDE(MFX_PLATFORM_GEMINILAKE) = 10, /*!< Code name Gemini Lake. */
MFX_DEPRECATED_ENUM_FIELD_INSIDE(MFX_PLATFORM_COFFEELAKE) = 11, /*!< Code name Coffee Lake. */
MFX_DEPRECATED_ENUM_FIELD_INSIDE(MFX_PLATFORM_CANNONLAKE) = 20, /*!< Code name Cannon Lake. */
MFX_DEPRECATED_ENUM_FIELD_INSIDE(MFX_PLATFORM_ICELAKE) = 30, /*!< Code name Ice Lake. */
MFX_DEPRECATED_ENUM_FIELD_INSIDE(MFX_PLATFORM_JASPERLAKE) = 32, /*!< Code name Jasper Lake. */
MFX_DEPRECATED_ENUM_FIELD_INSIDE(MFX_PLATFORM_ELKHARTLAKE) = 33, /*!< Code name Elkhart Lake. */
MFX_DEPRECATED_ENUM_FIELD_INSIDE(MFX_PLATFORM_TIGERLAKE) = 40, /*!< Code name Tiger Lake. */
MFX_DEPRECATED_ENUM_FIELD_INSIDE(MFX_PLATFORM_ROCKETLAKE) = 42, /*!< Code name Rocket Lake. */
MFX_DEPRECATED_ENUM_FIELD_INSIDE(MFX_PLATFORM_ALDERLAKE_S) = 43, /*!< Code name Alder Lake S. */
MFX_DEPRECATED_ENUM_FIELD_INSIDE(MFX_PLATFORM_ALDERLAKE_P) = 44, /*!< Code name Alder Lake P. */
MFX_DEPRECATED_ENUM_FIELD_INSIDE(MFX_PLATFORM_ARCTICSOUND_P) = 45,
MFX_DEPRECATED_ENUM_FIELD_INSIDE(MFX_PLATFORM_XEHP_SDV) = 45, /*!< Code name XeHP SDV. */
MFX_DEPRECATED_ENUM_FIELD_INSIDE(MFX_PLATFORM_DG2) = 46, /*!< Code name DG2. */
MFX_DEPRECATED_ENUM_FIELD_INSIDE(MFX_PLATFORM_ATS_M) = 46, /*!< Code name ATS-M, same media functionality as DG2. */
MFX_DEPRECATED_ENUM_FIELD_INSIDE(MFX_PLATFORM_ALDERLAKE_N) = 55, /*!< Code name Alder Lake N. */
MFX_DEPRECATED_ENUM_FIELD_INSIDE(MFX_PLATFORM_KEEMBAY) = 50, /*!< Code name Keem Bay. */
MFX_DEPRECATED_ENUM_FIELD_INSIDE(MFX_PLATFORM_METEORLAKE) = 51, /*!< Code name Meteor Lake. */
MFX_DEPRECATED_ENUM_FIELD_INSIDE(MFX_PLATFORM_BATTLEMAGE) = 52, /*!< Code name Battlemage. */
MFX_DEPRECATED_ENUM_FIELD_INSIDE(MFX_PLATFORM_LUNARLAKE) = 53, /*!< Code name Lunar Lake. */
MFX_DEPRECATED_ENUM_FIELD_INSIDE(MFX_PLATFORM_ARROWLAKE) = 54, /*!< Code name Arrow Lake. */
MFX_DEPRECATED_ENUM_FIELD_INSIDE(MFX_PLATFORM_MAXIMUM) = 65535, /*!< General code name. */
};
/*! The mfxMediaAdapterType enumerator itemizes types of graphics adapters. */
typedef enum
{
MFX_MEDIA_UNKNOWN = 0xffff, /*!< Unknown type. */
MFX_MEDIA_INTEGRATED = 0, /*!< Integrated graphics adapter. */
MFX_MEDIA_DISCRETE = 1 /*!< Discrete graphics adapter. */
} mfxMediaAdapterType;
MFX_PACK_BEGIN_USUAL_STRUCT()
/*! Contains information about hardware platform for the Legacy mode. */
typedef struct {
MFX_DEPRECATED mfxU16 CodeName; /*!< Deprecated. */
mfxU16 DeviceId; /*!< Unique identifier of graphics device. */
mfxU16 MediaAdapterType; /*!< Description of graphics adapter type. See the mfxMediaAdapterType enumerator for a list of possible values. */
mfxU16 reserved[13]; /*!< Reserved for future use. */
} mfxPlatform;
MFX_PACK_END()
/*! The mfxResourceType enumerator specifies types of different native data frames and buffers. */
typedef enum {
MFX_RESOURCE_SYSTEM_SURFACE = 1, /*!< System memory. */
MFX_RESOURCE_VA_SURFACE_PTR = 2, /*!< Pointer to VA surface index. */
MFX_RESOURCE_VA_SURFACE = MFX_RESOURCE_VA_SURFACE_PTR, /*!< Pointer to VA surface index. */
MFX_RESOURCE_VA_BUFFER_PTR = 3, /*!< Pointer to VA buffer index. */
MFX_RESOURCE_VA_BUFFER = MFX_RESOURCE_VA_BUFFER_PTR, /*!< Pointer to VA buffer index. */
MFX_RESOURCE_DX9_SURFACE = 4, /*!< Pointer to IDirect3DSurface9. */
MFX_RESOURCE_DX11_TEXTURE = 5, /*!< Pointer to ID3D11Texture2D. */
MFX_RESOURCE_DX12_RESOURCE = 6, /*!< Pointer to ID3D12Resource. */
MFX_RESOURCE_DMA_RESOURCE = 7, /*!< DMA resource. */
MFX_RESOURCE_HDDLUNITE_REMOTE_MEMORY = 8, /*!< HDDL Unite Remote memory handle. */
} mfxResourceType;
/*! Maximum allowed length of the implementation name. */
#define MFX_IMPL_NAME_LEN 32
/*! Maximum allowed length of the implementation name. */
#define MFX_STRFIELD_LEN 128
#ifdef ONEVPL_EXPERIMENTAL
#define MFX_DECEXTDESCRIPTION_VERSION MFX_STRUCT_VERSION(1, 0)
MFX_PACK_BEGIN_STRUCT_W_PTR()
/*! The mfxDecExtDescription structure represents the extended description of a decoder. */
typedef struct {
mfxStructVersion Version; /*!< Version of the structure. */
mfxU16 reserved[14]; /*!< Reserved for future use. */
mfxU16 NumExtBufferIDs; /*!< Number of supported extended buffer IDs. */
mfxU32* ExtBufferIDs; /*!< Pointer to the array of supported extended buffer IDs. */
} mfxDecExtDescription;
MFX_PACK_END()
#define MFX_DECMEMEXTDESCRIPTION_VERSION MFX_STRUCT_VERSION(1, 0)
MFX_PACK_BEGIN_STRUCT_W_PTR()
/*! The mfxDecMemExtDescription structure represents the extended description for decoder memory. */
typedef struct {
mfxStructVersion Version; /*!< Version of the structure. */
mfxU16 reserved[13]; /*!< Reserved for future use. */
mfxU16 MaxBitDepth; /*!< Maximum supported bit depth. */
mfxU16 NumChromaSubsamplings; /*!< Number of supported output chroma subsamplings. */
mfxU16* ChromaSubsamplings; /*!< Pointer to the array of supported output chroma subsamplings. */
} mfxDecMemExtDescription;
MFX_PACK_END()
#endif
#ifdef ONEVPL_EXPERIMENTAL
#define MFX_DECODERDESCRIPTION_VERSION MFX_STRUCT_VERSION(1, 1)
#else
#define MFX_DECODERDESCRIPTION_VERSION MFX_STRUCT_VERSION(1, 0)
#endif
MFX_PACK_BEGIN_STRUCT_W_PTR()
/*! The mfxDecoderDescription structure represents the description of a decoder. */
typedef struct {
mfxStructVersion Version; /*!< Version of the structure. */
mfxU16 reserved[7]; /*!< Reserved for future use. */
mfxU16 NumCodecs; /*!< Number of supported decoders. */
/*! This structure represents the decoder description. */
struct decoder {
mfxU32 CodecID; /*!< Decoder ID in FourCC format. */
#ifdef ONEVPL_EXPERIMENTAL
mfxU16 reserved[2]; /*!< Reserved for future use. */
union {
mfxDecExtDescription* DecExtDesc; /*!< Pointer to the extended descriptions of the decoder. */
mfxU16 reserved2[4]; /*!< Reserved for future use. */
};
mfxU16 reserved3[2]; /*!< Reserved for future use. */
#else
mfxU16 reserved[8]; /*!< Reserved for future use. */
#endif
mfxU16 MaxcodecLevel; /*!< Maximum supported codec level. See the CodecProfile enumerator for possible values. */
mfxU16 NumProfiles; /*!< Number of supported profiles. */
/*! This structure represents the codec profile description. */
struct decprofile {
mfxU32 Profile; /*!< Profile ID. See the CodecProfile enumerator for possible values.*/
mfxU16 reserved[7]; /*!< Reserved for future use. */
mfxU16 NumMemTypes; /*!< Number of supported memory types. */
/*! This structure represents the underlying details of the memory type. */
struct decmemdesc {
mfxResourceType MemHandleType; /*!< Memory handle type. */
mfxRange32U Width; /*!< Range of supported image widths. */
mfxRange32U Height; /*!< Range of supported image heights. */
#ifdef ONEVPL_EXPERIMENTAL
mfxU16 reserved[2]; /*!< Reserved for future use. */
union {
mfxDecMemExtDescription* MemExtDesc; /*!< Pointer to the extended descriptions for decoder memory. */
mfxU16 reserved2[4]; /*!< Reserved for future use. */
};
mfxU16 reserved3; /*!< Reserved for future use. */
#else
mfxU16 reserved[7]; /*!< Reserved for future use. */
#endif
mfxU16 NumColorFormats; /*!< Number of supported output color formats. */
mfxU32* ColorFormats; /*!< Pointer to the array of supported output color formats (in FOURCC). */
} * MemDesc; /*!< Pointer to the array of memory types. */
} * Profiles; /*!< Pointer to the array of profiles supported by the codec. */
} * Codecs; /*!< Pointer to the array of decoders. */
} mfxDecoderDescription;
MFX_PACK_END()
#ifdef ONEVPL_EXPERIMENTAL
#define MFX_ENCEXTDESCRIPTION_VERSION MFX_STRUCT_VERSION(1, 0)
MFX_PACK_BEGIN_STRUCT_W_PTR()
/*! The mfxEncExtDescription structure represents the extended description of an encoder. */
typedef struct {
mfxStructVersion Version; /*!< Version of the structure. */
mfxU16 reserved[10]; /*!< Reserved for future use. */
mfxU16 NumRateControlMethods; /*!< Number of supported bitrate control methods. */
mfxU16* RateControlMethods; /*!< Pointer to the array of supported bitrate control methods. */
mfxU16 reserved2[11]; /*!< Reserved for future use. */
mfxU16 NumExtBufferIDs; /*!< Number of supported extended buffer IDs. */
mfxU32* ExtBufferIDs; /*!< Pointer to the array of supported extended buffer IDs. */
} mfxEncExtDescription;
MFX_PACK_END()
#define MFX_ENCMEMEXTDESCRIPTION_VERSION MFX_STRUCT_VERSION(1, 0)
MFX_PACK_BEGIN_STRUCT_W_PTR()
/*! The mfxEncMemExtDescription structure represents the extended description for encoder memory. */
typedef struct {
mfxStructVersion Version; /*!< Version of the structure. */
mfxU16 reserved[13]; /*!< Reserved for future use. */
mfxU16 TargetMaxBitDepth; /*!< Maximum supported bit depth. */
mfxU16 NumTargetChromaSubsamplings; /*!< Number of supported target chroma subsamplings. */
mfxU16* TargetChromaSubsamplings; /*!< Pointer to the array of supported target chroma subsamplings. */
} mfxEncMemExtDescription;
MFX_PACK_END()
#endif
#ifdef ONEVPL_EXPERIMENTAL
#define MFX_ENCODERDESCRIPTION_VERSION MFX_STRUCT_VERSION(1, 1)
#else
#define MFX_ENCODERDESCRIPTION_VERSION MFX_STRUCT_VERSION(1, 0)
#endif
MFX_PACK_BEGIN_STRUCT_W_PTR()
/*! This structure represents an encoder description. */
typedef struct {
mfxStructVersion Version; /*!< Version of the structure. */
mfxU16 reserved[7]; /*!< Reserved for future use. */
mfxU16 NumCodecs; /*!< Number of supported encoders. */
/*! This structure represents encoder description. */
struct encoder {
mfxU32 CodecID; /*!< Encoder ID in FourCC format. */
mfxU16 MaxcodecLevel; /*!< Maximum supported codec level. See the CodecProfile enumerator for possible values. */
mfxU16 BiDirectionalPrediction; /*!< Indicates B-frames support. */
#ifdef ONEVPL_EXPERIMENTAL
union {
mfxEncExtDescription* EncExtDesc; /*!< Pointer to the extended descriptions of the encoder. */
mfxU16 reserved2[4]; /*!< Reserved for future use. */
};
mfxU16 reserved[3]; /*!< Reserved for future use. */
#else
mfxU16 reserved[7]; /*!< Reserved for future use. */
#endif
mfxU16 NumProfiles; /*!< Number of supported profiles. */
/*! This structure represents the codec profile description. */
struct encprofile {
mfxU32 Profile; /*!< Profile ID. See the CodecProfile enumerator for possible values.*/
mfxU16 reserved[7]; /*!< Reserved for future use. */
mfxU16 NumMemTypes; /*!< Number of supported memory types. */
/*! This structure represents the underlying details of the memory type. */
struct encmemdesc {
mfxResourceType MemHandleType; /*!< Memory handle type. */
mfxRange32U Width; /*!< Range of supported image widths. */
mfxRange32U Height; /*!< Range of supported image heights. */
#ifdef ONEVPL_EXPERIMENTAL
mfxU16 reserved[2]; /*!< Reserved for future use. */
union {
mfxEncMemExtDescription* MemExtDesc; /*!< Pointer to the extended descriptions for encoder memory. */
mfxU16 reserved2[4]; /*!< Reserved for future use. */
};
mfxU16 reserved3; /*!< Reserved for future use. */
#else
mfxU16 reserved[7]; /*!< Reserved for future use. */
#endif
mfxU16 NumColorFormats; /*!< Number of supported input color formats. */
mfxU32* ColorFormats; /*!< Pointer to the array of supported input color formats (in FOURCC). */
} * MemDesc; /*!< Pointer to the array of memory types. */
} * Profiles; /*!< Pointer to the array of profiles supported by the codec. */
} * Codecs; /*!< Pointer to the array of encoders. */
} mfxEncoderDescription;
MFX_PACK_END()
#define MFX_VPPDESCRIPTION_VERSION MFX_STRUCT_VERSION(1, 0)
MFX_PACK_BEGIN_STRUCT_W_PTR()
/*! This structure represents VPP description. */
typedef struct {
mfxStructVersion Version; /*!< Version of the structure. */
mfxU16 reserved[7]; /*!< Reserved for future use. */
mfxU16 NumFilters; /*!< Number of supported VPP filters. */
/*! This structure represents the VPP filters description. */
struct filter {
mfxU32 FilterFourCC; /*!< Filter ID in FourCC format. */
mfxU16 MaxDelayInFrames; /*!< Introduced output delay in frames. */
mfxU16 reserved[7]; /*!< Reserved for future use. */
mfxU16 NumMemTypes; /*!< Number of supported memory types. */
/*! This structure represents the underlying details of the memory type. */
struct memdesc {
mfxResourceType MemHandleType; /*!< Memory handle type. */
mfxRange32U Width; /*!< Range of supported image widths. */
mfxRange32U Height; /*!< Range of supported image heights. */
mfxU16 reserved[7]; /*!< Reserved for future use. */
mfxU16 NumInFormats; /*!< Number of supported input color formats. */
/*! This structure represents the input color format description. */
struct format {
mfxU32 InFormat; /*!< Input color in FourCC format. */
mfxU16 reserved[5]; /*!< Reserved for future use. */
mfxU16 NumOutFormat; /*!< Number of supported output color formats. */
mfxU32* OutFormats; /*!< Pointer to the array of supported output color formats (in FOURCC). */
} * Formats; /*!< Pointer to the array of supported formats. */
} * MemDesc; /*!< Pointer to the array of memory types. */
} * Filters; /*!< Pointer to the array of supported filters. */
} mfxVPPDescription;
MFX_PACK_END()
/*! The current version of mfxDeviceDescription structure. */
#define MFX_DEVICEDESCRIPTION_VERSION MFX_STRUCT_VERSION(1, 1)
MFX_PACK_BEGIN_STRUCT_W_PTR()
/*! This structure represents device description. */
typedef struct {
mfxStructVersion Version; /*!< Version of the structure. */
mfxU16 reserved[6]; /*!< reserved for future use. */
mfxU16 MediaAdapterType; /*!< Graphics adapter type. See the mfxMediaAdapterType enumerator for a list of possible values. */
mfxChar DeviceID[MFX_STRFIELD_LEN]; /*!< Null terminated string with device ID. */
mfxU16 NumSubDevices; /*!< Number of available uniform sub-devices. Pure software implementation can report 0. */
/*! This structure represents sub-device description. */
struct subdevices {
mfxU32 Index; /*!< Index of the sub-device, started from 0 and increased by 1.*/
mfxChar SubDeviceID[MFX_STRFIELD_LEN]; /*!< Null terminated string with unique sub-device ID, mapped to the system ID. */
mfxU32 reserved[7]; /*!< reserved for future use. */
} * SubDevices; /*!< Pointer to the array of available sub-devices. */
} mfxDeviceDescription;
MFX_PACK_END()
/*! This enum itemizes implementation type. */
typedef enum {
MFX_IMPL_TYPE_SOFTWARE = 0x0001, /*!< Pure Software Implementation. */
MFX_IMPL_TYPE_HARDWARE = 0x0002, /*!< Hardware Accelerated Implementation. */
} mfxImplType;
/*! This enum itemizes hardware acceleration stack to use. */
typedef enum {
MFX_ACCEL_MODE_NA = 0, /*!< Hardware acceleration is not applicable. */
MFX_ACCEL_MODE_VIA_D3D9 = 0x0200, /*!< Hardware acceleration goes through the Microsoft* Direct3D9* infrastructure. */
MFX_ACCEL_MODE_VIA_D3D11 = 0x0300, /*!< Hardware acceleration goes through the Microsoft* Direct3D11* infrastructure. */
MFX_ACCEL_MODE_VIA_VAAPI = 0x0400, /*!< Hardware acceleration goes through the Linux* VA-API infrastructure. */
MFX_ACCEL_MODE_VIA_VAAPI_DRM_RENDER_NODE
= MFX_ACCEL_MODE_VIA_VAAPI, /*!< Hardware acceleration goes through the Linux* VA-API infrastructure with DRM RENDER MODE as default acceleration access point. */
MFX_ACCEL_MODE_VIA_VAAPI_DRM_MODESET = 0x0401, /*!< Hardware acceleration goes through the Linux* VA-API infrastructure with DRM MODESET as default acceleration access point. */
MFX_ACCEL_MODE_VIA_VAAPI_GLX = 0x0402, /*!< Hardware acceleration goes through the Linux* VA-API infrastructure with OpenGL Extension to the X Window System
as default acceleration access point. */
MFX_ACCEL_MODE_VIA_VAAPI_X11 = 0x0403, /*!< Hardware acceleration goes through the Linux* VA-API infrastructure with X11 as default acceleration access point. */
MFX_ACCEL_MODE_VIA_VAAPI_WAYLAND = 0x0404, /*!< Hardware acceleration goes through the Linux* VA-API infrastructure with Wayland as default acceleration access point. */
MFX_ACCEL_MODE_VIA_HDDLUNITE = 0x0500, /*!< Hardware acceleration goes through the HDDL* Unite*. */
} mfxAccelerationMode;
#define MFX_ACCELERATIONMODESCRIPTION_VERSION MFX_STRUCT_VERSION(1, 0)
MFX_PACK_BEGIN_STRUCT_W_PTR()
/*! This structure represents acceleration modes description. */
typedef struct {
mfxStructVersion Version; /*!< Version of the structure. */
mfxU16 reserved[2]; /*!< reserved for future use. */
mfxU16 NumAccelerationModes; /*!< Number of supported acceleration modes. */
mfxAccelerationMode* Mode; /*!< Pointer to the array of supported acceleration modes. */
} mfxAccelerationModeDescription;
MFX_PACK_END()
/*! Specifies the surface pool allocation policies. */
typedef enum {
/*! Recommends to limit max pool size by sum of requested surfaces asked by components. */
MFX_ALLOCATION_OPTIMAL = 0,
/*! Dynamic allocation with no limit. */
MFX_ALLOCATION_UNLIMITED = 1,
/*! Max pool size is limited by NumberToPreAllocate + DeltaToAllocateOnTheFly. */
MFX_ALLOCATION_LIMITED = 2,
} mfxPoolAllocationPolicy;
/*! The current version of mfxPoolPolicyDescription structure. */
#define MFX_POOLPOLICYDESCRIPTION_VERSION MFX_STRUCT_VERSION(1, 0)
MFX_PACK_BEGIN_STRUCT_W_PTR()
/*! This structure represents pool policy description. */
typedef struct {
mfxStructVersion Version; /*!< Version of the structure. */
mfxU16 reserved[2]; /*!< reserved for future use. */
mfxU16 NumPoolPolicies; /*!< Number of supported pool policies. */
mfxPoolAllocationPolicy* Policy; /*!< Pointer to the array of supported pool policies. */
} mfxPoolPolicyDescription;
MFX_PACK_END()
/*! The current version of mfxImplDescription structure. */
#define MFX_IMPLDESCRIPTION_VERSION MFX_STRUCT_VERSION(1, 2)
MFX_PACK_BEGIN_STRUCT_W_PTR()
/*! This structure represents the implementation description. */
typedef struct {
mfxStructVersion Version; /*!< Version of the structure. */
mfxImplType Impl; /*!< Impl type: software/hardware. */
mfxAccelerationMode AccelerationMode; /*!< Default Hardware acceleration stack to use. OS dependent parameter. Use VA for Linux* and DX* for Windows*. */
mfxVersion ApiVersion; /*!< Supported API version. */
mfxChar ImplName[MFX_IMPL_NAME_LEN]; /*!< Null-terminated string with implementation name given by vendor. */
mfxChar License[MFX_STRFIELD_LEN]; /*!< Null-terminated string with comma-separated list of license names of the implementation. */
mfxChar Keywords[MFX_STRFIELD_LEN]; /*!< Null-terminated string with comma-separated list of keywords specific to this implementation that dispatcher can search for. */
mfxU32 VendorID; /*!< Standard vendor ID 0x8086 - Intel. */
mfxU32 VendorImplID; /*!< Vendor specific number with given implementation ID. */
mfxDeviceDescription Dev; /*!< Supported device. */
mfxDecoderDescription Dec; /*!< Decoder configuration. */
mfxEncoderDescription Enc; /*!< Encoder configuration. */
mfxVPPDescription VPP; /*!< VPP configuration. */
union
{
mfxAccelerationModeDescription AccelerationModeDescription; /*!< Supported acceleration modes. */
mfxU32 reserved3[4];
};
mfxPoolPolicyDescription PoolPolicies; /*!< Supported surface pool polices. */
mfxU32 reserved[8]; /*!< Reserved for future use. */
mfxU32 NumExtParam; /*!< Number of extension buffers. Reserved for future use. Must be 0. */
union {
mfxExtBuffer **ExtParam; /*!< Array of extension buffers. */
mfxU64 Reserved2; /*!< Reserved for future use. */
} ExtParams; /*!< Extension buffers. Reserved for future. */
} mfxImplDescription;
MFX_PACK_END()
MFX_PACK_BEGIN_STRUCT_W_PTR()
/*! This structure represents the list of names of implemented functions. */
typedef struct {
mfxU16 NumFunctions; /*!< Number of function names in the FunctionsName array. */
mfxChar** FunctionsName; /*!< Array of the null-terminated strings. Each string contains name of the implemented function. */
} mfxImplementedFunctions;
MFX_PACK_END()
#define MFX_EXTENDEDDEVICEID_VERSION MFX_STRUCT_VERSION(1, 0)
MFX_PACK_BEGIN_USUAL_STRUCT()
/*! Specifies various physical device properties for device matching and identification outside of oneAPI Video Processing Library (oneVPL). */
typedef struct {
mfxStructVersion Version; /*!< Version of the structure. */
mfxU16 VendorID; /*!< PCI vendor ID. */
mfxU16 DeviceID; /*!< PCI device ID. */
mfxU32 PCIDomain; /*!< PCI bus domain. Equals to '0' if OS doesn't support it or
has sequential numbering of buses across domains. */
mfxU32 PCIBus; /*!< The number of the bus that the physical device is located on. */
mfxU32 PCIDevice; /*!< The index of the physical device on the bus. */
mfxU32 PCIFunction; /*!< The function number of the device on the physical device. */
mfxU8 DeviceLUID[8]; /*!< LUID of DXGI adapter. */
mfxU32 LUIDDeviceNodeMask; /*!< Bitfield identifying the node within a linked
device adapter corresponding to the device. */
mfxU32 LUIDValid; /*!< Boolean value that will be 1 if DeviceLUID contains a valid LUID
and LUIDDeviceNodeMask contains a valid node mask,
and 0 if they do not. */
mfxU32 DRMRenderNodeNum; /*!< Number of the DRM render node from the path /dev/dri/RenderD\<num\>.
Value equals to 0 means that this field doesn't contain valid DRM Render
Node number.*/
mfxU32 DRMPrimaryNodeNum; /*!< Number of the DRM primary node from the path /dev/dri/card\<num\>.
Value equals to 0x7FFFFFFF means that this field doesn't contain valid DRM Primary
Node number.*/
mfxU16 RevisionID; /*!< PCI revision ID. The value contains microarchitecture version. */
mfxU8 reserved1[18]; /*!< Reserved for future use. */
mfxChar DeviceName[MFX_STRFIELD_LEN]; /*!< Null-terminated string in utf-8 with the name of the device. */
} mfxExtendedDeviceId;
MFX_PACK_END()
MFX_PACK_BEGIN_USUAL_STRUCT()
/*! Cross domain structure to define device UUID. It is defined here to check backward compatibility.*/
typedef struct {
mfxU16 vendor_id; /*!< PCI vendor ID. Same as mfxExtendedDeviceId::VendorID. */
mfxU16 device_id; /*!< PCI device ID. Same as mfxExtendedDeviceId::DeviceID. */
mfxU16 revision_id; /*!< PCI revision ID. Same as mfxExtendedDeviceId::RevisionID. */
mfxU16 pci_domain; /*!< PCI bus domain. Same as mfxExtendedDeviceId::PCIDomain. */
mfxU8 pci_bus; /*!< The number of the bus that the physical device is located on. Same as mfxExtendedDeviceId::PCIBus. */
mfxU8 pci_dev; /*!< The index of the physical device on the bus. Same as mfxExtendedDeviceId::PCIDevice. */
mfxU8 pci_func; /*!< The function number of the device on the physical device. Same as mfxExtendedDeviceId::PCIFunction. */
mfxU8 reserved[4]; /*!< Reserved for future use. */
mfxU8 sub_device_id; /*!< SubDevice ID.*/
} extDeviceUUID;
MFX_PACK_END()
/*! The mfxImplCapsDeliveryFormat enumerator specifies delivery format of the implementation capability. */
typedef enum {
MFX_IMPLCAPS_IMPLDESCSTRUCTURE = 1, /*!< Deliver capabilities as mfxImplDescription structure. */
MFX_IMPLCAPS_IMPLEMENTEDFUNCTIONS = 2, /*!< Deliver capabilities as mfxImplementedFunctions structure. */
MFX_IMPLCAPS_IMPLPATH = 3, /*!< Deliver pointer to the null-terminated string with the path to the
implementation. String is delivered in a form of buffer of
mfxChar type. */
MFX_IMPLCAPS_DEVICE_ID_EXTENDED = 4, /*!< Deliver extended device ID information as mfxExtendedDeviceId
structure.*/
#ifdef ONEVPL_EXPERIMENTAL
MFX_IMPLCAPS_SURFACE_TYPES = 5, /*!< Deliver capabilities as mfxSurfaceTypesSupported structure. */
#endif
} mfxImplCapsDeliveryFormat;
MFX_PACK_BEGIN_STRUCT_W_PTR()
/*! Specifies initialization parameters for API version starting from 2.0.
*/
typedef struct {
mfxAccelerationMode AccelerationMode; /*!< Hardware acceleration stack to use. OS dependent parameter. Use VA for Linux*, DX* for Windows* or HDDL. */
#ifdef ONEVPL_EXPERIMENTAL
mfxU16 DeviceCopy; /*!< Enables or disables device's accelerated copying between device and
host. See the GPUCopy enumerator for a list of valid values.
This parameter is the equivalent of mfxInitParam::GPUCopy. */
mfxU16 reserved[2]; /*!< Reserved for future use. */
#else
mfxU16 reserved[3]; /*!< Reserved for future use. */
#endif
mfxU16 NumExtParam; /*!< The number of extra configuration structures attached to this
structure. */
mfxExtBuffer **ExtParam; /*!< Points to an array of pointers to the extra configuration structures;
see the ExtendedBufferID enumerator for a list of extended
configurations. */
mfxU32 VendorImplID; /*!< Vendor specific number with given implementation ID. Represents
the same field from mfxImplDescription. */
mfxU32 reserved2[3]; /*!< Reserved for future use. */
} mfxInitializationParam;
MFX_PACK_END()
#ifdef ONEVPL_EXPERIMENTAL
MFX_PACK_BEGIN_STRUCT_W_PTR()
/*! Represents a name/value pair to indicate requested properties. For use with MFXQueryImplsProperties() */
typedef struct {
mfxU8* PropName; /*!< Property name string to indicate the requested Property. */
mfxVariant PropVar; /*!< Property value corresponding to the property name. */
} mfxQueryProperty;
MFX_PACK_END()
#endif
#ifdef __cplusplus
}
#endif /* __cplusplus */
#endif
+349
View File
@@ -0,0 +1,349 @@
/*############################################################################
# Copyright Intel Corporation
#
# SPDX-License-Identifier: MIT
############################################################################*/
#ifndef __MFXDEFS_H__
#define __MFXDEFS_H__
#define MFX_VERSION_MAJOR 2
#define MFX_VERSION_MINOR 17
// MFX_VERSION - version of API that 'assumed' by build may be provided externally
// if it omitted then latest stable API derived from Major.Minor is assumed
#if !defined(MFX_VERSION)
#define MFX_VERSION (MFX_VERSION_MAJOR * 1000 + MFX_VERSION_MINOR)
#else
#undef MFX_VERSION_MAJOR
#define MFX_VERSION_MAJOR ((MFX_VERSION) / 1000)
#undef MFX_VERSION_MINOR
#define MFX_VERSION_MINOR ((MFX_VERSION) % 1000)
#endif
/*! The corresponding version of the Intel(r) Media SDK legacy API that is used as a basis
for the current API. */
#define MFX_LEGACY_VERSION 1035
#ifdef __cplusplus
extern "C"
{
#endif /* __cplusplus */
/* In preprocessor syntax # symbol has stringize meaning,
so to expand some macro to preprocessor pragma we need to use
special compiler dependent construction */
#if defined(_MSC_VER)
#define MFX_PRAGMA_IMPL(x) __pragma(x)
#else
#define MFX_PRAGMA_IMPL(x) _Pragma(#x)
#endif
#define MFX_PACK_BEGIN_X(x) MFX_PRAGMA_IMPL(pack(push, x))
#define MFX_PACK_END() MFX_PRAGMA_IMPL(pack(pop))
/* The general rule for alignment is following:
- structures with pointers have 4/8 bytes alignment on 32/64 bit systems
- structures with fields of type mfxU64/mfxF64 (unsigned long long / double)
have alignment 8 bytes on 64 bit and 32 bit Windows, on Linux alignment is 4 bytes
- all the rest structures are 4 bytes aligned
- there are several exceptions: some structs which had 4-byte alignment were extended
with pointer / long type fields; such structs have 4-byte alignment to keep binary
compatibility with previously release API */
#define MFX_PACK_BEGIN_USUAL_STRUCT() MFX_PACK_BEGIN_X(4)
/* 64-bit LP64 data model */
#if defined(_WIN64) || defined(__LP64__)
#define MFX_PACK_BEGIN_STRUCT_W_PTR() MFX_PACK_BEGIN_X(8)
#define MFX_PACK_BEGIN_STRUCT_W_L_TYPE() MFX_PACK_BEGIN_X(8)
/* 32-bit ILP32 data model Windows* (Intel(r) architecture) */
#elif defined(_WIN32) || defined(_M_IX86) && !defined(__linux__)
#define MFX_PACK_BEGIN_STRUCT_W_PTR() MFX_PACK_BEGIN_X(4)
#define MFX_PACK_BEGIN_STRUCT_W_L_TYPE() MFX_PACK_BEGIN_X(8)
/* 32-bit ILP32 data model Linux* */
#elif defined(__ILP32__) || defined(__arm__)
#define MFX_PACK_BEGIN_STRUCT_W_PTR() MFX_PACK_BEGIN_X(4)
#define MFX_PACK_BEGIN_STRUCT_W_L_TYPE() MFX_PACK_BEGIN_X(4)
#else
#error Unknown packing
#endif
#ifdef _WIN32
#define MFX_CDECL __cdecl
#define MFX_STDCALL __stdcall
#else
#define MFX_CDECL
#define MFX_STDCALL
#endif /* _WIN32 */
#define MFX_INFINITE 0xFFFFFFFF
#ifndef MFX_DEPRECATED_OFF
#if defined(__cplusplus) && __cplusplus >= 201402L
#define MFX_DEPRECATED [[deprecated]]
#define MFX_DEPRECATED_ENUM_FIELD_INSIDE(arg) arg [[deprecated]]
#define MFX_DEPRECATED_ENUM_FIELD_OUTSIDE(arg)
#elif defined(__clang__)
#define MFX_DEPRECATED __attribute__((deprecated))
#define MFX_DEPRECATED_ENUM_FIELD_INSIDE(arg) arg __attribute__((deprecated))
#define MFX_DEPRECATED_ENUM_FIELD_OUTSIDE(arg)
#elif defined(__INTEL_COMPILER)
#if (defined(_WIN32) || defined(_WIN64))
#define MFX_DEPRECATED __declspec(deprecated)
#define MFX_DEPRECATED_ENUM_FIELD_INSIDE(arg) arg
#define MFX_DEPRECATED_ENUM_FIELD_OUTSIDE(arg) __pragma(deprecated(arg))
#elif defined(__linux__)
#define MFX_DEPRECATED __attribute__((deprecated))
#if defined(__cplusplus)
#define MFX_DEPRECATED_ENUM_FIELD_INSIDE(arg) arg __attribute__((deprecated))
#else
#define MFX_DEPRECATED_ENUM_FIELD_INSIDE(arg) arg
#endif
#define MFX_DEPRECATED_ENUM_FIELD_OUTSIDE(arg)
#endif
#elif defined(_MSC_VER) && _MSC_VER > 1200 // VS 6 doesn't support deprecation
#define MFX_DEPRECATED __declspec(deprecated)
#define MFX_DEPRECATED_ENUM_FIELD_INSIDE(arg) arg
#define MFX_DEPRECATED_ENUM_FIELD_OUTSIDE(arg) __pragma(deprecated(arg))
#elif defined(__GNUC__)
#define MFX_DEPRECATED __attribute__((deprecated))
#define MFX_DEPRECATED_ENUM_FIELD_INSIDE(arg) arg __attribute__((deprecated))
#define MFX_DEPRECATED_ENUM_FIELD_OUTSIDE(arg)
#else
#define MFX_DEPRECATED
#define MFX_DEPRECATED_ENUM_FIELD_INSIDE(arg) arg
#define MFX_DEPRECATED_ENUM_FIELD_OUTSIDE(arg)
#endif
#else
#define MFX_DEPRECATED
#define MFX_DEPRECATED_ENUM_FIELD_INSIDE(arg) arg
#define MFX_DEPRECATED_ENUM_FIELD_OUTSIDE(arg)
#endif
typedef unsigned char mfxU8; /*!< Unsigned integer, 8 bit type. */
typedef char mfxI8; /*!< Signed integer, 8 bit type. */
typedef short mfxI16; /*!< Signed integer, 16 bit type. */
typedef unsigned short mfxU16; /*!< Unsigned integer, 16 bit type. */
typedef unsigned int mfxU32; /*!< Unsigned integer, 32 bit type. */
typedef int mfxI32; /*!< Signed integer, 32 bit type. */
#if defined( _WIN32 ) || defined ( _WIN64 )
typedef unsigned long mfxUL32; /*!< Unsigned integer, 32 bit type. */
typedef long mfxL32; /*!< Signed integer, 32 bit type. */
#else
typedef unsigned int mfxUL32; /*!< Unsigned integer, 32 bit type. */
typedef int mfxL32; /*!< Signed integer, 32 bit type. */
#endif
typedef float mfxF32; /*!< Single-precision floating point, 32 bit type. */
typedef double mfxF64; /*!< Double-precision floating point, 64 bit type. */
typedef unsigned long long mfxU64; /*!< Unsigned integer, 64 bit type. */
typedef long long mfxI64; /*!< Signed integer, 64 bit type. */
typedef void* mfxHDL; /*!< Handle type. */
typedef mfxHDL mfxMemId; /*!< Memory ID type. */
typedef void* mfxThreadTask; /*!< Thread task type. */
typedef char mfxChar; /*!< UTF-8 byte. */
typedef unsigned short mfxFP16; /*!< Half precision floating point, 16 bit type. */
/* MFX structures version info */
MFX_PACK_BEGIN_USUAL_STRUCT()
/*! Introduce the field Version for any structure.
Assumed that any structure changes are backward binary compatible.
mfxStructVersion starts from {1,0} for any new API structures. If mfxStructVersion is
added to the existent legacy structure (replacing reserved fields) it starts from {1, 1}.
*/
typedef union {
/*! Structure with Major and Minor fields. */
/*! @struct Anonymous */
struct {
/*! @{
@name Major and Minor fields
Anonymous structure with Major and Minor fields. Minor number is incremented when reserved fields are used. Major number is incremented when the size of structure is increased. */
mfxU8 Minor; /*!< Minor number of the correspondent structure. */
mfxU8 Major; /*!< Major number of the correspondent structure. */
/*! @} */
};
mfxU16 Version; /*!< Structure version number. */
} mfxStructVersion;
MFX_PACK_END()
#define MFX_STRUCT_VERSION(MAJOR, MINOR) (256*(MAJOR) + (MINOR))
#define MFX_VARIANT_VERSION MFX_STRUCT_VERSION(1, 1)
/*! The mfxDataType enumerates data type for mfxDataType. */
typedef enum {
MFX_DATA_TYPE_UNSET = 0, /*!< Undefined type. */
MFX_DATA_TYPE_U8, /*!< 8-bit unsigned integer. */
MFX_DATA_TYPE_I8, /*!< 8-bit signed integer. */
MFX_DATA_TYPE_U16, /*!< 16-bit unsigned integer. */
MFX_DATA_TYPE_I16, /*!< 16-bit signed integer. */
MFX_DATA_TYPE_U32, /*!< 32-bit unsigned integer. */
MFX_DATA_TYPE_I32, /*!< 32-bit signed integer. */
MFX_DATA_TYPE_U64, /*!< 64-bit unsigned integer. */
MFX_DATA_TYPE_I64, /*!< 64-bit signed integer. */
MFX_DATA_TYPE_F32, /*!< 32-bit single precision floating point. */
MFX_DATA_TYPE_F64, /*!< 64-bit double precision floating point. */
MFX_DATA_TYPE_PTR, /*!< Generic type pointer. */
MFX_DATA_TYPE_FP16, /*!< 16-bit half precision floating point. */
}mfxDataType;
/*! The mfxVariantType enumerator data types for mfxVariantType. */
typedef enum {
MFX_VARIANT_TYPE_UNSET = MFX_DATA_TYPE_UNSET, /*!< Undefined type. */
MFX_VARIANT_TYPE_U8 = MFX_DATA_TYPE_U8, /*!< 8-bit unsigned integer. */
MFX_VARIANT_TYPE_I8 = MFX_DATA_TYPE_I8, /*!< 8-bit signed integer. */
MFX_VARIANT_TYPE_U16 = MFX_DATA_TYPE_U16, /*!< 16-bit unsigned integer. */
MFX_VARIANT_TYPE_I16 = MFX_DATA_TYPE_I16, /*!< 16-bit signed integer. */
MFX_VARIANT_TYPE_U32 = MFX_DATA_TYPE_U32, /*!< 32-bit unsigned integer. */
MFX_VARIANT_TYPE_I32 = MFX_DATA_TYPE_I32, /*!< 32-bit signed integer. */
MFX_VARIANT_TYPE_U64 = MFX_DATA_TYPE_U64, /*!< 64-bit unsigned integer. */
MFX_VARIANT_TYPE_I64 = MFX_DATA_TYPE_I64, /*!< 64-bit signed integer. */
MFX_VARIANT_TYPE_F32 = MFX_DATA_TYPE_F32, /*!< 32-bit single precision floating point. */
MFX_VARIANT_TYPE_F64 = MFX_DATA_TYPE_F64, /*!< 64-bit double precision floating point. */
MFX_VARIANT_TYPE_PTR = MFX_DATA_TYPE_PTR, /*!< Generic type pointer. */
MFX_VARIANT_TYPE_FP16 = MFX_DATA_TYPE_FP16, /*!< 16-bit half precision floating point. */
#ifdef ONEVPL_EXPERIMENTAL
MFX_VARIANT_TYPE_QUERY = 0x00000100, /*!< Bitmask to OR with other variant types when using property-based query API */
#endif
} mfxVariantType;
MFX_PACK_BEGIN_STRUCT_W_PTR()
/*! The mfxVariantType enumerator data types for mfxVariant type. */
typedef struct {
mfxStructVersion Version; /*!< Version of the structure. */
mfxVariantType Type; /*!< Value type. */
/*! Value data holder. */
union data {
mfxU8 U8; /*!< mfxU8 data. */
mfxI8 I8; /*!< mfxI8 data. */
mfxU16 U16; /*!< mfxU16 data. */
mfxI16 I16; /*!< mfxI16 data. */
mfxU32 U32; /*!< mfxU32 data. */
mfxI32 I32; /*!< mfxI32 data. */
mfxU64 U64; /*!< mfxU64 data. */
mfxI64 I64; /*!< mfxI64 data. */
mfxF32 F32; /*!< mfxF32 data. */
mfxF64 F64; /*!< mfxF64 data. */
mfxFP16 FP16; /*!< mfxFP16 data. */
mfxHDL Ptr; /*!< Pointer. When this points to a string the string must be null terminated. */
} Data; /*!< Value data member. */
} mfxVariant;
MFX_PACK_END()
MFX_PACK_BEGIN_USUAL_STRUCT()
/*! Represents a range of unsigned values. */
typedef struct {
mfxU32 Min; /*!< Minimal value of the range. */
mfxU32 Max; /*!< Maximal value of the range. */
mfxU32 Step; /*!< Value increment. */
} mfxRange32U;
MFX_PACK_END()
MFX_PACK_BEGIN_USUAL_STRUCT()
/*! Represents a pair of numbers of type mfxI16. */
typedef struct {
mfxI16 x; /*!< First number. */
mfxI16 y; /*!< Second number. */
} mfxI16Pair;
MFX_PACK_END()
MFX_PACK_BEGIN_STRUCT_W_PTR()
/*! Represents pair of handles of type mfxHDL. */
typedef struct {
mfxHDL first; /*!< First handle. */
mfxHDL second; /*!< Second handle. */
} mfxHDLPair;
MFX_PACK_END()
/*********************************************************************************\
Error message
\*********************************************************************************/
/*! @enum mfxStatus Itemizes status codes returned by API functions. */
typedef enum
{
/* no error */
MFX_ERR_NONE = 0, /*!< No error. */
/* reserved for unexpected errors */
MFX_ERR_UNKNOWN = -1, /*!< Unknown error. */
/* error codes <0 */
MFX_ERR_NULL_PTR = -2, /*!< Null pointer. */
MFX_ERR_UNSUPPORTED = -3, /*!< Unsupported feature. */
MFX_ERR_MEMORY_ALLOC = -4, /*!< Failed to allocate memory. */
MFX_ERR_NOT_ENOUGH_BUFFER = -5, /*!< Insufficient buffer at input/output. */
MFX_ERR_INVALID_HANDLE = -6, /*!< Invalid handle. */
MFX_ERR_LOCK_MEMORY = -7, /*!< Failed to lock the memory block. */
MFX_ERR_NOT_INITIALIZED = -8, /*!< Member function called before initialization. */
MFX_ERR_NOT_FOUND = -9, /*!< The specified object is not found. */
MFX_ERR_MORE_DATA = -10, /*!< Expect more data at input. */
MFX_ERR_MORE_SURFACE = -11, /*!< Expect more surface at output. */
MFX_ERR_ABORTED = -12, /*!< Operation aborted. */
MFX_ERR_DEVICE_LOST = -13, /*!< Lose the hardware acceleration device. */
MFX_ERR_INCOMPATIBLE_VIDEO_PARAM = -14, /*!< Incompatible video parameters. */
MFX_ERR_INVALID_VIDEO_PARAM = -15, /*!< Invalid video parameters. */
MFX_ERR_UNDEFINED_BEHAVIOR = -16, /*!< Undefined behavior. */
MFX_ERR_DEVICE_FAILED = -17, /*!< Device operation failure. */
MFX_ERR_MORE_BITSTREAM = -18, /*!< Expect more bitstream buffers at output. */
MFX_ERR_GPU_HANG = -21, /*!< Device operation failure caused by GPU hang. */
MFX_ERR_REALLOC_SURFACE = -22, /*!< Bigger output surface required. */
MFX_ERR_RESOURCE_MAPPED = -23, /*!< Write access is already acquired and user requested
another write access, or read access with MFX_MEMORY_NO_WAIT flag. */
MFX_ERR_NOT_IMPLEMENTED = -24, /*!< Feature or function not implemented. */
MFX_ERR_MORE_EXTBUFFER = -25, /*!< Expect additional extended configuration buffer. */
/* warnings >0 */
MFX_WRN_IN_EXECUTION = 1, /*!< The previous asynchronous operation is in execution. */
MFX_WRN_DEVICE_BUSY = 2, /*!< The hardware acceleration device is busy. */
MFX_WRN_VIDEO_PARAM_CHANGED = 3, /*!< The video parameters are changed during decoding. */
MFX_WRN_PARTIAL_ACCELERATION = 4, /*!< Software acceleration is used. */
MFX_WRN_INCOMPATIBLE_VIDEO_PARAM = 5, /*!< Incompatible video parameters. */
MFX_WRN_VALUE_NOT_CHANGED = 6, /*!< The value is saturated based on its valid range. */
MFX_WRN_OUT_OF_RANGE = 7, /*!< The value is out of valid range. */
MFX_WRN_FILTER_SKIPPED = 10, /*!< One of requested filters has been skipped. */
/* low-delay partial output */
MFX_ERR_NONE_PARTIAL_OUTPUT = 12, /*!< Frame is not ready, but bitstream contains partial output. */
MFX_WRN_ALLOC_TIMEOUT_EXPIRED = 13, /*!< Timeout expired for internal frame allocation. */
/* threading statuses */
MFX_TASK_DONE = MFX_ERR_NONE, /*!< Task has been completed. */
MFX_TASK_WORKING = 8, /*!< There is some more work to do. */
MFX_TASK_BUSY = 9, /*!< Task is waiting for resources. */
/* plug-in statuses */
MFX_ERR_MORE_DATA_SUBMIT_TASK = -10000, /*!< Return MFX_ERR_MORE_DATA but submit internal asynchronous task. */
} mfxStatus;
MFX_PACK_BEGIN_USUAL_STRUCT()
/*! Represents Globally Unique Identifier (GUID) with memory layout
compliant to RFC 4122. See https://www.rfc-editor.org/info/rfc4122 for details. */
typedef struct
{
mfxU8 Data[16]; /*!< Array to keep GUID. */
} mfxGUID;
MFX_PACK_END()
// Application
#if defined(MFX_DISPATCHER_EXPOSED_PREFIX)
#include "mfxdispatcherprefixedfunctions.h"
#endif // MFX_DISPATCHER_EXPOSED_PREFIX
#ifdef __cplusplus
}
#endif /* __cplusplus */
#endif /* __MFXDEFS_H__ */
+271
View File
@@ -0,0 +1,271 @@
/*############################################################################
# Copyright Intel Corporation
#
# SPDX-License-Identifier: MIT
############################################################################*/
#ifndef __MFXDISPATCHER_H__
#define __MFXDISPATCHER_H__
#include "mfxdefs.h"
#include "mfxcommon.h"
#include "mfxsession.h"
#ifdef __cplusplus
extern "C" {
#endif
/*! Loader handle. */
typedef struct _mfxLoader *mfxLoader;
/*! Config handle. */
typedef struct _mfxConfig *mfxConfig;
/*!
@brief Creates the loader.
@return Loader Loader handle or NULL if failed.
@since This function is available since API version 2.0.
*/
mfxLoader MFX_CDECL MFXLoad(void);
/*!
@brief Destroys the dispatcher.
@param[in] loader Loader handle.
@since This function is available since API version 2.0.
*/
void MFX_CDECL MFXUnload(mfxLoader loader);
/*!
@brief Creates dispatcher configuration.
@details Creates the dispatcher internal configuration, which is used to filter out available implementations.
This configuration is used to walk through selected implementations to gather more details and select the appropriate
implementation to load. The loader object remembers all created mfxConfig objects and destroys them during the mfxUnload
function call.
Multiple configurations per single mfxLoader object are possible.
Usage example:
@code
mfxLoader loader = MFXLoad();
mfxConfig cfg = MFXCreateConfig(loader);
MFXCreateSession(loader,0,&session);
@endcode
@param[in] loader Loader handle.
@return Config handle or NULL pointer is failed.
@since This function is available since API version 2.0.
*/
mfxConfig MFX_CDECL MFXCreateConfig(mfxLoader loader);
/*!
@brief Adds additional filter properties (any fields of the mfxImplDescription structure) to the configuration of the loader object.
@note Each new call with the same parameter name will overwrite the previously set value. This may invalidate other properties.
@param[in] config Config handle.
@param[in] name Name of the parameter (see mfxImplDescription structure and example).
@param[in] value Value of the parameter.
@return
MFX_ERR_NONE The function completed successfully.
MFX_ERR_NULL_PTR If config is NULL. \n
MFX_ERR_NULL_PTR If name is NULL. \n
MFX_ERR_NOT_FOUND If name contains unknown parameter name.
MFX_ERR_UNSUPPORTED If value data type does not equal the parameter with provided name.
@since This function is available since API version 2.0.
*/
mfxStatus MFX_CDECL MFXSetConfigFilterProperty(mfxConfig config, const mfxU8* name, mfxVariant value);
/*!
@brief Iterates over filtered out implementations to gather their details. This function allocates memory to store
a structure or string corresponding to the type specified by format. For example, if format is set to
MFX_IMPLCAPS_IMPLDESCSTRUCTURE, then idesc will return a pointer to a structure of type mfxImplDescription.
Use the MFXDispReleaseImplDescription function to free memory allocated to this structure or string.
@param[in] loader Loader handle.
@param[in] i Index of the implementation.
@param[in] format Format in which capabilities need to be delivered. See the mfxImplCapsDeliveryFormat enumerator for more details.
@param[out] idesc Pointer to the structure or string corresponding to the requested format.
@return
MFX_ERR_NONE The function completed successfully. The idesc contains valid information.\n
MFX_ERR_NULL_PTR If loader is NULL. \n
MFX_ERR_NULL_PTR If idesc is NULL. \n
MFX_ERR_NOT_FOUND Provided index is out of possible range. \n
MFX_ERR_UNSUPPORTED If requested format is not supported.
@since This function is available since API version 2.0.
*/
mfxStatus MFX_CDECL MFXEnumImplementations(mfxLoader loader, mfxU32 i, mfxImplCapsDeliveryFormat format, mfxHDL* idesc);
/*!
@brief Loads and initializes the implementation.
@code
mfxLoader loader = MFXLoad();
int i=0;
while(1) {
mfxImplDescription *idesc;
MFXEnumImplementations(loader, i, MFX_IMPLCAPS_IMPLDESCSTRUCTURE, (mfxHDL*)&idesc);
if(is_good(idesc)) {
MFXCreateSession(loader, i,&session);
// ...
MFXDispReleaseImplDescription(loader, idesc);
}
else
{
MFXDispReleaseImplDescription(loader, idesc);
break;
}
}
@endcode
@param[in] loader Loader handle.
@param[in] i Index of the implementation.
@param[out] session Pointer to the session handle.
@return
MFX_ERR_NONE The function completed successfully. The session contains a pointer to the session handle.\n
MFX_ERR_NULL_PTR If loader is NULL. \n
MFX_ERR_NULL_PTR If session is NULL. \n
MFX_ERR_NOT_FOUND Provided index is out of possible range.
@since This function is available since API version 2.0.
*/
mfxStatus MFX_CDECL MFXCreateSession(mfxLoader loader, mfxU32 i, mfxSession* session);
/*!
@brief
Destroys handle allocated by the MFXEnumImplementations function.
@param[in] loader Loader handle.
@param[in] hdl Handle to destroy. Can be equal to NULL.
@return
MFX_ERR_NONE The function completed successfully. \n
MFX_ERR_NULL_PTR If loader is NULL. \n
MFX_ERR_INVALID_HANDLE Provided hdl handle is not associated with this loader.
@since This function is available since API version 2.0.
*/
mfxStatus MFX_CDECL MFXDispReleaseImplDescription(mfxLoader loader, mfxHDL hdl);
/*!
@brief
Macro help to return UUID in the common oneAPI format.
@param[in] devinfo Handle to mfxExtendedDeviceId.
@param[in] sub_dev_num SubDevice number. Can be obtained from mfxDeviceDescription::SubDevices::Index. Set to zero if no SubDevices.
@param[out] uuid Pointer to UUID.
*/
#define MFX_UUID_COMPUTE_DEVICE_ID(devinfo, sub_dev_num, uuid) \
{ \
extDeviceUUID t_uuid = { 0 }; \
extDeviceUUID* shared_uuid = (extDeviceUUID*)(uuid); \
t_uuid.vendor_id = (devinfo)->VendorID; \
t_uuid.device_id = (devinfo)->DeviceID; \
t_uuid.revision_id = (devinfo)->RevisionID; \
t_uuid.pci_domain = (devinfo)->PCIDomain; \
t_uuid.pci_bus = (mfxU8)(devinfo)->PCIBus; \
t_uuid.pci_dev = (mfxU8)(devinfo)->PCIDevice; \
t_uuid.pci_func = (mfxU8)(devinfo)->PCIFunction; \
t_uuid.sub_device_id = (mfxU8)(sub_dev_num); \
*shared_uuid = t_uuid; \
}
/* Helper macro definitions to add config filter properties. */
/*! Adds single property of mfxU32 type.
@param[in] loader Valid mfxLoader object
@param[in] name Property name string
@param[in] value Property value
*/
#define MFX_ADD_PROPERTY_U32(loader, name, value) \
{ \
mfxVariant impl_value; \
mfxConfig cfg = MFXCreateConfig(loader); \
impl_value.Version.Version = MFX_VARIANT_VERSION; \
impl_value.Type = MFX_VARIANT_TYPE_U32; \
impl_value.Data.U32 = value; \
MFXSetConfigFilterProperty(cfg, (mfxU8 *)name, impl_value); \
}
/*! Adds single property of mfxU16 type.
@param[in] loader Valid mfxLoader object
@param[in] name Property name string
@param[in] value Property value
*/
#define MFX_ADD_PROPERTY_U16(loader, name, value) \
{ \
mfxVariant impl_value = { 0 }; \
mfxConfig cfg = MFXCreateConfig(loader); \
impl_value.Version.Version = MFX_VARIANT_VERSION; \
impl_value.Type = MFX_VARIANT_TYPE_U16; \
impl_value.Data.U16 = value; \
MFXSetConfigFilterProperty(cfg, (mfxU8 *)name, impl_value); \
}
/*! Adds single property of pointer type.
@param[in] loader Valid mfxLoader object
@param[in] name Property name string
@param[in] value Property value
*/
#define MFX_ADD_PROPERTY_PTR(loader, name, value) \
{ \
mfxVariant impl_value = { 0 }; \
mfxConfig cfg = MFXCreateConfig(loader); \
impl_value.Version.Version = MFX_VARIANT_VERSION; \
impl_value.Type = MFX_VARIANT_TYPE_PTR; \
impl_value.Data.Ptr = (mfxHDL)value; \
MFXSetConfigFilterProperty(cfg, (mfxU8 *)name, impl_value); \
}
/*! Update existing property of mfxU32 type.
@param[in] loader Valid mfxLoader object
@param[in] config Valid mfxConfig object
@param[in] name Property name string
@param[in] value Property value
*/
#define MFX_UPDATE_PROPERTY_U32(loader, config, name, value) \
{ \
mfxVariant impl_value; \
impl_value.Version.Version = MFX_VARIANT_VERSION; \
impl_value.Type = MFX_VARIANT_TYPE_U32; \
impl_value.Data.U32 = value; \
MFXSetConfigFilterProperty(config, (mfxU8 *)name, impl_value); \
}
/*! Update existing property of mfxU16 type.
@param[in] loader Valid mfxLoader object
@param[in] config Valid mfxConfig object
@param[in] name Property name string
@param[in] value Property value
*/
#define MFX_UPDATE_PROPERTY_U16(loader, config, name, value) \
{ \
mfxVariant impl_value; \
impl_value.Version.Version = MFX_VARIANT_VERSION; \
impl_value.Type = MFX_VARIANT_TYPE_U16; \
impl_value.Data.U16 = value; \
MFXSetConfigFilterProperty(config, (mfxU8 *)name, impl_value); \
}
/*! Update existing property of pointer type.
@param[in] loader Valid mfxLoader object
@param[in] config Valid mfxConfig object
@param[in] name Property name string
@param[in] value Property value
*/
#define MFX_UPDATE_PROPERTY_PTR(loader, config, name, value) \
{ \
mfxVariant impl_value; \
impl_value.Version.Version = MFX_VARIANT_VERSION; \
impl_value.Type = MFX_VARIANT_TYPE_PTR; \
impl_value.Data.Ptr = (mfxHDL)value; \
MFXSetConfigFilterProperty(config, (mfxU8 *)name, impl_value); \
}
#ifdef __cplusplus
}
#endif
#endif
@@ -0,0 +1,154 @@
/*############################################################################
# Copyright Intel Corporation
#
# SPDX-License-Identifier: MIT
############################################################################*/
#ifndef __MFXDISPATCHERPREFIXEDFUNCTIONS_H__
#define __MFXDISPATCHERPREFIXEDFUNCTIONS_H__
// API 1.0 functions
#define MFXInit disp_MFXInit
#define MFXClose disp_MFXClose
#define MFXQueryIMPL disp_MFXQueryIMPL
#define MFXQueryVersion disp_MFXQueryVersion
#define MFXJoinSession disp_MFXJoinSession
#define MFXDisjoinSession disp_MFXDisjoinSession
#define MFXCloneSession disp_MFXCloneSession
#define MFXSetPriority disp_MFXSetPriority
#define MFXGetPriority disp_MFXGetPriority
#define MFXVideoCORE_SetFrameAllocator disp_MFXVideoCORE_SetFrameAllocator
#define MFXVideoCORE_SetHandle disp_MFXVideoCORE_SetHandle
#define MFXVideoCORE_GetHandle disp_MFXVideoCORE_GetHandle
#define MFXVideoCORE_SyncOperation disp_MFXVideoCORE_SyncOperation
#define MFXVideoENCODE_Query disp_MFXVideoENCODE_Query
#define MFXVideoENCODE_QueryIOSurf disp_MFXVideoENCODE_QueryIOSurf
#define MFXVideoENCODE_Init disp_MFXVideoENCODE_Init
#define MFXVideoENCODE_Reset disp_MFXVideoENCODE_Reset
#define MFXVideoENCODE_Close disp_MFXVideoENCODE_Close
#define MFXVideoENCODE_GetVideoParam disp_MFXVideoENCODE_GetVideoParam
#define MFXVideoENCODE_GetEncodeStat disp_MFXVideoENCODE_GetEncodeStat
#define MFXVideoENCODE_EncodeFrameAsync disp_MFXVideoENCODE_EncodeFrameAsync
#define MFXVideoDECODE_Query disp_MFXVideoDECODE_Query
#define MFXVideoDECODE_DecodeHeader disp_MFXVideoDECODE_DecodeHeader
#define MFXVideoDECODE_QueryIOSurf disp_MFXVideoDECODE_QueryIOSurf
#define MFXVideoDECODE_Init disp_MFXVideoDECODE_Init
#define MFXVideoDECODE_Reset disp_MFXVideoDECODE_Reset
#define MFXVideoDECODE_Close disp_MFXVideoDECODE_Close
#define MFXVideoDECODE_GetVideoParam disp_MFXVideoDECODE_GetVideoParam
#define MFXVideoDECODE_GetDecodeStat disp_MFXVideoDECODE_GetDecodeStat
#define MFXVideoDECODE_SetSkipMode disp_MFXVideoDECODE_SetSkipMode
#define MFXVideoDECODE_GetPayload disp_MFXVideoDECODE_GetPayload
#define MFXVideoDECODE_DecodeFrameAsync disp_MFXVideoDECODE_DecodeFrameAsync
#define MFXVideoVPP_Query disp_MFXVideoVPP_Query
#define MFXVideoVPP_QueryIOSurf disp_MFXVideoVPP_QueryIOSurf
#define MFXVideoVPP_Init disp_MFXVideoVPP_Init
#define MFXVideoVPP_Reset disp_MFXVideoVPP_Reset
#define MFXVideoVPP_Close disp_MFXVideoVPP_Close
#define MFXVideoVPP_GetVideoParam disp_MFXVideoVPP_GetVideoParam
#define MFXVideoVPP_GetVPPStat disp_MFXVideoVPP_GetVPPStat
#define MFXVideoVPP_RunFrameVPPAsync disp_MFXVideoVPP_RunFrameVPPAsync
// API 1.1 functions
#define MFXVideoUSER_Register disp_MFXVideoUSER_Register
#define MFXVideoUSER_Unregister disp_MFXVideoUSER_Unregister
#define MFXVideoUSER_ProcessFrameAsync disp_MFXVideoUSER_ProcessFrameAsync
// API 1.10 functions
#define MFXVideoENC_Query disp_MFXVideoENC_Query
#define MFXVideoENC_QueryIOSurf disp_MFXVideoENC_QueryIOSurf
#define MFXVideoENC_Init disp_MFXVideoENC_Init
#define MFXVideoENC_Reset disp_MFXVideoENC_Reset
#define MFXVideoENC_Close disp_MFXVideoENC_Close
#define MFXVideoENC_ProcessFrameAsync disp_MFXVideoENC_ProcessFrameAsync
#define MFXVideoVPP_RunFrameVPPAsyncEx disp_MFXVideoVPP_RunFrameVPPAsyncEx
#define MFXVideoUSER_Load disp_MFXVideoUSER_Load
#define MFXVideoUSER_UnLoad disp_MFXVideoUSER_UnLoad
// API 1.11 functions
#define MFXVideoPAK_Query disp_MFXVideoPAK_Query
#define MFXVideoPAK_QueryIOSurf disp_MFXVideoPAK_QueryIOSurf
#define MFXVideoPAK_Init disp_MFXVideoPAK_Init
#define MFXVideoPAK_Reset disp_MFXVideoPAK_Reset
#define MFXVideoPAK_Close disp_MFXVideoPAK_Close
#define MFXVideoPAK_ProcessFrameAsync disp_MFXVideoPAK_ProcessFrameAsync
// API 1.13 functions
#define MFXVideoUSER_LoadByPath disp_MFXVideoUSER_LoadByPath
// API 1.14 functions
#define MFXInitEx disp_MFXInitEx
// Audio library functions
// API 1.8 functions
#define MFXAudioCORE_SyncOperation disp_MFXAudioCORE_SyncOperation
#define MFXAudioENCODE_Query disp_MFXAudioENCODE_Query
#define MFXAudioENCODE_QueryIOSize disp_MFXAudioENCODE_QueryIOSize
#define MFXAudioENCODE_Init disp_MFXAudioENCODE_Init
#define MFXAudioENCODE_Reset disp_MFXAudioENCODE_Reset
#define MFXAudioENCODE_Close disp_MFXAudioENCODE_Close
#define MFXAudioENCODE_GetAudioParam disp_MFXAudioENCODE_GetAudioParam
#define MFXAudioENCODE_EncodeFrameAsync disp_MFXAudioENCODE_EncodeFrameAsync
#define MFXAudioDECODE_Query disp_MFXAudioDECODE_Query
#define MFXAudioDECODE_DecodeHeader disp_MFXAudioDECODE_DecodeHeader
#define MFXAudioDECODE_Init disp_MFXAudioDECODE_Init
#define MFXAudioDECODE_Reset disp_MFXAudioDECODE_Reset
#define MFXAudioDECODE_Close disp_MFXAudioDECODE_Close
#define MFXAudioDECODE_QueryIOSize disp_MFXAudioDECODE_QueryIOSize
#define MFXAudioDECODE_GetAudioParam disp_MFXAudioDECODE_GetAudioParam
#define MFXAudioDECODE_DecodeFrameAsync disp_MFXAudioDECODE_DecodeFrameAsync
// API 1.9 functions
#define MFXAudioUSER_Register disp_MFXAudioUSER_Register
#define MFXAudioUSER_Unregister disp_MFXAudioUSER_Unregister
#define MFXAudioUSER_ProcessFrameAsync disp_MFXAudioUSER_ProcessFrameAsync
#define MFXAudioUSER_Load disp_MFXAudioUSER_Load
#define MFXAudioUSER_UnLoad disp_MFXAudioUSER_UnLoad
// API 1.19 functions
#define MFXVideoENC_GetVideoParam disp_MFXVideoENC_GetVideoParam
#define MFXVideoPAK_GetVideoParam disp_MFXVideoPAK_GetVideoParam
#define MFXVideoCORE_QueryPlatform disp_MFXVideoCORE_QueryPlatform
#define MFXVideoUSER_GetPlugin disp_MFXVideoUSER_GetPlugin
// API 2.0 functions
#define MFXMemory_GetSurfaceForVPP disp_MFXMemory_GetSurfaceForVPP
#define MFXMemory_GetSurfaceForEncode disp_MFXMemory_GetSurfaceForEncode
#define MFXMemory_GetSurfaceForDecode disp_MFXMemory_GetSurfaceForDecode
#define MFXQueryImplsDescription disp_MFXQueryImplsDescription
#define MFXReleaseImplDescription disp_MFXReleaseImplDescription
#define MFXInitialize disp_MFXInitialize
// API 2.1 functions
#define MFXMemory_GetSurfaceForVPPOut disp_MFXMemory_GetSurfaceForVPPOut
#define MFXVideoDECODE_VPP_Init disp_MFXVideoDECODE_VPP_Init
#define MFXVideoDECODE_VPP_DecodeFrameAsync disp_MFXVideoDECODE_VPP_DecodeFrameAsync
#define MFXVideoDECODE_VPP_Reset disp_MFXVideoDECODE_VPP_Reset
#define MFXVideoDECODE_VPP_GetChannelParam disp_MFXVideoDECODE_VPP_GetChannelParam
#define MFXVideoDECODE_VPP_Close disp_MFXVideoDECODE_VPP_Close
#define MFXVideoVPP_ProcessFrameAsync disp_MFXVideoVPP_ProcessFrameAsync
// API 2.15 functions
#ifdef ONEVPL_EXPERIMENTAL
#define MFXQueryImplsProperties disp_MFXQueryImplsProperties
#endif
#endif
+458
View File
@@ -0,0 +1,458 @@
/*############################################################################
# Copyright Intel Corporation
#
# SPDX-License-Identifier: MIT
############################################################################*/
#ifdef ONEVPL_EXPERIMENTAL
#ifndef __MFXENCODESTATS_H__
#define __MFXENCODESTATS_H__
#include "mfxcommon.h"
#include "mfxstructures.h"
#ifdef __cplusplus
extern "C"
{
#endif /* __cplusplus */
/*!< The enum to specify memory layout for statistics. */
typedef enum {
MFX_ENCODESTATS_MEMORY_LAYOUT_DEFAULT = 0, /*!< The default memory layout for statistics. */
} mfxEncodeBlkStatsMemLayout;
/*!< The enum to specify mode to gather statistics. */
typedef enum {
MFX_ENCODESTATS_MODE_DEFAULT = 0, /*!< Encode mode is selected by the implementation. */
MFX_ENCODESTATS_MODE_ENCODE = 1, /*!< Full encode mode. */
} mfxEncodeStatsMode;
/*!< Flags to specify what statistics will be reported by the implementation. */
enum {
MFX_ENCODESTATS_LEVEL_BLK = 0x1, /*!< Block level statistics. */
MFX_ENCODESTATS_LEVEL_SLICE = 0x2, /*!< Slice level statistics. */
MFX_ENCODESTATS_LEVEL_TILE = 0x4, /*!< Tile level statistics. */
MFX_ENCODESTATS_LEVEL_FRAME = 0x8, /*!< Frame level statistics. */
};
MFX_PACK_BEGIN_USUAL_STRUCT()
/*!< Specifies H.265 CTU header. */
typedef struct {
union {
struct {
mfxU32 CUcountminus1 : 6; /*!< Number of CU per CTU. */
mfxU32 MaxDepth : 2; /*!< Max quad-tree depth of CU in CTU. */
mfxU32 reserved : 24;
} bitfields0;
mfxU32 dword0;
};
mfxU16 CurrXAddr; /*!< Horizontal address of CTU. */
mfxU16 CurrYAddr; /*!< Vertical address of CTU. */
mfxU32 reserved1;
} mfxCTUHeader;
MFX_PACK_END()
MFX_PACK_BEGIN_USUAL_STRUCT()
/*!< Specifies H.265 CU info. */
typedef struct {
union {
struct {
mfxU32 CU_Size : 2; /*!< indicates the CU size of the current CU. 0: 8x8 1: 16x16 2: 32x32 3: 64x64 */
mfxU32 CU_pred_mode : 1; /*!< indicates the prediction mode for the current CU. 0: intra 1: inter */
/*!
indicates the PU partition mode for the current CU.
0: 2Nx2N
1: 2NxN (inter)
2: Nx2N (inter)
3: NXN (intra only, CU Size=8x8 only. Luma Intra Mode indicates the intra prediction mode for 4x4_0. The additional prediction modes are overloaded on 4x4_1, 4x4_2, 4x4_3 below)
4: 2NxnT (inter only)
5: 2NxnB (inter only)
6: nLx2N (inter only)
7: nRx2N (inter only).
*/
mfxU32 CU_part_mode : 3;
mfxU32 InterPred_IDC_MV0 : 2; /*!< indicates the prediction direction for PU0 of the current CU. 0: L0 1: L1 2: Bi 3: reserved */
mfxU32 InterPred_IDC_MV1 : 2; /*!< indicates the prediction direction for PU1 of the current CU. 0: L0 1: L1 2: Bi 3: reserved */
/*!
Final explicit Luma Intra Mode 4x4_0 for NxN.
Valid values 0..34
Note: CU_part_mode==NxN.
*/
mfxU32 LumaIntraMode : 6;
/*!
indicates the final explicit Luma Intra Mode for the CU.
0: DM (use Luma mode, from block 0 if NxN)
1: reserved
2: Planar
3: Vertical
4: Horizontal
5: DC */
mfxU32 ChromaIntraMode : 3;
mfxU32 reserved : 13;
} bitfields0;
mfxU32 dword0;
};
union {
struct {
/*!
Final explicit Luma Intra Mode 4x4_1.
Valid values 0..34
Note: CU_part_mode==NxN.
*/
mfxU32 LumaIntraMode4x4_1 : 6;
/*!
Final explicit Luma Intra Mode 4x4_2.
Valid values 0..34
Note: CU_part_mode==NxN.
*/
mfxU32 LumaIntraMode4x4_2 : 6;
/*!
Final explicit Luma Intra Mode 4x4_3.
Valid values 0..34
Note: CU_part_mode==NxN.
*/
mfxU32 LumaIntraMode4x4_3 : 6;
mfxU32 reserved1 : 14;
} bitfields1;
mfxU32 dword1;
};
mfxI8 QP; // signed QP value
mfxU8 reserved2[3];
/*! distortion measure, approximation to SAD.
Will deviate significantly (pre, post reconstruction) and due to variation in algorithm.
*/
mfxU32 SAD;
/*!
These parameters indicate motion vectors that are associated with the PU0/PU1 winners
range [-2048.00..2047.75].
L0/PU0 - MV[0][0]
L0/PU1 - MV[0][1]
L1/PU0 - MV[1][0]
L1/PU1 - MV[1][1]
*/
mfxI16Pair MV[2][2];
union {
struct {
/*!
This parameter indicates the reference index associated with the MV X/Y
that is populated in the L0_MV0.X and L0_MV0.Y fields. */
mfxU32 L0_MV0_RefID : 4;
/*!
This parameter indicates the reference index associated with the MV X/Y
that is populated in the L0_MV1.X and L0_MV1.Y fields. */
mfxU32 L0_MV1_RefID : 4;
/*!
This parameter indicates the reference index associated with the MV X/Y
that is populated in the L1_MV0.X and L1_MV0.Y fields. */
mfxU32 L1_MV0_RefID : 4;
/*!
This parameter indicates the reference index associated with the MV X/Y
that is populated in the L1_MV1.X and L1_MV1.Y fields. */
mfxU32 L1_MV1_RefID : 4;
mfxU32 reserved3 : 16;
} bitfields8;
mfxU32 dword8;
};
mfxU32 reserved4[10];
} mfxCUInfo;
MFX_PACK_END()
MFX_PACK_BEGIN_USUAL_STRUCT()
/*!< Specifies H.265 CTU. */
typedef struct {
mfxCTUHeader CtuHeader; /*!< H.265 CTU header. */
mfxCUInfo CuInfo[64]; /*!< Array of CU. */
mfxU32 reserved;
} mfxCTUInfo;
MFX_PACK_END()
MFX_PACK_BEGIN_USUAL_STRUCT()
/*!
The structure describes H.264 stats per MB.
*/
typedef struct {
union {
struct {
/*!
Together with @p IntraMbFlag this parameter specifies macroblock type according to the
ISO\*\/IEC\* 14496-10 with the following difference - it stores either intra or inter
values according to @p IntraMbFlag, but not intra after inter.
Values for P-slices are mapped to B-slice values. For example P_16x8 is coded with
B_FWD_16x8 value.
*/
mfxU32 MBType : 5;
/*!
This field specifies inter macroblock mode and is ignored for intra MB. It is derived from @p MbType and has next values:
@li 0 - 16x16 mode
@li 1 - 16x8 mode
@li 2 - 8x16 mode
@li 3 - 8x8 mode
*/
mfxU32 InterMBMode : 2;
/*!
This field specifies intra macroblock mode and is ignored for inter MB. It is derived from @p MbType and has next values:
@li 0 - 16x16 mode
@li 1 - 8x8 mode
@li 2 - 4x4 mode
@li 3 - PCM
*/
mfxU32 IntraMBMode : 2;
/*!
This flag specifies intra/inter MB type and has next values:
0 - Inter prediction MB type
1 - Intra prediction MB type
*/
mfxU32 IntraMBFlag : 1;
/*!
This field specifies subblock shapes for the current MB. Each block is described by 2 bits starting from lower bits for block 0.
@li 0 - 8x8
@li 1 - 8x4
@li 2 - 4x8
@li 3 - 4x4
*/
mfxU32 SubMBShapes : 8;
/*!
This field specifies prediction modes for the current MB partition blocks. Each block is described by 2 bits starting from lower bits for block 0.
@li 0 - Pred_L0
@li 1 - Pred_L1
@li 2 - BiPred
@li 3 - reserved
Only one prediction value for partition is reported, the rest values are set to zero. For example:
@li 16x16 Pred_L1 - 0x01 (only 2 lower bits are used)
@li 16x8 Pred_L1 / BiPred - 0x09 (1001b)
@li 8x16 BiPred / BiPred - 0x0a (1010b)
For P MBs this value is always zero.
*/
mfxU32 SubMBShapeMode : 8;
/*!
This value specifies chroma intra prediction mode.
@li 0 - DC
@li 1 - Horizontal
@li 2 - Vertical
@li 3 - Plane
*/
mfxU32 ChromaIntraPredMode : 2;
mfxU32 reserved : 4;
} bitfields0;
mfxU32 dword0;
} ;
/*!
Distortion measure, approximation to SAD.
Deviate significantly (pre, post reconstruction) and due to variation in algorithm.
*/
mfxU32 SAD;
mfxI8 Qp; /*!< MB QP. */
mfxU8 reserved1[3];
/*!
These values specify luma intra prediction modes for current MB. Each element of the array
corresponds to 8x8 block and each holds prediction modes for four 4x4 subblocks.
Four bits per mode, lowest bits for left top subblock.
All 16 prediction modes are always specified. For 8x8 case, block prediction mode is
populated to all subblocks of the 8x8 block. For 16x16 case - to all subblocks of the MB.
Prediction directions for 4x4 and 8x8 blocks:
@li 0 - Vertical
@li 1 - Horizontal
@li 2 - DC
@li 3 - Diagonal Down Left
@li 4 - Diagonal Down Right
@li 5 - Vertical Right
@li 6 - Horizontal Down
@li 7 - Vertical Left
@li 8 - Horizontal Up
Prediction directions for 16x16 blocks:
@li 0 - Vertical
@li 1 - Horizontal
@li 2 - DC
@li 3 - Plane
*/
mfxU16 LumaIntraMode[4];
mfxU32 reserved2;
} mfxMBInfo;
MFX_PACK_END()
/*!
The enum specifies block size.
*/
typedef enum {
MFX_BLOCK_4X4 = 0, /*!< 4x4 block size. */
MFX_BLOCK_16X16 = 1, /*!< 16x16 block size. */
} mfxBlockSize;
MFX_PACK_BEGIN_STRUCT_W_PTR()
/*!
The structure describes H.264 and H.265 stats per MB or CTUs.
*/
typedef struct {
union {
mfxU32 NumMB; /*!< Number of MBs per frame for H.264. */
mfxU32 NumCTU; /*!< number of CTUs per frame for H.265. */
};
union {
mfxCTUInfo *HEVCCTUArray; /*!< Array of CTU statistics. */
mfxMBInfo *AVCMBArray; /*!< Array of MB statistics. */
};
mfxU32 reserved[8];
} mfxEncodeBlkStats;
MFX_PACK_END()
MFX_PACK_BEGIN_STRUCT_W_L_TYPE()
/*!
The structure describes H.264/H.265 frame/slice/tile level statistics.
*/
typedef struct {
mfxF32 PSNRLuma; /*!< PSNR for LUMA samples. */
mfxF32 PSNRCb; /*!< PSNR for Chroma (Cb) samples. */
mfxF32 PSNRCr; /*!< PSNR for Chroma (Cr) samples. */
/*! distortion measure, approximation to SAD.
Will deviate significantly (pre, post reconstruction) and due to variation in algorithm.
*/
mfxU64 SADLuma;
mfxF32 Qp; /*!< average frame QP, may have fractional part in case of MBQP. */
union {
mfxU32 NumMB; /*!< Number of MBs per frame for H.264. */
mfxU32 NumCTU; /*!< number of CTUs per frame for H.265. */
};
mfxBlockSize BlockSize; /*! For H.264 it is always 16x16 corresponding to MB size.
In H.265 it's normalized to 4x4, so for each CU we calculate number of 4x4 which belongs to the block. */
mfxU32 NumIntraBlock; /*! Number of intra blocks in the frame. The size of block is defined by BlockSize.
For H.265 it can be more than number of intra CU. */
mfxU32 NumInterBlock; /*! Number of inter blocks in the frame. The size of block is defined by BlockSize.
For H.265 it can be more than number of inter CU. */
mfxU32 NumSkippedBlock; /*! Number of skipped blocks in the frame. The size of block is defined by BlockSize.
For H.265 it can be more than number of skipped CU. */
mfxU32 reserved[8];
} mfxEncodeHighLevelStats;
MFX_PACK_END()
/*!
Alias for the structure to describe H.264 and H.265 frame level stats.
*/
typedef mfxEncodeHighLevelStats mfxEncodeFrameStats;
MFX_PACK_BEGIN_STRUCT_W_PTR()
/*!
The structure describes H.264 and H.265 stats per Slice or Tile.
*/
typedef struct {
mfxU32 NumElements; /*!< Number of Slices or Tiles per frame for H.264/H.265. */
mfxEncodeHighLevelStats *HighLevelStatsArray; /*!< Array of CTU statistics. */
mfxU32 reserved[8];
} mfxEncodeSliceStats;
MFX_PACK_END()
/*!
Alias for the structure to describe H.264 and H.265 tile level stats.
*/
typedef mfxEncodeSliceStats mfxEncodeTileStats;
#define MFX_ENCODESTATSCONTAINER_VERSION MFX_STRUCT_VERSION(1, 0)
MFX_PACK_BEGIN_STRUCT_W_PTR()
/*! The structure represents reference counted container for output after encoding operation which includes statistics
and synchronization primitive for compressed bitstream.
The memory is allocated and released by the library.
*/
typedef struct mfxEncodeStatsContainer {
mfxStructVersion Version; /*!< The version of the structure. */
mfxRefInterface RefInterface; /*! < Reference counting interface. */
/*! @brief
Guarantees readiness of the statistics after a function completes.
Instead of MFXVideoCORE_SyncOperation which leads to the synchronization of all output objects,
users may directly call the mfxEncodeStatsContainer::SynchronizeStatistics function to get output statistics.
@param[in] ref_interface Valid interface.
@param[out] wait Wait time in milliseconds.
@return
MFX_ERR_NONE If no error. \n
MFX_ERR_NULL_PTR If interface is NULL. \n
MFX_ERR_INVALID_HANDLE If any of container is not valid object . \n
MFX_WRN_IN_EXECUTION If the given timeout is expired and the container is not ready. \n
MFX_ERR_ABORTED If the specified asynchronous function aborted due to data dependency on a previous asynchronous function that did not complete. \n
MFX_ERR_UNKNOWN Any internal error.
*/
mfxStatus (MFX_CDECL *SynchronizeStatistics)(mfxRefInterface* ref_interface, mfxU32 wait);
/*! @brief
Guarantees readiness of associated compressed bitstream after a function completes.
Instead of MFXVideoCORE_SyncOperation which leads to the synchronization of all output objects,
users may directly call the mfxEncodeStatsContainer::SynchronizeStatistics function to get output bitstream.
@param[in] ref_interface Valid interface.
@param[out] wait Wait time in milliseconds.
@return
MFX_ERR_NONE If no error. \n
MFX_ERR_NULL_PTR If interface is NULL. \n
MFX_ERR_INVALID_HANDLE If any of container is not valid object . \n
MFX_WRN_IN_EXECUTION If the given timeout is expired and the container is not ready. \n
MFX_ERR_ABORTED If the specified asynchronous function aborted due to data dependency on a previous asynchronous function that did not complete. \n
MFX_ERR_UNKNOWN Any internal error.
*/
mfxStatus (MFX_CDECL *SynchronizeBitstream)(mfxRefInterface* ref_interface, mfxU32 wait);
mfxHDL reserved[4];
mfxU32 reserved1[2];
mfxU32 DisplayOrder; /*!< To which frame number statistics belong. */
mfxEncodeBlkStatsMemLayout MemLayout; /*!< Memory layout for statistics. */
mfxEncodeBlkStats *EncodeBlkStats; /*!< Block level statistics. */
mfxEncodeSliceStats *EncodeSliceStats; /*!< Slice level statistics. */
mfxEncodeTileStats *EncodeTileStats; /*!< Tile level statistics. */
mfxEncodeFrameStats *EncodeFrameStats; /*!< Frame level statistics. */
mfxU32 reserved2[8];
}mfxEncodeStatsContainer;
MFX_PACK_END()
MFX_PACK_BEGIN_STRUCT_W_PTR()
/*! The extension buffer which should be attached by application for mfxBitstream buffer before
encode operation. As result the encoder will allocate memory for statistics and fill appropriate structures.
*/
typedef struct {
mfxExtBuffer Header; /*!< Extension buffer header. Header.BufferId must be equal to MFX_EXTBUFF_ENCODESTATS_BLK. */
mfxU16 EncodeStatsFlags; /*!< What statistics is required: block/slice/tile/frame level or any combinations.
In case of slice or tile output statistics for one slice or tile will be available only.*/
mfxEncodeStatsMode Mode; /*!< What encoding mode should be used to gather statistics. */
mfxEncodeStatsContainer *EncodeStatsContainer; /*!< encode output, filled by the implementation. */
mfxU32 reserved[8];
} mfxExtEncodeStatsOutput;
MFX_PACK_END()
#ifdef __cplusplus
} // extern "C"
#endif
#endif
#endif
+78
View File
@@ -0,0 +1,78 @@
/*############################################################################
# Copyright Intel Corporation
#
# SPDX-License-Identifier: MIT
############################################################################*/
#include "mfxdefs.h"
#ifndef __MFXIMPLCAPS_H__
#define __MFXIMPLCAPS_H__
#include "mfxstructures.h"
#ifdef __cplusplus
extern "C"
{
#endif
/*!
@brief
Delivers implementation capabilities in the requested format according to the format value. Calling this
function directly is not recommended. Instead, applications must call the MFXEnumImplementations function.
@param[in] format Format in which capabilities must be delivered. See mfxImplCapsDeliveryFormat for more details.
@param[out] num_impls Number of the implementations.
@return
Array of handles to the capability report or NULL in case of unsupported format or NULL num_impls pointer.
Length of array is equal to num_impls.
@since This function is available since API version 2.0.
*/
mfxHDL* MFX_CDECL MFXQueryImplsDescription(mfxImplCapsDeliveryFormat format, mfxU32* num_impls);
/*!
@brief
Destroys the handle allocated by the MFXQueryImplsDescription function or the MFXQueryImplsProperties function.
Implementation must remember which handles are released. Once the last handle is released, this function must release memory
allocated for the array of handles. Calling this function directly is not recommended. Instead, applications must call
the MFXDispReleaseImplDescription function.
@param[in] hdl Handle to destroy. Can be equal to NULL.
@return
MFX_ERR_NONE The function completed successfully.
@since This function is available since API version 2.0.
*/
mfxStatus MFX_CDECL MFXReleaseImplDescription(mfxHDL hdl);
#ifdef ONEVPL_EXPERIMENTAL
/*!
@brief
Delivers implementation capabilities for configured properties.
The returned capability report will be sparsely filled out, with only properties available which
were set via MFXSetConfigFilterProperty(). Calling this function directly is not recommended.
Instead, applications must call the MFXEnumImplementations function.
@param[in] properties Array of property name/value pairs indicating which properties to populate in the capability report.
@param[in] num_properties Number of property name/value pairs.
@param[out] num_impls Number of the implementations.
@return
Array of handles to the capability report or NULL in case of NULL properties pointer or zero num_properties or NULL num_impls pointer.
Length of array is equal to num_impls.
@since This function is available since API version 2.15.
*/
mfxHDL* MFX_CDECL MFXQueryImplsProperties(mfxQueryProperty** properties, mfxU32 num_properties, mfxU32* num_impls);
#endif
#ifdef __cplusplus
} // extern "C"
#endif
#endif // __MFXIMPLCAPS_H__
+136
View File
@@ -0,0 +1,136 @@
/*############################################################################
# Copyright Intel Corporation
#
# SPDX-License-Identifier: MIT
############################################################################*/
#ifndef __MFX_JPEG_H__
#define __MFX_JPEG_H__
#include "mfxdefs.h"
#include "mfxstructures.h"
#ifdef __cplusplus
extern "C"
{
#endif /* __cplusplus */
/* CodecId */
enum {
MFX_CODEC_JPEG = MFX_MAKEFOURCC('J','P','E','G') /*!< JPEG codec */
};
/* CodecProfile, CodecLevel */
enum
{
MFX_PROFILE_JPEG_BASELINE = 1 /*!< Baseline JPEG profile. */
};
/*! The Rotation enumerator itemizes the JPEG rotation options. */
enum
{
MFX_ROTATION_0 = 0, /*!< No rotation. */
MFX_ROTATION_90 = 1, /*!< 90 degree rotation. */
MFX_ROTATION_180 = 2, /*!< 180 degree rotation. */
MFX_ROTATION_270 = 3 /*!< 270 degree rotation. */
};
enum {
MFX_EXTBUFF_JPEG_QT = MFX_MAKEFOURCC('J','P','G','Q'), /*!< This extended buffer defines quantization tables for JPEG encoder. */
MFX_EXTBUFF_JPEG_HUFFMAN = MFX_MAKEFOURCC('J','P','G','H') /*!< This extended buffer defines Huffman tables for JPEG encoder. */
};
/*! The JPEGColorFormat enumerator itemizes the JPEG color format options. */
enum {
MFX_JPEG_COLORFORMAT_UNKNOWN = 0, /*! Unknown color format. The decoder tries to determine color format from available in bitstream information.
If such information is not present, then MFX_JPEG_COLORFORMAT_YCbCr color format is assumed. */
MFX_JPEG_COLORFORMAT_YCbCr = 1, /*! Bitstream contains Y, Cb and Cr components. */
MFX_JPEG_COLORFORMAT_RGB = 2 /*! Bitstream contains R, G and B components. */
};
/*! The JPEGScanType enumerator itemizes the JPEG scan types. */
enum {
MFX_SCANTYPE_UNKNOWN = 0, /*!< Unknown scan type. */
MFX_SCANTYPE_INTERLEAVED = 1, /*!< Interleaved scan. */
MFX_SCANTYPE_NONINTERLEAVED = 2 /*!< Non-interleaved scan. */
};
enum {
MFX_CHROMAFORMAT_JPEG_SAMPLING = 6 /*!< Color sampling specified via mfxInfoMFX::SamplingFactorH and SamplingFactorV. */
};
MFX_PACK_BEGIN_USUAL_STRUCT()
/*!
Specifies quantization tables. The application may specify up to 4 quantization tables. The encoder assigns an ID to each table.
That ID is equal to the table index in the Qm array. Table "0" is used for encoding of the Y component, table "1" for the U component, and table "2"
for the V component. The application may specify fewer tables than the number of components in the image. If two tables are specified,
then table "1" is used for both U and V components. If only one table is specified then it is used for all components in the image.
The following table illustrates this behavior.
@internal
+------------------+---------+------+---+
| Table ID | 0 | 1 | 2 |
+------------------+---------+------+---+
| Number of tables | | | |
+==================+=========+======+===+
| 0 | Y, U, V | | |
+------------------+---------+------+---+
| 1 | Y | U, V | |
+------------------+---------+------+---+
| 2 | Y | U | V |
+------------------+---------+------+---+
@endinternal
*/
typedef struct {
mfxExtBuffer Header; /*!< Extension buffer header. Header.BufferId must be equal to MFX_EXTBUFF_JPEG_QT. */
mfxU16 reserved[7];
mfxU16 NumTable; /*!< Number of quantization tables defined in Qm array. */
mfxU16 Qm[4][64]; /*!< Quantization table values. */
} mfxExtJPEGQuantTables;
MFX_PACK_END()
MFX_PACK_BEGIN_USUAL_STRUCT()
/*!
Specifies Huffman tables. The application may specify up to 2 quantization table pairs for baseline process. The encoder
assigns an ID to each table. That ID is equal to the table index in the DCTables and ACTables arrays. Table "0" is used for encoding of the Y component and
table "1" is used for encoding of the U and V component. The application may specify only one table, in which case the table will be used for all components in the image.
The following table illustrates this behavior.
@internal
+------------------+---------+------+
| Table ID | 0 | 1 |
+------------------+---------+------+
| Number of tables | | |
+==================+=========+======+
| 0 | Y, U, V | |
+------------------+---------+------+
| 1 | Y | U, V |
+------------------+---------+------+
@endinternal
*/
typedef struct {
mfxExtBuffer Header; /*!< Extension buffer header. Header.BufferId must be equal to MFX_EXTBUFF_JPEG_HUFFMAN. */
mfxU16 reserved[2];
mfxU16 NumDCTable; /*!< Number of DC quantization table in DCTables array. */
mfxU16 NumACTable; /*!< Number of AC quantization table in ACTables array. */
struct {
mfxU8 Bits[16]; /*!< Number of codes for each code length. */
mfxU8 Values[12]; /*!< List of the 8-bit symbol values. */
} DCTables[4]; /*!< Array of DC tables. */
struct {
mfxU8 Bits[16]; /*!< Number of codes for each code length. */
mfxU8 Values[162]; /*!< Array of AC tables. */
} ACTables[4]; /*!< List of the 8-bit symbol values. */
} mfxExtJPEGHuffmanTables;
MFX_PACK_END()
#ifdef __cplusplus
} // extern "C"
#endif /* __cplusplus */
#endif // __MFX_JPEG_H__
+348
View File
@@ -0,0 +1,348 @@
/*############################################################################
# Copyright Intel Corporation
#
# SPDX-License-Identifier: MIT
############################################################################*/
#ifndef __MFXMEMORY_H__
#define __MFXMEMORY_H__
#include "mfxsession.h"
#include "mfxstructures.h"
#ifdef __cplusplus
extern "C"
{
#endif
/*!
@brief
Returns surface which can be used as input for VPP.
VPP should be initialized before this call.
Surface should be released with mfxFrameSurface1::FrameInterface.Release(...) after usage. The value of mfxFrameSurface1::Data.Locked for the returned surface is 0.
@param[in] session Session handle.
@param[out] surface Pointer is set to valid mfxFrameSurface1 object.
@return
MFX_ERR_NONE The function completed successfully. \n
MFX_ERR_NULL_PTR If double-pointer to the @p surface is NULL. \n
MFX_ERR_INVALID_HANDLE If @p session was not initialized. \n
MFX_ERR_NOT_INITIALIZED If VPP was not initialized (allocator needs to know surface size from somewhere). \n
MFX_ERR_MEMORY_ALLOC In case of any other internal allocation error. \n
MFX_WRN_ALLOC_TIMEOUT_EXPIRED In case of waiting timeout expired (if set with mfxExtAllocationHints).
@since This function is available since API version 2.0.
*/
mfxStatus MFX_CDECL MFXMemory_GetSurfaceForVPP(mfxSession session, mfxFrameSurface1** surface);
/*!
@brief
Returns surface which can be used as output of VPP.
VPP should be initialized before this call.
Surface should be released with mfxFrameSurface1::FrameInterface.Release(...) after usage. The value of mfxFrameSurface1::Data.Locked for the returned surface is 0.
@param[in] session Session handle.
@param[out] surface Pointer is set to valid mfxFrameSurface1 object.
@return
MFX_ERR_NONE The function completed successfully. \n
MFX_ERR_NULL_PTR If double-pointer to the @p surface is NULL. \n
MFX_ERR_INVALID_HANDLE If @p session was not initialized. \n
MFX_ERR_NOT_INITIALIZED If VPP was not initialized (allocator needs to know surface size from somewhere). \n
MFX_ERR_MEMORY_ALLOC In case of any other internal allocation error. \n
MFX_WRN_ALLOC_TIMEOUT_EXPIRED In case of waiting timeout expired (if set with mfxExtAllocationHints).
@since This function is available since API version 2.1.
*/
mfxStatus MFX_CDECL MFXMemory_GetSurfaceForVPPOut(mfxSession session, mfxFrameSurface1** surface);
/*! Alias for MFXMemory_GetSurfaceForVPP function. */
#define MFXMemory_GetSurfaceForVPPIn MFXMemory_GetSurfaceForVPP
/*!
@brief
Returns a surface which can be used as input for the encoder.
Encoder should be initialized before this call.
Surface should be released with mfxFrameSurface1::FrameInterface.Release(...) after usage. The value of mfxFrameSurface1::Data.Locked for the returned surface is 0.
@param[in] session Session handle.
@param[out] surface Pointer is set to valid mfxFrameSurface1 object.
@return
MFX_ERR_NONE The function completed successfully.\n
MFX_ERR_NULL_PTR If surface is NULL.\n
MFX_ERR_INVALID_HANDLE If session was not initialized.\n
MFX_ERR_NOT_INITIALIZED If the encoder was not initialized (allocator needs to know surface size from somewhere).\n
MFX_ERR_MEMORY_ALLOC In case of any other internal allocation error. \n
MFX_WRN_ALLOC_TIMEOUT_EXPIRED In case of waiting timeout expired (if set with mfxExtAllocationHints).
@since This function is available since API version 2.0.
*/
mfxStatus MFX_CDECL MFXMemory_GetSurfaceForEncode(mfxSession session, mfxFrameSurface1** surface);
/*!
@brief
Returns a surface which can be used as output of the decoder.
Decoder should be initialized before this call.
Surface should be released with mfxFrameSurface1::FrameInterface.Release(...) after usage. The value of mfxFrameSurface1::Data.Locked for the returned surface is 0.'
@note This function was added to simplify transition from legacy surface management to the proposed internal allocation approach.
Previously, the user allocated surfaces for the working pool and fed them to the decoder using DecodeFrameAsync calls. With MFXMemory_GetSurfaceForDecode
it is possible to change the existing pipeline by just changing the source of work surfaces.
Newly developed applications should prefer direct usage of DecodeFrameAsync with internal allocation.
@param[in] session Session handle.
@param[out] surface Pointer is set to valid mfxFrameSurface1 object.
@return
MFX_ERR_NONE The function completed successfully.\n
MFX_ERR_NULL_PTR If surface is NULL.\n
MFX_ERR_INVALID_HANDLE If session was not initialized.\n
MFX_ERR_NOT_INITIALIZED If the decoder was not initialized (allocator needs to know surface size from somewhere).\n
MFX_ERR_MEMORY_ALLOC Other internal allocation error. \n
MFX_WRN_ALLOC_TIMEOUT_EXPIRED In case of waiting timeout expired (if set with mfxExtAllocationHints).
@since This function is available since API version 2.0.
*/
mfxStatus MFX_CDECL MFXMemory_GetSurfaceForDecode(mfxSession session, mfxFrameSurface1** surface);
#ifdef ONEVPL_EXPERIMENTAL
MFX_PACK_BEGIN_STRUCT_W_PTR()
typedef struct {
mfxSurfaceInterface SurfaceInterface;
mfxHDL texture2D; /*!< Pointer to texture, type ID3D11Texture2D* */
mfxHDL reserved[7];
} mfxSurfaceD3D11Tex2D;
MFX_PACK_END()
MFX_PACK_BEGIN_STRUCT_W_PTR()
typedef struct {
mfxSurfaceInterface SurfaceInterface;
mfxHDL vaDisplay; /*!< Object of type VADisplay. */
mfxU32 vaSurfaceID; /*!< Object of type VASurfaceID. */
mfxU32 reserved1;
mfxHDL reserved[6];
} mfxSurfaceVAAPI;
MFX_PACK_END()
MFX_PACK_BEGIN_STRUCT_W_PTR()
/*!
Optional extension buffer, which can be attached to mfxSurfaceHeader::ExtParam
(second parameter of mfxFrameSurfaceInterface::Export) in order to pass OCL parameters
during mfxFrameSurface1 exporting to OCL surface.
If buffer is not provided all resources will be created by oneAPI Video Processing Library (oneVPL) RT internally.
*/
typedef struct {
mfxExtBuffer Header; /*!< Extension buffer header. Header.BufferId must be equal to MFX_EXTBUFF_EXPORT_SHARING_DESC_OCL. */
mfxHDL ocl_context; /*!< Object of type cl_context (OpenCL context). */
mfxHDL ocl_command_queue; /*!< Object of type cl_command_queue (OpenCL command queue). */
mfxHDL reserved[8];
} mfxExtSurfaceOpenCLImg2DExportDescription;
MFX_PACK_END()
MFX_PACK_BEGIN_STRUCT_W_PTR()
typedef struct {
mfxSurfaceInterface SurfaceInterface;
mfxHDL ocl_context; /*!< Object of type cl_context (OpenCL context). */
mfxHDL ocl_command_queue; /*!< Object of type cl_command_queue (OpenCL command queue). */
mfxHDL ocl_image[4]; /*!< Object of type cl_mem[4] (array of 4 OpenCL 2D images). */
mfxU32 ocl_image_num; /*!< Number of valid images (planes), depends on color format. */
mfxHDL reserved[8];
} mfxSurfaceOpenCLImg2D;
MFX_PACK_END()
MFX_PACK_BEGIN_STRUCT_W_PTR()
/*!
Optional extension buffer, which can be attached to mfxSurfaceHeader::ExtParam
(second parameter of mfxFrameSurfaceInterface::Export) in order to pass D3D12 parameters
during mfxFrameSurface1 exporting to D3D12 resource.
If buffer is not provided all resources will be created by oneAPI Video Processing Library (oneVPL) RT internally.
*/
typedef struct {
mfxExtBuffer Header; /*!< Extension buffer header. Header.BufferId must be equal to MFX_EXTBUFF_EXPORT_SHARING_DESC_D3D12. */
mfxHDL d3d12Device; /*!< Pointer to D3D12 Device, type ID3D12Device*. */
mfxHDL reserved[9];
} mfxExtSurfaceD3D12Tex2DExportDescription;
MFX_PACK_END()
MFX_PACK_BEGIN_STRUCT_W_PTR()
typedef struct {
mfxSurfaceInterface SurfaceInterface;
mfxHDL texture2D; /*!< Pointer to D3D12 resource, type ID3D12Resource*. */
mfxHDL reserved[7];
} mfxSurfaceD3D12Tex2D;
MFX_PACK_END()
MFX_PACK_BEGIN_STRUCT_W_PTR()
/*!
Optional extension buffer, which can be attached to mfxSurfaceHeader::ExtParam
(second parameter of mfxFrameSurfaceInterface::Export) in order to pass Vulkan parameters
during mfxFrameSurface1 exporting to Vulkan surface.
If buffer is not provided all resources will be created by oneAPI Video Processing Library (oneVPL) RT internally.
*/
typedef struct {
mfxExtBuffer Header; /*!< Extension buffer header. Header.BufferId must be equal to MFX_EXTBUFF_EXPORT_SHARING_DESC_VULKAN. */
mfxHDL instance; /*!< Object of type VkInstance (Vulkan instance). */
mfxHDL physicalDevice; /*!< Object of type VkPhysicalDevice (Vulkan physical device). */
mfxHDL device; /*!< Object of type VkDevice (Vulkan device). */
mfxHDL reserved[7];
} mfxExtSurfaceVulkanImg2DExportDescription;
MFX_PACK_END()
MFX_PACK_BEGIN_STRUCT_W_PTR()
typedef struct {
mfxSurfaceInterface SurfaceInterface;
mfxHDL instance; /*!< Object of type VkInstance (Vulkan instance). */
mfxHDL physicalDevice; /*!< Object of type VkPhysicalDevice (Vulkan physical device). */
mfxHDL device; /*!< Object of type VkDevice (Vulkan device). */
mfxHDL image2D; /*!< Object of type VkImage (Vulkan 2D images). */
mfxHDL image2DMemory; /*!< Object of type VkDeviceMemory (Vulkan device memory). */
mfxHDL reserved[10];
} mfxSurfaceVulkanImg2D;
MFX_PACK_END()
/*! The mfxSurfaceComponent enumerator specifies the internal surface pool to use when importing surfaces. */
typedef enum {
MFX_SURFACE_COMPONENT_UNKNOWN = 0, /*!< Unknown surface component. */
MFX_SURFACE_COMPONENT_ENCODE = 1, /*!< Shared surface for encoding. */
MFX_SURFACE_COMPONENT_DECODE = 2, /*!< Shared surface for decoding. */
MFX_SURFACE_COMPONENT_VPP_INPUT = 3, /*!< Shared surface for VPP input. */
MFX_SURFACE_COMPONENT_VPP_OUTPUT = 4, /*!< Shared surface for VPP output. */
} mfxSurfaceComponent;
/*! The current version of mfxSurfaceTypesSupported structure. */
#define MFX_SURFACETYPESSUPPORTED_VERSION MFX_STRUCT_VERSION(1, 0)
MFX_PACK_BEGIN_STRUCT_W_PTR()
/*! This structure describes the supported surface types and modes. */
typedef struct {
mfxStructVersion Version; /*!< Version of the structure. */
mfxU16 NumSurfaceTypes; /*!< Number of supported surface types. */
struct surftype {
mfxSurfaceType SurfaceType; /*!< Supported surface type. */
mfxU32 reserved[6]; /*!< Reserved for future use. */
mfxU16 NumSurfaceComponents; /*!< Number of supported surface components. */
struct surfcomp {
mfxSurfaceComponent SurfaceComponent; /*!< Supported surface component. */
mfxU32 SurfaceFlags; /*!< Supported surface flags for this component (may be OR'd). */
mfxU32 reserved[7]; /*!< Reserved for future use. */
} *SurfaceComponents;
} *SurfaceTypes;
mfxU32 reserved[4]; /*!< Reserved for future use. */
} mfxSurfaceTypesSupported;
MFX_PACK_END()
#define MFX_MEMORYINTERFACE_VERSION MFX_STRUCT_VERSION(1, 1)
MFX_PACK_BEGIN_STRUCT_W_PTR()
/* Specifies memory interface. */
typedef struct mfxMemoryInterface {
mfxHDL Context; /*!< The context of the memory interface. User should not touch (change, set, null) this pointer. */
mfxStructVersion Version; /*!< The version of the structure. */
/*!
@brief
Imports an application-provided surface into mfxFrameSurface1 which may be used as input for encoding or video processing.
@param[in] memory_interface Valid memory interface.
@param[in] surf_component Surface component type. Required for allocating new surfaces from the appropriate pool.
@param[in,out] external_surface Pointer to the mfxSurfaceXXX object describing the surface to be imported. All fields in
mfxSurfaceHeader must be set by the application. mfxSurfaceHeader::SurfaceType is
read by oneVPL runtime to determine which particular mfxSurfaceXXX structure is supplied.
For example, if mfxSurfaceXXX::SurfaceType == MFX_SURFACE_TYPE_D3D11_TEX2D, then the handle
will be interpreted as an object of type mfxSurfaceD3D11Tex2D. The application should
set or clear other fields as specified in the corresponding structure description.
After successful import, the value of mfxSurfaceHeader::SurfaceFlags will be replaced with the actual
import type. It can be used to determine which import type (with or without copy) took place in the case
of initial default setting, or if multiple import flags were OR'ed.
All external sync operations on the ext_surface must be completed before calling this function.
@param[out] imported_surface Pointer to a valid mfxFrameSurface1 object containing the imported frame.
imported_surface may be passed as an input to Encode or VPP processing operations.
@return
MFX_ERR_NONE The function completed successfully.\n
MFX_ERR_NULL_PTR If ext_surface or imported_surface are NULL.\n
MFX_ERR_INVALID_HANDLE If the corresponding session was not initialized.\n
MFX_ERR_UNSUPPORTED If surf_component is not one of [MFX_SURFACE_COMPONENT_ENCODE, MFX_SURFACE_COMPONENT_VPP_INPUT], or if
mfxSurfaceHeader::SurfaceType is not supported by oneVPL runtime for this operation.\n
@since This function is available since API version 2.10.
*/
/* For reference with Export flow please search for mfxFrameSurfaceInterface::Export. */
mfxStatus (MFX_CDECL *ImportFrameSurface)(struct mfxMemoryInterface* memory_interface, mfxSurfaceComponent surf_component, mfxSurfaceHeader* external_surface, mfxFrameSurface1** imported_surface);
/*!
@brief
Get a buffer in video memory into mfxBitstream which is used to store bitstream data for video decoding.
@param[in] memory_interface Valid memory interface.
@param[in,out] queried_bsBuffer Pointer to a valid mfxBitstream object.
When the call succeeds, the Data, DataOffset, DataLength, MaxLength, DataFlag of the queried_bsBuffer will be updated.
@return
MFX_ERR_NONE The function completed successfully. The Data, DataOffset, DataLength, MaxLength, DataFlag of the queried_bsBuffer will be updated.\n
MFX_ERR_NOT_INITIALIZED The function is called before MFXVideoDECODE_Init.\n
MFX_ERR_INVALID_HANDLE The input memory_interface is an invalid interface.\n
MFX_ERR_NULL_PTR The input queried_bsBuffer is a null pointer.\n
MFX_ERR_UNSUPPORTED The function is not supported for this codec.\n
MFX_ERR_DEVICE_FAILED The function fails to get the bitstream buffer in video memory.\n
@note This function should be called after MFXVideoDECODE_Init succeeds.
The bitstream buffer returned in queried_bsBuffer will be managed by the runtime and has been mapped for CPU access. The application does not need to allocate or delete memory for this buffer.
It will be allocated by runtime when the codec supports this function. It will be released automatically when the corresponding DecodeFrameAsync succeeds.
The mfxBitstream::Data will be reset to nullptr by DecodeFrameAsync when the buffer is released internally.
The MFX_BITSTREAM_IN_VIDEO_MEMORY bit of mfxBitstream::DataFlag will be set if this function succeeds, indicating the bitstream buffer is in video memory.
It is required for app to ensure the bitstream is a single complete frame (set the MFX_BITSTREAM_COMPLETE_FRAME of queried_bsBuffer->DataFlag) when using this function.
If the bitstream is not a complete frame, the mfxFrameData::Corrupted of the output surface will be set with MFX_CORRUPTION_MAJOR or MFX_CORRUPTION_MINOR according to the hardware decoding status.
@since This function is available since API version 2.17.
*/
mfxStatus (MFX_CDECL* GetBitstreamBuffer)(struct mfxMemoryInterface* memory_interface, mfxBitstream* queried_bsBuffer);
mfxHDL reserved[15];
} mfxMemoryInterface;
MFX_PACK_END()
/*! Alias for returning interface of type mfxMemoryInterface. */
#define MFXGetMemoryInterface(session, piface) MFXVideoCORE_GetHandle((session), MFX_HANDLE_MEMORY_INTERFACE, (mfxHDL *)(piface))
#endif
#ifdef __cplusplus
} // extern "C"
#endif
#endif
+105
View File
@@ -0,0 +1,105 @@
/*############################################################################
# Copyright Intel Corporation
#
# SPDX-License-Identifier: MIT
############################################################################*/
#ifndef __MFXMVC_H__
#define __MFXMVC_H__
#include "mfxdefs.h"
#ifdef __cplusplus
extern "C" {
#endif
/* CodecProfile, CodecLevel */
enum {
/* MVC profiles */
MFX_PROFILE_AVC_MULTIVIEW_HIGH = 118, /*!< Multi-view high profile. The encoding of VDEnc or LowPower ON is not supported. */
MFX_PROFILE_AVC_STEREO_HIGH = 128 /*!< Stereo high profile. The encoding of VDEnc or LowPower ON is not supported. */
};
/* Extended Buffer Ids */
enum {
MFX_EXTBUFF_MVC_SEQ_DESC = MFX_MAKEFOURCC('M','V','C','D'), /*!< This extended buffer describes the MVC stream information of view dependencies, view identifiers, and operation points. See the ITU*-T H.264 specification chapter H.7.3.2.1.4 for details. */
MFX_EXTBUFF_MVC_TARGET_VIEWS = MFX_MAKEFOURCC('M','V','C','T') /*!< This extended buffer defines target views at the decoder output.*/
};
MFX_PACK_BEGIN_USUAL_STRUCT()
/*!
Describes MVC view dependencies.
*/
typedef struct {
mfxU16 ViewId; /*!< View identifier of this dependency structure. */
mfxU16 NumAnchorRefsL0; /*!< Number of view components for inter-view prediction in the initial reference picture list RefPicList0 for anchor view components. */
mfxU16 NumAnchorRefsL1; /*!< Number of view components for inter-view prediction in the initial reference picture list RefPicList1 for anchor view components. */
mfxU16 AnchorRefL0[16]; /*!< View identifiers of the view components for inter-view prediction in the initial reference picture list RefPicList0 for anchor view components. */
mfxU16 AnchorRefL1[16]; /*!< View identifiers of the view components for inter-view prediction in the initial reference picture list RefPicList1 for anchor view components. */
mfxU16 NumNonAnchorRefsL0; /*!< Number of view components for inter-view prediction in the initial reference picture list RefPicList0 for non-anchor view components. */
mfxU16 NumNonAnchorRefsL1; /*!< Number of view components for inter-view prediction in the initial reference picture list RefPicList1 for non-anchor view components. */
mfxU16 NonAnchorRefL0[16]; /*!< View identifiers of the view components for inter-view prediction in the initial reference picture list RefPicList0 for non-anchor view components. */
mfxU16 NonAnchorRefL1[16]; /*!< View identifiers of the view components for inter-view prediction in the initial reference picture list RefPicList0 for non-anchor view components. */
} mfxMVCViewDependency;
MFX_PACK_END()
MFX_PACK_BEGIN_STRUCT_W_PTR()
/*!
Describes the MVC operation point.
*/
typedef struct {
mfxU16 TemporalId; /*!< Temporal identifier of the operation point. */
mfxU16 LevelIdc; /*!< Level value signaled for the operation point. */
mfxU16 NumViews; /*!< Number of views required for decoding the target output views that correspond to the operation point. */
mfxU16 NumTargetViews; /*!< Number of target output views for the operation point. */
mfxU16 *TargetViewId; /*!< Target output view identifiers for operation point. */
} mfxMVCOperationPoint;
MFX_PACK_END()
MFX_PACK_BEGIN_STRUCT_W_PTR()
/*!
Describes the MVC stream information of view dependencies, view identifiers, and operation points. See the ITU*-T H.264 specification chapter H.7.3.2.1.4 for details.
*/
typedef struct {
mfxExtBuffer Header; /*!< Extension buffer header. Header.BufferId must be equal to MFX_EXTBUFF_MVC_SEQUENCE_DESCRIPTION. */
mfxU32 NumView; /*!< Number of views. */
mfxU32 NumViewAlloc; /*!< The allocated view dependency array size. */
mfxMVCViewDependency *View; /*!< Pointer to a list of the mfxMVCViewDependency. */
mfxU32 NumViewId; /*!< Number of view identifiers. */
mfxU32 NumViewIdAlloc; /*!< The allocated view identifier array size. */
mfxU16 *ViewId; /*!< Pointer to the list of view identifier. */
mfxU32 NumOP; /*!< Number of operation points. */
mfxU32 NumOPAlloc; /*!< The allocated operation point array size. */
mfxMVCOperationPoint *OP; /*!< Pointer to a list of the mfxMVCOperationPoint structure. */
mfxU16 NumRefsTotal; /*!< Total number of reference frames in all views required to decode the stream. This value is returned from the MFXVideoDECODE_Decodeheader function. Do not modify this value. */
mfxU32 Reserved[16];
} mfxExtMVCSeqDesc;
MFX_PACK_END()
MFX_PACK_BEGIN_USUAL_STRUCT()
/*!
Configures views for the decoding output.
*/
typedef struct {
mfxExtBuffer Header; /*!< Extension buffer header. Header.BufferId must be equal to MFX_EXTBUFF_MVC_TARGET_VIEWS. */
mfxU16 TemporalId; /*!< The temporal identifier to be decoded. */
mfxU32 NumView; /*!< The number of views to be decoded. */
mfxU16 ViewId[1024]; /*!< List of view identifiers to be decoded. */
} mfxExtMVCTargetViews ;
MFX_PACK_END()
#ifdef __cplusplus
} // extern "C"
#endif
#endif
+45
View File
@@ -0,0 +1,45 @@
/*############################################################################
# Copyright Intel Corporation
#
# SPDX-License-Identifier: MIT
############################################################################*/
#ifndef __MFXPCP_H__
#define __MFXPCP_H__
#include "mfxstructures.h"
#ifdef __cplusplus
extern "C"
{
#endif /* __cplusplus */
/*! The Protected enumerator describes the protection schemes. */
enum {
MFX_PROTECTION_CENC_WV_CLASSIC = 0x0004, /*!< The protection scheme is based on the Widevine* DRM from Google*. */
MFX_PROTECTION_CENC_WV_GOOGLE_DASH = 0x0005, /*!< The protection scheme is based on the Widevine* Modular DRM* from Google*. */
};
/* Extended Buffer Ids */
enum {
MFX_EXTBUFF_CENC_PARAM = MFX_MAKEFOURCC('C','E','N','P') /*!< This structure is used to pass decryption status report index for Common
Encryption usage model. See the mfxExtCencParam structure for more details. */
};
MFX_PACK_BEGIN_USUAL_STRUCT()
/*!
Used to pass the decryption status report index for the Common Encryption usage model. The application can
attach this extended buffer to the mfxBitstream structure at runtime.
*/
typedef struct _mfxExtCencParam{
mfxExtBuffer Header; /*!< Extension buffer header. Header.BufferId must be equal to MFX_EXTBUFF_CENC_PARAM. */
mfxU32 StatusReportIndex; /*!< Decryption status report index. */
mfxU32 reserved[15];
} mfxExtCencParam;
MFX_PACK_END()
#ifdef __cplusplus
} // extern "C"
#endif /* __cplusplus */
#endif
+234
View File
@@ -0,0 +1,234 @@
/*############################################################################
# Copyright Intel Corporation
#
# SPDX-License-Identifier: MIT
############################################################################*/
#ifndef __MFXSESSION_H__
#define __MFXSESSION_H__
#include "mfxcommon.h"
#ifdef __cplusplus
extern "C"
{
#endif /* __cplusplus */
/* Global Functions */
/*! Session handle. */
typedef struct _mfxSession *mfxSession;
/*!
@brief
Creates and initializes a session in the legacy mode for compatibility with Intel(r) Media SDK applications.
This function is deprecated starting from API version 2.0, applications must use MFXLoad with mfxCreateSession
to select the implementation and initialize the session.
Call this function before calling
any other API function. If the desired implementation specified by ``impl`` is MFX_IMPL_AUTO,
the function will search for the platform-specific implementation.
If the function cannot find the platform-specific implementation, it will use the software implementation instead.
The ``ver`` argument indicates the desired version of the library implementation.
The loaded implementation will have an API version compatible to the specified version (equal in
the major version number, and no less in the minor version number.) If the desired version
is not specified, the default is to use the API version from the library release with
which an application is built.
Production applications should always specify the minimum API version that meets the
functional requirements. For example, if an application uses only H.264 decoding as described
in API v1.0, the application should initialize the library with API v1.0. This ensures
backward compatibility.
@param[in] impl mfxIMPL enumerator that indicates the desired legacy Intel(r) Media SDK implementation.
@param[in] ver Pointer to the minimum library version or zero, if not specified.
@param[out] session Pointer to the legacy Intel(r) Media SDK session handle.
@return
MFX_ERR_NONE The function completed successfully. The output parameter contains the handle of the session.\n
MFX_ERR_UNSUPPORTED The function cannot find the desired legacy Intel(r) Media SDK implementation or version.
@since This function is available since API version 1.0.
@deprecated Deprecated in API version 2.3. Use MFXLoad and MFXCreateSession to initialize the session.
Use MFX_DEPRECATED_OFF macro to turn off the deprecation message visualization.
*/
MFX_DEPRECATED mfxStatus MFX_CDECL MFXInit(mfxIMPL impl, mfxVersion *ver, mfxSession *session);
/*!
@brief
Creates and initializes a session in the legacy mode for compatibility with Intel(r) Media SDK applications.
This function is deprecated starting from API version 2.0, applications must use MFXLoad with mfxCreateSession
to select the implementation and initialize the session.
Call this function before calling any other API functions.
If the desired implementation specified by ``par`` is MFX_IMPL_AUTO, the function will search for
the platform-specific implementation. If the function cannot find the platform-specific implementation, it will use the software implementation instead.
The argument ``par.Version`` indicates the desired version of the implementation. The loaded implementation will have an API
version compatible to the specified version (equal in the major version number, and no less in the minor version number.)
If the desired version is not specified, the default is to use the API version from the library release with
which an application is built.
Production applications should always specify the minimum API version that meets the functional requirements.
For example, if an application uses only H.264 decoding as described in API v1.0, the application should initialize the library with API v1.0. This ensures backward compatibility.
The argument ``par.ExternalThreads`` specifies threading mode. Value 0 means that the implementation should create and
handle work threads internally (this is essentially the equivalent of the regular MFXInit).
@param[in] par mfxInitParam structure that indicates the desired implementation, minimum library version and desired threading mode.
@param[out] session Pointer to the session handle.
@return
MFX_ERR_NONE The function completed successfully. The output parameter contains the handle of the session.\n
MFX_ERR_UNSUPPORTED The function cannot find the desired implementation or version.
@since This function is available since API version 1.14.
@deprecated Deprecated in API version 2.3. Use MFXLoad and MFXCreateSession to initialize the session.
Use MFX_DEPRECATED_OFF macro to turn off the deprecation message visualization.
*/
MFX_DEPRECATED mfxStatus MFX_CDECL MFXInitEx(mfxInitParam par, mfxSession *session);
/*!
@brief
Creates and initializes a session starting from API version 2.0. This function is used by the dispatcher.
The dispatcher creates and fills the mfxInitializationParam structure according to mfxConfig values set by an application.
Calling this function directly is not recommended. Instead, applications must call the MFXCreateSession function.
@param[in] par mfxInitializationParam structure that indicates the minimum library version and acceleration type.
@param[out] session Pointer to the session handle.
@return
MFX_ERR_NONE The function completed successfully. The output parameter contains the handle of the session.\n
MFX_ERR_UNSUPPORTED The function cannot find the desired implementation or version.
@since This function is available since API version 2.0.
*/
mfxStatus MFX_CDECL MFXInitialize(mfxInitializationParam par, mfxSession *session);
/*!
@brief Completes and deinitializes a session. Any active tasks in execution or
in queue are aborted. The application cannot call any API function after calling this function.
All child sessions must be disjoined before closing a parent session.
@param[in] session session handle.
@return MFX_ERR_NONE The function completed successfully.
@since This function is available since API version 1.0.
*/
mfxStatus MFX_CDECL MFXClose(mfxSession session);
/*!
@brief Returns the implementation type of a given session.
@param[in] session Session handle.
@param[out] impl Pointer to the implementation type
@return MFX_ERR_NONE The function completed successfully.
@since This function is available since API version 1.0.
*/
mfxStatus MFX_CDECL MFXQueryIMPL(mfxSession session, mfxIMPL *impl);
/*!
@brief Returns the implementation version.
@param[in] session Session handle.
@param[out] version Pointer to the returned implementation version.
@return MFX_ERR_NONE The function completed successfully.
@since This function is available since API version 1.0.
*/
mfxStatus MFX_CDECL MFXQueryVersion(mfxSession session, mfxVersion *version);
/*!
@brief Joins the child session to the current session.
After joining, the two sessions share thread and resource scheduling for asynchronous
operations. However, each session still maintains its own device manager and buffer/frame
allocator. Therefore, the application must use a compatible device manager and buffer/frame
allocator to share data between two joined sessions.
The application can join multiple sessions by calling this function multiple times. When joining
the first two sessions, the current session becomes the parent responsible for thread and
resource scheduling of any later joined sessions.
Joining of two parent sessions is not supported.
@param[in,out] session The current session handle.
@param[in] child The child session handle to be joined
@return MFX_ERR_NONE The function completed successfully. \n
MFX_WRN_IN_EXECUTION Active tasks are executing or in queue in one of the
sessions. Call this function again after all tasks are completed. \n
MFX_ERR_UNSUPPORTED The child session cannot be joined with the current session.
@since This function is available since API version 1.1.
*/
mfxStatus MFX_CDECL MFXJoinSession(mfxSession session, mfxSession child);
/*!
@brief Removes the joined state of the current session.
After disjoining, the current session becomes independent. The application must ensure there is no active task running in the session before calling this API function.
@param[in,out] session The current session handle.
@return MFX_ERR_NONE The function completed successfully. \n
MFX_WRN_IN_EXECUTION Active tasks are executing or in queue in one of the
sessions. Call this function again after all tasks are completed. \n
MFX_ERR_UNDEFINED_BEHAVIOR The session is independent, or this session is the parent of all joined sessions.
@since This function is available since API version 1.1.
*/
mfxStatus MFX_CDECL MFXDisjoinSession(mfxSession session);
/*!
@brief Creates a clean copy of the current session.
The cloned session is an independent session and does not inherit any user-defined buffer, frame allocator, or device manager handles from the current session.
This function is a light-weight equivalent of MFXJoinSession after MFXInit.
@param[in] session The current session handle.
@param[out] clone Pointer to the cloned session handle.
@return MFX_ERR_NONE The function completed successfully.
@since This function is available since API version 1.1.
*/
mfxStatus MFX_CDECL MFXCloneSession(mfxSession session, mfxSession *clone);
/*!
@brief Sets the current session priority.
@param[in] session The current session handle.
@param[in] priority Priority value.
@return MFX_ERR_NONE The function completed successfully.
@since This function is available since API version 1.1.
*/
mfxStatus MFX_CDECL MFXSetPriority(mfxSession session, mfxPriority priority);
/*!
@brief Returns the current session priority.
@param[in] session The current session handle.
@param[out] priority Pointer to the priority value.
@return MFX_ERR_NONE The function completed successfully.
@since This function is available since API version 1.1.
*/
mfxStatus MFX_CDECL MFXGetPriority(mfxSession session, mfxPriority *priority);
#ifdef __cplusplus
}
#endif /* __cplusplus */
#endif
File diff suppressed because it is too large Load Diff
+184
View File
@@ -0,0 +1,184 @@
/*############################################################################
# Copyright Intel Corporation
#
# SPDX-License-Identifier: MIT
############################################################################*/
#ifndef __MFX_SURFACE_POOL_H__
#define __MFX_SURFACE_POOL_H__
#include "mfxstructures.h"
/*! GUID to obtain mfxSurfacePoolInterface. */
static const mfxGUID MFX_GUID_SURFACE_POOL = {{0x35, 0x24, 0xf3, 0xda, 0x96, 0x4e, 0x47, 0xf1, 0xaf, 0xb4, 0xec, 0xb1, 0x15, 0x08, 0x06, 0xb1}};
/*! Specifies type of pool for VPP component. */
typedef enum {
MFX_VPP_POOL_IN = 0, /*!< Input pool. */
MFX_VPP_POOL_OUT = 1 /*!< Output pool. */
} mfxVPPPoolType;
MFX_PACK_BEGIN_USUAL_STRUCT()
/*! The extension buffer specifies surface pool management policy.
Absence of the attached buffer means MFX_ALLOCATION_UNLIMITED policy:
each call of GetSurfaceForXXX leads to surface allocation.
*/
typedef struct {
mfxExtBuffer Header; /*!< Extension buffer header. Header.BufferId must be equal to MFX_EXTBUFF_ALLOCATION_HINTS. */
mfxPoolAllocationPolicy AllocationPolicy; /*!< Allocation policy. */
/*! How many surfaces to allocate during Init.
It's applicable for any polices set by mfxPoolAllocationPolicy::AllocationPolicy
even if the requested number exceeds recommended size of the pool. */
mfxU32 NumberToPreAllocate;
/*! DeltaToAllocateOnTheFly specifies how many surfaces are allocated
in addition to NumberToPreAllocate in MFX_ALLOCATION_LIMITED mode.
Maximum number of allocated frames will be
NumberToPreAllocate + DeltaToAllocateOnTheFly.
*/
mfxU32 DeltaToAllocateOnTheFly;
union {
mfxVPPPoolType VPPPoolType; /*!< Defines what VPP pool is targeted - input or output. Ignored for other components. */
mfxU32 reserved;
};
mfxU32 Wait; /*!< Time in milliseconds for GetSurfaceForXXX() and DecodeFrameAsync functions to wait until surface will be available. */
mfxU32 reserved1[4]; /*!< Reserved for future use */
} mfxExtAllocationHints;
MFX_PACK_END()
MFX_PACK_BEGIN_STRUCT_W_PTR()
/*! Specifies the surface pool interface. */
typedef struct mfxSurfacePoolInterface
{
mfxHDL Context; /*!< The context of the surface pool interface. User should not touch (change, set, null) this pointer. */
/*! @brief
Increments the internal reference counter of the mfxSurfacePoolInterface. The mfxSurfacePoolInterface is not destroyed until the
mfxSurfacePoolInterface is destroyed with mfxSurfacePoolInterface::Release function. mfxSurfacePoolInterface::AddRef should be used each time a new link to the
mfxSurfacePoolInterface is created for proper management.
@param[in] pool Valid pool.
@return
MFX_ERR_NONE If no error. \n
MFX_ERR_NULL_PTR If pool is NULL. \n
MFX_ERR_INVALID_HANDLE If mfxSurfacePoolInterface->Context is invalid (for example NULL). \n
MFX_ERR_UNKNOWN Any internal error.
*/
mfxStatus (MFX_CDECL *AddRef)(struct mfxSurfacePoolInterface *pool);
/*! @brief
Decrements the internal reference counter of the mfxSurfacePoolInterface. mfxSurfacePoolInterface::Release
should be called after using the mfxSurfacePoolInterface::AddRef function to add a mfxSurfacePoolInterface or when allocation logic requires it.
For example, call mfxSurfacePoolInterface::Release to release a mfxSurfacePoolInterface obtained with
the mfxFrameSurfaceInterface::QueryInterface function.
@param[in] pool Valid pool.
@return
MFX_ERR_NONE If no error. \n
MFX_ERR_NULL_PTR If pool is NULL. \n
MFX_ERR_INVALID_HANDLE If mfxSurfacePoolInterface->Context is invalid (for example NULL). \n
MFX_ERR_UNDEFINED_BEHAVIOR If Reference Counter of mfxSurfacePoolInterface is zero before call. \n
MFX_ERR_UNKNOWN Any internal error.
*/
mfxStatus (MFX_CDECL *Release)(struct mfxSurfacePoolInterface *pool);
/*! @brief
Returns current reference counter of mfxSurfacePoolInterface structure.
@param[in] pool Valid pool.
@param[out] counter Sets counter to the current reference counter value.
@return
MFX_ERR_NONE If no error. \n
MFX_ERR_NULL_PTR If pool or counter is NULL. \n
MFX_ERR_INVALID_HANDLE If mfxSurfacePoolInterface->Context is invalid (for example NULL). \n
MFX_ERR_UNKNOWN Any internal error.
*/
mfxStatus (MFX_CDECL *GetRefCounter)(struct mfxSurfacePoolInterface *pool, mfxU32* counter);
/*! @brief
The function should be called by oneAPI Video Processing Library (oneVPL) components or application to specify how many surfaces
it will use concurrently.
Internally, oneVPL allocates surfaces in the shared pool according to the component's policy set by mfxPoolAllocationPolicy.
The exact moment of surfaces allocation is defined by the component and generally independent from that call.
@param[in] pool Valid pool.
@param[in] num_surfaces The number of surfaces required by the component.
@return
MFX_ERR_NONE If no error. \n
MFX_ERR_NULL_PTR If pool is NULL. \n
MFX_ERR_INVALID_HANDLE If mfxSurfacePoolInterface->Context is invalid (for example NULL). \n
MFX_WRN_INCOMPATIBLE_VIDEO_PARAM If pool has MFX_ALLOCATION_UNLIMITED or MFX_ALLOCATION_LIMITED policy. \n
MFX_ERR_UNKNOWN Any internal error.
*/
mfxStatus (MFX_CDECL *SetNumSurfaces)(struct mfxSurfacePoolInterface *pool, mfxU32 num_surfaces);
/*! @brief
The function should be called by oneVPL components when component is closed or reset and doesn't need to use pool more. It helps
to manage memory accordingly and release redundant memory. Important to specify the same number of surfaces which is requested
during SetNumSurfaces call, otherwise it may lead to the pipeline stalls.
@param[in] pool Valid pool.
@param[in] num_surfaces The number of surfaces used by the component.
@return
MFX_ERR_NONE If no error. \n
MFX_WRN_OUT_OF_RANGE If num_surfaces doesn't equal to num_surfaces requested during SetNumSurfaces call. \n
MFX_ERR_NULL_PTR If pool is NULL. \n
MFX_ERR_INVALID_HANDLE If mfxSurfacePoolInterface->Context is invalid (for example NULL). \n
MFX_WRN_INCOMPATIBLE_VIDEO_PARAM If pool has MFX_ALLOCATION_UNLIMITED or MFX_ALLOCATION_LIMITED policy. \n
MFX_ERR_UNKNOWN Any internal error.
*/
mfxStatus (MFX_CDECL *RevokeSurfaces)(struct mfxSurfacePoolInterface *pool, mfxU32 num_surfaces);
/*! @brief
Returns current allocation policy.
@param[in] pool Valid pool.
@param[out] policy Sets policy to the current allocation policy value.
@return
MFX_ERR_NONE If no error. \n
MFX_ERR_NULL_PTR If pool or policy is NULL. \n
MFX_ERR_INVALID_HANDLE If mfxSurfacePoolInterface->Context is invalid (for example NULL). \n
MFX_ERR_UNKNOWN Any internal error.
*/
mfxStatus (MFX_CDECL *GetAllocationPolicy)(struct mfxSurfacePoolInterface *pool, mfxPoolAllocationPolicy *policy);
/*! @brief
Returns maximum pool size. In case of mfxPoolAllocationPolicy::MFX_ALLOCATION_UNLIMITED policy 0xFFFFFFFF will be returned.
@param[in] pool Valid pool.
@param[out] size Sets size to the maximum pool size value.
@return
MFX_ERR_NONE If no error. \n
MFX_ERR_NULL_PTR If pool or size is NULL. \n
MFX_ERR_INVALID_HANDLE If mfxSurfacePoolInterface->Context is invalid (for example NULL). \n
MFX_ERR_UNKNOWN Any internal error.
*/
mfxStatus (MFX_CDECL *GetMaximumPoolSize)(struct mfxSurfacePoolInterface *pool, mfxU32 *size);
/*! @brief
Returns current pool size.
@param[in] pool Valid pool.
@param[out] size Sets size to the current pool size value.
@return
MFX_ERR_NONE If no error. \n
MFX_ERR_NULL_PTR If pool or size is NULL. \n
MFX_ERR_INVALID_HANDLE If mfxSurfacePoolInterface->Context is invalid (for example NULL). \n
MFX_ERR_UNKNOWN Any internal error.
*/
mfxStatus (MFX_CDECL *GetCurrentPoolSize)(struct mfxSurfacePoolInterface *pool, mfxU32 *size);
mfxHDL reserved[4]; /*!< Reserved for future use. */
} mfxSurfacePoolInterface;
MFX_PACK_END()
#endif /* __MFX_SURFACE_POOL_H__ */
+594
View File
@@ -0,0 +1,594 @@
/*###########################################################################
# Copyright Intel Corporation
#
# SPDX-License-Identifier: MIT
###########################################################################*/
#ifndef __MFXVIDEOPLUSPLUS_H
#define __MFXVIDEOPLUSPLUS_H
#ifdef MFXVIDEO_CPP_ENABLE_MFXLOAD
#include "vpl/mfx.h"
#define MFX_IMPL_ACCELMODE(x) (0xff00 & (x))
#else
#include "mfxvideo.h"
#endif
class MFXVideoSessionBase {
public:
virtual ~MFXVideoSessionBase() {}
virtual mfxStatus Init(mfxIMPL impl, mfxVersion* ver) = 0;
virtual mfxStatus InitEx(mfxInitParam par) = 0;
virtual mfxStatus Close(void) = 0;
virtual mfxStatus QueryIMPL(mfxIMPL* impl) = 0;
virtual mfxStatus QueryVersion(mfxVersion* version) = 0;
virtual mfxStatus JoinSession(mfxSession child_session) = 0;
virtual mfxStatus DisjoinSession() = 0;
virtual mfxStatus CloneSession(mfxSession* clone) = 0;
virtual mfxStatus SetPriority(mfxPriority priority) = 0;
virtual mfxStatus GetPriority(mfxPriority* priority) = 0;
virtual mfxStatus SetFrameAllocator(mfxFrameAllocator* allocator) = 0;
virtual mfxStatus SetHandle(mfxHandleType type, mfxHDL hdl) = 0;
virtual mfxStatus GetHandle(mfxHandleType type, mfxHDL* hdl) = 0;
virtual mfxStatus QueryPlatform(mfxPlatform* platform) = 0;
virtual mfxStatus SyncOperation(mfxSyncPoint syncp, mfxU32 wait) = 0;
virtual mfxStatus GetSurfaceForEncode(mfxFrameSurface1** output_surf) = 0;
virtual mfxStatus GetSurfaceForDecode(mfxFrameSurface1** output_surf) = 0;
virtual mfxStatus GetSurfaceForVPP(mfxFrameSurface1** output_surf) = 0;
virtual mfxStatus GetSurfaceForVPPOut(mfxFrameSurface1** output_surf) = 0;
virtual operator mfxSession(void) = 0;
};
class MFXVideoENCODEBase {
public:
virtual ~MFXVideoENCODEBase() {}
virtual mfxStatus Query(mfxVideoParam* in, mfxVideoParam* out) = 0;
virtual mfxStatus QueryIOSurf(mfxVideoParam* par, mfxFrameAllocRequest* request) = 0;
virtual mfxStatus Init(mfxVideoParam* par) = 0;
virtual mfxStatus Reset(mfxVideoParam* par) = 0;
virtual mfxStatus Close(void) = 0;
virtual mfxStatus GetVideoParam(mfxVideoParam* par) = 0;
virtual mfxStatus GetEncodeStat(mfxEncodeStat* stat) = 0;
virtual mfxStatus EncodeFrameAsync(mfxEncodeCtrl* ctrl,
mfxFrameSurface1* surface,
mfxBitstream* bs,
mfxSyncPoint* syncp) = 0;
virtual mfxStatus GetSurface(mfxFrameSurface1** output_surf) = 0;
};
class MFXVideoDECODEBase {
public:
virtual ~MFXVideoDECODEBase() {}
virtual mfxStatus Query(mfxVideoParam* in, mfxVideoParam* out) = 0;
virtual mfxStatus DecodeHeader(mfxBitstream* bs, mfxVideoParam* par) = 0;
virtual mfxStatus QueryIOSurf(mfxVideoParam* par, mfxFrameAllocRequest* request) = 0;
virtual mfxStatus Init(mfxVideoParam* par) = 0;
virtual mfxStatus Reset(mfxVideoParam* par) = 0;
virtual mfxStatus Close(void) = 0;
virtual mfxStatus GetVideoParam(mfxVideoParam* par) = 0;
virtual mfxStatus GetDecodeStat(mfxDecodeStat* stat) = 0;
virtual mfxStatus GetPayload(mfxU64* ts, mfxPayload* payload) = 0;
virtual mfxStatus SetSkipMode(mfxSkipMode mode) = 0;
virtual mfxStatus DecodeFrameAsync(mfxBitstream* bs,
mfxFrameSurface1* surface_work,
mfxFrameSurface1** surface_out,
mfxSyncPoint* syncp) = 0;
virtual mfxStatus GetSurface(mfxFrameSurface1** output_surf) = 0;
};
class MFXVideoVPPBase {
public:
virtual ~MFXVideoVPPBase() {}
virtual mfxStatus Query(mfxVideoParam* in, mfxVideoParam* out) = 0;
virtual mfxStatus QueryIOSurf(mfxVideoParam* par, mfxFrameAllocRequest request[2]) = 0;
virtual mfxStatus Init(mfxVideoParam* par) = 0;
virtual mfxStatus Reset(mfxVideoParam* par) = 0;
virtual mfxStatus Close(void) = 0;
virtual mfxStatus GetVideoParam(mfxVideoParam* par) = 0;
virtual mfxStatus GetVPPStat(mfxVPPStat* stat) = 0;
virtual mfxStatus RunFrameVPPAsync(mfxFrameSurface1* in,
mfxFrameSurface1* out,
mfxExtVppAuxData* aux,
mfxSyncPoint* syncp) = 0;
virtual mfxStatus GetSurfaceIn(mfxFrameSurface1** output_surf) = 0;
virtual mfxStatus GetSurfaceOut(mfxFrameSurface1** output_surf) = 0;
virtual mfxStatus ProcessFrameAsync(mfxFrameSurface1* in, mfxFrameSurface1** out) = 0;
};
class MFXVideoSession : public MFXVideoSessionBase {
public:
MFXVideoSession(void) {
m_session = (mfxSession)0;
#ifdef MFXVIDEO_CPP_ENABLE_MFXLOAD
m_loader = (mfxLoader)0;
#endif
}
virtual ~MFXVideoSession(void) {
Close();
}
virtual mfxStatus Init(mfxIMPL impl, mfxVersion *ver) override {
#ifdef MFXVIDEO_CPP_ENABLE_MFXLOAD
mfxInitParam par = {};
par.Implementation = impl;
par.Version = *ver;
return InitSession(par);
#else
return MFXInit(impl, ver, &m_session);
#endif
}
virtual mfxStatus InitEx(mfxInitParam par) override {
#ifdef MFXVIDEO_CPP_ENABLE_MFXLOAD
return InitSession(par);
#else
return MFXInitEx(par, &m_session);
#endif
}
virtual mfxStatus Close(void) override {
#ifdef MFXVIDEO_CPP_ENABLE_MFXLOAD
if (m_session) {
mfxStatus mfxRes;
mfxRes = MFXClose(m_session);
m_session = (mfxSession)0;
if (m_loader) {
MFXUnload(m_loader);
m_loader = (mfxLoader)0;
}
return mfxRes;
}
else {
return MFX_ERR_NONE;
}
#else
mfxStatus mfxRes;
mfxRes = MFXClose(m_session);
m_session = (mfxSession)0;
return mfxRes;
#endif
}
virtual mfxStatus QueryIMPL(mfxIMPL *impl) override {
return MFXQueryIMPL(m_session, impl);
}
virtual mfxStatus QueryVersion(mfxVersion *version) override {
return MFXQueryVersion(m_session, version);
}
virtual mfxStatus JoinSession(mfxSession child_session) override {
return MFXJoinSession(m_session, child_session);
}
virtual mfxStatus DisjoinSession() override {
return MFXDisjoinSession(m_session);
}
virtual mfxStatus CloneSession(mfxSession *clone) override {
return MFXCloneSession(m_session, clone);
}
virtual mfxStatus SetPriority(mfxPriority priority) override {
return MFXSetPriority(m_session, priority);
}
virtual mfxStatus GetPriority(mfxPriority *priority) override {
return MFXGetPriority(m_session, priority);
}
virtual mfxStatus SetFrameAllocator(mfxFrameAllocator *allocator) override {
return MFXVideoCORE_SetFrameAllocator(m_session, allocator);
}
virtual mfxStatus SetHandle(mfxHandleType type, mfxHDL hdl) override {
return MFXVideoCORE_SetHandle(m_session, type, hdl);
}
virtual mfxStatus GetHandle(mfxHandleType type, mfxHDL *hdl) override {
return MFXVideoCORE_GetHandle(m_session, type, hdl);
}
virtual mfxStatus QueryPlatform(mfxPlatform *platform) override {
return MFXVideoCORE_QueryPlatform(m_session, platform);
}
virtual mfxStatus SyncOperation(mfxSyncPoint syncp, mfxU32 wait) override {
return MFXVideoCORE_SyncOperation(m_session, syncp, wait);
}
virtual mfxStatus GetSurfaceForEncode(mfxFrameSurface1** output_surf) override {
return MFXMemory_GetSurfaceForEncode(m_session, output_surf);
}
virtual mfxStatus GetSurfaceForDecode(mfxFrameSurface1** output_surf) override {
return MFXMemory_GetSurfaceForDecode(m_session, output_surf);
}
virtual mfxStatus GetSurfaceForVPP (mfxFrameSurface1** output_surf) override {
return MFXMemory_GetSurfaceForVPP (m_session, output_surf);
}
virtual mfxStatus GetSurfaceForVPPOut(mfxFrameSurface1** output_surf) override {
return MFXMemory_GetSurfaceForVPPOut(m_session, output_surf);
}
virtual operator mfxSession(void) override {
return m_session;
}
protected:
mfxSession m_session; // (mfxSession) handle to the owning session
#ifdef MFXVIDEO_CPP_ENABLE_MFXLOAD
mfxLoader m_loader;
inline void InitVariant(mfxVariant *var, mfxU32 data) {
var->Version.Version = (mfxU16)MFX_VARIANT_VERSION;
var->Type = MFX_VARIANT_TYPE_U32;
var->Data.U32 = data;
}
inline void InitVariant(mfxVariant *var, mfxU16 data) {
var->Version.Version = (mfxU16)MFX_VARIANT_VERSION;
var->Type = MFX_VARIANT_TYPE_U16;
var->Data.U16 = data;
}
inline void InitVariant(mfxVariant *var, mfxHDL data) {
var->Version.Version = (mfxU16)MFX_VARIANT_VERSION;
var->Type = MFX_VARIANT_TYPE_PTR;
var->Data.Ptr = data;
}
template <typename varDataType>
mfxStatus CreateConfig(varDataType data, const char *propertyName) {
mfxConfig cfg = MFXCreateConfig(m_loader);
if (cfg == nullptr)
return MFX_ERR_NULL_PTR;
mfxVariant variant;
InitVariant(&variant, data);
return MFXSetConfigFilterProperty(cfg, (mfxU8 *)propertyName, variant);
}
mfxStatus InitSession(mfxInitParam par) {
// already initialized
if (m_session)
return MFX_ERR_NONE;
m_loader = MFXLoad();
if (!m_loader)
return MFX_ERR_NOT_FOUND;
mfxStatus mfxRes = MFX_ERR_NONE;
mfxU32 implBaseType = MFX_IMPL_BASETYPE(par.Implementation);
// select implementation type
switch (implBaseType) {
case MFX_IMPL_AUTO:
case MFX_IMPL_AUTO_ANY:
break;
case MFX_IMPL_SOFTWARE:
mfxRes = CreateConfig<mfxU32>(MFX_IMPL_TYPE_SOFTWARE, "mfxImplDescription.Impl");
break;
case MFX_IMPL_HARDWARE:
case MFX_IMPL_HARDWARE_ANY:
case MFX_IMPL_HARDWARE2:
case MFX_IMPL_HARDWARE3:
case MFX_IMPL_HARDWARE4:
mfxRes = CreateConfig<mfxU32>(MFX_IMPL_TYPE_HARDWARE, "mfxImplDescription.Impl");
break;
default:
mfxRes = MFX_ERR_UNSUPPORTED;
break;
}
if (MFX_ERR_NONE != mfxRes)
return MFX_ERR_UNSUPPORTED;
// select adapter index (if specified)
// see notes below about how VendorImplID is interpreted for each acceleration mode
switch (implBaseType) {
case MFX_IMPL_HARDWARE:
mfxRes = CreateConfig<mfxU32>(0, "mfxImplDescription.VendorImplID");
break;
case MFX_IMPL_HARDWARE2:
mfxRes = CreateConfig<mfxU32>(1, "mfxImplDescription.VendorImplID");
break;
case MFX_IMPL_HARDWARE3:
mfxRes = CreateConfig<mfxU32>(2, "mfxImplDescription.VendorImplID");
break;
case MFX_IMPL_HARDWARE4:
mfxRes = CreateConfig<mfxU32>(3, "mfxImplDescription.VendorImplID");
break;
}
if (MFX_ERR_NONE != mfxRes)
return MFX_ERR_UNSUPPORTED;
mfxU32 implAccelMode = MFX_IMPL_ACCELMODE(par.Implementation);
if (implAccelMode == MFX_IMPL_VIA_D3D9) {
// D3D9 - because VendorImplID corresponds to DXGI adapter index (DX11 enumeration),
// this may not map directly to D3D9 index in multi-adapter/multi-monitor configurations
mfxRes = CreateConfig<mfxU32>(MFX_ACCEL_MODE_VIA_D3D9,
"mfxImplDescription.AccelerationMode");
}
else if (implAccelMode == MFX_IMPL_VIA_D3D11) {
// D3D11 - VendorImplID corresponds to DXGI adapter index
mfxRes = CreateConfig<mfxU32>(MFX_ACCEL_MODE_VIA_D3D11,
"mfxImplDescription.AccelerationMode");
}
else if (implAccelMode == MFX_IMPL_VIA_VAAPI) {
// VAAPI - in general MFXInitEx treats any HARDWAREn the same way (relies on application to pass
// correct VADisplay via SetHandle), but 2.x RT only reports actual number of adapters
mfxRes = CreateConfig<mfxU32>(MFX_ACCEL_MODE_VIA_VAAPI,
"mfxImplDescription.AccelerationMode");
}
if (MFX_ERR_NONE != mfxRes)
return MFX_ERR_UNSUPPORTED;
// set required API level
mfxRes =
CreateConfig<mfxU32>(par.Version.Version, "mfxImplDescription.ApiVersion.Version");
if (MFX_ERR_NONE != mfxRes)
return MFX_ERR_UNSUPPORTED;
// set GPUCopy parameter
if (par.GPUCopy) {
mfxRes = CreateConfig<mfxU16>(par.GPUCopy, "DeviceCopy");
if (MFX_ERR_NONE != mfxRes)
return MFX_ERR_UNSUPPORTED;
}
// ExternalThreads was deprecated in API 2.x along with MFXDoWork()
if (par.ExternalThreads) {
return MFX_ERR_UNSUPPORTED;
}
// pass extBufs
if (par.NumExtParam) {
for (mfxU32 idx = 0; idx < par.NumExtParam; idx++) {
mfxRes = CreateConfig<mfxHDL>(par.ExtParam[idx], "ExtBuffer");
if (MFX_ERR_NONE != mfxRes)
return MFX_ERR_UNSUPPORTED;
}
}
// create session with highest priority implementation remaining after filters
mfxRes = MFXCreateSession(m_loader, 0, &m_session);
return mfxRes;
}
#endif
private:
MFXVideoSession(const MFXVideoSession &);
void operator=(MFXVideoSession &);
};
class MFXVideoENCODE : public MFXVideoENCODEBase {
public:
explicit MFXVideoENCODE(mfxSession session) {
m_session = session;
}
virtual ~MFXVideoENCODE(void) {
Close();
}
virtual mfxStatus Query(mfxVideoParam *in, mfxVideoParam *out) override {
return MFXVideoENCODE_Query(m_session, in, out);
}
virtual mfxStatus QueryIOSurf(mfxVideoParam *par, mfxFrameAllocRequest *request) override {
return MFXVideoENCODE_QueryIOSurf(m_session, par, request);
}
virtual mfxStatus Init(mfxVideoParam *par) override {
return MFXVideoENCODE_Init(m_session, par);
}
virtual mfxStatus Reset(mfxVideoParam *par) override {
return MFXVideoENCODE_Reset(m_session, par);
}
virtual mfxStatus Close(void) override {
return MFXVideoENCODE_Close(m_session);
}
virtual mfxStatus GetVideoParam(mfxVideoParam *par) override {
return MFXVideoENCODE_GetVideoParam(m_session, par);
}
virtual mfxStatus GetEncodeStat(mfxEncodeStat *stat) override {
return MFXVideoENCODE_GetEncodeStat(m_session, stat);
}
virtual mfxStatus EncodeFrameAsync(mfxEncodeCtrl *ctrl,
mfxFrameSurface1 *surface,
mfxBitstream *bs,
mfxSyncPoint *syncp) override {
return MFXVideoENCODE_EncodeFrameAsync(m_session, ctrl, surface, bs, syncp);
}
virtual mfxStatus GetSurface(mfxFrameSurface1** output_surf) override {
return MFXMemory_GetSurfaceForEncode(m_session, output_surf);
}
protected:
mfxSession m_session; // (mfxSession) handle to the owning session
private:
MFXVideoENCODE(const MFXVideoENCODE& other);
MFXVideoENCODE& operator=(const MFXVideoENCODE& other);
};
class MFXVideoDECODE : public MFXVideoDECODEBase {
public:
explicit MFXVideoDECODE(mfxSession session) {
m_session = session;
}
virtual ~MFXVideoDECODE(void) {
Close();
}
virtual mfxStatus Query(mfxVideoParam *in, mfxVideoParam *out) override {
return MFXVideoDECODE_Query(m_session, in, out);
}
virtual mfxStatus DecodeHeader(mfxBitstream *bs, mfxVideoParam *par) override {
return MFXVideoDECODE_DecodeHeader(m_session, bs, par);
}
virtual mfxStatus QueryIOSurf(mfxVideoParam *par, mfxFrameAllocRequest *request) override {
return MFXVideoDECODE_QueryIOSurf(m_session, par, request);
}
virtual mfxStatus Init(mfxVideoParam *par) override {
return MFXVideoDECODE_Init(m_session, par);
}
virtual mfxStatus Reset(mfxVideoParam *par) override {
return MFXVideoDECODE_Reset(m_session, par);
}
virtual mfxStatus Close(void) override {
return MFXVideoDECODE_Close(m_session);
}
virtual mfxStatus GetVideoParam(mfxVideoParam *par) override {
return MFXVideoDECODE_GetVideoParam(m_session, par);
}
virtual mfxStatus GetDecodeStat(mfxDecodeStat *stat) override {
return MFXVideoDECODE_GetDecodeStat(m_session, stat);
}
virtual mfxStatus GetPayload(mfxU64 *ts, mfxPayload *payload) override {
return MFXVideoDECODE_GetPayload(m_session, ts, payload);
}
virtual mfxStatus SetSkipMode(mfxSkipMode mode) override {
return MFXVideoDECODE_SetSkipMode(m_session, mode);
}
virtual mfxStatus DecodeFrameAsync(mfxBitstream *bs,
mfxFrameSurface1 *surface_work,
mfxFrameSurface1 **surface_out,
mfxSyncPoint *syncp) override {
return MFXVideoDECODE_DecodeFrameAsync(m_session, bs, surface_work, surface_out, syncp);
}
virtual mfxStatus GetSurface(mfxFrameSurface1** output_surf) override {
return MFXMemory_GetSurfaceForDecode(m_session, output_surf);
}
protected:
mfxSession m_session; // (mfxSession) handle to the owning session
private:
MFXVideoDECODE(const MFXVideoDECODE& other);
MFXVideoDECODE& operator=(const MFXVideoDECODE& other);
};
class MFXVideoVPP : public MFXVideoVPPBase {
public:
explicit MFXVideoVPP(mfxSession session) {
m_session = session;
}
virtual ~MFXVideoVPP(void) {
Close();
}
virtual mfxStatus Query(mfxVideoParam *in, mfxVideoParam *out) override {
return MFXVideoVPP_Query(m_session, in, out);
}
virtual mfxStatus QueryIOSurf(mfxVideoParam *par, mfxFrameAllocRequest request[2]) override {
return MFXVideoVPP_QueryIOSurf(m_session, par, request);
}
virtual mfxStatus Init(mfxVideoParam *par) override {
return MFXVideoVPP_Init(m_session, par);
}
virtual mfxStatus Reset(mfxVideoParam *par) override {
return MFXVideoVPP_Reset(m_session, par);
}
virtual mfxStatus Close(void) override {
return MFXVideoVPP_Close(m_session);
}
virtual mfxStatus GetVideoParam(mfxVideoParam *par) override {
return MFXVideoVPP_GetVideoParam(m_session, par);
}
virtual mfxStatus GetVPPStat(mfxVPPStat *stat) override {
return MFXVideoVPP_GetVPPStat(m_session, stat);
}
virtual mfxStatus RunFrameVPPAsync(mfxFrameSurface1 *in,
mfxFrameSurface1 *out,
mfxExtVppAuxData *aux,
mfxSyncPoint *syncp) override {
return MFXVideoVPP_RunFrameVPPAsync(m_session, in, out, aux, syncp);
}
virtual mfxStatus GetSurfaceIn(mfxFrameSurface1** output_surf) override {
return MFXMemory_GetSurfaceForVPP(m_session, output_surf);
}
virtual mfxStatus GetSurfaceOut(mfxFrameSurface1** output_surf) override {
return MFXMemory_GetSurfaceForVPPOut(m_session, output_surf);
}
virtual mfxStatus ProcessFrameAsync(mfxFrameSurface1 *in, mfxFrameSurface1 **out) override {
return MFXVideoVPP_ProcessFrameAsync(m_session, in, out);
}
protected:
mfxSession m_session; // (mfxSession) handle to the owning session
private:
MFXVideoVPP(const MFXVideoVPP& other);
MFXVideoVPP& operator=(const MFXVideoVPP& other);
};
class MFXVideoDECODE_VPP
{
public:
explicit MFXVideoDECODE_VPP(mfxSession session) {
m_session = session;
}
virtual ~MFXVideoDECODE_VPP(void) {
Close();
}
virtual mfxStatus Init(mfxVideoParam* decode_par, mfxVideoChannelParam** vpp_par_array, mfxU32 num_channel_par) {
return MFXVideoDECODE_VPP_Init(m_session, decode_par, vpp_par_array, num_channel_par);
}
virtual mfxStatus Reset(mfxVideoParam* decode_par, mfxVideoChannelParam** vpp_par_array, mfxU32 num_channel_par) {
return MFXVideoDECODE_VPP_Reset(m_session, decode_par, vpp_par_array, num_channel_par);
}
virtual mfxStatus GetChannelParam(mfxVideoChannelParam *par, mfxU32 channel_id) {
return MFXVideoDECODE_VPP_GetChannelParam(m_session, par, channel_id);
}
virtual mfxStatus DecodeFrameAsync(mfxBitstream *bs, mfxU32* skip_channels, mfxU32 num_skip_channels, mfxSurfaceArray **surf_array_out) {
return MFXVideoDECODE_VPP_DecodeFrameAsync(m_session, bs, skip_channels, num_skip_channels, surf_array_out);
}
virtual mfxStatus DecodeHeader(mfxBitstream *bs, mfxVideoParam *par) {
return MFXVideoDECODE_VPP_DecodeHeader(m_session, bs, par);
}
virtual mfxStatus Close(void) {
return MFXVideoDECODE_VPP_Close(m_session);
}
virtual mfxStatus GetVideoParam(mfxVideoParam *par) {
return MFXVideoDECODE_VPP_GetVideoParam(m_session, par);
}
virtual mfxStatus GetDecodeStat(mfxDecodeStat *stat) {
return MFXVideoDECODE_VPP_GetDecodeStat(m_session, stat);
}
virtual mfxStatus GetPayload(mfxU64 *ts, mfxPayload *payload) {
return MFXVideoDECODE_VPP_GetPayload(m_session, ts, payload);
}
virtual mfxStatus SetSkipMode(mfxSkipMode mode) {
return MFXVideoDECODE_VPP_SetSkipMode(m_session, mode);
}
protected:
mfxSession m_session; // (mfxSession) handle to the owning session
private:
MFXVideoDECODE_VPP(const MFXVideoDECODE_VPP& other);
MFXVideoDECODE_VPP& operator=(const MFXVideoDECODE_VPP& other);
};
#endif //__MFXVIDEOPLUSPLUS_H
File diff suppressed because it is too large Load Diff
+65
View File
@@ -0,0 +1,65 @@
/*############################################################################
# Copyright Intel Corporation
#
# SPDX-License-Identifier: MIT
############################################################################*/
#ifndef __MFXVP8_H__
#define __MFXVP8_H__
#include "mfxdefs.h"
#ifdef __cplusplus
extern "C" {
#endif
enum {
MFX_CODEC_VP8 = MFX_MAKEFOURCC('V','P','8',' '),
};
/* VP8 CodecProfile*/
enum {
MFX_PROFILE_VP8_0 = 0+1,
MFX_PROFILE_VP8_1 = 1+1,
MFX_PROFILE_VP8_2 = 2+1,
MFX_PROFILE_VP8_3 = 3+1,
};
/* Extended Buffer Ids */
enum {
/*!
This extended buffer describes VP8 encoder configuration parameters. See the mfxExtVP8CodingOption structure for details.
The application can attach this buffer to the mfxVideoParam structure for encoding initialization.
*/
MFX_EXTBUFF_VP8_CODING_OPTION = MFX_MAKEFOURCC('V','P','8','E'),
};
MFX_PACK_BEGIN_USUAL_STRUCT()
/*! Describes VP8 coding options. */
typedef struct {
mfxExtBuffer Header; /*!< Extension buffer header. Header.BufferId must be equal to MFX_EXTBUFF_VP8_CODING_OPTION. */
mfxU16 Version; /*!< Determines the bitstream version. Corresponds to the same VP8 syntax element in frame_tag. */
mfxU16 EnableMultipleSegments; /*!< Set this option to ON to enable segmentation. This is tri-state option. See the CodingOptionValue
enumerator for values of this option. */
mfxU16 LoopFilterType; /*!< Select the type of filter (normal or simple). Corresponds to VP8 syntax element filter_type. */
mfxU16 LoopFilterLevel[4]; /*!< Controls the filter strength. Corresponds to VP8 syntax element loop_filter_level. */
mfxU16 SharpnessLevel; /*!< Controls the filter sensitivity. Corresponds to VP8 syntax element sharpness_level. */
mfxU16 NumTokenPartitions; /*!< Specifies number of token partitions in the coded frame. */
mfxI16 LoopFilterRefTypeDelta[4]; /*!< Loop filter level delta for reference type (intra, last, golden, altref). */
mfxI16 LoopFilterMbModeDelta[4]; /*!< Loop filter level delta for MB modes. */
mfxI16 SegmentQPDelta[4]; /*!< QP delta for segment. */
mfxI16 CoeffTypeQPDelta[5]; /*!< QP delta for coefficient type (YDC, Y2AC, Y2DC, UVAC, UVDC). */
mfxU16 WriteIVFHeaders; /*!< Set this option to ON to enable insertion of IVF container headers into bitstream. This is tri-state
option. See the CodingOptionValue enumerator for values of this option */
mfxU32 NumFramesForIVFHeader; /*!< Specifies number of frames for IVF header when WriteIVFHeaders is ON. */
mfxU16 reserved[223];
} mfxExtVP8CodingOption;
MFX_PACK_END()
#ifdef __cplusplus
} // extern "C"
#endif
#endif
@@ -0,0 +1,2 @@
The files in this preview folder are provided as a preview of upcoming features.
The content of this folder may be changed or removed without respect for backward compatibility.
@@ -0,0 +1,25 @@
The file preview/legacy/mfxvideo++.h is a preview implementation of "class
MFXVideoSession" which takes advantage of API 2.0 functions of the
Intel® Video Processing Library (Intel® VPL) for implementation selection and
session creation.
Known limitations:
- The parameter mfxInitParam.ExternalThreads is not supported.
- The API version returned by MFXVideoSession::QueryVersion() may be different
on platforms for which libmfx-gen is the default runtime implementation.
- On Windows, accelerators selected using MFX_IMPL_HARDWARE, MFX_IMPL_HARDWARE2,
MFX_IMPL_HARDWARE3, or MFX_IMPL_HARDWARE4 are always enumerated according to
IDXGIFactory::EnumAdapters (i.e. D3D11) indexes, regardless of the
acceleration mode selected. On a multi-monitor or multi-adapter system, D3D9
and D3D11 adapter indexing may not match. Applications needing to create a
session on a specific D3D9 adapter should instead use the Dispatcher
Configuration Property mfxExtendedDeviceId.DeviceLUID to select the desired
adapter.
- Identical behavior between the production implementation of mfxvideo++.h and
the preview is not guaranteed. Applications may however define
MFXVIDEO_CPP_USE_DEPRECATED when compiling the preview application to build
with the previous implementation.
@@ -0,0 +1,581 @@
/*###########################################################################
# Copyright Intel Corporation
#
# SPDX-License-Identifier: MIT
###########################################################################*/
#ifndef __MFXVIDEOPLUSPLUS_H
#define __MFXVIDEOPLUSPLUS_H
#include "vpl/mfx.h"
#define MFX_IMPL_ACCELMODE(x) (0xff00 & (x))
class MFXVideoSessionBase {
public:
virtual ~MFXVideoSessionBase() {}
virtual mfxStatus Init(mfxIMPL impl, mfxVersion* ver) = 0;
virtual mfxStatus InitEx(mfxInitParam par) = 0;
virtual mfxStatus Close(void) = 0;
virtual mfxStatus QueryIMPL(mfxIMPL* impl) = 0;
virtual mfxStatus QueryVersion(mfxVersion* version) = 0;
virtual mfxStatus JoinSession(mfxSession child_session) = 0;
virtual mfxStatus DisjoinSession() = 0;
virtual mfxStatus CloneSession(mfxSession* clone) = 0;
virtual mfxStatus SetPriority(mfxPriority priority) = 0;
virtual mfxStatus GetPriority(mfxPriority* priority) = 0;
virtual mfxStatus SetFrameAllocator(mfxFrameAllocator* allocator) = 0;
virtual mfxStatus SetHandle(mfxHandleType type, mfxHDL hdl) = 0;
virtual mfxStatus GetHandle(mfxHandleType type, mfxHDL* hdl) = 0;
virtual mfxStatus QueryPlatform(mfxPlatform* platform) = 0;
virtual mfxStatus SyncOperation(mfxSyncPoint syncp, mfxU32 wait) = 0;
virtual mfxStatus GetSurfaceForEncode(mfxFrameSurface1** output_surf) = 0;
virtual mfxStatus GetSurfaceForDecode(mfxFrameSurface1** output_surf) = 0;
virtual mfxStatus GetSurfaceForVPP(mfxFrameSurface1** output_surf) = 0;
virtual mfxStatus GetSurfaceForVPPOut(mfxFrameSurface1** output_surf) = 0;
virtual operator mfxSession(void) = 0;
};
class MFXVideoENCODEBase {
public:
virtual ~MFXVideoENCODEBase() {}
virtual mfxStatus Query(mfxVideoParam* in, mfxVideoParam* out) = 0;
virtual mfxStatus QueryIOSurf(mfxVideoParam* par, mfxFrameAllocRequest* request) = 0;
virtual mfxStatus Init(mfxVideoParam* par) = 0;
virtual mfxStatus Reset(mfxVideoParam* par) = 0;
virtual mfxStatus Close(void) = 0;
virtual mfxStatus GetVideoParam(mfxVideoParam* par) = 0;
virtual mfxStatus GetEncodeStat(mfxEncodeStat* stat) = 0;
virtual mfxStatus EncodeFrameAsync(mfxEncodeCtrl* ctrl,
mfxFrameSurface1* surface,
mfxBitstream* bs,
mfxSyncPoint* syncp) = 0;
virtual mfxStatus GetSurface(mfxFrameSurface1** output_surf) = 0;
};
class MFXVideoDECODEBase {
public:
virtual ~MFXVideoDECODEBase() {}
virtual mfxStatus Query(mfxVideoParam* in, mfxVideoParam* out) = 0;
virtual mfxStatus DecodeHeader(mfxBitstream* bs, mfxVideoParam* par) = 0;
virtual mfxStatus QueryIOSurf(mfxVideoParam* par, mfxFrameAllocRequest* request) = 0;
virtual mfxStatus Init(mfxVideoParam* par) = 0;
virtual mfxStatus Reset(mfxVideoParam* par) = 0;
virtual mfxStatus Close(void) = 0;
virtual mfxStatus GetVideoParam(mfxVideoParam* par) = 0;
virtual mfxStatus GetDecodeStat(mfxDecodeStat* stat) = 0;
virtual mfxStatus GetPayload(mfxU64* ts, mfxPayload* payload) = 0;
virtual mfxStatus SetSkipMode(mfxSkipMode mode) = 0;
virtual mfxStatus DecodeFrameAsync(mfxBitstream* bs,
mfxFrameSurface1* surface_work,
mfxFrameSurface1** surface_out,
mfxSyncPoint* syncp) = 0;
virtual mfxStatus GetSurface(mfxFrameSurface1** output_surf) = 0;
};
class MFXVideoVPPBase {
public:
virtual ~MFXVideoVPPBase() {}
virtual mfxStatus Query(mfxVideoParam* in, mfxVideoParam* out) = 0;
virtual mfxStatus QueryIOSurf(mfxVideoParam* par, mfxFrameAllocRequest request[2]) = 0;
virtual mfxStatus Init(mfxVideoParam* par) = 0;
virtual mfxStatus Reset(mfxVideoParam* par) = 0;
virtual mfxStatus Close(void) = 0;
virtual mfxStatus GetVideoParam(mfxVideoParam* par) = 0;
virtual mfxStatus GetVPPStat(mfxVPPStat* stat) = 0;
virtual mfxStatus RunFrameVPPAsync(mfxFrameSurface1* in,
mfxFrameSurface1* out,
mfxExtVppAuxData* aux,
mfxSyncPoint* syncp) = 0;
virtual mfxStatus GetSurfaceIn(mfxFrameSurface1** output_surf) = 0;
virtual mfxStatus GetSurfaceOut(mfxFrameSurface1** output_surf) = 0;
virtual mfxStatus ProcessFrameAsync(mfxFrameSurface1* in, mfxFrameSurface1** out) = 0;
};
class MFXVideoSession : public MFXVideoSessionBase {
public:
MFXVideoSession(void) {
m_session = (mfxSession)0;
#ifndef MFXVIDEO_CPP_USE_DEPRECATED
m_loader = (mfxLoader)0;
#endif
}
virtual ~MFXVideoSession(void) {
Close();
}
virtual mfxStatus Init(mfxIMPL impl, mfxVersion *ver) override {
#ifndef MFXVIDEO_CPP_USE_DEPRECATED
mfxInitParam par = {};
par.Implementation = impl;
par.Version = *ver;
return InitSession(par);
#else
return MFXInit(impl, ver, &m_session);
#endif
}
virtual mfxStatus InitEx(mfxInitParam par) override {
#ifndef MFXVIDEO_CPP_USE_DEPRECATED
return InitSession(par);
#else
return MFXInitEx(par, &m_session);
#endif
}
virtual mfxStatus Close(void) override {
#ifndef MFXVIDEO_CPP_USE_DEPRECATED
if (m_session) {
mfxStatus mfxRes;
mfxRes = MFXClose(m_session);
m_session = (mfxSession)0;
if (m_loader) {
MFXUnload(m_loader);
m_loader = (mfxLoader)0;
}
return mfxRes;
}
else {
return MFX_ERR_NONE;
}
#else
mfxStatus mfxRes;
mfxRes = MFXClose(m_session);
m_session = (mfxSession)0;
return mfxRes;
#endif
}
virtual mfxStatus QueryIMPL(mfxIMPL *impl) override {
return MFXQueryIMPL(m_session, impl);
}
virtual mfxStatus QueryVersion(mfxVersion *version) override {
return MFXQueryVersion(m_session, version);
}
virtual mfxStatus JoinSession(mfxSession child_session) override {
return MFXJoinSession(m_session, child_session);
}
virtual mfxStatus DisjoinSession() override {
return MFXDisjoinSession(m_session);
}
virtual mfxStatus CloneSession(mfxSession *clone) override {
return MFXCloneSession(m_session, clone);
}
virtual mfxStatus SetPriority(mfxPriority priority) override {
return MFXSetPriority(m_session, priority);
}
virtual mfxStatus GetPriority(mfxPriority *priority) override {
return MFXGetPriority(m_session, priority);
}
virtual mfxStatus SetFrameAllocator(mfxFrameAllocator *allocator) override {
return MFXVideoCORE_SetFrameAllocator(m_session, allocator);
}
virtual mfxStatus SetHandle(mfxHandleType type, mfxHDL hdl) override {
return MFXVideoCORE_SetHandle(m_session, type, hdl);
}
virtual mfxStatus GetHandle(mfxHandleType type, mfxHDL *hdl) override {
return MFXVideoCORE_GetHandle(m_session, type, hdl);
}
virtual mfxStatus QueryPlatform(mfxPlatform *platform) override {
return MFXVideoCORE_QueryPlatform(m_session, platform);
}
virtual mfxStatus SyncOperation(mfxSyncPoint syncp, mfxU32 wait) override {
return MFXVideoCORE_SyncOperation(m_session, syncp, wait);
}
virtual mfxStatus GetSurfaceForEncode(mfxFrameSurface1** output_surf) override {
return MFXMemory_GetSurfaceForEncode(m_session, output_surf);
}
virtual mfxStatus GetSurfaceForDecode(mfxFrameSurface1** output_surf) override {
return MFXMemory_GetSurfaceForDecode(m_session, output_surf);
}
virtual mfxStatus GetSurfaceForVPP (mfxFrameSurface1** output_surf) override {
return MFXMemory_GetSurfaceForVPP (m_session, output_surf);
}
virtual mfxStatus GetSurfaceForVPPOut(mfxFrameSurface1** output_surf) override {
return MFXMemory_GetSurfaceForVPPOut(m_session, output_surf);
}
virtual operator mfxSession(void) override {
return m_session;
}
protected:
mfxSession m_session; // (mfxSession) handle to the owning session
#ifndef MFXVIDEO_CPP_USE_DEPRECATED
mfxLoader m_loader;
inline void InitVariant(mfxVariant *var, mfxU32 data) {
var->Version.Version = (mfxU16)MFX_VARIANT_VERSION;
var->Type = MFX_VARIANT_TYPE_U32;
var->Data.U32 = data;
}
inline void InitVariant(mfxVariant *var, mfxU16 data) {
var->Version.Version = (mfxU16)MFX_VARIANT_VERSION;
var->Type = MFX_VARIANT_TYPE_U16;
var->Data.U16 = data;
}
inline void InitVariant(mfxVariant *var, mfxHDL data) {
var->Version.Version = (mfxU16)MFX_VARIANT_VERSION;
var->Type = MFX_VARIANT_TYPE_PTR;
var->Data.Ptr = data;
}
template <typename varDataType>
mfxStatus CreateConfig(varDataType data, const char *propertyName) {
mfxConfig cfg = MFXCreateConfig(m_loader);
if (cfg == nullptr)
return MFX_ERR_NULL_PTR;
mfxVariant variant;
InitVariant(&variant, data);
return MFXSetConfigFilterProperty(cfg, (mfxU8 *)propertyName, variant);
}
mfxStatus InitSession(mfxInitParam par) {
// already initialized
if (m_session)
return MFX_ERR_NONE;
m_loader = MFXLoad();
if (!m_loader)
return MFX_ERR_NOT_FOUND;
mfxStatus mfxRes = MFX_ERR_NONE;
mfxU32 implBaseType = MFX_IMPL_BASETYPE(par.Implementation);
// select implementation type
switch (implBaseType) {
case MFX_IMPL_AUTO:
case MFX_IMPL_AUTO_ANY:
break;
case MFX_IMPL_SOFTWARE:
mfxRes = CreateConfig<mfxU32>(MFX_IMPL_TYPE_SOFTWARE, "mfxImplDescription.Impl");
break;
case MFX_IMPL_HARDWARE:
case MFX_IMPL_HARDWARE_ANY:
case MFX_IMPL_HARDWARE2:
case MFX_IMPL_HARDWARE3:
case MFX_IMPL_HARDWARE4:
mfxRes = CreateConfig<mfxU32>(MFX_IMPL_TYPE_HARDWARE, "mfxImplDescription.Impl");
break;
default:
mfxRes = MFX_ERR_UNSUPPORTED;
break;
}
if (MFX_ERR_NONE != mfxRes)
return MFX_ERR_UNSUPPORTED;
// select adapter index (if specified)
// see notes below about how VendorImplID is interpreted for each acceleration mode
switch (implBaseType) {
case MFX_IMPL_HARDWARE:
mfxRes = CreateConfig<mfxU32>(0, "mfxImplDescription.VendorImplID");
break;
case MFX_IMPL_HARDWARE2:
mfxRes = CreateConfig<mfxU32>(1, "mfxImplDescription.VendorImplID");
break;
case MFX_IMPL_HARDWARE3:
mfxRes = CreateConfig<mfxU32>(2, "mfxImplDescription.VendorImplID");
break;
case MFX_IMPL_HARDWARE4:
mfxRes = CreateConfig<mfxU32>(3, "mfxImplDescription.VendorImplID");
break;
}
if (MFX_ERR_NONE != mfxRes)
return MFX_ERR_UNSUPPORTED;
mfxU32 implAccelMode = MFX_IMPL_ACCELMODE(par.Implementation);
if (implAccelMode == MFX_IMPL_VIA_D3D9) {
// D3D9 - because VendorImplID corresponds to DXGI adapter index (DX11 enumeration),
// this may not map directly to D3D9 index in multi-adapter/multi-monitor configurations
mfxRes = CreateConfig<mfxU32>(MFX_ACCEL_MODE_VIA_D3D9,
"mfxImplDescription.AccelerationMode");
}
else if (implAccelMode == MFX_IMPL_VIA_D3D11) {
// D3D11 - VendorImplID corresponds to DXGI adapter index
mfxRes = CreateConfig<mfxU32>(MFX_ACCEL_MODE_VIA_D3D11,
"mfxImplDescription.AccelerationMode");
}
else if (implAccelMode == MFX_IMPL_VIA_VAAPI) {
// VAAPI - in general MFXInitEx treats any HARDWAREn the same way (relies on application to pass
// correct VADisplay via SetHandle), but 2.x RT only reports actual number of adapters
mfxRes = CreateConfig<mfxU32>(MFX_ACCEL_MODE_VIA_VAAPI,
"mfxImplDescription.AccelerationMode");
}
if (MFX_ERR_NONE != mfxRes)
return MFX_ERR_UNSUPPORTED;
// set required API level
mfxRes =
CreateConfig<mfxU32>(par.Version.Version, "mfxImplDescription.ApiVersion.Version");
if (MFX_ERR_NONE != mfxRes)
return MFX_ERR_UNSUPPORTED;
// set GPUCopy parameter
if (par.GPUCopy) {
mfxRes = CreateConfig<mfxU16>(par.GPUCopy, "DeviceCopy");
if (MFX_ERR_NONE != mfxRes)
return MFX_ERR_UNSUPPORTED;
}
// set ExternalThreads parameter
if (par.ExternalThreads) {
// TO DO - need to add this as config filter property and member of mfxInitializationParam
return MFX_ERR_UNSUPPORTED;
}
// pass extBufs
if (par.NumExtParam) {
for (mfxU32 idx = 0; idx < par.NumExtParam; idx++) {
mfxRes = CreateConfig<mfxHDL>(par.ExtParam[idx], "ExtBuffer");
if (MFX_ERR_NONE != mfxRes)
return MFX_ERR_UNSUPPORTED;
}
}
// create session with highest priority implementation remaining after filters
mfxRes = MFXCreateSession(m_loader, 0, &m_session);
return mfxRes;
}
#endif
private:
MFXVideoSession(const MFXVideoSession &);
void operator=(MFXVideoSession &);
};
class MFXVideoENCODE : public MFXVideoENCODEBase {
public:
explicit MFXVideoENCODE(mfxSession session) {
m_session = session;
}
virtual ~MFXVideoENCODE(void) {
Close();
}
virtual mfxStatus Query(mfxVideoParam *in, mfxVideoParam *out) override {
return MFXVideoENCODE_Query(m_session, in, out);
}
virtual mfxStatus QueryIOSurf(mfxVideoParam *par, mfxFrameAllocRequest *request) override {
return MFXVideoENCODE_QueryIOSurf(m_session, par, request);
}
virtual mfxStatus Init(mfxVideoParam *par) override {
return MFXVideoENCODE_Init(m_session, par);
}
virtual mfxStatus Reset(mfxVideoParam *par) override {
return MFXVideoENCODE_Reset(m_session, par);
}
virtual mfxStatus Close(void) override {
return MFXVideoENCODE_Close(m_session);
}
virtual mfxStatus GetVideoParam(mfxVideoParam *par) override {
return MFXVideoENCODE_GetVideoParam(m_session, par);
}
virtual mfxStatus GetEncodeStat(mfxEncodeStat *stat) override {
return MFXVideoENCODE_GetEncodeStat(m_session, stat);
}
virtual mfxStatus EncodeFrameAsync(mfxEncodeCtrl *ctrl,
mfxFrameSurface1 *surface,
mfxBitstream *bs,
mfxSyncPoint *syncp) override {
return MFXVideoENCODE_EncodeFrameAsync(m_session, ctrl, surface, bs, syncp);
}
virtual mfxStatus GetSurface(mfxFrameSurface1** output_surf) override {
return MFXMemory_GetSurfaceForEncode(m_session, output_surf);
}
protected:
mfxSession m_session; // (mfxSession) handle to the owning session
};
class MFXVideoDECODE : public MFXVideoDECODEBase {
public:
explicit MFXVideoDECODE(mfxSession session) {
m_session = session;
}
virtual ~MFXVideoDECODE(void) {
Close();
}
virtual mfxStatus Query(mfxVideoParam *in, mfxVideoParam *out) override {
return MFXVideoDECODE_Query(m_session, in, out);
}
virtual mfxStatus DecodeHeader(mfxBitstream *bs, mfxVideoParam *par) override {
return MFXVideoDECODE_DecodeHeader(m_session, bs, par);
}
virtual mfxStatus QueryIOSurf(mfxVideoParam *par, mfxFrameAllocRequest *request) override {
return MFXVideoDECODE_QueryIOSurf(m_session, par, request);
}
virtual mfxStatus Init(mfxVideoParam *par) override {
return MFXVideoDECODE_Init(m_session, par);
}
virtual mfxStatus Reset(mfxVideoParam *par) override {
return MFXVideoDECODE_Reset(m_session, par);
}
virtual mfxStatus Close(void) override {
return MFXVideoDECODE_Close(m_session);
}
virtual mfxStatus GetVideoParam(mfxVideoParam *par) override {
return MFXVideoDECODE_GetVideoParam(m_session, par);
}
virtual mfxStatus GetDecodeStat(mfxDecodeStat *stat) override {
return MFXVideoDECODE_GetDecodeStat(m_session, stat);
}
virtual mfxStatus GetPayload(mfxU64 *ts, mfxPayload *payload) override {
return MFXVideoDECODE_GetPayload(m_session, ts, payload);
}
virtual mfxStatus SetSkipMode(mfxSkipMode mode) override {
return MFXVideoDECODE_SetSkipMode(m_session, mode);
}
virtual mfxStatus DecodeFrameAsync(mfxBitstream *bs,
mfxFrameSurface1 *surface_work,
mfxFrameSurface1 **surface_out,
mfxSyncPoint *syncp) override {
return MFXVideoDECODE_DecodeFrameAsync(m_session, bs, surface_work, surface_out, syncp);
}
virtual mfxStatus GetSurface(mfxFrameSurface1** output_surf) override {
return MFXMemory_GetSurfaceForDecode(m_session, output_surf);
}
protected:
mfxSession m_session; // (mfxSession) handle to the owning session
};
class MFXVideoVPP : public MFXVideoVPPBase {
public:
explicit MFXVideoVPP(mfxSession session) {
m_session = session;
}
virtual ~MFXVideoVPP(void) {
Close();
}
virtual mfxStatus Query(mfxVideoParam *in, mfxVideoParam *out) override {
return MFXVideoVPP_Query(m_session, in, out);
}
virtual mfxStatus QueryIOSurf(mfxVideoParam *par, mfxFrameAllocRequest request[2]) override {
return MFXVideoVPP_QueryIOSurf(m_session, par, request);
}
virtual mfxStatus Init(mfxVideoParam *par) override {
return MFXVideoVPP_Init(m_session, par);
}
virtual mfxStatus Reset(mfxVideoParam *par) override {
return MFXVideoVPP_Reset(m_session, par);
}
virtual mfxStatus Close(void) override {
return MFXVideoVPP_Close(m_session);
}
virtual mfxStatus GetVideoParam(mfxVideoParam *par) override {
return MFXVideoVPP_GetVideoParam(m_session, par);
}
virtual mfxStatus GetVPPStat(mfxVPPStat *stat) override {
return MFXVideoVPP_GetVPPStat(m_session, stat);
}
virtual mfxStatus RunFrameVPPAsync(mfxFrameSurface1 *in,
mfxFrameSurface1 *out,
mfxExtVppAuxData *aux,
mfxSyncPoint *syncp) override {
return MFXVideoVPP_RunFrameVPPAsync(m_session, in, out, aux, syncp);
}
virtual mfxStatus GetSurfaceIn(mfxFrameSurface1** output_surf) override {
return MFXMemory_GetSurfaceForVPP(m_session, output_surf);
}
virtual mfxStatus GetSurfaceOut(mfxFrameSurface1** output_surf) override {
return MFXMemory_GetSurfaceForVPPOut(m_session, output_surf);
}
virtual mfxStatus ProcessFrameAsync(mfxFrameSurface1 *in, mfxFrameSurface1 **out) override {
return MFXVideoVPP_ProcessFrameAsync(m_session, in, out);
}
protected:
mfxSession m_session; // (mfxSession) handle to the owning session
};
class MFXVideoDECODE_VPP
{
public:
explicit MFXVideoDECODE_VPP(mfxSession session) {
m_session = session;
}
virtual ~MFXVideoDECODE_VPP(void) {
Close();
}
virtual mfxStatus Init(mfxVideoParam* decode_par, mfxVideoChannelParam** vpp_par_array, mfxU32 num_channel_par) {
return MFXVideoDECODE_VPP_Init(m_session, decode_par, vpp_par_array, num_channel_par);
}
virtual mfxStatus Reset(mfxVideoParam* decode_par, mfxVideoChannelParam** vpp_par_array, mfxU32 num_channel_par) {
return MFXVideoDECODE_VPP_Reset(m_session, decode_par, vpp_par_array, num_channel_par);
}
virtual mfxStatus GetChannelParam(mfxVideoChannelParam *par, mfxU32 channel_id) {
return MFXVideoDECODE_VPP_GetChannelParam(m_session, par, channel_id);
}
virtual mfxStatus DecodeFrameAsync(mfxBitstream *bs, mfxU32* skip_channels, mfxU32 num_skip_channels, mfxSurfaceArray **surf_array_out) {
return MFXVideoDECODE_VPP_DecodeFrameAsync(m_session, bs, skip_channels, num_skip_channels, surf_array_out);
}
virtual mfxStatus DecodeHeader(mfxBitstream *bs, mfxVideoParam *par) {
return MFXVideoDECODE_VPP_DecodeHeader(m_session, bs, par);
}
virtual mfxStatus Close(void) {
return MFXVideoDECODE_VPP_Close(m_session);
}
virtual mfxStatus GetVideoParam(mfxVideoParam *par) {
return MFXVideoDECODE_VPP_GetVideoParam(m_session, par);
}
virtual mfxStatus GetDecodeStat(mfxDecodeStat *stat) {
return MFXVideoDECODE_VPP_GetDecodeStat(m_session, stat);
}
virtual mfxStatus GetPayload(mfxU64 *ts, mfxPayload *payload) {
return MFXVideoDECODE_VPP_GetPayload(m_session, ts, payload);
}
virtual mfxStatus SetSkipMode(mfxSkipMode mode) {
return MFXVideoDECODE_VPP_SetSkipMode(m_session, mode);
}
protected:
mfxSession m_session; // (mfxSession) handle to the owning session
};
#endif //__MFXVIDEOPLUSPLUS_H
@@ -0,0 +1,111 @@
# ##############################################################################
# Copyright (C) Intel Corporation
#
# SPDX-License-Identifier: MIT
# ##############################################################################
#
# Set compilation options
#
if(MSVC)
add_compile_options("$<$<CONFIG:Debug>:/D_DEBUG>")
else()
add_compile_options("$<$<CONFIG:Debug>:-D_DEBUG -O0 -g>")
endif()
if(ENABLE_WARNING_AS_ERROR)
message(STATUS "Warnings as errors enabled")
set(MFX_DEPRECATED_OFF 1)
endif()
if(DEFINED ENV{MFX_DEPRECATED_OFF})
set(MFX_DEPRECATED_OFF 1)
endif()
if(MFX_DEPRECATED_OFF)
message(STATUS "Deprecation warnings disabled")
add_definitions(-DMFX_DEPRECATED_OFF)
endif()
set(CMAKE_POSITION_INDEPENDENT_CODE ON)
if(MSVC)
add_link_options("/guard:cf")
add_link_options("/DYNAMICBASE")
if("${CMAKE_SIZEOF_VOID_P}" STREQUAL "8")
add_link_options("/HIGHENTROPYVA")
endif()
add_link_options("/LARGEADDRESSAWARE")
add_link_options("/NXCOMPAT")
if(ENABLE_WARNING_AS_ERROR)
add_compile_options("/WX")
endif()
add_compile_options("/GS")
add_compile_options("/guard:cf")
else()
if(UNIX)
set(CMAKE_EXE_LINKER_FLAGS "${CMAKE_EXE_LINKER_FLAGS} -pie")
endif()
add_compile_options("-Wformat")
add_compile_options("-Wformat-security")
add_compile_options("-Werror=format-security")
if(NOT MINGW)
string(TOLOWER ${CMAKE_BUILD_TYPE} CMAKE_BUILD_TYPE)
if(NOT CMAKE_BUILD_TYPE MATCHES debug)
add_definitions("-D_FORTIFY_SOURCE=2")
endif()
endif()
if(NOT MINGW)
add_compile_options("-fstack-protector-strong")
endif()
if(NOT MINGW)
add_link_options("-Wl,-z,relro,-z,now,-z,noexecstack")
endif()
add_compile_options("-Wall")
if(ENABLE_WARNING_AS_ERROR)
add_compile_options("-Werror")
endif()
endif()
# Define RelWithDebRT Build Mode
if(MSVC)
set(CMAKE_DEBUG_POSTFIX "d")
set(CMAKE_RELWITHDEBRT_POSTFIX "d")
set(CMAKE_CXX_FLAGS_RELWITHDEBRT
"${CMAKE_CXX_FLAGS_RELEASE}"
CACHE STRING "Flags used by the C++ compiler during RelWithDebRT builds."
FORCE)
set(CMAKE_C_FLAGS_RELWITHDEBRT
"${CMAKE_C_FLAGS_RELEASE}"
CACHE STRING "Flags used by the C compiler during RelWithDebRT builds."
FORCE)
foreach(build_flag CMAKE_CXX_FLAGS_RELWITHDEBRT CMAKE_C_FLAGS_RELWITHDEBRT)
string(REPLACE "/MDd" "" ${build_flag} "${${build_flag}}")
string(REPLACE "/MTd" "" ${build_flag} "${${build_flag}}")
string(REPLACE "/MD" "" ${build_flag} "${${build_flag}}")
string(REPLACE "/MT" "" ${build_flag} "${${build_flag}}")
set(${build_flag} "${${build_flag}} /MDd /DEBUG:NONE")
endforeach()
set(CMAKE_EXE_LINKER_FLAGS_RELWITHDEBRT
"${CMAKE_EXE_LINKER_FLAGS_RELEASE}"
CACHE STRING
"Flags used for linking binaries during RelWithDebRT builds." FORCE)
set(CMAKE_SHARED_LINKER_FLAGS_RELWITHDEBRT
"${CMAKE_SHARED_LINKER_FLAGS_RELEASE}"
CACHE
STRING
"Flags used by the shared libraries linker during RelWithDebRT builds."
FORCE)
set(CMAKE_MODULE_LINKER_FLAGS_RELWITHDEBRT
"${CMAKE_MODULE_LINKER_FLAGS_RELEASE}"
CACHE STRING "Flags used by the linker during RelWithDebRT builds." FORCE)
mark_as_advanced(
CMAKE_CXX_FLAGS_RELWITHDEBRT CMAKE_C_FLAGS_RELWITHDEBRT
CMAKE_EXE_LINKER_FLAGS_RELWITHDEBRT CMAKE_SHARED_LINKER_FLAGS_RELWITHDEBRT)
# Add RelWithDebRT to avalible config types
set(CMAKE_CONFIGURATION_TYPES "${CMAKE_CONFIGURATION_TYPES};RelWithDebRT")
endif()
+101
View File
@@ -0,0 +1,101 @@
# ##############################################################################
# Copyright (C) Intel Corporation
#
# SPDX-License-Identifier: MIT
# ##############################################################################
#
# Set installation directories
#
if(WIN32
AND NOT CMAKE_CROSSCOMPILING
AND CMAKE_SIZEOF_VOID_P EQUAL 4)
if(NOT CMAKE_INSTALL_BINDIR)
set(BINARCH x86)
endif()
if(NOT CMAKE_INSTALL_LIBDIR)
set(LIBARCH x86)
endif()
endif()
# See https://cmake.org/cmake/help/latest/module/GNUInstallDirs.html for
# variables GNUInstallDirs exposes. This project commonly uses:
# CMAKE_INSTALL_INCLUDEDIR CMAKE_INSTALL_DOCDIR CMAKE_INSTALL_BINDIR
# CMAKE_INSTALL_LIBDIR
include(GNUInstallDirs)
set(CMAKE_INSTALL_DOCDIR ${CMAKE_INSTALL_DATAROOTDIR}/doc/${PROJECT_NAME})
gnuinstalldirs_get_absolute_install_dir(CMAKE_INSTALL_FULL_DOCDIR
CMAKE_INSTALL_DOCDIR DOCDIR)
if(WIN32 AND CMAKE_SIZEOF_VOID_P EQUAL 4)
set(CMAKE_INSTALL_BINDIR
${CMAKE_INSTALL_BINDIR}/${BINARCH}
CACHE PATH "user executables" FORCE)
set(CMAKE_INSTALL_LIBDIR
${CMAKE_INSTALL_LIBDIR}/${LIBARCH}
CACHE PATH "Object code libraries" FORCE)
foreach(dir LIBDIR BINDIR)
gnuinstalldirs_get_absolute_install_dir(CMAKE_INSTALL_FULL_${dir}
CMAKE_INSTALL_${dir} ${dir})
endforeach()
endif()
if(NOT VPL_INSTALL_PKGCONFIGDIR)
set(VPL_INSTALL_PKGCONFIGDIR ${CMAKE_INSTALL_LIBDIR}/pkgconfig)
set(VPL_INSTALL_FULL_PKGCONFIGDIR ${CMAKE_INSTALL_FULL_LIBDIR}/pkgconfig)
else()
gnuinstalldirs_get_absolute_install_dir(
VPL_INSTALL_FULL_PKGCONFIGDIR VPL_INSTALL_PKGCONFIGDIR
VPL_INSTALL_PKGCONFIGDIR)
endif()
if(NOT VPL_INSTALL_CMAKEDIR)
set(VPL_INSTALL_CMAKEDIR ${CMAKE_INSTALL_LIBDIR}/cmake)
set(VPL_INSTALL_FULL_CMAKEDIR ${CMAKE_INSTALL_FULL_LIBDIR}/cmake)
else()
gnuinstalldirs_get_absolute_install_dir(
VPL_INSTALL_FULL_CMAKEDIR VPL_INSTALL_CMAKEDIR VPL_INSTALL_CMAKEDIR)
endif()
if(NOT VPL_INSTALL_ENVDIR)
set(VPL_INSTALL_ENVDIR ${CMAKE_INSTALL_SYSCONFDIR}/${PROJECT_NAME})
set(VPL_INSTALL_FULL_ENVDIR ${CMAKE_INSTALL_FULL_SYSCONFDIR}/${PROJECT_NAME})
else()
gnuinstalldirs_get_absolute_install_dir(VPL_INSTALL_FULL_ENVDIR
VPL_INSTALL_ENVDIR VPL_INSTALL_ENVDIR)
endif()
if(NOT VPL_INSTALL_MODFILEDIR)
set(VPL_INSTALL_MODFILEDIR ${CMAKE_INSTALL_SYSCONFDIR}/modulefiles)
set(VPL_INSTALL_FULL_MODFILEDIR ${CMAKE_INSTALL_FULL_SYSCONFDIR}/modulefiles)
else()
gnuinstalldirs_get_absolute_install_dir(
VPL_INSTALL_FULL_MODFILEDIR VPL_INSTALL_MODFILEDIR VPL_INSTALL_MODFILEDIR)
endif()
if(NOT VPL_INSTALL_EXAMPLEDIR)
set(VPL_INSTALL_EXAMPLEDIR
${CMAKE_INSTALL_DATAROOTDIR}/${PROJECT_NAME}/examples)
set(VPL_INSTALL_FULL_EXAMPLEDIR
${CMAKE_INSTALL_FULL_DATAROOTDIR}/${PROJECT_NAME}/examples)
else()
gnuinstalldirs_get_absolute_install_dir(
VPL_INSTALL_FULL_EXAMPLEDIR VPL_INSTALL_EXAMPLEDIR VPL_INSTALL_EXAMPLEDIR)
endif()
if(NOT VPL_INSTALL_LICENSEDIR)
set(VPL_INSTALL_LICENSEDIR
${CMAKE_INSTALL_DATAROOTDIR}/${PROJECT_NAME}/licensing)
set(VPL_INSTALL_FULL_LICENSEDIR
${CMAKE_INSTALL_FULL_DATAROOTDIR}/${PROJECT_NAME}/licensing)
else()
gnuinstalldirs_get_absolute_install_dir(
VPL_INSTALL_FULL_LICENSEDIR VPL_INSTALL_LICENSEDIR VPL_INSTALL_LICENSEDIR)
endif()
if(WIN32)
set(CMAKE_INSTALL_SYSTEM_RUNTIME_DESTINATION ${CMAKE_INSTALL_BINDIR})
else()
set(CMAKE_INSTALL_SYSTEM_RUNTIME_DESTINATION ${CMAKE_INSTALL_LIBDIR})
endif()
@@ -0,0 +1,30 @@
# ##############################################################################
# Copyright (C) Intel Corporation
#
# SPDX-License-Identifier: MIT
# ##############################################################################
#
# Packaging
#
set(CPACK_GENERATOR "ZIP")
set(CPACK_ARCHIVE_COMPONENT_INSTALL ON)
set(CPACK_PACKAGE_DIRECTORY ${PROJECT_BINARY_DIR})
set(CPACK_PACKAGE_VERSION_MAJOR ${PROJECT_VERSION_MAJOR})
set(CPACK_PACKAGE_VERSION_MINOR ${PROJECT_VERSION_MINOR})
set(CPACK_PACKAGE_VERSION_PATCH ${PROJECT_VERSION_PATCH})
set(VPL_COMPONENT_DEV dev)
set(VPL_COMPONENT_LIB lib)
set(VPL_COMPONENT_TOOLS tools)
set(CPACK_COMPONENTS_ALL all ${VPL_COMPONENT_DEV} ${VPL_COMPONENT_LIB}
${VPL_COMPONENT_TOOLS})
set(CPACK_COMPONENTS_GROUPING IGNORE)
include(CPack)
cpack_add_component(${VPL_COMPONENT_LIB})
cpack_add_component(${VPL_COMPONENT_DEV} DEPENDS ${VPL_COMPONENT_LIB})
cpack_add_component(${VPL_COMPONENT_TOOLS} DEPENDS ${VPL_COMPONENT_LIB})
+204
View File
@@ -0,0 +1,204 @@
# ##############################################################################
# Copyright (C) Intel Corporation
#
# SPDX-License-Identifier: MIT
# ##############################################################################
cmake_minimum_required(VERSION 3.13.0)
set(TARGET VPL)
set(OUTPUT_NAME "vpl")
set(DLL_PREFIX "lib")
if(WIN32)
set(SOURCES
src/windows/main.cpp
src/windows/mfx_critical_section.cpp
src/windows/mfx_dispatcher.cpp
src/windows/mfx_dispatcher_log.cpp
src/windows/mfx_driver_store_loader.cpp
src/windows/mfx_dxva2_device.cpp
src/windows/mfx_function_table.cpp
src/windows/mfx_library_iterator.cpp
src/windows/mfx_load_dll.cpp
src/windows/mfx_win_reg_key.cpp
src/windows/libmfx.def)
if(BUILD_SHARED_LIBS)
configure_file(src/windows/version.rc.in src/windows/version.rc @ONLY)
list(APPEND SOURCES ${CMAKE_CURRENT_BINARY_DIR}/src/windows/version.rc)
endif()
endif()
if(UNIX)
set(SOURCES src/linux/mfxloader.cpp)
if(NOT DEFINED MFX_MODULES_DIR)
set(MFX_MODULES_DIR ${CMAKE_INSTALL_FULL_LIBDIR})
endif()
add_definitions(-DMFX_MODULES_DIR="${MFX_MODULES_DIR}")
message(STATUS "MFX_MODULES_DIR=${MFX_MODULES_DIR}")
endif()
if(BUILD_EXPERIMENTAL)
add_definitions(-DONEVPL_EXPERIMENTAL)
endif()
list(
APPEND
SOURCES
src/mfx_dispatcher_vpl.cpp
src/mfx_dispatcher_vpl_loader.cpp
src/mfx_dispatcher_vpl_config.cpp
src/mfx_dispatcher_vpl_lowlatency.cpp
src/mfx_dispatcher_vpl_log.cpp
src/mfx_dispatcher_vpl_msdk.cpp
src/mfx_config_interface/mfx_config_interface.cpp
src/mfx_config_interface/mfx_config_interface_string_api.cpp)
add_library(${TARGET} "")
# disable warnings for dispatcher library itself but not subprojects (unit
# tests)
set_target_properties(${TARGET} PROPERTIES COMPILE_DEFINITIONS
"MFX_DEPRECATED_OFF")
# optionally include the install location of dispatcher dll in the runtime
# search process
if(UNIX)
if(ENABLE_LIBDIR_IN_RUNTIME_SEARCH)
file(TO_CMAKE_PATH "${CMAKE_INSTALL_FULL_LIBDIR}" _install_libdir_search)
target_compile_definitions(
${TARGET} PRIVATE INSTALL_LIBDIR_SEARCH=\"${_install_libdir_search}\")
message(STATUS "Enabled install libdir search: ${_install_libdir_search}")
endif()
endif()
if(WIN32)
# force libxxx style sharedlib name on Windows
if(BUILD_SHARED_LIBS)
set_target_properties(${TARGET} PROPERTIES PREFIX ${DLL_PREFIX})
endif()
set(SHLIB_FILE_NAME ${DLL_PREFIX}${OUTPUT_NAME}${CMAKE_SHARED_LIBRARY_SUFFIX})
set(SHLIB_FILE_NAME_DEBUG
${DLL_PREFIX}${OUTPUT_NAME}${CMAKE_DEBUG_POSTFIX}${CMAKE_SHARED_LIBRARY_SUFFIX}
)
else()
# use version script on Linux
set_target_properties(
${TARGET}
PROPERTIES
LINK_FLAGS
"-Wl,--version-script=${CMAKE_CURRENT_SOURCE_DIR}/src/linux/libvpl.map")
set(SHLIB_FILE_NAME
${CMAKE_SHARED_LIBRARY_PREFIX}${OUTPUT_NAME}${CMAKE_SHARED_LIBRARY_SUFFIX}.${API_VERSION_MAJOR}
)
set(SHLIB_FILE_NAME_DEBUG ${SHLIB_FILE_NAME})
endif()
set(LIB_FILE_NAME
${CMAKE_STATIC_LIBRARY_PREFIX}${OUTPUT_NAME}${CMAKE_STATIC_LIBRARY_SUFFIX})
set(IMPLIB_FILE_NAME
${CMAKE_IMPORT_LIBRARY_PREFIX}${OUTPUT_NAME}${CMAKE_IMPORT_LIBRARY_SUFFIX})
set(LIB_FILE_NAME_DEBUG
${CMAKE_STATIC_LIBRARY_PREFIX}${OUTPUT_NAME}${CMAKE_DEBUG_POSTFIX}${CMAKE_STATIC_LIBRARY_SUFFIX}
)
set(IMPLIB_FILE_NAME_DEBUG
${CMAKE_IMPORT_LIBRARY_PREFIX}${OUTPUT_NAME}${CMAKE_DEBUG_POSTFIX}${CMAKE_IMPORT_LIBRARY_SUFFIX}
)
set_target_properties(
${TARGET}
PROPERTIES OUTPUT_NAME ${OUTPUT_NAME} SOVERSION ${API_VERSION_MAJOR}
VERSION ${API_VERSION_MAJOR}.${API_VERSION_MINOR})
target_sources(${TARGET} PRIVATE ${SOURCES})
if(UNIX)
# require pthreads for loading legacy MSDK runtimes
set(CMAKE_THREAD_PREFER_PTHREAD TRUE)
set(THREADS_PREFER_PTHREAD_FLAG TRUE)
find_package(Threads REQUIRED)
if(BUILD_SHARED_LIBS)
target_link_libraries(${TARGET} PRIVATE Threads::Threads)
else()
target_link_libraries(${TARGET} PUBLIC Threads::Threads)
endif()
endif()
target_link_libraries(${TARGET} PUBLIC vpl-api)
if(BUILD_SHARED_LIBS)
target_link_libraries(${TARGET} PRIVATE ${CMAKE_DL_LIBS})
else()
target_link_libraries(${TARGET} PUBLIC ${CMAKE_DL_LIBS})
endif()
target_include_directories(${TARGET} PRIVATE ${CMAKE_CURRENT_SOURCE_DIR}
${CMAKE_CURRENT_BINARY_DIR})
if(INSTALL_LIB)
install(
TARGETS ${TARGET}
LIBRARY DESTINATION ${CMAKE_INSTALL_LIBDIR}
COMPONENT ${VPL_COMPONENT_LIB}
NAMELINK_SKIP
RUNTIME DESTINATION ${CMAKE_INSTALL_BINDIR} COMPONENT ${VPL_COMPONENT_LIB})
endif()
if(INSTALL_DEV)
install(
TARGETS ${TARGET}
LIBRARY DESTINATION ${CMAKE_INSTALL_LIBDIR}
COMPONENT ${VPL_COMPONENT_DEV}
NAMELINK_ONLY)
if(WIN32)
install(TARGETS ${TARGET} ARCHIVE DESTINATION ${CMAKE_INSTALL_LIBDIR}
COMPONENT ${VPL_COMPONENT_DEV})
endif()
file(RELATIVE_PATH cmake_rel_prefix ${CMAKE_INSTALL_FULL_LIBDIR}/cmake/vpl
${CMAKE_INSTALL_PREFIX})
file(RELATIVE_PATH cmake_rel_libdir ${CMAKE_INSTALL_FULL_LIBDIR}/cmake/vpl
${CMAKE_INSTALL_FULL_LIBDIR})
file(RELATIVE_PATH cmake_rel_incdir ${CMAKE_INSTALL_FULL_LIBDIR}/cmake/vpl
${CMAKE_INSTALL_FULL_INCLUDEDIR})
file(RELATIVE_PATH cmake_rel_bindir ${CMAKE_INSTALL_FULL_LIBDIR}/cmake/vpl
${CMAKE_INSTALL_FULL_BINDIR})
configure_file(cmake/VPLConfig.cmake.in cmake/VPLConfig.cmake @ONLY)
configure_file(cmake/VPLConfigVersion.cmake.in cmake/VPLConfigVersion.cmake
@ONLY)
install(
FILES "${CMAKE_CURRENT_BINARY_DIR}/cmake/VPLConfig.cmake"
"${CMAKE_CURRENT_BINARY_DIR}/cmake/VPLConfigVersion.cmake"
DESTINATION "${CMAKE_INSTALL_LIBDIR}/cmake/vpl"
COMPONENT ${VPL_COMPONENT_DEV})
file(RELATIVE_PATH pc_rel_prefix ${CMAKE_INSTALL_FULL_LIBDIR}/pkgconfig
${CMAKE_INSTALL_PREFIX})
file(RELATIVE_PATH pc_rel_libdir ${CMAKE_INSTALL_FULL_LIBDIR}/pkgconfig
${CMAKE_INSTALL_FULL_LIBDIR})
file(RELATIVE_PATH pc_rel_incdir ${CMAKE_INSTALL_FULL_LIBDIR}/pkgconfig
${CMAKE_INSTALL_FULL_INCLUDEDIR})
set(pc_rel_prefix "$\{pcfiledir}/${pc_rel_prefix}")
set(pc_rel_libdir "$\{pcfiledir}/${pc_rel_libdir}")
set(pc_rel_incdir "$\{pcfiledir}/${pc_rel_incdir}")
if(CMAKE_DL_LIBS)
set(DL_LIBS "-l${CMAKE_DL_LIBS}")
endif()
if(MINGW)
# being conservative by restricting to MinGW, these are probably ok for
# WIN32 in general
set(MINGW_LIBS "-lole32 -lgdi32 -luuid")
endif()
set(VPL_PKGCONFIG_DEPENDENT_LIBS
"${DL_LIBS} ${CMAKE_THREAD_LIBS_INIT} ${MINGW_LIBS} ${CXX_LIB}")
configure_file("pkgconfig/vpl.pc.in" "pkgconfig/vpl.pc" @ONLY)
install(
FILES "${CMAKE_CURRENT_BINARY_DIR}/pkgconfig/vpl.pc"
DESTINATION "${CMAKE_INSTALL_LIBDIR}/pkgconfig"
COMPONENT ${VPL_COMPONENT_DEV})
endif()
if(BUILD_TESTS)
add_subdirectory(test)
endif()
@@ -0,0 +1,146 @@
# ##############################################################################
# Copyright (C) Intel Corporation
#
# SPDX-License-Identifier: MIT
# ##############################################################################
# * Config file for the VPL package It defines the following variables
# VPL_<component>_FOUND VPL_IMPORTED_TARGETS
#
# VPLConfigVersion.cmake defines VPL_VERSION
option(VPL_SHARED "Use shared instead of static version of dispatcher."
@BUILD_SHARED_LIBS@)
# Initialize to default values
set(VPL_INTERFACE_VERSION @API_VERSION_MAJOR@)
get_filename_component(_vpl_config_file
"${CMAKE_CURRENT_LIST_DIR}/VPLConfig.cmake" REALPATH)
get_filename_component(_vpl_config_dir "${_vpl_config_file}" DIRECTORY)
get_filename_component(_vpl_install_prefix
"${_vpl_config_dir}/@cmake_rel_prefix@" REALPATH)
get_filename_component(VPL_LIB_DIR "${_vpl_config_dir}/@cmake_rel_libdir@"
ABSOLUTE)
get_filename_component(VPL_INCLUDE_DIR "${_vpl_config_dir}/@cmake_rel_incdir@"
ABSOLUTE)
get_filename_component(VPL_BIN_DIR "${_vpl_config_dir}/@cmake_rel_bindir@"
ABSOLUTE)
if(CMAKE_SYSTEM_NAME MATCHES Windows)
set(VPL_SHLIB_DIR ${VPL_BIN_DIR})
else()
set(VPL_SHLIB_DIR ${VPL_LIB_DIR})
endif()
if(NOT VPL_IMPORTED_TARGETS)
set(VPL_IMPORTED_TARGETS "")
endif()
if(NOT VPL_FIND_COMPONENTS)
set(VPL_FIND_COMPONENTS "dispatcher;api")
foreach(_vpl_component ${VPL_FIND_COMPONENTS})
set(VPL_FIND_REQUIRED_${_vpl_component} 1)
endforeach()
endif()
# VPL::dispatcher
set(VPL_dispatcher_FOUND 0)
get_filename_component(_dispatcher_shlib "${VPL_SHLIB_DIR}/@SHLIB_FILE_NAME@"
ABSOLUTE)
get_filename_component(_dispatcher_debug_shlib
"${VPL_SHLIB_DIR}/@SHLIB_FILE_NAME_DEBUG@" ABSOLUTE)
get_filename_component(_dispatcher_lib "${VPL_LIB_DIR}/@LIB_FILE_NAME@"
ABSOLUTE)
get_filename_component(_dispatcher_debug_lib
"${VPL_LIB_DIR}/@LIB_FILE_NAME_DEBUG@" ABSOLUTE)
get_filename_component(_dispatcher_implib "${VPL_LIB_DIR}/@LIB_FILE_NAME@"
ABSOLUTE)
get_filename_component(_dispatcher_debug_implib
"${VPL_LIB_DIR}/@LIB_FILE_NAME_DEBUG@" ABSOLUTE)
if(TARGET VPL::dispatcher)
list(APPEND VPL_IMPORTED_TARGETS VPL::dispatcher)
set(VPL_dispatcher_FOUND 1)
else()
if(VPL_SHARED)
if(EXISTS "${_dispatcher_shlib}" OR EXISTS "${_dispatcher_debug_shlib}")
list(APPEND VPL_IMPORTED_TARGETS VPL::dispatcher)
set(VPL_dispatcher_FOUND 1)
add_library(VPL::dispatcher SHARED IMPORTED)
set_target_properties(
VPL::dispatcher
PROPERTIES IMPORTED_LOCATION_RELEASE ${_dispatcher_shlib}
IMPORTED_LOCATION_RELWITHDEBINFO ${_dispatcher_shlib}
IMPORTED_LOCATION_MINSIZEREL ${_dispatcher_shlib}
IMPORTED_LOCATION_DEBUG ${_dispatcher_debug_shlib}
IMPORTED_LOCATION_RELWITHDEBRT ${_dispatcher_debug_shlib})
if(CMAKE_SYSTEM_NAME MATCHES Windows)
set_target_properties(
VPL::dispatcher
PROPERTIES IMPORTED_IMPLIB_RELEASE ${_dispatcher_implib}
IMPORTED_IMPLIB_RELWITHDEBINFO ${_dispatcher_implib}
IMPORTED_IMPLIB_MINSIZEREL ${_dispatcher_implib}
IMPORTED_IMPLIB_DEBUG ${_dispatcher_debug_implib}
IMPORTED_IMPLIB_RELWITHDEBRT ${_dispatcher_debug_implib})
endif()
elseif(VPL_FIND_REQUIRED AND VPL_FIND_REQUIRED_dispatcher)
message(STATUS "Unable to find required VPL component: dispatcher")
set(VPL_FOUND FALSE)
endif()
else()
if(EXISTS "${_dispatcher_lib}" OR EXISTS "${_dispatcher_debug_lib}")
list(APPEND VPL_IMPORTED_TARGETS VPL::dispatcher)
set(VPL_dispatcher_FOUND 1)
add_library(VPL::dispatcher STATIC IMPORTED)
set_target_properties(
VPL::dispatcher
PROPERTIES IMPORTED_LOCATION_RELEASE ${_dispatcher_lib}
IMPORTED_LOCATION_RELWITHDEBINFO ${_dispatcher_lib}
IMPORTED_LOCATION_MINSIZEREL ${_dispatcher_lib}
IMPORTED_LOCATION_DEBUG ${_dispatcher_debug_lib}
IMPORTED_LOCATION_RELWITHDEBRT ${_dispatcher_debug_lib})
if(UNIX)
# require pthreads for loading legacy MSDK runtimes
set(CMAKE_THREAD_PREFER_PTHREAD TRUE)
set(THREADS_PREFER_PTHREAD_FLAG TRUE)
find_package(Threads REQUIRED)
target_link_libraries(VPL::dispatcher INTERFACE Threads::Threads)
endif()
target_link_libraries(VPL::dispatcher INTERFACE ${CMAKE_DL_LIBS})
elseif(VPL_FIND_REQUIRED AND VPL_FIND_REQUIRED_dispatcher)
message(STATUS "Unable to find required VPL component: dispatcher")
set(VPL_FOUND FALSE)
endif()
endif()
set_target_properties(VPL::dispatcher PROPERTIES INTERFACE_INCLUDE_DIRECTORIES
"${VPL_INCLUDE_DIR}")
endif()
unset(_dispatcher_shlib)
# VPL::api
set(VPL_api_FOUND 0)
if(EXISTS ${VPL_INCLUDE_DIR})
if(NOT TARGET VPL::api)
add_library(VPL::api INTERFACE IMPORTED)
set_target_properties(VPL::api PROPERTIES INTERFACE_INCLUDE_DIRECTORIES
"${VPL_INCLUDE_DIR}")
endif()
list(APPEND VPL_IMPORTED_TARGETS VPL::api)
set(VPL_api_FOUND 1)
endif()
# VPL::cppapi
set(VPL_cppapi_FOUND 0)
if(EXISTS ${VPL_INCLUDE_DIR})
if(NOT TARGET VPL::cppapi)
add_library(VPL::cppapi INTERFACE IMPORTED)
set_target_properties(VPL::cppapi PROPERTIES INTERFACE_INCLUDE_DIRECTORIES
"${VPL_INCLUDE_DIR}")
endif()
list(APPEND VPL_IMPORTED_TARGETS VPL::cppapi)
set(VPL_cppapi_FOUND 1)
endif()
@@ -0,0 +1,17 @@
# ##############################################################################
# Copyright (C) Intel Corporation
#
# SPDX-License-Identifier: MIT
# ##############################################################################
set(PACKAGE_VERSION "@API_VERSION_MAJOR@.@API_VERSION_MINOR@")
# Check whether the requested PACKAGE_FIND_VERSION is compatible
if("${PACKAGE_VERSION}" VERSION_LESS "${PACKAGE_FIND_VERSION}")
set(PACKAGE_VERSION_COMPATIBLE FALSE)
else()
set(PACKAGE_VERSION_COMPATIBLE TRUE)
if("${PACKAGE_VERSION}" VERSION_EQUAL "${PACKAGE_FIND_VERSION}")
set(PACKAGE_VERSION_EXACT TRUE)
endif()
endif()
@@ -0,0 +1,12 @@
prefix=@pc_rel_prefix@
libdir=@pc_rel_libdir@
includedir=@pc_rel_incdir@
Name: Intel(R) Video Processing Library
Description: Accelerated video decode, encode, and frame processing capabilities on Intel(R) GPUs
Version: @API_VERSION_MAJOR@.@API_VERSION_MINOR@
URL: https://github.com/intel/libvpl
Libs: -L${libdir} -l@OUTPUT_NAME@ @VPL_PKGCONFIG_DEPENDENT_LIBS@
Libs.private: @VPL_PKGCONFIG_PRIVATE_LIBS@
Cflags: -I${includedir} -I${includedir}/vpl
@@ -0,0 +1,448 @@
/*############################################################################
# Copyright (C) Intel Corporation
#
# SPDX-License-Identifier: MIT
############################################################################*/
#ifndef LIBVPL_SRC_LINUX_DEVICE_IDS_H_
#define LIBVPL_SRC_LINUX_DEVICE_IDS_H_
// Tables from:
// https://github.com/Intel-Media-SDK/MediaSDK/blob/master/_studio/shared/src/libmfx_core_vaapi.cpp
// https://github.com/Intel-Media-SDK/MediaSDK/blob/master/_studio/shared/include/mfxstructures-int.h
#include <algorithm>
#include <fstream>
#include <string>
#include <vector>
#include "vpl/mfxvideo.h"
enum eMFXHWType {
MFX_HW_UNKNOWN = 0,
MFX_HW_SNB = 0x300000,
MFX_HW_IVB = 0x400000,
MFX_HW_HSW = 0x500000,
MFX_HW_HSW_ULT = 0x500001,
MFX_HW_VLV = 0x600000,
MFX_HW_BDW = 0x700000,
MFX_HW_CHT = 0x800000,
MFX_HW_SCL = 0x900000,
MFX_HW_APL = 0x1000000,
MFX_HW_KBL = 0x1100000,
MFX_HW_GLK = MFX_HW_KBL + 1,
MFX_HW_CFL = MFX_HW_KBL + 2,
MFX_HW_CNL = 0x1200000,
MFX_HW_ICL = 0x1400000,
MFX_HW_ICL_LP = MFX_HW_ICL + 1,
MFX_HW_JSL = 0x1500001,
MFX_HW_EHL = 0x1500002,
};
enum eMFXGTConfig { MFX_GT_UNKNOWN = 0, MFX_GT1 = 1, MFX_GT2 = 2, MFX_GT3 = 3, MFX_GT4 = 4 };
typedef struct {
unsigned int device_id;
eMFXHWType platform;
eMFXGTConfig config;
} mfx_device_item;
// list of legal dev ID for Intel's graphics
static const mfx_device_item listLegalDevIDs[] = {
/*IVB*/
{ 0x0156, MFX_HW_IVB, MFX_GT1 }, /* GT1 mobile */
{ 0x0166, MFX_HW_IVB, MFX_GT2 }, /* GT2 mobile */
{ 0x0152, MFX_HW_IVB, MFX_GT1 }, /* GT1 desktop */
{ 0x0162, MFX_HW_IVB, MFX_GT2 }, /* GT2 desktop */
{ 0x015a, MFX_HW_IVB, MFX_GT1 }, /* GT1 server */
{ 0x016a, MFX_HW_IVB, MFX_GT2 }, /* GT2 server */
/*HSW*/
{ 0x0402, MFX_HW_HSW, MFX_GT1 }, /* GT1 desktop */
{ 0x0412, MFX_HW_HSW, MFX_GT2 }, /* GT2 desktop */
{ 0x0422, MFX_HW_HSW, MFX_GT2 }, /* GT2 desktop */
{ 0x041e, MFX_HW_HSW, MFX_GT2 }, /* Core i3-4130 */
{ 0x040a, MFX_HW_HSW, MFX_GT1 }, /* GT1 server */
{ 0x041a, MFX_HW_HSW, MFX_GT2 }, /* GT2 server */
{ 0x042a, MFX_HW_HSW, MFX_GT2 }, /* GT2 server */
{ 0x0406, MFX_HW_HSW, MFX_GT1 }, /* GT1 mobile */
{ 0x0416, MFX_HW_HSW, MFX_GT2 }, /* GT2 mobile */
{ 0x0426, MFX_HW_HSW, MFX_GT2 }, /* GT2 mobile */
{ 0x0C02, MFX_HW_HSW, MFX_GT1 }, /* SDV GT1 desktop */
{ 0x0C12, MFX_HW_HSW, MFX_GT2 }, /* SDV GT2 desktop */
{ 0x0C22, MFX_HW_HSW, MFX_GT2 }, /* SDV GT2 desktop */
{ 0x0C0A, MFX_HW_HSW, MFX_GT1 }, /* SDV GT1 server */
{ 0x0C1A, MFX_HW_HSW, MFX_GT2 }, /* SDV GT2 server */
{ 0x0C2A, MFX_HW_HSW, MFX_GT2 }, /* SDV GT2 server */
{ 0x0C06, MFX_HW_HSW, MFX_GT1 }, /* SDV GT1 mobile */
{ 0x0C16, MFX_HW_HSW, MFX_GT2 }, /* SDV GT2 mobile */
{ 0x0C26, MFX_HW_HSW, MFX_GT2 }, /* SDV GT2 mobile */
{ 0x0A02, MFX_HW_HSW, MFX_GT1 }, /* ULT GT1 desktop */
{ 0x0A12, MFX_HW_HSW, MFX_GT2 }, /* ULT GT2 desktop */
{ 0x0A22, MFX_HW_HSW, MFX_GT2 }, /* ULT GT2 desktop */
{ 0x0A0A, MFX_HW_HSW, MFX_GT1 }, /* ULT GT1 server */
{ 0x0A1A, MFX_HW_HSW, MFX_GT2 }, /* ULT GT2 server */
{ 0x0A2A, MFX_HW_HSW, MFX_GT2 }, /* ULT GT2 server */
{ 0x0A06, MFX_HW_HSW, MFX_GT1 }, /* ULT GT1 mobile */
{ 0x0A16, MFX_HW_HSW, MFX_GT2 }, /* ULT GT2 mobile */
{ 0x0A26, MFX_HW_HSW, MFX_GT2 }, /* ULT GT2 mobile */
{ 0x0D02, MFX_HW_HSW, MFX_GT1 }, /* CRW GT1 desktop */
{ 0x0D12, MFX_HW_HSW, MFX_GT2 }, /* CRW GT2 desktop */
{ 0x0D22, MFX_HW_HSW, MFX_GT2 }, /* CRW GT2 desktop */
{ 0x0D0A, MFX_HW_HSW, MFX_GT1 }, /* CRW GT1 server */
{ 0x0D1A, MFX_HW_HSW, MFX_GT2 }, /* CRW GT2 server */
{ 0x0D2A, MFX_HW_HSW, MFX_GT2 }, /* CRW GT2 server */
{ 0x0D06, MFX_HW_HSW, MFX_GT1 }, /* CRW GT1 mobile */
{ 0x0D16, MFX_HW_HSW, MFX_GT2 }, /* CRW GT2 mobile */
{ 0x0D26, MFX_HW_HSW, MFX_GT2 }, /* CRW GT2 mobile */
/* this dev IDs added per HSD 5264859 request */
{ 0x040B, MFX_HW_HSW, MFX_GT1 },
/*HASWELL_B_GT1 */ /* Reserved */
{ 0x041B, MFX_HW_HSW, MFX_GT2 }, /*HASWELL_B_GT2*/
{ 0x042B, MFX_HW_HSW, MFX_GT3 }, /*HASWELL_B_GT3*/
{ 0x040E, MFX_HW_HSW, MFX_GT1 },
/*HASWELL_E_GT1*/ /* Reserved */
{ 0x041E, MFX_HW_HSW, MFX_GT2 }, /*HASWELL_E_GT2*/
{ 0x042E, MFX_HW_HSW, MFX_GT3 }, /*HASWELL_E_GT3*/
{ 0x0C0B, MFX_HW_HSW, MFX_GT1 },
/*HASWELL_SDV_B_GT1*/ /* Reserved */
{ 0x0C1B, MFX_HW_HSW, MFX_GT2 }, /*HASWELL_SDV_B_GT2*/
{ 0x0C2B, MFX_HW_HSW, MFX_GT3 }, /*HASWELL_SDV_B_GT3*/
{ 0x0C0E, MFX_HW_HSW, MFX_GT1 },
/*HASWELL_SDV_B_GT1*/ /* Reserved */
{ 0x0C1E, MFX_HW_HSW, MFX_GT2 }, /*HASWELL_SDV_B_GT2*/
{ 0x0C2E, MFX_HW_HSW, MFX_GT3 }, /*HASWELL_SDV_B_GT3*/
{ 0x0A0B, MFX_HW_HSW, MFX_GT1 },
/*HASWELL_ULT_B_GT1*/ /* Reserved */
{ 0x0A1B, MFX_HW_HSW, MFX_GT2 }, /*HASWELL_ULT_B_GT2*/
{ 0x0A2B, MFX_HW_HSW, MFX_GT3 }, /*HASWELL_ULT_B_GT3*/
{ 0x0A0E, MFX_HW_HSW, MFX_GT1 },
/*HASWELL_ULT_E_GT1*/ /* Reserved */
{ 0x0A1E, MFX_HW_HSW, MFX_GT2 }, /*HASWELL_ULT_E_GT2*/
{ 0x0A2E, MFX_HW_HSW, MFX_GT3 }, /*HASWELL_ULT_E_GT3*/
{ 0x0D0B, MFX_HW_HSW, MFX_GT1 },
/*HASWELL_CRW_B_GT1*/ /* Reserved */
{ 0x0D1B, MFX_HW_HSW, MFX_GT2 }, /*HASWELL_CRW_B_GT2*/
{ 0x0D2B, MFX_HW_HSW, MFX_GT3 }, /*HASWELL_CRW_B_GT3*/
{ 0x0D0E, MFX_HW_HSW, MFX_GT1 },
/*HASWELL_CRW_E_GT1*/ /* Reserved */
{ 0x0D1E, MFX_HW_HSW, MFX_GT2 }, /*HASWELL_CRW_E_GT2*/
{ 0x0D2E, MFX_HW_HSW, MFX_GT3 }, /*HASWELL_CRW_E_GT3*/
/* VLV */
{ 0x0f30, MFX_HW_VLV, MFX_GT1 }, /* VLV mobile */
{ 0x0f31, MFX_HW_VLV, MFX_GT1 }, /* VLV mobile */
{ 0x0f32, MFX_HW_VLV, MFX_GT1 }, /* VLV mobile */
{ 0x0f33, MFX_HW_VLV, MFX_GT1 }, /* VLV mobile */
{ 0x0157, MFX_HW_VLV, MFX_GT1 },
{ 0x0155, MFX_HW_VLV, MFX_GT1 },
/* BDW */
/*GT3: */
{ 0x162D, MFX_HW_BDW, MFX_GT3 },
{ 0x162A, MFX_HW_BDW, MFX_GT3 },
/*GT2: */
{ 0x161D, MFX_HW_BDW, MFX_GT2 },
{ 0x161A, MFX_HW_BDW, MFX_GT2 },
/* GT1: */
{ 0x160D, MFX_HW_BDW, MFX_GT1 },
{ 0x160A, MFX_HW_BDW, MFX_GT1 },
/* BDW-ULT */
/* (16x2 - ULT, 16x6 - ULT, 16xB - Iris, 16xE - ULX) */
/*GT3: */
{ 0x162E, MFX_HW_BDW, MFX_GT3 },
{ 0x162B, MFX_HW_BDW, MFX_GT3 },
{ 0x1626, MFX_HW_BDW, MFX_GT3 },
{ 0x1622, MFX_HW_BDW, MFX_GT3 },
{ 0x1636, MFX_HW_BDW, MFX_GT3 }, /* ULT */
{ 0x163B, MFX_HW_BDW, MFX_GT3 }, /* Iris */
{ 0x163E, MFX_HW_BDW, MFX_GT3 }, /* ULX */
{ 0x1632, MFX_HW_BDW, MFX_GT3 }, /* ULT */
{ 0x163A, MFX_HW_BDW, MFX_GT3 }, /* Server */
{ 0x163D, MFX_HW_BDW, MFX_GT3 }, /* Workstation */
/* GT2: */
{ 0x161E, MFX_HW_BDW, MFX_GT2 },
{ 0x161B, MFX_HW_BDW, MFX_GT2 },
{ 0x1616, MFX_HW_BDW, MFX_GT2 },
{ 0x1612, MFX_HW_BDW, MFX_GT2 },
/* GT1: */
{ 0x160E, MFX_HW_BDW, MFX_GT1 },
{ 0x160B, MFX_HW_BDW, MFX_GT1 },
{ 0x1606, MFX_HW_BDW, MFX_GT1 },
{ 0x1602, MFX_HW_BDW, MFX_GT1 },
/* CHT */
{ 0x22b0, MFX_HW_CHT, MFX_GT1 },
{ 0x22b1, MFX_HW_CHT, MFX_GT1 },
{ 0x22b2, MFX_HW_CHT, MFX_GT1 },
{ 0x22b3, MFX_HW_CHT, MFX_GT1 },
/* SCL */
/* GT1F */
{ 0x1902, MFX_HW_SCL, MFX_GT1 }, // DT, 2x1F, 510
{ 0x1906, MFX_HW_SCL, MFX_GT1 }, // U-ULT, 2x1F, 510
{ 0x190A, MFX_HW_SCL, MFX_GT1 }, // Server, 4x1F
{ 0x190B, MFX_HW_SCL, MFX_GT1 },
{ 0x190E, MFX_HW_SCL, MFX_GT1 }, // Y-ULX 2x1F
/*GT1.5*/
{ 0x1913, MFX_HW_SCL, MFX_GT1 }, // U-ULT, 2x1.5
{ 0x1915, MFX_HW_SCL, MFX_GT1 }, // Y-ULX, 2x1.5
{ 0x1917, MFX_HW_SCL, MFX_GT1 }, // DT, 2x1.5
/* GT2 */
{ 0x1912, MFX_HW_SCL, MFX_GT2 }, // DT, 2x2, 530
{ 0x1916, MFX_HW_SCL, MFX_GT2 }, // U-ULD 2x2, 520
{ 0x191A, MFX_HW_SCL, MFX_GT2 }, // 2x2,4x2, Server
{ 0x191B, MFX_HW_SCL, MFX_GT2 }, // DT, 2x2, 530
{ 0x191D, MFX_HW_SCL, MFX_GT2 }, // 4x2, WKS, P530
{ 0x191E, MFX_HW_SCL, MFX_GT2 }, // Y-ULX, 2x2, P510,515
{ 0x1921, MFX_HW_SCL, MFX_GT2 }, // U-ULT, 2x2F, 540
/* GT3 */
{ 0x1923, MFX_HW_SCL, MFX_GT3 }, // U-ULT, 2x3, 535
{ 0x1926, MFX_HW_SCL, MFX_GT3 }, // U-ULT, 2x3, 540 (15W)
{ 0x1927, MFX_HW_SCL, MFX_GT3 }, // U-ULT, 2x3e, 550 (28W)
{ 0x192A, MFX_HW_SCL, MFX_GT3 }, // Server, 2x3
{ 0x192B, MFX_HW_SCL, MFX_GT3 }, // Halo 3e
{ 0x192D, MFX_HW_SCL, MFX_GT3 },
/* GT4e*/
{ 0x1932, MFX_HW_SCL, MFX_GT4 }, // DT
{ 0x193A, MFX_HW_SCL, MFX_GT4 }, // SRV
{ 0x193B, MFX_HW_SCL, MFX_GT4 }, // Halo
{ 0x193D, MFX_HW_SCL, MFX_GT4 }, // WKS
/* APL */
{ 0x0A84, MFX_HW_APL, MFX_GT1 },
{ 0x0A85, MFX_HW_APL, MFX_GT1 },
{ 0x0A86, MFX_HW_APL, MFX_GT1 },
{ 0x0A87, MFX_HW_APL, MFX_GT1 },
{ 0x1A84, MFX_HW_APL, MFX_GT1 },
{ 0x1A85, MFX_HW_APL, MFX_GT1 },
{ 0x5A84, MFX_HW_APL, MFX_GT1 },
{ 0x5A85, MFX_HW_APL, MFX_GT1 },
/* KBL */
{ 0x5902, MFX_HW_KBL, MFX_GT1 }, // DT GT1
{ 0x5906, MFX_HW_KBL, MFX_GT1 }, // ULT GT1
{ 0x5908, MFX_HW_KBL, MFX_GT1 }, // HALO GT1F
{ 0x590A, MFX_HW_KBL, MFX_GT1 }, // SERV GT1
{ 0x590B, MFX_HW_KBL, MFX_GT1 }, // HALO GT1
{ 0x590E, MFX_HW_KBL, MFX_GT1 }, // ULX GT1
{ 0x5912, MFX_HW_KBL, MFX_GT2 }, // DT GT2
{ 0x5913, MFX_HW_KBL, MFX_GT1 }, // ULT GT1 5
{ 0x5915, MFX_HW_KBL, MFX_GT1 }, // ULX GT1 5
{ 0x5916, MFX_HW_KBL, MFX_GT2 }, // ULT GT2
{ 0x5917, MFX_HW_KBL, MFX_GT2 }, // ULT GT2 R
{ 0x591A, MFX_HW_KBL, MFX_GT2 }, // SERV GT2
{ 0x591B, MFX_HW_KBL, MFX_GT2 }, // HALO GT2
{ 0x591C, MFX_HW_KBL, MFX_GT2 }, // ULX GT2
{ 0x591D, MFX_HW_KBL, MFX_GT2 }, // WRK GT2
{ 0x591E, MFX_HW_KBL, MFX_GT2 }, // ULX GT2
{ 0x5921, MFX_HW_KBL, MFX_GT2 }, // ULT GT2F
{ 0x5923, MFX_HW_KBL, MFX_GT3 }, // ULT GT3
{ 0x5926, MFX_HW_KBL, MFX_GT3 }, // ULT GT3 15W
{ 0x5927, MFX_HW_KBL, MFX_GT3 }, // ULT GT3 28W
{ 0x592A, MFX_HW_KBL, MFX_GT3 }, // SERV GT3
{ 0x592B, MFX_HW_KBL, MFX_GT3 }, // HALO GT3
{ 0x5932, MFX_HW_KBL, MFX_GT4 }, // DT GT4
{ 0x593A, MFX_HW_KBL, MFX_GT4 }, // SERV GT4
{ 0x593B, MFX_HW_KBL, MFX_GT4 }, // HALO GT4
{ 0x593D, MFX_HW_KBL, MFX_GT4 }, // WRK GT4
{ 0x87C0, MFX_HW_KBL, MFX_GT2 }, // ULX GT2
/* GLK */
{ 0x3184, MFX_HW_GLK, MFX_GT1 },
{ 0x3185, MFX_HW_GLK, MFX_GT1 },
/* CFL */
{ 0x3E90, MFX_HW_CFL, MFX_GT1 },
{ 0x3E91, MFX_HW_CFL, MFX_GT2 },
{ 0x3E92, MFX_HW_CFL, MFX_GT2 },
{ 0x3E93, MFX_HW_CFL, MFX_GT1 },
{ 0x3E94, MFX_HW_CFL, MFX_GT2 },
{ 0x3E96, MFX_HW_CFL, MFX_GT2 },
{ 0x3E98, MFX_HW_CFL, MFX_GT2 },
{ 0x3E99, MFX_HW_CFL, MFX_GT1 },
{ 0x3E9A, MFX_HW_CFL, MFX_GT2 },
{ 0x3E9C, MFX_HW_CFL, MFX_GT1 },
{ 0x3E9B, MFX_HW_CFL, MFX_GT2 },
{ 0x3EA5, MFX_HW_CFL, MFX_GT3 },
{ 0x3EA6, MFX_HW_CFL, MFX_GT3 },
{ 0x3EA7, MFX_HW_CFL, MFX_GT3 },
{ 0x3EA8, MFX_HW_CFL, MFX_GT3 },
{ 0x3EA9, MFX_HW_CFL, MFX_GT2 },
{ 0x87CA, MFX_HW_CFL, MFX_GT2 },
/* WHL */
{ 0x3EA0, MFX_HW_CFL, MFX_GT2 },
{ 0x3EA1, MFX_HW_CFL, MFX_GT1 },
{ 0x3EA2, MFX_HW_CFL, MFX_GT3 },
{ 0x3EA3, MFX_HW_CFL, MFX_GT2 },
{ 0x3EA4, MFX_HW_CFL, MFX_GT1 },
/* CML GT1 */
{ 0x9b21, MFX_HW_CFL, MFX_GT1 },
{ 0x9baa, MFX_HW_CFL, MFX_GT1 },
{ 0x9bab, MFX_HW_CFL, MFX_GT1 },
{ 0x9bac, MFX_HW_CFL, MFX_GT1 },
{ 0x9ba0, MFX_HW_CFL, MFX_GT1 },
{ 0x9ba5, MFX_HW_CFL, MFX_GT1 },
{ 0x9ba8, MFX_HW_CFL, MFX_GT1 },
{ 0x9ba4, MFX_HW_CFL, MFX_GT1 },
{ 0x9ba2, MFX_HW_CFL, MFX_GT1 },
/* CML GT2 */
{ 0x9b41, MFX_HW_CFL, MFX_GT2 },
{ 0x9bca, MFX_HW_CFL, MFX_GT2 },
{ 0x9bcb, MFX_HW_CFL, MFX_GT2 },
{ 0x9bcc, MFX_HW_CFL, MFX_GT2 },
{ 0x9bc0, MFX_HW_CFL, MFX_GT2 },
{ 0x9bc5, MFX_HW_CFL, MFX_GT2 },
{ 0x9bc8, MFX_HW_CFL, MFX_GT2 },
{ 0x9bc4, MFX_HW_CFL, MFX_GT2 },
{ 0x9bc2, MFX_HW_CFL, MFX_GT2 },
{ 0x9bc6, MFX_HW_CFL, MFX_GT2 },
{ 0x9be6, MFX_HW_CFL, MFX_GT2 },
{ 0x9bf6, MFX_HW_CFL, MFX_GT2 },
/* CNL */
{ 0x5A51, MFX_HW_CNL, MFX_GT2 },
{ 0x5A52, MFX_HW_CNL, MFX_GT2 },
{ 0x5A5A, MFX_HW_CNL, MFX_GT2 },
{ 0x5A40, MFX_HW_CNL, MFX_GT2 },
{ 0x5A42, MFX_HW_CNL, MFX_GT2 },
{ 0x5A4A, MFX_HW_CNL, MFX_GT2 },
{ 0x5A4C, MFX_HW_CNL, MFX_GT1 },
{ 0x5A50, MFX_HW_CNL, MFX_GT2 },
{ 0x5A54, MFX_HW_CNL, MFX_GT1 },
{ 0x5A59, MFX_HW_CNL, MFX_GT2 },
{ 0x5A5C, MFX_HW_CNL, MFX_GT1 },
{ 0x5A41, MFX_HW_CNL, MFX_GT2 },
{ 0x5A44, MFX_HW_CNL, MFX_GT1 },
{ 0x5A49, MFX_HW_CNL, MFX_GT2 },
/* ICL LP */
{ 0xFF05, MFX_HW_ICL_LP, MFX_GT1 },
{ 0x8A50, MFX_HW_ICL_LP, MFX_GT2 },
{ 0x8A51, MFX_HW_ICL_LP, MFX_GT2 },
{ 0x8A52, MFX_HW_ICL_LP, MFX_GT2 },
{ 0x8A53, MFX_HW_ICL_LP, MFX_GT2 },
{ 0x8A54, MFX_HW_ICL_LP, MFX_GT1 },
{ 0x8A56, MFX_HW_ICL_LP, MFX_GT1 },
{ 0x8A57, MFX_HW_ICL_LP, MFX_GT1 },
{ 0x8A58, MFX_HW_ICL_LP, MFX_GT1 },
{ 0x8A59, MFX_HW_ICL_LP, MFX_GT1 },
{ 0x8A5A, MFX_HW_ICL_LP, MFX_GT1 },
{ 0x8A5B, MFX_HW_ICL_LP, MFX_GT1 },
{ 0x8A5C, MFX_HW_ICL_LP, MFX_GT1 },
{ 0x8A5D, MFX_HW_ICL_LP, MFX_GT1 },
{ 0x8A70, MFX_HW_ICL_LP, MFX_GT1 },
{ 0x8A71, MFX_HW_ICL_LP, MFX_GT1 }, // GT05, but 1 ok in this context
/* JSL */
{ 0x4E51, MFX_HW_JSL, MFX_GT2 },
{ 0x4E55, MFX_HW_JSL, MFX_GT2 },
{ 0x4E61, MFX_HW_JSL, MFX_GT2 },
{ 0x4E71, MFX_HW_JSL, MFX_GT2 },
/* EHL */
{ 0x4500, MFX_HW_EHL, MFX_GT2 },
{ 0x4541, MFX_HW_EHL, MFX_GT2 },
{ 0x4551, MFX_HW_EHL, MFX_GT2 },
{ 0x4555, MFX_HW_EHL, MFX_GT2 },
{ 0x4569, MFX_HW_EHL, MFX_GT2 },
{ 0x4571, MFX_HW_EHL, MFX_GT2 },
};
typedef struct {
unsigned int vendor_id;
unsigned int device_id;
eMFXHWType platform;
} Device;
static inline eMFXHWType get_platform(unsigned int device_id) {
for (unsigned i = 0; i < sizeof(listLegalDevIDs) / sizeof(listLegalDevIDs[0]); ++i) {
if (listLegalDevIDs[i].device_id == device_id) {
return listLegalDevIDs[i].platform;
}
}
return MFX_HW_UNKNOWN;
}
static mfxStatus get_devices(std::vector<Device> &allDevices) {
const char *dir = "/sys/class/drm";
const char *device_id_file = "/device/device";
const char *vendor_id_file = "/device/vendor";
int i = 0;
for (; i < 64; ++i) {
Device device;
std::string path = std::string(dir) + "/renderD" + std::to_string(128 + i) + vendor_id_file;
std::string line;
std::ifstream dev_str;
dev_str.open(path);
if (!dev_str.is_open()) {
continue;
}
std::getline(dev_str, line);
dev_str.close();
try {
device.vendor_id = std::stoul(line, 0, 16);
}
catch (std::invalid_argument &) {
continue;
}
catch (std::out_of_range &) {
continue;
}
// Filter out non-Intel devices
if (device.vendor_id != 0x8086)
continue;
path = std::string(dir) + "/renderD" + std::to_string(128 + i) + device_id_file;
dev_str.open(path);
if (!dev_str.is_open()) {
continue;
}
std::getline(dev_str, line);
dev_str.close();
try {
device.device_id = std::stoul(line, 0, 16);
}
catch (std::invalid_argument &) {
continue;
}
catch (std::out_of_range &) {
continue;
}
device.platform = get_platform(device.device_id);
allDevices.emplace_back(device);
}
// sort by platform, unknown will appear at beginning
std::sort(allDevices.begin(), allDevices.end(), [](const Device &a, const Device &b) {
return a.platform < b.platform;
});
if (allDevices.size() == 0)
return MFX_ERR_NOT_FOUND;
return MFX_ERR_NONE;
}
#endif // LIBVPL_SRC_LINUX_DEVICE_IDS_H_
@@ -0,0 +1,81 @@
LIBVPL_2.0 {
global:
MFXInit;
MFXClose;
MFXQueryIMPL;
MFXQueryVersion;
MFXJoinSession;
MFXDisjoinSession;
MFXCloneSession;
MFXSetPriority;
MFXGetPriority;
MFXVideoCORE_SetFrameAllocator;
MFXVideoCORE_SetHandle;
MFXVideoCORE_GetHandle;
MFXVideoCORE_QueryPlatform;
MFXVideoCORE_SyncOperation;
MFXVideoENCODE_Query;
MFXVideoENCODE_QueryIOSurf;
MFXVideoENCODE_Init;
MFXVideoENCODE_Reset;
MFXVideoENCODE_Close;
MFXVideoENCODE_GetVideoParam;
MFXVideoENCODE_GetEncodeStat;
MFXVideoENCODE_EncodeFrameAsync;
MFXVideoDECODE_Query;
MFXVideoDECODE_DecodeHeader;
MFXVideoDECODE_QueryIOSurf;
MFXVideoDECODE_Init;
MFXVideoDECODE_Reset;
MFXVideoDECODE_Close;
MFXVideoDECODE_GetVideoParam;
MFXVideoDECODE_GetDecodeStat;
MFXVideoDECODE_SetSkipMode;
MFXVideoDECODE_GetPayload;
MFXVideoDECODE_DecodeFrameAsync;
MFXVideoVPP_Query;
MFXVideoVPP_QueryIOSurf;
MFXVideoVPP_Init;
MFXVideoVPP_Reset;
MFXVideoVPP_Close;
MFXVideoVPP_GetVideoParam;
MFXVideoVPP_GetVPPStat;
MFXVideoVPP_RunFrameVPPAsync;
MFXInitEx;
MFXLoad;
MFXUnload;
MFXCreateConfig;
MFXSetConfigFilterProperty;
MFXEnumImplementations;
MFXCreateSession;
MFXDispReleaseImplDescription;
MFXMemory_GetSurfaceForVPP;
MFXMemory_GetSurfaceForEncode;
MFXMemory_GetSurfaceForDecode;
local:
*;
};
LIBVPL_2.1 {
global:
MFXMemory_GetSurfaceForVPPOut;
MFXVideoDECODE_VPP_Init;
MFXVideoDECODE_VPP_DecodeFrameAsync;
MFXVideoDECODE_VPP_Reset;
MFXVideoDECODE_VPP_GetChannelParam;
MFXVideoDECODE_VPP_Close;
MFXVideoVPP_ProcessFrameAsync;
local:
*;
} LIBVPL_2.0;
@@ -0,0 +1,842 @@
/*############################################################################
# Copyright (C) Intel Corporation
#
# SPDX-License-Identifier: MIT
############################################################################*/
#include <assert.h>
#include <dlfcn.h>
#include <string.h>
#include <unistd.h>
#include <algorithm>
#include <list>
#include <memory>
#include <mutex>
#include <utility>
#include <vector>
#include "vpl/mfxvideo.h"
// internal implementation of mfxConfigInterface
#include "src/mfx_config_interface/mfx_config_interface.h"
#include "src/linux/device_ids.h"
#include "src/linux/mfxloader.h"
namespace MFX {
#if defined(__x86_64__) || (INTPTR_MAX == INT64_MAX)
#define LIBMFXSW "libmfxsw64.so.1"
#define LIBMFXHW "libmfxhw64.so.1"
#define ONEVPLSW "libvplswref64.so.1"
#define ONEVPLHW "libmfx-gen.so.1.2"
#elif defined(__i386__) || (INTPTR_MAX == INT32_MAX)
#define LIBMFXSW "libmfxsw32.so.1"
#define LIBMFXHW "libmfxhw32.so.1"
#define ONEVPLSW "libvplswref32.so.1"
#define ONEVPLHW "libmfx-gen.so.1.2"
#else
#error Unsupported architecture
#endif
#undef FUNCTION
#define FUNCTION(return_value, func_name, formal_param_list, actual_param_list) e##func_name,
enum Function {
eMFXInit,
eMFXInitEx,
eMFXClose,
eMFXJoinSession,
eMFXVideoCORE_GetHandle,
#include "src/linux/mfxvideo_functions.h"
eFunctionsNum,
eNoMoreFunctions = eFunctionsNum
};
// new functions for API 2.x
enum Function2 {
// 2.0
eMFXQueryImplsDescription = 0,
eMFXReleaseImplDescription,
eMFXMemory_GetSurfaceForVPP,
eMFXMemory_GetSurfaceForEncode,
eMFXMemory_GetSurfaceForDecode,
eMFXInitialize,
// 2.1
eMFXMemory_GetSurfaceForVPPOut,
eMFXVideoDECODE_VPP_Init,
eMFXVideoDECODE_VPP_DecodeFrameAsync,
eMFXVideoDECODE_VPP_Reset,
eMFXVideoDECODE_VPP_GetChannelParam,
eMFXVideoDECODE_VPP_Close,
eMFXVideoVPP_ProcessFrameAsync,
eFunctionsNum2,
};
struct FunctionsTable {
Function id;
const char *name;
mfxVersion version;
};
struct FunctionsTable2 {
Function2 id;
const char *name;
mfxVersion version;
};
#define VERSION(major, minor) \
{ \
{ minor, major } \
}
#undef FUNCTION
#define FUNCTION(return_value, func_name, formal_param_list, actual_param_list) \
{ e##func_name, #func_name, API_VERSION },
static const FunctionsTable g_mfxFuncTable[] = {
{ eMFXInit, "MFXInit", VERSION(1, 0) },
{ eMFXInitEx, "MFXInitEx", VERSION(1, 14) },
{ eMFXClose, "MFXClose", VERSION(1, 0) },
{ eMFXJoinSession, "MFXJoinSession", VERSION(1, 1) },
{ eMFXVideoCORE_GetHandle, "MFXVideoCORE_GetHandle", VERSION(1, 0) },
#include "src/linux/mfxvideo_functions.h" // NOLINT(build/include)
{ eNoMoreFunctions }
};
static const FunctionsTable2 g_mfxFuncTable2[] = {
{ eMFXQueryImplsDescription, "MFXQueryImplsDescription", VERSION(2, 0) },
{ eMFXReleaseImplDescription, "MFXReleaseImplDescription", VERSION(2, 0) },
{ eMFXMemory_GetSurfaceForVPP, "MFXMemory_GetSurfaceForVPP", VERSION(2, 0) },
{ eMFXMemory_GetSurfaceForEncode, "MFXMemory_GetSurfaceForEncode", VERSION(2, 0) },
{ eMFXMemory_GetSurfaceForDecode, "MFXMemory_GetSurfaceForDecode", VERSION(2, 0) },
{ eMFXInitialize, "MFXInitialize", VERSION(2, 0) },
{ eMFXMemory_GetSurfaceForVPPOut, "MFXMemory_GetSurfaceForVPPOut", VERSION(2, 1) },
{ eMFXVideoDECODE_VPP_Init, "MFXVideoDECODE_VPP_Init", VERSION(2, 1) },
{ eMFXVideoDECODE_VPP_DecodeFrameAsync, "MFXVideoDECODE_VPP_DecodeFrameAsync", VERSION(2, 1) },
{ eMFXVideoDECODE_VPP_Reset, "MFXVideoDECODE_VPP_Reset", VERSION(2, 1) },
{ eMFXVideoDECODE_VPP_GetChannelParam, "MFXVideoDECODE_VPP_GetChannelParam", VERSION(2, 1) },
{ eMFXVideoDECODE_VPP_Close, "MFXVideoDECODE_VPP_Close", VERSION(2, 1) },
{ eMFXVideoVPP_ProcessFrameAsync, "MFXVideoVPP_ProcessFrameAsync", VERSION(2, 1) },
};
class LoaderCtx {
public:
mfxStatus Init(mfxInitParam &par,
mfxInitializationParam &vplParam,
mfxU16 *pDeviceID,
char *dllName,
bool bCloneSession = false);
mfxStatus Close();
inline void *getFunction(Function func) const {
return m_table[func];
}
inline void *getFunction2(Function2 func) const {
return m_table2[func];
}
inline mfxSession getSession() const {
return m_session;
}
inline mfxIMPL getImpl() const {
return m_implementation;
}
inline mfxVersion getVersion() const {
return m_version;
}
inline void *getHandle() const {
return m_dlh.get();
}
inline const char *getLibPath() const {
return m_libToLoad.c_str();
}
// special operations to set session pointer and version from MFXCloneSession()
inline void setSession(const mfxSession session) {
m_session = session;
}
inline void setVersion(const mfxVersion version) {
m_version = version;
}
private:
std::shared_ptr<void> m_dlh;
mfxVersion m_version{};
mfxIMPL m_implementation{};
mfxSession m_session = nullptr;
void *m_table[eFunctionsNum]{};
void *m_table2[eFunctionsNum2]{};
std::string m_libToLoad;
};
std::shared_ptr<void> make_dlopen(const char *filename, int flags) {
return std::shared_ptr<void>(dlopen(filename, flags), [](void *handle) {
if (handle)
dlclose(handle);
});
}
mfxStatus LoaderCtx::Init(mfxInitParam &par,
mfxInitializationParam &vplParam,
mfxU16 *pDeviceID,
char *dllName,
bool bCloneSession) {
mfxStatus mfx_res = MFX_ERR_NONE;
std::vector<std::string> libs;
std::vector<Device> devices;
eMFXHWType msdk_platform;
// query graphics device_id
// if it is found on list of legacy devices, load MSDK RT
// otherwise load Intel® Video Processing Library (Intel® VPL) RT
mfxU16 deviceID = 0;
mfx_res = get_devices(devices);
if (mfx_res == MFX_ERR_NOT_FOUND) {
// query failed
msdk_platform = MFX_HW_UNKNOWN;
}
else {
// query succeeded:
// may be a valid platform from listLegalDevIDs[] or MFX_HW_UNKNOWN
// if underlying device_id is unrecognized (i.e. new platform)
msdk_platform = devices[0].platform;
deviceID = devices[0].device_id;
}
if (pDeviceID)
*pDeviceID = deviceID;
if (dllName) {
// attempt to load only this DLL, fail if unsuccessful
// this may also be used later by MFXCloneSession()
m_libToLoad = dllName;
libs.emplace_back(m_libToLoad);
}
else {
mfxIMPL implType = MFX_IMPL_BASETYPE(par.Implementation);
// add HW lib
if (implType == MFX_IMPL_AUTO || implType == MFX_IMPL_AUTO_ANY ||
(implType & MFX_IMPL_HARDWARE) || (implType & MFX_IMPL_HARDWARE_ANY)) {
if (msdk_platform == MFX_HW_UNKNOWN) {
// if not on list of known MSDK platforms, prefer Intel® VPL
libs.emplace_back(ONEVPLHW);
libs.emplace_back(MFX_MODULES_DIR "/" ONEVPLHW);
}
// use MSDK (fallback if Intel® VPL is not installed)
libs.emplace_back(LIBMFXHW);
libs.emplace_back(MFX_MODULES_DIR "/" LIBMFXHW);
}
// add SW lib (Intel® VPL only)
if (implType == MFX_IMPL_AUTO || implType == MFX_IMPL_AUTO_ANY ||
(implType & MFX_IMPL_SOFTWARE)) {
libs.emplace_back(ONEVPLSW);
libs.emplace_back(MFX_MODULES_DIR "/" ONEVPLSW);
}
}
// fail if libs is empty (invalid Implementation)
mfx_res = MFX_ERR_UNSUPPORTED;
for (auto &lib : libs) {
std::shared_ptr<void> hdl = make_dlopen(lib.c_str(), RTLD_LOCAL | RTLD_NOW);
if (hdl) {
do {
/* Loading functions table */
bool wrong_version = false;
for (int i = 0; i < eFunctionsNum; ++i) {
assert(i == g_mfxFuncTable[i].id);
m_table[i] = dlsym(hdl.get(), g_mfxFuncTable[i].name);
if (!m_table[i] && ((g_mfxFuncTable[i].version <= par.Version))) {
wrong_version = true;
break;
}
}
// if version >= 2.0, load these functions as well
if (par.Version.Major >= 2) {
for (int i = 0; i < eFunctionsNum2; ++i) {
assert(i == g_mfxFuncTable2[i].id);
m_table2[i] = dlsym(hdl.get(), g_mfxFuncTable2[i].name);
if (!m_table2[i] && (g_mfxFuncTable2[i].version <= par.Version)) {
wrong_version = true;
break;
}
}
}
if (wrong_version) {
mfx_res = MFX_ERR_UNSUPPORTED;
break;
}
if (bCloneSession == true) {
// success - exit loop since caller will create session with MFXCloneSession()
mfx_res = MFX_ERR_NONE;
break;
}
if (par.Version.Major >= 2) {
// for API >= 2.0 call MFXInitialize instead of MFXInitEx
mfx_res =
((decltype(MFXInitialize) *)m_table2[eMFXInitialize])(vplParam, &m_session);
}
else {
if (m_table[eMFXInitEx]) {
// initialize with MFXInitEx if present (API >= 1.14)
mfx_res = ((decltype(MFXInitEx) *)m_table[eMFXInitEx])(par, &m_session);
}
else {
// initialize with MFXInit for API < 1.14
mfx_res = ((decltype(MFXInit) *)m_table[eMFXInit])(par.Implementation,
&(par.Version),
&m_session);
}
}
if (MFX_ERR_NONE != mfx_res) {
break;
}
// Below we just get some data and double check that we got what we have expected
// to get. Some of these checks are done inside mediasdk init function
mfx_res =
((decltype(MFXQueryVersion) *)m_table[eMFXQueryVersion])(m_session, &m_version);
if (MFX_ERR_NONE != mfx_res) {
break;
}
if (m_version < par.Version) {
mfx_res = MFX_ERR_UNSUPPORTED;
break;
}
mfx_res = ((decltype(MFXQueryIMPL) *)m_table[eMFXQueryIMPL])(m_session,
&m_implementation);
if (MFX_ERR_NONE != mfx_res) {
mfx_res = MFX_ERR_UNSUPPORTED;
break;
}
} while (false);
if (MFX_ERR_NONE == mfx_res) {
m_dlh = std::move(hdl);
break;
}
else {
Close();
}
}
}
return mfx_res;
}
mfxStatus LoaderCtx::Close() {
auto proc = (decltype(MFXClose) *)m_table[eMFXClose];
mfxStatus mfx_res = (proc) ? (*proc)(m_session) : MFX_ERR_NONE;
m_implementation = {};
m_version = {};
m_session = nullptr;
std::fill(std::begin(m_table), std::end(m_table), nullptr);
return mfx_res;
}
} // namespace MFX
// internal function - load a specific DLL, return unsupported if it fails
// vplParam is required for API >= 2.0 (load via MFXInitialize)
mfxStatus MFXInitEx2(mfxVersion version,
mfxInitializationParam vplParam,
mfxIMPL hwImpl,
mfxSession *session,
mfxU16 *deviceID,
char *dllName) {
if (!session)
return MFX_ERR_NULL_PTR;
*deviceID = 0;
// fill minimal 1.x parameters for Init to choose correct initialization path
mfxInitParam par = {};
par.Version = version;
// select first adapter if not specified
// only relevant for MSDK-via-MFXLoad path
if (!hwImpl)
hwImpl = MFX_IMPL_HARDWARE;
switch (vplParam.AccelerationMode) {
case MFX_ACCEL_MODE_NA:
par.Implementation = MFX_IMPL_SOFTWARE;
break;
case MFX_ACCEL_MODE_VIA_D3D9:
par.Implementation = hwImpl | MFX_IMPL_VIA_D3D9;
break;
case MFX_ACCEL_MODE_VIA_D3D11:
par.Implementation = hwImpl | MFX_IMPL_VIA_D3D11;
break;
case MFX_ACCEL_MODE_VIA_VAAPI:
par.Implementation = hwImpl | MFX_IMPL_VIA_VAAPI;
break;
default:
par.Implementation = hwImpl;
break;
}
// also pass extBuf array (if any) to MFXInitEx for 1.x API
par.NumExtParam = vplParam.NumExtParam;
par.ExtParam = (vplParam.NumExtParam ? vplParam.ExtParam : nullptr);
#ifdef ONEVPL_EXPERIMENTAL
// if GPUCopy is enabled via MFXSetConfigProperty(DeviceCopy), set corresponding
// flag in mfxInitParam for legacy RTs
par.GPUCopy = vplParam.DeviceCopy;
#endif
try {
std::unique_ptr<MFX::LoaderCtx> loader;
loader.reset(new MFX::LoaderCtx{});
mfxStatus mfx_res = loader->Init(par, vplParam, deviceID, dllName);
if (MFX_ERR_NONE == mfx_res) {
*session = (mfxSession)loader.release();
}
else {
*session = nullptr;
}
return mfx_res;
}
catch (...) {
return MFX_ERR_MEMORY_ALLOC;
}
}
#ifdef __cplusplus
extern "C" {
#endif
mfxStatus MFXInit(mfxIMPL impl, mfxVersion *ver, mfxSession *session) {
mfxInitParam par{};
par.Implementation = impl;
if (ver) {
par.Version = *ver;
}
else {
par.Version = VERSION(MFX_VERSION_MAJOR, MFX_VERSION_MINOR);
}
return MFXInitEx(par, session);
}
mfxStatus MFXInitEx(mfxInitParam par, mfxSession *session) {
if (!session)
return MFX_ERR_NULL_PTR;
const mfxIMPL implMethod = par.Implementation & (MFX_IMPL_VIA_ANY - 1);
mfxInitializationParam vplParam = {};
if (implMethod == MFX_IMPL_SOFTWARE) {
vplParam.AccelerationMode = MFX_ACCEL_MODE_NA;
}
else {
vplParam.AccelerationMode = MFX_ACCEL_MODE_VIA_VAAPI;
}
try {
std::unique_ptr<MFX::LoaderCtx> loader;
loader.reset(new MFX::LoaderCtx{});
mfxStatus mfx_res = loader->Init(par, vplParam, nullptr, nullptr);
if (MFX_ERR_NONE == mfx_res) {
*session = (mfxSession)loader.release();
}
else {
*session = nullptr;
}
return mfx_res;
}
catch (...) {
return MFX_ERR_MEMORY_ALLOC;
}
}
mfxStatus MFXClose(mfxSession session) {
if (!session)
return MFX_ERR_INVALID_HANDLE;
try {
std::unique_ptr<MFX::LoaderCtx> loader((MFX::LoaderCtx *)session);
mfxStatus mfx_res = loader->Close();
if (mfx_res == MFX_ERR_UNDEFINED_BEHAVIOR) {
// It is possible, that there is an active child session.
// Can't unload library in this case.
loader.release();
}
return mfx_res;
}
catch (...) {
return MFX_ERR_MEMORY_ALLOC;
}
}
// passthrough functions to implementation
mfxStatus MFXMemory_GetSurfaceForVPP(mfxSession session, mfxFrameSurface1 **surface) {
if (!session)
return MFX_ERR_INVALID_HANDLE;
MFX::LoaderCtx *loader = (MFX::LoaderCtx *)session;
auto proc = (decltype(MFXMemory_GetSurfaceForVPP) *)loader->getFunction2(
MFX::eMFXMemory_GetSurfaceForVPP);
if (!proc) {
return MFX_ERR_INVALID_HANDLE;
}
return (*proc)(loader->getSession(), surface);
}
mfxStatus MFXMemory_GetSurfaceForVPPOut(mfxSession session, mfxFrameSurface1 **surface) {
if (!session)
return MFX_ERR_INVALID_HANDLE;
MFX::LoaderCtx *loader = (MFX::LoaderCtx *)session;
auto proc = (decltype(MFXMemory_GetSurfaceForVPPOut) *)loader->getFunction2(
MFX::eMFXMemory_GetSurfaceForVPPOut);
if (!proc) {
return MFX_ERR_INVALID_HANDLE;
}
return (*proc)(loader->getSession(), surface);
}
mfxStatus MFXMemory_GetSurfaceForEncode(mfxSession session, mfxFrameSurface1 **surface) {
if (!session)
return MFX_ERR_INVALID_HANDLE;
MFX::LoaderCtx *loader = (MFX::LoaderCtx *)session;
auto proc = (decltype(MFXMemory_GetSurfaceForEncode) *)loader->getFunction2(
MFX::eMFXMemory_GetSurfaceForEncode);
if (!proc) {
return MFX_ERR_INVALID_HANDLE;
}
return (*proc)(loader->getSession(), surface);
}
mfxStatus MFXMemory_GetSurfaceForDecode(mfxSession session, mfxFrameSurface1 **surface) {
if (!session)
return MFX_ERR_INVALID_HANDLE;
MFX::LoaderCtx *loader = (MFX::LoaderCtx *)session;
auto proc = (decltype(MFXMemory_GetSurfaceForDecode) *)loader->getFunction2(
MFX::eMFXMemory_GetSurfaceForDecode);
if (!proc) {
return MFX_ERR_INVALID_HANDLE;
}
return (*proc)(loader->getSession(), surface);
}
mfxStatus MFXVideoDECODE_VPP_Init(mfxSession session,
mfxVideoParam *decode_par,
mfxVideoChannelParam **vpp_par_array,
mfxU32 num_vpp_par) {
if (!session)
return MFX_ERR_INVALID_HANDLE;
MFX::LoaderCtx *loader = (MFX::LoaderCtx *)session;
auto proc =
(decltype(MFXVideoDECODE_VPP_Init) *)loader->getFunction2(MFX::eMFXVideoDECODE_VPP_Init);
if (!proc) {
return MFX_ERR_INVALID_HANDLE;
}
return (*proc)(loader->getSession(), decode_par, vpp_par_array, num_vpp_par);
}
mfxStatus MFXVideoDECODE_VPP_DecodeFrameAsync(mfxSession session,
mfxBitstream *bs,
mfxU32 *skip_channels,
mfxU32 num_skip_channels,
mfxSurfaceArray **surf_array_out) {
if (!session)
return MFX_ERR_INVALID_HANDLE;
MFX::LoaderCtx *loader = (MFX::LoaderCtx *)session;
auto proc = (decltype(MFXVideoDECODE_VPP_DecodeFrameAsync) *)loader->getFunction2(
MFX::eMFXVideoDECODE_VPP_DecodeFrameAsync);
if (!proc) {
return MFX_ERR_INVALID_HANDLE;
}
return (*proc)(loader->getSession(), bs, skip_channels, num_skip_channels, surf_array_out);
}
mfxStatus MFXVideoDECODE_VPP_Reset(mfxSession session,
mfxVideoParam *decode_par,
mfxVideoChannelParam **vpp_par_array,
mfxU32 num_vpp_par) {
if (!session)
return MFX_ERR_INVALID_HANDLE;
MFX::LoaderCtx *loader = (MFX::LoaderCtx *)session;
auto proc =
(decltype(MFXVideoDECODE_VPP_Reset) *)loader->getFunction2(MFX::eMFXVideoDECODE_VPP_Reset);
if (!proc) {
return MFX_ERR_INVALID_HANDLE;
}
return (*proc)(loader->getSession(), decode_par, vpp_par_array, num_vpp_par);
}
mfxStatus MFXVideoDECODE_VPP_GetChannelParam(mfxSession session,
mfxVideoChannelParam *par,
mfxU32 channel_id) {
if (!session)
return MFX_ERR_INVALID_HANDLE;
MFX::LoaderCtx *loader = (MFX::LoaderCtx *)session;
auto proc = (decltype(MFXVideoDECODE_VPP_GetChannelParam) *)loader->getFunction2(
MFX::eMFXVideoDECODE_VPP_GetChannelParam);
if (!proc) {
return MFX_ERR_INVALID_HANDLE;
}
return (*proc)(loader->getSession(), par, channel_id);
}
mfxStatus MFXVideoDECODE_VPP_Close(mfxSession session) {
if (!session)
return MFX_ERR_INVALID_HANDLE;
MFX::LoaderCtx *loader = (MFX::LoaderCtx *)session;
auto proc =
(decltype(MFXVideoDECODE_VPP_Close) *)loader->getFunction2(MFX::eMFXVideoDECODE_VPP_Close);
if (!proc) {
return MFX_ERR_INVALID_HANDLE;
}
return (*proc)(loader->getSession());
}
mfxStatus MFXVideoVPP_ProcessFrameAsync(mfxSession session,
mfxFrameSurface1 *in,
mfxFrameSurface1 **out) {
if (!session)
return MFX_ERR_INVALID_HANDLE;
MFX::LoaderCtx *loader = (MFX::LoaderCtx *)session;
auto proc = (decltype(MFXVideoVPP_ProcessFrameAsync) *)loader->getFunction2(
MFX::eMFXVideoVPP_ProcessFrameAsync);
if (!proc) {
return MFX_ERR_INVALID_HANDLE;
}
return (*proc)(loader->getSession(), in, out);
}
// implement as a non-passthrough function so that we can catch dispatcher-level interface query requests
mfxStatus MFXVideoCORE_GetHandle(mfxSession session, mfxHandleType type, mfxHDL *hdl) {
if (!session)
return MFX_ERR_INVALID_HANDLE;
MFX::LoaderCtx *loader = (MFX::LoaderCtx *)session;
// first check if handle type points to an interface implemented inside the dispatcher
if (type == MFX_HANDLE_CONFIG_INTERFACE) {
if (!hdl)
return MFX_ERR_NULL_PTR;
*hdl = (mfxHDL)(&(MFX_CONFIG_INTERFACE::g_dispatcher_mfxConfigInterface));
return MFX_ERR_NONE;
}
// passthrough to runtime
auto proc =
(decltype(MFXVideoCORE_GetHandle) *)loader->getFunction(MFX::eMFXVideoCORE_GetHandle);
if (!proc) {
return MFX_ERR_INVALID_HANDLE;
}
return (*proc)(loader->getSession(), type, hdl);
}
mfxStatus MFXJoinSession(mfxSession session, mfxSession child_session) {
if (!session || !child_session) {
return MFX_ERR_INVALID_HANDLE;
}
MFX::LoaderCtx *loader = (MFX::LoaderCtx *)session;
MFX::LoaderCtx *child_loader = (MFX::LoaderCtx *)child_session;
if (loader->getVersion().Version != child_loader->getVersion().Version) {
return MFX_ERR_INVALID_HANDLE;
}
auto proc = (decltype(MFXJoinSession) *)loader->getFunction(MFX::eMFXJoinSession);
if (!proc) {
return MFX_ERR_INVALID_HANDLE;
}
return (*proc)(loader->getSession(), child_loader->getSession());
}
static mfxStatus AllocateCloneLoader(MFX::LoaderCtx *parentLoader, MFX::LoaderCtx **cloneLoader) {
// initialization param structs are not used when bCloneSession == true
mfxInitParam par = {};
mfxInitializationParam vplParam = {};
mfxU16 deviceID = 0;
// initialization extBufs are not saved at this level
// the RT should save these when the parent session is created and may use
// them when creating the cloned session
par.NumExtParam = 0;
try {
std::unique_ptr<MFX::LoaderCtx> cl;
cl.reset(new MFX::LoaderCtx{});
mfxStatus mfx_res =
cl->Init(par, vplParam, &deviceID, (char *)parentLoader->getLibPath(), true);
if (MFX_ERR_NONE == mfx_res) {
*cloneLoader = cl.release();
}
else {
*cloneLoader = nullptr;
}
return mfx_res;
}
catch (...) {
return MFX_ERR_MEMORY_ALLOC;
}
}
mfxStatus MFXCloneSession(mfxSession session, mfxSession *clone) {
if (!session || !clone)
return MFX_ERR_INVALID_HANDLE;
MFX::LoaderCtx *loader = (MFX::LoaderCtx *)session;
mfxVersion version = loader->getVersion();
*clone = nullptr;
// initialize the clone session
// for runtimes with 1.x API, call MFXInit followed by MFXJoinSession
// for runtimes with 2.x API, use RT implementation of MFXCloneSession (passthrough)
if (version.Major == 1) {
mfxStatus mfx_res = MFXInit(loader->getImpl(), &version, clone);
if (MFX_ERR_NONE != mfx_res) {
return mfx_res;
}
// join the sessions
mfx_res = MFXJoinSession(session, *clone);
if (MFX_ERR_NONE != mfx_res) {
MFXClose(*clone);
*clone = nullptr;
return mfx_res;
}
}
else if (version.Major == 2) {
MFX::LoaderCtx *loader = (MFX::LoaderCtx *)session;
// MFXCloneSession not included in function pointer search during init
// for bwd-compat, check for it here and fail gracefully if missing
void *libHandle = loader->getHandle();
auto proc = (decltype(MFXCloneSession) *)(dlsym(libHandle, "MFXCloneSession"));
if (!proc)
return MFX_ERR_UNSUPPORTED;
// allocate new dispatcher-level session object and copy
// state from parent session (function pointer tables, impl type, etc.)
MFX::LoaderCtx *cloneLoader;
mfxStatus mfx_res = AllocateCloneLoader(loader, &cloneLoader);
if (mfx_res != MFX_ERR_NONE)
return mfx_res;
// call RT implementation of MFXCloneSession
mfxSession cloneRT;
mfx_res = (*proc)(loader->getSession(), &cloneRT);
if (mfx_res != MFX_ERR_NONE || cloneRT == NULL) {
// RT call failed, delete cloned loader (no valid session created)
delete cloneLoader;
return MFX_ERR_UNSUPPORTED;
}
cloneLoader->setSession(cloneRT);
// get version of cloned session
mfxVersion cloneVersion = {};
mfx_res = MFXQueryVersion((mfxSession)cloneLoader, &cloneVersion);
cloneLoader->setVersion(cloneVersion);
if (mfx_res != MFX_ERR_NONE) {
MFXClose((mfxSession)cloneLoader);
return mfx_res;
}
*clone = (mfxSession)cloneLoader;
}
else {
return MFX_ERR_UNSUPPORTED;
}
return MFX_ERR_NONE;
}
#undef FUNCTION
#define FUNCTION(return_value, func_name, formal_param_list, actual_param_list) \
return_value MFX_CDECL func_name formal_param_list { \
/* get the function's address and make a call */ \
if (!session) \
return MFX_ERR_INVALID_HANDLE; \
\
MFX::LoaderCtx *loader = (MFX::LoaderCtx *)session; \
\
auto proc = (decltype(func_name) *)loader->getFunction(MFX::e##func_name); \
if (!proc) \
return MFX_ERR_INVALID_HANDLE; \
\
/* get the real session pointer */ \
session = loader->getSession(); \
/* pass down the call */ \
return (*proc)actual_param_list; \
}
#include "src/linux/mfxvideo_functions.h" // NOLINT(build/include)
#ifdef __cplusplus
}
#endif
@@ -0,0 +1,30 @@
/*############################################################################
# Copyright (C) Intel Corporation
#
# SPDX-License-Identifier: MIT
############################################################################*/
#ifndef LIBVPL_SRC_LINUX_MFXLOADER_H_
#define LIBVPL_SRC_LINUX_MFXLOADER_H_
#include <limits.h>
#include <list>
#include <sstream>
#include <string>
#include "vpl/mfxvideo.h"
inline bool operator<(const mfxVersion &lhs, const mfxVersion &rhs) {
return (lhs.Major < rhs.Major || (lhs.Major == rhs.Major && lhs.Minor < rhs.Minor));
}
inline bool operator<=(const mfxVersion &lhs, const mfxVersion &rhs) {
return (lhs < rhs || (lhs.Major == rhs.Major && lhs.Minor == rhs.Minor));
}
inline bool operator==(mfxGUID const &l, mfxGUID const &r) {
return std::equal(l.Data, l.Data + 16, r.Data);
}
#endif // LIBVPL_SRC_LINUX_MFXLOADER_H_
@@ -0,0 +1,176 @@
/*############################################################################
# Copyright (C) Intel Corporation
#
# SPDX-License-Identifier: MIT
############################################################################*/
//
// WARNING:
// this file doesn't contain an include guard by design.
// The file may be included into a source file many times.
// That is why this header doesn't contain any include directive.
// Please, do no try to fix it.
//
// NOLINT(build/header_guard)
// Use define API_VERSION to set the API of functions listed further
// When new functions are added new section with functions declarations must be started with updated define
//
// API version 1.0 functions
//
// API version where a function is added. Minor value should precedes the major value
#define API_VERSION \
{ \
{ 0, 1 } \
}
FUNCTION(mfxStatus, MFXQueryIMPL, (mfxSession session, mfxIMPL *impl), (session, impl))
FUNCTION(mfxStatus, MFXQueryVersion, (mfxSession session, mfxVersion *version), (session, version))
// CORE interface functions
FUNCTION(mfxStatus,
MFXVideoCORE_SetFrameAllocator,
(mfxSession session, mfxFrameAllocator *allocator),
(session, allocator))
FUNCTION(mfxStatus,
MFXVideoCORE_SetHandle,
(mfxSession session, mfxHandleType type, mfxHDL hdl),
(session, type, hdl))
FUNCTION(mfxStatus,
MFXVideoCORE_SyncOperation,
(mfxSession session, mfxSyncPoint syncp, mfxU32 wait),
(session, syncp, wait))
// ENCODE interface functions
FUNCTION(mfxStatus,
MFXVideoENCODE_Query,
(mfxSession session, mfxVideoParam *in, mfxVideoParam *out),
(session, in, out))
FUNCTION(mfxStatus,
MFXVideoENCODE_QueryIOSurf,
(mfxSession session, mfxVideoParam *par, mfxFrameAllocRequest *request),
(session, par, request))
FUNCTION(mfxStatus, MFXVideoENCODE_Init, (mfxSession session, mfxVideoParam *par), (session, par))
FUNCTION(mfxStatus, MFXVideoENCODE_Reset, (mfxSession session, mfxVideoParam *par), (session, par))
FUNCTION(mfxStatus, MFXVideoENCODE_Close, (mfxSession session), (session))
FUNCTION(mfxStatus,
MFXVideoENCODE_GetVideoParam,
(mfxSession session, mfxVideoParam *par),
(session, par))
FUNCTION(mfxStatus,
MFXVideoENCODE_GetEncodeStat,
(mfxSession session, mfxEncodeStat *stat),
(session, stat))
FUNCTION(mfxStatus,
MFXVideoENCODE_EncodeFrameAsync,
(mfxSession session,
mfxEncodeCtrl *ctrl,
mfxFrameSurface1 *surface,
mfxBitstream *bs,
mfxSyncPoint *syncp),
(session, ctrl, surface, bs, syncp))
// DECODE interface functions
FUNCTION(mfxStatus,
MFXVideoDECODE_Query,
(mfxSession session, mfxVideoParam *in, mfxVideoParam *out),
(session, in, out))
FUNCTION(mfxStatus,
MFXVideoDECODE_DecodeHeader,
(mfxSession session, mfxBitstream *bs, mfxVideoParam *par),
(session, bs, par))
FUNCTION(mfxStatus,
MFXVideoDECODE_QueryIOSurf,
(mfxSession session, mfxVideoParam *par, mfxFrameAllocRequest *request),
(session, par, request))
FUNCTION(mfxStatus, MFXVideoDECODE_Init, (mfxSession session, mfxVideoParam *par), (session, par))
FUNCTION(mfxStatus, MFXVideoDECODE_Reset, (mfxSession session, mfxVideoParam *par), (session, par))
FUNCTION(mfxStatus, MFXVideoDECODE_Close, (mfxSession session), (session))
FUNCTION(mfxStatus,
MFXVideoDECODE_GetVideoParam,
(mfxSession session, mfxVideoParam *par),
(session, par))
FUNCTION(mfxStatus,
MFXVideoDECODE_GetDecodeStat,
(mfxSession session, mfxDecodeStat *stat),
(session, stat))
FUNCTION(mfxStatus,
MFXVideoDECODE_SetSkipMode,
(mfxSession session, mfxSkipMode mode),
(session, mode))
FUNCTION(mfxStatus,
MFXVideoDECODE_GetPayload,
(mfxSession session, mfxU64 *ts, mfxPayload *payload),
(session, ts, payload))
FUNCTION(mfxStatus,
MFXVideoDECODE_DecodeFrameAsync,
(mfxSession session,
mfxBitstream *bs,
mfxFrameSurface1 *surface_work,
mfxFrameSurface1 **surface_out,
mfxSyncPoint *syncp),
(session, bs, surface_work, surface_out, syncp))
// VPP interface functions
FUNCTION(mfxStatus,
MFXVideoVPP_Query,
(mfxSession session, mfxVideoParam *in, mfxVideoParam *out),
(session, in, out))
FUNCTION(mfxStatus,
MFXVideoVPP_QueryIOSurf,
(mfxSession session, mfxVideoParam *par, mfxFrameAllocRequest *request),
(session, par, request))
FUNCTION(mfxStatus, MFXVideoVPP_Init, (mfxSession session, mfxVideoParam *par), (session, par))
FUNCTION(mfxStatus, MFXVideoVPP_Reset, (mfxSession session, mfxVideoParam *par), (session, par))
FUNCTION(mfxStatus, MFXVideoVPP_Close, (mfxSession session), (session))
FUNCTION(mfxStatus,
MFXVideoVPP_GetVideoParam,
(mfxSession session, mfxVideoParam *par),
(session, par))
FUNCTION(mfxStatus, MFXVideoVPP_GetVPPStat, (mfxSession session, mfxVPPStat *stat), (session, stat))
FUNCTION(mfxStatus,
MFXVideoVPP_RunFrameVPPAsync,
(mfxSession session,
mfxFrameSurface1 *in,
mfxFrameSurface1 *out,
mfxExtVppAuxData *aux,
mfxSyncPoint *syncp),
(session, in, out, aux, syncp))
#undef API_VERSION
//
// API version 1.1 functions
//
#define API_VERSION \
{ \
{ 1, 1 } \
}
FUNCTION(mfxStatus, MFXDisjoinSession, (mfxSession session), (session))
FUNCTION(mfxStatus, MFXSetPriority, (mfxSession session, mfxPriority priority), (session, priority))
FUNCTION(mfxStatus,
MFXGetPriority,
(mfxSession session, mfxPriority *priority),
(session, priority))
#undef API_VERSION
#define API_VERSION \
{ \
{ 19, 1 } \
}
FUNCTION(mfxStatus,
MFXVideoCORE_QueryPlatform,
(mfxSession session, mfxPlatform *platform),
(session, platform))
#undef API_VERSION
@@ -0,0 +1,145 @@
---
Language: Cpp
AccessModifierOffset: -4
AlignAfterOpenBracket: Align
AlignConsecutiveAssignments: true
AlignConsecutiveDeclarations: false
AlignConsecutiveMacros: true
AlignEscapedNewlines: Left
AlignOperands: true
AlignTrailingComments: false
AllowAllArgumentsOnNextLine: false
AllowAllConstructorInitializersOnNextLine: false
AllowAllParametersOfDeclarationOnNextLine: false
AllowShortBlocksOnASingleLine: false
AllowShortCaseLabelsOnASingleLine: false
AllowShortFunctionsOnASingleLine: Empty
AllowShortIfStatementsOnASingleLine: Never
AllowShortLambdasOnASingleLine: None
AllowShortLoopsOnASingleLine: false
AlwaysBreakAfterDefinitionReturnType: None
AlwaysBreakAfterReturnType: None
AlwaysBreakBeforeMultilineStrings: false
AlwaysBreakTemplateDeclarations: true
BinPackArguments: false
BinPackParameters: false
BraceWrapping:
AfterCaseLabel: false
AfterClass: false
AfterControlStatement: false
AfterEnum: false
AfterFunction: false
AfterNamespace: false
AfterObjCDeclaration: false
AfterStruct: false
AfterUnion: false
AfterExternBlock: false
BeforeCatch: true
BeforeElse: true
IndentBraces: false
SplitEmptyFunction: false
SplitEmptyRecord: false
SplitEmptyNamespace: false
BreakBeforeBinaryOperators: None
BreakBeforeBraces: Custom
BreakBeforeTernaryOperators: true
BreakConstructorInitializers: BeforeColon
BreakInheritanceList: BeforeColon
BreakStringLiterals: false
ColumnLimit: 150
CommentPragmas: '^ IWYU pragma:'
CompactNamespaces: false
ConstructorInitializerAllOnOneLineOrOnePerLine: true
ConstructorInitializerIndentWidth: 8
ContinuationIndentWidth: 4
Cpp11BracedListStyle: false
DerivePointerAlignment: false
DisableFormat: false
FixNamespaceComments: true
ForEachMacros:
- foreach
- Q_FOREACH
- BOOST_FOREACH
IncludeBlocks: Preserve
IncludeCategories:
- Regex: '^<ext/.*\.h>'
Priority: 2
- Regex: '^<.*\.h>'
Priority: 1
- Regex: '^<.*'
Priority: 2
- Regex: '.*'
Priority: 3
IncludeIsMainRegex: '([-_](test|unittest))?$'
IndentCaseLabels: true
IndentPPDirectives: BeforeHash
IndentWidth: 4
IndentWrappedFunctionNames: false
KeepEmptyLinesAtTheStartOfBlocks: false
MacroBlockBegin: ''
MacroBlockEnd: ''
MaxEmptyLinesToKeep: 1
NamespaceIndentation: None
PenaltyBreakAssignment: 2
PenaltyBreakBeforeFirstCallParameter: 1
PenaltyBreakComment: 300
PenaltyBreakFirstLessLess: 120
PenaltyBreakString: 1000
PenaltyBreakTemplateDeclaration: 10
PenaltyExcessCharacter: 1000000
PenaltyReturnTypeOnItsOwnLine: 200
PointerAlignment: Right
RawStringFormats:
- Language: Cpp
Delimiters:
- cc
- CC
- cpp
- Cpp
- CPP
- 'c++'
- 'C++'
CanonicalDelimiter: ''
BasedOnStyle: google
- Language: TextProto
Delimiters:
- pb
- PB
- proto
- PROTO
EnclosingFunctions:
- EqualsProto
- EquivToProto
- PARSE_PARTIAL_TEXT_PROTO
- PARSE_TEST_PROTO
- PARSE_TEXT_PROTO
- ParseTextOrDie
- ParseTextProtoOrDie
CanonicalDelimiter: ''
BasedOnStyle: google
ReflowComments: false
SortIncludes: true
SortUsingDeclarations: false
SpaceAfterCStyleCast: false
SpaceAfterLogicalNot: false
SpaceAfterTemplateKeyword: true
SpaceBeforeAssignmentOperators: true
SpaceBeforeCpp11BracedList: false
SpaceBeforeCtorInitializerColon: true
SpaceBeforeInheritanceColon: true
SpaceBeforeParens: ControlStatements
SpaceBeforeRangeBasedForLoopColon: true
SpaceInEmptyParentheses: false
SpacesBeforeTrailingComments: 1
SpacesInAngles: false
SpacesInContainerLiterals: false
SpacesInCStyleCastParentheses: false
SpacesInParentheses: false
SpacesInSquareBrackets: false
Standard: Cpp11
StatementMacros:
- Q_UNUSED
- QT_REQUIRE_VERSION
TabWidth: 1
UseTab: Never
...
@@ -0,0 +1,110 @@
/*############################################################################
# Copyright (C) Intel Corporation
#
# SPDX-License-Identifier: MIT
############################################################################*/
#include "src/mfx_config_interface/mfx_config_interface.h"
namespace MFX_CONFIG_INTERFACE {
// leave table formatting alone
// clang-format off
// Dispatcher-level implementation of mfxConfigInterface.
// Current implementation is stateless (string API only) so set Context to a special key.
// We could replace Context with pointer to mfxSession (or some object within mfxSession)
// if future extensions require access to the session state.
// The application is not permitted to modify or dereference Context (see struct definition)
// so we can set this to whatever we need.
const mfxConfigInterface g_dispatcher_mfxConfigInterface = {
MFX_CONFIG_INTERFACE_CONTEXT, // Context
{ { 0, 1 } }, // Version
MFX_CONFIG_INTERFACE::ExtSetParameter, // SetParameter (callback function)
{}, // reserved
};
// end table formatting
// clang-format on
// callback function - set mfxConfigInterface::SetParameter to this
// this has to be static since it's returned as function pointer, so cast session to _mfxSession
// to access session-specific state (current implementation is stateless)
mfxStatus ExtSetParameter(struct mfxConfigInterface *config_interface,
const mfxU8 *key,
const mfxU8 *value,
mfxStructureType struct_type,
mfxHDL structure,
mfxExtBuffer *ext_buffer) {
if (struct_type == MFX_STRUCTURE_TYPE_VIDEO_PARAM) {
return SetParameter(key, value, (mfxVideoParam *)structure, ext_buffer);
}
return MFX_ERR_UNSUPPORTED;
}
// validate key and value input strings
mfxStatus ValidateKVPair(const mfxU8 *key, const mfxU8 *value, KVPair &kvStr) {
mfxU32 lengthKey, lengthValue;
// manually check length of key string
for (lengthKey = 0; lengthKey < MAX_PARAM_STRING_LENGTH; lengthKey++) {
if (!key[lengthKey])
break;
}
if (lengthKey == 0 || lengthKey == MAX_PARAM_STRING_LENGTH)
return MFX_ERR_INVALID_VIDEO_PARAM;
// manually check length of value string
for (lengthValue = 0; lengthValue < MAX_PARAM_STRING_LENGTH; lengthValue++) {
if (!value[lengthValue])
break;
}
if (lengthValue == 0 || lengthValue == MAX_PARAM_STRING_LENGTH)
return MFX_ERR_INVALID_VIDEO_PARAM;
kvStr.first = (const char *)key;
kvStr.second = (const char *)value;
return MFX_ERR_NONE;
}
mfxStatus SetParameter(const mfxU8 *key, const mfxU8 *value, mfxVideoParam *videoParam, mfxExtBuffer *extBuf) {
if (!key || !value || !videoParam || !extBuf)
return MFX_ERR_NULL_PTR;
*extBuf = {}; // clear extBuf, will be filled in if new extBuf is required from caller
mfxStatus sts = MFX_ERR_NOT_FOUND; // default if no valid parameters
// validate C-style key and value strings
// output is a pair of std::string's in kvStr
KVPair kvStr;
sts = ValidateKVPair(key, value, kvStr);
if (sts != MFX_ERR_NONE)
return sts;
if (IsExtBuf(kvStr)) {
mfxExtBuffer extBufRequired = {};
// key maps to a parameter in an extension buffer
sts = UpdateExtBufParam(kvStr, videoParam, &extBufRequired);
// if required extension buffer is not attached, we request the app to allocate it
// and then call the SetParameter function again
if (sts == MFX_ERR_MORE_EXTBUFFER) {
extBuf->BufferId = extBufRequired.BufferId;
extBuf->BufferSz = extBufRequired.BufferSz;
}
return sts;
}
// key maps to a parameter in mfxVideoParam
sts = UpdateVideoParam(kvStr, videoParam);
return sts;
}
} // namespace MFX_CONFIG_INTERFACE
@@ -0,0 +1,50 @@
/*############################################################################
# Copyright (C) Intel Corporation
#
# SPDX-License-Identifier: MIT
############################################################################*/
#ifndef LIBVPL_SRC_MFX_CONFIG_INTERFACE_MFX_CONFIG_INTERFACE_H_
#define LIBVPL_SRC_MFX_CONFIG_INTERFACE_MFX_CONFIG_INTERFACE_H_
#include <algorithm>
#include <list>
#include <map>
#include <sstream>
#include <string>
#include <utility>
#include <vector>
#include "vpl/mfxvideo.h"
// special key to indicate Context is not a real context, as it is a stateless interface
#define MFX_CONFIG_INTERFACE_CONTEXT ((mfxHDL)(-1))
namespace MFX_CONFIG_INTERFACE {
// dispatcher returns this interface from call to MFXVideoCORE_GetHandle(type = MFX_HANDLE_CONFIG_INTERFACE)
extern const mfxConfigInterface g_dispatcher_mfxConfigInterface;
// string K-V pairs, each key may only have a single value
typedef std::pair<std::string, std::string> KVPair;
mfxStatus MFX_CDECL ExtSetParameter(struct mfxConfigInterface *config_interface,
const mfxU8 *key,
const mfxU8 *value,
mfxStructureType struct_type,
mfxHDL structure,
mfxExtBuffer *ext_buffer);
mfxStatus SetParameter(const mfxU8 *key, const mfxU8 *value, mfxVideoParam *videoParam, mfxExtBuffer *extBuf);
mfxStatus UpdateVideoParam(const KVPair &kvStr, mfxVideoParam *videoParam);
mfxStatus UpdateExtBufParam(const KVPair &kvStr, mfxVideoParam *videoParam, mfxExtBuffer *extBufRequired);
bool IsExtBuf(const KVPair &kvStr);
mfxStatus ValidateKVPair(const mfxU8 *key, const mfxU8 *value, KVPair &kvStr);
mfxStatus SetExtBufParam(mfxExtBuffer *extBufActual, KVPair &kvStrParsed);
mfxStatus GetExtBufType(const KVPair &kvStr, mfxExtBuffer *extBufHeader, KVPair &kvStrParsed);
}; // namespace MFX_CONFIG_INTERFACE
#endif // LIBVPL_SRC_MFX_CONFIG_INTERFACE_MFX_CONFIG_INTERFACE_H_

Some files were not shown because too many files have changed in this diff Show More