Multicam (c) fix 3

This commit is contained in:
chk
2026-06-06 11:17:34 +02:00
parent 1811b3281d
commit ebc8dcc928
4 changed files with 176 additions and 445 deletions

View File

@@ -77,7 +77,7 @@ class MpjpegParser {
//
// Events: 'frame' (Buffer) je ein Live-JPEG
class CameraSwitch extends EventEmitter {
constructor({ id, device, liveSize = '640x480', liveFps = 30, hiresSize = '1280x960', hiresFps = 15, encode = 'copybsf', onDemand = true, idleGraceMs = 15000 }) {
constructor({ id, device, liveSize = '640x480', liveFps = 30, hiresSize = '1280x960', hiresFps = 15, encode = 'copybsf', hiresEncode, onDemand = true, idleGraceMs = 15000 }) {
super();
this.setMaxListeners(0); // beliebig viele Stream-Clients
this.id = id;
@@ -86,7 +86,8 @@ class CameraSwitch extends EventEmitter {
this.liveFps = liveFps;
this.hiresSize = hiresSize;
this.hiresFps = hiresFps;
this.encode = encode; // 'copybsf' (Default, niedrige CPU) | 'mjpeg' (Re-Encode)
this.encode = encode; // für Live: 'copybsf' (Default) | 'mjpeg' (Re-Encode)
this.hiresEncode = hiresEncode ?? encode; // für Grab: fällt auf encode zurück wenn nicht gesetzt
this.onDemand = onDemand; // Live nur laufen lassen, solange Verbraucher da sind
this.idleGraceMs = idleGraceMs; // Karenz nach letztem Verbraucher vor dem Stop
@@ -200,20 +201,27 @@ class CameraSwitch extends EventEmitter {
}, 1500);
}
// ── HD-Grab: Live sauber stoppen → hires greifen → Live zurück ──────────────
// Garantie: zwischen Stop und hires-Start liegt das `close`-Event des Live-
// FFmpeg → /dev/videoN ist frei. Niemals zwei Encoder gleichzeitig.
// ── HD-Grab ────────────────────────────────────────────────────────────────
// Wenn liveSize == hiresSize: kein Format-Wechsel nötig. Live-Frame direkt
// zurückgeben (on-demand startet den Stream bei Bedarf). Schnell, kein Gerät-
// Neustart, kein Format-Übergangs-Problem.
//
// minWidth wird automatisch aus hiresSize abgeleitet (90 % der Soll-Breite),
// damit Frames die noch auf der alten Live-Auflösung basieren abgelehnt werden.
// Beispiel: hiresSize="1920x1080" → minWidth=1728 → lehnt 1280er-Frames ab.
// Wenn liveSize ≠ hiresSize: Live stoppen → hires-FFmpeg → zurück.
// minWidth (90 % der Soll-Breite) verhindert dass Übergangs-Frames (falsche
// Auflösung aus dem v4l2-Buffer des vorigen Formats) akzeptiert werden.
// Beispiel: hiresSize="1280x960" → minWidth=1152 → lehnt 640er-Frames ab.
async grabHires(opts = {}) {
// Shortcut: keine Format-Umschaltung wenn Live- und Hires-Auflösung identisch
if (this.liveSize === this.hiresSize) {
return this.getFrame();
}
const hiresW = parseInt(this.hiresSize.split('x')[0], 10);
const {
minSize = 15000,
minWidth = Math.floor(hiresW * 0.9), // nur Frames bei (fast) der angeforderten Breite
minWidth = Math.floor(hiresW * 0.9), // nur Frames nahe der Soll-Breite akzeptieren
settleFrames = 6,
maxWaitMs = 10000, // mehr Zeit: Kamera braucht nach Format-Wechsel länger
maxWaitMs = 10000,
} = opts;
if (this.lock) throw new Error('HD-Grab läuft bereits');
this.lock = true;
@@ -268,7 +276,7 @@ class CameraSwitch extends EventEmitter {
'-f', 'v4l2', '-input_format', 'mjpeg',
'-video_size', this.hiresSize, '-framerate', String(this.hiresFps),
'-i', this.device,
...videoOutArgs(this.encode), '-f', 'mpjpeg', 'pipe:1',
...videoOutArgs(this.hiresEncode), '-f', 'mpjpeg', 'pipe:1',
];
let p;
try {