Second attempt at fixing Windows Start/Stop crashes (#881)

* Use shared setHelperRealTime in internal WASAPI threads.

* Capture this instead of all by reference in internal WASAPI threads.

* Clean up refcounts of COM objects.

* Missed release of default audio device.

* Event handle needs to be closed on stop().

* Fix cross-compile definitions to allow asan to be used in the first place.

* Make sure TX/RX thread stops before audio devices do.
ms-hamlib-crash-fix
Mooneer Salem 2025-05-13 17:03:42 -07:00 committed by GitHub
parent 3084177af3
commit 6b2debfdb4
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
9 changed files with 67 additions and 35 deletions

View File

@ -118,6 +118,9 @@ set( _windlls
# Filter Python DLLs as they're already included via install() # Filter Python DLLs as they're already included via install()
python312.dll python312.dll
# Additional DLLs needed by Address Sanitizer
api-ms-win-core-synch-l1-2-0.dll
) )
list(REMOVE_ITEM _deps ${_windlls}) list(REMOVE_ITEM _deps ${_windlls})

View File

@ -11,9 +11,9 @@ set(CMAKE_AR ${triple}-ar)
set(CMAKE_RANLIB ${triple}-ranlib) set(CMAKE_RANLIB ${triple}-ranlib)
set(CMAKE_RC_COMPILER ${triple}-windres) set(CMAKE_RC_COMPILER ${triple}-windres)
set(CMAKE_C_FLAGS "-Wno-unused-command-line-argument -gcodeview") set(CMAKE_C_FLAGS "${CMAKE_C_FLAGS} -Wno-unused-command-line-argument -gcodeview")
set(CMAKE_CXX_FLAGS "-Wno-unused-command-line-argument -gcodeview") set(CMAKE_CXX_FLAGS "${CMAKE_CXX_FLAGS} -Wno-unused-command-line-argument -gcodeview")
set(CMAKE_EXE_LINKER_FLAGS -Wl,--pdb=) set(CMAKE_EXE_LINKER_FLAGS "${CMAKE_EXE_LINKER_FLAGS} -Wl,--pdb=")
# For make package use. # For make package use.
set(CMAKE_OBJDUMP ${triple}-objdump) set(CMAKE_OBJDUMP ${triple}-objdump)

View File

@ -12,9 +12,9 @@ set(CMAKE_AR ${triple}-ar)
set(CMAKE_RANLIB ${triple}-ranlib) set(CMAKE_RANLIB ${triple}-ranlib)
set(CMAKE_RC_COMPILER ${triple}-windres) set(CMAKE_RC_COMPILER ${triple}-windres)
set(CMAKE_C_FLAGS "-Wno-unused-command-line-argument -gcodeview") set(CMAKE_C_FLAGS "${CMAKE_C_FLAGS} -Wno-unused-command-line-argument -gcodeview")
set(CMAKE_CXX_FLAGS "-Wno-unused-command-line-argument -gcodeview") set(CMAKE_CXX_FLAGS "${CMAKE_CXX_FLAGS} -Wno-unused-command-line-argument -gcodeview")
set(CMAKE_EXE_LINKER_FLAGS -Wl,--pdb=) set(CMAKE_EXE_LINKER_FLAGS "${CMAKE_EXE_LINKER_FLAGS} -Wl,--pdb=")
# For make package use. # For make package use.
set(CMAKE_OBJDUMP ${triple}-objdump) set(CMAKE_OBJDUMP ${triple}-objdump)

View File

@ -34,7 +34,7 @@
// Nanoseconds per REFERENCE_TIME unit // Nanoseconds per REFERENCE_TIME unit
#define NS_PER_REFTIME (100) #define NS_PER_REFTIME (100)
thread_local HANDLE WASAPIAudioDevice::helperTask_ = nullptr; thread_local HANDLE WASAPIAudioDevice::HelperTask_ = nullptr;
WASAPIAudioDevice::WASAPIAudioDevice(IAudioClient* client, IAudioEngine::AudioDirection direction, int sampleRate, int numChannels) WASAPIAudioDevice::WASAPIAudioDevice(IAudioClient* client, IAudioEngine::AudioDirection direction, int sampleRate, int numChannels)
: client_(client) : client_(client)
@ -45,13 +45,12 @@ WASAPIAudioDevice::WASAPIAudioDevice(IAudioClient* client, IAudioEngine::AudioDi
, numChannels_(numChannels) , numChannels_(numChannels)
, bufferFrameCount_(0) , bufferFrameCount_(0)
, initialized_(false) , initialized_(false)
, lowLatencyTask_(nullptr)
, latencyFrames_(0) , latencyFrames_(0)
, renderCaptureEvent_(nullptr) , renderCaptureEvent_(nullptr)
, isRenderCaptureRunning_(false) , isRenderCaptureRunning_(false)
, semaphore_(nullptr) , semaphore_(nullptr)
{ {
// empty client_->AddRef();
} }
WASAPIAudioDevice::~WASAPIAudioDevice() WASAPIAudioDevice::~WASAPIAudioDevice()
@ -291,7 +290,7 @@ void WASAPIAudioDevice::start()
// Start render/capture thread. // Start render/capture thread.
isRenderCaptureRunning_ = true; isRenderCaptureRunning_ = true;
renderCaptureThread_ = std::thread([&]() { renderCaptureThread_ = std::thread([this]() {
log_info("Starting render/capture thread"); log_info("Starting render/capture thread");
HRESULT res = CoInitializeEx(nullptr, COINIT_MULTITHREADED | COINIT_DISABLE_OLE1DDE); HRESULT res = CoInitializeEx(nullptr, COINIT_MULTITHREADED | COINIT_DISABLE_OLE1DDE);
@ -299,14 +298,21 @@ void WASAPIAudioDevice::start()
{ {
log_warn("Could not initialize COM (res = %d)", res); log_warn("Could not initialize COM (res = %d)", res);
} }
// Temporarily raise priority of task // Increment refcounts of COM objects used by thread
DWORD taskIndex = 0; // to avoid instability during stop/restart.
lowLatencyTask_ = AvSetMmThreadCharacteristics(TEXT("Pro Audio"), &taskIndex); client_->AddRef();
if (lowLatencyTask_ == nullptr) if (renderClient_ != nullptr)
{ {
log_warn("Could not increase thread priority"); renderClient_->AddRef();
} }
if (captureClient_ != nullptr)
{
captureClient_->AddRef();
}
// Temporarily raise priority of task
setHelperRealTime();
while (isRenderCaptureRunning_) while (isRenderCaptureRunning_)
{ {
@ -326,12 +332,19 @@ void WASAPIAudioDevice::start()
log_info("Exiting render/capture thread"); log_info("Exiting render/capture thread");
if (lowLatencyTask_ != nullptr) clearHelperRealTime();
// Decrement refcounts prior to exit.
client_->Release();
if (renderClient_ != nullptr)
{ {
AvRevertMmThreadCharacteristics(lowLatencyTask_); renderClient_->Release();
lowLatencyTask_ = nullptr;
} }
if (captureClient_ != nullptr)
{
captureClient_->Release();
}
CoUninitialize(); CoUninitialize();
}); });
@ -369,6 +382,12 @@ void WASAPIAudioDevice::stop()
} }
} }
if (renderCaptureEvent_ != nullptr)
{
CloseHandle(renderCaptureEvent_);
renderCaptureEvent_ = nullptr;
}
if (renderClient_ != nullptr) if (renderClient_ != nullptr)
{ {
renderClient_->Release(); renderClient_->Release();
@ -412,8 +431,8 @@ int WASAPIAudioDevice::getLatencyInMicroseconds()
void WASAPIAudioDevice::setHelperRealTime() void WASAPIAudioDevice::setHelperRealTime()
{ {
DWORD taskIndex = 0; DWORD taskIndex = 0;
helperTask_ = AvSetMmThreadCharacteristics(TEXT("Pro Audio"), &taskIndex); HelperTask_ = AvSetMmThreadCharacteristics(TEXT("Pro Audio"), &taskIndex);
if (helperTask_ == nullptr) if (HelperTask_ == nullptr)
{ {
log_warn("Could not increase thread priority"); log_warn("Could not increase thread priority");
} }
@ -445,10 +464,10 @@ void WASAPIAudioDevice::stopRealTimeWork()
void WASAPIAudioDevice::clearHelperRealTime() void WASAPIAudioDevice::clearHelperRealTime()
{ {
if (helperTask_ != nullptr) if (HelperTask_ != nullptr)
{ {
AvRevertMmThreadCharacteristics(helperTask_); AvRevertMmThreadCharacteristics(HelperTask_);
helperTask_ = nullptr; HelperTask_ = nullptr;
} }
} }

View File

@ -79,7 +79,6 @@ private:
int numChannels_; int numChannels_;
UINT32 bufferFrameCount_; UINT32 bufferFrameCount_;
bool initialized_; bool initialized_;
HANDLE lowLatencyTask_;
int latencyFrames_; int latencyFrames_;
std::thread renderCaptureThread_; std::thread renderCaptureThread_;
HANDLE renderCaptureEvent_; HANDLE renderCaptureEvent_;
@ -89,7 +88,7 @@ private:
void renderAudio_(); void renderAudio_();
void captureAudio_(); void captureAudio_();
static thread_local HANDLE helperTask_; static thread_local HANDLE HelperTask_;
}; };
#endif // WASAPI_AUDIO_DEVICE_H #endif // WASAPI_AUDIO_DEVICE_H

View File

@ -218,6 +218,7 @@ AudioDeviceSpecification WASAPIAudioEngine::getDefaultAudioDevice(AudioDirection
if (defaultSpec.name == spec.name) if (defaultSpec.name == spec.name)
{ {
prom->set_value(spec); prom->set_value(spec);
defaultDevice->Release();
return; return;
} }
} }
@ -303,6 +304,9 @@ std::shared_ptr<IAudioDevice> WASAPIAudioEngine::getAudioDevice(wxString deviceN
auto devPtr = new WASAPIAudioDevice(client, direction, sampleRate, numChannels); auto devPtr = new WASAPIAudioDevice(client, direction, sampleRate, numChannels);
result = std::shared_ptr<IAudioDevice>(devPtr); result = std::shared_ptr<IAudioDevice>(devPtr);
client->Release();
device->Release();
} }
} }
prom->set_value(result); prom->set_value(result);
@ -395,7 +399,7 @@ AudioDeviceSpecification WASAPIAudioEngine::getDeviceSpecification_(IMMDevice* d
return AudioDeviceSpecification::GetInvalidDevice(); return AudioDeviceSpecification::GetInvalidDevice();
} }
WAVEFORMATEX* streamFormat; WAVEFORMATEX* streamFormat = nullptr;
hr = audioClient->GetMixFormat(&streamFormat); hr = audioClient->GetMixFormat(&streamFormat);
if (FAILED(hr)) if (FAILED(hr))
{ {

View File

@ -2855,6 +2855,9 @@ void MainFrame::stopRxStream()
if (m_txThread) if (m_txThread)
{ {
m_txThread->terminateThread();
m_txThread->Wait();
if (txInSoundDevice) if (txInSoundDevice)
{ {
txInSoundDevice->stop(); txInSoundDevice->stop();
@ -2867,15 +2870,15 @@ void MainFrame::stopRxStream()
txOutSoundDevice.reset(); txOutSoundDevice.reset();
} }
m_txThread->terminateThread();
m_txThread->Wait();
delete m_txThread; delete m_txThread;
m_txThread = nullptr; m_txThread = nullptr;
} }
if (m_rxThread) if (m_rxThread)
{ {
m_rxThread->terminateThread();
m_rxThread->Wait();
if (rxInSoundDevice) if (rxInSoundDevice)
{ {
rxInSoundDevice->stop(); rxInSoundDevice->stop();
@ -2888,9 +2891,6 @@ void MainFrame::stopRxStream()
rxOutSoundDevice.reset(); rxOutSoundDevice.reset();
} }
m_rxThread->terminateThread();
m_rxThread->Wait();
delete m_txThread; delete m_txThread;
m_rxThread = nullptr; m_rxThread = nullptr;
} }

View File

@ -515,7 +515,8 @@ void* TxRxThread::Entry()
void TxRxThread::OnExit() void TxRxThread::OnExit()
{ {
// No actions required for exit. // Free allocated buffer.
inputSamples_ = nullptr;
} }
void TxRxThread::terminateThread() void TxRxThread::terminateThread()

View File

@ -58,6 +58,12 @@ public:
new short[std::max(inputSampleRate_, outputSampleRate_)], new short[std::max(inputSampleRate_, outputSampleRate_)],
std::default_delete<short[]>()); std::default_delete<short[]>());
} }
virtual ~TxRxThread()
{
// Free allocated buffer
inputSamples_ = nullptr;
}
// thread execution starts here // thread execution starts here
void *Entry(); void *Entry();