T-HUB/infra/peertube-workers/win/selftest-qsv.ps1
glass beadian aa5e262f3a Tactical Media Broadcast Hub — initial release
Self-hosted multi-platform broadcast newsroom for activist collectives:
publishing pipeline with group approval and DST-proof scheduling, TweetDeck-style
column wall with hostile-content-safe rendering, live control room with Auto
Director, bridged chat governance with polls and quorum, distributed sandboxed
transcode workers, encrypted content-addressed archive, monthly analytics.

MIT licensed. See README.md and docs/.
2026-10-10 11:56:42 +00:00

159 lines
7.8 KiB
PowerShell

# Windows QSV self-test for the PeerTube transcode worker (validator).
#
# Gates registration. A real encode MUST produce a positive PDH
# \GPU Engine\Utilization Percentage (engtype_VideoEncode) signal; exit 0 alone
# is insufficient. No CPU fallback exists anywhere in this worker.
#
# Usage (PowerShell 5.1+, from C:\pt-worker\bin after the ffmpeg zip is unpacked):
# powershell -ExecutionPolicy Bypass -File selftest-qsv.ps1
# Writes C:\pt-worker\state\qsv.json on completion.
$ErrorActionPreference = 'Continue'
$State = 'C:\pt-worker\state'
$Bin = 'C:\pt-worker\bin'
$FFmpeg = Join-Path $Bin 'ffmpeg.exe'
$FFprobe = Join-Path $Bin 'ffprobe.exe'
New-Item -ItemType Directory -Force -Path $State | Out-Null
function HexToBytes([string]$hex) {
$bytes = New-Object byte[] ($hex.Length / 2)
for ($i = 0; $i -lt $bytes.Length; $i++) { $bytes[$i] = [Convert]::ToByte($hex.Substring($i * 2, 2), 16) }
return ,$bytes
}
# 5-second synthetic 1080p30 clip (testsrc2 -> x264 -> mp4). No network, no libx264 use at encode time.
$TestClip = Join-Path $State 'self-test-source.mp4'
& $FFmpeg -hide_banner -loglevel error -y -f lavfi -i 'testsrc2=size=1920x1080:rate=30' -t 5 -c:v libx264 -pix_fmt yuv420p $TestClip
if ($LASTEXITCODE -ne 0) { Write-Error 'failed to build self-test source clip'; exit 2 }
function Get-VideoEncodeBusy([int]$ProcId, [double]$Seconds = 3.0) {
# Sum of engtype_VideoEncode instances of \GPU Engine(pid_<id>_*)\Utilization Percentage.
# Locale-sensitive counter names/decimal separators: unparseable => -1 (unknown, never 0).
$out = & typeperf "\GPU Engine(pid_$ProcId`_*)\Utilization Percentage" -sc 1 2>$null
$rows = @($out | Where-Object { $_ -like '"*' })
if ($rows.Count -lt 2) { return -1 }
$headers = ($rows[0] -replace '^"|"$','').split('","')
$values = ($rows[1] -replace '^"|"$','').split('","')
$total = 0.0; $seen = $false
for ($i = 0; $i -lt $headers.Count; $i++) {
if ($headers[$i] -notmatch 'engtype_VideoEncode') { continue }
if ($i -ge $values.Count) { return -1 }
$raw = $values[$i]
$num = 0.0
if (-not [double]::TryParse($raw, [Globalization.NumberStyles]::Float,
[Globalization.CultureInfo]::InvariantCulture, [ref]$num)) {
$raw = $raw -replace '\.', '' -replace ',', '.'
if (-not [double]::TryParse($raw, [Globalization.NumberStyles]::Float,
[Globalization.CultureInfo]::InvariantCulture, [ref]$num)) { return -1 }
}
$total += $num; $seen = $true
}
if (-not $seen) { return -1 }
return $total
}
function Test-RealEncode([string]$Name, [string[]]$ExtraArgs, [int]$ProcId) {
$log = Join-Path $State "selftest-$Name.log"
$proc = Start-Process -FilePath $FFmpeg -ArgumentList $ExtraArgs -NoNewWindow -PassThru `
-RedirectStandardError $log -RedirectStandardOutput (Join-Path $State "selftest-$Name.out")
$maxBusy = 0.0; $samples = 0; $readable = $false
while (-not $proc.HasExited) {
$busy = Get-VideoEncodeBusy -ProcId ($proc.Id)
if ($busy -ge 0) { $readable = $true; $samples++; if ($busy -gt $maxBusy) { $maxBusy = $busy } }
Start-Sleep -Milliseconds 800
}
$proc.WaitForExit() | Out-Null
return @{ name = $Name; exit = $proc.ExitCode; max_busy = $maxBusy;
samples = $samples; counters_readable = $readable; log = $log }
}
$report = [ordered]@{
schema = 'pt-worker-qsv-selftest/1'
tested_at = [DateTimeOffset]::UtcNow.ToUnixTimeSeconds()
gpu = @(); driver_version = $null
dispatcher_dlls = @{ libmfx = (Test-Path (Join-Path $Bin 'libmfx64-gen.dll'));
libvpl = (Test-Path (Join-Path $Bin 'libvpl64-1.dll')) }
ffmpeg_version = $null; qsv_h264_ok = $false; qsv_hevc_ok = $false
h264_mf_ok = $false; av1_decode_ok = $null; av1_encode_ok = $false
tests = @{}; failures = @(); boot_id = (Get-CimInstance Win32_OperatingSystem).LastBootUpTime.ToString('o')
}
$gpus = @(); try {
$gpus = @(Get-CimInstance Win32_VideoController | Select-Object Name, DriverVersion, Status)
} catch { }
$report.gpu = $gpus
$intel = $gpus | Where-Object { $_.Name -match 'Intel.*(?:Iris|UHD|Graphics)' } | Select-Object -First 1
if (-not $intel) { $report.failures += 'no Intel graphics adapter found via WMI' }
else { $report.driver_version = $intel.DriverVersion }
$report.ffmpeg_version = (& $FFmpeg -version | Select-Object -First 1)
# --- capability listing (necessary, not sufficient) ---
$encoders = (& $FFmpeg -hide_banner -encoders) -join "`n"
foreach ($codec in @('h264_qsv', 'hevc_qsv', 'h264_mf', 'av1_qsv')) {
$report.tests["encoder_$codec"] = ($encoders -match [regex]::Escape($codec))
}
if ($report.tests.encoder_h264_qsv -ne $true) { $report.failures += 'h264_qsv encoder missing from ffmpeg build' }
# --- session init proof (device open) ---
$initLog = Join-Path $State 'selftest-qsv-init.log'
& $FFmpeg -hide_banner -v verbose -y -init_hw_device qsv=hw -f lavfi -i 'testsrc2=size=1280x720:rate=30' `
-frames:v 10 -c:v h264_qsv -f null - 2> $initLog
$initExit = $LASTEXITCODE
$initText = Get-Content $initLog -Raw
$report.tests.qsv_device_init_exit = $initExit
if ($initExit -ne 0) { $report.failures += "h264_qsv session init failed (exit $initExit); see selftest-qsv-init.log" }
# --- REAL H.264 1080p encode with mandatory VideoEncode counter proof ---
$h264 = Test-RealEncode -Name 'qsv-h264' -ProcId 0 -ExtraArgs @(
'-hide_banner', '-nostdin', '-v', 'info',
'-init_hw_device', 'qsv=qs', '-filter_hw_device', 'qs',
'-hwaccel', 'qsv', '-hwaccel_output_format', 'qsv', '-c:v', 'h264_qsv',
'-i', $TestClip,
'-vf', 'scale_qsv=w=1280:h=720:format=nv12',
'-c:v', 'h264_qsv', '-preset', 'medium', '-b:v', '2M', '-maxrate', '2M', '-bufsize', '4M', '-bf', '2',
'-an', '-f', 'null', '-'
)
$report.tests.h264 = $h264
if ($h264.exit -eq 0 -and $h264.counters_readable -and $h264.samples -ge 3 -and $h264.max_busy -gt 0) {
$report.qsv_h264_ok = $true
} elseif ($h264.exit -eq 0) {
$report.failures += 'h264_qsv exited 0 without a positive VideoEncode counter; not accepted as hardware proof'
}
# --- HEVC encode attempt (capability discovery; not enabled in production) ---
$hevc = Test-RealEncode -Name 'qsv-hevc' -ProcId 0 -ExtraArgs @(
'-hide_banner', '-nostdin', '-v', 'info',
'-init_hw_device', 'qsv=qs', '-filter_hw_device', 'qs',
'-hwaccel', 'qsv', '-hwaccel_output_format', 'qsv', '-c:v', 'hevc_qsv',
'-i', $TestClip,
'-vf', 'scale_qsv=w=1280:h=720:format=nv12',
'-c:v', 'hevc_qsv', '-preset', 'medium', '-b:v', '2M',
'-an', '-f', 'null', '-'
)
$report.tests.hevc = $hevc
$report.qsv_hevc_ok = ($hevc.exit -eq 0 -and $hevc.counters_readable -and $hevc.samples -ge 3 -and $hevc.max_busy -gt 0)
# --- AV1: decode-only expectation on Alder Lake; verify, never assume ---
# Build an AV1 source only if an AV1 encoder (software) exists in this build;
# decoding AV1 hardware-wise is the capability under test.
$av1DecodeOk = $false
$av1Source = Join-Path $State 'self-test-source-av1.mp4'
& $FFmpeg -hide_banner -loglevel error -y -f lavfi -i 'testsrc2=size=320x180:rate=30' -t 1 -c:v libaom-av1 $av1Source 2>$null
if ($LASTEXITCODE -eq 0) {
$av1Log = Join-Path $State 'selftest-av1-decode.log'
& $FFmpeg -hide_banner -v error -y -c:v av1_qsv -i $av1Source -frames:v 5 -f null - 2> $av1Log
$av1DecodeOk = ($LASTEXITCODE -eq 0)
}
$report.av1_decode_ok = $av1DecodeOk
$encAv1 = ($encoders -match 'av1_qsv')
$report.av1_encode_ok = $false
$report.tests.av1_encoder_present = $encAv1
# --- Record + verdict. NO CPU fallback exists; registration is blocked on failure. ---
$report | ConvertTo-Json -Depth 5 | Set-Content -Encoding UTF8 (Join-Path $State 'qsv.json')
if ($report.failures.Count -gt 0 -or -not $report.qsv_h264_ok) {
Write-Host 'SELF-TEST FAILED:' ($report.failures -join '; ')
exit 1
}
Write-Host 'SELF-TEST PASSED: h264_qsv with positive VideoEncode counter proof'
exit 0