247 lines
12 KiB
Markdown
247 lines
12 KiB
Markdown
# đŠ 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.
|