Windows: Fix test failures (#1300)

* Windows: increase audio block time to match other implementations.

* Set Communications category for FreeDV streams.

* Fix compiler errors.

* Another attempt at fixing compiler errors.

* Fix typo.

* Disable HW offload.

* Add PR #1300 to changelog.

* AudioClientProperties needs to be set before getting mix format.

* Try including intttypes.h.

* Try FALSE in GetBufferSizeLimits.

* Try enabling offload first, then fall back to no offload.

* Try installing audio drivers as administrator.

* Fix syntax errors.

* Fix typo in script.

* Fix additional typo.

* Take wakeup time into account for timeout calculation.

* Fix incorrect cast.

* Use same method of retrieving cert as for Virtual Audio Cable.

* Update driver install script to create missing Registry key.

* Round up to next millisecond.

* Add basic implementation of IAudioClient3, no tuning of periods yet.

* Fix typo.

* Fix another compiler error.
pull/1306/head
Mooneer Salem 2026-04-23 10:58:10 -07:00 committed by GitHub
parent 8022b8bbaa
commit 6940f812e1
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
6 changed files with 143 additions and 29 deletions

View File

@ -111,11 +111,14 @@ jobs:
run: |
Invoke-WebRequest https://download.vb-audio.com/Download_CABLE/VBCABLE_Driver_Pack45.zip -OutFile VBCABLE_Driver_Pack45.zip
Expand-Archive -Path VBCABLE_Driver_Pack45.zip -DestinationPath VBCABLE_Driver_Pack45
Import-Certificate -FilePath ${{github.workspace}}\ci\vbcable.cer -CertStoreLocation Cert:\LocalMachine\root
Import-Certificate -FilePath ${{github.workspace}}\ci\vbcable.cer -CertStoreLocation Cert:\LocalMachine\TrustedPublisher
Get-ChildItem -Path $Env:WindowsSdkDir -Recurse -Filter "devgen.exe" | Select-Object FullName
& "${{ env.WindowsSdkDir }}\Tools\${{ env.WindowsSDKVersion }}\${{ env.Platform }}\devgen.exe" /add /bus ROOT /hardwareid VBAudioVACWDM
pnputil /add-driver VBCable_Driver_Pack45\vbMmeCable64_win10.inf /install
$driverFile = 'VBCable_Driver_Pack45\vbaudio_cable64_win10.sys';
$outputFile = '${{github.workspace}}\vbcable.cer';
$exportType = [System.Security.Cryptography.X509Certificates.X509ContentType]::Cert;
$cert = (Get-AuthenticodeSignature $driverFile).SignerCertificate;
[System.IO.File]::WriteAllBytes($outputFile, $cert.Export($exportType));
.\ci\Install-Driver.ps1 $outputFile VBCable_Driver_Pack45\vbMmeCable64_win10.inf VBAudioVACWDM
- name: Install VAC ("analog" sound device)
run: |
@ -128,10 +131,7 @@ jobs:
$cert = (Get-AuthenticodeSignature $driverFile).SignerCertificate;
[System.IO.File]::WriteAllBytes($outputFile, $cert.Export($exportType));
Import-Certificate -FilePath ${{github.workspace}}\vac.cer -CertStoreLocation Cert:\LocalMachine\root
Import-Certificate -FilePath ${{github.workspace}}\vac.cer -CertStoreLocation Cert:\LocalMachine\TrustedPublisher
& "${{ env.WindowsSdkDir }}\Tools\${{ env.WindowsSDKVersion }}\${{ env.Platform }}\devgen.exe" /add /bus ROOT /hardwareid VirtualAudioCable_83ed7f0e-2028-4956-b0b4-39c76fdaef1d
pnputil /add-driver "vac470lite\vrtaucbl.inf" /install
.\ci\Install-Driver.ps1 $outputFile "vac470lite/vrtaucbl.inf" VirtualAudioCable_83ed7f0e-2028-4956-b0b4-39c76fdaef1d
- name: Screenshot if failed
if: failure()

View File

@ -952,7 +952,7 @@ LDPC | Low Density Parity Check Codes - a family of powerful FEC codes
* Update Hamlib to 4.7.0. (PR #1226)
* Update wxWidgets to 3.3.2. (PR #1244)
* Flex: Update Docker container version to match AppImage version. (PR #1256)
* Reenable Windows on ARM builds previously disabled in 2.0.0. (PR #1297)
* Reenable Windows on ARM builds previously disabled in 2.0.0. (PR #1297, #1300)
4. Documentation:
* Update README instructions to reflect current Windows build steps. (PR #1232)
* Add OmniRig troubleshooting to the user manual. (PR #1264)

View File

@ -0,0 +1,26 @@
# Make sure we are running as Admin
If (-NOT ([Security.Principal.WindowsPrincipal][Security.Principal.WindowsIdentity]::GetCurrent()).IsInRole([Security.Principal.WindowsBuiltInRole] "Administrator")){
$arguments = "& '" + $myinvocation.mycommand.definition + "'"
Start-Process powershell -Verb runAs -ArgumentList $arguments
Break
}
$certName = $args[0]
$infName = $args[1]
$hwId = $args[2]
Import-Certificate -FilePath $certName -CertStoreLocation Cert:\LocalMachine\root
# Some runners in the GH environment will emit "Access Denied" for Import-Certificate
# if the proper TrustedPublisher Registry key doesn't exist first. Create prior to
# attempting import.
# See https://learn.microsoft.com/en-us/answers/questions/1679945/(import-certificate)-unauthorizedaccessexception-e
$Key = "HKLM:\Software\Microsoft\SystemCertificates\TrustedPublisher"
If (-Not (Test-Path $Key)) {
New-Item -Path $Key -ItemType RegistryKey -Force
}
`
Import-Certificate -FilePath $certName -CertStoreLocation Cert:\LocalMachine\TrustedPublisher
& "$Env:WindowsSdkDir\Tools\$Env:WindowsSDKVersion\$Env:Platform\devgen.exe" /add /bus ROOT /hardwareid $hwId
pnputil /add-driver $infName /install

View File

@ -27,16 +27,17 @@
#include <thread>
#include <future>
#include <avrt.h>
#include <inttypes.h>
#include "../util/logging/ulog.h"
#define BLOCK_TIME_NS (10000000)
#define BLOCK_TIME_NS (20000000)
// Nanoseconds per REFERENCE_TIME unit
#define NS_PER_REFTIME (100)
thread_local HANDLE WASAPIAudioDevice::HelperTask_ = nullptr;
WASAPIAudioDevice::WASAPIAudioDevice(ComPtr<IAudioClient> client, ComPtr<IMMDevice> device, IAudioEngine::AudioDirection direction, int sampleRate, int numChannels)
WASAPIAudioDevice::WASAPIAudioDevice(ComPtr<IAudioClient3> client, ComPtr<IMMDevice> device, IAudioEngine::AudioDirection direction, int sampleRate, int numChannels)
: Win32COMObject("WASAPIDev")
, client_(client)
, device_(device)
@ -52,6 +53,7 @@ WASAPIAudioDevice::WASAPIAudioDevice(ComPtr<IAudioClient> client, ComPtr<IMMDevi
, isRenderCaptureRunning_(false)
, semaphore_(nullptr)
, tmpBuf_(nullptr)
, extraTimeMs_(0)
{
// empty
}
@ -94,12 +96,33 @@ void WASAPIAudioDevice::start()
WAVEFORMATEX* streamFormatPtr = nullptr;
WAVEFORMATEX streamFormat;
bool freeStreamFormat = false;
// Set AudioClientProperties for stream. Must be done prior to Initialize().
AudioClientProperties prop;
prop.cbSize = sizeof(AudioClientProperties);
prop.bIsOffload = TRUE;
prop.eCategory = AudioCategory_Communications;
prop.Options = AUDCLNT_STREAMOPTIONS_NONE;
HRESULT hr = client_->SetClientProperties(&prop);
if (FAILED(hr))
{
// Try disabling offload as not all devices support it.
prop.bIsOffload = FALSE;
hr = client_->SetClientProperties(&prop);
if (FAILED(hr))
{
// Non-critical error, can continue without setting properties.
std::stringstream ss;
ss << "Could not set AudioClient properties (hr = " << hr << ")";
log_warn(ss.str().c_str());
}
}
// Populate stream format based on requested sample
// rate/number of channels.
// NOTE: this should already have been determined valid
// by the audio engine!
HRESULT hr = client_->GetMixFormat(&streamFormatPtr);
hr = client_->GetMixFormat(&streamFormatPtr);
if (SUCCEEDED(hr))
{
freeStreamFormat = true;
@ -187,16 +210,65 @@ void WASAPIAudioDevice::start()
if (!initialized_)
{
//REFERENCE_TIME desiredRefTime = BLOCK_TIME_NS / NS_PER_REFTIME; // REFERENCE_TIME is in 100ns units
UINT32 defaultPeriodInFrames = 0;
UINT32 fundamentalPeriodInFrames = 0;
UINT32 minPeriodInFrames = 0;
UINT32 maxPeriodInFrames = 0;
hr = client_->GetSharedModeEnginePeriod(
streamFormatPtr,
&defaultPeriodInFrames,
&fundamentalPeriodInFrames,
&minPeriodInFrames,
&maxPeriodInFrames);
if (FAILED(hr))
{
std::stringstream ss;
ss << "Could not get audio engine period (hr = " << hr << ")";
log_error(ss.str().c_str());
if (onAudioErrorFunction)
{
onAudioErrorFunction(*this, ss.str(), onAudioErrorState);
}
prom->set_value();
return;
}
log_info(
"Default period: %d, fundamental period: %d, minPeriod: %d, maxPeriod: %d",
defaultPeriodInFrames,
fundamentalPeriodInFrames,
minPeriodInFrames,
maxPeriodInFrames);
// Initialize the audio client with the above format
hr = client_->Initialize(
AUDCLNT_SHAREMODE_SHARED,
AUDCLNT_STREAMFLAGS_EVENTCALLBACK |
AUDCLNT_STREAMFLAGS_AUTOCONVERTPCM |
AUDCLNT_STREAMFLAGS_SRC_DEFAULT_QUALITY,
BLOCK_TIME_NS / NS_PER_REFTIME, // REFERENCE_TIME is in 100ns units
0,
hr = client_->InitializeSharedAudioStream(
AUDCLNT_STREAMFLAGS_EVENTCALLBACK,
defaultPeriodInFrames,
streamFormatPtr,
nullptr);
if (AUDCLNT_E_ENGINE_PERIODICITY_LOCKED == hr)
{
// Try again with actual period
WAVEFORMATEX* tempFormat = nullptr;
hr = client_->GetCurrentSharedModeEnginePeriod(
&tempFormat,
&defaultPeriodInFrames);
(void)tempFormat; // ignore warnings
if (FAILED(hr))
{
std::stringstream ss;
ss << "Could not get current engine period (hr = " << hr << ")";
log_warn(ss.str().c_str());
}
hr = client_->InitializeSharedAudioStream(
AUDCLNT_STREAMFLAGS_EVENTCALLBACK,
defaultPeriodInFrames,
streamFormatPtr,
nullptr);
}
if (freeStreamFormat)
{
CoTaskMemFree(streamFormatPtr);
@ -402,7 +474,7 @@ void WASAPIAudioDevice::start()
// Capture references for use by this thread.
ComPtr<IAudioRenderClient> renderClientRef = renderClient_;
ComPtr<IAudioCaptureClient> captureClientRef = captureClient_;
ComPtr<IAudioClient> clientRef = client_;
ComPtr<IAudioClient3> clientRef = client_;
HRESULT res = CoInitializeEx(nullptr, COINIT_MULTITHREADED | COINIT_DISABLE_OLE1DDE);
if (FAILED(res))
@ -525,7 +597,7 @@ void WASAPIAudioDevice::setHelperRealTime()
void WASAPIAudioDevice::startRealTimeWork()
{
// empty
startTime_ = std::chrono::steady_clock::now();
}
void WASAPIAudioDevice::stopRealTimeWork(bool fastMode)
@ -537,9 +609,21 @@ void WASAPIAudioDevice::stopRealTimeWork(bool fastMode)
return;
}
int64_t msec = ((1000 * bufferFrameCount_) / sampleRate_) >> (fastMode ? 1 : 0);
msec -= extraTimeMs_;
if (msec <= 0)
{
extraTimeMs_ = 0;
return;
}
// Wait a maximum of (bufferSize / sampleRate) seconds for the semaphore to return
DWORD result = WaitForSingleObject(semaphore_, ((1000 * bufferFrameCount_) / sampleRate_) >> (fastMode ? 1 : 0));
auto endTime = std::chrono::steady_clock::now();
auto duration = std::chrono::ceil<std::chrono::milliseconds>(endTime - startTime_).count() - msec;
extraTimeMs_ = std::max((int64_t)0, (int64_t)duration); // cap extra time to >= 0.
if (result != WAIT_TIMEOUT && result != WAIT_OBJECT_0)
{
// Fallback to a simple sleep.

View File

@ -69,10 +69,10 @@ public:
protected:
friend class WASAPIAudioEngine;
WASAPIAudioDevice(ComPtr<IAudioClient> client, ComPtr<IMMDevice> device, IAudioEngine::AudioDirection direction, int sampleRate, int numChannels);
WASAPIAudioDevice(ComPtr<IAudioClient3> client, ComPtr<IMMDevice> device, IAudioEngine::AudioDirection direction, int sampleRate, int numChannels);
private:
ComPtr<IAudioClient> client_;
ComPtr<IAudioClient3> client_;
ComPtr<IMMDevice> device_;
ComPtr<IAudioRenderClient> renderClient_;
ComPtr<IAudioCaptureClient> captureClient_;
@ -93,6 +93,10 @@ private:
bool isFloatingPoint_;
short* tmpBuf_;
// For handling additional wakeup time after semaphore timeout
int extraTimeMs_;
std::chrono::time_point<std::chrono::steady_clock> startTime_;
void renderAudio_(ComPtr<IAudioRenderClient> renderClient);
void captureAudio_(ComPtr<IAudioCaptureClient> captureClient);
void copyFromWindowsBuffer_(void* buf, int numFrames);

View File

@ -283,7 +283,7 @@ std::shared_ptr<IAudioDevice> WASAPIAudioEngine::getAudioDevice(wxString deviceN
log_info("Creating WASAPIAudioDevice for device %s (ID %d, direction = %d, sample rate = %d, number of channels = %d)", (const char*)deviceName.ToUTF8(), (int)dev.deviceId, (int)direction, sampleRate, numChannels);
ComPtr<IMMDevice> device = nullptr;
ComPtr<IAudioClient> client = nullptr;
ComPtr<IAudioClient3> client = nullptr;
HRESULT hr = coll->Item(dev.deviceId, device.GetAddressOf());
if (FAILED(hr))
@ -299,7 +299,7 @@ std::shared_ptr<IAudioDevice> WASAPIAudioEngine::getAudioDevice(wxString deviceN
}
hr = device->Activate(
IID_IAudioClient, CLSCTX_ALL,
IID_IAudioClient3, CLSCTX_ALL,
nullptr, (void**)client.GetAddressOf());
if (FAILED(hr))
{
@ -461,8 +461,8 @@ AudioDeviceSpecification WASAPIAudioEngine::getDeviceSpecification_(ComPtr<IMMDe
}
// Activate IAudioClient so we can obtain format info
ComPtr<IAudioClient> audioClient = nullptr;
hr = device->Activate(IID_IAudioClient, CLSCTX_ALL, nullptr, (void**)audioClient.GetAddressOf());
ComPtr<IAudioClient3> audioClient = nullptr;
hr = device->Activate(IID_IAudioClient3, CLSCTX_ALL, nullptr, (void**)audioClient.GetAddressOf());
if (FAILED(hr))
{
std::stringstream ss;