From e27bc690f1d6de95982c3b7be9fa1415520f18b3 Mon Sep 17 00:00:00 2001 From: Mike Solar Date: Thu, 24 Sep 2026 19:10:12 +0800 Subject: [PATCH] oak-task: verify a hardware encoder actually works before using it MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit `ffmpeg -encoders` lists every compiled-in encoder — a full Windows build advertises h264_nvenc/qsv/amf even when the machine has none of that hardware — so probe_hw_encoder picked NVENC on the GPU-less Windows runner and the proxy transcode failed ('Cannot load libcuda.so.1', caught by engine_proxy_generate_and_delete now that the Windows package ships an ffmpeg CLI). Probe a one-frame null encode per candidate and fall through to the next, finally to libx264. Reproduced locally with a full ffmpeg build: h264_nvenc exits 255 without the driver, libx264 succeeds. --- crates/oak-task/src/proxy.rs | 33 ++++++++++++++++++++++++++++++++- 1 file changed, 32 insertions(+), 1 deletion(-) diff --git a/crates/oak-task/src/proxy.rs b/crates/oak-task/src/proxy.rs index 6a5a327e4..d3b071bbb 100644 --- a/crates/oak-task/src/proxy.rs +++ b/crates/oak-task/src/proxy.rs @@ -234,13 +234,44 @@ impl ProxyTask { ] }; for (name, enc) in preference { - if has(name) { + // `-encoders` lists every compiled-in encoder — a full build + // advertises h264_nvenc/qsv/amf even on machines without the + // matching hardware — so a one-frame null encode decides + // whether the candidate can actually initialize. Failures fall + // through to the next candidate and finally to software (the + // old list-only check made proxy generation fail outright on + // GPU-less Windows machines with such a build). + if has(name) && Self::encoder_usable(ffmpeg_path, name) { return *enc; } } HwEncoder::Software } + /// Whether `encoder` can initialize and encode one tiny frame; see the + /// caller for why the `-encoders` list is not enough. + fn encoder_usable(ffmpeg_path: &str, encoder: &str) -> bool { + let out = Command::new(ffmpeg_path) + .args([ + "-hide_banner", + "-loglevel", + "error", + "-f", + "lavfi", + "-i", + "color=size=64x64:rate=1:duration=1", + "-frames:v", + "1", + "-c:v", + encoder, + "-f", + "null", + "-", + ]) + .output(); + matches!(out, Ok(out) if out.status.success()) + } + /// libx264 预设名 → NVENC p1..p7(NVENC 预设是编号)。 fn nvenc_preset(preset: &str) -> &'static str { match preset {