CoPilot Github: Stream-Umstellen

This commit is contained in:
chk
2026-06-06 11:42:48 +02:00
parent ebc8dcc928
commit b1949d682f

View File

@@ -232,15 +232,19 @@ class CameraSwitch extends EventEmitter {
// 1. Live-FFmpeg beenden, auf Prozess-Ende warten (= Device-FD frei)
await this._killCurrentAndWait();
// Kurze Pause: v4l2-Buffer der Kamera können noch Frames der alten Live-
// Auflösung enthalten (z.B. 640×480-Rest wenn hires 1920×1080 fordert).
// 300 ms genügen damit die Kamera den Format-Reset abschliessen kann.
await sleep(300);
// 2. Kamera resetten: kurz warten, dann zwischenformat öffnen, wieder warten.
await sleep(800);
const warmupSize = this._chooseWarmupSize();
if (warmupSize) {
console.log(`[cam ${this.id}] HD: Zwischenformat ${warmupSize} zum Kamera-Reset`);
await this._warmupFormat(warmupSize);
await sleep(500);
}
this.state = 'grabbing';
console.log(`[cam ${this.id}] HD: Live gestoppt nach ${Date.now() - t0}ms, Gerät frei → ${this.hiresSize}-Grab (minWidth=${minWidth})`);
// 2. hires-FFmpeg starten, warmlaufen lassen, besten Frame greifen
// 3. hires-FFmpeg starten, warmlaufen lassen, besten Frame greifen
const jpeg = await this._captureHires({ minSize, minWidth, settleFrames, maxWaitMs });
const gotW = readJpegWidth(jpeg) ?? '?';
console.log(`[cam ${this.id}] HD OK ${jpeg.length} bytes, Breite=${gotW}px (Soll: ${hiresW}px, ${Date.now() - t0}ms)`);
@@ -269,10 +273,60 @@ class CameraSwitch extends EventEmitter {
});
}
_chooseWarmupSize() {
const liveW = parseInt(this.liveSize.split('x')[0], 10);
const hiresW = parseInt(this.hiresSize.split('x')[0], 10);
if (Number.isNaN(liveW) || Number.isNaN(hiresW)) return null;
if (liveW < 1280 && hiresW >= 1280) return '1280x720';
return null;
}
_warmupFormat(size) {
return new Promise((resolve) => {
const args = [
'-hide_banner', '-loglevel', 'warning',
'-fflags', 'nobuffer',
'-f', 'v4l2', '-input_format', 'mjpeg',
'-video_size', size, '-framerate', String(this.hiresFps),
'-i', this.device,
'-frames:v', '4', '-f', 'null', '-'
];
let p;
try {
p = spawn('ffmpeg', args, { stdio: ['ignore', 'pipe', 'ignore'] });
} catch (_e) {
return resolve();
}
this.proc = p;
this.stopping = false;
let finished = false;
const done = () => {
if (finished) return;
finished = true;
if (p) {
try { p.kill('SIGTERM'); } catch (_e) {}
}
resolve();
};
p.stderr.on('data', (c) => {
const s = c.toString();
if (/error|busy|invalid|no such|cannot|denied/i.test(s)) console.error(`[cam ${this.id}] warmup ffmpeg: ${s.trim()}`);
});
p.on('error', done);
p.on('close', done);
setTimeout(done, 1600);
});
}
_captureHires({ minSize, minWidth, settleFrames, maxWaitMs }) {
return new Promise((resolve, reject) => {
const args = [
'-hide_banner', '-loglevel', 'warning',
'-fflags', 'nobuffer',
'-probesize', '5000000',
'-analyzeduration', '1000000',
'-f', 'v4l2', '-input_format', 'mjpeg',
'-video_size', this.hiresSize, '-framerate', String(this.hiresFps),
'-i', this.device,