153 lines
5.7 KiB
Markdown
153 lines
5.7 KiB
Markdown
# AppRobotWebcam – Roadmap
|
||
|
||
## Ziel
|
||
|
||
Sauberer, fokussierter Webcam-Streaming-Service als Docker-Container.
|
||
Kein Robot-Control, kein ArUco – nur zwei Verantwortlichkeiten:
|
||
|
||
1. **Live-Video** mit minimaler Latenz (MJPEG über WebSocket)
|
||
2. **Standbilder** auf Abruf via HTTP REST (`/api/snapshot/cam{n}`)
|
||
|
||
Das Homing-Projekt holt seine Standbilder über den Snapshot-Endpunkt – keine weitere Kopplung nötig.
|
||
|
||
---
|
||
|
||
## Architektur
|
||
|
||
```
|
||
USB Kameras
|
||
│
|
||
▼
|
||
FFmpeg (v4l2 → MJPEG)
|
||
│
|
||
▼
|
||
Node.js Server (Express + ws)
|
||
├── WebSocket /ws/cam0, /ws/cam1 → Browser (Live-Stream)
|
||
└── HTTP GET /api/snapshot/cam0 → Homing-Projekt (JPEG)
|
||
```
|
||
|
||
**Stack-Entscheide (bereits umgesetzt):**
|
||
|
||
| Komponente | Wahl | Begründung |
|
||
|------------|------|------------|
|
||
| Webserver | Node.js + Express | Wartbar, grosses Ecosystem, user-präferiert |
|
||
| WebSocket | `ws`-Library | Schlank, bewährt, kein Overhead |
|
||
| Video-Capture | FFmpeg | Stabil, flexibel, MJPEG-passthrough möglich |
|
||
| Stream-Protokoll | MJPEG über WebSocket | Geringste Latenz, einfach im Browser |
|
||
| Snapshot-API | HTTP GET → raw JPEG | Einfachste Schnittstelle für Consumer |
|
||
| Container | `dockerfile_inline` in docker-compose | Kein separates Dockerfile, Portainer-tauglich |
|
||
|
||
---
|
||
|
||
## Tasks
|
||
|
||
### Phase 1 – Grundgerüst ✅ (erledigt)
|
||
|
||
- [x] Projektstruktur anlegen
|
||
- [x] `package.json` mit Abhängigkeiten (express, ws)
|
||
- [x] `docker-compose.yaml` mit `dockerfile_inline` (Node.js + FFmpeg, kein separates Dockerfile)
|
||
- [x] `server.js` – HTTP + WebSocket-Server, Graceful Shutdown
|
||
- [x] `src/deviceDetect.js` – Kamera-Erkennung (env → by-id → /dev/video*)
|
||
- [x] `src/videoStream.js` – FFmpegStreamer (MJPEG splitten, WebSocket broadcast, Auto-Restart mit Backoff)
|
||
- [x] `src/snapshotService.js` – REST-Endpunkt: aktuellstes Frame aus laufendem Stream
|
||
- [x] `public/index.html` + `public/viewer.js` – Basis-Viewer
|
||
|
||
### Phase 2 – Deployment & Latenz-Baseline
|
||
|
||
- [ ] **Kamera-Zugriff im Container** verifizieren:
|
||
- `/dev/video0` und `/dev/video2` im Container sichtbar
|
||
- `group_add: video` greift (Zugriffsrechte)
|
||
- Fallback auf YUYV422 wenn MJPEG nicht unterstützt
|
||
- [ ] **Latenz messen** (Baseline):
|
||
- Uhr auf Kamera richten, Screenshot → Differenz ablesen
|
||
- Zielwert: <100 ms Ende-zu-Ende (Kamera → Browser-Pixel)
|
||
- [ ] **Multi-Format-Fallback** implementieren: MJPEG → YUYV422 → RGB24
|
||
- [ ] **Health-Endpunkt** `/health` erweitern: Kamera-Status, verbundene Clients, FPS
|
||
|
||
### Phase 3 – Latenz optimieren
|
||
|
||
- [ ] **FFmpeg-Flags tunen** für minimale Latenz:
|
||
```
|
||
-fflags nobuffer -flags low_delay -probesize 32 -analyzeduration 0
|
||
```
|
||
- [ ] **Native MJPEG pass-through** testen:
|
||
Wenn Kamera MJPEG nativ bei Zielauflösung liefert → `-vcodec copy`
|
||
(kein Re-Encoding, minimale CPU-Last, minimale Latenz)
|
||
- [ ] **Canvas-Rendering** im Browser: `createImageBitmap()` statt Blob-URL-Overhead
|
||
- [ ] **WebRTC evaluieren**: <50 ms möglich, aber STUN/TURN-Komplexität in Docker –
|
||
sinnvoll erst wenn MJPEG-Latenz >150 ms bleibt
|
||
|
||
### Phase 4 – Hochauflösende Snapshots
|
||
|
||
**Aktuell**: Snapshot = letztes Frame aus dem Stream (Auflösung = Stream-Auflösung).
|
||
**Ziel**: Snapshot in originaler Kamera-Auflösung (z.B. 1280×960).
|
||
|
||
Drei Optionen (noch offen):
|
||
|
||
| Option | Vorgehen | Pro | Contra |
|
||
|--------|----------|-----|--------|
|
||
| A | Stream-Frame direkt nehmen | Sofort, kein Aufwand | Auflösung = Stream-Auflösung |
|
||
| B | Zweite FFmpeg-Pipeline 0.5 FPS High-Res | Immer verfügbar | CPU-Last, pipe:3 Komplexität |
|
||
| C | Einmaliger `ffmpeg -frames:v 1` on-demand | Hohe Qualität | ~500 ms Delay, Stream-Unterbrechung |
|
||
|
||
- [ ] Option wählen und implementieren
|
||
- [ ] Mit Homing-Projekt testen (Consumer von `/api/snapshot`)
|
||
- [ ] Snapshot-Metadaten in Response-Headern: Zeitstempel, Auflösung, Kamera-ID
|
||
- [ ] Optionaler Webhook: POST nach Snapshot an konfigurierbaren Endpunkt
|
||
|
||
### Phase 5 – Robustheit & Produktion
|
||
|
||
- [ ] Kamera hot-plug: Stream-Neustart wenn `/dev/videoX` verschwindet/wiederkommt
|
||
- [ ] Resource Limits: `--memory 512m --cpus 1.0` in docker-compose
|
||
- [ ] HTTPS: Reverse Proxy (nginx/traefik) vorschalten – kein TLS im App-Code
|
||
- [ ] JSON-Logging mit Level (info/warn/error)
|
||
- [ ] Kamera-Parameter per env var: `CAM0_WIDTH`, `CAM0_HEIGHT`, `CAM0_FPS`, `CAM0_QUALITY`
|
||
|
||
---
|
||
|
||
## Abgrenzung zu appRobotVideoControls
|
||
|
||
| Feature | appRobotVideoControls | appRobotWebcam |
|
||
|---------|-----------------------|----------------|
|
||
| Video-Streaming | ✅ | ✅ (verbessert) |
|
||
| Snapshots | ✅ (komplex, dual-pipe) | ✅ (HTTP REST, einfach) |
|
||
| Robot-Control (G-Code) | ✅ | ❌ anderes Projekt |
|
||
| ArUco / Homing | ✅ (Python+OpenCV) | ❌ anderes Projekt |
|
||
| Gamepad / Keyboard | ✅ | ❌ |
|
||
| HTTPS (self-signed) | ✅ | ❌ (Reverse Proxy empfohlen) |
|
||
| Separates Dockerfile | ✅ (gross, OpenCV-Build) | ❌ (inline in compose) |
|
||
|
||
---
|
||
|
||
## Ports & Netzwerk
|
||
|
||
| Service | Container-Port | Host-Port |
|
||
|---------|---------------|-----------|
|
||
| HTTP + WS | 8080 | 8444 |
|
||
|
||
```bash
|
||
# Netzwerk einmalig erstellen (falls noch nicht vorhanden)
|
||
docker network create appRobotNet
|
||
```
|
||
|
||
---
|
||
|
||
## Datei-Struktur
|
||
|
||
```
|
||
appRobotWebcam/
|
||
├── src/
|
||
│ ├── deviceDetect.js Kamera-Erkennung (env → by-id → /dev/video*)
|
||
│ ├── videoStream.js FFmpeg-MJPEG-Streamer + WebSocket-Broadcast
|
||
│ └── snapshotService.js REST-Router für /api/snapshot
|
||
├── public/
|
||
│ ├── index.html Basis-Viewer
|
||
│ └── viewer.js WebSocket-Client, MJPEG-Rendering
|
||
├── doc/
|
||
│ ├── 01_WebcamRoadmap.md (diese Datei)
|
||
│ └── 05_OptionalToDo_roadmap.md Control-Optionen
|
||
├── docker-compose.yaml Einzige Docker-Datei (dockerfile_inline)
|
||
├── package.json
|
||
└── server.js Einstiegspunkt
|
||
```
|