# AppRobotWebcam – Roadmap ## Ziel Fokussierter Webcam-Service als Docker-Container. Zwei Verantwortlichkeiten: 1. **Live-Video** mit minimaler Latenz (MJPEG via Browser ``) 2. **HD-Standbilder** auf Abruf via HTTP REST (`/api/snapshot/:id/hires`) Das Homing-Projekt und andere Container holen Standbilder über den HTTP-Endpunkt — keine weitere Kopplung. --- ## Architektur (aktuell) ``` cameras.json → server.js → CameraSwitch (eine Instanz pro /dev/videoN) │ ┌──────────────┴──────────────┐ │ Live (On-Demand) │ HD-Grab │ ffmpeg MJPEG passthrough │ Live stoppen → hires → zurück │ → multipart/x-mixed-replace │ close-Event = FD frei (kein Race) ▼ ▼ Browser JPEG via HTTP Node.js / Express :8444 ├── GET / Viewer (index.html + viewer.js) ├── GET /api/cameras Metadaten aller Kameras (aus cameras.json) ├── GET /api/snapshot Liste der Kameras mit Metadaten ├── GET /api/snapshot/:id 640er JPEG (aus Live-Puffer, on-demand) ├── GET /api/snapshot/:id/hires HD-JPEG (grabHires, 2–3 s) ├── GET /api/stream/:id MJPEG multipart/x-mixed-replace (Live) └── GET /health Zustand aller CameraSwitch-Instanzen ``` **Warum kein go2rtc mehr:** go2rtc konnte nicht zuverlässig melden, wann FFmpeg das Gerät freigibt → Race: zwei Encoder auf `/dev/videoN` → 106 % CPU + Hang. Mit eigenem FFmpeg-Start ist das `close`-Event des Kindprozesses der harte Beweis „Gerät frei". → Details: `doc/09_Bug_reports.md`, Entwicklungsgeschichte: `doc/05_screenShot_roadmap.md`. **Stack:** | Komponente | Wahl | Begründung | |---|---|---| | Streaming | Node-eigener FFmpeg → MJPEG | kein Race, geringer Overhead | | Webserver | Node.js + Express | wartbar, user-präferiert | | Live-Protokoll | MJPEG multipart (``) | native Browser-Unterstützung, ~139 ms | | HD-Grab | MJPEG copybsf (Kamera-JPEG pur) | kein Re-Encode, keine zweite Kompression | | Container | docker-compose, inline Dockerfile | Portainer-tauglich, kein externes Image | --- ## Gemessene Werte (Hardware, 2026-06-05/06) | Kenngrösse | Wert | |---|---| | Live-Latenz Kamera→Browser | **139 ms** | | CPU idle (niemand schaut) | **~5 %** (On-Demand) | | CPU aktiv | **~35 %/Kamera** (copybsf) | | HD-Grab Dauer | **~2–3 s** (settleFrames + Format-Switch) | | HD-Auflösung C270 | 1280×960 JPEG | | HD-Auflösung C920 | 1920×1080 JPEG | --- ## Kamera-Konfiguration (`cameras.json`) Einzige Konfigurationsquelle — kein Hardcode im Code, kein Redeploy für neue Kameras. ```json { "cameras": [ { "id": "cam0", "device": "/dev/video0", "name": "Kamera 0", "position": "front", "stream": true, "hires": true, "hiresSize": "1280x960", "note": "usb-046d_0825_3BB3FE20-video-index0" }, { "id": "cam1", "device": "/dev/video2", "name": "Kamera 1", "position": "left", "stream": true, "hires": true, "hiresSize": "1280x960", "note": "usb-046d_081b_342D4F40-video-index0" }, { "id": "cam2", "device": "/dev/video4", "name": "Kamera 2", "position": "right", "stream": true, "hires": true, "hiresSize": "1920x1080", "note": "usb-046d_HD_Pro_Webcam_C920_9C5591DF-video-index0" } ] } ``` Per-Kamera-Felder: `liveSize`, `liveFps`, `hiresSize`, `hiresFps`, `encode`, `hiresEncode` (überschreibt globale Env-Defaults). Details: `doc/07_multipleCam_roadmap.md`. Geräte in `docker-compose.yaml` über stabile `by-id`-Pfade einbinden: ```yaml devices: - /dev/v4l/by-id/usb-...-video-index0:/dev/videoN ``` --- ## Datei-Struktur ``` appRobotWebcam/ ├── cameras.json Kamera-Konfiguration (Geräte, Namen, Auflösungen) ├── server.js Einstiegspunkt; lädt cameras.json, erzeugt CameraSwitch ├── src/ │ ├── cameraSwitch.js CameraSwitch-Klasse (ein FFmpeg pro Gerät, Live + Grab) │ └── snapshotService.js Express-Router für /api/snapshot, /api/stream, /api/cameras ├── public/ │ ├── index.html Viewer-HTML + CSS │ └── viewer.js Kamera-Boxen, Live-Start/Stop, HD-Grab, Snapshot-alle ├── tools/ │ └── hires-probe.js Diagnose-Skript: Hires-Grab direkt auf dem Host testen ├── docker-compose.yaml Einzige Deploy-Datei (inline Dockerfile, by-id devices) ├── package.json └── doc/ ├── 01_WebcamRoadmap.md (diese Datei) ├── 04_Delay_roadmap.md Latenz-Geschichte + Messwerte ├── 05_screenShot_roadmap.md HD-Grab Architektur + Encode-Qualität ├── 06_portForwarding_roadmap.md Port-Forwarding / Internet-Zugang ├── 07_multipleCam_roadmap.md Multi-Kamera-Konfiguration, cameras.json-Referenz └── 09_Bug_reports.md Bug-Dokumentation (go2rtc-Race, Warmup-Irrweg, …) ``` --- ## Entwicklungsgeschichte (Phasen) ### Phase 1 — Grundgerüst ✅ Projektstruktur, Node + Express, erster eigener FFmpeg-MJPEG-Stream über WebSocket. Verworfen wegen Latenz. ### Phase 2 — go2rtc / WebRTC ✅ (abgelöst) go2rtc als Streaming-Backend (Kamera-Capture, H.264/WebRTC). WebRTC ~130 ms, MJPEG ~200 ms. Stabiler Snapshot-Endpunkt `/api/snapshot/cam{n}` via Node-Proxy. ### Phase 3 — go2rtc-Race-Bug → Node-MJPEG-Schalter ✅ (2026-06-05) go2rtc konnte den Gerätezustand beim HD-Grab nicht zuverlässig signalisieren → 106 %-CPU- Race. go2rtc entfernt. Node besitzt die Kameras direkt; `close`-Event = FD frei. Latenz: **139 ms** (besser als go2rtc). CPU idle: **0 %** (On-Demand). ### Phase 4 — Multi-Kamera + cameras.json ✅ (2026-06-06) `cameras.json` als Konfigurationsquelle. Dritte Kamera (C920, 1920×1080) in Betrieb. `hiresEncode` pro Kamera. Stabile by-id-Gerätepfade. --- ## Offene Punkte | Punkt | Priorität | |---|---| | Internet-Zugang (TLS via Caddy / Reverse-Proxy) | mittel — `doc/06_portForwarding_roadmap.md` | | WebService-Push (POST /api/snapshot/trigger + Volume) | niedrig — erst bei konkretem Aufrufer | | Verlustfreie Standbilder (YUYV→PNG) | niedrig — nur falls JPEG-Qualität nicht reicht | | Stream-Freeze (selten, Einzelfall) | niedrig — clientseitiger Watchdog noch offen |