add helper scripts
This commit is contained in:
1 parent
1853360506
commit
bebdeab0d5
2 files changed
+487
No files matched your search
@@ -103,6 +103,40 @@ Zeigt:
|
||||
|
||||
Zeigt alle verfügbaren Kamera-Auflösungen und Formate.
|
||||
|
||||
## Multi-Camera: alle Kameras auf einmal
|
||||
|
||||
`start_all_cameras.sh` erkennt alle angeschlossenen, von VIZION unterstützten
|
||||
Kameras (via `vizion-ctl -l`, Fallback: `lsusb`/Cypress 04b4) und startet für
|
||||
jede der Reihe nach eine `vizionStreamer`-Instanz mit eigenem Socket
|
||||
(`/tmp/vizion_cam<idx>.sock`) und startet den Stream.
|
||||
|
||||
```bash
|
||||
# Erkannte Kameras auflisten
|
||||
./scripts/start_all_cameras.sh --list
|
||||
|
||||
# Alle Kameras per UDP streamen (Port 5000 + Kamera-Index)
|
||||
./scripts/start_all_cameras.sh --udp 192.168.1.100 5000
|
||||
|
||||
# Alle Kameras als MJPEG-HTTP-Stream (Port 8080 + Kamera-Index)
|
||||
./scripts/start_all_cameras.sh --mjpeg 8080
|
||||
|
||||
# Alle Kameras in Dateien aufnehmen (/tmp/cam<idx>.mp4)
|
||||
./scripts/start_all_cameras.sh --file /home/user/recordings
|
||||
|
||||
# Lokale Anzeige
|
||||
./scripts/start_all_cameras.sh --display
|
||||
|
||||
# Eigene Pipeline ({port} wird durch 5000+Kamera-Index ersetzt)
|
||||
./scripts/start_all_cameras.sh --pipeline "videoconvert ! x264enc tune=zerolatency ! rtph264pay ! udpsink host=192.168.1.100 port={port}"
|
||||
|
||||
# Alle gestarteten Instanzen stoppen
|
||||
./scripts/start_all_cameras.sh --stop
|
||||
```
|
||||
|
||||
Ohne Modus-Option wird standardmäßig per UDP nach `127.0.0.1` gestreamt.
|
||||
Das Script läuft im Vordergrund und stoppt mit `Ctrl+C` alle Instanzen
|
||||
sauber (Stream stoppen + Prozess beenden). Logs: `/tmp/vizion_cam<idx>.log`.
|
||||
|
||||
## Typischer Workflow
|
||||
|
||||
```bash
|
||||
|
||||
Executable
+453
@@ -0,0 +1,453 @@
|
||||
#!/bin/bash
|
||||
#
|
||||
# start_all_cameras.sh
|
||||
#
|
||||
# Sucht alle angeschlossenen, von VIZION unterstützten Kameras und startet
|
||||
# für jede (der Reihe nach) eine vizionStreamer-Instanz mit eigenem
|
||||
# Control-Socket und startet den Stream.
|
||||
#
|
||||
# Erkennung:
|
||||
# 1. vizion-ctl -l (VizionSDK, /opt/vizionsdk/bin/vizion-ctl) - offizielle
|
||||
# Kameradetection (USB + MIPI)
|
||||
# 2. Fallback: lsusb (Cypress USB-Geräte, Vendor 04b4)
|
||||
#
|
||||
# Pro Kamera werden angelegt:
|
||||
# /tmp/vizion_cam<idx>.sock Control-Socket
|
||||
# /tmp/vizion_cam<idx>.pid PID-Datei
|
||||
# /tmp/vizion_cam<idx>.log Log der Instanz
|
||||
#
|
||||
# Copyright (c) 2025 Maik Jurischka
|
||||
# Lizenziert unter CC BY-NC-SA 4.0 - https://creativecommons.org/licenses/by-nc-sa/4.0/
|
||||
|
||||
set -u
|
||||
|
||||
SCRIPT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)"
|
||||
ROOT_DIR="$(dirname "$SCRIPT_DIR")"
|
||||
BINARY="$ROOT_DIR/build/vizionStreamer"
|
||||
|
||||
# VizionSDK-Tool (offizielle Kameradetection)
|
||||
VIZION_CTL="$(command -v vizion-ctl 2>/dev/null || true)"
|
||||
[ -n "$VIZION_CTL" ] || VIZION_CTL="/opt/vizionsdk/bin/vizion-ctl"
|
||||
|
||||
SOCKET_PREFIX="/tmp/vizion_cam" # /tmp/vizion_cam<idx>.sock
|
||||
PID_PREFIX="/tmp/vizion_cam" # /tmp/vizion_cam<idx>.pid
|
||||
LOG_PREFIX="/tmp/vizion_cam" # /tmp/vizion_cam<idx>.log
|
||||
WAIT_TIMEOUT=15 # Sekunden, die auf den Socket gewartet wird
|
||||
|
||||
# ---------------------------------------------------------------------------
|
||||
# Ausgabe-Helfer
|
||||
# ---------------------------------------------------------------------------
|
||||
log() { echo "[$(date '+%Y-%m-%d %H:%M:%S')] $*"; }
|
||||
err() { echo "[$(date '+%Y-%m-%d %H:%M:%S')] ERROR: $*" >&2; }
|
||||
|
||||
usage() {
|
||||
cat <<'EOF'
|
||||
Nutzung: start_all_cameras.sh [Optionen]
|
||||
|
||||
Sucht alle angeschlossenen VIZION-Kameras und startet für jede (der Reihe
|
||||
nach) einen Stream.
|
||||
|
||||
Optionen:
|
||||
--list Nur erkannte Kameras auflisten
|
||||
--udp <host> [port] UDP H.264-Stream (Standard: 127.0.0.1, Port 5000+Kamera-Index)
|
||||
--mjpeg [port] MJPEG HTTP-Stream (Standard: Port 8080+Kamera-Index)
|
||||
--file [dir] Aufnahme in Datei (Standard: /tmp/cam<idx>.mp4)
|
||||
--display Lokale Anzeige (autovideosink)
|
||||
--pipeline <pipe> Eigene GStreamer-Pipeline (für {port} wird 5000+Kamera-Index eingesetzt)
|
||||
--stop Alle von diesem Script gestarteten Instanzen stoppen
|
||||
-h, --help Diese Hilfe anzeigen
|
||||
|
||||
Beispiele:
|
||||
./scripts/start_all_cameras.sh --list
|
||||
./scripts/start_all_cameras.sh --udp 192.168.1.100 5000
|
||||
./scripts/start_all_cameras.sh --mjpeg 8080
|
||||
./scripts/start_all_cameras.sh --file /home/user/recordings
|
||||
./scripts/start_all_cameras.sh --pipeline "videoconvert ! x264enc tune=zerolatency ! rtph264pay ! udpsink host=192.168.1.100 port={port}"
|
||||
|
||||
Empfangen (UDP, auf dem Zielrechner):
|
||||
gst-launch-1.0 udpsrc port=5000 ! application/x-rtp,encoding-name=H264 ! rtph264depay ! h264parse ! avdec_h264 ! videoconvert ! autovideosink
|
||||
EOF
|
||||
}
|
||||
|
||||
# ---------------------------------------------------------------------------
|
||||
# Socket-Helfer
|
||||
# ---------------------------------------------------------------------------
|
||||
|
||||
# send_cmd <socket> <json> - JSON-Command an eine Instanz senden
|
||||
send_cmd() {
|
||||
echo "$2" | socat - "UNIX-CONNECT:$1" 2>/dev/null
|
||||
}
|
||||
|
||||
# wait_for_socket <socket> - bis der Socket existiert warten
|
||||
wait_for_socket() {
|
||||
local sock="$1"
|
||||
local i
|
||||
for ((i = 0; i < WAIT_TIMEOUT * 2; i++)); do
|
||||
[ -S "$sock" ] && return 0
|
||||
sleep 0.5
|
||||
done
|
||||
return 1
|
||||
}
|
||||
|
||||
# json_escape <string> - String für JSON-Strings escapen
|
||||
json_escape() {
|
||||
local s="$1"
|
||||
s=${s//\\/\\\\}
|
||||
s=${s//\"/\\\"}
|
||||
printf '%s' "$s"
|
||||
}
|
||||
|
||||
# ---------------------------------------------------------------------------
|
||||
# Kamera-Erkennung
|
||||
# ---------------------------------------------------------------------------
|
||||
CAMERAS=()
|
||||
DETECT_OUTPUT=""
|
||||
|
||||
detect_cameras() {
|
||||
if [ -x "$VIZION_CTL" ]; then
|
||||
DETECT_OUTPUT="$("$VIZION_CTL" -l 2>&1)"
|
||||
|
||||
# Indizes aus Zeilen wie "[0] VCI-AR0234 ..." extrahieren
|
||||
local idxs
|
||||
idxs="$(echo "$DETECT_OUTPUT" | grep -oE '^[[:space:]]*\[[0-9]+\]' | tr -d '[]' | sort -n -u)"
|
||||
if [ -n "$idxs" ]; then
|
||||
while IFS= read -r i; do
|
||||
CAMERAS+=("$i")
|
||||
done <<< "$idxs"
|
||||
else
|
||||
# Fallback: Anzahl aus "Found N camera(s)" o.ä. parsen
|
||||
local n
|
||||
n="$(echo "$DETECT_OUTPUT" | grep -oiE '[0-9]+[[:space:]]*camera' | grep -oE '^[0-9]+' | head -1)"
|
||||
if [ -n "$n" ]; then
|
||||
local i
|
||||
for ((i = 0; i < n; i++)); do
|
||||
CAMERAS+=("$i")
|
||||
done
|
||||
fi
|
||||
fi
|
||||
else
|
||||
log "vizion-ctl nicht gefunden ($VIZION_CTL) - verwende lsusb als Fallback"
|
||||
fi
|
||||
|
||||
# Fallback: Cypress USB-Geräte (Vendor 04b4) zählen
|
||||
if [ ${#CAMERAS[@]} -eq 0 ] && command -v lsusb >/dev/null 2>&1; then
|
||||
local nusb
|
||||
nusb="$(lsusb 2>/dev/null | grep -cE 'ID 04b4:')"
|
||||
if [ "$nusb" -gt 0 ]; then
|
||||
log "lsusb hat $nusb Cypress-USB-Gerät(e) (Vendor 04b4) gefunden"
|
||||
local i
|
||||
for ((i = 0; i < nusb; i++)); do
|
||||
CAMERAS+=("$i")
|
||||
done
|
||||
fi
|
||||
fi
|
||||
}
|
||||
|
||||
# ---------------------------------------------------------------------------
|
||||
# Pipeline je nach Modus (Parameter: Kamera-Index)
|
||||
# ---------------------------------------------------------------------------
|
||||
make_pipeline() {
|
||||
local i="$1"
|
||||
case "$MODE" in
|
||||
udp)
|
||||
echo "videoconvert ! x264enc tune=zerolatency bitrate=2000 ! rtph264pay ! udpsink host=$UDP_HOST port=$((UDP_PORT + i))"
|
||||
;;
|
||||
mjpeg)
|
||||
echo "videoconvert ! jpegenc ! multipartmux ! tcpserversink host=0.0.0.0 port=$((MJPEG_PORT + i))"
|
||||
;;
|
||||
file)
|
||||
echo "videoconvert ! x264enc ! mp4mux ! filesink location=$FILE_DIR/cam${i}.mp4"
|
||||
;;
|
||||
display)
|
||||
echo "videoconvert ! autovideosink"
|
||||
;;
|
||||
custom)
|
||||
local p="$CUSTOM_PIPELINE"
|
||||
if [[ "$p" == *"{port}"* ]]; then
|
||||
p="${p//\{port\}/$((5000 + i))}"
|
||||
fi
|
||||
echo "$p"
|
||||
;;
|
||||
*)
|
||||
echo ""
|
||||
;;
|
||||
esac
|
||||
}
|
||||
|
||||
# ---------------------------------------------------------------------------
|
||||
# Instanz starten (Parameter: Kamera-Index)
|
||||
# ---------------------------------------------------------------------------
|
||||
PIDS=()
|
||||
|
||||
start_camera() {
|
||||
local i="$1"
|
||||
local sock="$SOCKET_PREFIX${i}.sock"
|
||||
local pidfile="$PID_PREFIX${i}.pid"
|
||||
local logf="$LOG_PREFIX${i}.log"
|
||||
local pipeline resp pid
|
||||
|
||||
# Läuft für diese Kamera bereits eine Instanz?
|
||||
if [ -f "$pidfile" ] && kill -0 "$(cat "$pidfile" 2>/dev/null)" 2>/dev/null; then
|
||||
log "Kamera $i: läuft bereits (PID $(cat "$pidfile")) - wird übersprungen"
|
||||
return 0
|
||||
fi
|
||||
|
||||
pipeline="$(make_pipeline "$i")"
|
||||
if [ -z "$pipeline" ]; then
|
||||
err "Kamera $i: keine Pipeline konfiguriert"
|
||||
return 1
|
||||
fi
|
||||
|
||||
# Eventuelle alte Socket-Datei entfernen (Instanz ist laut PID-Check nicht aktiv)
|
||||
rm -f "$sock"
|
||||
|
||||
log "Kamera $i: starte vizionStreamer (Socket: $sock)"
|
||||
"$BINARY" --camera "$i" --socket "$sock" >> "$logf" 2>&1 &
|
||||
pid=$!
|
||||
echo "$pid" > "$pidfile"
|
||||
PIDS+=("$pid")
|
||||
|
||||
if ! wait_for_socket "$sock"; then
|
||||
err "Kamera $i: Socket ist nicht aufgetaucht (Kamera nicht verfügbar? Log: $logf)"
|
||||
wait "$pid" 2>/dev/null
|
||||
rm -f "$pidfile"
|
||||
return 1
|
||||
fi
|
||||
|
||||
# Pipeline setzen
|
||||
resp="$(send_cmd "$sock" "{\"command\":\"set_pipeline\",\"params\":{\"pipeline\":\"$(json_escape "$pipeline")\"}}")"
|
||||
if ! echo "$resp" | grep -q '"status"[[:space:]]*:[[:space:]]*"success"'; then
|
||||
err "Kamera $i: set_pipeline fehlgeschlagen: $resp"
|
||||
return 1
|
||||
fi
|
||||
|
||||
# Stream starten
|
||||
send_cmd "$sock" '{"command":"start_stream"}' >/dev/null 2>&1
|
||||
sleep 1
|
||||
|
||||
# Status prüfen
|
||||
resp="$(send_cmd "$sock" '{"command":"get_status"}')"
|
||||
if echo "$resp" | grep -q '"streaming"[[:space:]]*:[[:space:]]*true'; then
|
||||
log "Kamera $i: [OK] Stream läuft | $pipeline"
|
||||
return 0
|
||||
else
|
||||
err "Kamera $i: Stream konnte nicht gestartet werden: $resp"
|
||||
return 1
|
||||
fi
|
||||
}
|
||||
|
||||
# ---------------------------------------------------------------------------
|
||||
# Alle Instanzen stoppen
|
||||
# ---------------------------------------------------------------------------
|
||||
stop_all() {
|
||||
local found=0
|
||||
local pidfile idx sock pid
|
||||
for pidfile in "$PID_PREFIX"*.pid; do
|
||||
[ -e "$pidfile" ] || continue
|
||||
idx="${pidfile#"$PID_PREFIX"}"
|
||||
idx="${idx%.pid}"
|
||||
sock="$SOCKET_PREFIX$idx.sock"
|
||||
pid="$(cat "$pidfile" 2>/dev/null || true)"
|
||||
|
||||
# Zuerst den Stream stoppen (best effort)
|
||||
if [ -S "$sock" ]; then
|
||||
send_cmd "$sock" '{"command":"stop_stream"}' >/dev/null 2>&1
|
||||
fi
|
||||
if [ -n "$pid" ] && kill -0 "$pid" 2>/dev/null; then
|
||||
kill "$pid" 2>/dev/null
|
||||
log "Kamera $idx: gestoppt (PID $pid)"
|
||||
fi
|
||||
rm -f "$pidfile"
|
||||
found=1
|
||||
done
|
||||
[ "$found" -eq 1 ] || log "Keine laufenden Instanzen gefunden."
|
||||
}
|
||||
|
||||
cleanup() {
|
||||
echo
|
||||
log "Stoppe alle Streams..."
|
||||
stop_all
|
||||
log "Fertig."
|
||||
exit 0
|
||||
}
|
||||
|
||||
# ---------------------------------------------------------------------------
|
||||
# Argumente parsen
|
||||
# ---------------------------------------------------------------------------
|
||||
MODE=""
|
||||
UDP_HOST="127.0.0.1"
|
||||
UDP_PORT=5000
|
||||
MJPEG_PORT=8080
|
||||
FILE_DIR="/tmp"
|
||||
CUSTOM_PIPELINE=""
|
||||
LIST_ONLY=0
|
||||
STOP_ONLY=0
|
||||
|
||||
while [ $# -gt 0 ]; do
|
||||
case "$1" in
|
||||
--list)
|
||||
LIST_ONLY=1
|
||||
;;
|
||||
--stop)
|
||||
STOP_ONLY=1
|
||||
;;
|
||||
--udp)
|
||||
MODE="udp"
|
||||
if [ $# -ge 2 ] && [[ "$2" != --* ]]; then UDP_HOST="$2"; shift; fi
|
||||
if [ $# -ge 2 ] && [[ "$2" =~ ^[0-9]+$ ]]; then UDP_PORT="$2"; shift; fi
|
||||
;;
|
||||
--mjpeg)
|
||||
MODE="mjpeg"
|
||||
if [ $# -ge 2 ] && [[ "$2" =~ ^[0-9]+$ ]]; then MJPEG_PORT="$2"; shift; fi
|
||||
;;
|
||||
--file)
|
||||
MODE="file"
|
||||
if [ $# -ge 2 ] && [[ "$2" != --* ]]; then FILE_DIR="$2"; shift; fi
|
||||
;;
|
||||
--display)
|
||||
MODE="display"
|
||||
;;
|
||||
--pipeline)
|
||||
MODE="custom"
|
||||
if [ $# -lt 2 ]; then
|
||||
err "--pipeline benötigt ein Argument"
|
||||
exit 1
|
||||
fi
|
||||
CUSTOM_PIPELINE="$2"
|
||||
shift
|
||||
;;
|
||||
-h|--help)
|
||||
usage
|
||||
exit 0
|
||||
;;
|
||||
*)
|
||||
err "Unbekannte Option: $1"
|
||||
usage
|
||||
exit 1
|
||||
;;
|
||||
esac
|
||||
shift
|
||||
done
|
||||
|
||||
# ---------------------------------------------------------------------------
|
||||
# Hauptablauf
|
||||
# ---------------------------------------------------------------------------
|
||||
|
||||
# Nur stoppen?
|
||||
if [ $STOP_ONLY -eq 1 ]; then
|
||||
stop_all
|
||||
exit 0
|
||||
fi
|
||||
|
||||
# Nur auflisten?
|
||||
if [ $LIST_ONLY -eq 1 ]; then
|
||||
detect_cameras
|
||||
if [ ${#CAMERAS[@]} -eq 0 ]; then
|
||||
echo "Keine VIZION-Kameras gefunden."
|
||||
exit 1
|
||||
fi
|
||||
echo "Gefunden: ${#CAMERAS[@]} VIZION-Kamera(n)"
|
||||
if [ -n "$DETECT_OUTPUT" ]; then
|
||||
echo "$DETECT_OUTPUT"
|
||||
else
|
||||
for i in "${CAMERAS[@]}"; do
|
||||
echo " [$i] (Cypress USB-Gerät)"
|
||||
done
|
||||
fi
|
||||
exit 0
|
||||
fi
|
||||
|
||||
# Standardmodus
|
||||
if [ -z "$MODE" ]; then
|
||||
MODE="udp"
|
||||
log "Kein Modus angegeben - Standard: UDP nach 127.0.0.1 (mit --udp <host> an einen Remote-Rechner streamen)"
|
||||
fi
|
||||
|
||||
# Abhängigkeiten prüfen
|
||||
if [ ! -x "$BINARY" ]; then
|
||||
err "vizionStreamer nicht gefunden: $BINARY"
|
||||
err "Erst bauen: cmake -B build -S . && cmake --build build"
|
||||
exit 1
|
||||
fi
|
||||
if ! command -v socat >/dev/null 2>&1; then
|
||||
err "socat nicht gefunden (installieren mit: sudo apt install socat)"
|
||||
exit 1
|
||||
fi
|
||||
if [ "$MODE" = "file" ]; then
|
||||
mkdir -p "$FILE_DIR" || { err "Kann Verzeichnis $FILE_DIR nicht anlegen"; exit 1; }
|
||||
fi
|
||||
|
||||
# Kameras erkennen
|
||||
detect_cameras
|
||||
if [ ${#CAMERAS[@]} -eq 0 ]; then
|
||||
err "Keine VIZION-Kameras gefunden."
|
||||
err "Verbindung prüfen (dmesg) und ggf. /opt/vizionsdk/init_driver.sh ausführen."
|
||||
exit 1
|
||||
fi
|
||||
|
||||
log "Gefunden: ${#CAMERAS[@]} VIZION-Kamera(n): ${CAMERAS[*]}"
|
||||
|
||||
# Ctrl+C -> alles sauber stoppen
|
||||
trap cleanup INT TERM
|
||||
|
||||
# Der Reihe nach: für jede Kamera Instanz starten + Stream
|
||||
OK_COUNT=0
|
||||
FAIL_COUNT=0
|
||||
for i in "${CAMERAS[@]}"; do
|
||||
if start_camera "$i"; then
|
||||
OK_COUNT=$((OK_COUNT + 1))
|
||||
else
|
||||
FAIL_COUNT=$((FAIL_COUNT + 1))
|
||||
fi
|
||||
done
|
||||
|
||||
# Zusammenfassung
|
||||
echo
|
||||
echo "========================================"
|
||||
echo "Streams gestartet: $OK_COUNT / ${#CAMERAS[@]}"
|
||||
case "$MODE" in
|
||||
udp)
|
||||
for i in "${CAMERAS[@]}"; do
|
||||
echo " Kamera $i -> udp://$UDP_HOST:$((UDP_PORT + i))"
|
||||
done
|
||||
echo "Empfangen (Zielrechner, Kamera 0):"
|
||||
echo " gst-launch-1.0 udpsrc port=$UDP_PORT ! application/x-rtp,encoding-name=H264 ! rtph264depay ! h264parse ! avdec_h264 ! videoconvert ! autovideosink"
|
||||
;;
|
||||
mjpeg)
|
||||
for i in "${CAMERAS[@]}"; do
|
||||
echo " Kamera $i -> http://<board-ip>:$((MJPEG_PORT + i))"
|
||||
done
|
||||
echo "Im Browser oder mit: ffplay http://<board-ip>:$MJPEG_PORT"
|
||||
;;
|
||||
file)
|
||||
for i in "${CAMERAS[@]}"; do
|
||||
echo " Kamera $i -> $FILE_DIR/cam${i}.mp4"
|
||||
done
|
||||
;;
|
||||
display)
|
||||
echo " Fenster werden lokal angezeigt"
|
||||
;;
|
||||
custom)
|
||||
echo " Pipeline: $CUSTOM_PIPELINE"
|
||||
;;
|
||||
esac
|
||||
if [ $FAIL_COUNT -gt 0 ]; then
|
||||
echo " ($FAIL_COUNT Kamera(n) fehlgeschlagen - Logs unter /tmp/vizion_cam<idx>.log)"
|
||||
fi
|
||||
echo "========================================"
|
||||
echo "Drücke Ctrl+C, um alle Streams zu stoppen."
|
||||
|
||||
# Überwachung: melden, wenn eine Instanz stirbt
|
||||
while true; do
|
||||
sleep 5
|
||||
alive=0
|
||||
for pid in ${PIDS[@]+"${PIDS[@]}"}; do
|
||||
if kill -0 "$pid" 2>/dev/null; then
|
||||
alive=$((alive + 1))
|
||||
fi
|
||||
done
|
||||
if [ "$alive" -eq 0 ]; then
|
||||
log "Alle Instanzen haben sich beendet."
|
||||
break
|
||||
fi
|
||||
done
|
||||
Reference in new issue
Block a user