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

12 KiB
Raw Blame History

📦 Re-Render & Compress (H.264)

Status: Code implementiert (Phasen 13), 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), 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): ~25 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), gespeist aus multipart/x-mixed-replace (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 ms1 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: copybsfmpjpegmultipart<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 und 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:

{
  "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 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):

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)

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)

  • 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)

  • 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)

  • 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: 12 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 (Stoppuhr-Foto).

Phase 1 — Encode-Pfad 4. /dev/dri-Passthrough (docker-compose.yaml) + GPU-Auswahl GPU=intel|amd|none / HWENC (src/hwencode.js). 5. FFmpeg-H.264-fMP4-Profil (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 (fMP4 + Init-Segment-Cache + MJPEG-Nebenausgang für Snapshots) + src/fmp4Parser.js. 7. H.264-Stream-Route (video/mp4, Init-first, Fan-out) + Modus/mseCodec in /api/snapshot & /api/cameras (src/snapshotService.js).

Phase 3 — Client 8. MSE-<video>-Player + isTypeSupported-Feature-Detection + Snapshot-Fallback (public/viewer.js). 9. config.html/config.js: Kompressions-Auswahl (MJPEG/H.264) + encode durch 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.