#!/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.sock Control-Socket # /tmp/vizion_cam.pid PID-Datei # /tmp/vizion_cam.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.sock PID_PREFIX="/tmp/vizion_cam" # /tmp/vizion_cam.pid LOG_PREFIX="/tmp/vizion_cam" # /tmp/vizion_cam.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 [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.mp4) --display Lokale Anzeige (autovideosink) --pipeline 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 - JSON-Command an eine Instanz senden send_cmd() { echo "$2" | socat - "UNIX-CONNECT:$1" 2>/dev/null } # wait_for_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 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 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://:$((MJPEG_PORT + i))" done echo "Im Browser oder mit: ffplay http://:$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.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