Files
appRobotWebcam/doc/04_Delay_roadmap.md
2026-06-04 07:04:25 +02:00

213 lines
8.1 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 Delay / Ruckler-Analyse
## Symptom
Nach Umstieg auf WebRTC/H.264: Bild ruckelt, friert teils 12 s ein, manchmal
bleibt ein Einzelbild ganz stehen. Im reinen MJPEG-Modus trat das **nicht** auf.
---
## Diagnose-Verlauf
### Phase 1 — Messung und Eingrenzung
| Quelle | CPU |
|--------|-----|
| AppRobotGo2RTC, 1 Client | ~35103 % |
| AppRobotGo2RTC, 2 Clients | 65114 % |
| AppRobotWebcam (Node.js) | 0 % |
| Browser-Client (Laptop) | ~10 % |
`getStats()` im Browser lieferte konstant `recv=30/s decoded=30/s dropped=0/s`
Browser und Netz sind nicht das Problem.
Zwei Browser (Laptop + Handy) zeigen exakt identische Latenz für cam0 bzw. cam1.
Ändert sich die Latenz, ändert sie sich auf beiden Clients synchron →
**Problem sitzt in go2rtc/FFmpeg, nicht in Netz oder Browser.**
### Phase 2 — Root-Cause-Analyse
go2rtc's generierter FFmpeg-Befehl (simple URL-Form):
```
-readrate_initial_burst 0.001 -re -i /dev/videoX
-c:v libx264 -g 50 -preset:v superfast -tune:v zerolatency
```
**`-re`** = Rate-Emulation für Datei-Wiedergabe — puffert Live-Frames künstlich.
**`-g 50`** = Keyframe alle 1,67 s → bis zu 1,67 s Standbild nach Loss/Reconnect.
**libx264** = Software-Encoding → CPU-intensiv, skaliert schlecht mit mehreren Clients.
### Phase 3 — Source-Format-Experimente (alle versucht, Ergebnis unbefriedigend)
| Source-Format | Ergebnis | Warum nicht ausreichend |
|---------------|----------|------------------------|
| `ffmpeg:/dev/video0#video=h264` | ~35% CPU mit 1 Client, Bild funktioniert | `-re` erzeugt variable Latenz |
| `ffmpeg:/dev/video0#video=h264#video=mjpeg` | ~95% CPU | Doppeltes Encoding |
| `v4l2:/dev/video0#video=h264` | 0% CPU ohne Client (on-demand ✓), **kein Bild** | v4l2: Source unterstützt `#video=h264` nicht |
| `ffmpeg:-f v4l2 ...#video=h264` | FFmpeg-Parsing-Fehler: `-f` wird als Dateiname interpretiert | go2rtc splittet den String nicht in Args |
| `ffmpeg:device?video=/dev/video0&input_format=mjpeg...#video=h264` | ~103% CPU, Bild funktioniert | Kein `-re` (gut), aber libx264 läuft trotzdem durch |
### Kern-Erkenntnis (nach Phase 3)
> **Das Source-Format ist nicht das Problem. libx264 Software-Encoding ist es.**
> Egal wie die Frames reinkommen — der Encoder frisst denselben CPU.
> Alle Source-Experimente haben daran nichts geändert.
On-Demand-Verhalten ist ein Nebeneffekt: go2rtc startet den Encoder erst bei
erstem Client, stoppt bei letztem. Das ist Standard-go2rtc-Verhalten, unabhängig
vom Source-Format.
---
## Schlussfolgerung: Zwei echte Lösungen
### Lösung 1 — Hardware-Encoding (Intel QuickSync / VAAPI) ← bevorzugt
H.264-Encoding auf der Intel-iGPU statt auf der CPU.
CPU-Last: ~35% → **~5%**. Latenz unverändert (~130ms WebRTC).
Voraussetzung prüfen:
```bash
ls -la /dev/dri/
# renderD128 vorhanden? → Hardware-Encoding möglich
```
Wenn ja, Umsetzung:
```yaml
# docker-compose.yaml — go2rtc service:
devices:
- /dev/video0:/dev/video0
- /dev/video2:/dev/video2
- /dev/dri:/dev/dri # ← GPU durchreichen
# go2rtc-Config:
streams:
cam0: "ffmpeg:device?video=/dev/video0&input_format=mjpeg&video_size=640x480&framerate=30#video=h264#hardware"
cam1: "ffmpeg:device?video=/dev/video2&input_format=mjpeg&video_size=640x480&framerate=30#video=h264#hardware"
```
`#hardware` weist go2rtc an, h264_vaapi zu verwenden. go2rtc baut den FFmpeg-Befehl
mit VAAPI-Flags — ohne `-re`, mit GPU-Encoding.
Zu verifizieren nach Aktivierung:
1. CPU fällt auf <10%?
2. Latenz stabil <200ms?
3. `go2rtc`-Log zeigt `h264_vaapi` statt `libx264`?
### Lösung 2 — MJPEG (Fallback, sofort umsetzbar)
Kein Encoding, kein GOP, keine CPU-Last. War nachweislich stabil und flüssig.
Latenz ~200ms (70ms mehr als WebRTC — für Roboter-Überwachung vertretbar).
```yaml
streams:
cam0: "ffmpeg:device?video=/dev/video0&input_format=mjpeg&video_size=640x480&framerate=30#video=mjpeg"
cam1: "ffmpeg:device?video=/dev/video2&input_format=mjpeg&video_size=640x480&framerate=30#video=mjpeg"
```
Im Browser-Viewer `MODE` anpassen:
```javascript
const MODE = 'mjpeg'; // statt 'webrtc,mse,mjpeg'
```
CPU erwartet: **<5%**. Kein `-g 50`, keine Freezes, kein Encoding-Jitter.
---
## Ergebnis aller Versuche — Entscheid
### Hardware-Encoding: gescheitert (go2rtc-Limitation)
`renderD128` ist vorhanden (`ls -la /dev/dri/` bestätigt). go2rtc's `#hardware`
verwendet `-hwaccel vaapi -hwaccel_output_format vaapi` auf Input-Seite. Das setzt
voraus, dass der **Decoder** VAAPI nutzt. MJPEG von v4l2 wird aber per Software
dekodiert — `hwupload` findet keine VAAPI-Device-Referenz → Filterchain-Fehler.
```
[hwupload] A hardware device reference is required to upload frames to.
[AVFilterGraph] Error initializing filters
```
go2rtc's `#hardware` ist für Re-Encoding von RTSP-H.264-Streams gebaut,
**nicht** für MJPEG-Kamera-Input. Ohne eigenen FFmpeg-Befehl (den go2rtc nicht
erlaubt) ist Hardware-Encoding für diesen Use-Case nicht erreichbar.
### Entscheid: MJPEG-Passthrough ✓ (umgesetzt)
```yaml
cam0: "ffmpeg:device?video=/dev/video0&input_format=mjpeg&video_size=640x480&framerate=30#video=mjpeg"
cam1: "ffmpeg:device?video=/dev/video2&input_format=mjpeg&video_size=640x480&framerate=30#video=mjpeg"
```
Kamera liefert MJPEG nativ → go2rtc reicht es 1:1 durch → kein Encoding → CPU <5%.
| | H.264 Software | H.264 Hardware | **MJPEG Passthrough** |
|-|---------------|----------------|----------------------|
| CPU | ~100% | gescheitert | **<5%** |
| Latenz | ~130ms | — | **~200ms** |
| Freezes | gelegentlich | — | **keine** |
| Stabilität | mittel | — | **hoch** |
70ms mehr Latenz ist für Roboter-Überwachung vertretbar.
Snapshots haben native JPEG-Qualität (kein H.264-Artefakte).
### Falls doch noch H.264 gewünscht (mit korrektem VAAPI)
Erfordert MediaMTX als Zwischenstufe:
```
v4l2 → FFmpeg (vaapi_device + eigene Flags) → RTSP (MediaMTX) → go2rtc WebRTC
```
FFmpeg-Befehl der funktionieren würde:
```bash
ffmpeg -vaapi_device /dev/dri/renderD128 \
-f v4l2 -input_format mjpeg -video_size 640x480 -framerate 30 -i /dev/video0 \
-vf "format=nv12,hwupload" -c:v h264_vaapi -g 15 -bf 0 \
-f rtsp rtsp://mediamtx:8554/cam0
```
Aufwand: ~2h (zusätzlicher Container, RTSP-Verkabelung). Lohnt sich erst wenn
200ms Latenz nachweislich ein Problem für den Anwendungsfall ist.
---
## Hi-Res-Snapshots — offenes Problem
### Warum es nicht trivial ist
Eine USB-Kamera kann gleichzeitig nur **eine** Auflösung liefern.
go2rtc hält die Kamera offen — Snapshot-Auflösung = Stream-Auflösung.
Versuch: `video_size=1280x960` im laufenden Stream → CPU sprang auf 112%.
Ursache unklar (vermutlich MJPEG-Frames 4× grösser → mehr I/O-Last in go2rtc).
**Zurückgesetzt auf stabilen Zustand: 640x480 @ 30fps, ~20% CPU.**
### Drei Optionen (noch nicht umgesetzt)
**Option 1 — Hi-Res-Stream + CSS-Skalierung im Browser**
- Stream auf 1280x720 oder 1280x960 setzen
- Browser zeigt 640x480 (CSS), Snapshot = volle Auflösung
- Problem: CPU-Last beim Hochskalieren steigt (s.o.)
- Lösung: erst `v4l2-ctl --list-formats-ext -d /dev/video0` prüfen welche
MJPEG-Auflösungen die Kamera nativ unterstützt. Dann schrittweise testen:
640x480 → 1280x720 → 1280x960. CPU nach jedem Schritt messen.
- Zeitaufwand: 30 min
**Option 2 — Stream stoppen, Snapshot, Stream starten**
- Node.js orchestriert: go2rtc-Stream stoppen → FFmpeg einmalig
`-frames:v 1` bei maximaler Auflösung → Bild speichern → Stream neu starten
- Video-Blackout: ~12 Sekunden
- CPU-Peak: kurz, dann zurück auf normal
- Aufwand: ~2h (Node.js Orchestrierungslogik + go2rtc Stream-API)
- Geeignet wenn Snapshots nur alle 1030s gebraucht werden
**Option 3 — Separate Kameras für Homing**
- Zwei zusätzliche USB-Kameras, nur für Homing (kein Live-Stream)
- go2rtc öffnet sie nicht → kein Konflikt, volle Auflösung on-demand
- Aufwand: Hardware-Kosten + Montage + FFmpeg one-shot in Node.js
- Sauberste Lösung langfristig
### Empfehlung
Option 1 zuerst, aber schrittweise mit CPU-Messung pro Auflösungsstufe.
Option 2 wenn Blackout akzeptabel und Option 1 zu viel CPU braucht.
Option 3 wenn Homing-Qualität kritisch und Budget vorhanden.