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

8.1 KiB
Raw Blame History

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:

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:

  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).

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 — 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.