Files
appRobotWebcam/doc/14_ReRender_roadmap.md
2026-06-07 17:13:34 +02:00

247 lines
12 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: **Code implementiert (Phasen 1–3), unit-getestet.** Noch NICHT auf dem
> Host verifiziert — die FFmpeg-VAAPI/QSV-Pipeline lief bisher nur in Tests, nicht
> gegen echte Kameras/GPU. Alle Bitraten/Latenz-Zahlen bleiben Hypothesen bis zur
> Messung auf der Intel- bzw. AMD-Box (Phase 0 + 4).
## 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)
Legende: ✅ Code fertig & unit-getestet · đŸ§Ș nur auf dem Host verifizierbar · đŸ”Č offen
**Phase 0 — Messen (Vorbedingung; auf User-Wunsch parallel zur Implementierung)**
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: H.264/MSE vs. heutiges MJPEG, Methode wie in [doc/03_Protocoll_roadmap.md](03_Protocoll_roadmap.md) (Stoppuhr-Foto).
**Phase 1 — Encode-Pfad**
4. ✅ `/dev/dri`-Passthrough ([docker-compose.yaml](../docker-compose.yaml)) + GPU-Auswahl `GPU=intel|amd|none` / `HWENC` ([src/hwencode.js](../src/hwencode.js)).
5. ✅ FFmpeg-H.264-fMP4-Profil ([src/hwencode.js](../src/hwencode.js)). · đŸ§Ș CPU/GPU/Latenz/Bitrate auf Intel- UND AMD-Box messen + Profil/Level ggf. anpassen.
**Phase 2 — Server**
6. ✅ `encode='h264'` in [src/cameraSwitch.js](../src/cameraSwitch.js) (fMP4 + Init-Segment-Cache + MJPEG-Nebenausgang fĂŒr Snapshots) + [src/fmp4Parser.js](../src/fmp4Parser.js).
7. ✅ H.264-Stream-Route (`video/mp4`, Init-first, Fan-out) + Modus/`mseCodec` in `/api/snapshot` & `/api/cameras` ([src/snapshotService.js](../src/snapshotService.js)).
**Phase 3 — Client**
8. ✅ MSE-`<video>`-Player + `isTypeSupported`-Feature-Detection + Snapshot-Fallback ([public/viewer.js](../public/viewer.js)).
9. ✅ `config.html`/`config.js`: Kompressions-Auswahl (MJPEG/H.264) + `encode` durch [src/configService.js](../src/configService.js).
**Phase 4 — Verifikation (auf dem Host, mit echten Kameras + GPU)**
10. đŸ§Ș Eine Kamera testweise auf `encode='h264'` (UI oder `GPU=intel`/`amd` setzen), Bild im Browser prĂŒfen, Codec-Log (`h264_vaapi`/`h264_qsv`) kontrollieren.
11. đŸ§Ș Bandbreite & CPU/GPU MJPEG vs. H.264 vergleichen; Profil (`H264_PROFILE`/`H264_MSE_CODEC`), GOP & Bitrate nachjustieren.
12. đŸ§Ș AMD-Box gegenprĂŒfen (`GPU=amd`), falls sie Zielhardware ist.