From b6933ff054823bd3c0e2b8e89111c08543541c11 Mon Sep 17 00:00:00 2001 From: Calum Matheson Date: Wed, 25 Sep 2024 09:03:39 +0100 Subject: [PATCH] Add workaround for audio bug on Windows when output device has more than 2 channels Co-authored-by: Casper Jeukendrup <48658420+cbjeukendrup@users.noreply.github.com> --- src/framework/audio/audiomodule.cpp | 4 +-- .../internal/audiooutputdevicecontroller.cpp | 2 +- .../platform/win/wasapiaudioclient.cpp | 28 +++++++++++++++++-- .../internal/platform/win/wasapiaudioclient.h | 2 ++ 4 files changed, 30 insertions(+), 6 deletions(-) diff --git a/src/framework/audio/audiomodule.cpp b/src/framework/audio/audiomodule.cpp index feb312b1c32e3..e2dc72b2667ea 100644 --- a/src/framework/audio/audiomodule.cpp +++ b/src/framework/audio/audiomodule.cpp @@ -259,7 +259,7 @@ void AudioModule::setupAudioDriver(const IApplication::RunMode& mode) if (m_configuration->shouldMeasureInputLag()) { requiredSpec.callback = [this](void* /*userdata*/, uint8_t* stream, int byteCount) { - auto samplesPerChannel = byteCount / (2 * sizeof(float)); + auto samplesPerChannel = byteCount / (2 * sizeof(float)); // 2 == m_configuration->audioChannelsCount() float* dest = reinterpret_cast(stream); m_audioBuffer->pop(dest, samplesPerChannel); measureInputLag(dest, samplesPerChannel * m_audioBuffer->audioChannelCount()); @@ -298,7 +298,7 @@ void AudioModule::setupAudioWorker(const IAudioDriver::Spec& activeSpec) // Setup audio engine m_audioEngine->init(m_audioBuffer, consts); - m_audioEngine->setAudioChannelsCount(activeSpec.channels); + m_audioEngine->setAudioChannelsCount(m_configuration->audioChannelsCount()); m_audioEngine->setSampleRate(activeSpec.sampleRate); m_audioEngine->setReadBufferSize(activeSpec.samples); diff --git a/src/framework/audio/internal/audiooutputdevicecontroller.cpp b/src/framework/audio/internal/audiooutputdevicecontroller.cpp index d20c385676a2e..08fd7e673452a 100644 --- a/src/framework/audio/internal/audiooutputdevicecontroller.cpp +++ b/src/framework/audio/internal/audiooutputdevicecontroller.cpp @@ -100,7 +100,7 @@ void AudioOutputDeviceController::onOutputDeviceChanged() IAudioDriver::Spec activeSpec = audioDriver()->activeSpec(); async::Async::call(this, [this, activeSpec]() { - audioEngine()->setAudioChannelsCount(activeSpec.channels); + // TODO: audioEngine()->setAudioChannelsCount(activeSpec.channels); audioEngine()->setSampleRate(activeSpec.sampleRate); audioEngine()->setReadBufferSize(activeSpec.samples); }, AudioThread::ID); diff --git a/src/framework/audio/internal/platform/win/wasapiaudioclient.cpp b/src/framework/audio/internal/platform/win/wasapiaudioclient.cpp index 9da88b8f6a489..6aa0c7435092a 100644 --- a/src/framework/audio/internal/platform/win/wasapiaudioclient.cpp +++ b/src/framework/audio/internal/platform/win/wasapiaudioclient.cpp @@ -560,11 +560,33 @@ void WasapiAudioClient::getSamples(uint32_t framesAvailable) uint8_t* data; uint32_t actualFramesToRead = framesAvailable; - uint32_t actualBytesToRead = actualFramesToRead * m_mixFormat->nBlockAlign; + + // WASAPI: "nBlockAlign must be equal to the product of nChannels and wBitsPerSample divided by 8 (bits per byte)" + const uint32_t clientFrameSize = m_mixFormat->nBlockAlign; + + // MuseScore assumes only 2 audio channels (same calculation as above to determine frame size) + const uint32_t muFrameSize = 2 * m_mixFormat->wBitsPerSample / 8; check_hresult(m_audioRenderClient->GetBuffer(actualFramesToRead, &data)); - if (actualBytesToRead > 0) { - m_sampleRequestCallback(nullptr, data, actualBytesToRead); + if (actualFramesToRead > 0) { + // Based on the previous calculations, the only way that clientFrameSize will be larger than muFrameSize is + // if the client specifies more than 2 channels. MuseScore doesn't support this (yet), so we use a workaround + // where the missing channels are padded with zeroes... + if (clientFrameSize > muFrameSize) { + const size_t tempBufferDesiredSize = actualFramesToRead * muFrameSize; + if (m_tempBuffer.size() < tempBufferDesiredSize) { + m_tempBuffer.resize(tempBufferDesiredSize); + } + + m_sampleRequestCallback(nullptr, m_tempBuffer.data(), (int)tempBufferDesiredSize); + + for (uint32_t i = 0; i < actualFramesToRead; ++i) { + std::memcpy(data + i * clientFrameSize, m_tempBuffer.data() + i * muFrameSize, muFrameSize); + std::memset(data + i * clientFrameSize + muFrameSize, 0, clientFrameSize - muFrameSize); + } + } else { + m_sampleRequestCallback(nullptr, data, actualFramesToRead * clientFrameSize); + } } check_hresult(m_audioRenderClient->ReleaseBuffer(actualFramesToRead, 0)); } diff --git a/src/framework/audio/internal/platform/win/wasapiaudioclient.h b/src/framework/audio/internal/platform/win/wasapiaudioclient.h index 8c622d57aa281..d643f3da7dd13 100644 --- a/src/framework/audio/internal/platform/win/wasapiaudioclient.h +++ b/src/framework/audio/internal/platform/win/wasapiaudioclient.h @@ -71,6 +71,8 @@ struct WasapiAudioClient : implements m_tempBuffer; //! NOTE: Used for surround workaround - see #17648 + hstring m_deviceIdString; hstring m_fallbackDeviceIdString; uint32_t m_bufferFrames = 0;