Files
appRobotWebcam/doc/01_WebcamRoadmap.md
2026-06-06 13:12:18 +02:00

6.4 KiB
Raw Permalink Blame History

AppRobotWebcam Roadmap

Ziel

Fokussierter Webcam-Service als Docker-Container. Zwei Verantwortlichkeiten:

  1. Live-Video mit minimaler Latenz (MJPEG via Browser <img>)
  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 <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, 23 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 ~23 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