diff --git a/.github/workflows/release.yml b/.github/workflows/release.yml index ad04e82..64c4560 100644 --- a/.github/workflows/release.yml +++ b/.github/workflows/release.yml @@ -78,7 +78,7 @@ jobs: run: | mkdir -p "${{ github.workspace }}/.vcpkg-cache" /opt/vcpkg/vcpkg install \ - 'ffmpeg[core,avcodec,avformat,swscale,swresample]:x64-linux-static' \ + 'ffmpeg[core,avcodec,avformat,swscale,swresample,nvcodec]:x64-linux-static' \ --overlay-triplets="$GITHUB_WORKSPACE/.github/vcpkg-triplets" - name: Install stable Rust @@ -110,6 +110,17 @@ jobs: PKG_CONFIG_ALL_STATIC: "1" run: cargo build --release --bin wallr --features static-ffmpeg + - name: Verify static FFmpeg hardware support + env: + MEDIA_PREFIX: /opt/vcpkg/installed/x64-linux-static + PKG_CONFIG_PATH: /opt/vcpkg/installed/x64-linux-static/lib/pkgconfig + PKG_CONFIG_ALL_STATIC: "1" + run: | + set -euo pipefail + capabilities=$(cargo run -q -p wallr-core --example video_probe --release --features static-ffmpeg -- --capabilities) + printf '%s\n' "$capabilities" + grep -qx 'cuda: enabled' <<<"$capabilities" + - name: Verify binary is self-contained run: | set -euo pipefail diff --git a/CHANGELOG.md b/CHANGELOG.md index 31107c9..f284b85 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -25,6 +25,10 @@ and versions follow [Semantic Versioning](https://semver.org/). ### Fixed +- Linux release binaries now include FFmpeg NVCodec support, so configured + NVDEC playback no longer silently falls back to CPU decoding. +- Explicit hardware decode failures are reported as software fallback rather + than ordinary software decoding. - Fixed false hardware-decoding reports when a decoder opened but produced no hardware frames. - Fixed decoder shutdown caused by transient queue pressure. diff --git a/docs/video-wallpaper.md b/docs/video-wallpaper.md index 5ac70d8..cbe2803 100644 --- a/docs/video-wallpaper.md +++ b/docs/video-wallpaper.md @@ -20,6 +20,9 @@ MP4, WebM, MKV, MOV, and AVI. Anything FFmpeg can demux is a candidate; the FFmp - Frames are decoded into a small bounded latest-frame queue and presented on PTS timing. Temporary queue pressure drops stale frames instead of terminating the decoder or allowing memory growth. - `wallpaper.loop_video` (default `true`) restarts the stream when it ends, producing a continuous loop. - `wallr ipc info` distinguishes hardware negotiation, active hardware frames, software decoding, software fallback, and decoder failure. A backend is not reported as active until a hardware frame has actually been received. +- Official Linux release binaries include FFmpeg NVCodec support; NVDEC still + loads the installed NVIDIA driver at runtime and falls back cleanly when it + is unavailable. ## Playback control diff --git a/scripts/benchmark-video.sh b/scripts/benchmark-video.sh index be2ac7c..a31e20c 100755 --- a/scripts/benchmark-video.sh +++ b/scripts/benchmark-video.sh @@ -10,22 +10,52 @@ report=${BENCHMARK_REPORT:-"$root_dir/benchmarks/video-$(date -u +%Y%m%d-%H%M%S) [[ -s "$input" ]] || { echo "error: missing video: $input" >&2; exit 2; } mkdir -p "$(dirname "$report")" +probe="$root_dir/target/release/examples/video_probe" +cargo build -q -p wallr-core --example video_probe --release run_probe() { local backend=$1 - cargo run -q -p wallr-core --example video_probe --release -- "$input" "$backend" 2>&1 + TIMEFORMAT='wall_seconds=%R user_seconds=%U system_seconds=%S' + { time "$probe" "$input" "$backend"; } 2>&1 } -software=$(run_probe software) +software=$(run_probe software) || { printf '%s\n' "$software" >&2; exit 1; } +if ! grep -qx 'result: PASS' <<<"$software"; then + printf 'error: software probe failed; no benchmark report generated\n%s\n' "$software" >&2 + exit 1 +fi vaapi=$(run_probe vaapi || true) +nvdec=$(run_probe nvdec || true) frames=$(awk '/^decoded / {print $2; exit}' <<<"$software") +vaapi_frames=$(awk '/^decoded / {print $2; exit}' <<<"$vaapi") +nvdec_frames=$(awk '/^decoded / {print $2; exit}' <<<"$nvdec") software_rate=$(sed -n 's/^decoded .* (\([0-9.]*\) fps,.*/\1/p' <<<"$software" | head -n1) -software_state=$(sed -n 's/^active backend: \([^ ]*\) (state:.*/\1/p' <<<"$software" | head -n1) +software_state=$(sed -n 's/^active backend: .* (state: \([^,]*\),.*/\1/p' <<<"$software" | head -n1) vaapi_rate=$(sed -n 's/^decoded .* (\([0-9.]*\) fps,.*/\1/p' <<<"$vaapi" | head -n1) -vaapi_state=$(sed -n 's/^active backend: \([^ ]*\) (state:.*/\1/p' <<<"$vaapi" | head -n1) +vaapi_backend=$(sed -n 's/^active backend: \([^ ]*\) (state:.*/\1/p' <<<"$vaapi" | head -n1) +vaapi_state=$(sed -n 's/^active backend: .* (state: \([^,]*\),.*/\1/p' <<<"$vaapi" | head -n1) +nvdec_rate=$(sed -n 's/^decoded .* (\([0-9.]*\) fps,.*/\1/p' <<<"$nvdec" | head -n1) +nvdec_backend=$(sed -n 's/^active backend: \([^ ]*\) (state:.*/\1/p' <<<"$nvdec" | head -n1) +nvdec_state=$(sed -n 's/^active backend: .* (state: \([^,]*\),.*/\1/p' <<<"$nvdec" | head -n1) software_drops=$(awk -F'dropped frames: ' '/^active backend:/ {gsub(/\).*/, "", $2); print $2; exit}' <<<"$software") vaapi_drops=$(awk -F'dropped frames: ' '/^active backend:/ {gsub(/\).*/, "", $2); print $2; exit}' <<<"$vaapi") +nvdec_drops=$(awk -F'dropped frames: ' '/^active backend:/ {gsub(/\).*/, "", $2); print $2; exit}' <<<"$nvdec") +software_cpu=$(awk -F'[ =]' '/^wall_seconds=/ {printf "%.3f", $4 + $6; exit}' <<<"$software") +vaapi_cpu=$(awk -F'[ =]' '/^wall_seconds=/ {printf "%.3f", $4 + $6; exit}' <<<"$vaapi") +nvdec_cpu=$(awk -F'[ =]' '/^wall_seconds=/ {printf "%.3f", $4 + $6; exit}' <<<"$nvdec") + +vaapi_reason="" +if [[ $vaapi_backend != VAAPI || $vaapi_state != 'hardware active' ]] || ! grep -qx 'result: PASS' <<<"$vaapi" || ! grep -qx 'fallback occurred: false' <<<"$vaapi"; then + vaapi_reason="probe failed, fell back, or did not remain hardware active (backend: ${vaapi_backend:-unavailable}, state: ${vaapi_state:-unavailable})" + vaapi_frames= vaapi_rate= vaapi_cpu= vaapi_drops= +fi + +nvdec_reason="" +if [[ $nvdec_backend != NVDEC || $nvdec_state != 'hardware active' ]] || ! grep -qx 'result: PASS' <<<"$nvdec" || ! grep -qx 'fallback occurred: false' <<<"$nvdec"; then + nvdec_reason="probe failed, fell back, or did not remain hardware active (backend: ${nvdec_backend:-unavailable}, state: ${nvdec_state:-unavailable})" + nvdec_frames= nvdec_rate= nvdec_cpu= nvdec_drops= +fi { echo "# Wallr video decoder benchmark" @@ -33,14 +63,17 @@ vaapi_drops=$(awk -F'dropped frames: ' '/^active backend:/ {gsub(/\).*/, "", $2) echo "Machine-specific decoder measurements. This report does not measure compositor presentation or GPU render time." echo printf -- '- Date (UTC): %s\n' "$(date -u '+%Y-%m-%d %H:%M:%S')" - printf -- '- Host: %s\n' "$(hostname)" + printf -- '- Host: %s\n' "$(uname -n)" printf -- '- Input: `%s`\n' "$input" printf -- '- Wallr version: %s\n' "$(cargo run -q -p wallr -- --version 2>/dev/null)" echo - echo "| Backend | Frames | Throughput (fps) | Decoder state | Dropped frames |" - echo "|:--|--:|--:|:--|--:|" - printf '| Software | %s | %s | %s | %s |\n' "${frames:-unavailable}" "${software_rate:-unavailable}" "${software_state:-unavailable}" "${software_drops:-unavailable}" - printf '| VAAPI | %s | %s | %s | %s |\n' "${frames:-unavailable}" "${vaapi_rate:-unavailable}" "${vaapi_state:-unavailable}" "${vaapi_drops:-unavailable}" + echo "| Backend | Frames | Throughput (fps) | CPU time (s) | Decoder state | Dropped frames |" + echo "|:--|--:|--:|--:|:--|--:|" + printf '| Software | %s | %s | %s | %s | %s |\n' "${frames:-unavailable}" "${software_rate:-unavailable}" "${software_cpu:-unavailable}" "${software_state:-unavailable}" "${software_drops:-unavailable}" + printf '| VAAPI | %s | %s | %s | %s | %s |\n' "${vaapi_frames:-unavailable}" "${vaapi_rate:-unavailable}" "${vaapi_cpu:-unavailable}" "${vaapi_state:-unavailable}" "${vaapi_drops:-unavailable}" + printf '| NVDEC | %s | %s | %s | %s | %s |\n' "${nvdec_frames:-unavailable}" "${nvdec_rate:-unavailable}" "${nvdec_cpu:-unavailable}" "${nvdec_state:-unavailable}" "${nvdec_drops:-unavailable}" + [[ -z $vaapi_reason ]] || printf '\n- VAAPI unavailable: %s.\n' "$vaapi_reason" + [[ -z $nvdec_reason ]] || printf '\n- NVDEC unavailable: %s.\n' "$nvdec_reason" } >"$report" echo "Report saved to: $report" diff --git a/wallr-core/examples/video_probe.rs b/wallr-core/examples/video_probe.rs index 6ced2d0..363f384 100644 --- a/wallr-core/examples/video_probe.rs +++ b/wallr-core/examples/video_probe.rs @@ -11,6 +11,10 @@ use wallr_core::video::{HwAccel, VideoDecoder}; fn main() { let mut args = std::env::args().skip(1); let path = args.next().expect("usage: video_probe