Umbau mit cameraSwitch
This commit is contained in:
@@ -1,4 +1,27 @@
|
||||
# AppRobotWebcam – Hi-Res-Snapshot via Consumer-Umhängen
|
||||
> # ⛔ ABGELÖST (2026-06-05) — dieser Ansatz war die Ursache des 106%-Bugs
|
||||
>
|
||||
> Der unten beschriebene **Consumer-Umhängen-Ansatz mit go2rtc** (`cam0` loslassen →
|
||||
> go2rtc gibt Gerät frei → `cam0_hires` greifen) hat sich als **prinzipiell racy**
|
||||
> erwiesen: go2rtcs API kann nicht zuverlässig melden, wann FFmpeg `/dev/videoN`
|
||||
> freigibt → zwei Encoder auf einem Gerät → **106% CPU + Freeze** (siehe `09_Bug_reports.md`).
|
||||
>
|
||||
> **Aktuelle, maßgebliche Architektur:** **Node-MJPEG-Schalter, go2rtc entfernt.**
|
||||
> Node besitzt die Kameras selbst; das `close`-Event des eigenen FFmpeg ist der harte
|
||||
> Beweis „Gerät frei". Das Race ist damit konstruktiv ausgeschlossen.
|
||||
>
|
||||
> | | alt (unten, abgelöst) | **neu (maßgeblich)** |
|
||||
> |-|----------------------|----------------------|
|
||||
> | Geräte-Öffner | go2rtc | **Node** `src/cameraSwitch.js` |
|
||||
> | Live | go2rtc-WS + `video-stream.js` | MJPEG multipart → `<img>` |
|
||||
> | HD-Grab | 2. go2rtc-Stream `cam_hires` (Race) | Schalter: Live stoppen (`close`=FD frei) → 1280 → zurück |
|
||||
> | Multi-User | brach | gelöst (ein FFmpeg → Fan-out) |
|
||||
>
|
||||
> **→ Neue Architektur + Hardware-Testplan stehen weiter unten in diesem Dokument
|
||||
> (Abschnitt „## Node-MJPEG-Schalter").** Alles ab hier bis dorthin ist **Historie**.
|
||||
|
||||
---
|
||||
|
||||
# AppRobotWebcam – Hi-Res-Snapshot via Consumer-Umhängen ⛔ (historisch)
|
||||
|
||||
> Status: **Phase 2 implementiert und funktional** (2026-06-04):
|
||||
> HD-Grab liefert echten 1280×960-Frame (76071 bytes bestätigt). Bekanntes Problem
|
||||
@@ -334,3 +357,87 @@ und zur Laufzeit wird go2rtc nur **gelesen**, nie verändert.
|
||||
**Reihenfolge:** Phase 1 (messen, ~null Risiko) → Pausen aus der Messung setzen →
|
||||
Phase 2 (Grab). Fällt Phase 1, bleibt Weg A (separate Kamera) aus `04_*` der sichere
|
||||
Fallback.
|
||||
|
||||
---
|
||||
---
|
||||
|
||||
# ✅ Node-MJPEG-Schalter (2026-06-05) — maßgebliche Architektur
|
||||
|
||||
> Ersetzt den gesamten go2rtc-Ansatz oben. Bei Widerspruch gilt dieser Abschnitt.
|
||||
|
||||
## Kernidee: Node besitzt die Kamera selbst
|
||||
|
||||
Das 106%-Race entstand, weil **zwei** FFmpeg (Live 640 + HD 1280) gleichzeitig auf
|
||||
**demselben** `/dev/videoN` liefen, und go2rtcs API nicht zuverlässig melden konnte, wann
|
||||
ein FFmpeg das Gerät freigibt. **Lösung:** Node startet die FFmpeg-Prozesse selbst → das
|
||||
`close`-Event des Kindprozesses ist der harte Beweis „Prozess weg ⇒ Kernel-FD geschlossen
|
||||
⇒ Gerät frei". Race konstruktiv ausgeschlossen, nicht über Timing entschärft.
|
||||
|
||||
```
|
||||
go2rtc ── ENTFERNT
|
||||
Node (server.js)
|
||||
├─ CameraSwitch cam0 ── besitzt /dev/video0 ── EIN FFmpeg (Live ODER HD)
|
||||
├─ CameraSwitch cam1 ── besitzt /dev/video2 ── EIN FFmpeg (Live ODER HD)
|
||||
├─ /api/stream/<id> ── MJPEG multipart/x-mixed-replace → Browser <img>
|
||||
└─ /api/snapshot/<id> ── 640 aus RAM · /<id>/hires → HD-Grab über den Schalter
|
||||
```
|
||||
|
||||
## Der Schalter (`src/cameraSwitch.js`)
|
||||
|
||||
Eine `CameraSwitch`-Instanz pro Gerät — der **einzige** Öffner von `/dev/videoN`. Hält
|
||||
immer nur **einen** FFmpeg. Zustände `stopped | live | grabbing`, Mutex pro Kamera.
|
||||
|
||||
- **Live (Dauerbetrieb):** `ffmpeg -f v4l2 -input_format mjpeg -video_size 640x480
|
||||
-framerate 30 -i /dev/videoN -c:v copy -f mpjpeg pipe:1` → kein Re-Encode. Node parst
|
||||
die mpjpeg-Frames (Content-Length-basiert), hält den letzten (für `/api/snapshot`),
|
||||
sendet sie an alle Stream-Clients. Crash → Auto-Restart nach 1,5 s.
|
||||
- **HD-Grab (`grabHires`):** Live-FFmpeg `SIGTERM` → **auf `close` warten** (FD frei) →
|
||||
1280-FFmpeg, warmlaufen (ab Frame ≥6, ≥15 KB, Breite ≥1000), besten Frame greifen →
|
||||
beenden, auf `close` warten → `finally`: **immer** Live zurück (Live hat Priorität).
|
||||
- **Blackout:** `<img>` friert ~1–3 s ein, läuft dann weiter. **Kein Client-Handling
|
||||
nötig** (das war früher die Fehlerquelle).
|
||||
|
||||
## Auslieferung / Multi-User
|
||||
|
||||
`/api/stream/<id>` = `multipart/x-mixed-replace`; ein FFmpeg → Fan-out an N Clients.
|
||||
Backpressure: voller Socket-Puffer (>1 MB) eines langsamen Clients → Frames für ihn
|
||||
droppen, andere bleiben flüssig. Clients halten **kein** Gerät → **Multi-User gelöst.**
|
||||
|
||||
## Konfiguration (`docker-compose.yaml`)
|
||||
|
||||
Ein Node-Container mit FFmpeg, Geräte durchgereicht, `group_add: video`. Env-Overrides:
|
||||
`DEV0/DEV1`, `LIVE_SIZE/LIVE_FPS`, `HIRES_SIZE/HIRES_FPS`. Firewall: nur noch **TCP 8444**.
|
||||
|
||||
## Verifiziert vs. offen
|
||||
|
||||
- **Lokal verifiziert (ohne Kamera):** MJPEG-Parser (Unittest, Chunk-robust, `\r\n\r\n`
|
||||
im Body), HTTP-Routing (snapshot/stream/health, 404/503), Crash-Auto-Restart rate-limitiert.
|
||||
- **FFmpeg-Args = die der bisher funktionierenden go2rtc-Quelle** (`-f v4l2 -input_format
|
||||
mjpeg -video_size … -framerate …`), nur Ausgabe `-c:v copy -f mpjpeg`.
|
||||
- **Auf der Hardware noch zu verifizieren:** CPU-Last, Latenz, HD-Blackout-Dauer, und der
|
||||
Bug-Reproweg unten.
|
||||
|
||||
## Hardware-Testplan
|
||||
|
||||
1. Code syncen, Stack neu deployen (Image baut FFmpeg ein — erster Build dauert länger).
|
||||
2. Viewer öffnen → beide Kameras Live (`MJPEG · live`). **CPU messen** (Erwartung < 50 %).
|
||||
3. **Bug-Reproweg:** Anmelden → „HD" → Download → Stream nach kurzem Freeze weiter →
|
||||
**neu anmelden / Tab neu laden.** Erwartung: **keine 106%, kein Dauer-Freeze.**
|
||||
4. Zwei Browser gleichzeitig → „HD" während beide verbunden → **kein 503** (Multi-User).
|
||||
5. HD-Bild: 1280×960, nicht schwarz. Blackout-Dauer notieren.
|
||||
6. Eine Kamera abziehen → Log rate-limitierter Restart, andere Kamera + Node unberührt.
|
||||
|
||||
`docker logs AppRobotWebcam` zeigt jeden Zustandswechsel des Schalters.
|
||||
|
||||
## Rollback
|
||||
|
||||
`git checkout <commit-vor-umbau> -- docker-compose.yaml server.js package.json public/ src/`
|
||||
(der go2rtc-Stand liegt vollständig in der Git-Historie).
|
||||
|
||||
## Mögliche Folgeschritte
|
||||
|
||||
- **Hi-Res nativ?** `v4l2-ctl --list-formats-ext -d /dev/video0` — liefert die Kamera
|
||||
1280×960 als **MJPEG**? Falls nur YUYV → `-c:v copy` scheitert → andere native Auflösung
|
||||
oder bewusst Re-Encode (teurer).
|
||||
- **On-Demand Live** (FFmpeg erst bei erstem Client) wäre stromsparender, ist aber bewusst
|
||||
weggelassen — Dauerbetrieb hält die Übergabe-Logik simpel (weniger Race-Fläche).
|
||||
|
||||
Reference in New Issue
Block a user