109 lines
3.8 KiB
JavaScript
109 lines
3.8 KiB
JavaScript
'use strict';
|
||
|
||
// ── Hires-Grab-Diagnose — läuft DIREKT auf dem Host, ohne Docker/Express ──────
|
||
//
|
||
// Zweck: herausfinden, welche Auflösung /dev/videoN beim Grab WIRKLICH liefert,
|
||
// entkoppelt vom Container (kein Sync-Lag, kein Redeploy). Loggt JEDEN Frame mit
|
||
// Breite×Höhe und Bytes, speichert den besten als /tmp/hires-probe.jpg.
|
||
//
|
||
// Aufruf (auf dem ThinkCentre, im Projektverzeichnis):
|
||
// node tools/hires-probe.js /dev/video4 1920x1080 mjpeg
|
||
// node tools/hires-probe.js /dev/video4 1920x1080 copybsf
|
||
// node tools/hires-probe.js /dev/video0 1280x960 copybsf
|
||
//
|
||
// Args: <device> <size WxH> <encode: mjpeg|copybsf> [framerate=15] [frames=12]
|
||
|
||
const { spawn } = require('child_process');
|
||
const fs = require('fs');
|
||
|
||
const device = process.argv[2] || '/dev/video4';
|
||
const size = process.argv[3] || '1920x1080';
|
||
const encode = process.argv[4] || 'mjpeg';
|
||
const fps = process.argv[5] || '15';
|
||
const maxFrames = parseInt(process.argv[6] || '12', 10);
|
||
|
||
// SOF-Marker lesen: Höhe bei +5, Breite bei +7
|
||
function readJpegDims(buf) {
|
||
let i = 2;
|
||
while (i < buf.length - 8) {
|
||
if (buf[i] !== 0xFF) break;
|
||
const marker = buf[i + 1];
|
||
const segLen = buf.readUInt16BE(i + 2);
|
||
if (marker === 0xC0 || marker === 0xC2) {
|
||
return { h: buf.readUInt16BE(i + 5), w: buf.readUInt16BE(i + 7) };
|
||
}
|
||
i += 2 + segLen;
|
||
}
|
||
return { h: null, w: null };
|
||
}
|
||
|
||
// mpjpeg-Parser (identisch zur App: keyt auf Content-Length)
|
||
class MpjpegParser {
|
||
constructor(onFrame) { this.onFrame = onFrame; this.buf = Buffer.alloc(0); this.need = -1; }
|
||
push(chunk) {
|
||
this.buf = this.buf.length ? Buffer.concat([this.buf, chunk]) : chunk;
|
||
for (;;) {
|
||
if (this.need < 0) {
|
||
const he = this.buf.indexOf('\r\n\r\n');
|
||
if (he < 0) return;
|
||
const m = /content-length:\s*(\d+)/i.exec(this.buf.toString('latin1', 0, he));
|
||
if (!m) { this.buf = this.buf.subarray(he + 4); continue; }
|
||
this.need = parseInt(m[1], 10);
|
||
this.buf = this.buf.subarray(he + 4);
|
||
}
|
||
if (this.buf.length < this.need) return;
|
||
const f = this.buf.subarray(0, this.need);
|
||
this.buf = this.buf.subarray(this.need);
|
||
this.need = -1;
|
||
try { this.onFrame(f); } catch (_e) {}
|
||
}
|
||
}
|
||
}
|
||
|
||
// EXAKT die Args, die die App im hires-Pfad nutzt (videoOutArgs):
|
||
const outArgs = encode === 'mjpeg'
|
||
? ['-c:v', 'mjpeg', '-q:v', '5']
|
||
: ['-c:v', 'copy', '-bsf:v', 'mjpeg2jpeg'];
|
||
|
||
const args = [
|
||
'-hide_banner', '-loglevel', 'warning',
|
||
'-f', 'v4l2', '-input_format', 'mjpeg',
|
||
'-video_size', size, '-framerate', fps,
|
||
'-i', device,
|
||
...outArgs, '-f', 'mpjpeg', 'pipe:1',
|
||
];
|
||
|
||
console.log(`\n[probe] device=${device} size=${size} encode=${encode} fps=${fps}`);
|
||
console.log(`[probe] ffmpeg ${args.join(' ')}\n`);
|
||
|
||
const p = spawn('ffmpeg', args, { stdio: ['ignore', 'pipe', 'pipe'] });
|
||
|
||
let n = 0;
|
||
let best = null;
|
||
const parser = new MpjpegParser((frame) => {
|
||
n++;
|
||
const { w, h } = readJpegDims(frame);
|
||
console.log(`[probe] frame ${String(n).padStart(2)} ${String(w)}x${String(h)} ${frame.length} bytes`);
|
||
if (!best || frame.length > best.length) best = Buffer.from(frame);
|
||
if (n >= maxFrames) finish();
|
||
});
|
||
|
||
p.stdout.on('data', (c) => parser.push(c));
|
||
p.stderr.on('data', (c) => process.stderr.write(`[ffmpeg] ${c}`));
|
||
p.on('error', (e) => { console.error('[probe] spawn-Fehler:', e.message); process.exit(1); });
|
||
|
||
let finished = false;
|
||
function finish() {
|
||
if (finished) return; finished = true;
|
||
try { p.kill('SIGKILL'); } catch (_e) {}
|
||
if (best) {
|
||
const { w, h } = readJpegDims(best);
|
||
fs.writeFileSync('/tmp/hires-probe.jpg', best);
|
||
console.log(`\n[probe] ERGEBNIS: bester Frame ${w}x${h}, ${best.length} bytes → /tmp/hires-probe.jpg`);
|
||
} else {
|
||
console.log('\n[probe] ERGEBNIS: KEIN Frame empfangen');
|
||
}
|
||
process.exit(0);
|
||
}
|
||
setTimeout(finish, 8000); // Sicherheitsnetz
|