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

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:
2026-07-15 01:14:04 +02:00
parent 9724fb4a4e
commit e71cb9b7bd
8 changed files with 161 additions and 43 deletions
+3 -2
View File
@@ -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": {
+5
View File
@@ -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).
+16 -3
View File
@@ -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; 100400 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 {
+24
View File
@@ -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 {
+106 -38
View File
@@ -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"),
} }
} }
+2
View File
@@ -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}"))
}); });
+3
View File
@@ -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,
} }
} }
} }