diff --git a/crates/pf-encode/src/enc/linux/mod.rs b/crates/pf-encode/src/enc/linux/mod.rs index 69014d0f..9fc2fc0c 100644 --- a/crates/pf-encode/src/enc/linux/mod.rs +++ b/crates/pf-encode/src/enc/linux/mod.rs @@ -468,7 +468,15 @@ impl NvencEncoder { // sessions) and reopen this session without intra-refresh; any other failure — and // any failure when IR wasn't requested — propagates untouched (the bitrate probe // keys on EINVAL, which must not trip the latch). - Err(e) if intra_refresh && format!("{e:#}").contains("Function not implemented") => { + Err(e) + if intra_refresh + && matches!( + e, + ffmpeg::Error::Other { + errno: ffmpeg::util::error::ENOSYS + } + ) => + { tracing::warn!( encoder = name, "NVENC intra-refresh not supported by this GPU — falling back to IDR-only \ diff --git a/crates/pf-encode/src/lib.rs b/crates/pf-encode/src/lib.rs index 4a20be95..ad25d6e4 100644 --- a/crates/pf-encode/src/lib.rs +++ b/crates/pf-encode/src/lib.rs @@ -897,13 +897,29 @@ fn open_nvenc_probed( } // EINVAL = above this GPU's level ceiling → step down. Any other failure (no GPU, // bad mode, OOM) is real — surface it rather than masking it with bitrate retries. - Err(e) if format!("{e:#}").contains("Invalid argument") => last = Some(e), + Err(e) if nvenc_open_einval(&e) => last = Some(e), Err(e) => return Err(e), } } Err(last.unwrap_or_else(|| anyhow::anyhow!("encoder open failed at every probed bitrate"))) } +/// Whether a libav NVENC open failed with EINVAL — the "bitrate above this GPU's level ceiling" +/// signal [`open_nvenc_probed`]'s ladder steps down on. Typed: the root `ffmpeg::Error` survives +/// the `anyhow` context chain, so match it there instead of substring-matching the English +/// strerror rendering of the whole chain — which also fired on any OTHER wrapped EINVAL (e.g. a +/// CUDA-context errno) and steered the ladder on failures that have nothing to do with bitrate. +#[cfg(target_os = "linux")] +fn nvenc_open_einval(e: &anyhow::Error) -> bool { + use ffmpeg_next as ffmpeg; + matches!( + e.downcast_ref::(), + Some(ffmpeg::Error::Other { + errno: ffmpeg::util::error::EINVAL + }) + ) +} + /// Whether the direct-SDK NVENC path is active. **Default ON** — on-glass validated 2026-07-12: /// real RFI landed 73/73 as clean P-frame recovery anchors (never IDR) on an RTX host with a real /// Steam Deck client (design/linux-direct-nvenc.md §9). `PUNKTFUNK_NVENC_DIRECT=0` (also `false`/