CI (Windows): make collect-pgo-profile wait for real audio endpoints (#1483)
"Generate PGO data" fails intermittently when a VB-Cable / VAC endpoint isn't enumerable at the moment FreeDV starts a UT: FreeDV pops a fatal "device cannot be found" message box and aborts. The old "Start Windows Audio Service" step only polled WMI Win32_SoundDevice (driver nodes, not the MMDevice endpoints FreeDV uses), had an operator-precedence bug in its until-condition ((A -and B) -or C), ran once minutes before the test, and never checked the specific endpoints the job needs. * ci/Wait-AudioDevices.ps1: restarts AudioEndpointBuilder/audiosrv and polls "Get-AudioDevice -List" (the active-endpoint surface FreeDV actually enumerates) until every required playback/recording endpoint is present, nudging the audio stack again halfway through and dumping full diagnostics before failing on timeout. * ci/Invoke-PgoProfileCollection.ps1: runs the endpoint wait + GeneratePGOProfiles.ps1 and retries the whole thing up to 3x, since an endpoint can also drop mid-run right after SoX releases the capture device. Partial .profraw files are cleared between attempts. * collect-pgo-profile now calls these; the Generate PGO data timeout goes 10 -> 25 min to cover the retries. * GeneratePGOProfiles.ps1: best-effort wait for the endpoints before each of the TX and RX passes. The identical "Start Windows Audio Service" step in the test job is left as-is here; this change is scoped to collect-pgo-profile. Claude-Session: https://claude.ai/code/session_01AYJXLpaEyPTsegX93QzE1a Co-authored-by: Claude Sonnet 5 <noreply@anthropic.com>ms-fix-rade-reporting-awgn-flake^2
parent
f3159ca346
commit
1665b53ef7
|
|
@ -168,29 +168,26 @@ jobs:
|
|||
New-Item -Path "HKLM:\SOFTWARE\Policies\Microsoft\Windows\" -Name "AppPrivacy" -Force
|
||||
Set-ItemProperty -Path "HKLM:\SOFTWARE\Policies\Microsoft\Windows\AppPrivacy" -Name LetAppsAccessMicrophone -Value 0
|
||||
|
||||
- name: Start Windows Audio Service
|
||||
- name: Wait for virtual audio endpoints
|
||||
shell: pwsh
|
||||
run: |
|
||||
Restart-Service -Name audiosrv -Force
|
||||
$deadline = (Get-Date).AddSeconds(60)
|
||||
do {
|
||||
Start-Sleep -Seconds 2
|
||||
$deviceNames = (Get-CimInstance win32_sounddevice).Name
|
||||
} until (
|
||||
($deviceNames -match "VB-Audio Virtual Cable") -and
|
||||
($deviceNames -match "Virtual Audio Cable") -or
|
||||
((Get-Date) -gt $deadline)
|
||||
)
|
||||
# Restart the audio stack and poll until every endpoint FreeDV needs is
|
||||
# actually enumerable, instead of starting a test and letting it abort
|
||||
# with a fatal "device cannot be found" message box when a virtual
|
||||
# cable endpoint hasn't come up (or has briefly dropped) yet.
|
||||
& "${{github.workspace}}\ci\Wait-AudioDevices.ps1" `
|
||||
-Playback "${{ env.COMPUTER_TO_RADIO_DEVICE }}", "${{ env.COMPUTER_TO_SPEAKER_DEVICE }}" `
|
||||
-Recording "${{ env.RADIO_TO_COMPUTER_DEVICE }}", "${{ env.MICROPHONE_TO_COMPUTER_DEVICE }}" `
|
||||
-TimeoutSeconds 180
|
||||
|
||||
- name: List audio devices
|
||||
shell: pwsh
|
||||
run: |
|
||||
Get-CimInstance win32_sounddevice
|
||||
Get-AudioDevice -List | Format-Table -AutoSize Index, Default, Type, Name
|
||||
|
||||
- name: Set Windows default audio devices
|
||||
shell: pwsh
|
||||
run: |
|
||||
Invoke-WebRequest https://github.com/frgnca/AudioDeviceCmdlets/releases/download/v3.1/AudioDeviceCmdlets.dll -OutFile AudioDeviceCmdlets.dll
|
||||
Install-Module -Name AudioDeviceCmdlets -Force -Confirm:$false
|
||||
Get-AudioDevice -List | where { $_.Name -eq "${{ env.MICROPHONE_TO_COMPUTER_DEVICE }}" } | Set-AudioDevice
|
||||
Get-AudioDevice -List | where { $_.Name -eq "${{ env.COMPUTER_TO_SPEAKER_DEVICE }}" } | Set-AudioDevice
|
||||
|
||||
|
|
@ -209,9 +206,13 @@ jobs:
|
|||
- name: Generate PGO data
|
||||
shell: pwsh
|
||||
working-directory: ${{github.workspace}}\FreeDV-Install-Location\bin
|
||||
timeout-minutes: 25
|
||||
run: |
|
||||
.\GeneratePGOProfiles.ps1 -RadioToComputerDevice "${{env.RADIO_TO_COMPUTER_DEVICE}}" -ComputerToRadioDevice "${{env.COMPUTER_TO_RADIO_DEVICE}}" -MicrophoneToComputerDevice "${{env.MICROPHONE_TO_COMPUTER_DEVICE}}" -ComputerToSpeakerDevice "${{env.COMPUTER_TO_SPEAKER_DEVICE}}"
|
||||
timeout-minutes: 10
|
||||
& "${{github.workspace}}\ci\Invoke-PgoProfileCollection.ps1" `
|
||||
-RadioToComputerDevice "${{env.RADIO_TO_COMPUTER_DEVICE}}" `
|
||||
-ComputerToRadioDevice "${{env.COMPUTER_TO_RADIO_DEVICE}}" `
|
||||
-MicrophoneToComputerDevice "${{env.MICROPHONE_TO_COMPUTER_DEVICE}}" `
|
||||
-ComputerToSpeakerDevice "${{env.COMPUTER_TO_SPEAKER_DEVICE}}"
|
||||
|
||||
- name: Stash profile data
|
||||
uses: actions/upload-artifact@v7
|
||||
|
|
|
|||
|
|
@ -0,0 +1,73 @@
|
|||
<#
|
||||
.SYNOPSIS
|
||||
Runs GeneratePGOProfiles.ps1 with an audio-endpoint pre-check, retrying a few
|
||||
times.
|
||||
|
||||
.DESCRIPTION
|
||||
On CI the virtual-cable audio endpoints (VB-Cable / VAC) intermittently fail to
|
||||
be enumerable when FreeDV starts a UT -- sometimes not yet up when the job
|
||||
begins, sometimes briefly dropping out mid-run right after SoX releases the
|
||||
capture device. Either way FreeDV aborts with a fatal "device cannot be found"
|
||||
message box.
|
||||
|
||||
This wrapper waits for the required endpoints (ci/Wait-AudioDevices.ps1, which
|
||||
also restarts the audio stack), runs GeneratePGOProfiles.ps1, and retries the
|
||||
whole thing a few times before giving up. Must be run from the folder
|
||||
containing freedv.exe / GeneratePGOProfiles.ps1.
|
||||
|
||||
.PARAMETER Attempts
|
||||
How many times to try the full collection. Default 3.
|
||||
#>
|
||||
param (
|
||||
[Parameter(Mandatory = $true)] [string] $RadioToComputerDevice,
|
||||
[Parameter(Mandatory = $true)] [string] $ComputerToRadioDevice,
|
||||
[Parameter(Mandatory = $true)] [string] $MicrophoneToComputerDevice,
|
||||
[Parameter(Mandatory = $true)] [string] $ComputerToSpeakerDevice,
|
||||
[int] $Attempts = 3
|
||||
)
|
||||
|
||||
$ErrorActionPreference = 'Stop'
|
||||
|
||||
$waitScript = Join-Path $PSScriptRoot 'Wait-AudioDevices.ps1'
|
||||
|
||||
for ($attempt = 1; $attempt -le $Attempts; $attempt++) {
|
||||
Write-Host "=== GeneratePGOProfiles attempt $attempt/$Attempts ==="
|
||||
|
||||
# Playback (render) endpoints FreeDV emits to; recording (capture) endpoints
|
||||
# FreeDV / SoX read from. "Line 1 (Virtual Audio Cable)" is used for both.
|
||||
try {
|
||||
& $waitScript `
|
||||
-Playback $ComputerToRadioDevice, $ComputerToSpeakerDevice `
|
||||
-Recording $RadioToComputerDevice, $MicrophoneToComputerDevice `
|
||||
-TimeoutSeconds 120
|
||||
}
|
||||
catch {
|
||||
Write-Host "::warning::Audio endpoint wait failed on attempt ${attempt}: $_"
|
||||
}
|
||||
|
||||
$rc = 1
|
||||
try {
|
||||
& .\GeneratePGOProfiles.ps1 `
|
||||
-RadioToComputerDevice $RadioToComputerDevice `
|
||||
-ComputerToRadioDevice $ComputerToRadioDevice `
|
||||
-MicrophoneToComputerDevice $MicrophoneToComputerDevice `
|
||||
-ComputerToSpeakerDevice $ComputerToSpeakerDevice
|
||||
$rc = $LASTEXITCODE
|
||||
}
|
||||
catch {
|
||||
Write-Host "::warning::GeneratePGOProfiles threw on attempt ${attempt}: $_"
|
||||
$rc = 1
|
||||
}
|
||||
|
||||
if ($rc -eq 0) {
|
||||
Write-Host "PGO data generated on attempt $attempt."
|
||||
exit 0
|
||||
}
|
||||
|
||||
Write-Host "::warning::GeneratePGOProfiles failed on attempt $attempt (exit $rc)."
|
||||
# Drop any partial .profraw so the retry (and the upload) start clean.
|
||||
Remove-Item -ErrorAction SilentlyContinue *.profraw
|
||||
}
|
||||
|
||||
Write-Host "::error::GeneratePGOProfiles failed after $Attempts attempts."
|
||||
exit 1
|
||||
|
|
@ -0,0 +1,119 @@
|
|||
<#
|
||||
.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"
|
||||
|
|
@ -51,6 +51,36 @@ param (
|
|||
|
||||
$current_loc = Get-Location
|
||||
|
||||
# Best-effort: wait for the configured audio endpoints to be enumerable before
|
||||
# starting FreeDV, so a virtual cable that has briefly dropped out of the
|
||||
# MMDevice list doesn't make the UT abort with a fatal "device cannot be found"
|
||||
# message box. The CI workflow does a stricter, failing check up front; this is
|
||||
# just a short guard against a mid-script drop between the TX and RX passes.
|
||||
function Wait-ForAudioDevices {
|
||||
param (
|
||||
[string[]] $Names,
|
||||
[int] $TimeoutSeconds = 90
|
||||
)
|
||||
|
||||
if (-not (Get-Module -ListAvailable -Name AudioDeviceCmdlets)) { return }
|
||||
Import-Module AudioDeviceCmdlets -ErrorAction SilentlyContinue
|
||||
|
||||
$wanted = @($Names | Where-Object { $_ } | Select-Object -Unique)
|
||||
$deadline = (Get-Date).AddSeconds($TimeoutSeconds)
|
||||
while ($true) {
|
||||
try { $have = @((Get-AudioDevice -List).Name) } catch { $have = @() }
|
||||
$missing = @($wanted | Where-Object { $have -notcontains $_ })
|
||||
if ($missing.Count -eq 0) { return }
|
||||
if ((Get-Date) -ge $deadline) {
|
||||
Write-Host "WARNING: audio device(s) still missing after ${TimeoutSeconds}s: $($missing -join ', ')"
|
||||
return
|
||||
}
|
||||
Start-Sleep -Seconds 3
|
||||
}
|
||||
}
|
||||
|
||||
$allDevices = @($RadioToComputerDevice, $ComputerToRadioDevice, $MicrophoneToComputerDevice, $ComputerToSpeakerDevice)
|
||||
|
||||
# Clone the RADE test corpus if not already present, then resample the TX test file to 48 kHz
|
||||
# to reduce CPU usage during the run.
|
||||
if (-not (Test-Path "$current_loc\rade_src")) {
|
||||
|
|
@ -91,6 +121,8 @@ $soxProcess.StartInfo = $soxPsi
|
|||
[void]$soxProcess.Start()
|
||||
|
||||
# Start FreeDV in test mode to record TX
|
||||
Wait-ForAudioDevices -Names $allDevices
|
||||
|
||||
$psi = New-Object System.Diagnostics.ProcessStartInfo
|
||||
$psi.CreateNoWindow = $true
|
||||
$psi.UseShellExecute = $false
|
||||
|
|
@ -121,6 +153,10 @@ try {
|
|||
}
|
||||
$soxProcess.WaitForExit()
|
||||
|
||||
# Killing SoX above can briefly disturb the shared audio engine; make sure the
|
||||
# endpoints are back before the RX pass starts.
|
||||
Wait-ForAudioDevices -Names $allDevices
|
||||
|
||||
$psi.Arguments = @("/f $quoted_conf_filename /ut rx /utmode RADEV1 /rxfile `"$current_loc\test.wav`" /rxfeaturefile `"$current_loc\rxfeatures.f32`"")
|
||||
|
||||
$conf_tmpl = Get-Content "$current_loc\freedv-pgo.conf.tmpl"
|
||||
|
|
|
|||
Loading…
Reference in New Issue