From e4a4ca1c9dbffc905654436ed9b2996f254a40c9 Mon Sep 17 00:00:00 2001 From: Brad Stein Date: Tue, 19 May 2026 12:54:29 -0300 Subject: [PATCH] fix: allow proven software HEVC handoff --- Cargo.lock | 6 ++-- client/Cargo.toml | 2 +- common/Cargo.toml | 2 +- scripts/install/server.sh | 33 +++++++++++++++++++ server/Cargo.toml | 2 +- .../install/server_install_script_contract.rs | 8 +++-- ...stall_preserves_codec_settings_contract.rs | 12 +++++-- 7 files changed, 53 insertions(+), 12 deletions(-) diff --git a/Cargo.lock b/Cargo.lock index 42383ea..3bcf692 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -1658,7 +1658,7 @@ checksum = "09edd9e8b54e49e587e4f6295a7d29c3ea94d469cb40ab8ca70b288248a81db2" [[package]] name = "lesavka_client" -version = "0.26.0" +version = "0.26.1" dependencies = [ "anyhow", "async-stream", @@ -1692,7 +1692,7 @@ dependencies = [ [[package]] name = "lesavka_common" -version = "0.26.0" +version = "0.26.1" dependencies = [ "anyhow", "base64", @@ -1704,7 +1704,7 @@ dependencies = [ [[package]] name = "lesavka_server" -version = "0.26.0" +version = "0.26.1" dependencies = [ "anyhow", "base64", diff --git a/client/Cargo.toml b/client/Cargo.toml index 3095742..4724ffc 100644 --- a/client/Cargo.toml +++ b/client/Cargo.toml @@ -4,7 +4,7 @@ path = "src/main.rs" [package] name = "lesavka_client" -version = "0.26.0" +version = "0.26.1" edition = "2024" [dependencies] diff --git a/common/Cargo.toml b/common/Cargo.toml index 36786b0..250f9ff 100644 --- a/common/Cargo.toml +++ b/common/Cargo.toml @@ -1,6 +1,6 @@ [package] name = "lesavka_common" -version = "0.26.0" +version = "0.26.1" edition = "2024" build = "build.rs" diff --git a/scripts/install/server.sh b/scripts/install/server.sh index cf7bcf3..18db7c0 100755 --- a/scripts/install/server.sh +++ b/scripts/install/server.sh @@ -110,6 +110,8 @@ if [[ -n "${LESAVKA_INSTALL_CAM_CODEC+x}" || -n "${LESAVKA_CAM_CODEC+x}" ]]; the fi REQUESTED_CAM_CODEC=${LESAVKA_INSTALL_CAM_CODEC:-${LESAVKA_CAM_CODEC:-hevc}} INSTALL_CAM_CODEC=$(normalize_cam_codec "${REQUESTED_CAM_CODEC}") +INSTALL_HEVC_DECODER=${LESAVKA_INSTALL_HEVC_DECODER:-${LESAVKA_HEVC_DECODER:-}} +INSTALL_ALLOW_SOFTWARE_VIDEO=${LESAVKA_INSTALL_ALLOW_SOFTWARE_VIDEO:-${LESAVKA_ALLOW_SOFTWARE_VIDEO:-0}} INSTALL_UPLINK_AUDIO_CODEC=${LESAVKA_INSTALL_UPLINK_AUDIO_CODEC:-${LESAVKA_UPLINK_AUDIO_CODEC:-pcm}} INSTALL_UVC_FRAME_META=${LESAVKA_INSTALL_UVC_FRAME_META:-${LESAVKA_UVC_FRAME_META:-0}} INSTALL_UVC_FRAME_META_LOG_PATH=${LESAVKA_INSTALL_UVC_FRAME_META_LOG_PATH:-${LESAVKA_UVC_FRAME_META_LOG_PATH:-/tmp/lesavka-uvc-frame-meta.jsonl}} @@ -288,6 +290,29 @@ ensure_hevc_decode_support() { echo "✅ hardware HEVC decoder passed a real 1280x720 decode smoke: $hevc_decoder" else if [[ "$INSTALL_CAM_CODEC" == "hevc" ]]; then + local software_hevc_decoder="" + local software_hevc_smoke_log + software_hevc_smoke_log=$(mktemp "${TMPDIR}/lesavka-hevc-software-decode-smoke.XXXXXX.log") + for candidate in avdec_h265 libde265dec; do + if gst-inspect-1.0 "$candidate" >/dev/null 2>&1 && timeout 20 bash -o pipefail -c \ + "gst-launch-1.0 -q videotestsrc num-buffers=30 ! video/x-raw,format=I420,width=1280,height=720,framerate=30/1 ! x265enc speed-preset=ultrafast tune=zerolatency key-int-max=30 ! h265parse disable-passthrough=true config-interval=-1 ! video/x-h265,stream-format=byte-stream,alignment=au ! ${candidate} ! videoconvert ! fakesink sync=false" \ + >"$software_hevc_smoke_log" 2>&1; then + software_hevc_decoder=$candidate + break + fi + done + + if [[ -n "$software_hevc_decoder" && "$INSTALL_CAM_CODEC_EXPLICIT" == "0" ]]; then + echo "⚠️ hardware HEVC decoder is exposed but the synthetic 1280x720 decode smoke failed: $hevc_decoder" >&2 + echo " smoke log: $hevc_smoke_log" >&2 + sed -n '1,120p' "$hevc_smoke_log" >&2 || true + echo " Software HEVC decoder passed the same 1280x720 smoke: $software_hevc_decoder" >&2 + echo " Keeping default HEVC upstream to avoid direct-MJPEG UVC artifacts; monitor CPU/RSS during field runs." >&2 + INSTALL_HEVC_DECODER=$software_hevc_decoder + INSTALL_ALLOW_SOFTWARE_VIDEO=1 + return 0 + fi + if [[ "$INSTALL_CAM_CODEC_EXPLICIT" == "0" ]]; then echo "⚠️ hardware HEVC decoder is exposed but the synthetic 1280x720 decode smoke failed: $hevc_decoder" >&2 echo " smoke log: $hevc_smoke_log" >&2 @@ -299,6 +324,10 @@ ensure_hevc_decode_support() { echo "❌ hardware HEVC decoder is exposed but failed a real 1280x720 decode smoke: $hevc_decoder" >&2 echo " smoke log: $hevc_smoke_log" >&2 sed -n '1,120p' "$hevc_smoke_log" >&2 || true + if [[ -n "$software_hevc_decoder" ]]; then + echo " Software HEVC decoder passed: $software_hevc_decoder" >&2 + echo " To explicitly use it, rerun with LESAVKA_ALLOW_SOFTWARE_VIDEO=1 LESAVKA_HEVC_DECODER=$software_hevc_decoder." >&2 + fi echo " Refusing HEVC upstream install because production video decode must be hardware-accelerated and proven." >&2 echo " Use LESAVKA_INSTALL_CAM_CODEC=mjpeg while the HEVC decoder stack is repaired." >&2 exit 1 @@ -1604,6 +1633,10 @@ SERVER_ENV_TMP=$(mktemp) printf 'LESAVKA_CAM_OUTPUT=%s\n' "${LESAVKA_INSTALL_CAM_OUTPUT:-uvc}" printf 'LESAVKA_CAM_CODEC=%s\n' "${INSTALL_CAM_CODEC}" printf 'LESAVKA_UPLINK_CAMERA_CODEC=%s\n' "${INSTALL_CAM_CODEC}" + if [[ -n "$INSTALL_HEVC_DECODER" ]]; then + printf 'LESAVKA_HEVC_DECODER=%s\n' "$INSTALL_HEVC_DECODER" + fi + printf 'LESAVKA_ALLOW_SOFTWARE_VIDEO=%s\n' "${INSTALL_ALLOW_SOFTWARE_VIDEO}" printf 'LESAVKA_UPLINK_AUDIO_CODEC=%s\n' "${INSTALL_UPLINK_AUDIO_CODEC}" printf 'LESAVKA_CAM_WIDTH=%s\n' "${LESAVKA_CAM_WIDTH:-1920}" printf 'LESAVKA_CAM_HEIGHT=%s\n' "${LESAVKA_CAM_HEIGHT:-1080}" diff --git a/server/Cargo.toml b/server/Cargo.toml index 7eb7429..5c3a9f8 100644 --- a/server/Cargo.toml +++ b/server/Cargo.toml @@ -16,7 +16,7 @@ bench = false [package] name = "lesavka_server" -version = "0.26.0" +version = "0.26.1" edition = "2024" autobins = false diff --git a/tests/contract/scripts/install/server_install_script_contract.rs b/tests/contract/scripts/install/server_install_script_contract.rs index b25773a..df64f31 100644 --- a/tests/contract/scripts/install/server_install_script_contract.rs +++ b/tests/contract/scripts/install/server_install_script_contract.rs @@ -263,8 +263,10 @@ fn server_install_pins_hdmi_camera_and_display_defaults() { "install script should try the Raspberry Pi HEVC decoder before requiring another hardware decoder" ); assert!( - SERVER_INSTALL.contains("will not fall back to avdec_h265 in production"), - "install script should fail loud instead of silently using software HEVC decode" + SERVER_INSTALL.contains("Software HEVC decoder passed the same 1280x720 smoke") + && SERVER_INSTALL.contains("LESAVKA_HEVC_DECODER") + && SERVER_INSTALL.contains("LESAVKA_ALLOW_SOFTWARE_VIDEO"), + "install script should make software HEVC fallback explicit and smoke-proven" ); assert!( SERVER_INSTALL.contains("hardware HEVC decoder passed a real 1280x720 decode smoke") @@ -288,7 +290,7 @@ fn server_install_pins_hdmi_camera_and_display_defaults() { SERVER_INSTALL.contains("Refusing HEVC upstream install because production video decode must be hardware-accelerated and proven") && SERVER_INSTALL.contains("Use LESAVKA_INSTALL_CAM_CODEC=mjpeg while the HEVC decoder stack is repaired") && SERVER_INSTALL.contains("Default HEVC upstream cannot be proven on this host; falling back to MJPEG ingress."), - "explicit HEVC installs should fail loud while default HEVC installs can safely fall back" + "explicit HEVC installs should fail loud while default HEVC installs can use proven fallback paths" ); assert!( !SERVER_INSTALL diff --git a/tests/regression/install/install_preserves_codec_settings_contract.rs b/tests/regression/install/install_preserves_codec_settings_contract.rs index 555c3b6..5ca7371 100644 --- a/tests/regression/install/install_preserves_codec_settings_contract.rs +++ b/tests/regression/install/install_preserves_codec_settings_contract.rs @@ -4,8 +4,8 @@ // explicit, while still allowing operator-provided install overrides. // Targets: server/client install scripts and client camera capture defaults. // Why: Lesavka now supports both MJPEG and HEVC upstream media, and installer -// reruns may prefer HEVC only when hardware decode is proven, and must fall -// back to MJPEG instead of producing black frames when it is not. +// reruns may prefer HEVC when decode is proven, falling back to either a +// smoke-tested software HEVC decoder or MJPEG instead of producing black frames. const SERVER_INSTALL: &str = include_str!(concat!( env!("CARGO_MANIFEST_DIR"), @@ -27,8 +27,13 @@ fn server_install_defaults_to_hevc_ingress_with_mjpeg_fallback_and_mjpeg_uvc_out "REQUESTED_CAM_CODEC=${LESAVKA_INSTALL_CAM_CODEC:-${LESAVKA_CAM_CODEC:-hevc}}", "INSTALL_CAM_CODEC=$(normalize_cam_codec \"${REQUESTED_CAM_CODEC}\")", "INSTALL_CAM_CODEC_EXPLICIT=0", + "INSTALL_HEVC_DECODER=${LESAVKA_INSTALL_HEVC_DECODER:-${LESAVKA_HEVC_DECODER:-}}", + "INSTALL_ALLOW_SOFTWARE_VIDEO=${LESAVKA_INSTALL_ALLOW_SOFTWARE_VIDEO:-${LESAVKA_ALLOW_SOFTWARE_VIDEO:-0}}", + "Software HEVC decoder passed the same 1280x720 smoke", "Default HEVC upstream cannot be proven on this host; falling back to MJPEG ingress.", "printf 'LESAVKA_CAM_CODEC=%s\\n' \"${INSTALL_CAM_CODEC}\"", + "printf 'LESAVKA_HEVC_DECODER=%s\\n' \"$INSTALL_HEVC_DECODER\"", + "printf 'LESAVKA_ALLOW_SOFTWARE_VIDEO=%s\\n' \"${INSTALL_ALLOW_SOFTWARE_VIDEO}\"", "printf 'LESAVKA_UVC_CODEC=%s\\n' \"${INSTALL_UVC_CODEC}\"", "\"LESAVKA_UVC_CODEC=${INSTALL_UVC_CODEC}\"", "uvc_env_value LESAVKA_UVC_WIDTH 1280", @@ -83,7 +88,8 @@ fn hevc_prerequisites_are_rechecked_idempotently() { "modprobe rpi_hevc_dec", "/etc/modules-load.d/lesavka-hevc.conf", "gst-inspect-1.0 v4l2slh265dec", - "will not fall back to avdec_h265 in production", + "avdec_h265 libde265dec", + "Keeping default HEVC upstream to avoid direct-MJPEG UVC artifacts", "Refusing HEVC upstream install because production video decode must be hardware-accelerated and proven", ] { assert!(