fix(sync): harden mjpeg uvc capture path
This commit is contained in:
parent
8b5dc220ad
commit
ecc5b6df87
6
Cargo.lock
generated
6
Cargo.lock
generated
@ -1642,7 +1642,7 @@ checksum = "09edd9e8b54e49e587e4f6295a7d29c3ea94d469cb40ab8ca70b288248a81db2"
|
||||
|
||||
[[package]]
|
||||
name = "lesavka_client"
|
||||
version = "0.14.30"
|
||||
version = "0.14.31"
|
||||
dependencies = [
|
||||
"anyhow",
|
||||
"async-stream",
|
||||
@ -1676,7 +1676,7 @@ dependencies = [
|
||||
|
||||
[[package]]
|
||||
name = "lesavka_common"
|
||||
version = "0.14.30"
|
||||
version = "0.14.31"
|
||||
dependencies = [
|
||||
"anyhow",
|
||||
"base64",
|
||||
@ -1688,7 +1688,7 @@ dependencies = [
|
||||
|
||||
[[package]]
|
||||
name = "lesavka_server"
|
||||
version = "0.14.30"
|
||||
version = "0.14.31"
|
||||
dependencies = [
|
||||
"anyhow",
|
||||
"base64",
|
||||
|
||||
@ -4,7 +4,7 @@ path = "src/main.rs"
|
||||
|
||||
[package]
|
||||
name = "lesavka_client"
|
||||
version = "0.14.30"
|
||||
version = "0.14.31"
|
||||
edition = "2024"
|
||||
|
||||
[dependencies]
|
||||
|
||||
@ -1,6 +1,6 @@
|
||||
[package]
|
||||
name = "lesavka_common"
|
||||
version = "0.14.30"
|
||||
version = "0.14.31"
|
||||
edition = "2024"
|
||||
build = "build.rs"
|
||||
|
||||
|
||||
@ -27,7 +27,7 @@ render_uvc_env_file() {
|
||||
# generated by lesavka/scripts/install/server.sh
|
||||
# Edit only for local UVC hardware overrides; rerunning the installer refreshes defaults.
|
||||
LESAVKA_UVC_DEBUG=${LESAVKA_UVC_DEBUG:-1}
|
||||
LESAVKA_UVC_MAXPACKET=${LESAVKA_UVC_MAXPACKET:-256}
|
||||
LESAVKA_UVC_MAXPACKET=${LESAVKA_UVC_MAXPACKET:-1024}
|
||||
LESAVKA_UVC_LIMIT_PCT=${LESAVKA_UVC_LIMIT_PCT:-100}
|
||||
LESAVKA_UVC_FPS=${LESAVKA_UVC_FPS:-20}
|
||||
LESAVKA_UVC_INTERVAL=${LESAVKA_UVC_INTERVAL:-500000}
|
||||
|
||||
@ -19,8 +19,9 @@ TAIL_SECONDS=${TAIL_SECONDS:-2}
|
||||
CAPTURE_SECONDS=${CAPTURE_SECONDS:-$((PROBE_DURATION_SECONDS + PROBE_WARMUP_SECONDS + LEAD_IN_SECONDS + TAIL_SECONDS))}
|
||||
REMOTE_CAPTURE=${REMOTE_CAPTURE:-/tmp/lesavka-upstream-av-sync.mkv}
|
||||
LOCAL_OUTPUT_DIR=${LOCAL_OUTPUT_DIR:-"${REPO_ROOT}/tmp"}
|
||||
REMOTE_VIDEO_DEVICE=${REMOTE_VIDEO_DEVICE:-auto}
|
||||
VIDEO_SIZE=${VIDEO_SIZE:-auto}
|
||||
VIDEO_FPS=${VIDEO_FPS:-30}
|
||||
VIDEO_FPS=${VIDEO_FPS:-auto}
|
||||
VIDEO_FORMAT=${VIDEO_FORMAT:-mjpeg}
|
||||
REMOTE_CAPTURE_STACK=${REMOTE_CAPTURE_STACK:-pulse}
|
||||
REMOTE_PULSE_CAPTURE_TOOL=${REMOTE_PULSE_CAPTURE_TOOL:-ffmpeg}
|
||||
@ -120,6 +121,7 @@ echo "==> starting Tethys capture on ${TETHYS_HOST}"
|
||||
ssh ${SSH_OPTS} "${TETHYS_HOST}" bash -s -- \
|
||||
"${REMOTE_CAPTURE}" \
|
||||
"${CAPTURE_SECONDS}" \
|
||||
"${REMOTE_VIDEO_DEVICE}" \
|
||||
"${VIDEO_SIZE}" \
|
||||
"${VIDEO_FPS}" \
|
||||
"${VIDEO_FORMAT}" \
|
||||
@ -133,14 +135,15 @@ ssh ${SSH_OPTS} "${TETHYS_HOST}" bash -s -- \
|
||||
set -euo pipefail
|
||||
remote_capture=$1
|
||||
capture_seconds=$2
|
||||
video_size=$3
|
||||
video_fps=$4
|
||||
video_format=$5
|
||||
remote_capture_stack=$6
|
||||
remote_pulse_capture_tool=$7
|
||||
remote_pulse_video_mode=$8
|
||||
remote_audio_source=$9
|
||||
remote_audio_quiesce_user_audio=${10}
|
||||
remote_video_device=$3
|
||||
video_size=$4
|
||||
video_fps=$5
|
||||
video_format=$6
|
||||
remote_capture_stack=$7
|
||||
remote_pulse_capture_tool=$8
|
||||
remote_pulse_video_mode=$9
|
||||
remote_audio_source=${10}
|
||||
remote_audio_quiesce_user_audio=${11}
|
||||
|
||||
rm -f "${remote_capture}"
|
||||
|
||||
@ -156,6 +159,44 @@ quiesce_user_audio() {
|
||||
sleep 1
|
||||
}
|
||||
|
||||
resolve_video_device() {
|
||||
local requested=$1
|
||||
if [[ "${requested}" != "auto" ]]; then
|
||||
printf '%s\n' "${requested}"
|
||||
return 0
|
||||
fi
|
||||
|
||||
local by_id
|
||||
by_id=$(find /dev/v4l/by-id -maxdepth 1 -type l -name '*Lesavka*video-index0' 2>/dev/null | head -n1 || true)
|
||||
if [[ -n "${by_id}" ]]; then
|
||||
printf '%s\n' "${by_id}"
|
||||
return 0
|
||||
fi
|
||||
|
||||
if command -v v4l2-ctl >/dev/null 2>&1; then
|
||||
local resolved
|
||||
resolved="$(
|
||||
v4l2-ctl --list-devices 2>/dev/null \
|
||||
| awk '
|
||||
BEGIN { want=0 }
|
||||
/Lesavka Composite: UVC Camera/ { want=1; next }
|
||||
/^[^ \t]/ { want=0 }
|
||||
want && /^[ \t]+\/dev\/video[0-9]+/ {
|
||||
gsub(/^[ \t]+/, "", $0)
|
||||
print
|
||||
exit
|
||||
}
|
||||
'
|
||||
)"
|
||||
if [[ -n "${resolved}" ]]; then
|
||||
printf '%s\n' "${resolved}"
|
||||
return 0
|
||||
fi
|
||||
fi
|
||||
|
||||
printf '/dev/video0\n'
|
||||
}
|
||||
|
||||
resolve_pulse_source() {
|
||||
if ! command -v pactl >/dev/null 2>&1; then
|
||||
return 1
|
||||
@ -178,13 +219,13 @@ gst_video_source_caps() {
|
||||
printf 'image/jpeg,width=%s,height=%s,framerate=%s/1' \
|
||||
"${resolved_video_size%x*}" \
|
||||
"${resolved_video_size#*x}" \
|
||||
"${video_fps}"
|
||||
"${resolved_video_fps}"
|
||||
;;
|
||||
yuyv422|YUYV|yuyv)
|
||||
printf 'video/x-raw,format=YUY2,width=%s,height=%s,framerate=%s/1' \
|
||||
"${resolved_video_size%x*}" \
|
||||
"${resolved_video_size#*x}" \
|
||||
"${video_fps}"
|
||||
"${resolved_video_fps}"
|
||||
;;
|
||||
*)
|
||||
printf 'unsupported gst video_format=%s\n' "${video_format}" >&2
|
||||
@ -208,18 +249,56 @@ gst_video_decode_chain() {
|
||||
esac
|
||||
}
|
||||
|
||||
current_video_profile() {
|
||||
if ! command -v v4l2-ctl >/dev/null 2>&1; then
|
||||
return 1
|
||||
fi
|
||||
|
||||
v4l2-ctl -d "${resolved_video_device}" --all 2>/dev/null \
|
||||
| awk '
|
||||
/Width\/Height[[:space:]]*:/ {
|
||||
split($0, a, ":")
|
||||
gsub(/^[ \t]+/, "", a[2])
|
||||
split(a[2], wh, "/")
|
||||
width=wh[1]
|
||||
height=wh[2]
|
||||
next
|
||||
}
|
||||
/Frames per second:/ {
|
||||
fps=$4
|
||||
sub(/\..*/, "", fps)
|
||||
}
|
||||
END {
|
||||
if (width != "" && height != "") {
|
||||
print "size=" width "x" height
|
||||
}
|
||||
if (fps != "") {
|
||||
print "fps=" fps
|
||||
}
|
||||
}
|
||||
'
|
||||
}
|
||||
|
||||
resolve_video_size() {
|
||||
local requested=$1
|
||||
if [[ "${requested}" != "auto" ]]; then
|
||||
printf '%s\n' "${requested}"
|
||||
return 0
|
||||
fi
|
||||
local current_profile
|
||||
current_profile="$(current_video_profile || true)"
|
||||
local current_size
|
||||
current_size="$(awk -F= '/^size=/{print $2; exit}' <<<"${current_profile}")"
|
||||
if [[ -n "${current_size}" ]]; then
|
||||
printf '%s\n' "${current_size}"
|
||||
return 0
|
||||
fi
|
||||
if ! command -v v4l2-ctl >/dev/null 2>&1; then
|
||||
printf '1280x720\n'
|
||||
printf '640x480\n'
|
||||
return 0
|
||||
fi
|
||||
local listing
|
||||
listing="$(v4l2-ctl -d /dev/video0 --list-formats-ext 2>/dev/null || true)"
|
||||
listing="$(v4l2-ctl -d "${resolved_video_device}" --list-formats-ext 2>/dev/null || true)"
|
||||
local preferred
|
||||
for preferred in 1920x1080 1360x768 1280x720; do
|
||||
if grep -q "Size: Discrete ${preferred}" <<<"${listing}"; then
|
||||
@ -227,7 +306,42 @@ resolve_video_size() {
|
||||
return 0
|
||||
fi
|
||||
done
|
||||
printf '1280x720\n'
|
||||
local first_size
|
||||
first_size="$(grep -m1 -o 'Size: Discrete [0-9]\+x[0-9]\+' <<<"${listing}" | awk '{print $3}' || true)"
|
||||
if [[ -n "${first_size}" ]]; then
|
||||
printf '%s\n' "${first_size}"
|
||||
return 0
|
||||
fi
|
||||
printf '640x480\n'
|
||||
}
|
||||
|
||||
resolve_video_fps() {
|
||||
local requested=$1
|
||||
if [[ "${requested}" != "auto" ]]; then
|
||||
printf '%s\n' "${requested}"
|
||||
return 0
|
||||
fi
|
||||
local current_profile
|
||||
current_profile="$(current_video_profile || true)"
|
||||
local current_fps
|
||||
current_fps="$(awk -F= '/^fps=/{print $2; exit}' <<<"${current_profile}")"
|
||||
if [[ -n "${current_fps}" ]]; then
|
||||
printf '%s\n' "${current_fps}"
|
||||
return 0
|
||||
fi
|
||||
if ! command -v v4l2-ctl >/dev/null 2>&1; then
|
||||
printf '20\n'
|
||||
return 0
|
||||
fi
|
||||
local listing
|
||||
listing="$(v4l2-ctl -d "${resolved_video_device}" --list-formats-ext 2>/dev/null || true)"
|
||||
local first_fps
|
||||
first_fps="$(grep -m1 -o '[0-9]\+\.[0-9]\+ fps' <<<"${listing}" | awk '{sub(/\..*/, "", $1); print $1}' || true)"
|
||||
if [[ -n "${first_fps}" ]]; then
|
||||
printf '%s\n' "${first_fps}"
|
||||
return 0
|
||||
fi
|
||||
printf '20\n'
|
||||
}
|
||||
|
||||
resolve_pw_audio_target() {
|
||||
@ -327,15 +441,31 @@ case "${remote_capture_stack}" in
|
||||
;;
|
||||
esac
|
||||
|
||||
resolved_video_device="$(resolve_video_device "${remote_video_device}")"
|
||||
resolved_video_size="$(resolve_video_size "${video_size}")"
|
||||
printf 'using video mode: %s (%s)\n' "${resolved_video_size}" "${video_format:-driver-default}" >&2
|
||||
video_args=(-f video4linux2 -framerate "${video_fps}" -video_size "${resolved_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
|
||||
video_args=(-f video4linux2 -framerate "${resolved_video_fps}" -video_size "${resolved_video_size}")
|
||||
if [[ -n "${video_format}" ]]; then
|
||||
video_args+=(-input_format "${video_format}")
|
||||
fi
|
||||
gst_source_caps="$(gst_video_source_caps)"
|
||||
gst_decode_chain="$(gst_video_decode_chain)"
|
||||
|
||||
run_ffmpeg_capture() {
|
||||
local rc=0
|
||||
timeout --signal=INT "$((capture_seconds + 5))" "$@" || rc=$?
|
||||
case "${rc}" in
|
||||
0|124|130)
|
||||
return 0
|
||||
;;
|
||||
*)
|
||||
return "${rc}"
|
||||
;;
|
||||
esac
|
||||
}
|
||||
|
||||
quiesce_for_alsa=0
|
||||
case "${remote_audio_quiesce_user_audio}" in
|
||||
1|true|yes)
|
||||
@ -372,7 +502,7 @@ if [[ "${capture_mode}" == "pwpipe" ]]; then
|
||||
| pw-v4l2 ffmpeg -hide_banner -loglevel error -y \
|
||||
-thread_queue_size 1024 \
|
||||
"${video_args[@]}" \
|
||||
-i /dev/video0 \
|
||||
-i "${resolved_video_device}" \
|
||||
-thread_queue_size 1024 \
|
||||
-f s16le -ar 48000 -ac 2 \
|
||||
-i pipe:0 \
|
||||
@ -386,10 +516,10 @@ elif [[ "${capture_mode}" == "pulse" ]]; then
|
||||
ffmpeg)
|
||||
case "${remote_pulse_video_mode}" in
|
||||
copy)
|
||||
ffmpeg -hide_banner -loglevel error -y \
|
||||
run_ffmpeg_capture ffmpeg -hide_banner -loglevel error -y \
|
||||
-thread_queue_size 1024 \
|
||||
"${video_args[@]}" \
|
||||
-i /dev/video0 \
|
||||
-i "${resolved_video_device}" \
|
||||
-thread_queue_size 1024 \
|
||||
-f pulse \
|
||||
-i "${pulse_source}" \
|
||||
@ -399,15 +529,15 @@ elif [[ "${capture_mode}" == "pulse" ]]; then
|
||||
"${remote_capture}"
|
||||
;;
|
||||
cfr)
|
||||
ffmpeg -hide_banner -loglevel error -y \
|
||||
run_ffmpeg_capture ffmpeg -hide_banner -loglevel error -y \
|
||||
-thread_queue_size 1024 \
|
||||
"${video_args[@]}" \
|
||||
-i /dev/video0 \
|
||||
-i "${resolved_video_device}" \
|
||||
-thread_queue_size 1024 \
|
||||
-f pulse \
|
||||
-i "${pulse_source}" \
|
||||
-t "${capture_seconds}" \
|
||||
-vf "fps=${video_fps}" \
|
||||
-vf "fps=${resolved_video_fps}" \
|
||||
-c:v libx264 -preset ultrafast -crf 12 -g 1 -pix_fmt yuv420p \
|
||||
-c:a pcm_s16le \
|
||||
"${remote_capture}"
|
||||
@ -428,7 +558,7 @@ elif [[ "${capture_mode}" == "pulse" ]]; then
|
||||
timeout --signal=INT "$((capture_seconds + 3))" \
|
||||
gst-launch-1.0 -q -e \
|
||||
matroskamux name=mux ! filesink location="${remote_capture}" \
|
||||
v4l2src device=/dev/video0 do-timestamp=true ! \
|
||||
v4l2src device="${resolved_video_device}" do-timestamp=true ! \
|
||||
${gst_source_caps} ! \
|
||||
queue ! mux. \
|
||||
pulsesrc device="${pulse_source}" do-timestamp=true ! \
|
||||
@ -439,7 +569,7 @@ elif [[ "${capture_mode}" == "pulse" ]]; then
|
||||
timeout --signal=INT "$((capture_seconds + 3))" \
|
||||
gst-launch-1.0 -q -e \
|
||||
matroskamux name=mux ! filesink location="${remote_capture}" \
|
||||
v4l2src device=/dev/video0 do-timestamp=true ! \
|
||||
v4l2src device="${resolved_video_device}" do-timestamp=true ! \
|
||||
${gst_source_caps} ! \
|
||||
${gst_decode_chain} \
|
||||
videoconvert ! videorate ! video/x-raw,framerate="${video_fps}"/1 ! \
|
||||
@ -462,10 +592,10 @@ elif [[ "${capture_mode}" == "pulse" ]]; then
|
||||
;;
|
||||
esac
|
||||
else
|
||||
ffmpeg -hide_banner -loglevel error -y \
|
||||
run_ffmpeg_capture ffmpeg -hide_banner -loglevel error -y \
|
||||
-thread_queue_size 1024 \
|
||||
"${video_args[@]}" \
|
||||
-i /dev/video0 \
|
||||
-i "${resolved_video_device}" \
|
||||
-thread_queue_size 1024 \
|
||||
-f alsa -ac 2 -ar 48000 \
|
||||
-i "${alsa_audio_dev}" \
|
||||
|
||||
@ -10,7 +10,7 @@ bench = false
|
||||
|
||||
[package]
|
||||
name = "lesavka_server"
|
||||
version = "0.14.30"
|
||||
version = "0.14.31"
|
||||
edition = "2024"
|
||||
autobins = false
|
||||
|
||||
|
||||
@ -46,7 +46,7 @@ fn server_install_pins_hdmi_camera_and_display_defaults() {
|
||||
assert!(SERVER_INSTALL.contains("${LESAVKA_UPSTREAM_PLAYOUT_DELAY_MS:-1000}"));
|
||||
assert!(SERVER_INSTALL.contains("${LESAVKA_UPSTREAM_PAIR_SLACK_US:-20000}"));
|
||||
assert!(SERVER_INSTALL.contains("${LESAVKA_UPSTREAM_STALE_DROP_MS:-80}"));
|
||||
assert!(SERVER_INSTALL.contains("${LESAVKA_UVC_MAXPACKET:-256}"));
|
||||
assert!(SERVER_INSTALL.contains("${LESAVKA_UVC_MAXPACKET:-1024}"));
|
||||
assert!(SERVER_INSTALL.contains("${LESAVKA_UVC_INTERVAL:-500000}"));
|
||||
assert!(SERVER_INSTALL.contains("${LESAVKA_UVC_WIDTH:-640}"));
|
||||
assert!(SERVER_INSTALL.contains("${LESAVKA_UVC_HEIGHT:-480}"));
|
||||
|
||||
Loading…
x
Reference in New Issue
Block a user