6.4 KiB
AppRobotWebcam – Roadmap
Ziel
Fokussierter Webcam-Service als Docker-Container. Zwei Verantwortlichkeiten:
- Live-Video mit minimaler Latenz (MJPEG via Browser
<img>) - 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 <img> 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 (<img>) |
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.
{
"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:
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 |