Fix a buffer allocation issue in the WASAPI backend, update comments.

Claude Sonnect helped spot this, but I'm unsure if it will actually fix #21711
This commit is contained in:
Henrik Rydgård
2026-05-21 01:39:53 +02:00
parent 9d43abc8bc
commit caf6d885e9
2 changed files with 40 additions and 15 deletions
+38 -12
View File
@@ -174,10 +174,12 @@ bool WASAPIContext::TryInitAudioClient3(IMMDevice *device, LatencyMode latencyMo
}
curSamplesPerSec_ = format_->nSamplesPerSec;
// We only use AudioClient3 if we got the format we wanted (stereo float).
// 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) {
// Let's fall back to the old path. The docs seem to be wrong, if you try to create an
// AudioClient3 with low latency audio with AUTOCONVERTPCM, you get the error 0x88890021.
INFO_LOG(Log::Audio, "AudioClient3: Got %d channels or non-float format, falling back to AudioClient", format_->nChannels);
audioClient3_.Reset();
// Free the format before falling through - AudioClient will allocate a new one
@@ -213,7 +215,9 @@ bool WASAPIContext::TryInitAudioClient3(IMMDevice *device, LatencyMode latencyMo
}
actualPeriodFrames_ = minPeriodFrames_;
hr = audioClient3_->GetBufferSize(&reportedBufferSize_);
UINT32 bufSize = 0;
hr = audioClient3_->GetBufferSize(&bufSize);
reportedBufferSize_.store(bufSize);
if (FAILED(hr)) {
audioClient3_.Reset();
CoTaskMemFree(format_);
@@ -328,7 +332,9 @@ bool WASAPIContext::TryInitAudioClient(IMMDevice *device, LatencyMode latencyMod
return false;
}
hr = audioClient_->GetBufferSize(&reportedBufferSize_);
UINT32 bufSize = 0;
hr = audioClient_->GetBufferSize(&bufSize);
reportedBufferSize_.store(bufSize);
if (FAILED(hr)) {
audioClient_.Reset();
CoTaskMemFree(format_);
@@ -336,7 +342,7 @@ bool WASAPIContext::TryInitAudioClient(IMMDevice *device, LatencyMode latencyMod
SetErrorString("AudioClient GetBufferSize failed", hr);
return false;
}
actualPeriodFrames_ = reportedBufferSize_; // we don't have a better estimate.
actualPeriodFrames_.store(reportedBufferSize_.load()); // we don't have a better estimate.
hr = audioClient_->SetEventHandle(audioEvent_);
if (FAILED(hr)) {
@@ -357,7 +363,7 @@ bool WASAPIContext::TryInitAudioClient(IMMDevice *device, LatencyMode latencyMod
}
if (createBuffer) {
tempBuf_ = std::make_unique<float[]>(reportedBufferSize_ * 2);
tempBuf_ = std::make_unique<float[]>(reportedBufferSize_.load() * 2);
}
return true;
}
@@ -510,6 +516,12 @@ void WASAPIContext::AudioLoop() {
audioClient3_->Stop();
return;
}
// Check if buffer grew beyond what we allocated tempBuf_ for
if (tempBuf_ && available > reportedBufferSize_.load()) {
INFO_LOG(Log::Audio, "Buffer size grew from %d to %d, reallocating tempBuf_", reportedBufferSize_.load(), available);
tempBuf_ = std::make_unique<float[]>(available * 2);
reportedBufferSize_.store(available);
}
} else if (audioClient_) {
hr = audioClient_->Start();
if (FAILED(hr)) {
@@ -522,6 +534,12 @@ void WASAPIContext::AudioLoop() {
audioClient_->Stop();
return;
}
// Check if buffer grew beyond what we allocated tempBuf_ for
if (tempBuf_ && available > reportedBufferSize_.load()) {
INFO_LOG(Log::Audio, "Buffer size grew from %d to %d, reallocating tempBuf_", reportedBufferSize_.load(), available);
tempBuf_ = std::make_unique<float[]>(available * 2);
reportedBufferSize_.store(available);
}
} else {
// No audio client, nothing to do.
SetErrorString("No audio client in AudioLoop", 0);
@@ -552,7 +570,15 @@ void WASAPIContext::AudioLoop() {
audioClient_->GetCurrentPadding(&padding);
}
const UINT32 framesToWrite = available - padding;
UINT32 framesToWrite = available - padding;
// Safety: clamp framesToWrite to tempBuf_ capacity if using conversion path
const UINT32 bufCapacity = reportedBufferSize_.load();
if (tempBuf_ && framesToWrite > bufCapacity) {
WARN_LOG(Log::Audio, "framesToWrite (%d) exceeds buffer capacity (%d), clamping", framesToWrite, bufCapacity);
framesToWrite = bufCapacity;
}
BYTE* buffer = nullptr;
if (framesToWrite > 0 && SUCCEEDED(renderClient_->GetBuffer(framesToWrite, &buffer))) {
if (!tempBuf_) {
@@ -646,11 +672,11 @@ void WASAPIContext::AudioLoop() {
}
renderClient_->ReleaseBuffer(framesToWrite, 0);
}
// In the old mode, we just estimate the "actualPeriodFrames_" from the framesToWrite.
if (audioClient_ && framesToWrite < actualPeriodFrames_) {
actualPeriodFrames_ = framesToWrite;
// In the old mode, we just estimate the "actualPeriodFrames_" from the framesToWrite.
if (audioClient_ && framesToWrite < actualPeriodFrames_.load()) {
actualPeriodFrames_.store(framesToWrite);
}
}
}
+2 -3
View File
@@ -109,9 +109,8 @@ private:
UINT32 maxPeriodFrames_ = 0;
std::atomic<bool> running_ = true;
// NOTE: these do not need to be atomic, due to usage.
UINT32 actualPeriodFrames_ = 0; // may not be the requested.
UINT32 reportedBufferSize_ = 0;
std::atomic<UINT32> actualPeriodFrames_{0}; // may not be the requested.
std::atomic<UINT32> reportedBufferSize_{0};
Microsoft::WRL::ComPtr<IMMDeviceEnumerator> enumerator_;
DeviceNotificationClient notificationClient_;