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/.
159 lines
7.8 KiB
PowerShell
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
|