test(server-rc): stabilize pulse capture harness

This commit is contained in:
Brad Stein 2026-05-04 14:05:55 -03:00
parent 1854018e2a
commit e17464e1f9
7 changed files with 76 additions and 55 deletions

6
Cargo.lock generated
View File

@ -1652,7 +1652,7 @@ checksum = "09edd9e8b54e49e587e4f6295a7d29c3ea94d469cb40ab8ca70b288248a81db2"
[[package]] [[package]]
name = "lesavka_client" name = "lesavka_client"
version = "0.19.15" version = "0.19.16"
dependencies = [ dependencies = [
"anyhow", "anyhow",
"async-stream", "async-stream",
@ -1686,7 +1686,7 @@ dependencies = [
[[package]] [[package]]
name = "lesavka_common" name = "lesavka_common"
version = "0.19.15" version = "0.19.16"
dependencies = [ dependencies = [
"anyhow", "anyhow",
"base64", "base64",
@ -1698,7 +1698,7 @@ dependencies = [
[[package]] [[package]]
name = "lesavka_server" name = "lesavka_server"
version = "0.19.15" version = "0.19.16"
dependencies = [ dependencies = [
"anyhow", "anyhow",
"base64", "base64",

View File

@ -4,7 +4,7 @@ path = "src/main.rs"
[package] [package]
name = "lesavka_client" name = "lesavka_client"
version = "0.19.15" version = "0.19.16"
edition = "2024" edition = "2024"
[dependencies] [dependencies]

View File

@ -1,6 +1,6 @@
[package] [package]
name = "lesavka_common" name = "lesavka_common"
version = "0.19.15" version = "0.19.16"
edition = "2024" edition = "2024"
build = "build.rs" build = "build.rs"

View File

@ -81,6 +81,7 @@ REMOTE_PULSE_CAPTURE_TOOL=${REMOTE_PULSE_CAPTURE_TOOL:-gst}
REMOTE_PULSE_VIDEO_MODE=${REMOTE_PULSE_VIDEO_MODE:-cfr} REMOTE_PULSE_VIDEO_MODE=${REMOTE_PULSE_VIDEO_MODE:-cfr}
REMOTE_CAPTURE_STACK=${REMOTE_CAPTURE_STACK:-pulse} REMOTE_CAPTURE_STACK=${REMOTE_CAPTURE_STACK:-pulse}
REMOTE_AUDIO_SOURCE=${REMOTE_AUDIO_SOURCE:-auto} REMOTE_AUDIO_SOURCE=${REMOTE_AUDIO_SOURCE:-auto}
REMOTE_CAPTURE_ALLOW_ALSA_FALLBACK=${REMOTE_CAPTURE_ALLOW_ALSA_FALLBACK:-0}
LESAVKA_OUTPUT_DELAY_PROBE_AUDIO_DELAY_US=${LESAVKA_OUTPUT_DELAY_PROBE_AUDIO_DELAY_US:-0} LESAVKA_OUTPUT_DELAY_PROBE_AUDIO_DELAY_US=${LESAVKA_OUTPUT_DELAY_PROBE_AUDIO_DELAY_US:-0}
STAMP="$(date +%Y%m%d-%H%M%S)" STAMP="$(date +%Y%m%d-%H%M%S)"
@ -963,6 +964,7 @@ echo " ↪ modes=${LESAVKA_SERVER_RC_MODES}"
echo " ↪ mode_source=${LESAVKA_SERVER_RC_MODE_SOURCE}" echo " ↪ mode_source=${LESAVKA_SERVER_RC_MODE_SOURCE}"
echo " ↪ video_delays=${LESAVKA_SERVER_RC_MODE_DELAYS_US}" echo " ↪ video_delays=${LESAVKA_SERVER_RC_MODE_DELAYS_US}"
echo " ↪ audio_delays=${LESAVKA_SERVER_RC_MODE_AUDIO_DELAYS_US}" echo " ↪ audio_delays=${LESAVKA_SERVER_RC_MODE_AUDIO_DELAYS_US}"
echo " ↪ capture_stack=${REMOTE_CAPTURE_STACK} audio_source=${REMOTE_AUDIO_SOURCE} pulse_tool=${REMOTE_PULSE_CAPTURE_TOOL} video_mode=${REMOTE_PULSE_VIDEO_MODE}"
echo " ↪ freshness_limit_ms=${LESAVKA_SERVER_RC_FRESHNESS_MAX_AGE_MS}" echo " ↪ freshness_limit_ms=${LESAVKA_SERVER_RC_FRESHNESS_MAX_AGE_MS}"
echo " ↪ reconfigure=${LESAVKA_SERVER_RC_RECONFIGURE} strategy=${LESAVKA_SERVER_RC_RECONFIGURE_STRATEGY} allow_gadget_reset=${LESAVKA_SERVER_RC_ALLOW_GADGET_RESET}" echo " ↪ reconfigure=${LESAVKA_SERVER_RC_RECONFIGURE} strategy=${LESAVKA_SERVER_RC_RECONFIGURE_STRATEGY} allow_gadget_reset=${LESAVKA_SERVER_RC_ALLOW_GADGET_RESET}"
echo " ↪ tethys_ready=${LESAVKA_SERVER_RC_WAIT_TETHYS_READY} settle=${LESAVKA_SERVER_RC_TETHYS_SETTLE_SECONDS}s timeout=${LESAVKA_SERVER_RC_TETHYS_READY_TIMEOUT_SECONDS}s preroll_discard=${LESAVKA_SERVER_RC_PREROLL_DISCARD_SECONDS}s" echo " ↪ tethys_ready=${LESAVKA_SERVER_RC_WAIT_TETHYS_READY} settle=${LESAVKA_SERVER_RC_TETHYS_SETTLE_SECONDS}s timeout=${LESAVKA_SERVER_RC_TETHYS_READY_TIMEOUT_SECONDS}s preroll_discard=${LESAVKA_SERVER_RC_PREROLL_DISCARD_SECONDS}s"
@ -1000,6 +1002,7 @@ for mode in "${modes[@]}"; do
REMOTE_PULSE_VIDEO_MODE="${REMOTE_PULSE_VIDEO_MODE}" \ REMOTE_PULSE_VIDEO_MODE="${REMOTE_PULSE_VIDEO_MODE}" \
REMOTE_CAPTURE_STACK="${REMOTE_CAPTURE_STACK}" \ REMOTE_CAPTURE_STACK="${REMOTE_CAPTURE_STACK}" \
REMOTE_AUDIO_SOURCE="${REMOTE_AUDIO_SOURCE}" \ REMOTE_AUDIO_SOURCE="${REMOTE_AUDIO_SOURCE}" \
REMOTE_CAPTURE_ALLOW_ALSA_FALLBACK="${REMOTE_CAPTURE_ALLOW_ALSA_FALLBACK}" \
REMOTE_CAPTURE_PREROLL_DISCARD_SECONDS="${LESAVKA_SERVER_RC_PREROLL_DISCARD_SECONDS}" \ REMOTE_CAPTURE_PREROLL_DISCARD_SECONDS="${LESAVKA_SERVER_RC_PREROLL_DISCARD_SECONDS}" \
PROBE_PREBUILD=0 \ PROBE_PREBUILD=0 \
VIDEO_SIZE="${width}x${height}" \ VIDEO_SIZE="${width}x${height}" \

View File

@ -19,7 +19,8 @@ LESAVKA_SERVER_SCHEME=${LESAVKA_SERVER_SCHEME:-https}
LESAVKA_TLS_DOMAIN=${LESAVKA_TLS_DOMAIN:-lesavka-server} LESAVKA_TLS_DOMAIN=${LESAVKA_TLS_DOMAIN:-lesavka-server}
PROBE_DURATION_SECONDS=${PROBE_DURATION_SECONDS:-20} PROBE_DURATION_SECONDS=${PROBE_DURATION_SECONDS:-20}
PROBE_WARMUP_SECONDS=${PROBE_WARMUP_SECONDS:-4} PROBE_WARMUP_SECONDS=${PROBE_WARMUP_SECONDS:-4}
PROBE_TIMEOUT_SECONDS=${PROBE_TIMEOUT_SECONDS:-$((PROBE_DURATION_SECONDS + PROBE_WARMUP_SECONDS + 20))} PROBE_START_GRACE_SECONDS=${PROBE_START_GRACE_SECONDS:-20}
PROBE_TIMEOUT_SECONDS=${PROBE_TIMEOUT_SECONDS:-$((PROBE_DURATION_SECONDS + PROBE_WARMUP_SECONDS + PROBE_START_GRACE_SECONDS))}
PROBE_PULSE_PERIOD_MS=${PROBE_PULSE_PERIOD_MS:-1000} PROBE_PULSE_PERIOD_MS=${PROBE_PULSE_PERIOD_MS:-1000}
PROBE_PULSE_WIDTH_MS=${PROBE_PULSE_WIDTH_MS:-120} PROBE_PULSE_WIDTH_MS=${PROBE_PULSE_WIDTH_MS:-120}
PROBE_EVENT_WIDTH_CODES=${PROBE_EVENT_WIDTH_CODES:-1,2,1,3,2,4,1,1,3,1,4,2,1,2,3,4,1,3,2,2,4,1,2,4,3,1,1,4,2,3,1,2} PROBE_EVENT_WIDTH_CODES=${PROBE_EVENT_WIDTH_CODES:-1,2,1,3,2,4,1,1,3,1,4,2,1,2,3,4,1,3,2,2,4,1,2,4,3,1,1,4,2,3,1,2}
@ -30,7 +31,7 @@ LESAVKA_OUTPUT_DELAY_PROBE_VIDEO_DELAY_US=${LESAVKA_OUTPUT_DELAY_PROBE_VIDEO_DEL
# VIDIOC_STREAMON if the camera is starved during pre-roll. # VIDIOC_STREAMON if the camera is starved during pre-roll.
LEAD_IN_SECONDS=${LEAD_IN_SECONDS:-0} LEAD_IN_SECONDS=${LEAD_IN_SECONDS:-0}
TAIL_SECONDS=${TAIL_SECONDS:-2} TAIL_SECONDS=${TAIL_SECONDS:-2}
CAPTURE_SECONDS=${CAPTURE_SECONDS:-$((PROBE_DURATION_SECONDS + PROBE_WARMUP_SECONDS + LEAD_IN_SECONDS + TAIL_SECONDS))} CAPTURE_SECONDS=${CAPTURE_SECONDS:-$((PROBE_DURATION_SECONDS + PROBE_WARMUP_SECONDS + PROBE_START_GRACE_SECONDS + LEAD_IN_SECONDS + TAIL_SECONDS))}
LOCAL_OUTPUT_DIR=${LOCAL_OUTPUT_DIR:-/tmp} LOCAL_OUTPUT_DIR=${LOCAL_OUTPUT_DIR:-/tmp}
REMOTE_VIDEO_DEVICE=${REMOTE_VIDEO_DEVICE:-auto} REMOTE_VIDEO_DEVICE=${REMOTE_VIDEO_DEVICE:-auto}
VIDEO_SIZE=${VIDEO_SIZE:-auto} VIDEO_SIZE=${VIDEO_SIZE:-auto}
@ -41,6 +42,7 @@ REMOTE_PULSE_CAPTURE_TOOL=${REMOTE_PULSE_CAPTURE_TOOL:-gst}
REMOTE_PULSE_VIDEO_MODE=${REMOTE_PULSE_VIDEO_MODE:-copy} REMOTE_PULSE_VIDEO_MODE=${REMOTE_PULSE_VIDEO_MODE:-copy}
REMOTE_AUDIO_SOURCE=${REMOTE_AUDIO_SOURCE:-auto} REMOTE_AUDIO_SOURCE=${REMOTE_AUDIO_SOURCE:-auto}
REMOTE_AUDIO_QUIESCE_USER_AUDIO=${REMOTE_AUDIO_QUIESCE_USER_AUDIO:-auto} REMOTE_AUDIO_QUIESCE_USER_AUDIO=${REMOTE_AUDIO_QUIESCE_USER_AUDIO:-auto}
REMOTE_CAPTURE_ALLOW_ALSA_FALLBACK=${REMOTE_CAPTURE_ALLOW_ALSA_FALLBACK:-0}
REMOTE_CAPTURE_PREROLL_DISCARD_SECONDS=${REMOTE_CAPTURE_PREROLL_DISCARD_SECONDS:-0} REMOTE_CAPTURE_PREROLL_DISCARD_SECONDS=${REMOTE_CAPTURE_PREROLL_DISCARD_SECONDS:-0}
ANALYSIS_NORMALIZE=${ANALYSIS_NORMALIZE:-0} ANALYSIS_NORMALIZE=${ANALYSIS_NORMALIZE:-0}
ANALYSIS_SCALE_WIDTH=${ANALYSIS_SCALE_WIDTH:-1280} ANALYSIS_SCALE_WIDTH=${ANALYSIS_SCALE_WIDTH:-1280}
@ -533,11 +535,11 @@ preflight_server_path() {
"${REMOTE_EXPECT_UVC_HEIGHT}" \ "${REMOTE_EXPECT_UVC_HEIGHT}" \
"${REMOTE_EXPECT_UVC_FPS}" <<'REMOTE_PREFLIGHT' "${REMOTE_EXPECT_UVC_FPS}" <<'REMOTE_PREFLIGHT'
set -euo pipefail set -euo pipefail
expect_cam_output=$1 expect_cam_output=${1:-}
expect_uvc_codec=$2 expect_uvc_codec=${2:-}
expect_uvc_width=$3 expect_uvc_width=${3:-}
expect_uvc_height=$4 expect_uvc_height=${4:-}
expect_uvc_fps=$5 expect_uvc_fps=${5:-}
read_env_value() { read_env_value() {
local key=$1 local key=$1
@ -1978,6 +1980,7 @@ ssh ${SSH_OPTS} "${TETHYS_HOST}" bash -s -- \
"${REMOTE_PULSE_VIDEO_MODE}" \ "${REMOTE_PULSE_VIDEO_MODE}" \
"${REMOTE_AUDIO_SOURCE}" \ "${REMOTE_AUDIO_SOURCE}" \
"${REMOTE_AUDIO_QUIESCE_USER_AUDIO}" \ "${REMOTE_AUDIO_QUIESCE_USER_AUDIO}" \
"${REMOTE_CAPTURE_ALLOW_ALSA_FALLBACK}" \
"${REMOTE_CAPTURE_PREROLL_DISCARD_SECONDS}" \ "${REMOTE_CAPTURE_PREROLL_DISCARD_SECONDS}" \
> >(tee "${LOCAL_CAPTURE_LOG}") \ > >(tee "${LOCAL_CAPTURE_LOG}") \
2> >(tee -a "${LOCAL_CAPTURE_LOG}" >&2) <<'REMOTE_CAPTURE_SCRIPT' & 2> >(tee -a "${LOCAL_CAPTURE_LOG}" >&2) <<'REMOTE_CAPTURE_SCRIPT' &
@ -1993,7 +1996,8 @@ remote_pulse_capture_tool=$8
remote_pulse_video_mode=$9 remote_pulse_video_mode=$9
remote_audio_source=${10} remote_audio_source=${10}
remote_audio_quiesce_user_audio=${11} remote_audio_quiesce_user_audio=${11}
remote_capture_preroll_discard_seconds=${12} remote_capture_allow_alsa_fallback=${12}
remote_capture_preroll_discard_seconds=${13}
rm -f "${remote_capture}" rm -f "${remote_capture}"
@ -2252,7 +2256,7 @@ raise SystemExit(1)
' '
} }
capture_mode="alsa" capture_mode=""
alsa_audio_dev="hw:3,0" alsa_audio_dev="hw:3,0"
pulse_source="" pulse_source=""
pw_audio_target="" pw_audio_target=""
@ -2262,21 +2266,23 @@ case "${remote_capture_stack}" in
if [[ "${remote_audio_source}" == "auto" ]]; then if [[ "${remote_audio_source}" == "auto" ]]; then
if pulse_source="$(resolve_pulse_source)"; then if pulse_source="$(resolve_pulse_source)"; then
capture_mode="pulse" capture_mode="pulse"
elif alsa_audio_dev="$(resolve_alsa_audio_device)"; then
capture_mode="alsa"
printf 'PipeWire Lesavka source not found; falling back to ALSA device %s\n' "${alsa_audio_dev}" >&2
elif command -v pw-record >/dev/null 2>&1 \ elif command -v pw-record >/dev/null 2>&1 \
&& command -v pw-v4l2 >/dev/null 2>&1 \ && command -v pw-v4l2 >/dev/null 2>&1 \
&& pw_audio_target="$(resolve_pw_audio_target)"; then && pw_audio_target="$(resolve_pw_audio_target)"; then
capture_mode="pwpipe" capture_mode="pwpipe"
elif [[ "${remote_capture_allow_alsa_fallback}" == "1" ]] && alsa_audio_dev="$(resolve_alsa_audio_device)"; then
capture_mode="alsa"
printf 'PipeWire Lesavka source not found; using explicit diagnostic ALSA fallback device %s\n' "${alsa_audio_dev}" >&2
else else
printf 'Lesavka audio source not found in PipeWire or ALSA; capture host does not currently expose the gadget microphone.\n' >&2 printf 'Lesavka Pulse/PipeWire audio source not found; refusing raw ALSA fallback for timing-sensitive capture.\n' >&2
printf 'Set REMOTE_CAPTURE_STACK=alsa or REMOTE_CAPTURE_ALLOW_ALSA_FALLBACK=1 only for diagnostic signal-presence checks.\n' >&2
exit 64 exit 64
fi fi
elif [[ "${remote_audio_source}" == pulse:* ]]; then elif [[ "${remote_audio_source}" == pulse:* ]]; then
capture_mode="pulse" capture_mode="pulse"
pulse_source="${remote_audio_source#pulse:}" pulse_source="${remote_audio_source#pulse:}"
elif [[ "${remote_audio_source}" == alsa:* ]]; then elif [[ "${remote_audio_source}" == alsa:* ]]; then
capture_mode="alsa"
alsa_audio_dev="${remote_audio_source#alsa:}" alsa_audio_dev="${remote_audio_source#alsa:}"
else else
printf 'unsupported REMOTE_AUDIO_SOURCE=%s\n' "${remote_audio_source}" >&2 printf 'unsupported REMOTE_AUDIO_SOURCE=%s\n' "${remote_audio_source}" >&2
@ -2452,11 +2458,8 @@ elif [[ "${capture_mode}" == "pulse" ]]; then
-thread_queue_size 1024 \ -thread_queue_size 1024 \
-f pulse \ -f pulse \
-i "${pulse_source}" \ -i "${pulse_source}" \
-f lavfi \
-i anullsrc=channel_layout=stereo:sample_rate=48000 \
-filter_complex "[1:a][2:a]amix=inputs=2:duration=longest:dropout_transition=0[aout]" \
-map 0:v:0 \ -map 0:v:0 \
-map "[aout]" \ -map 1:a:0 \
-t "${capture_seconds}" \ -t "${capture_seconds}" \
-c:v copy \ -c:v copy \
-c:a pcm_s16le \ -c:a pcm_s16le \
@ -2470,11 +2473,8 @@ elif [[ "${capture_mode}" == "pulse" ]]; then
-thread_queue_size 1024 \ -thread_queue_size 1024 \
-f pulse \ -f pulse \
-i "${pulse_source}" \ -i "${pulse_source}" \
-f lavfi \
-i anullsrc=channel_layout=stereo:sample_rate=48000 \
-filter_complex "[1:a][2:a]amix=inputs=2:duration=longest:dropout_transition=0[aout]" \
-map 0:v:0 \ -map 0:v:0 \
-map "[aout]" \ -map 1:a:0 \
-t "${capture_seconds}" \ -t "${capture_seconds}" \
-vf "fps=${resolved_video_fps}" \ -vf "fps=${resolved_video_fps}" \
-c:v libx264 -preset ultrafast -crf 12 -g 1 -pix_fmt yuv420p \ -c:v libx264 -preset ultrafast -crf 12 -g 1 -pix_fmt yuv420p \
@ -2500,13 +2500,8 @@ elif [[ "${capture_mode}" == "pulse" ]]; then
v4l2src device="${resolved_video_device}" do-timestamp=true ! \ v4l2src device="${resolved_video_device}" do-timestamp=true ! \
${gst_source_caps} ! \ ${gst_source_caps} ! \
queue ! mux. \ queue ! mux. \
audiotestsrc wave=silence is-live=true samplesperbuffer=480 ! \
audio/x-raw,rate=48000,channels=2 ! \
queue ! mix. \
pulsesrc device="${pulse_source}" do-timestamp=true ! \ pulsesrc device="${pulse_source}" do-timestamp=true ! \
audio/x-raw,rate=48000,channels=2 ! \ audio/x-raw,rate=48000,channels=2 ! \
audioconvert ! audioresample ! queue ! mix. \
audiomixer name=mix ! \
audioconvert ! audioresample ! audio/x-raw,rate=48000,channels=2 ! \ audioconvert ! audioresample ! audio/x-raw,rate=48000,channels=2 ! \
queue ! mux. || true queue ! mux. || true
;; ;;
@ -2521,13 +2516,8 @@ elif [[ "${capture_mode}" == "pulse" ]]; then
x264enc tune=zerolatency speed-preset=ultrafast key-int-max=1 bitrate=5000 ! \ x264enc tune=zerolatency speed-preset=ultrafast key-int-max=1 bitrate=5000 ! \
h264parse ! \ h264parse ! \
queue ! mux. \ queue ! mux. \
audiotestsrc wave=silence is-live=true samplesperbuffer=480 ! \
audio/x-raw,rate=48000,channels=2 ! \
queue ! mix. \
pulsesrc device="${pulse_source}" do-timestamp=true ! \ pulsesrc device="${pulse_source}" do-timestamp=true ! \
audio/x-raw,rate=48000,channels=2 ! \ audio/x-raw,rate=48000,channels=2 ! \
audioconvert ! audioresample ! queue ! mix. \
audiomixer name=mix ! \
audioconvert ! audioresample ! audio/x-raw,rate=48000,channels=2 ! \ audioconvert ! audioresample ! audio/x-raw,rate=48000,channels=2 ! \
queue ! mux. || true queue ! mux. || true
;; ;;
@ -2543,17 +2533,39 @@ elif [[ "${capture_mode}" == "pulse" ]]; then
;; ;;
esac esac
else else
run_ffmpeg_capture ffmpeg -hide_banner -loglevel error -y \ case "${remote_pulse_video_mode}" in
-thread_queue_size 1024 \ copy)
"${video_args[@]}" \ run_ffmpeg_capture ffmpeg -hide_banner -loglevel error -y \
-i "${resolved_video_device}" \ -thread_queue_size 1024 \
-thread_queue_size 1024 \ "${video_args[@]}" \
-f alsa -ac 2 -ar 48000 \ -i "${resolved_video_device}" \
-i "${alsa_audio_dev}" \ -thread_queue_size 1024 \
-t "${capture_seconds}" \ -f alsa -ac 2 -ar 48000 \
-c:v ffv1 -level 3 -g 1 \ -i "${alsa_audio_dev}" \
-c:a pcm_s16le \ -t "${capture_seconds}" \
"${remote_capture}" -c:v copy \
-c:a pcm_s16le \
"${remote_capture}"
;;
cfr)
run_ffmpeg_capture ffmpeg -hide_banner -loglevel error -y \
-thread_queue_size 1024 \
"${video_args[@]}" \
-i "${resolved_video_device}" \
-thread_queue_size 1024 \
-f alsa -ac 2 -ar 48000 \
-i "${alsa_audio_dev}" \
-t "${capture_seconds}" \
-vf "fps=${resolved_video_fps}" \
-c:v libx264 -preset ultrafast -crf 12 -g 1 -pix_fmt yuv420p \
-c:a pcm_s16le \
"${remote_capture}"
;;
*)
printf 'unsupported REMOTE_PULSE_VIDEO_MODE=%s\n' "${remote_pulse_video_mode}" >&2
exit 64
;;
esac
fi fi
REMOTE_CAPTURE_SCRIPT REMOTE_CAPTURE_SCRIPT
capture_pid=$! capture_pid=$!

View File

@ -10,7 +10,7 @@ bench = false
[package] [package]
name = "lesavka_server" name = "lesavka_server"
version = "0.19.15" version = "0.19.16"
edition = "2024" edition = "2024"
autobins = false autobins = false

View File

@ -139,11 +139,14 @@ fn upstream_sync_script_tunnels_auto_server_addr_through_ssh() {
"\"${LESAVKA_OUTPUT_DELAY_PROBE_AUDIO_DELAY_US}\"", "\"${LESAVKA_OUTPUT_DELAY_PROBE_AUDIO_DELAY_US}\"",
"\"${LESAVKA_OUTPUT_DELAY_PROBE_VIDEO_DELAY_US}\"", "\"${LESAVKA_OUTPUT_DELAY_PROBE_VIDEO_DELAY_US}\"",
"REMOTE_PULSE_CAPTURE_TOOL=${REMOTE_PULSE_CAPTURE_TOOL:-gst}", "REMOTE_PULSE_CAPTURE_TOOL=${REMOTE_PULSE_CAPTURE_TOOL:-gst}",
"REMOTE_CAPTURE_ALLOW_ALSA_FALLBACK=${REMOTE_CAPTURE_ALLOW_ALSA_FALLBACK:-0}",
"output_delay_calibration_json", "output_delay_calibration_json",
"direct UVC/UAC output-delay calibration", "direct UVC/UAC output-delay calibration",
"calibration-save-default", "calibration-save-default",
"LEAD_IN_SECONDS=${LEAD_IN_SECONDS:-0}", "LEAD_IN_SECONDS=${LEAD_IN_SECONDS:-0}",
"PROBE_TIMEOUT_SECONDS=${PROBE_TIMEOUT_SECONDS:-$((PROBE_DURATION_SECONDS + PROBE_WARMUP_SECONDS + 20))}", "PROBE_START_GRACE_SECONDS=${PROBE_START_GRACE_SECONDS:-20}",
"PROBE_TIMEOUT_SECONDS=${PROBE_TIMEOUT_SECONDS:-$((PROBE_DURATION_SECONDS + PROBE_WARMUP_SECONDS + PROBE_START_GRACE_SECONDS))}",
"CAPTURE_SECONDS=${CAPTURE_SECONDS:-$((PROBE_DURATION_SECONDS + PROBE_WARMUP_SECONDS + PROBE_START_GRACE_SECONDS + LEAD_IN_SECONDS + TAIL_SECONDS))}",
"ANALYSIS_TIMELINE_WINDOW=${ANALYSIS_TIMELINE_WINDOW:-1}", "ANALYSIS_TIMELINE_WINDOW=${ANALYSIS_TIMELINE_WINDOW:-1}",
"compute_analysis_window_arg", "compute_analysis_window_arg",
"analyzer timeline window:", "analyzer timeline window:",
@ -159,13 +162,13 @@ fn upstream_sync_script_tunnels_auto_server_addr_through_ssh() {
"grep -q \"${CAPTURE_READY_MARKER}\"", "grep -q \"${CAPTURE_READY_MARKER}\"",
"Lesavka UVC video device not found on Tethys; refusing to fall back to an unrelated webcam/capture card.", "Lesavka UVC video device not found on Tethys; refusing to fall back to an unrelated webcam/capture card.",
"resolve_alsa_audio_device", "resolve_alsa_audio_device",
"PipeWire Lesavka source not found; falling back to ALSA device", "PipeWire Lesavka source not found; using explicit diagnostic ALSA fallback device",
"Lesavka audio source not found in PipeWire or ALSA; capture host does not currently expose the gadget microphone.", "Set REMOTE_CAPTURE_STACK=alsa or REMOTE_CAPTURE_ALLOW_ALSA_FALLBACK=1 only for diagnostic signal-presence checks.",
"Lesavka Pulse/PipeWire audio source not found; refusing raw ALSA fallback for timing-sensitive capture.",
"discarding %ss of post-enumeration capture before probe", "discarding %ss of post-enumeration capture before probe",
"audiotestsrc wave=silence is-live=true samplesperbuffer=480", "using Pulse source:",
"audiomixer name=mix", "-f pulse",
"anullsrc=channel_layout=stereo:sample_rate=48000", "-map 1:a:0",
"amix=inputs=2:duration=longest",
"artifact_dir: ${LOCAL_REPORT_DIR}", "artifact_dir: ${LOCAL_REPORT_DIR}",
"events_csv: ${LOCAL_EVENTS_CSV}", "events_csv: ${LOCAL_EVENTS_CSV}",
"server_timeline_json: ${LOCAL_SERVER_TIMELINE_JSON}", "server_timeline_json: ${LOCAL_SERVER_TIMELINE_JSON}",
@ -238,6 +241,7 @@ fn server_rc_mode_matrix_validates_advertised_uvc_profiles() {
"mode_source=${LESAVKA_SERVER_RC_MODE_SOURCE}", "mode_source=${LESAVKA_SERVER_RC_MODE_SOURCE}",
"video_delays=${LESAVKA_SERVER_RC_MODE_DELAYS_US}", "video_delays=${LESAVKA_SERVER_RC_MODE_DELAYS_US}",
"audio_delays=${LESAVKA_SERVER_RC_MODE_AUDIO_DELAYS_US}", "audio_delays=${LESAVKA_SERVER_RC_MODE_AUDIO_DELAYS_US}",
"pulse_tool=${REMOTE_PULSE_CAPTURE_TOOL}",
"fast runtime env updated: CAM_OUTPUT=uvc", "fast runtime env updated: CAM_OUTPUT=uvc",
"cycling UVC gadget descriptors", "cycling UVC gadget descriptors",
"lesavka-core reconfigure log:", "lesavka-core reconfigure log:",
@ -262,6 +266,8 @@ fn server_rc_mode_matrix_validates_advertised_uvc_profiles() {
"calibration:", "calibration:",
"REMOTE_PULSE_CAPTURE_TOOL=\"${REMOTE_PULSE_CAPTURE_TOOL}\"", "REMOTE_PULSE_CAPTURE_TOOL=\"${REMOTE_PULSE_CAPTURE_TOOL}\"",
"REMOTE_PULSE_VIDEO_MODE=\"${REMOTE_PULSE_VIDEO_MODE}\"", "REMOTE_PULSE_VIDEO_MODE=\"${REMOTE_PULSE_VIDEO_MODE}\"",
"REMOTE_CAPTURE_STACK=\"${REMOTE_CAPTURE_STACK}\"",
"REMOTE_CAPTURE_ALLOW_ALSA_FALLBACK=\"${REMOTE_CAPTURE_ALLOW_ALSA_FALLBACK}\"",
"REMOTE_CAPTURE_PREROLL_DISCARD_SECONDS=\"${LESAVKA_SERVER_RC_PREROLL_DISCARD_SECONDS}\"", "REMOTE_CAPTURE_PREROLL_DISCARD_SECONDS=\"${LESAVKA_SERVER_RC_PREROLL_DISCARD_SECONDS}\"",
"PROBE_PREBUILD=0", "PROBE_PREBUILD=0",
"VIDEO_SIZE=\"${width}x${height}\"", "VIDEO_SIZE=\"${width}x${height}\"",