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

157 lines
6.4 KiB
Markdown
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
# 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.
```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 |