Files
appRobotWebcam/doc/14_ReRender_roadmap.md
2026-06-07 12:01:39 +02:00

241 lines
10 KiB
Markdown
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
# 📦 Re-Render & Compress (H.264)
> Status: **Entwurf / noch nichts implementiert.** Alle Zahlen unten sind
> Hypothesen, bis sie auf dem Host gemessen sind (siehe Phase 0).
## Problem
MJPEG überträgt jedes Frame als vollständiges JPEG → die Bandbreite skaliert
linear mit Auflösung × Framerate × Clients. Bei höheren Auflösungen oder mehreren
gleichzeitigen Clients kann das im WAN / mobil teuer werden.
⚠️ **Wichtig – aktuelle Realität prüfen, nicht annehmen:**
Die Live-Streams laufen derzeit auf **320×240** (`liveSize` pro Kamera in
[cameras.json](../cameras.json)), **nicht** 1080p. Das `1920x1080` dort ist die
**`hiresSize`** — also das *Einzelbild* beim HD-Knopf bzw. im Snapshot-Modus, kein
Dauerstream. Die oft zitierten „~30 MBit/s" gelten also bestenfalls für einen
hypothetischen 1080p-Dauerstream, nicht fĂźr den heutigen Betrieb.
👉 Bevor hier irgendwas gebaut wird, gilt **Phase 0: messen**. Ohne belastbare
Bandbreiten-Zahl ist unklar, ob sich der ganze Umbau Ăźberhaupt lohnt.
## Ziel
Reduktion der Bandbreite durch **optionale, pro Kamera schaltbare** Neukodierung
MJPEG → H.264 (GPU), **ohne** die schlanke Default-Architektur (Node besitzt die
Kameras, MJPEG-Passthrough, `<img>`-Viewer) fĂźr den LAN-Fall aufzugeben.
Zielbitrate (Hypothese): ~2–5 MBit/s bei vergleichbarer wahrgenommener Qualität.
---
## ⚠️ Der eigentliche Knackpunkt zuerst: Wiedergabe im Browser
Das ist der Teil, der den Umbau groß macht — und der im ersten Entwurf als
„UI-Checkbox" verharmlost war.
Der aktuelle Viewer rendert den Stream in einem **`<img>`**
([public/viewer.js](../public/viewer.js)), gespeist aus
`multipart/x-mixed-replace` ([src/snapshotService.js](../src/snapshotService.js)).
Ein `<img>` kann **ausschließlich MJPEG** darstellen.
> **H.264 läuft niemals in einem `<img>`.** „Der Browser soll mit beidem umgehen"
> ist daher kein Schalter, sondern ein **zweiter, vollständiger Wiedergabe-Pfad.**
H.264 + MJPEG schließen sich auch im Transport gegenseitig aus — H.264 lässt sich
nicht in MJPEG-multipart verpacken. Es braucht einen eigenen Container und einen
eigenen Player. Optionen:
| Transport | Client | Aufwand | Latenz (Erwartung, zu messen) |
|-----------|--------|---------|-------------------------------|
| **MSE (fMP4)** | `<video>` + `MediaSource` + JS-Feeder | mittel | gut, mit Low-Latency-Tuning; sonst 200 ms–1 s Puffer |
| **WebRTC** | `RTCPeerConnection` + Signaling | hoch | am niedrigsten |
| HLS/DASH | `<video>` / hls.js | gering | Sekunden — für Live untauglich |
**Achtung – Déjà-vu:** WebRTC + H.264 ist genau das, was go2rtc gemacht hat und
was bewusst entfernt wurde (siehe Architektur-Doku). WebRTC wĂźrde Signaling-
Infrastruktur wieder einführen. **Empfehlung: MSE-fMP4**, weil es die „Node besitzt
die Kameras"-Architektur erhält (Node → ffmpeg → Byte-Stream → Browser) und keine
ICE/STUN/TURN-Maschinerie braucht. EndgĂźltige Wahl erst nach der Latenz-Messung
(Phase 0).
### MSE-Besonderheit: Init-Segment fßr späte Clients
Anders als bei MJPEG (jedes Frame eigenständig) muss bei fragmentiertem MP4 ein
Client, der **mitten im Stream** dazukommt, zuerst das **Init-Segment**
(`ftyp`+`moov`) bekommen, dann die Media-Fragmente. Der Server muss das
Init-Segment also **zwischenspeichern** und jedem neuen Client zuerst schicken,
bevor er ihn in den Fan-out hängt. Das ist neue Logik gegenßber dem heutigen
„jedes Frame an jeden"-Modell.
---
## LĂśsungsansatz: zwei Modi pro Kamera
### 1. 🟢 MJPEG-Passthrough (Default, unverändert)
- bestehender Pfad: `copybsf` → `mpjpeg` → `multipart` → `<img>`
- minimale Latenz, keine GPU-Abhängigkeit, ~5 % idle-CPU
- ideal im LAN / bei wenigen Clients
### 2. 🔵 H.264 (optional, GPU)
- MJPEG → H.264 (VAAPI/QSV) → fMP4 → MSE-`<video>`
- drastisch reduzierte Bandbreite, fĂźr mobil / WAN / viele Clients
- hÜhere Komplexität + GPU-Abhängigkeit + (zu messende) Zusatzlatenz
Der Browser wählt **pro Kamera** anhand der Server-Metadaten den richtigen Player
(`<img>` oder `<video>`).
---
## Architektur-Entscheidung
- Encoding erfolgt **direkt in Node via FFmpeg + GPU** (kein go2rtc mehr).
- Kamera liefert weiterhin MJPEG; der `CameraSwitch` bleibt einziger Geräte-Öffner.
- Der Modus hängt am **vorhandenen `encode`-Feld** (siehe Konfigurationsmodell).
```
Kamera (MJPEG, v4l2)
│
┌─┴─────────────────────────── encode = 'copybsf' | 'mjpeg'
│ ffmpeg -c:v copy -bsf mjpeg2jpeg -f mpjpeg
│ → multipart/x-mixed-replace → <img> (heutiger Pfad, unverändert)
│
└───────────────────────────── encode = 'h264'
ffmpeg -c:v h264_vaapi -f mp4 (fragmentiert)
→ Byte-Stream (+ gecachtes Init-Segment) → MSE → <video> (neu)
```
---
## Konfigurationsmodell
**Kein neues `compress`-Flag** — das würde sich mit dem bestehenden Encode-Schalter
Ăźberschneiden. Stattdessen das vorhandene `encode`-Feld erweitern, das in
[server.js](../server.js) und [src/cameraSwitch.js](../src/cameraSwitch.js) bereits
pro Kamera verdrahtet ist:
| `encode` | Bedeutung |
|----------|-----------|
| `copybsf` | Default, Bitstream-Copy, niedrigste CPU (heute) |
| `mjpeg` | Re-Encode MJPEG→MJPEG, Fallback (heute) |
| `h264` | **neu:** GPU-H.264 → fMP4 (VAAPI/QSV, Auto-Erkennung) |
Beispiel `cameras.json`:
```json
{
"id": "cam2",
"device": "/dev/video4",
"stream": true,
"encode": "h264",
"liveSize": "640x480"
}
```
`hiresEncode` bleibt davon unberührt (HD-Snapshot bleibt JPEG — sinnvoll, da ein
Einzelbild bandbreiten-unkritisch ist).
---
## Technische Integration (was wirklich zu tun ist)
### 1. GPU in den Container durchreichen
- `/dev/dri` ins Docker-`devices` (wie in [doc/02_HardwareEncoding.md](02_HardwareEncoding.md) fßr die Intel-Box bestätigt: `/dev/dri/renderD128` vorhanden).
- Encoder dynamisch wählen (`h264_vaapi` vs. `h264_qsv`) statt Hardcoding.
### 2. FFmpeg-H.264-Profil (Node spawnt direkt — `#hardware` von go2rtc gibt es nicht mehr)
Skizze (VAAPI, Werte in Phase 1 zu tunen/messen):
```bash
ffmpeg -fflags nobuffer \
-f v4l2 -input_format mjpeg -video_size 640x480 -framerate 30 -i /dev/video4 \
-vaapi_device /dev/dri/renderD128 -vf 'format=nv12,hwupload' \
-c:v h264_vaapi -b:v 3M -g 60 \
-f mp4 -movflags +frag_keyframe+empty_moov+default_base_moof -frag_duration 100000 \
pipe:1
```
- MJPEG-**Decode** bleibt vorerst CPU (USB-MJPEG via VAAPI dekodieren ist wackelig)
→ die „nur GPU"-Erwartung in Phase 0/1 **messen**, nicht annehmen.
- Kurze GOP (`-g`) + `frag_duration` klein = niedrige Latenz, mehr Overhead → Trade-off messen.
### 3. `CameraSwitch` erweitern ([src/cameraSwitch.js](../src/cameraSwitch.js))
Nicht „nur neue Args" — betroffen sind mehrere Stellen:
- `videoOutArgs()` um den `h264`-Zweig erweitern (anderer Muxer als `-f mpjpeg`).
- Der `MpjpegParser` ist MJPEG-spezifisch und greift hier **nicht**; fĂźr fMP4 wird
der Byte-Stream durchgereicht (Init-Segment cachen, Media-Fragmente fan-out).
- On-Demand / idle-Stop / Auto-Restart gelten weiter — die Pipeline startet wie
heute erst bei Verbrauchern.
### 4. Neue Stream-Route ([src/snapshotService.js](../src/snapshotService.js))
- `createStreamRouter` sendet heute `multipart/x-mixed-replace`. FĂźr H.264 braucht
es eine Variante (oder zweite Route), die `video/mp4` als fortlaufenden
Byte-Stream liefert und neuen Clients zuerst das Init-Segment schickt.
- `/api/cameras` muss den Modus (`mjpeg`|`h264`) mitliefern, damit der Viewer den
Player wählen kann.
### 5. Viewer erweitern ([public/viewer.js](../public/viewer.js))
- Bei `encode==='h264'`: `<video>` + `MediaSource` + SourceBuffer-Feeder statt `<img>`.
- **Auto-Fallback statt schwarzem Bild:** client-seitig
`MediaSource.isTypeSupported('video/mp4; codecs="avc1.42E01E"')` prĂźfen; bei
fehlender UnterstĂźtzung sichtbare Meldung + automatischer RĂźckfall auf MJPEG,
nicht stilles Schwarz.
### 6. UI ([public/config.html](../public/config.html))
- Statt Checkbox „Compress": Dropdown/Select für `encode` (copybsf / mjpeg / h264).
---
## Hardware-Bewertung (Erwartung — in Phase 0/1 zu bestätigen)
### 🖥️ Intel UHD 630 (Coffee Lake) — die heutige Box
- VAAPI / Quick Sync (H.264/H.265), `/dev/dri/renderD128` bestätigt.
- Erwartung: 1–2 H.264-Streams stabil, niedrige CPU bei GPU-Encode.
### 🖥️ AMD Radeon 680M (Rembrandt) — falls Zielhardware
- VAAPI / VCN 3.x; erwartet deutlich mehr Encode-Reserve.
- ⚠️ **Erst prüfen, ob diese Box überhaupt Ziel ist** und ob `/dev/dri` + VAAPI dort
laufen — die bisherige Doku basiert auf der Intel-Box.
---
## Design-Prinzipien
- ✅ **Backward-compatible** — Default bleibt MJPEG, nichts ändert sich für LAN.
- ✅ **Pro Kamera schaltbar** über das vorhandene `encode`-Feld.
- ✅ **Node behält die Kameras** — kein go2rtc/WebRTC-Rückbau.
- ⚠️ **Low latency** nur, wenn die Messung es bestätigt (MSE puffert).
- ⚠️ **GPU optional** — H.264-Kameras hängen an funktionierendem VAAPI/QSV.
---
## Offene Entscheidungen
1. **Lohnt es sich überhaupt?** → Phase 0 (Bandbreite der realen Live-Streams messen).
2. **Transport: MSE-fMP4 (empfohlen) oder WebRTC?** → entscheidet Latenz + Aufwand,
abhängig von Phase-0-Messung.
3. **Decode auf CPU oder GPU?** → messen, ob USB-MJPEG-VAAPI-Decode stabil ist.
4. **Zielhardware Intel-Box oder AMD 680M?**
---
## Nächste Schritte (ToDo — ehrlicher Stand: nichts erledigt)
**Phase 0 — Messen (Vorbedingung, blockiert alles andere)**
1. 🔲 Bandbreite der heutigen Live-Streams (320×240 bzw. konfigurierte `liveSize`) auf dem Host messen, bei 1 und bei n Clients.
2. 🔲 Hypothese 1080p-Stream gegen die echte Zielauflösung abgleichen — lohnt der Umbau?
3. 🔲 Transport-Latenz-Test: ein Test-fMP4-Stream (MSE) vs. heutiges MJPEG, Methode wie in [doc/03_Protocoll_roadmap.md](03_Protocoll_roadmap.md) (Stoppuhr-Foto).
**Phase 1 — Encode-Pfad (nur wenn Phase 0 positiv)**
4. 🔲 `/dev/dri`-Passthrough + VAAPI/QSV-Auto-Erkennung.
5. 🔲 FFmpeg-H.264-fMP4-Profil definieren; CPU/GPU/Latenz auf dem Host messen.
**Phase 2 — Server**
6. 🔲 `encode='h264'` in `videoOutArgs` / `CameraSwitch` (inkl. Init-Segment-Cache + Byte-Stream-Fan-out).
7. 🔲 H.264-Stream-Route + Modus in `/api/cameras`.
**Phase 3 — Client**
8. 🔲 MSE-`<video>`-Player + Feature-Detection + Auto-Fallback auf MJPEG.
9. 🔲 `config.html`: `encode`-Auswahl.
**Phase 4 — Verifikation**
10. 🔲 Bandbreite & CPU/GPU vorher/nachher vergleichen (gemessen, auf dem Host).