# 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__*)\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