CoPilot Github: Stream fex 4
This commit is contained in:
108
tools/hires-probe.js
Normal file
108
tools/hires-probe.js
Normal file
@@ -0,0 +1,108 @@
|
||||
'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
|
||||
Reference in New Issue
Block a user