claude: webcam
This commit is contained in:
@@ -2,106 +2,89 @@
|
||||
|
||||
## Ziel
|
||||
|
||||
Sauberer, fokussierter Webcam-Streaming-Service als Docker-Container.
|
||||
Kein Robot-Control, kein ArUco – nur zwei Verantwortlichkeiten:
|
||||
Sauberer, fokussierter Webcam-Service als Docker-Container. Kein Robot-Control, kein
|
||||
ArUco – nur zwei Verantwortlichkeiten:
|
||||
|
||||
1. **Live-Video** mit minimaler Latenz (MJPEG über WebSocket)
|
||||
1. **Live-Video** mit minimaler Latenz (WebRTC via go2rtc)
|
||||
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.
|
||||
Das Homing-Projekt holt seine Standbilder über den Snapshot-Endpunkt – keine weitere Kopplung.
|
||||
|
||||
---
|
||||
|
||||
## Architektur
|
||||
## Architektur (final)
|
||||
|
||||
```
|
||||
USB Kameras
|
||||
│
|
||||
▼
|
||||
FFmpeg (v4l2 → MJPEG)
|
||||
go2rtc (Capture · H.264-Encode · WebRTC) ── intern, Port 1984 + UDP 8555
|
||||
│
|
||||
▼
|
||||
Node.js Server (Express + ws)
|
||||
├── WebSocket /ws/cam0, /ws/cam1 → Browser (Live-Stream)
|
||||
└── HTTP GET /api/snapshot/cam0 → Homing-Projekt (JPEG)
|
||||
Node.js / Express ── öffentlich, Port 8444
|
||||
├── / eigener Viewer (go2rtc <video-stream>-Component)
|
||||
├── /api/ws WebRTC-Signaling → proxied zu go2rtc
|
||||
├── /api/snapshot/* Standbilder → proxied zu go2rtc /api/frame.jpeg
|
||||
└── /health
|
||||
```
|
||||
|
||||
**Stack-Entscheide (bereits umgesetzt):**
|
||||
**Warum go2rtc statt eigenem FFmpeg-Stream:**
|
||||
Erste Version (eigener Node-FFmpeg-MJPEG-Stream über WebSocket) hatte spürbare Latenz.
|
||||
go2rtc ist ein spezialisierter Streaming-Server: WebRTC mit ~50–150 ms, automatisches
|
||||
Encoding, ICE-Negotiation, robuster Client mit Auto-Fallback (WebRTC→MSE→MJPEG).
|
||||
|
||||
**Warum Node davor (statt go2rtc direkt):**
|
||||
- Ein einziger öffentlicher Port (8444); go2rtc-Admin bleibt unerreichbar
|
||||
- Stabile Snapshot-Schnittstelle, entkoppelt von go2rtc-Interna
|
||||
- Eigener, schlanker Viewer
|
||||
|
||||
**Stack-Entscheide:**
|
||||
|
||||
| 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 |
|
||||
| Streaming | go2rtc | WebRTC out-of-the-box, niedrige Latenz, internet-tauglich |
|
||||
| Webserver | Node.js + Express | wartbar, user-präferiert |
|
||||
| Proxy | http-proxy-middleware | reicht HTTP + WebSocket transparent durch |
|
||||
| Live-Protokoll | WebRTC (Fallback MSE/MJPEG) | niedrigste Latenz, skaliert über Internet |
|
||||
| Snapshot-API | HTTP GET → JPEG | einfachste Schnittstelle für Consumer |
|
||||
| Container | docker-compose, `configs` inline | kein Dockerfile-File, Portainer-tauglich |
|
||||
|
||||
---
|
||||
|
||||
## Tasks
|
||||
|
||||
### Phase 1 – Grundgerüst ✅ (erledigt)
|
||||
### Phase 1 – Grundgerüst ✅
|
||||
- [x] Projektstruktur, package.json, docker-compose mit inline-Config
|
||||
- [x] Erste Version (Node-FFmpeg-MJPEG über WebSocket) – verworfen wegen Latenz
|
||||
|
||||
- [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 – Umstieg auf go2rtc / WebRTC ✅
|
||||
- [x] go2rtc als Streaming-Backend (Kamera-Capture + WebRTC)
|
||||
- [x] go2rtc-Config in docker-compose eingebettet (`configs.content`)
|
||||
- [x] Node als Reverse-Proxy (`/api/ws`, `/api/frame.jpeg`, Player-Scripts)
|
||||
- [x] Eigener Viewer mit go2rtc `<video-stream>`-Component (Auto-Fallback)
|
||||
- [x] Stabile Snapshot-API `/api/snapshot/cam{n}`
|
||||
- [x] Auflösung fest 640×480 → Latenz „akzeptabel" (war vorher das Hauptproblem)
|
||||
|
||||
### Phase 2 – Deployment & Latenz-Baseline
|
||||
### Phase 3 – Latenz final tunen (offen)
|
||||
- [ ] Messvergleich WebRTC ⟷ MJPEG durchführen → siehe `03_Protocoll_roadmap.md`
|
||||
- [ ] Falls nötig: Auflösung 320×240 testen (kleiner = weniger Browser-Last)
|
||||
- [ ] Falls nötig: Keyframe-Intervall senken (`-g 15`), zerolatency-Tuning
|
||||
- [ ] Prüfen ob Kamera natives H.264 liefert (`v4l2-ctl --list-formats`) → kein Re-Encode
|
||||
|
||||
- [ ] **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 4 – Internet-Härtung (offen, vor Produktiv-Schaltung)
|
||||
- [ ] **TLS**: Reverse Proxy (Caddy/nginx/traefik) mit HTTPS vor Port 8444
|
||||
(WebRTC im Browser läuft über Internet zuverlässig nur im secure context)
|
||||
- [ ] **WebRTC-Candidate**: `stun:8555` testen; falls NAT-Probleme → feste public IP/Domain
|
||||
in der go2rtc-Config eintragen (`candidates: [robot.example.com:8555]`)
|
||||
- [ ] **TURN**: nur falls reines STUN + Port-Forward UDP 8555 nicht reicht → coturn
|
||||
- [ ] **Zugriffsschutz**: Basic-Auth oder Token am Reverse Proxy (1–3 bekannte User)
|
||||
- [ ] **Firewall**: TCP 8444 + UDP 8555 forwarden; Port 1984 NICHT exponieren
|
||||
|
||||
### 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`
|
||||
### Phase 5 – Robustheit (optional)
|
||||
- [ ] Kamera hot-plug: go2rtc-Verhalten bei Device-Verlust prüfen
|
||||
- [ ] Resource Limits dokumentieren (`mem_limit`, `cpus`)
|
||||
- [ ] JSON-Logging
|
||||
- [ ] Snapshot-Metadaten / optionaler Webhook nach Snapshot
|
||||
|
||||
---
|
||||
|
||||
@@ -109,26 +92,21 @@ Drei Optionen (noch offen):
|
||||
|
||||
| Feature | appRobotVideoControls | appRobotWebcam |
|
||||
|---------|-----------------------|----------------|
|
||||
| Video-Streaming | ✅ | ✅ (verbessert) |
|
||||
| Snapshots | ✅ (komplex, dual-pipe) | ✅ (HTTP REST, einfach) |
|
||||
| Video-Streaming | eigener FFmpeg-MJPEG/WS | go2rtc / WebRTC |
|
||||
| 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) |
|
||||
| ArUco / Homing | ✅ | ❌ anderes Projekt |
|
||||
| Separates Dockerfile | ✅ (OpenCV-Build) | ❌ (inline in compose) |
|
||||
|
||||
---
|
||||
|
||||
## Ports & Netzwerk
|
||||
## Ports
|
||||
|
||||
| Service | Container-Port | Host-Port |
|
||||
|---------|---------------|-----------|
|
||||
| HTTP + WS | 8080 | 8444 |
|
||||
|
||||
```bash
|
||||
# Netzwerk einmalig erstellen (falls noch nicht vorhanden)
|
||||
docker network create appRobotNet
|
||||
```
|
||||
| Dienst | Port | Exponiert? |
|
||||
|--------|------|-----------|
|
||||
| Node Viewer + API + Signaling | TCP 8444 | ja (Firewall) |
|
||||
| WebRTC Media | UDP 8555 | ja (Firewall) |
|
||||
| go2rtc HTTP/Debug-UI | TCP 1984 | nein (nur intern/LAN) |
|
||||
|
||||
---
|
||||
|
||||
@@ -137,16 +115,16 @@ docker network create appRobotNet
|
||||
```
|
||||
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
|
||||
│ └── snapshotService.js Snapshot-Router (proxied go2rtc /api/frame.jpeg)
|
||||
├── public/
|
||||
│ ├── index.html Basis-Viewer
|
||||
│ └── viewer.js WebSocket-Client, MJPEG-Rendering
|
||||
│ ├── index.html Viewer (lädt go2rtc <video-stream>)
|
||||
│ └── viewer.js baut Kamera-Views, Auto-Fallback WebRTC→MSE→MJPEG
|
||||
├── doc/
|
||||
│ ├── 01_WebcamRoadmap.md (diese Datei)
|
||||
│ └── 05_OptionalToDo_roadmap.md Control-Optionen
|
||||
├── docker-compose.yaml Einzige Docker-Datei (dockerfile_inline)
|
||||
│ ├── 01_WebcamRoadmap.md (diese Datei)
|
||||
│ ├── 03_Protocoll_roadmap.md WebRTC⟷MJPEG-Vergleich (nachzuholen)
|
||||
│ └── 05_OptionalToDo_roadmap.md Control-Integration (Optionen)
|
||||
├── docker-compose.yaml einzige Deploy-Datei (go2rtc-Config eingebettet)
|
||||
├── go2rtc.yaml nur Referenz/lokal (Config ist in compose eingebettet)
|
||||
├── package.json
|
||||
└── server.js Einstiegspunkt
|
||||
└── server.js Node-Einstiegspunkt
|
||||
```
|
||||
|
||||
100
doc/03_Protocoll_roadmap.md
Normal file
100
doc/03_Protocoll_roadmap.md
Normal file
@@ -0,0 +1,100 @@
|
||||
# AppRobotWebcam – Protokoll-Vergleich WebRTC ⟷ MJPEG
|
||||
|
||||
## Status
|
||||
|
||||
- **Auflösung fest auf 640×480** → Latenz ist jetzt **„immer akzeptabel"** (vorher schwankend/träge).
|
||||
Das bestätigt: ein Teil der gefühlten Latenz kam von zu großen Frames im Browser
|
||||
(Decode + Render). Kleineres Bild = schnellerer Browser.
|
||||
- **Entscheid vorläufig: WebRTC** – skaliert besser über echtes Internet (NAT, mehrere User).
|
||||
- go2rtc bleibt die Basis → **http://thinkcentre.local:1984** bleibt jederzeit als
|
||||
Vergleichs- und Debug-Oberfläche verfügbar.
|
||||
|
||||
> Der direkte Messvergleich WebRTC ⟷ MJPEG steht noch aus (Zeitgründen).
|
||||
> Diese Datei hält fest, **wie** man ihn nachholt.
|
||||
|
||||
---
|
||||
|
||||
## Warum überhaupt vergleichen?
|
||||
|
||||
| Protokoll | Latenz (LAN, 480p) | Mechanik | Bandbreite |
|
||||
|-----------|--------------------|----------|------------|
|
||||
| **MJPEG** | am niedrigsten | Jedes Frame ein eigenständiges JPEG, kein Buffer, `<img>` rendert sofort | hoch (jedes Frame voll) |
|
||||
| **WebRTC** | niedrig | H.264-Encode + Jitter-Buffer im Browser; dafür effiziente Kompression | niedrig |
|
||||
| **MSE** | mittel–hoch | Für VOD optimiert, größerer Puffer | niedrig |
|
||||
|
||||
- **MJPEG** = theoretische Latenz-Untergrenze, aber bei mehr Usern / über Internet
|
||||
bandbreitenhungrig und ohne NAT-Traversal.
|
||||
- **WebRTC** = minimal mehr Latenz durch Encode/Buffer, dafür internet-tauglich
|
||||
(NAT-Traversal via STUN/TURN, geringe Bandbreite, mehrere User).
|
||||
|
||||
Für **1–3 User im LAN** kann MJPEG gewinnen. Über **Internet** gewinnt WebRTC fast immer.
|
||||
|
||||
---
|
||||
|
||||
## So führst du den Vergleich durch
|
||||
|
||||
go2rtc stellt jeden Stream über **mehrere** Protokolle bereit. Jeweils einzeln und
|
||||
direkt im Browser öffnen (für cam1: `cam0` → `cam1` ersetzen):
|
||||
|
||||
| Modus | URL | Was es testet |
|
||||
|-------|-----|---------------|
|
||||
| **MJPEG roh** | `http://thinkcentre.local:1984/api/stream.mjpeg?src=cam0` | Untergrenze: reines Bild, kein Player, kein Buffer |
|
||||
| **WebRTC pur** | `http://thinkcentre.local:1984/webrtc.html?src=cam0` | WebRTC isoliert |
|
||||
| **MSE pur** | `http://thinkcentre.local:1984/mse.html?src=cam0` | MSE isoliert (Referenz) |
|
||||
| **Alle Links** | `http://thinkcentre.local:1984/links.html?src=cam0` | go2rtc listet selbst alle Endpunkte auf |
|
||||
|
||||
> Hinweis zur go2rtc-Startseite: Die Checkboxen (WebRTC/MSE/MJPEG) oben filtern nur,
|
||||
> welche Technologien der **kombinierte** Player `stream.html` ausprobieren darf –
|
||||
> sichtbar ändert sich dabei nichts, weil er automatisch die erste passende nimmt.
|
||||
> Für einen echten Vergleich **die obigen Einzel-URLs** verwenden.
|
||||
|
||||
### Latenz messen (objektiv, in ms)
|
||||
|
||||
1. Handy-Stoppuhr mit Millisekunden-Anzeige vor die Kamera halten.
|
||||
2. MJPEG-URL und WebRTC-URL in zwei Tabs/Fenstern nebeneinander öffnen.
|
||||
3. Einen Screenshot machen, der **die echte Stoppuhr** und **beide Stream-Bilder**
|
||||
gleichzeitig zeigt (Handy + Monitor zusammen abfotografieren ist am einfachsten).
|
||||
4. Differenz „echte Zeit ↔ Bild im Stream" ablesen = Gesamt-Latenz pro Protokoll.
|
||||
|
||||
### Ergebnis-Tabelle (später ausfüllen)
|
||||
|
||||
| Kamera | MJPEG roh | WebRTC | MSE | Sieger |
|
||||
|--------|-----------|--------|-----|--------|
|
||||
| cam0 | ? ms | ? ms | ? ms | ? |
|
||||
| cam1 | ? ms | ? ms | ? ms | ? |
|
||||
|
||||
---
|
||||
|
||||
## Entscheidungslogik nach dem Test
|
||||
|
||||
- **WebRTC ≈ MJPEG (Differenz < ~50 ms):**
|
||||
→ Bei **WebRTC** bleiben. Vorteil Internet-Skalierung überwiegt die paar ms.
|
||||
|
||||
- **MJPEG deutlich schneller (Differenz > ~100 ms) UND nur LAN-Nutzung:**
|
||||
→ Optional **MJPEG-Viewer** zusätzlich anbieten (simple `<img>`-Seite).
|
||||
go2rtc liefert MJPEG ohnehin schon unter `/api/stream.mjpeg?src=camN`.
|
||||
|
||||
- **Auflösung weiter drücken:**
|
||||
→ In `docker-compose.yaml` unter `configs:` `video_size=640x480` → `320x240`.
|
||||
Test wiederholen. Kleiner = weniger Browser-Last = weniger Latenz.
|
||||
|
||||
---
|
||||
|
||||
## Weitere Latenz-Stellschrauben (falls WebRTC noch zu träge)
|
||||
|
||||
1. **Keyframe-Intervall senken** – H.264 startet erst beim nächsten Keyframe.
|
||||
In der go2rtc-Quelle den Encoder mit `-g 15` (Keyframe alle 0.5 s @30fps) zwingen.
|
||||
2. **Kamera-natives H.264** – falls die Webcam H.264 direkt liefert (UVC H.264),
|
||||
kann go2rtc ohne Re-Encode durchreichen → minimale CPU + Latenz.
|
||||
Prüfen mit: `v4l2-ctl -d /dev/video0 --list-formats`
|
||||
3. **`zerolatency`-Tuning** im Encoder (ultrafast + tune zerolatency).
|
||||
4. **WebRTC statt über Proxy direkt** – Jitter-Buffer des Browsers ist Fixkosten,
|
||||
lässt sich nur begrenzt beeinflussen.
|
||||
|
||||
---
|
||||
|
||||
## Wichtig: go2rtc bleibt erhalten
|
||||
|
||||
Da der finale Aufbau weiter auf go2rtc setzt, bleibt **http://thinkcentre.local:1984**
|
||||
dauerhaft als Debug-/Vergleichs-UI nutzbar. Der Protokoll-Vergleich kann also
|
||||
**jederzeit später** nachgeholt werden, ohne etwas umzubauen.
|
||||
Reference in New Issue
Block a user