From d97f42c225c8923eeb98fce11476ca2433951768 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Henrik=20Rydg=C3=A5rd?= Date: Tue, 4 Aug 2026 00:08:55 +0200 Subject: [PATCH] WASAPI: Cache channel count to avoid unsafe pointer access from audio thread Store format_->nChannels in atomic curChannels_ member to avoid unsynchronized access to format_ pointer from the audio thread. The format_ pointer can be freed/reallocated during Stop() while the audio thread might still be reading it. --- Windows/WASAPIContext.cpp | 14 +++++++++----- Windows/WASAPIContext.h | 1 + 2 files changed, 10 insertions(+), 5 deletions(-) diff --git a/Windows/WASAPIContext.cpp b/Windows/WASAPIContext.cpp index 13028d3430..be96c4badb 100644 --- a/Windows/WASAPIContext.cpp +++ b/Windows/WASAPIContext.cpp @@ -208,14 +208,15 @@ bool WASAPIContext::TryInitAudioClient3(IMMDevice *device, LatencyMode latencyMo return false; } curSamplesPerSec_ = format_->nSamplesPerSec; + curChannels_.store(format_->nChannels); // AudioClient3 requires an exact format match because it doesn't support AUTOCONVERTPCM. // Our callback always produces stereo float (see RenderCallback in AudioBackend.h), // so we can only use AudioClient3 when the device's native format is stereo float. // For other formats, we fall back to AudioClient which supports conversion via AUTOCONVERTPCM // or manual conversion through tempBuf_. - if (format_->nChannels != 2 || Classify(format_) != AudioFormat::Float) { - INFO_LOG(Log::Audio, "AudioClient3: Got %d channels or non-float format, falling back to AudioClient", format_->nChannels); + if (curChannels_.load() != 2 || Classify(format_) != AudioFormat::Float) { + INFO_LOG(Log::Audio, "AudioClient3: Got %d channels or non-float format, falling back to AudioClient", curChannels_.load()); audioClient3_.Reset(); // Free the format before falling through - AudioClient will allocate a new one CoTaskMemFree(format_); @@ -302,11 +303,12 @@ bool WASAPIContext::TryInitAudioClient(IMMDevice *device, LatencyMode latencyMod const AudioFormat fmt = Classify(format_); curSamplesPerSec_ = format_->nSamplesPerSec; + curChannels_.store(format_->nChannels); bool createBuffer = false; if (fmt == AudioFormat::Float) { - if (format_->nChannels != 2) { - INFO_LOG(Log::Audio, "Got %d channels, asking for stereo instead", format_->nChannels); + if (curChannels_.load() != 2) { + INFO_LOG(Log::Audio, "Got %d channels, asking for stereo instead", curChannels_.load()); WAVEFORMATEXTENSIBLE stereo; BuildStereoFloatFormat((const WAVEFORMATEXTENSIBLE *)format_, &stereo); @@ -320,6 +322,7 @@ bool WASAPIContext::TryInitAudioClient(IMMDevice *device, LatencyMode latencyMod memcpy(newFormat, &stereo, sizeof(WAVEFORMATEX) + stereo.Format.cbSize); CoTaskMemFree(format_); format_ = newFormat; + curChannels_.store(newFormat->nChannels); extraStreamFlags = AUDCLNT_STREAMFLAGS_AUTOCONVERTPCM | AUDCLNT_STREAMFLAGS_SRC_DEFAULT_QUALITY; INFO_LOG(Log::Audio, "Successfully asked for two channels"); } else if (result == S_FALSE) { @@ -529,6 +532,7 @@ void WASAPIContext::Stop() { CoTaskMemFree(format_); format_ = nullptr; } + curChannels_.store(0); { std::lock_guard guard(deviceLock_); curDeviceId_.clear(); @@ -622,7 +626,7 @@ void WASAPIContext::AudioLoop() { } const AudioFormat format = Classify(format_); - const int nChannels = format_->nChannels; + const int nChannels = curChannels_.load(); ClearErrorString(); diff --git a/Windows/WASAPIContext.h b/Windows/WASAPIContext.h index 02c4c4fc2f..5877ee6c09 100644 --- a/Windows/WASAPIContext.h +++ b/Windows/WASAPIContext.h @@ -103,6 +103,7 @@ private: HANDLE audioEvent_ = nullptr; std::thread audioThread_; int curSamplesPerSec_ = 0; + std::atomic curChannels_{0}; // Cached from format_->nChannels to avoid unsafe pointer access from audio thread UINT32 defaultPeriodFrames_ = 0; UINT32 fundamentalPeriodFrames_ = 0; UINT32 minPeriodFrames_ = 0;