From 7002ba514a964923608188819d0fb01faf8c591d Mon Sep 17 00:00:00 2001 From: Brad Stein Date: Tue, 28 Apr 2026 04:03:06 -0300 Subject: [PATCH] fix(sync): fail loud when tethys lacks gadget --- scripts/manual/run_upstream_av_sync.sh | 65 ++++++++++++++++--- .../client_manual_sync_script_contract.rs | 9 ++- 2 files changed, 64 insertions(+), 10 deletions(-) diff --git a/scripts/manual/run_upstream_av_sync.sh b/scripts/manual/run_upstream_av_sync.sh index 98d8857..8055b79 100755 --- a/scripts/manual/run_upstream_av_sync.sh +++ b/scripts/manual/run_upstream_av_sync.sh @@ -43,6 +43,7 @@ FETCH_CAPTURE=${FETCH_CAPTURE:-0} REMOTE_SERVER_PREFLIGHT=${REMOTE_SERVER_PREFLIGHT:-1} REMOTE_EXPECT_CAM_OUTPUT=${REMOTE_EXPECT_CAM_OUTPUT:-uvc} REMOTE_EXPECT_UVC_CODEC=${REMOTE_EXPECT_UVC_CODEC:-mjpeg} +CAPTURE_READY_MARKER="__LESAVKA_CAPTURE_READY__" mkdir -p "${LOCAL_OUTPUT_DIR}" STAMP="$(date +%Y%m%d-%H%M%S)" @@ -286,7 +287,8 @@ resolve_video_device() { fi fi - printf '/dev/video0\n' + printf 'Lesavka UVC video device not found on Tethys; refusing to fall back to an unrelated webcam/capture card.\n' >&2 + exit 64 } resolve_pulse_source() { @@ -305,6 +307,31 @@ resolve_pulse_source() { ' } +resolve_alsa_audio_device() { + if ! command -v arecord >/dev/null 2>&1; then + return 1 + fi + + arecord -l 2>/dev/null | awk ' + /^card [0-9]+:/ && ($0 ~ /Lesavka|UAC2_Gadget|UAC2Gadget|Composite/) { + card=$2 + sub(":", "", card) + for (i = 1; i <= NF; i++) { + if ($i == "device") { + dev=$(i + 1) + sub(":", "", dev) + printf "hw:%s,%s\n", card, dev + found=1 + exit 0 + } + } + } + END { + if (!found) exit 1 + } + ' +} + gst_video_source_caps() { case "${video_format}" in ""|mjpeg|MJPG) @@ -478,12 +505,16 @@ case "${remote_capture_stack}" in if [[ "${remote_audio_source}" == "auto" ]]; then if pulse_source="$(resolve_pulse_source)"; then 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 \ && command -v pw-v4l2 >/dev/null 2>&1 \ && pw_audio_target="$(resolve_pw_audio_target)"; then capture_mode="pwpipe" else - printf 'PipeWire Lesavka source not found; falling back to hw:3,0\n' >&2 + printf 'Lesavka audio source not found in PipeWire or ALSA; capture host does not currently expose the gadget microphone.\n' >&2 + exit 64 fi elif [[ "${remote_audio_source}" == pulse:* ]]; then capture_mode="pulse" @@ -538,6 +569,7 @@ resolved_video_size="$(resolve_video_size "${video_size}")" resolved_video_fps="$(resolve_video_fps "${video_fps}")" printf 'using video device: %s\n' "${resolved_video_device}" >&2 printf 'using video mode: %s @ %s fps (%s)\n' "${resolved_video_size}" "${resolved_video_fps}" "${video_format:-driver-default}" >&2 +printf '%s\n' "__LESAVKA_CAPTURE_READY__" video_args=(-f video4linux2 -framerate "${resolved_video_fps}" -video_size "${resolved_video_size}") if [[ -n "${video_format}" ]]; then video_args+=(-input_format "${video_format}") @@ -699,13 +731,28 @@ fi REMOTE_CAPTURE_SCRIPT capture_pid=$! -sleep 1 -if ! kill -0 "${capture_pid}" >/dev/null 2>&1; then - capture_status=0 - wait "${capture_pid}" || capture_status=$? - echo "Tethys capture failed before the sync probe could start; see ${LOCAL_CAPTURE_LOG} for details." >&2 - exit "${capture_status}" -fi +wait_for_capture_ready() { + local tries=100 + local i=0 + while (( i < tries )); do + if [[ -f "${LOCAL_CAPTURE_LOG}" ]] && grep -q "${CAPTURE_READY_MARKER}" "${LOCAL_CAPTURE_LOG}"; then + return 0 + fi + if ! kill -0 "${capture_pid}" >/dev/null 2>&1; then + capture_status=0 + wait "${capture_pid}" || capture_status=$? + echo "Tethys capture failed before the sync probe could start; see ${LOCAL_CAPTURE_LOG} for details." >&2 + exit "${capture_status}" + fi + sleep 0.1 + ((i += 1)) + done + + echo "Timed out waiting for Tethys capture to become ready; see ${LOCAL_CAPTURE_LOG} for details." >&2 + exit 90 +} + +wait_for_capture_ready sleep "${LEAD_IN_SECONDS}" diff --git a/testing/tests/client_manual_sync_script_contract.rs b/testing/tests/client_manual_sync_script_contract.rs index 30f643a..f5dc56f 100644 --- a/testing/tests/client_manual_sync_script_contract.rs +++ b/testing/tests/client_manual_sync_script_contract.rs @@ -18,8 +18,15 @@ fn upstream_sync_script_tunnels_auto_server_addr_through_ssh() { "127.0.0.1:${local_port}:127.0.0.1:${remote_port}", "RESOLVED_LESAVKA_SERVER_ADDR=\"http://127.0.0.1:${SERVER_TUNNEL_LOCAL_PORT}\"", "tunneled to ${LESAVKA_SERVER_HOST}:127.0.0.1:${SERVER_TUNNEL_REMOTE_PORT}", + "CAPTURE_READY_MARKER=\"__LESAVKA_CAPTURE_READY__\"", "Tethys capture failed before the sync probe could start", - "kill -0 \"${capture_pid}\"", + "wait_for_capture_ready", + "Timed out waiting for Tethys capture to become ready", + "grep -q \"${CAPTURE_READY_MARKER}\"", + "Lesavka UVC video device not found on Tethys; refusing to fall back to an unrelated webcam/capture card.", + "resolve_alsa_audio_device", + "PipeWire Lesavka source not found; falling back to ALSA device", + "Lesavka audio source not found in PipeWire or ALSA; capture host does not currently expose the gadget microphone.", ] { assert!( SYNC_SCRIPT.contains(expected),