6.7 KiB
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:
ls -la /dev/dri/
# renderD128 vorhanden? → Hardware-Encoding möglich
Wenn ja, Umsetzung:
# 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:
- CPU fällt auf <10%?
- Latenz stabil <200ms?
go2rtc-Log zeigth264_vaapistattlibx264?
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).
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:
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)
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:
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
Snapshot-Auflösung = Stream-Auflösung (USB-Kamera kann nur in einer Auflösung gleichzeitig geöffnet sein).
Bei Lösung 1 (Hardware H.264): Stream hochauflösend konfigurieren, Browser
skaliert herunter. /api/frame.jpeg liefert H.264-Frame (leicht verlustbehaftet).
Beste Qualität: zusätzlich #video=mjpeg für Snapshot-Track (wenn GPU übrig hat).
Bei Lösung 2 (MJPEG): /api/frame.jpeg = natives Kamera-JPEG, volle Qualität, gratis.