Files
appRobotWebcam/doc/09_Bug_reports.md
2026-06-05 06:48:40 +02:00

121 lines
6.1 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.
## Multi-User ##
Wenn zwei (oder mehr) User streamen, dann kann kein High-Res Bild mehr
gemacht werden. Das Problem: Der Stream bleibt irgendwo aufrecht erhalten.
Fix-Vorschlag Option 1: Alle Browser erhalten bei einem High-Res Request
die Anweisung, den Stream abzumelden.
Fix-Vorschlag Option 2: Der Stream wird umgeleitet zu einer Zwischenstelle "Schalter" und erst von dort aus an die Browser verteilt. Dann wird die
"unterbrechung" bzw. "umleitung" des Streams serverseitig erledigt.
## BugReport 106% ##
Anmelden abmelden funktioniert. Es bleibt bei 35% Prozessor-Last
Anmelden Screenshot funktioniert. Es bleibt bei 35% Prozessor-Last
Anmelden Screenshot neu anmelden > Es gibt 108% Prozessor-Last und ein Stream friert ein.
Wenn ich den Container restarte, funktioniert es.
siehe auch doc/04_Delay_roadmap.md sowie doc/05_screenshot_roadmap.md
### Root Cause (2026-06-05)
**Race condition:** `cam_hires` und `cam` belegen kurz gleichzeitig dasselbe `/dev/videoN`.
```
1. stopStream(cam0) → 0 Consumer → go2rtc stoppt cam0-FFmpeg → /dev/video0 frei
2. Server: frame.jpeg → go2rtc startet cam0_hires-FFmpeg auf /dev/video0 (1280×960)
3. Response fertig → go2rtc sendet SIGTERM an cam0_hires-FFmpeg
4. Server antwortet Client (OHNE auf FFmpeg-Exit zu warten!) ← Fehler
5. Client sleep(600ms) → startStream(cam0) → go2rtc startet cam0-FFmpeg auf /dev/video0
⚠ cam0_hires-FFmpeg hält /dev/video0 noch offen → 2 FFmpeg auf 1 Device → 108%
```
Der Konflikt in Schritt 5 versetzt `cam0_hires` in einen Fehlerzustand in go2rtc.
Beim nächsten Reconnect ("neu anmelden") startet go2rtc's Retry `cam0_hires` erneut —
gleichzeitig mit `cam0` → wieder 108% + Stream friert ein.
### ❌ Fix-Versuch 1 (2026-06-05) GESCHEITERT — „Warten bis cam_hires gestoppt"
Idee war: Server pollt nach dem frame.jpeg-Fetch, bis `cam_hires`-Producer nicht mehr
`state=='running'` ist (+400ms Puffer), erst dann Antwort an Client.
**Der Log beweist: der Poll ist ein No-op.**
```
[hires][cam1] Versuch 1: 90586 bytes, Breite=1280
[hires][cam1] cam_hires gestoppt nach 1ms Gerät frei ← bricht SOFORT ab
[hires][cam0] cam_hires gestoppt nach 1ms Gerät frei
```
go2rtc meldet den hires-Producer schon **1ms** nach dem Frame als „nicht running" —
obwohl der FFmpeg gerade eben noch ein 1280-Bild geliefert hat. Die tragende Annahme
(*API-State `running` ⇒ FFmpeg hält das Device*) ist **falsch**. Übrig bleibt nur das
blinde `sleep(400)`. Das ist exakt der Fehler-Typ aus 04 (#5/#7): „verifiziert"
behauptet, aber die sicherheitskritische Annahme war ungemessen.
**Folge: jetzt löst schon EIN Screenshot 106% aus** (vorher erst beim Reconnect). Erklärung:
Es war immer ein **Race auf dem geteilten `/dev/videoN`**. Die 400ms haben das Timing nur
verschoben — jetzt landen wir auf der schlechten Seite. **Timing-Pflaster verschieben das
Race, sie beseitigen es nicht.** → zurückgerollt (Schritt 3 wieder entfernt).
### Eigentliches Problem (eine Ebene tiefer)
`cam` (640) und `cam_hires` (1280) zeigen auf **dasselbe physische `/dev/videoN`**.
Eine USB-Kamera = ein Öffner (eiserne Regel 04). 106% = **zwei Encoder gleichzeitig** auf
einem Device (bestätigt: nicht „device busy → exit", sondern beide laufen).
**Kernhürde:** go2rtc's REST-API kann **nicht** zuverlässig sagen, wann FFmpeg den
Device-FD wirklich freigegeben hat. Die State-Felder flippen, bevor der Kernel-FD frei
ist (beim Start zeigte der Monitor sogar `cam1_hires producer state="unknown"`). Damit
ist **jede Timing-basierte Übergabe ein Ratespiel**.
Der **Hinweg** (Schritt 1: warten bis `cam` frei, bevor `cam_hires` startet) funktioniert
— er wartet echt (4870ms / 5069ms im Log). Kaputt ist der **Rückweg** (`cam_hires``cam`):
dort wird nicht zuverlässig gewartet.
### ✅ GELÖST (2026-06-05) — Node-MJPEG-Schalter, go2rtc entfernt
Statt go2rtc zu orchestrieren (blind, racet weiter) **besitzt Node die Kameras jetzt
selbst**. Damit liefert das `close`-Event des selbst gestarteten FFmpeg den harten Beweis
„Gerät frei" — genau das Signal, das go2rtcs API verweigerte. Das Race ist **eliminiert,
nicht getimt**. Details der finalen Architektur: `doc/05_screenshot_roadmap.md`.
**Was sich änderte:**
| Komponente | vorher (go2rtc) | jetzt (Node-Schalter) |
|-----------|-----------------|----------------------|
| Geräte-Öffner | go2rtc | **Node** (`src/cameraSwitch.js`, eine Instanz pro Gerät) |
| Live-Auslieferung | go2rtc WS + `video-stream.js` | MJPEG `multipart/x-mixed-replace``<img>` |
| HD-Snapshot | 2. go2rtc-Stream `cam_hires` (Race!) | Schalter stoppt Live (Prozess-`close` = FD frei), greift 1280, zurück |
| Multi-User | brach (Consumer ≠ 0) | **gelöst**: ein FFmpeg → Fan-out an alle, Clients halten kein Gerät |
| go2rtc | nötig | **entfernt** |
**Warum 106% jetzt nicht mehr auftritt:** Pro Gerät hält der Schalter immer nur **einen**
FFmpeg. Übergang Live→HD und HD→Live wird über das `close`-Event synchronisiert — zwei
Encoder auf einem `/dev/videoN` sind konstruktiv ausgeschlossen.
**Verifiziert (lokal, ohne Kamera):** MJPEG-Parser (Content-Length-basiert, Chunk-robust,
`\r\n\r\n` im Body) per Unittest; HTTP-Routing (snapshot/stream/health, 404/503-Pfade);
Crash-Auto-Restart rate-limitiert. **Auf der Hardware noch zu verifizieren:** CPU-Last,
Latenz, HD-Blackout-Dauer, kein 106% nach Screenshot+Reconnect (Testplan in 05).
**FFmpeg = der bewährte go2rtc-`#video=mjpeg`-Pfad:** `-f v4l2 -input_format mjpeg
-video_size 640x480 -framerate 30 -i … -c:v mjpeg -q:v 5 -f mpjpeg pipe:1` (Re-Encode
mjpeg→mjpeg, ~50% für 2 Kameras).
### ⚠ Regression am 2026-06-05 (eingebaut **und** korrigiert): `-c:v copy`
Erste Fassung des Schalters nutzte `-c:v copy` (Passthrough) → **CPU 100%+ und wieder
hängendes Bild.** Ursache: `copy` ist auf dieser Kamera empirisch tot — 04/09 dokumentieren
es (107%), FFmpeg verschluckt sich an den APP-Feldern des Kamera-MJPEG
(`[mjpeg] unable to decode APP fields: Invalid data`) → keine validen Frames → Freeze.
**Das war exakt der in 04/05 dokumentierte und ignorierte Punkt.** Fix: `-c:v copy`
`-c:v mjpeg` (Re-Encode, wie go2rtc). Lehre erneut: **erst die Doku-Fakten anwenden,
dann bauen.**