Claude: WebRTC arbeiten und Roadmap
This commit is contained in:
191
doc/04_Delay_roadmap.md
Normal file
191
doc/04_Delay_roadmap.md
Normal file
@@ -0,0 +1,191 @@
|
||||
# AppRobotWebcam – Delay / Ruckler-Analyse
|
||||
|
||||
## Symptom
|
||||
|
||||
Nach Umstieg auf WebRTC/H.264: Bild ruckelt, friert teils >1 s ein, manchmal
|
||||
bleibt ein Einzelbild ganz stehen. Im reinen MJPEG-Modus trat das **nicht** auf.
|
||||
|
||||
## Messung (2026-06-03)
|
||||
|
||||
| Quelle | CPU |
|
||||
|--------|-----|
|
||||
| System gesamt | ~40 % |
|
||||
| **Container AppRobotGo2RTC** | **~95 %** |
|
||||
|
||||
`docker stats` rechnet pro Kern: **95 % ≈ ein CPU-Kern voll ausgelastet.**
|
||||
→ Flaschenhals ist **go2rtc (Encoding)**, nicht Netzwerk und nicht der Node-Server.
|
||||
|
||||
---
|
||||
|
||||
## Ursachenanalyse
|
||||
|
||||
### Ursache 1 — Software-H.264-Encoding sättigt die CPU
|
||||
Die Kamera liefert **MJPEG** nativ. WebRTC im Browser braucht aber **H.264**.
|
||||
go2rtc transcodiert also jeden Frame MJPEG→H.264 in Software (libx264).
|
||||
Zwei Kameras parallel → ein Kern voll. Wenn der Encoder nicht nachkommt,
|
||||
stauen sich Frames → Ruckeln und Aussetzer.
|
||||
|
||||
> Verstärkt wurde es vorher durch `#video=h264#video=mjpeg`: das ließ go2rtc
|
||||
> **doppelt** encodieren (H.264 *und* MJPEG). Das `#video=mjpeg` ist inzwischen
|
||||
> in der Config entfernt — der Hauptkostenfaktor (H.264-Software-Encode) bleibt.
|
||||
|
||||
### Ursache 2 — Großes GOP (Keyframe-Abstand `-g 50`)
|
||||
go2rtc setzt standardmäßig ein Keyframe alle 50 Frames = **1,67 s bei 30 fps**.
|
||||
H.264 überträgt zwischen Keyframes nur Differenzbilder. Geht ein Paket verloren
|
||||
oder verbindet sich der Client neu, **wartet der Browser bis zum nächsten Keyframe**
|
||||
— bis zu 1,67 s Standbild. Das erklärt exakt das „ein Bild bleibt ganz stehen".
|
||||
|
||||
### Der Grundkonflikt
|
||||
- **MJPEG**: kein Encode (Kamera-nativ), kein GOP → flüssig, ~200 ms, höhere Bandbreite
|
||||
- **H.264/WebRTC**: ~130 ms, geringe Bandbreite → aber Encode-Last + GOP-Freezes
|
||||
|
||||
Wir zahlen also CPU-Last und Komplexität für ~70 ms Latenzgewinn. Ob sich das
|
||||
lohnt, hängt davon ab, ob wir die CPU-Last loswerden (Hardware-Encode / native H.264).
|
||||
|
||||
---
|
||||
|
||||
## Lösungsweg — geordnet nach Aufwand/Wirkung
|
||||
|
||||
### Schritt 1 (5 min) — Prüfen: kann die Kamera H.264 nativ?
|
||||
```bash
|
||||
docker exec AppRobotGo2RTC v4l2-ctl --list-formats-ext -d /dev/video0
|
||||
# v4l2-ctl fehlt im go2rtc-Image? → auf dem Host ausführen:
|
||||
v4l2-ctl --list-formats-ext -d /dev/video0
|
||||
```
|
||||
- **Steht „H264" in der Liste** → go2rtc kann den Stream **durchreichen** (passthrough),
|
||||
praktisch NULL Encode-Last und niedrigste Latenz. Bestfall.
|
||||
- Steht nur MJPEG/YUYV → weiter mit Schritt 2.
|
||||
|
||||
### Schritt 2 (Hauptfix) — Hardware-Encoding (Intel QuickSync / VAAPI)
|
||||
Ein ThinkCentre hat fast sicher eine Intel-iGPU mit QuickSync. Damit wandert das
|
||||
H.264-Encoding von der CPU auf die GPU → **CPU von ~95 % auf ~10 %**.
|
||||
|
||||
Prüfen ob GPU verfügbar:
|
||||
```bash
|
||||
ls -l /dev/dri # renderD128 vorhanden?
|
||||
```
|
||||
Umsetzung (später):
|
||||
```yaml
|
||||
# beim go2rtc-Service:
|
||||
devices:
|
||||
- /dev/video0:/dev/video0
|
||||
- /dev/video2:/dev/video2
|
||||
- /dev/dri:/dev/dri # ← GPU durchreichen
|
||||
# in der go2rtc-Config:
|
||||
streams:
|
||||
cam0: "ffmpeg:/dev/video0#video=h264#hardware"
|
||||
cam1: "ffmpeg:/dev/video2#video=h264#hardware"
|
||||
```
|
||||
|
||||
### Schritt 3 — GOP verkürzen (gegen Freeze nach Loss/Reconnect)
|
||||
Standard-Format erlaubt kein `-g`. Dafür `exec:`-Source mit eigenem FFmpeg-Befehl:
|
||||
```yaml
|
||||
streams:
|
||||
cam0:
|
||||
- "exec:ffmpeg -hide_banner -f v4l2 -input_format mjpeg -video_size 640x480
|
||||
-framerate 30 -i /dev/video0
|
||||
-c:v h264_vaapi -g 15 -bf 0 -tune zerolatency
|
||||
-f rtsp {output}"
|
||||
```
|
||||
`-g 15` = Keyframe alle 0,5 s → Freeze nach Störung max 0,5 s statt 1,67 s.
|
||||
`-bf 0` = keine B-Frames (kein Lookahead-Delay).
|
||||
|
||||
### Schritt 4 — Stellschrauben (zusätzliche Reserve)
|
||||
- Auflösung 640×480 → **320×240** (viertelt die Encode-Pixel)
|
||||
- Framerate 30 → **15 fps** (halbiert die Encode-Frequenz)
|
||||
|
||||
### Schritt 5 (Fallback) — zurück zu MJPEG
|
||||
Falls Hardware-Encode nicht verfügbar ist oder zickt:
|
||||
- **kein Encode, kein GOP → keine Freezes**, stabil flüssig
|
||||
- ~200 ms Latenz (statt 130 ms), höhere Bandbreite — bei 1–3 LAN-Usern egal
|
||||
- go2rtc liefert MJPEG direkt; Viewer: `MODE = 'mjpeg'` oder simples `<img>`
|
||||
|
||||
---
|
||||
|
||||
## Entscheidungsbaum
|
||||
|
||||
```
|
||||
Kamera kann H.264 nativ? ──ja──► Passthrough (Schritt 1) ✓ fertig
|
||||
│ nein
|
||||
▼
|
||||
/dev/dri vorhanden? ──ja──► Hardware-Encode (Schritt 2) ✓ Hauptfix
|
||||
│ nein + GOP kürzen (Schritt 3)
|
||||
▼
|
||||
Latenz 200ms akzeptabel? ──ja──► MJPEG-Fallback (Schritt 5) ✓ robust
|
||||
│ nein
|
||||
▼
|
||||
Auflösung/fps senken (Schritt 4), notfalls 1 Kamera
|
||||
```
|
||||
|
||||
## Empfehlung
|
||||
|
||||
Reihenfolge **1 → 2 → 3**:
|
||||
1. Erst native H.264 prüfen (kostet 5 min, evtl. löst es alles).
|
||||
2. Sonst Hardware-Encoding aktivieren — das ist der eigentliche Hebel gegen die 95 %.
|
||||
3. Dann GOP kürzen, damit auch die Restfreezes verschwinden.
|
||||
|
||||
**MJPEG (Schritt 5) ist der sichere Hafen**, falls die GPU nicht mitspielt:
|
||||
es war nachweislich flüssig, nur 70 ms langsamer. Für diesen Anwendungsfall
|
||||
(Roboter-Überwachung, 1–3 User) völlig ausreichend.
|
||||
|
||||
## Hi-Res-Snapshots — Analyse (Live-Video + Foto alle ~10 s)
|
||||
|
||||
Ziel: schnelles Live-Video **und** gelegentlich (≈ alle 10 s) ein hochauflösendes Foto.
|
||||
|
||||
### Die entscheidende Einschränkung
|
||||
Eine USB-Kamera kann **gleichzeitig nur in einer Auflösung** geöffnet werden.
|
||||
Solange go2rtc das Device für den Live-Stream hält, kann kein zweiter Prozess
|
||||
parallel ein höher aufgelöstes Foto ziehen (Device belegt).
|
||||
|
||||
→ **Snapshot-Auflösung = Stream-Auflösung.** Es gibt keinen billigen Nebenweg zu
|
||||
einem höher aufgelösten Foto, solange der Stream klein läuft. `/api/frame.jpeg`
|
||||
decodiert immer einen Frame **aus dem laufenden Stream**.
|
||||
|
||||
Konsequenz: Für Hi-Res-Fotos muss der **Stream selbst hochauflösend** laufen und
|
||||
fürs Live-Bild im Browser heruntergerechnet werden. Der Trick ist, das billig zu halten.
|
||||
|
||||
### Weg A — MJPEG hochauflösend (Passthrough)
|
||||
- Quelle: Kamera hochauflösend MJPEG → go2rtc reicht 1:1 durch, **kein Encode**
|
||||
- Snapshot: `/api/frame.jpeg` = voller Frame, **native JPEG-Qualität**, gratis
|
||||
- Live: MJPEG, im Browser auf 480 skaliert (~200 ms, war flüssig)
|
||||
- CPU ~5 %, keine Freezes. Preis: höhere LAN-Bandbreite (unkritisch bei 1–3 Usern)
|
||||
|
||||
### Weg B — WebRTC + Hardware-Encoding ◄ favorisiert, mit Bedingung
|
||||
- Quelle: Kamera hochauflösend; Live-Track H.264 **per Intel-GPU (QuickSync)**
|
||||
- Live: WebRTC ~130 ms, CPU ~10 %
|
||||
|
||||
**Bedingung des Users: der Frame aus dem Stream MUSS hochauflösend sein.**
|
||||
Antwort: **ja, per Definition** — `/api/frame.jpeg` hat dieselbe Auflösung wie der
|
||||
Stream. Läuft H.264 in 1280×960, ist das Foto 1280×960. Garantiert durch die Config
|
||||
(Stream-Auflösung explizit hochauflösend setzen → WebRTC überträgt hochauflösend,
|
||||
Browser skaliert fürs Display herunter).
|
||||
|
||||
**Qualitäts-Nuance:** Ein aus H.264 decodierter Frame ist leicht verlustbehaftet
|
||||
(H.264 → JPEG). Für ArUco meist ausreichend, aber nicht optimal.
|
||||
|
||||
**Beste Variante (Hi-Res UND native Qualität)** — erst durch HW-Encode praktikabel:
|
||||
```yaml
|
||||
# Quelle hochauflösend; H.264 (GPU) für Live + MJPEG-Passthrough für Snapshot
|
||||
cam0: "ffmpeg:/dev/video0#video=h264#hardware#video=mjpeg"
|
||||
```
|
||||
- Live-Track: H.264 per GPU (billig)
|
||||
- Snapshot-Track: MJPEG-Passthrough (gratis, kamera-nativ)
|
||||
- `/api/frame.jpeg` sollte den **MJPEG-Track** nehmen → volle Auflösung, native Qualität
|
||||
- Das ist `#video=h264#video=mjpeg` wie früher — aber OHNE Flaschenhals, weil nur
|
||||
H.264 die GPU nutzt und MJPEG reines Durchreichen ist.
|
||||
|
||||
### Vor Weg B zu verifizieren („sichergestellt" erst danach)
|
||||
1. `ls -l /dev/dri` → ist `renderD128` vorhanden? (Intel-GPU verfügbar)
|
||||
2. Hardware-Encode testweise aktivieren (`#hardware`) → fällt CPU wirklich von 95 %?
|
||||
3. `/api/frame.jpeg?src=cam0` abrufen → **Auflösung prüfen** (hoch?) **und Qualität**
|
||||
4. Klären, welchen Track `/api/frame.jpeg` bei `#video=h264#hardware#video=mjpeg`
|
||||
tatsächlich verwendet (MJPEG-Passthrough = native Qualität gewünscht)
|
||||
|
||||
> Diese 4 Checks können nicht aus der Ferne garantiert werden — sie müssen am
|
||||
> ThinkCentre laufen. Erst danach ist Weg B „sichergestellt".
|
||||
|
||||
### Snapshot-Takt (alle ~10 s)
|
||||
Der 10-s-Takt erzeugt **keine** Dauerlast: pro Foto wird nur ein Frame aus dem
|
||||
ohnehin laufenden Stream abgegriffen. Trigger wahlweise:
|
||||
- Pull: Homing-Projekt ruft `/api/snapshot/cam0` alle 10 s ab (aktuell so vorgesehen)
|
||||
- Push: kleiner Timer im Node-Server, der das Foto ablegt / per Webhook sendet (Phase 5)
|
||||
Reference in New Issue
Block a user