213 lines
8.1 KiB
Markdown
213 lines
8.1 KiB
Markdown
# AppRobotWebcam – Delay / Ruckler-Analyse
|
||
|
||
## Symptom
|
||
|
||
Nach Umstieg auf WebRTC/H.264: Bild ruckelt, friert teils 1–2 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 | ~35–103 % |
|
||
| AppRobotGo2RTC, 2 Clients | 65–114 % |
|
||
| 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: ~1–2 Sekunden
|
||
- CPU-Peak: kurz, dann zurück auf normal
|
||
- Aufwand: ~2h (Node.js Orchestrierungslogik + go2rtc Stream-API)
|
||
- Geeignet wenn Snapshots nur alle 10–30s 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.
|