feat(core,host): CODEC_PYROWAVE negotiation — opt-in only, host dispatch wired
audit / bun-audit (push) Successful in 11s
windows-host / package (push) Failing after 48s
ci / docs-site (push) Successful in 50s
ci / web (push) Successful in 52s
android / android (push) Failing after 58s
decky / build-publish (push) Successful in 32s
docker / build-push (., web/Dockerfile, punktfunk-web) (push) Successful in 38s
audit / cargo-audit (push) Successful in 2m15s
windows-msix / package (arm64, C:\Users\Public\ffmpeg-arm64, --no-default-features, aarch64-pc-windows-msvc, C:\t-a64) (push) Successful in 1m43s
windows-msix / package (x64, C:\Users\Public\ffmpeg, , x86_64-pc-windows-msvc, C:\t) (push) Successful in 3m18s
ci / bench (push) Successful in 6m37s
docker / build-push (docs-site, docs-site/Dockerfile, punktfunk-docs) (push) Successful in 10s
deb / build-publish (push) Failing after 8m23s
windows / build (aarch64-pc-windows-msvc) (push) Successful in 4m59s
arch / build-publish (push) Failing after 12m6s
docker / build-push (ci, ci/rust-ci.Dockerfile, punktfunk-rust-ci) (push) Successful in 10m10s
docker / build-push (--build-arg FEDORA_VERSION=44, ci, ci/fedora-rpm.Dockerfile, punktfunk-fedora44-rpm) (push) Successful in 12m54s
docker / build-push (ci, ci/fedora-rpm.Dockerfile, punktfunk-fedora-rpm) (push) Successful in 11m49s
flatpak / build-publish (push) Failing after 8m16s
windows / build (x86_64-pc-windows-msvc) (push) Successful in 5m46s
ci / rust (push) Failing after 19m32s
rpm / build-publish (43, bazzite, punktfunk-fedora-rpm) (push) Failing after 11m33s
rpm / build-publish (44, fedora-44, punktfunk-fedora44-rpm) (push) Failing after 10m56s
docker / deploy-docs (push) Successful in 21s
apple / swift (push) Successful in 4m53s
release / apple (push) Successful in 25m50s
apple / screenshots (push) Successful in 20m55s
audit / bun-audit (push) Successful in 11s
windows-host / package (push) Failing after 48s
ci / docs-site (push) Successful in 50s
ci / web (push) Successful in 52s
android / android (push) Failing after 58s
decky / build-publish (push) Successful in 32s
docker / build-push (., web/Dockerfile, punktfunk-web) (push) Successful in 38s
audit / cargo-audit (push) Successful in 2m15s
windows-msix / package (arm64, C:\Users\Public\ffmpeg-arm64, --no-default-features, aarch64-pc-windows-msvc, C:\t-a64) (push) Successful in 1m43s
windows-msix / package (x64, C:\Users\Public\ffmpeg, , x86_64-pc-windows-msvc, C:\t) (push) Successful in 3m18s
ci / bench (push) Successful in 6m37s
docker / build-push (docs-site, docs-site/Dockerfile, punktfunk-docs) (push) Successful in 10s
deb / build-publish (push) Failing after 8m23s
windows / build (aarch64-pc-windows-msvc) (push) Successful in 4m59s
arch / build-publish (push) Failing after 12m6s
docker / build-push (ci, ci/rust-ci.Dockerfile, punktfunk-rust-ci) (push) Successful in 10m10s
docker / build-push (--build-arg FEDORA_VERSION=44, ci, ci/fedora-rpm.Dockerfile, punktfunk-fedora44-rpm) (push) Successful in 12m54s
docker / build-push (ci, ci/fedora-rpm.Dockerfile, punktfunk-fedora-rpm) (push) Successful in 11m49s
flatpak / build-publish (push) Failing after 8m16s
windows / build (x86_64-pc-windows-msvc) (push) Successful in 5m46s
ci / rust (push) Failing after 19m32s
rpm / build-publish (43, bazzite, punktfunk-fedora-rpm) (push) Failing after 11m33s
rpm / build-publish (44, fedora-44, punktfunk-fedora44-rpm) (push) Failing after 10m56s
docker / deploy-docs (push) Successful in 21s
apple / swift (push) Successful in 4m53s
release / apple (push) Successful in 25m50s
apple / screenshots (push) Successful in 20m55s
Phase 2a+2c of design/pyrowave-codec-plan.md. Core: CODEC_PYROWAVE = 0x08 on Hello::video_codecs/Welcome::codec. Deliberately absent from resolve_codec's precedence ladder (plan §3 — a 100-400 Mbps codec must never win a negotiation by mere mutual support): reachable exclusively through the client's explicit preferred_codec. Invariant tests cover never-auto-selected (even as the only shared codec), preferred-path selection, and graceful fallback. ABI mirror PUNKTFUNK_CODEC_PYROWAVE + lockstep assert for the Apple/Android embedders. Host: Codec::PyroWave variant threaded through the wire mappings; a negotiated PyroWave session routes straight to the backend ahead of the PUNKTFUNK_ENCODER pref dispatch (which stays a lab override). The advertisement bit rides host_wire_caps only when the capture side would actually deliver ingestible frames — linux_zero_copy_is_vaapi(), i.e. AMD/Intel auto or an explicit operator pref on NVIDIA; per-session raw-dmabuf OutputFormat plumbing is recorded as the Phase-3 item. The libavcodec name helpers are dispatch-guarded unreachable; the web console gains ApiCodec::PyroWave (api/openapi.json regenerated). Validated on .21: 308 host tests green with and without the feature, 145 core tests green with quic, clippy clean. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
This commit is contained in:
+3
-2
@@ -10,7 +10,7 @@
|
|||||||
"name": "MIT OR Apache-2.0",
|
"name": "MIT OR Apache-2.0",
|
||||||
"identifier": "MIT OR Apache-2.0"
|
"identifier": "MIT OR Apache-2.0"
|
||||||
},
|
},
|
||||||
"version": "0.9.1"
|
"version": "0.11.0"
|
||||||
},
|
},
|
||||||
"paths": {
|
"paths": {
|
||||||
"/api/v1/clients": {
|
"/api/v1/clients": {
|
||||||
@@ -2047,7 +2047,8 @@
|
|||||||
"enum": [
|
"enum": [
|
||||||
"h264",
|
"h264",
|
||||||
"h265",
|
"h265",
|
||||||
"av1"
|
"av1",
|
||||||
|
"pyrowave"
|
||||||
]
|
]
|
||||||
},
|
},
|
||||||
"ApiDisplayInfo": {
|
"ApiDisplayInfo": {
|
||||||
|
|||||||
@@ -928,6 +928,10 @@ pub const PUNKTFUNK_CODEC_H264: u8 = 0x01;
|
|||||||
pub const PUNKTFUNK_CODEC_HEVC: u8 = 0x02;
|
pub const PUNKTFUNK_CODEC_HEVC: u8 = 0x02;
|
||||||
/// Codec bit: AV1. (Mirrors `quic::CODEC_AV1`.)
|
/// Codec bit: AV1. (Mirrors `quic::CODEC_AV1`.)
|
||||||
pub const PUNKTFUNK_CODEC_AV1: u8 = 0x04;
|
pub const PUNKTFUNK_CODEC_AV1: u8 = 0x04;
|
||||||
|
/// Codec bit: PyroWave — the opt-in wired-LAN intra-only wavelet codec. Never auto-selected:
|
||||||
|
/// the host picks it ONLY when the client also passes it as `preferred_codec`
|
||||||
|
/// (design/pyrowave-codec-plan.md §3). (Mirrors `quic::CODEC_PYROWAVE`.)
|
||||||
|
pub const PUNKTFUNK_CODEC_PYROWAVE: u8 = 0x08;
|
||||||
|
|
||||||
// Keep the ABI cap bits in lockstep with the wire constants (compile-time guard against drift).
|
// Keep the ABI cap bits in lockstep with the wire constants (compile-time guard against drift).
|
||||||
#[cfg(feature = "quic")]
|
#[cfg(feature = "quic")]
|
||||||
@@ -938,6 +942,7 @@ const _: () = {
|
|||||||
assert!(PUNKTFUNK_CODEC_H264 == crate::quic::CODEC_H264);
|
assert!(PUNKTFUNK_CODEC_H264 == crate::quic::CODEC_H264);
|
||||||
assert!(PUNKTFUNK_CODEC_HEVC == crate::quic::CODEC_HEVC);
|
assert!(PUNKTFUNK_CODEC_HEVC == crate::quic::CODEC_HEVC);
|
||||||
assert!(PUNKTFUNK_CODEC_AV1 == crate::quic::CODEC_AV1);
|
assert!(PUNKTFUNK_CODEC_AV1 == crate::quic::CODEC_AV1);
|
||||||
|
assert!(PUNKTFUNK_CODEC_PYROWAVE == crate::quic::CODEC_PYROWAVE);
|
||||||
};
|
};
|
||||||
|
|
||||||
// Keep the ABI gamepad constants in lockstep with the wire enum (compile-time guard against drift).
|
// Keep the ABI gamepad constants in lockstep with the wire enum (compile-time guard against drift).
|
||||||
|
|||||||
@@ -151,15 +151,28 @@ pub const CODEC_H264: u8 = 0x01;
|
|||||||
pub const CODEC_HEVC: u8 = 0x02;
|
pub const CODEC_HEVC: u8 = 0x02;
|
||||||
/// [`Hello::video_codecs`] bit: the client can decode AV1.
|
/// [`Hello::video_codecs`] bit: the client can decode AV1.
|
||||||
pub const CODEC_AV1: u8 = 0x04;
|
pub const CODEC_AV1: u8 = 0x04;
|
||||||
|
/// [`Hello::video_codecs`] bit: the client can decode **PyroWave** — the opt-in wired-LAN
|
||||||
|
/// intra-only wavelet codec (design/pyrowave-codec-plan.md; 100–400 Mbps class, 8-bit SDR,
|
||||||
|
/// every frame independently decodable). Deliberately **absent from [`resolve_codec`]'s
|
||||||
|
/// precedence ladder**: it is selected only when the client also names it
|
||||||
|
/// [`Hello::preferred_codec`] (or the host operator forces the advertisement mask) — a codec
|
||||||
|
/// that needs a wired-LAN bitrate must never win a negotiation just because both ends support
|
||||||
|
/// it. The bit means "PyroWave bitstream as of the punktfunk-vendored pin"
|
||||||
|
/// (`crates/pyrowave-sys/vendor/pyrowave/PUNKTFUNK-VENDOR.txt`): upstream has no bitstream
|
||||||
|
/// version field, so a vendored bump that changes the bitstream bumps the punktfunk protocol
|
||||||
|
/// version instead (plan §4.2).
|
||||||
|
pub const CODEC_PYROWAVE: u8 = 0x08;
|
||||||
|
|
||||||
/// Resolve which single codec the host will emit, from the client's advertised [`Hello::video_codecs`]
|
/// Resolve which single codec the host will emit, from the client's advertised [`Hello::video_codecs`]
|
||||||
/// bitfield (`0` = an older client, treated as HEVC-only) intersected with what the host's chosen
|
/// bitfield (`0` = an older client, treated as HEVC-only) intersected with what the host's chosen
|
||||||
/// encoder can produce (`host_capable`, also a bitfield). `preferred` is the client's soft preference
|
/// encoder can produce (`host_capable`, also a bitfield). `preferred` is the client's soft preference
|
||||||
/// ([`Hello::preferred_codec`], `0` = none): when it's in the shared set it wins; otherwise the tie is
|
/// ([`Hello::preferred_codec`], `0` = none): when it's in the shared set it wins; otherwise the tie is
|
||||||
/// broken by **HEVC > AV1 > H.264** (HEVC is the established, best-tested path; H.264 is the
|
/// broken by **HEVC > AV1 > H.264** (HEVC is the established, best-tested path; H.264 is the
|
||||||
/// compatibility / software floor). Returns the single-bit codec value, or `None` when client and host
|
/// compatibility / software floor). [`CODEC_PYROWAVE`] is intentionally NOT in that ladder — it can
|
||||||
/// share nothing — the caller then refuses the session with a clear error rather than emitting a
|
/// only be returned via the `preferred` path (plan §3: opt-in, pinned, honest). Returns the
|
||||||
/// stream the client can't decode.
|
/// single-bit codec value, or `None` when client and host share nothing the ladder may pick — the
|
||||||
|
/// caller then refuses the session with a clear error rather than emitting a stream the client
|
||||||
|
/// can't decode.
|
||||||
pub fn resolve_codec(client_codecs: u8, host_capable: u8, preferred: u8) -> Option<u8> {
|
pub fn resolve_codec(client_codecs: u8, host_capable: u8, preferred: u8) -> Option<u8> {
|
||||||
// An older client (no codec byte) decodes HEVC — the only codec every pre-negotiation build sent.
|
// An older client (no codec byte) decodes HEVC — the only codec every pre-negotiation build sent.
|
||||||
let client = if client_codecs == 0 {
|
let client = if client_codecs == 0 {
|
||||||
|
|||||||
@@ -101,6 +101,30 @@ fn codec_negotiation_and_back_compat() {
|
|||||||
// A preference the host can't emit still can't rescue a no-shared-codec case.
|
// A preference the host can't emit still can't rescue a no-shared-codec case.
|
||||||
assert_eq!(resolve_codec(CODEC_HEVC, CODEC_H264, CODEC_HEVC), None);
|
assert_eq!(resolve_codec(CODEC_HEVC, CODEC_H264, CODEC_HEVC), None);
|
||||||
|
|
||||||
|
// PyroWave is opt-in ONLY (plan §3): mutual support NEVER auto-selects it — the ladder
|
||||||
|
// ignores it entirely...
|
||||||
|
assert_eq!(
|
||||||
|
resolve_codec(CODEC_HEVC | CODEC_PYROWAVE, CODEC_HEVC | CODEC_PYROWAVE, 0),
|
||||||
|
Some(CODEC_HEVC)
|
||||||
|
);
|
||||||
|
// ...even when it is the ONLY shared codec (an all-intra 200 Mbps stream must never be a
|
||||||
|
// silent fallback)...
|
||||||
|
assert_eq!(resolve_codec(CODEC_PYROWAVE, CODEC_PYROWAVE, 0), None);
|
||||||
|
// ...it is reachable exclusively through the client's explicit preference.
|
||||||
|
assert_eq!(
|
||||||
|
resolve_codec(
|
||||||
|
CODEC_HEVC | CODEC_PYROWAVE,
|
||||||
|
CODEC_HEVC | CODEC_PYROWAVE,
|
||||||
|
CODEC_PYROWAVE
|
||||||
|
),
|
||||||
|
Some(CODEC_PYROWAVE)
|
||||||
|
);
|
||||||
|
// A pyrowave preference against a host without the backend falls back to the ladder.
|
||||||
|
assert_eq!(
|
||||||
|
resolve_codec(CODEC_HEVC | CODEC_PYROWAVE, CODEC_HEVC, CODEC_PYROWAVE),
|
||||||
|
Some(CODEC_HEVC)
|
||||||
|
);
|
||||||
|
|
||||||
// A Hello advertising codecs roundtrips, and the wire form of a codec-only Hello decodes on
|
// A Hello advertising codecs roundtrips, and the wire form of a codec-only Hello decodes on
|
||||||
// a build that ignores the trailing byte (back-compat: extra bytes are skipped).
|
// a build that ignores the trailing byte (back-compat: extra bytes are skipped).
|
||||||
let h = Hello {
|
let h = Hello {
|
||||||
|
|||||||
@@ -37,6 +37,10 @@ pub enum Codec {
|
|||||||
H264,
|
H264,
|
||||||
H265,
|
H265,
|
||||||
Av1,
|
Av1,
|
||||||
|
/// PyroWave — the opt-in wired-LAN intra-only wavelet codec (design/pyrowave-codec-plan.md).
|
||||||
|
/// Only ever negotiated via the client's explicit `preferred_codec` (never the precedence
|
||||||
|
/// ladder) and only emitted by the `pyrowave`-feature backend; every AU is a keyframe.
|
||||||
|
PyroWave,
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Chroma subsampling the encoder emits, negotiated with the client (the `PUNKTFUNK_444` gate + the
|
/// Chroma subsampling the encoder emits, negotiated with the client (the `PUNKTFUNK_444` gate + the
|
||||||
@@ -73,6 +77,7 @@ impl Codec {
|
|||||||
match bit {
|
match bit {
|
||||||
punktfunk_core::quic::CODEC_H264 => Codec::H264,
|
punktfunk_core::quic::CODEC_H264 => Codec::H264,
|
||||||
punktfunk_core::quic::CODEC_AV1 => Codec::Av1,
|
punktfunk_core::quic::CODEC_AV1 => Codec::Av1,
|
||||||
|
punktfunk_core::quic::CODEC_PYROWAVE => Codec::PyroWave,
|
||||||
_ => Codec::H265,
|
_ => Codec::H265,
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -83,6 +88,7 @@ impl Codec {
|
|||||||
Codec::H264 => punktfunk_core::quic::CODEC_H264,
|
Codec::H264 => punktfunk_core::quic::CODEC_H264,
|
||||||
Codec::H265 => punktfunk_core::quic::CODEC_HEVC,
|
Codec::H265 => punktfunk_core::quic::CODEC_HEVC,
|
||||||
Codec::Av1 => punktfunk_core::quic::CODEC_AV1,
|
Codec::Av1 => punktfunk_core::quic::CODEC_AV1,
|
||||||
|
Codec::PyroWave => punktfunk_core::quic::CODEC_PYROWAVE,
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -97,49 +103,74 @@ impl Codec {
|
|||||||
/// still lands on HEVC for an auto client, exactly the pre-probe behaviour. Fed to
|
/// still lands on HEVC for an auto client, exactly the pre-probe behaviour. Fed to
|
||||||
/// [`punktfunk_core::quic::resolve_codec`] against the client's advertised codecs.
|
/// [`punktfunk_core::quic::resolve_codec`] against the client's advertised codecs.
|
||||||
pub fn host_wire_caps() -> u8 {
|
pub fn host_wire_caps() -> u8 {
|
||||||
/// The static GPU superset (H.264 | HEVC | AV1) — mirrors the GameStream
|
// PyroWave rides ON TOP of whatever H.26x set resolves below: feature-gated, Linux-only
|
||||||
/// `SERVER_CODEC_MODE_SUPPORT` advertisement for the unprobed backends.
|
// for now (the Windows host leg is blocked on the .173 D3D11 interop debt), and inert in
|
||||||
const GPU_SUPERSET: u8 = punktfunk_core::quic::CODEC_H264
|
// negotiation unless the client explicitly prefers it (resolve_codec ignores the bit in
|
||||||
| punktfunk_core::quic::CODEC_HEVC
|
// its ladder). Advertised only when the capture side would actually deliver frames the
|
||||||
| punktfunk_core::quic::CODEC_AV1;
|
// backend ingests (raw-dmabuf passthrough / CPU RGB): `linux_zero_copy_is_vaapi()` —
|
||||||
#[cfg(target_os = "linux")]
|
// true on AMD/Intel auto and under an explicit PUNKTFUNK_ENCODER=pyrowave. On an NVIDIA
|
||||||
{
|
// host with `auto`, capture resolves to the EGL→CUDA import the backend can't consume,
|
||||||
if matches!(
|
// so the bit stays off until the OutputFormat plumbing carries a per-session
|
||||||
|
// raw-dmabuf decision (Phase 3); the operator opts in with the env instead (plan §3).
|
||||||
|
#[cfg(all(target_os = "linux", feature = "pyrowave"))]
|
||||||
|
let pyro = if linux_zero_copy_is_vaapi()
|
||||||
|
&& !matches!(
|
||||||
crate::config::config().encoder_pref.as_str(),
|
crate::config::config().encoder_pref.as_str(),
|
||||||
|
// A software pref usually means a GPU-less box — no Vulkan device to open.
|
||||||
"software" | "sw" | "openh264"
|
"software" | "sw" | "openh264"
|
||||||
) {
|
) {
|
||||||
return punktfunk_core::quic::CODEC_H264;
|
punktfunk_core::quic::CODEC_PYROWAVE
|
||||||
|
} else {
|
||||||
|
0u8
|
||||||
|
};
|
||||||
|
#[cfg(not(all(target_os = "linux", feature = "pyrowave")))]
|
||||||
|
let pyro = 0u8;
|
||||||
|
let base = (|| {
|
||||||
|
/// The static GPU superset (H.264 | HEVC | AV1) — mirrors the GameStream
|
||||||
|
/// `SERVER_CODEC_MODE_SUPPORT` advertisement for the unprobed backends.
|
||||||
|
const GPU_SUPERSET: u8 = punktfunk_core::quic::CODEC_H264
|
||||||
|
| punktfunk_core::quic::CODEC_HEVC
|
||||||
|
| punktfunk_core::quic::CODEC_AV1;
|
||||||
|
#[cfg(target_os = "linux")]
|
||||||
|
{
|
||||||
|
if matches!(
|
||||||
|
crate::config::config().encoder_pref.as_str(),
|
||||||
|
"software" | "sw" | "openh264"
|
||||||
|
) {
|
||||||
|
return punktfunk_core::quic::CODEC_H264;
|
||||||
|
}
|
||||||
|
if linux_zero_copy_is_vaapi() {
|
||||||
|
if let Some(m) = vaapi_codec_support().wire_mask() {
|
||||||
|
return m;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
// NVENC (static superset, like GameStream) — or an empty VAAPI probe (see above).
|
||||||
|
GPU_SUPERSET
|
||||||
}
|
}
|
||||||
if linux_zero_copy_is_vaapi() {
|
#[cfg(target_os = "windows")]
|
||||||
if let Some(m) = vaapi_codec_support().wire_mask() {
|
{
|
||||||
return m;
|
if windows_resolved_backend() == WindowsBackend::Software {
|
||||||
|
return punktfunk_core::quic::CODEC_H264;
|
||||||
|
}
|
||||||
|
if windows_backend_is_probed() {
|
||||||
|
if let Some(m) = windows_codec_support().wire_mask() {
|
||||||
|
return m;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
// NVENC (static superset, like GameStream) — or an empty AMF/QSV probe (see above).
|
||||||
|
GPU_SUPERSET
|
||||||
|
}
|
||||||
|
// The macOS dev/test host has no GPU encode backend — keep the pre-probe advertisement.
|
||||||
|
#[cfg(not(any(target_os = "linux", target_os = "windows")))]
|
||||||
|
{
|
||||||
|
let _ = GPU_SUPERSET;
|
||||||
|
match crate::config::config().encoder_pref.as_str() {
|
||||||
|
"software" | "sw" | "openh264" => punktfunk_core::quic::CODEC_H264,
|
||||||
|
_ => punktfunk_core::quic::CODEC_HEVC,
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
// NVENC (static superset, like GameStream) — or an empty VAAPI probe (see above).
|
})();
|
||||||
GPU_SUPERSET
|
base | pyro
|
||||||
}
|
|
||||||
#[cfg(target_os = "windows")]
|
|
||||||
{
|
|
||||||
if windows_resolved_backend() == WindowsBackend::Software {
|
|
||||||
return punktfunk_core::quic::CODEC_H264;
|
|
||||||
}
|
|
||||||
if windows_backend_is_probed() {
|
|
||||||
if let Some(m) = windows_codec_support().wire_mask() {
|
|
||||||
return m;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
// NVENC (static superset, like GameStream) — or an empty AMF/QSV probe (see above).
|
|
||||||
GPU_SUPERSET
|
|
||||||
}
|
|
||||||
// The macOS dev/test host has no GPU encode backend — keep the pre-probe advertisement.
|
|
||||||
#[cfg(not(any(target_os = "linux", target_os = "windows")))]
|
|
||||||
{
|
|
||||||
let _ = GPU_SUPERSET;
|
|
||||||
match crate::config::config().encoder_pref.as_str() {
|
|
||||||
"software" | "sw" | "openh264" => punktfunk_core::quic::CODEC_H264,
|
|
||||||
_ => punktfunk_core::quic::CODEC_HEVC,
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Lowercase stats/console label (`"h264"` / `"hevc"` / `"av1"`) — the codec string seeded into
|
/// Lowercase stats/console label (`"h264"` / `"hevc"` / `"av1"`) — the codec string seeded into
|
||||||
@@ -149,6 +180,7 @@ impl Codec {
|
|||||||
Codec::H264 => "h264",
|
Codec::H264 => "h264",
|
||||||
Codec::H265 => "hevc",
|
Codec::H265 => "hevc",
|
||||||
Codec::Av1 => "av1",
|
Codec::Av1 => "av1",
|
||||||
|
Codec::PyroWave => "pyrowave",
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -159,6 +191,9 @@ impl Codec {
|
|||||||
Codec::H264 => "h264_nvenc",
|
Codec::H264 => "h264_nvenc",
|
||||||
Codec::H265 => "hevc_nvenc",
|
Codec::H265 => "hevc_nvenc",
|
||||||
Codec::Av1 => "av1_nvenc",
|
Codec::Av1 => "av1_nvenc",
|
||||||
|
// Guarded by the open_video dispatch: a PyroWave session never reaches a
|
||||||
|
// libavcodec backend.
|
||||||
|
Codec::PyroWave => unreachable!("PyroWave has no FFmpeg encoder"),
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -172,6 +207,9 @@ impl Codec {
|
|||||||
Codec::H264 => "h264_vaapi",
|
Codec::H264 => "h264_vaapi",
|
||||||
Codec::H265 => "hevc_vaapi",
|
Codec::H265 => "hevc_vaapi",
|
||||||
Codec::Av1 => "av1_vaapi",
|
Codec::Av1 => "av1_vaapi",
|
||||||
|
// Guarded by the open_video dispatch: a PyroWave session never reaches a
|
||||||
|
// libavcodec backend.
|
||||||
|
Codec::PyroWave => unreachable!("PyroWave has no FFmpeg encoder"),
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -182,6 +220,9 @@ impl Codec {
|
|||||||
Codec::H264 => "h264_amf",
|
Codec::H264 => "h264_amf",
|
||||||
Codec::H265 => "hevc_amf",
|
Codec::H265 => "hevc_amf",
|
||||||
Codec::Av1 => "av1_amf",
|
Codec::Av1 => "av1_amf",
|
||||||
|
// Guarded by the open_video dispatch: a PyroWave session never reaches a
|
||||||
|
// libavcodec backend.
|
||||||
|
Codec::PyroWave => unreachable!("PyroWave has no FFmpeg encoder"),
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -192,6 +233,9 @@ impl Codec {
|
|||||||
Codec::H264 => "h264_qsv",
|
Codec::H264 => "h264_qsv",
|
||||||
Codec::H265 => "hevc_qsv",
|
Codec::H265 => "hevc_qsv",
|
||||||
Codec::Av1 => "av1_qsv",
|
Codec::Av1 => "av1_qsv",
|
||||||
|
// Guarded by the open_video dispatch: a PyroWave session never reaches a
|
||||||
|
// libavcodec backend.
|
||||||
|
Codec::PyroWave => unreachable!("PyroWave has no FFmpeg encoder"),
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -324,7 +368,9 @@ impl Codec {
|
|||||||
pub fn max_dimension(self) -> u32 {
|
pub fn max_dimension(self) -> u32 {
|
||||||
match self {
|
match self {
|
||||||
Codec::H264 => 4096,
|
Codec::H264 => 4096,
|
||||||
Codec::H265 | Codec::Av1 => 8192,
|
// PyroWave has no codec-level dimension cap (arbitrary even sizes); 8192 matches the
|
||||||
|
// buffer-math guard the other codecs get.
|
||||||
|
Codec::H265 | Codec::Av1 | Codec::PyroWave => 8192,
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -339,6 +385,9 @@ impl Codec {
|
|||||||
Codec::H264 => 480_000_000,
|
Codec::H264 => 480_000_000,
|
||||||
Codec::H265 => 800_000_000,
|
Codec::H265 => 800_000_000,
|
||||||
Codec::Av1 => 1_200_000_000,
|
Codec::Av1 => 1_200_000_000,
|
||||||
|
// No spec level/tier: the rate is a plain per-frame byte budget. Use the protocol's
|
||||||
|
// own bitrate clamp so the step-down probe logic never binds below it.
|
||||||
|
Codec::PyroWave => 8_000_000_000,
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -523,6 +572,20 @@ fn open_video_backend(
|
|||||||
};
|
};
|
||||||
#[cfg(target_os = "linux")]
|
#[cfg(target_os = "linux")]
|
||||||
{
|
{
|
||||||
|
// A NEGOTIATED PyroWave session (client advertised + preferred it, plan §3) routes
|
||||||
|
// straight to that backend — the PUNKTFUNK_ENCODER pref below stays a lab override.
|
||||||
|
if codec == Codec::PyroWave {
|
||||||
|
#[cfg(feature = "pyrowave")]
|
||||||
|
{
|
||||||
|
return pyrowave::PyroWaveEncoder::open(width, height, fps, bitrate_bps)
|
||||||
|
.map(|e| (Box::new(e) as Box<dyn Encoder>, "pyrowave"));
|
||||||
|
}
|
||||||
|
#[cfg(not(feature = "pyrowave"))]
|
||||||
|
anyhow::bail!(
|
||||||
|
"session negotiated PyroWave but this host was built without --features \
|
||||||
|
punktfunk-host/pyrowave (the advertisement bit should not have been set)"
|
||||||
|
);
|
||||||
|
}
|
||||||
// Pick the GPU encode backend. NVIDIA → NVENC/CUDA (the original path, unchanged);
|
// Pick the GPU encode backend. NVIDIA → NVENC/CUDA (the original path, unchanged);
|
||||||
// AMD/Intel → VAAPI (one libavcodec backend for both). Auto-detect by default so a single
|
// AMD/Intel → VAAPI (one libavcodec backend for both). Auto-detect by default so a single
|
||||||
// Linux binary serves any GPU; `PUNKTFUNK_ENCODER` forces a specific backend (and surfaces
|
// Linux binary serves any GPU; `PUNKTFUNK_ENCODER` forces a specific backend (and surfaces
|
||||||
@@ -657,6 +720,11 @@ fn open_video_backend(
|
|||||||
}
|
}
|
||||||
#[cfg(target_os = "windows")]
|
#[cfg(target_os = "windows")]
|
||||||
{
|
{
|
||||||
|
// The Windows host leg is blocked on the .173 D3D11-interop debt (plan Phase 0 §3);
|
||||||
|
// host_wire_caps never advertises the bit here, so this only guards a forged preference.
|
||||||
|
if codec == Codec::PyroWave {
|
||||||
|
anyhow::bail!("PyroWave host encode is not available on Windows yet");
|
||||||
|
}
|
||||||
let _ = cuda; // always false on Windows (no Cuda payload)
|
let _ = cuda; // always false on Windows (no Cuda payload)
|
||||||
// NVIDIA → NVENC (direct SDK), AMD → AMF, Intel → QSV (both libavcodec), else → software
|
// NVIDIA → NVENC (direct SDK), AMD → AMF, Intel → QSV (both libavcodec), else → software
|
||||||
// H.264. `auto` (the default) resolves from the selected render adapter's vendor.
|
// H.264. `auto` (the default) resolves from the selected render adapter's vendor.
|
||||||
|
|||||||
@@ -88,6 +88,8 @@ fn lp_idx(codec: Codec) -> usize {
|
|||||||
Codec::H264 => 0,
|
Codec::H264 => 0,
|
||||||
Codec::H265 => 1,
|
Codec::H265 => 1,
|
||||||
Codec::Av1 => 2,
|
Codec::Av1 => 2,
|
||||||
|
// Guarded by the open_video dispatch: PyroWave never opens the VAAPI backend.
|
||||||
|
Codec::PyroWave => unreachable!("PyroWave has no VAAPI encoder"),
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -873,6 +873,8 @@ fn parse_spike(args: &[String]) -> Result<Options> {
|
|||||||
Codec::H264 => "h264",
|
Codec::H264 => "h264",
|
||||||
Codec::H265 => "h265",
|
Codec::H265 => "h265",
|
||||||
Codec::Av1 => "obu",
|
Codec::Av1 => "obu",
|
||||||
|
// Raw concatenated PyroWave packets — a lab dump, not an FFmpeg-playable stream.
|
||||||
|
Codec::PyroWave => "pyrowave",
|
||||||
};
|
};
|
||||||
PathBuf::from(format!("/tmp/punktfunk-spike.{ext}"))
|
PathBuf::from(format!("/tmp/punktfunk-spike.{ext}"))
|
||||||
});
|
});
|
||||||
|
|||||||
@@ -310,6 +310,8 @@ enum ApiCodec {
|
|||||||
H264,
|
H264,
|
||||||
H265,
|
H265,
|
||||||
Av1,
|
Av1,
|
||||||
|
/// PyroWave — the opt-in wired-LAN intra-only wavelet codec.
|
||||||
|
PyroWave,
|
||||||
}
|
}
|
||||||
|
|
||||||
impl From<Codec> for ApiCodec {
|
impl From<Codec> for ApiCodec {
|
||||||
@@ -318,6 +320,7 @@ impl From<Codec> for ApiCodec {
|
|||||||
Codec::H264 => ApiCodec::H264,
|
Codec::H264 => ApiCodec::H264,
|
||||||
Codec::H265 => ApiCodec::H265,
|
Codec::H265 => ApiCodec::H265,
|
||||||
Codec::Av1 => ApiCodec::Av1,
|
Codec::Av1 => ApiCodec::Av1,
|
||||||
|
Codec::PyroWave => ApiCodec::PyroWave,
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
Reference in New Issue
Block a user