Files
NodecastV2/test/qsv-detection.test.js
2026-10-13 19:02:15 -07:00

55 lines
4.4 KiB
JavaScript

const test = require('node:test');
const assert = require('node:assert/strict');
const fs = require('node:fs');
const path = require('node:path');
const vm = require('node:vm');
const { createRequire } = require('node:module');
function fixture({ nodes = [], vendors = {}, inaccessible = [], pci = '', fail = false, platform = 'linux' } = {}) {
const calls = [], filename = path.join(__dirname, '../server/services/hwDetect.js'), module = { exports: {} };
vm.runInNewContext(fs.readFileSync(filename, 'utf8'), { module, console: { log() {} }, require: name => ({
os: { platform: () => platform }, fs: { constants: fs.constants, readdirSync: () => nodes,
accessSync: device => { if (inaccessible.includes(device)) throw Error('EACCES'); },
readFileSync: file => { const name = file.split('/')[4]; if (!vendors[name]) throw Error('ENOENT'); return vendors[name]; } },
child_process: { execSync: command => { calls.push({ command }); return pci; },
execFile: (binary, args, options, callback) => { calls.push({ binary, args, options }); callback(fail ? Error('Encode unavailable or timeout') : null); } }
})[name] }, { filename });
return { hw: module.exports, calls };
}
test('QSV verifies a mapped Intel device with the configured FFmpeg and bounded H.264 encode', async () => {
const f = fixture({ nodes: ['card0', 'renderD129', 'renderD128'], vendors: { renderD129: '0x8086\n', renderD128: '0x1002' } });
const qsv = await f.hw.detectQuickSync('/configured/ffmpeg');
assert.equal(qsv.available, true); assert.equal(qsv.device, '/dev/dri/renderD129');
assert.equal(f.calls.length, 1); const call = f.calls[0];
assert.equal(call.binary, '/configured/ffmpeg'); assert.equal(call.options.timeout, 5000);
assert.equal(call.options.windowsHide, true); assert.equal(call.options.maxBuffer, 262144);
assert.ok(call.args.includes('qsv=probe:hw,child_device=/dev/dri/renderD129,child_device_type=vaapi'));
assert.ok(call.args.includes('h264_qsv')); assert.ok(call.args.includes('format=nv12,hwupload=extra_hw_frames=16'));
});
test('QSV rejects unmapped, AMD, inaccessible and unusable Intel devices', async () => {
for (const options of [{ pci: 'VGA Intel' }, { nodes: ['renderD128'], vendors: { renderD128: '0x1002' }, pci: 'VGA Intel' },
{ nodes: ['renderD128'], vendors: { renderD128: '0x8086' }, inaccessible: ['/dev/dri/renderD128'] },
{ nodes: ['renderD128'], vendors: { renderD128: '0x8086' }, fail: true },
{ nodes: ['renderD128', 'renderD129'], pci: 'VGA Intel' }]) {
const f = fixture(options); assert.equal((await f.hw.detectQuickSync()).available, false);
if (!options.fail) assert.equal(f.calls.filter(call => call.binary).length, 0);
}
});
test('QSV handles a hidden sysfs vendor with PCI fallback and preserves Windows detection', async () => {
const f = fixture({ nodes: ['renderD128'], pci: '00:02.0 VGA compatible controller: Intel Corporation UHD Graphics' });
assert.equal((await f.hw.detectQuickSync()).available, true); assert.equal(f.calls[0].command, 'lspci');
const noIntel = fixture({ nodes: ['renderD128'], pci: 'VGA AMD Radeon' }); assert.equal((await noIntel.hw.detectQuickSync()).available, false);
const windows = fixture({ platform: 'win32', pci: 'Name\nIntel UHD Graphics' });
assert.equal((await windows.hw.detectQuickSync()).available, true); assert.equal(windows.calls.length, 1);
});
test('QSV sessions use the detected render node and keep Windows or forced defaults', () => {
const filename = path.join(__dirname, '../server/services/transcodeSession.js'), realRequire = createRequire(filename), module = { exports: {} };
let device = '/dev/dri/renderD129';
vm.runInNewContext(fs.readFileSync(filename, 'utf8'), { module, process, console, setTimeout, clearTimeout, setInterval, clearInterval,
require: name => name === './hwDetect' ? { getCapabilities: () => ({ qsv: { device } }) } : realRequire(name) }, { filename });
const session = Object.create(module.exports.TranscodeSession.prototype), args = [];
session.addHwAccelInputArgs(args, 'qsv');
assert.ok(args.includes('qsv=nodecast:hw,child_device=/dev/dri/renderD129,child_device_type=vaapi'));
assert.ok(args.includes('nodecast')); device = undefined;
const defaults = []; session.addHwAccelInputArgs(defaults, 'qsv'); assert.deepEqual(defaults, ['-hwaccel', 'qsv', '-hwaccel_output_format', 'qsv']);
});