6.5 KiB
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.
Lösungsvorschläge (geordnet nach Robustheit)
A — Separate Hi-Res-Kamera (Weg A aus 04). GARANTIERT. Zusätzliche USB-Kamera, die go2rtc nicht öffnet; Node grabbt sie on-demand per one-shot FFmpeg. Anderes Device → kein Race möglich. Kostet Hardware. Einzige 100%-Lösung.
B — Feature streichen, zurück auf KONSOLIDIERT (04). GARANTIERT.
cam0_hires/cam1_hires aus docker-compose, /hires aus snapshotService, HD-Button
aus dem Viewer. Nur noch 640er-Snapshot (read-only frame.jpeg). Stabil, kein Hi-Res.
C — No-Hardware-Versuch: Rückweg robust machen. MITTLERE Sicherheit, MUSS gemessen werden.
Statt auf state zu pollen: warten bis go2rtc das Producer-Objekt entfernt hat
(producers-Array leer für cam_hires) + großzügiger Settle. Vorher zwingend
empirisch messen (rein lesend, erlaubt): bei abgeschaltetem Live-Stream einmal
frame.jpeg?src=cam0_hires holen, dann GET /api/streams alle 100ms für ~10s loggen —
zeigt, ob/wann der Producer wirklich verschwindet. Erst wenn die Daten das belegen,
ausliefern. Bleibt prinzipiell ein Race auf geteiltem Device → kein Versprechen.
Empfehlung: Wenn Hi-Res verzichtbar → B. Wenn Hi-Res zwingend → A.
C nur, wenn kein Hardware-Budget UND Hi-Res nötig — und nur nach Messung (nie wieder
„verifiziert" ohne Messung auf cam, 04 eiserne Regel 3).
Messung Weg C (Probe) — Anleitung & Ergebnis
Temporäre, rein lesende Diagnose-Route in snapshotService.js: GET /:id/hires-probe.
Sie wartet bis cam frei ist, holt einen cam_hires-Frame und schreibt dann 12s lang
alle 100ms den cam_hires-Zustand mit (cons, prods, states).
Ablauf:
- Code auf Server syncen,
AppRobotWebcamneu starten (lädtserver.js; go2rtc unberührt). - Im Viewer die zu messende Kamera ausschalten (⏸) →
camhat 0 Consumer. curl http://<host>:8444/api/snapshot/cam0/hires-probe(oder im Browser öffnen).- JSON-Antwort + Container-Log (
[probe]…) hierher.
Entscheidend: producerGoneAtMs (wann prods auf 0 fällt) und wie sich states
entwickelt. Daraus wird der robuste Rückweg gebaut (warten bis prods===0 + Settle).
Wenn prods nie 0 wird → go2rtc baut den Producer gar nicht ab → Weg C ist tot,
dann bleibt nur A oder B.
Ergebnis: (hier eintragen nach der Messung)
Danach die hires-probe-Route wieder entfernen.
Noch offen: Multi-User (siehe Abschnitt oben)
Unabhängig vom 106%-Race: bei ≥2 aktiven Clients kann /hires nicht starten, weil
Schritt 1 wartet bis cam 0 Consumer hat (max 8s), ein zweiter Browser die Consumer-Zahl
aber nie auf 0 fallen lässt → Timeout → 503. Variante A löst das mit (separates Device,
kein Warten auf 0 Consumer). Sonst: „Schalter"-Idee oben (ein Producer, Server verteilt).