freedv-gui/ci/Wait-AudioDevices.ps1

120 lines
4.6 KiB
PowerShell

<#
.SYNOPSIS
Blocks until the named Windows audio endpoints are enumerable, or fails with
diagnostics after a timeout.
.DESCRIPTION
FreeDV enumerates audio devices through the Core Audio MMDevice API and aborts
with a fatal "device cannot be found" message box if a configured device is
missing when a test starts. On CI the virtual cables (VB-Cable / VAC) sometimes
take a while to expose their render / capture endpoints after the audio service
is (re)started, or briefly drop out of enumeration after another process
releases them -- which makes the PGO profile collection job fail intermittently.
This script polls "Get-AudioDevice -List" (from AudioDeviceCmdlets, the same
active-endpoint enumeration surface FreeDV uses) until every required endpoint
is present. It restarts the Windows audio stack once up front and once more
partway through as a nudge. On timeout it prints a full device dump and throws.
.PARAMETER Playback
Names of required playback (render) endpoints.
.PARAMETER Recording
Names of required recording (capture) endpoints.
.PARAMETER TimeoutSeconds
Total time to wait for all endpoints. Default 180.
.PARAMETER NoRestart
Do not restart the audio services (just poll).
.EXAMPLE
PS> ./ci/Wait-AudioDevices.ps1 `
-Playback "Speakers (VB-Audio Virtual Cable)", "Line 1 (Virtual Audio Cable)" `
-Recording "CABLE Output (VB-Audio Virtual Cable)", "Line 1 (Virtual Audio Cable)"
#>
param (
[string[]] $Playback = @(),
[string[]] $Recording = @(),
[int] $TimeoutSeconds = 180,
[switch] $NoRestart
)
$ErrorActionPreference = 'Stop'
if (-not (Get-Module -ListAvailable -Name AudioDeviceCmdlets)) {
Install-Module -Name AudioDeviceCmdlets -Force -Confirm:$false -Scope CurrentUser
}
Import-Module AudioDeviceCmdlets
$Playback = @($Playback | Where-Object { $_ } | Select-Object -Unique)
$Recording = @($Recording | Where-Object { $_ } | Select-Object -Unique)
function Get-MissingEndpoints {
$devices = Get-AudioDevice -List
$havePlay = @($devices | Where-Object { $_.Type -eq 'Playback' } | ForEach-Object { $_.Name })
$haveRec = @($devices | Where-Object { $_.Type -eq 'Recording' } | ForEach-Object { $_.Name })
$missing = @()
foreach ($name in $Playback) { if ($havePlay -notcontains $name) { $missing += "playback : $name" } }
foreach ($name in $Recording) { if ($haveRec -notcontains $name) { $missing += "recording: $name" } }
return $missing
}
function Restart-AudioStack {
if ($NoRestart) { return }
Write-Host "Restarting Windows audio services (AudioEndpointBuilder, audiosrv)..."
# AudioEndpointBuilder owns endpoint enumeration; audiosrv depends on it and
# is restarted with it. -Force also restarts dependent services.
foreach ($svc in 'AudioEndpointBuilder', 'audiosrv') {
try { Restart-Service -Name $svc -Force -ErrorAction Stop }
catch { Write-Host " ($svc could not be restarted: $_)" }
}
Start-Sleep -Seconds 3
}
function Show-DeviceDump {
Write-Host "--- Get-AudioDevice -List ---"
Get-AudioDevice -List | Format-Table -AutoSize Index, Default, Type, Name | Out-String | Write-Host
Write-Host "--- Win32_SoundDevice ---"
Get-CimInstance Win32_SoundDevice | Format-Table -AutoSize Name, Status, StatusInfo | Out-String | Write-Host
Write-Host "--- AudioEndpoint PnP devices ---"
Get-PnpDevice -Class AudioEndpoint -ErrorAction SilentlyContinue |
Format-Table -AutoSize Status, FriendlyName | Out-String | Write-Host
}
Write-Host "Waiting up to $TimeoutSeconds s for audio endpoints:"
$Playback | ForEach-Object { Write-Host " [playback ] $_" }
$Recording | ForEach-Object { Write-Host " [recording] $_" }
Restart-AudioStack
$deadline = (Get-Date).AddSeconds($TimeoutSeconds)
$nudged = $false
$missing = Get-MissingEndpoints
while ($missing.Count -gt 0 -and (Get-Date) -lt $deadline) {
$remaining = ($deadline - (Get-Date)).TotalSeconds
if (-not $nudged -and $remaining -lt ($TimeoutSeconds / 2)) {
Write-Host "Still missing after half the timeout; nudging the audio stack:"
$missing | ForEach-Object { Write-Host " $_" }
Restart-AudioStack
$nudged = $true
}
Start-Sleep -Seconds 3
$missing = Get-MissingEndpoints
}
if ($missing.Count -eq 0) {
Write-Host "All required audio endpoints are present."
Get-AudioDevice -List | Format-Table -AutoSize Index, Default, Type, Name | Out-String | Write-Host
exit 0
}
Write-Host "::error::Timed out waiting for audio endpoints; still missing:"
$missing | ForEach-Object { Write-Host " $_" }
Show-DeviceDump
throw "Required audio endpoints not available after $TimeoutSeconds s"