fix(client): prove hardware encoder availability
This commit is contained in:
parent
75df8c94e1
commit
164ef497f0
6
Cargo.lock
generated
6
Cargo.lock
generated
@ -1658,7 +1658,7 @@ checksum = "09edd9e8b54e49e587e4f6295a7d29c3ea94d469cb40ab8ca70b288248a81db2"
|
|||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "lesavka_client"
|
name = "lesavka_client"
|
||||||
version = "0.27.7"
|
version = "0.27.8"
|
||||||
dependencies = [
|
dependencies = [
|
||||||
"anyhow",
|
"anyhow",
|
||||||
"async-stream",
|
"async-stream",
|
||||||
@ -1692,7 +1692,7 @@ dependencies = [
|
|||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "lesavka_common"
|
name = "lesavka_common"
|
||||||
version = "0.27.7"
|
version = "0.27.8"
|
||||||
dependencies = [
|
dependencies = [
|
||||||
"anyhow",
|
"anyhow",
|
||||||
"base64",
|
"base64",
|
||||||
@ -1704,7 +1704,7 @@ dependencies = [
|
|||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "lesavka_server"
|
name = "lesavka_server"
|
||||||
version = "0.27.7"
|
version = "0.27.8"
|
||||||
dependencies = [
|
dependencies = [
|
||||||
"anyhow",
|
"anyhow",
|
||||||
"base64",
|
"base64",
|
||||||
|
|||||||
@ -4,7 +4,7 @@ path = "src/main.rs"
|
|||||||
|
|
||||||
[package]
|
[package]
|
||||||
name = "lesavka_client"
|
name = "lesavka_client"
|
||||||
version = "0.27.7"
|
version = "0.27.8"
|
||||||
edition = "2024"
|
edition = "2024"
|
||||||
|
|
||||||
[dependencies]
|
[dependencies]
|
||||||
|
|||||||
@ -55,7 +55,7 @@ impl CameraCapture {
|
|||||||
if buildable_encoder("vah264enc") {
|
if buildable_encoder("vah264enc") {
|
||||||
return Ok((
|
return Ok((
|
||||||
"vah264enc",
|
"vah264enc",
|
||||||
supported_encoder_property("vah264enc", &["keyframe-period"]),
|
supported_encoder_property("vah264enc", &["key-int-max", "keyframe-period"]),
|
||||||
));
|
));
|
||||||
}
|
}
|
||||||
if buildable_encoder("vaapih264enc") {
|
if buildable_encoder("vaapih264enc") {
|
||||||
@ -110,7 +110,7 @@ impl CameraCapture {
|
|||||||
"nvautogpuh265enc",
|
"nvautogpuh265enc",
|
||||||
&["iframeinterval", "idrinterval", "gop-size"][..],
|
&["iframeinterval", "idrinterval", "gop-size"][..],
|
||||||
),
|
),
|
||||||
("vah265enc", &["keyframe-period"][..]),
|
("vah265enc", &["key-int-max", "keyframe-period"][..]),
|
||||||
("vaapih265enc", &["keyframe-period"][..]),
|
("vaapih265enc", &["keyframe-period"][..]),
|
||||||
("v4l2h265enc", &["idrcount"][..]),
|
("v4l2h265enc", &["idrcount"][..]),
|
||||||
] {
|
] {
|
||||||
@ -157,8 +157,30 @@ impl CameraCapture {
|
|||||||
|
|
||||||
#[cfg(not(coverage))]
|
#[cfg(not(coverage))]
|
||||||
fn ffmpeg_hevc_nvenc_available() -> bool {
|
fn ffmpeg_hevc_nvenc_available() -> bool {
|
||||||
|
if !Path::new("/dev/nvidiactl").exists() {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
Command::new("ffmpeg")
|
Command::new("ffmpeg")
|
||||||
.args(["-hide_banner", "-loglevel", "error", "-h", "encoder=hevc_nvenc"])
|
.args([
|
||||||
|
"-hide_banner",
|
||||||
|
"-loglevel",
|
||||||
|
"error",
|
||||||
|
"-f",
|
||||||
|
"lavfi",
|
||||||
|
"-i",
|
||||||
|
"color=c=black:s=128x128:r=1",
|
||||||
|
"-frames:v",
|
||||||
|
"1",
|
||||||
|
"-an",
|
||||||
|
"-pix_fmt",
|
||||||
|
"nv12",
|
||||||
|
"-c:v",
|
||||||
|
"hevc_nvenc",
|
||||||
|
"-f",
|
||||||
|
"null",
|
||||||
|
"-",
|
||||||
|
])
|
||||||
|
.stdin(Stdio::null())
|
||||||
.stdout(Stdio::null())
|
.stdout(Stdio::null())
|
||||||
.stderr(Stdio::null())
|
.stderr(Stdio::null())
|
||||||
.status()
|
.status()
|
||||||
|
|||||||
@ -186,7 +186,7 @@ fn coverage_h264_encoder_choice_honors_stable_test_overrides() {
|
|||||||
("nvh264enc", ("nvh264enc", None)),
|
("nvh264enc", ("nvh264enc", None)),
|
||||||
("nvautogpuh264enc", ("nvautogpuh264enc", None)),
|
("nvautogpuh264enc", ("nvautogpuh264enc", None)),
|
||||||
("vulkanh264enc", ("vulkanh264enc", Some("idr-period"))),
|
("vulkanh264enc", ("vulkanh264enc", Some("idr-period"))),
|
||||||
("vah264enc", ("vah264enc", Some("keyframe-period"))),
|
("vah264enc", ("vah264enc", Some("key-int-max"))),
|
||||||
("vaapih264enc", ("vaapih264enc", Some("keyframe-period"))),
|
("vaapih264enc", ("vaapih264enc", Some("keyframe-period"))),
|
||||||
("v4l2h264enc", ("v4l2h264enc", Some("idrcount"))),
|
("v4l2h264enc", ("v4l2h264enc", Some("idrcount"))),
|
||||||
("unknown", ("x264enc", Some("key-int-max"))),
|
("unknown", ("x264enc", Some("key-int-max"))),
|
||||||
|
|||||||
@ -1,6 +1,6 @@
|
|||||||
[package]
|
[package]
|
||||||
name = "lesavka_common"
|
name = "lesavka_common"
|
||||||
version = "0.27.7"
|
version = "0.27.8"
|
||||||
edition = "2024"
|
edition = "2024"
|
||||||
build = "build.rs"
|
build = "build.rs"
|
||||||
|
|
||||||
|
|||||||
@ -293,6 +293,17 @@ gadget has successfully queued a verified frame. The honest long-term answer is
|
|||||||
to retain a small Linux verification host in the lab when possible; otherwise
|
to retain a small Linux verification host in the lab when possible; otherwise
|
||||||
the calibrated server telemetry is the available integrity contract.
|
the calibrated server telemetry is the available integrity contract.
|
||||||
|
|
||||||
|
### The `0.27.8` Proof Candidate
|
||||||
|
|
||||||
|
End-to-end preparation exposed a separate client hardware-selection defect:
|
||||||
|
FFmpeg advertising `hevc_nvenc` did not prove that CUDA could encode a frame.
|
||||||
|
On the operator workstation the NVIDIA device was unavailable while the Intel
|
||||||
|
VA-API HEVC/H.264 route was healthy. Release 0.27.8 runtime-probes NVENC before
|
||||||
|
selection, falls back to the working GStreamer hardware encoder, and uses the
|
||||||
|
modern `key-int-max` property for `vah264enc` and `vah265enc`. The hardware
|
||||||
|
smoke harness now exercises the same usable route and retains encoded streams
|
||||||
|
plus hardware-decoded proof frames.
|
||||||
|
|
||||||
## 7. The Current Downstream Video Failure
|
## 7. The Current Downstream Video Failure
|
||||||
|
|
||||||
The current blank downstream feeds fail before transport or decoding.
|
The current blank downstream feeds fail before transport or decoding.
|
||||||
@ -356,7 +367,7 @@ The safe completion sequence for this incident is:
|
|||||||
6. Run `scripts/install/server.sh` as the trusted deployment path. Preserve the
|
6. Run `scripts/install/server.sh` as the trusted deployment path. Preserve the
|
||||||
already-attached USB gadget unless a controlled rebuild is explicitly
|
already-attached USB gadget unless a controlled rebuild is explicitly
|
||||||
required.
|
required.
|
||||||
7. Confirm Theia reports server version `0.27.7`, the pushed release revision,
|
7. Confirm Theia reports server version `0.27.8`, the pushed release revision,
|
||||||
direct MJPEG normalizer timeout `0`, and a coherent UVC contract.
|
direct MJPEG normalizer timeout `0`, and a coherent UVC contract.
|
||||||
8. Open both downstream RPCs and prove that each emits changing, decodable H.264
|
8. Open both downstream RPCs and prove that each emits changing, decodable H.264
|
||||||
frames.
|
frames.
|
||||||
@ -374,7 +385,7 @@ hardware contract is repeatable. The remaining work falls into five groups.
|
|||||||
|
|
||||||
### A. Install And Version Parity
|
### A. Install And Version Parity
|
||||||
|
|
||||||
- Push and deploy `0.27.7` through the client/server install scripts.
|
- Push and deploy `0.27.8` through the client/server install scripts.
|
||||||
- Confirm client/server version and revision in every hardware probe artifact.
|
- Confirm client/server version and revision in every hardware probe artifact.
|
||||||
- Eliminate the current state where a fixed client talks to an unfixed server.
|
- Eliminate the current state where a fixed client talks to an unfixed server.
|
||||||
|
|
||||||
@ -440,6 +451,6 @@ host repair:
|
|||||||
9. disconnect/reconnect and device changes recover without stale media; and
|
9. disconnect/reconnect and device changes recover without stale media; and
|
||||||
10. diagnostics identify the failed physical stage when any item breaks.
|
10. diagnostics identify the failed physical stage when any item breaks.
|
||||||
|
|
||||||
Until that sequence passes on the installed `0.27.7` client/server pair, the
|
Until that sequence passes on the installed `0.27.8` client/server pair, the
|
||||||
current release should be described as a validated code correction awaiting
|
current release should be described as a validated code correction awaiting
|
||||||
hardware deployment and end-to-end acceptance, not as a completed product fix.
|
hardware deployment and end-to-end acceptance, not as a completed product fix.
|
||||||
|
|||||||
@ -147,6 +147,14 @@ first_available_gst_element() {
|
|||||||
return 1
|
return 1
|
||||||
}
|
}
|
||||||
|
|
||||||
|
ffmpeg_hevc_nvenc_usable() {
|
||||||
|
[[ -e /dev/nvidiactl ]] || return 1
|
||||||
|
run_as_user ffmpeg -hide_banner -loglevel error \
|
||||||
|
-f lavfi -i color=c=black:s=128x128:r=1 \
|
||||||
|
-frames:v 1 -an -pix_fmt nv12 -c:v hevc_nvenc -f null - \
|
||||||
|
>/dev/null 2>&1
|
||||||
|
}
|
||||||
|
|
||||||
report_client_media_acceleration() {
|
report_client_media_acceleration() {
|
||||||
log "1f. Inspecting client media acceleration routes"
|
log "1f. Inspecting client media acceleration routes"
|
||||||
|
|
||||||
@ -166,8 +174,7 @@ report_client_media_acceleration() {
|
|||||||
vah265enc \
|
vah265enc \
|
||||||
vaapih265enc \
|
vaapih265enc \
|
||||||
v4l2h265enc || true)
|
v4l2h265enc || true)
|
||||||
if command -v ffmpeg >/dev/null 2>&1 \
|
if command -v ffmpeg >/dev/null 2>&1 && ffmpeg_hevc_nvenc_usable; then
|
||||||
&& ffmpeg -hide_banner -loglevel error -h encoder=hevc_nvenc >/dev/null 2>&1; then
|
|
||||||
ffmpeg_hevc_encoder="hevc_nvenc"
|
ffmpeg_hevc_encoder="hevc_nvenc"
|
||||||
fi
|
fi
|
||||||
h264_encoder=$(first_available_gst_element \
|
h264_encoder=$(first_available_gst_element \
|
||||||
@ -209,7 +216,7 @@ report_client_media_acceleration() {
|
|||||||
if gst-inspect-1.0 nvcodec 2>&1 | grep -q 'Unable to initialize CUDA library'; then
|
if gst-inspect-1.0 nvcodec 2>&1 | grep -q 'Unable to initialize CUDA library'; then
|
||||||
echo "⚠️ NVIDIA nvcodec is installed but CUDA initialization failed; NVENC HEVC is unavailable to GStreamer."
|
echo "⚠️ NVIDIA nvcodec is installed but CUDA initialization failed; NVENC HEVC is unavailable to GStreamer."
|
||||||
if [[ -n $ffmpeg_hevc_encoder ]]; then
|
if [[ -n $ffmpeg_hevc_encoder ]]; then
|
||||||
echo " FFmpeg hevc_nvenc is available and will be used for HEVC upstream hardware encode."
|
echo " FFmpeg hevc_nvenc encoded a probe frame and will be used for HEVC upstream hardware encode."
|
||||||
else
|
else
|
||||||
echo " Install a working NVENC/VAAPI/V4L2 HEVC route before enabling HEVC upstream."
|
echo " Install a working NVENC/VAAPI/V4L2 HEVC route before enabling HEVC upstream."
|
||||||
fi
|
fi
|
||||||
|
|||||||
@ -10,10 +10,10 @@ RESULTS_TSV="${ARTIFACT_DIR}/results.tsv"
|
|||||||
SUMMARY_JSON="${ARTIFACT_DIR}/summary.json"
|
SUMMARY_JSON="${ARTIFACT_DIR}/summary.json"
|
||||||
SUMMARY_TXT="${ARTIFACT_DIR}/summary.txt"
|
SUMMARY_TXT="${ARTIFACT_DIR}/summary.txt"
|
||||||
|
|
||||||
UPSTREAM_HEVC_FILE="${ARTIFACT_DIR}/upstream-hevc-nvenc.hevc"
|
UPSTREAM_HEVC_FILE="${ARTIFACT_DIR}/upstream-hevc-hardware.hevc"
|
||||||
UPSTREAM_HEVC_FRAME="${ARTIFACT_DIR}/upstream-hevc-cuda-frame.png"
|
UPSTREAM_HEVC_FRAME="${ARTIFACT_DIR}/upstream-hevc-hardware-frame.png"
|
||||||
DOWNSTREAM_H264_FILE="${ARTIFACT_DIR}/downstream-h264-nvenc.h264"
|
DOWNSTREAM_H264_FILE="${ARTIFACT_DIR}/downstream-h264-hardware.h264"
|
||||||
DOWNSTREAM_H264_FRAME="${ARTIFACT_DIR}/downstream-h264-cuda-frame.png"
|
DOWNSTREAM_H264_FRAME="${ARTIFACT_DIR}/downstream-h264-hardware-frame.png"
|
||||||
AUDIO_WAV="${ARTIFACT_DIR}/audio-aac-roundtrip.wav"
|
AUDIO_WAV="${ARTIFACT_DIR}/audio-aac-roundtrip.wav"
|
||||||
AUDIO_RMS_JSON="${ARTIFACT_DIR}/audio-aac-roundtrip-rms.json"
|
AUDIO_RMS_JSON="${ARTIFACT_DIR}/audio-aac-roundtrip-rms.json"
|
||||||
SMOKE_WIDTH="${LESAVKA_HARDWARE_SMOKE_WIDTH:-1280}"
|
SMOKE_WIDTH="${LESAVKA_HARDWARE_SMOKE_WIDTH:-1280}"
|
||||||
@ -57,10 +57,13 @@ ffmpeg_has_encoder() {
|
|||||||
| awk -v codec="${codec}" '$2 == codec { found = 1 } END { exit found ? 0 : 1 }'
|
| awk -v codec="${codec}" '$2 == codec { found = 1 } END { exit found ? 0 : 1 }'
|
||||||
}
|
}
|
||||||
|
|
||||||
ffmpeg_has_decoder() {
|
ffmpeg_nvenc_usable() {
|
||||||
local codec="$1"
|
local encoder="$1"
|
||||||
ffmpeg -hide_banner -decoders 2>/dev/null \
|
[[ -e /dev/nvidiactl ]] || return 1
|
||||||
| awk -v codec="${codec}" '$2 == codec { found = 1 } END { exit found ? 0 : 1 }'
|
ffmpeg -hide_banner -loglevel error -nostdin \
|
||||||
|
-f lavfi -i color=c=black:s=128x128:r=1 \
|
||||||
|
-frames:v 1 -an -pix_fmt nv12 -c:v "${encoder}" -f null - \
|
||||||
|
>/dev/null 2>&1
|
||||||
}
|
}
|
||||||
|
|
||||||
run_logged() {
|
run_logged() {
|
||||||
@ -108,9 +111,9 @@ summary_txt = pathlib.Path(sys.argv[3])
|
|||||||
artifact_dir = pathlib.Path(sys.argv[4])
|
artifact_dir = pathlib.Path(sys.argv[4])
|
||||||
artifact_paths = {
|
artifact_paths = {
|
||||||
"upstream_hevc_stream": sys.argv[5],
|
"upstream_hevc_stream": sys.argv[5],
|
||||||
"upstream_hevc_cuda_frame": sys.argv[6],
|
"upstream_hevc_hardware_frame": sys.argv[6],
|
||||||
"downstream_h264_stream": sys.argv[7],
|
"downstream_h264_stream": sys.argv[7],
|
||||||
"downstream_h264_cuda_frame": sys.argv[8],
|
"downstream_h264_hardware_frame": sys.argv[8],
|
||||||
"audio_aac_roundtrip_wav": sys.argv[9],
|
"audio_aac_roundtrip_wav": sys.argv[9],
|
||||||
"audio_aac_roundtrip_rms": sys.argv[10],
|
"audio_aac_roundtrip_rms": sys.argv[10],
|
||||||
}
|
}
|
||||||
@ -190,6 +193,39 @@ select_gst_h264_decoder() {
|
|||||||
return 1
|
return 1
|
||||||
}
|
}
|
||||||
|
|
||||||
|
select_gst_hevc_decoder() {
|
||||||
|
local decoder
|
||||||
|
for decoder in nvh265dec vah265dec vaapih265dec v4l2h265dec v4l2slh265dec; do
|
||||||
|
if gst_has "${decoder}"; then
|
||||||
|
printf '%s\n' "${decoder}"
|
||||||
|
return 0
|
||||||
|
fi
|
||||||
|
done
|
||||||
|
return 1
|
||||||
|
}
|
||||||
|
|
||||||
|
select_gst_hevc_encoder() {
|
||||||
|
local encoder
|
||||||
|
for encoder in nvh265enc nvautogpuh265enc vah265enc vaapih265enc v4l2h265enc; do
|
||||||
|
if gst_has "${encoder}"; then
|
||||||
|
printf '%s\n' "${encoder}"
|
||||||
|
return 0
|
||||||
|
fi
|
||||||
|
done
|
||||||
|
return 1
|
||||||
|
}
|
||||||
|
|
||||||
|
select_gst_h264_encoder() {
|
||||||
|
local encoder
|
||||||
|
for encoder in nvh264enc nvautogpuh264enc vah264enc vaapih264enc v4l2h264enc; do
|
||||||
|
if gst_has "${encoder}"; then
|
||||||
|
printf '%s\n' "${encoder}"
|
||||||
|
return 0
|
||||||
|
fi
|
||||||
|
done
|
||||||
|
return 1
|
||||||
|
}
|
||||||
|
|
||||||
gst_h264_decode_chain() {
|
gst_h264_decode_chain() {
|
||||||
case "$1" in
|
case "$1" in
|
||||||
vulkanh264dec)
|
vulkanh264dec)
|
||||||
@ -204,6 +240,17 @@ gst_h264_decode_chain() {
|
|||||||
esac
|
esac
|
||||||
}
|
}
|
||||||
|
|
||||||
|
gst_hevc_decode_chain() {
|
||||||
|
case "$1" in
|
||||||
|
nvh265dec|vah265dec|vaapih265dec|v4l2h265dec|v4l2slh265dec)
|
||||||
|
printf '%s\n' "$1"
|
||||||
|
;;
|
||||||
|
*)
|
||||||
|
return 1
|
||||||
|
;;
|
||||||
|
esac
|
||||||
|
}
|
||||||
|
|
||||||
select_gst_aac_encoder() {
|
select_gst_aac_encoder() {
|
||||||
local encoder
|
local encoder
|
||||||
for encoder in fdkaacenc voaacenc avenc_aac; do
|
for encoder in fdkaacenc voaacenc avenc_aac; do
|
||||||
@ -226,36 +273,49 @@ main() {
|
|||||||
exit 1
|
exit 1
|
||||||
fi
|
fi
|
||||||
|
|
||||||
if ! ffmpeg_has_encoder hevc_nvenc; then
|
local hevc_encoder=""
|
||||||
record_result "client_upstream_hevc_nvenc_available" "fail" "ffmpeg hevc_nvenc encoder is missing" ""
|
local hevc_encoder_route=""
|
||||||
OVERALL=1
|
local h264_encoder=""
|
||||||
else
|
local h264_encoder_route=""
|
||||||
record_result "client_upstream_hevc_nvenc_available" "pass" "ffmpeg hevc_nvenc encoder is available" ""
|
local gst_hevc_decoder=""
|
||||||
fi
|
local gst_hevc_chain=""
|
||||||
|
|
||||||
if ! ffmpeg_has_encoder h264_nvenc; then
|
|
||||||
record_result "client_downstream_h264_nvenc_source_available" "fail" "ffmpeg h264_nvenc encoder is missing for downstream test source" ""
|
|
||||||
OVERALL=1
|
|
||||||
else
|
|
||||||
record_result "client_downstream_h264_nvenc_source_available" "pass" "ffmpeg h264_nvenc encoder is available for downstream test source" ""
|
|
||||||
fi
|
|
||||||
|
|
||||||
if ! ffmpeg_has_decoder hevc_cuvid; then
|
|
||||||
record_result "client_hevc_cuvid_visual_decode_available" "fail" "ffmpeg hevc_cuvid decoder is missing for visual evidence frame" ""
|
|
||||||
OVERALL=1
|
|
||||||
else
|
|
||||||
record_result "client_hevc_cuvid_visual_decode_available" "pass" "ffmpeg hevc_cuvid decoder is available for visual evidence frame" ""
|
|
||||||
fi
|
|
||||||
|
|
||||||
if ! ffmpeg_has_decoder h264_cuvid; then
|
|
||||||
record_result "client_h264_cuvid_visual_decode_available" "fail" "ffmpeg h264_cuvid decoder is missing for visual evidence frame" ""
|
|
||||||
OVERALL=1
|
|
||||||
else
|
|
||||||
record_result "client_h264_cuvid_visual_decode_available" "pass" "ffmpeg h264_cuvid decoder is available for visual evidence frame" ""
|
|
||||||
fi
|
|
||||||
|
|
||||||
local gst_h264_decoder=""
|
local gst_h264_decoder=""
|
||||||
local gst_h264_chain=""
|
local gst_h264_chain=""
|
||||||
|
|
||||||
|
if ffmpeg_has_encoder hevc_nvenc && ffmpeg_nvenc_usable hevc_nvenc; then
|
||||||
|
hevc_encoder="hevc_nvenc"
|
||||||
|
hevc_encoder_route="ffmpeg"
|
||||||
|
elif hevc_encoder="$(select_gst_hevc_encoder)"; then
|
||||||
|
hevc_encoder_route="gstreamer"
|
||||||
|
else
|
||||||
|
record_result "client_upstream_hevc_hardware_encoder" "fail" "no runtime-usable HEVC hardware encoder found" ""
|
||||||
|
OVERALL=1
|
||||||
|
fi
|
||||||
|
if [[ -n ${hevc_encoder} ]]; then
|
||||||
|
record_result "client_upstream_hevc_hardware_encoder" "pass" "selected ${hevc_encoder_route}:${hevc_encoder}" ""
|
||||||
|
fi
|
||||||
|
|
||||||
|
if ffmpeg_has_encoder h264_nvenc && ffmpeg_nvenc_usable h264_nvenc; then
|
||||||
|
h264_encoder="h264_nvenc"
|
||||||
|
h264_encoder_route="ffmpeg"
|
||||||
|
elif h264_encoder="$(select_gst_h264_encoder)"; then
|
||||||
|
h264_encoder_route="gstreamer"
|
||||||
|
else
|
||||||
|
record_result "client_downstream_h264_hardware_source" "fail" "no runtime-usable H.264 hardware encoder found" ""
|
||||||
|
OVERALL=1
|
||||||
|
fi
|
||||||
|
if [[ -n ${h264_encoder} ]]; then
|
||||||
|
record_result "client_downstream_h264_hardware_source" "pass" "selected ${h264_encoder_route}:${h264_encoder}" ""
|
||||||
|
fi
|
||||||
|
|
||||||
|
if gst_hevc_decoder="$(select_gst_hevc_decoder)"; then
|
||||||
|
gst_hevc_chain="$(gst_hevc_decode_chain "${gst_hevc_decoder}")"
|
||||||
|
record_result "client_upstream_gstreamer_hevc_hw_decoder_available" "pass" "selected ${gst_hevc_decoder}" ""
|
||||||
|
else
|
||||||
|
record_result "client_upstream_gstreamer_hevc_hw_decoder_available" "fail" "no GStreamer hardware H.265 decoder found" ""
|
||||||
|
OVERALL=1
|
||||||
|
fi
|
||||||
|
|
||||||
if gst_h264_decoder="$(select_gst_h264_decoder)"; then
|
if gst_h264_decoder="$(select_gst_h264_decoder)"; then
|
||||||
gst_h264_chain="$(gst_h264_decode_chain "${gst_h264_decoder}")"
|
gst_h264_chain="$(gst_h264_decode_chain "${gst_h264_decoder}")"
|
||||||
record_result "client_downstream_gstreamer_h264_hw_decoder_available" "pass" "selected ${gst_h264_decoder}" ""
|
record_result "client_downstream_gstreamer_h264_hw_decoder_available" "pass" "selected ${gst_h264_decoder}" ""
|
||||||
@ -273,29 +333,41 @@ main() {
|
|||||||
fi
|
fi
|
||||||
|
|
||||||
if [[ ${OVERALL} -eq 0 ]]; then
|
if [[ ${OVERALL} -eq 0 ]]; then
|
||||||
run_shell "client_upstream_hevc_nvenc_file" \
|
if [[ ${hevc_encoder_route} == ffmpeg ]]; then
|
||||||
"GPU encodes a synthetic upstream HEVC stream with hevc_nvenc" \
|
run_shell "client_upstream_hevc_hardware_file" \
|
||||||
"ffmpeg -hide_banner -loglevel warning -y -nostdin -f lavfi -i testsrc2=size=${SMOKE_WIDTH}x${SMOKE_HEIGHT}:rate=${SMOKE_FPS} -frames:v ${SMOKE_FRAMES} -an -sn -dn -vf format=nv12 -c:v hevc_nvenc -preset p1 -tune ll -rc cbr -b:v ${SMOKE_BITRATE_KBPS}k -maxrate ${SMOKE_BITRATE_KBPS}k -bufsize ${SMOKE_BITRATE_KBPS}k -g ${SMOKE_FPS} -bf 0 -forced-idr 1 -f hevc '${UPSTREAM_HEVC_FILE}'"
|
"GPU encodes a synthetic upstream HEVC stream with ${hevc_encoder}" \
|
||||||
|
"ffmpeg -hide_banner -loglevel warning -y -nostdin -f lavfi -i testsrc2=size=${SMOKE_WIDTH}x${SMOKE_HEIGHT}:rate=${SMOKE_FPS} -frames:v ${SMOKE_FRAMES} -an -sn -dn -vf format=nv12 -c:v ${hevc_encoder} -preset p1 -tune ll -rc cbr -b:v ${SMOKE_BITRATE_KBPS}k -maxrate ${SMOKE_BITRATE_KBPS}k -bufsize ${SMOKE_BITRATE_KBPS}k -g ${SMOKE_FPS} -bf 0 -forced-idr 1 -f hevc '${UPSTREAM_HEVC_FILE}'"
|
||||||
|
else
|
||||||
|
run_shell "client_upstream_hevc_hardware_file" \
|
||||||
|
"GPU encodes a synthetic upstream HEVC stream with ${hevc_encoder}" \
|
||||||
|
"gst-launch-1.0 -q videotestsrc num-buffers=${SMOKE_FRAMES} is-live=false pattern=smpte ! video/x-raw,width=${SMOKE_WIDTH},height=${SMOKE_HEIGHT},framerate=${SMOKE_FPS}/1 ! videoconvert ! video/x-raw,format=NV12 ! ${hevc_encoder} ! h265parse config-interval=-1 ! video/x-h265,stream-format=byte-stream,alignment=au ! filesink location='${UPSTREAM_HEVC_FILE}'"
|
||||||
|
fi
|
||||||
|
|
||||||
run_shell "client_upstream_hevc_gstreamer_parse" \
|
run_shell "client_upstream_hevc_gstreamer_parse" \
|
||||||
"GStreamer accepts the HEVC elementary stream shape used by the upstream bundle path" \
|
"GStreamer accepts the HEVC elementary stream shape used by the upstream bundle path" \
|
||||||
"gst-launch-1.0 -q filesrc location='${UPSTREAM_HEVC_FILE}' ! h265parse config-interval=-1 ! video/x-h265,stream-format=byte-stream,alignment=au ! fakesink sync=false"
|
"gst-launch-1.0 -q filesrc location='${UPSTREAM_HEVC_FILE}' ! h265parse config-interval=-1 ! video/x-h265,stream-format=byte-stream,alignment=au ! fakesink sync=false"
|
||||||
|
|
||||||
run_shell "client_upstream_hevc_cuvid_frame" \
|
run_shell "client_upstream_hevc_hardware_frame" \
|
||||||
"CUDA decodes one visual proof frame from the upstream HEVC stream" \
|
"GStreamer hardware decoder ${gst_hevc_decoder} emits one visual HEVC proof frame" \
|
||||||
"ffmpeg -hide_banner -loglevel warning -y -nostdin -c:v hevc_cuvid -i '${UPSTREAM_HEVC_FILE}' -frames:v 1 '${UPSTREAM_HEVC_FRAME}'"
|
"gst-launch-1.0 -q filesrc location='${UPSTREAM_HEVC_FILE}' ! h265parse ! ${gst_hevc_chain} ! videoconvert ! pngenc snapshot=true ! filesink location='${UPSTREAM_HEVC_FRAME}'"
|
||||||
|
|
||||||
run_shell "client_downstream_h264_nvenc_file" \
|
if [[ ${h264_encoder_route} == ffmpeg ]]; then
|
||||||
"GPU creates a downstream-like H.264 elementary stream for decoder verification" \
|
run_shell "client_downstream_h264_hardware_file" \
|
||||||
"ffmpeg -hide_banner -loglevel warning -y -nostdin -f lavfi -i testsrc2=size=${SMOKE_WIDTH}x${SMOKE_HEIGHT}:rate=${SMOKE_FPS} -frames:v ${SMOKE_FRAMES} -an -sn -dn -vf format=nv12 -c:v h264_nvenc -preset p1 -tune ll -rc cbr -b:v ${SMOKE_BITRATE_KBPS}k -maxrate ${SMOKE_BITRATE_KBPS}k -bufsize ${SMOKE_BITRATE_KBPS}k -g ${SMOKE_FPS} -bf 0 -forced-idr 1 -f h264 '${DOWNSTREAM_H264_FILE}'"
|
"GPU creates a downstream-like H.264 elementary stream with ${h264_encoder}" \
|
||||||
|
"ffmpeg -hide_banner -loglevel warning -y -nostdin -f lavfi -i testsrc2=size=${SMOKE_WIDTH}x${SMOKE_HEIGHT}:rate=${SMOKE_FPS} -frames:v ${SMOKE_FRAMES} -an -sn -dn -vf format=nv12 -c:v ${h264_encoder} -preset p1 -tune ll -rc cbr -b:v ${SMOKE_BITRATE_KBPS}k -maxrate ${SMOKE_BITRATE_KBPS}k -bufsize ${SMOKE_BITRATE_KBPS}k -g ${SMOKE_FPS} -bf 0 -forced-idr 1 -f h264 '${DOWNSTREAM_H264_FILE}'"
|
||||||
|
else
|
||||||
|
run_shell "client_downstream_h264_hardware_file" \
|
||||||
|
"GPU creates a downstream-like H.264 elementary stream with ${h264_encoder}" \
|
||||||
|
"gst-launch-1.0 -q videotestsrc num-buffers=${SMOKE_FRAMES} is-live=false pattern=smpte ! video/x-raw,width=${SMOKE_WIDTH},height=${SMOKE_HEIGHT},framerate=${SMOKE_FPS}/1 ! videoconvert ! video/x-raw,format=NV12 ! ${h264_encoder} ! h264parse config-interval=-1 ! video/x-h264,stream-format=byte-stream,alignment=au ! filesink location='${DOWNSTREAM_H264_FILE}'"
|
||||||
|
fi
|
||||||
|
|
||||||
run_shell "client_downstream_h264_gstreamer_hw_decode" \
|
run_shell "client_downstream_h264_gstreamer_hw_decode" \
|
||||||
"GStreamer decodes H.264 with hardware decoder ${gst_h264_decoder}" \
|
"GStreamer decodes H.264 with hardware decoder ${gst_h264_decoder}" \
|
||||||
"gst-launch-1.0 -q filesrc location='${DOWNSTREAM_H264_FILE}' ! h264parse config-interval=-1 ! video/x-h264,stream-format=byte-stream,alignment=au ! ${gst_h264_chain} ! videoconvert ! fakesink sync=false"
|
"gst-launch-1.0 -q filesrc location='${DOWNSTREAM_H264_FILE}' ! h264parse config-interval=-1 ! video/x-h264,stream-format=byte-stream,alignment=au ! ${gst_h264_chain} ! videoconvert ! fakesink sync=false"
|
||||||
|
|
||||||
run_shell "client_downstream_h264_cuvid_frame" \
|
run_shell "client_downstream_h264_hardware_frame" \
|
||||||
"CUDA decodes one visual proof frame from the downstream H.264 stream" \
|
"GStreamer hardware decoder ${gst_h264_decoder} emits one visual H.264 proof frame" \
|
||||||
"ffmpeg -hide_banner -loglevel warning -y -nostdin -c:v h264_cuvid -i '${DOWNSTREAM_H264_FILE}' -frames:v 1 '${DOWNSTREAM_H264_FRAME}'"
|
"gst-launch-1.0 -q filesrc location='${DOWNSTREAM_H264_FILE}' ! h264parse ! ${gst_h264_chain} ! videoconvert ! pngenc snapshot=true ! filesink location='${DOWNSTREAM_H264_FRAME}'"
|
||||||
|
|
||||||
run_shell "audio_aac_roundtrip_wav" \
|
run_shell "audio_aac_roundtrip_wav" \
|
||||||
"GStreamer encodes and decodes a 1 kHz tone to a WAV artifact for audio-path sanity" \
|
"GStreamer encodes and decodes a 1 kHz tone to a WAV artifact for audio-path sanity" \
|
||||||
|
|||||||
@ -16,7 +16,7 @@ bench = false
|
|||||||
|
|
||||||
[package]
|
[package]
|
||||||
name = "lesavka_server"
|
name = "lesavka_server"
|
||||||
version = "0.27.7"
|
version = "0.27.8"
|
||||||
edition = "2024"
|
edition = "2024"
|
||||||
autobins = false
|
autobins = false
|
||||||
|
|
||||||
|
|||||||
@ -216,13 +216,18 @@ mod camera_include_contract {
|
|||||||
}
|
}
|
||||||
|
|
||||||
#[test]
|
#[test]
|
||||||
fn hevc_prefers_proven_ffmpeg_nvenc_unless_gstreamer_is_explicit() {
|
fn hevc_prefers_runtime_proven_ffmpeg_nvenc_unless_gstreamer_is_explicit() {
|
||||||
let encoder_source = include_str!(concat!(
|
let encoder_source = include_str!(concat!(
|
||||||
env!("CARGO_MANIFEST_DIR"),
|
env!("CARGO_MANIFEST_DIR"),
|
||||||
"/client/src/input/camera/encoder_selection.rs"
|
"/client/src/input/camera/encoder_selection.rs"
|
||||||
));
|
));
|
||||||
|
|
||||||
for expected in [
|
for expected in [
|
||||||
|
"Path::new(\"/dev/nvidiactl\").exists()",
|
||||||
|
"color=c=black:s=128x128:r=1",
|
||||||
|
"\"hevc_nvenc\"",
|
||||||
|
"\"-f\",",
|
||||||
|
"\"null\"",
|
||||||
"Some(\"ffmpeg_hevc_nvenc\" | \"hevc_nvenc\" | \"nvenc\")",
|
"Some(\"ffmpeg_hevc_nvenc\" | \"hevc_nvenc\" | \"nvenc\")",
|
||||||
"Some(\"gstreamer\" | \"gst\") => false",
|
"Some(\"gstreamer\" | \"gst\") => false",
|
||||||
"_ => Self::ffmpeg_hevc_nvenc_available()",
|
"_ => Self::ffmpeg_hevc_nvenc_available()",
|
||||||
@ -236,6 +241,16 @@ mod camera_include_contract {
|
|||||||
!encoder_source.contains("!Self::gstreamer_hevc_hardware_encoder_available()"),
|
!encoder_source.contains("!Self::gstreamer_hevc_hardware_encoder_available()"),
|
||||||
"a present GStreamer HEVC encoder must not suppress the proven FFmpeg/NVENC path"
|
"a present GStreamer HEVC encoder must not suppress the proven FFmpeg/NVENC path"
|
||||||
);
|
);
|
||||||
|
assert!(
|
||||||
|
encoder_source.contains(
|
||||||
|
"supported_encoder_property(\"vah264enc\", &[\"key-int-max\", \"keyframe-period\"])",
|
||||||
|
),
|
||||||
|
"modern vah264enc must receive its real short-GOP property"
|
||||||
|
);
|
||||||
|
assert!(
|
||||||
|
encoder_source.contains("(\"vah265enc\", &[\"key-int-max\", \"keyframe-period\"][..])"),
|
||||||
|
"modern vah265enc must receive its real short-GOP property"
|
||||||
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
#[test]
|
#[test]
|
||||||
|
|||||||
@ -103,7 +103,9 @@ fn client_install_reports_nvidia_and_open_source_media_routes() {
|
|||||||
"first_available_gst_element",
|
"first_available_gst_element",
|
||||||
"nvidia-smi is available",
|
"nvidia-smi is available",
|
||||||
"NVIDIA nvcodec is installed but CUDA initialization failed",
|
"NVIDIA nvcodec is installed but CUDA initialization failed",
|
||||||
"FFmpeg hevc_nvenc is available",
|
"FFmpeg hevc_nvenc encoded a probe frame",
|
||||||
|
"ffmpeg_hevc_nvenc_usable",
|
||||||
|
"/dev/nvidiactl",
|
||||||
"proprietary NVIDIA GStreamer route",
|
"proprietary NVIDIA GStreamer route",
|
||||||
"Vulkan/VAAPI/V4L2 GStreamer route",
|
"Vulkan/VAAPI/V4L2 GStreamer route",
|
||||||
"nvh265enc",
|
"nvh265enc",
|
||||||
|
|||||||
@ -17,10 +17,10 @@ fn hardware_media_smoke_stays_manual_and_artifact_backed() {
|
|||||||
"Manual: local/remote hardware media smoke evidence; not part of CI.",
|
"Manual: local/remote hardware media smoke evidence; not part of CI.",
|
||||||
"summary_json: ${SUMMARY_JSON}",
|
"summary_json: ${SUMMARY_JSON}",
|
||||||
"summary_txt: ${SUMMARY_TXT}",
|
"summary_txt: ${SUMMARY_TXT}",
|
||||||
"upstream-hevc-nvenc.hevc",
|
"upstream-hevc-hardware.hevc",
|
||||||
"upstream-hevc-cuda-frame.png",
|
"upstream-hevc-hardware-frame.png",
|
||||||
"downstream-h264-nvenc.h264",
|
"downstream-h264-hardware.h264",
|
||||||
"downstream-h264-cuda-frame.png",
|
"downstream-h264-hardware-frame.png",
|
||||||
"audio-aac-roundtrip.wav",
|
"audio-aac-roundtrip.wav",
|
||||||
"audio-aac-roundtrip-rms.json",
|
"audio-aac-roundtrip-rms.json",
|
||||||
] {
|
] {
|
||||||
@ -36,8 +36,9 @@ fn hardware_media_smoke_uses_accelerated_video_paths() {
|
|||||||
for marker in [
|
for marker in [
|
||||||
"hevc_nvenc",
|
"hevc_nvenc",
|
||||||
"h264_nvenc",
|
"h264_nvenc",
|
||||||
"hevc_cuvid",
|
"ffmpeg_nvenc_usable",
|
||||||
"h264_cuvid",
|
"vah265enc",
|
||||||
|
"vah265dec",
|
||||||
"vah264dec",
|
"vah264dec",
|
||||||
"vulkanh264dec",
|
"vulkanh264dec",
|
||||||
"v4l2slh264dec",
|
"v4l2slh264dec",
|
||||||
|
|||||||
Loading…
x
Reference in New Issue
Block a user