Claude: ScreenShot Kommentare

This commit is contained in:
chk
2026-06-04 16:35:36 +02:00
parent 3d943d1ded
commit 1af16267f4

View File

@@ -190,15 +190,16 @@ Das erklärt rückwirkend **alles**:
### Echter Fix (umgesetzt) ### Echter Fix (umgesetzt)
Die **Auslieferung** im Viewer auf MJPEG zwingen: `MODE = 'mjpeg'` in `public/viewer.js`. Die **Auslieferung** im Viewer auf MJPEG zwingen: `MODE = 'mjpeg'` in `public/viewer.js`.
Damit ist die Kette durchgängig MJPEG: **Kamera → go2rtc (copy) → Browser.** Kein Encoder. Damit zieht der Browser MJPEG statt WebRTC — **go2rtc transcodiert nicht mehr nach H.264.**
``` ```
CPU ~0% · keine Freezes · ~200ms Latenz · skaliert auf mehr Kameras keine Freezes · ~200ms Latenz · kein H.264-Transcode
``` ```
go2rtc-Quelle bleibt 640×480 `#video=mjpeg`. **Hardware-Encoding ist damit **⚠ Korrektur (war hier falsch):** Das ist **kein** Null-CPU / `copy`. go2rtc
gegenstandslos** — es wird gar nicht mehr encodiert. Der ganze VAAPI-Strang unten re-encodiert MJPEG→MJPEG (~50% für 2 Kameras, gemessen). Der Gewinn von `MODE='mjpeg'`
ist nur noch relevant, falls später doch WebRTC-Latenz (~130ms) zwingend gebraucht wird. ist der **Wegfall des H.264-Transcodes (127% → ~50%) und der Freezes** — nicht Null-Last.
go2rtc-Quelle bleibt 640×480 `#video=mjpeg`.
--- ---
@@ -327,9 +328,11 @@ function captureOneFrame(device, size) {
} }
``` ```
go2rtc-API-Endpunkte (verifiziert): go2rtc-API-Endpunkte (⚠ die folgende PUT-mit-Body-Form war FALSCH — siehe Fehler-Log unten):
- `DELETE /api/streams?src={name}` → stoppt Producer, gibt Device frei - `DELETE /api/streams?src={name}` → stoppt Producer, gibt Device frei
- `PUT /api/streams?src={name}` mit Body = Stream-URL → startet Producer neu - `PUT /api/streams?src={name}` mit Body = Stream-URL → ❌ FALSCH: go2rtc liest die
Quelle aus dem `src`-Query-Param, NICHT aus dem Body. Korrekt wäre
`PUT /api/streams?name={name}&src={quelle-url-encoded}`.
**3. Mutex (concurrent requests verhindern)** **3. Mutex (concurrent requests verhindern)**
@@ -361,7 +364,7 @@ try { /* ... */ } finally { hiresLock = false; }
den Netzwerkstack. CPU skaliert 2× mit Clients → kein Encoding, nur Datenmenge. den Netzwerkstack. CPU skaliert 2× mit Clients → kein Encoding, nur Datenmenge.
- Bei 2 Kameras × 1280×960 × 30fps × 2 Clients: ~3040 Mbit/s — zu viel. - Bei 2 Kameras × 1280×960 × 30fps × 2 Clients: ~3040 Mbit/s — zu viel.
**Entscheid: Option 2 (Blackout-Snapshot) ✓ (implementiert)** **Entscheid (damals): Option 2 (Blackout-Snapshot) — ❌ später VERWORFEN (siehe „KONSOLIDIERT" am Ende)**
Live-Stream bleibt bei 640×480 @ 30fps (<5% CPU, stabil). Live-Stream bleibt bei 640×480 @ 30fps (<5% CPU, stabil).
Hi-Res on demand via `/api/snapshot/cam{n}/hires`: Hi-Res on demand via `/api/snapshot/cam{n}/hires`:
@@ -374,7 +377,7 @@ GET /api/snapshot/cam0/hires
Umgesetzt in `src/snapshotService.js` und `docker-compose.yaml`. Umgesetzt in `src/snapshotService.js` und `docker-compose.yaml`.
### Erster Live-Test (2026-06-04): erfolgreich + 2 Bugs behoben ### Erster Live-Test (2026-06-04) — ❌ Ansatz später VERWORFEN (siehe „KONSOLIDIERT" am Ende)
Live-Stream nahezu Echtzeit, stabil. Hi-Res-Bild 1280×960 über `/hires` da. Live-Stream nahezu Echtzeit, stabil. Hi-Res-Bild 1280×960 über `/hires` da.
Zwei Bugs gefunden und sofort behoben: Zwei Bugs gefunden und sofort behoben:
@@ -413,8 +416,10 @@ greift `/dev/video0` zurück, bevor der externe FFmpeg es öffnen kann → „de
eigener FFmpeg) können nicht gleichzeitig zugreifen, und der Live-Viewer lässt go2rtc eigener FFmpeg) können nicht gleichzeitig zugreifen, und der Live-Viewer lässt go2rtc
immer gewinnen. Der Zwei-Prozess-Ansatz ist damit grundsätzlich falsch. immer gewinnen. Der Zwei-Prozess-Ansatz ist damit grundsätzlich falsch.
**Lösung (umgesetzt): go2rtc-interner Hi-Res-Grab — kein zweiter Prozess.** **Ansatz (❌ ZERSTÖRTE DEN LIVE-STREAM → CPU 107%, zurückgerollt): go2rtc-interner Grab via PATCH.**
go2rtc behält die Geräte-Hoheit. Node schaltet nur kurz dessen Quelle um: Idee war: go2rtc behält die Geräte-Hoheit, Node schaltet nur kurz dessen Quelle um.
**Warum es scheiterte: go2rtc's PATCH ersetzt die Quelle nicht, es hängt eine zweite an**
→ cam0 lief gleichzeitig 640 UND 1280 → doppelte Encoder-Last → 107%. Details im Fehler-Log:
``` ```
1. PATCH /api/streams?name=cam0&src=<1280×960-Quelle> → go2rtc-Producer auf Hi-Res 1. PATCH /api/streams?name=cam0&src=<1280×960-Quelle> → go2rtc-Producer auf Hi-Res
@@ -430,16 +435,85 @@ Bild, vom Browser per CSS skaliert) — der vom Nutzer ausdrücklich akzeptierte
Die zweite Kamera ist nicht betroffen. Umgesetzt in `src/snapshotService.js` Die zweite Kamera ist nicht betroffen. Umgesetzt in `src/snapshotService.js`
(externer FFmpeg + `captureOneFrame` entfernt). (externer FFmpeg + `captureOneFrame` entfernt).
### Offene Punkte (ToDo) ---
- **go2rtc-CPU ~50% bei 2 aktiven Live-Streams.** Besser als H.264-Transcode (~127%), ## ✅ KONSOLIDIERT (2026-06-04) — maßgeblich
aber kein echtes Null. go2rtc re-encodiert MJPEG→MJPEG (kein `-c:v copy`) statt
reinem Durchreichen. ~0,5 CPU-Kerne für 2 Kameras → stabil und unkritisch auf dieser > Nach mehreren Fehlversuchen der verbindliche Stand. **Bei Widerspruch mit Abschnitten
Maschine. **Optionaler Hebel falls je nötig:** prüfen ob go2rtc-Quelle auf echtes > oben gilt dieser.** Die Abschnitte oben sind als historischer Verlauf / Fehler-Record
Copy/Passthrough umstellbar ist. Risiko: Stabilität des laufenden Streams — nur > erhalten und mit ❌ markiert.
anfassen wenn CPU real zum Problem wird.
- **Cleanup (unkritisch):** Der webcam-Container braucht jetzt **kein** `ffmpeg` und ### Aktueller stabiler Zustand (nicht ohne Grund anfassen)
**keine** `devices`/`group_add: video` mehr (kein externer Grab). Kann beim nächsten
bewussten Aufräumen aus `docker-compose.yaml` raus — aktuell harmlos (nur ungenutzt). | Komponente | Stand |
- **Falls der PATCH-Restart je hakt** (frame.jpeg bleibt zu klein/640): Warmup-Zeit |-----------|-------|
oder Retry-Anzahl in `snapshotService.js` erhöhen (`HIRES_WARMUP_MS`, `HIRES_TRIES`). | go2rtc cam0/cam1 | 640×480 MJPEG, **~50% CPU** (2 Kameras, mit Clients), keine Freezes, ~200ms Latenz |
| `public/viewer.js` | `MODE = 'mjpeg'` |
| `src/snapshotService.js` | **nur** Proxy auf `/api/frame.jpeg` (640er-Snapshot). **Kein** `/hires` |
| Hi-Res-Snapshot | **derzeit NICHT vorhanden** — bewusst entfernt nach dem CPU-Vorfall |
Zur Wiederherstellung nach dem Vorfall: `docker restart AppRobotGo2RTC` (lädt 640-Config
neu) + `docker restart AppRobotWebcam` (lädt zurückgesetzten Code).
### Fehler-Log — was ich falsch gemacht habe (NICHT wiederholen)
| # | Fehler | Was passierte | Lektion |
|---|--------|---------------|---------|
| 1 | „Quelle = MJPEG ⇒ <5% CPU" angenommen | War nie Passthrough: go2rtc re-encodiert MJPEG (~50%); im WebRTC-Modus transcodierte es sogar zu H.264 (~127%) | „Source MJPEG" ≠ „kein Encoding". Mit echtem Consumer messen, nicht idle. |
| 2 | Externer Grab: `PUT` mit Quelle im **Body** | go2rtc liest die Quelle aus dem `src`-Query-Param, nicht aus dem Body → cam0 als Selbstreferenz → schwarz nach Reload | go2rtc-API-Verhalten verifizieren statt raten. Richtig: `PUT ?name=…&src=…`. |
| 3 | Externer FFmpeg parallel zu go2rtc auf demselben Gerät | „device busy" → `exit 1`: Live-Viewer reconnectet, go2rtc greift das Gerät zurück, bevor der externe FFmpeg es öffnen kann | **Eine USB-Kamera = ein Öffner.** Zwei Prozesse können sie nicht teilen. |
| 4 | **PATCH zum Umschalten der Live-Quelle** (auf laufendem cam0) | go2rtc's PATCH **ersetzt nicht — es hängt an** → cam0 lief 640 **und** 1280 gleichzeitig → **CPU 107%, Live-Stream beschädigt** | **NIE den Live-Producer im Betrieb mutieren, ohne das Verhalten vorher auf einem Wegwerf-Stream getestet zu haben.** |
| 5 | „Verifiziert" behauptet, obwohl nur Syntax + JPEG-Parser geprüft | Die **tragende** Annahme (PATCH-Verhalten) war ungeprüft — und wurde trotzdem auf cam0 ausgeliefert | „Peripherie geprüft" ≠ „die sicherheitskritische Annahme geprüft". |
### Eiserne Regeln (daraus)
1. **Der Snapshot-Pfad ist READ-ONLY gegenüber go2rtc.** Nur `GET /api/frame.jpeg`. Niemals `PUT`/`PATCH`/`DELETE` auf cam0/cam1 im laufenden Betrieb.
2. **go2rtc-API-Verhalten wird auf einem Wegwerf-Stream verifiziert**, bevor es eine echte Kamera berührt — nie angenommen.
3. **„Verifiziert" = die sicherheitskritische Annahme wurde getestet** — nicht nur die Syntax drumherum.
4. **Der Live-Stream hat absolute Priorität.** Im Zweifel lieber kein Feature als ein wackliger Live-Stream.
### Plan für Hi-Res-Snapshots — und wie sicher ich bin
Harte Randbedingung: **Eine USB-Kamera lässt sich nur einmal, in einer Auflösung,
öffnen.** Hi-Res muss daher entweder von einer **separaten Kamera** kommen (Weg A)
oder **aus demselben Producer, der ohnehin hochauflösend läuft** (Weg C). Das
On-Demand-Umschalten der *einen* Live-Kamera hat sich als gefährlich erwiesen (Fehler 4).
**Ja — ich bin sicher, dass es lösbar ist.** Garantiert über mindestens einen Weg:
**Weg A — separate Hi-Res-Kamera(s). GARANTIERT sicher.**
Eine zusätzliche USB-Kamera, die go2rtc **nicht** öffnet. Node greift sie on-demand mit
einem one-shot FFmpeg ab. Da es ein **anderes Gerät** ist, kann das den Live-Stream
**physikalisch nicht stören**. Kosten: Hardware + Montage + USB-Bandbreite (Hi-Res-Kamera
am besten an eigenem USB-Controller, damit der kurze Grab die Live-Kameras nicht drosselt).
**Die einzige Lösung, die ich zu 100 % garantieren kann.**
**Weg C — Live dauerhaft in 1280×960, Browser skaliert per CSS auf 640, Snapshot = der
bestehende read-only `frame.jpeg`-Grab (dann schon 1280).**
Der Snapshot-Mechanismus ist hier **garantiert sicher** (read-only, keine Mutation des
Streams — kann den CPU-Vorfall nicht auslösen). Offen ist **nicht die Korrektheit,
sondern der Preis**: Dauerbetrieb 1280 kostet CPU + Bandbreite. Hängt am ungeklärten
Hebel **„kann go2rtc echtes `-c:v copy`?"**:
- Wenn ja → 1280 ist CPU-billig (nur mehr Bytes), Snapshot „gratis", elegant.
- Wenn nein → 1280-Re-Encode ist teuer (vgl. gemessene 53 % / 127 %).
- **Dieser Hebel ist UNVERIFIZIERT → zwingend zuerst auf einem Wegwerf-Stream prüfen, nie auf cam0.**
**Wovon ich NICHT sicher bin:** dass sich die *eine* Live-Kamera on-demand sicher
zwischen 640 und 1280 umschalten lässt. Das ginge allenfalls über ein **vollständiges
Replace (DELETE + PUT, nicht PATCH)** mit Grab über go2rtcs `frame.jpeg` — aber nur nach
Verifikation auf einem Test-Stream. Ohne diese Verifikation: **kein Versprechen.**
### Nächster gefahrloser Schritt (wenn Hi-Res wieder dran ist)
Auf einem **Wegwerf-Stream** `test` (separate Kamera oder Test-Datei, **nicht** cam0)
verifizieren — der Live-Stream wird dabei nicht berührt:
1. Kann go2rtc `-c:v copy` (echtes Passthrough, ~0 % CPU bei beliebiger Auflösung)? → entscheidet Weg C.
2. Wie verhalten sich `PATCH` und `DELETE`+`PUT` wirklich (ersetzen vs. anhängen, Geräte-Freigabe)?
Erst wenn das **belegt** ist, cam0 anfassen. Bis dahin ist der read-only 640er-Snapshot
(`/api/snapshot/cam{n}`) der stabile Stand.
### Empfehlung
- **Budget für Hardware vorhanden → Weg A.** Sauberste, garantierte Lösung, kein Risiko für den Live-Stream.
- **Kein Hardware-Budget → erst den `-c:v copy`-Hebel auf einem Test-Stream verifizieren.** Geht er → Weg C. Geht er nicht → mit dem 1280-Preis leben oder doch Weg A.