Umbau mit cameraSwitch Fix Delay
This commit is contained in:
@@ -388,14 +388,17 @@ Eine `CameraSwitch`-Instanz pro Gerät — der **einzige** Öffner von `/dev/vid
|
||||
immer nur **einen** FFmpeg. Zustände `stopped | live | grabbing`, Mutex pro Kamera.
|
||||
|
||||
- **Live (Dauerbetrieb):** `ffmpeg -f v4l2 -input_format mjpeg -video_size 640x480
|
||||
-framerate 30 -i /dev/videoN -c:v mjpeg -q:v 5 -f mpjpeg pipe:1`. Node parst
|
||||
die mpjpeg-Frames (Content-Length-basiert), hält den letzten (für `/api/snapshot`),
|
||||
sendet sie an alle Stream-Clients. Crash → Auto-Restart nach 1,5 s.
|
||||
> ⚠ **`-c:v mjpeg` (Re-Encode), NICHT `-c:v copy`.** `copy` ist auf dieser Kamera
|
||||
> empirisch tot: 04/09 dokumentieren CPU **107%** + hängendes Bild (FFmpeg verschluckt
|
||||
> sich an den APP-Feldern des Kamera-MJPEG). Re-Encode = der bewährte ~50%-Pfad
|
||||
> (entspricht go2rtcs `#video=mjpeg`). **Dieser Fehler wurde am 2026-06-05 zunächst
|
||||
> wiederholt (copy) und dann korrigiert.**
|
||||
-framerate 30 -i /dev/videoN -c:v copy -bsf:v mjpeg2jpeg -f mpjpeg pipe:1` (Default
|
||||
`ENCODE_MODE=copybsf`). Node parst die mpjpeg-Frames (Content-Length-basiert), hält den
|
||||
letzten (für `/api/snapshot`), sendet sie an alle Stream-Clients. Crash → Restart 1,5 s.
|
||||
> ⚠ **Der `mjpeg2jpeg`-Bitstream-Filter ist Pflicht.** Plain `-c:v copy` (ohne Filter)
|
||||
> ist auf dieser Kamera tot: **107% CPU + Hang** (04/09), weil das Kamera-MJPEG die
|
||||
> JPEG-Tables weglässt und der mpjpeg-Muxer sich verschluckt. **Auf dem Host getestet
|
||||
> (2026-06-05):** `copy -bsf:v mjpeg2jpeg` läuft sauber (der „APP fields"-Hinweis ist
|
||||
> eine einmalige Probe-Warnung) → kein Transcode → CPU < Re-Encode, browser-valide JPEGs.
|
||||
> Fallback `ENCODE_MODE=mjpeg` = Re-Encode ~50% (go2rtcs `#video=mjpeg`).
|
||||
> **Lehrgeld 2026-06-05:** erst `copy` ohne Filter ausgeliefert (107%), dann via Host-
|
||||
> Messung auf `copy+mjpeg2jpeg` korrigiert.
|
||||
- **HD-Grab (`grabHires`):** Live-FFmpeg `SIGTERM` → **auf `close` warten** (FD frei) →
|
||||
1280-FFmpeg, warmlaufen (ab Frame ≥6, ≥15 KB, Breite ≥1000), besten Frame greifen →
|
||||
beenden, auf `close` warten → `finally`: **immer** Live zurück (Live hat Priorität).
|
||||
@@ -413,14 +416,34 @@ droppen, andere bleiben flüssig. Clients halten **kein** Gerät → **Multi-Use
|
||||
Ein Node-Container mit FFmpeg, Geräte durchgereicht, `group_add: video`. Env-Overrides:
|
||||
`DEV0/DEV1`, `LIVE_SIZE/LIVE_FPS`, `HIRES_SIZE/HIRES_FPS`. Firewall: nur noch **TCP 8444**.
|
||||
|
||||
## Latenz-Tuning (2026-06-05)
|
||||
|
||||
Gemessen ~340 ms Kamera→Browser. Gegenmaßnahmen (verlustarm, Lost Frames erlaubt):
|
||||
- **FFmpeg Live:** `-fflags nobuffer` (Input nicht puffern) + `-flush_packets 1` (jedes
|
||||
Frame sofort aus dem Muxer in die Pipe).
|
||||
- **Node-Stream:** `socket.setNoDelay(true)` (Nagle aus) + `cork/uncork` um Header+JPEG+
|
||||
Trailer → ein TCP-Segment pro Frame, sofort gesendet.
|
||||
- Backpressure droppt Frames für langsame Clients statt zu puffern → Latenz steigt nicht.
|
||||
- Weitere Hebel, falls nötig: `LIVE_FPS` runter ändert die Latenz NICHT (nur Buffering),
|
||||
aber `HIRES_FPS` etc. egal hier. Browser-`<img>` fügt ~1 Frame Anzeige-Latenz dazu.
|
||||
|
||||
## On-Demand (2026-06-05, umgesetzt)
|
||||
|
||||
Live-FFmpeg läuft nur, solange Verbraucher da sind (Stream-Clients oder ein laufender
|
||||
Snapshot). `acquire()`/`release()` zählen Verbraucher; nach dem letzten + `IDLE_GRACE_MS`
|
||||
(15 s) Stop → **0 % idle**. `/api/snapshot` (`getFrame()`) startet die Kamera bei Bedarf
|
||||
und wartet auf ein frisches Bild (`latest` wird beim Stop genullt → kein stale Frame).
|
||||
Reconnect innerhalb der Karenz hält Live (kein Thrashing). Abschaltbar: `ON_DEMAND=false`.
|
||||
|
||||
## Verifiziert vs. offen
|
||||
|
||||
- **Lokal verifiziert (ohne Kamera):** MJPEG-Parser (Unittest, Chunk-robust, `\r\n\r\n`
|
||||
im Body), HTTP-Routing (snapshot/stream/health, 404/503), Crash-Auto-Restart rate-limitiert.
|
||||
- **FFmpeg = der bewährte go2rtc-`#video=mjpeg`-Pfad** (Re-Encode mjpeg→mjpeg, ~50% für
|
||||
2 Kameras), Ausgabe `-c:v mjpeg -q:v 5 -f mpjpeg`. **Nicht** `copy` (= 107%, s.o.).
|
||||
- **Auf der Hardware noch zu verifizieren:** CPU-Last, Latenz, HD-Blackout-Dauer, und der
|
||||
Bug-Reproweg unten.
|
||||
im Body); HTTP-Routing (snapshot/stream/health, 404/503); On-Demand-Lebenszyklus
|
||||
(acquire/release/idle-Stop/Reconnect-Karenz/getFrame/always-on) per Mock-Unittest.
|
||||
- **FFmpeg Default `copybsf`** = `-c:v copy -bsf:v mjpeg2jpeg` (Bitstream-Copy, kein
|
||||
Transcode; auf dem Host getestet). Fallback `ENCODE_MODE=mjpeg` (~50%, Re-Encode).
|
||||
- **Auf der Hardware:** CPU **69 % für 2 Kameras bestätigt** (User, copybsf). Latenz nach
|
||||
den Flags oben + Bug-Reproweg noch gegenzumessen.
|
||||
|
||||
## Hardware-Testplan
|
||||
|
||||
@@ -446,9 +469,7 @@ Ein Node-Container mit FFmpeg, Geräte durchgereicht, `group_add: video`. Env-Ov
|
||||
30 fps** (HD). ABER: trotz nativem MJPG ist `-c:v copy` auf dieser Kamera tot (107%,
|
||||
APP-Feld-Fehler) → **`-c:v mjpeg` (Re-Encode)**. (Optional `HIRES_FPS=30` verkürzt den
|
||||
Warmup leicht.)
|
||||
- **CPU unter 50% drücken?** Re-Encode 2×640@30 ≈ 50% (wie go2rtc). Hebel, falls nötig
|
||||
(zuerst auf dem Host **messen**, nicht raten): `LIVE_FPS=15` (halbiert Encode-Last) oder
|
||||
Test des Bitstream-Filters `-c:v copy -bsf:v mjpeg2jpeg` (fügt fehlende Tables ohne
|
||||
Decode hinzu — ungetestet, könnte echtes Low-CPU bringen oder auch scheitern).
|
||||
- **On-Demand Live** (FFmpeg erst bei erstem Client) wäre stromsparender, ist aber bewusst
|
||||
weggelassen — Dauerbetrieb hält die Übergabe-Logik simpel (weniger Race-Fläche).
|
||||
- **CPU:** `copybsf` (Bitstream-Copy) ist Default und liefert 69 % für 2 Kameras (gemessen).
|
||||
Weiter drücken nur falls nötig: `LIVE_FPS=15`. Fallback bei Problemen: `ENCODE_MODE=mjpeg`.
|
||||
- **On-Demand Live:** ✅ umgesetzt (s. o.) — Idle-CPU 0 statt ~60 %. Per `ON_DEMAND=false`
|
||||
abschaltbar, falls je ein dauerhaft warmer Stream gewünscht ist.
|
||||
|
||||
@@ -106,16 +106,22 @@ Encoder auf einem `/dev/videoN` sind konstruktiv ausgeschlossen.
|
||||
Crash-Auto-Restart rate-limitiert. **Auf der Hardware noch zu verifizieren:** CPU-Last,
|
||||
Latenz, HD-Blackout-Dauer, kein 106% nach Screenshot+Reconnect (Testplan in 05).
|
||||
|
||||
**FFmpeg = der bewährte go2rtc-`#video=mjpeg`-Pfad:** `-f v4l2 -input_format mjpeg
|
||||
-video_size 640x480 -framerate 30 -i … -c:v mjpeg -q:v 5 -f mpjpeg pipe:1` (Re-Encode
|
||||
mjpeg→mjpeg, ~50% für 2 Kameras).
|
||||
**FFmpeg (Default `ENCODE_MODE=copybsf`):** `-f v4l2 -input_format mjpeg -video_size
|
||||
640x480 -framerate 30 -i … -c:v copy -bsf:v mjpeg2jpeg -f mpjpeg pipe:1` — Bitstream-Copy,
|
||||
kein Transcode. Fallback `mjpeg` = Re-Encode mjpeg→mjpeg (~50%, wie go2rtc).
|
||||
|
||||
### ⚠ Regression am 2026-06-05 (eingebaut **und** korrigiert): `-c:v copy`
|
||||
### ⚠ Regression am 2026-06-05 (eingebaut → korrigiert → optimiert)
|
||||
|
||||
Erste Fassung des Schalters nutzte `-c:v copy` (Passthrough) → **CPU 100%+ und wieder
|
||||
hängendes Bild.** Ursache: `copy` ist auf dieser Kamera empirisch tot — 04/09 dokumentieren
|
||||
es (107%), FFmpeg verschluckt sich an den APP-Feldern des Kamera-MJPEG
|
||||
(`[mjpeg] unable to decode APP fields: Invalid data`) → keine validen Frames → Freeze.
|
||||
**Das war exakt der in 04/05 dokumentierte und ignorierte Punkt.** Fix: `-c:v copy` →
|
||||
`-c:v mjpeg` (Re-Encode, wie go2rtc). Lehre erneut: **erst die Doku-Fakten anwenden,
|
||||
dann bauen.**
|
||||
1. **Fehler:** Erste Schalter-Fassung nutzte plain `-c:v copy` (ohne Bitstream-Filter)
|
||||
→ **CPU 100%+ und hängendes Bild.** Das Kamera-MJPEG lässt JPEG-Tables weg, der
|
||||
mpjpeg-Muxer verschluckt sich (`[mjpeg] unable to decode APP fields`) → keine validen
|
||||
Frames. **Exakt der in 04/05 dokumentierte und von mir ignorierte Punkt.**
|
||||
2. **Sofort-Fix:** `-c:v mjpeg` (Re-Encode, ~50%, wie go2rtc) — stabil, aber nicht besser
|
||||
als der alte Stand.
|
||||
3. **Host-Messung (User, 2026-06-05):** `-c:v copy -bsf:v mjpeg2jpeg` läuft 10 s sauber
|
||||
durch (Copy bestätigt, nur einmalige Probe-Warnung). Der `mjpeg2jpeg`-Filter ergänzt
|
||||
die fehlenden Tables ohne Decode → **kein Transcode, CPU < Re-Encode, valide JPEGs.**
|
||||
→ als Default `copybsf` verdrahtet, `mjpeg` bleibt als Fallback (`ENCODE_MODE`).
|
||||
|
||||
Lehre erneut: **erst die Doku-Fakten anwenden, dann bauen** — und Optimierungen **messen**
|
||||
(Punkt 3), nicht vorhersagen.
|
||||
Reference in New Issue
Block a user