From 5865064387ef8baa8fd66ac04ed93b8fc3d87116 Mon Sep 17 00:00:00 2001 From: Connor McLaughlin Date: Wed, 30 Jun 2021 15:15:39 +1000 Subject: [PATCH] FrontendCommon: Add XAudio2 audio backend --- src/core/settings.cpp | 37 ++-- src/core/types.h | 3 + src/frontend-common/CMakeLists.txt | 2 + src/frontend-common/common_host_interface.cpp | 15 +- src/frontend-common/frontend-common.vcxproj | 2 + .../frontend-common.vcxproj.filters | 2 + src/frontend-common/xaudio2_audio_stream.cpp | 209 ++++++++++++++++++ src/frontend-common/xaudio2_audio_stream.h | 61 +++++ 8 files changed, 316 insertions(+), 15 deletions(-) create mode 100644 src/frontend-common/xaudio2_audio_stream.cpp create mode 100644 src/frontend-common/xaudio2_audio_stream.h diff --git a/src/core/settings.cpp b/src/core/settings.cpp index 17c4b1188..3987262be 100644 --- a/src/core/settings.cpp +++ b/src/core/settings.cpp @@ -828,24 +828,33 @@ float Settings::GetDisplayAspectRatioValue() const } } -static std::array s_audio_backend_names = {{ - "Null", - "Cubeb", -#ifndef ANDROID - "SDL", -#else - "OpenSLES", +static const auto s_audio_backend_names = make_array("Null", "Cubeb" +#ifdef _WIN32 + , + "XAudio2" #endif -}}; -static std::array s_audio_backend_display_names = {{ - TRANSLATABLE("AudioBackend", "Null (No Output)"), - TRANSLATABLE("AudioBackend", "Cubeb"), #ifndef ANDROID - TRANSLATABLE("AudioBackend", "SDL"), + , + "SDL" #else - TRANSLATABLE("AudioBackend", "OpenSL ES"), + , + "OpenSLES" #endif -}}; +); +static const auto s_audio_backend_display_names = + make_array(TRANSLATABLE("AudioBackend", "Null (No Output)"), TRANSLATABLE("AudioBackend", "Cubeb") +#ifdef _WIN32 + , + TRANSLATABLE("AudioBackend", "XAudio2") +#endif +#ifndef ANDROID + , + TRANSLATABLE("AudioBackend", "SDL") +#else + , + TRANSLATABLE("AudioBackend", "OpenSL ES") +#endif + ); std::optional Settings::ParseAudioBackend(const char* str) { diff --git a/src/core/types.h b/src/core/types.h index c084e1607..6b0f0cda3 100644 --- a/src/core/types.h +++ b/src/core/types.h @@ -108,6 +108,9 @@ enum class AudioBackend : u8 { Null, Cubeb, +#ifdef _WIN32 + XAudio2, +#endif #ifndef ANDROID SDL, #else diff --git a/src/frontend-common/CMakeLists.txt b/src/frontend-common/CMakeLists.txt index 30421d2ad..7f72510ce 100644 --- a/src/frontend-common/CMakeLists.txt +++ b/src/frontend-common/CMakeLists.txt @@ -55,6 +55,8 @@ if(WIN32) dinput_controller_interface.h imgui_impl_dx11.cpp imgui_impl_dx11.h + xaudio2_audio_stream.cpp + xaudio2_audio_stream.h xinput_controller_interface.cpp xinput_controller_interface.h ) diff --git a/src/frontend-common/common_host_interface.cpp b/src/frontend-common/common_host_interface.cpp index a658fd4db..24ef201a8 100644 --- a/src/frontend-common/common_host_interface.cpp +++ b/src/frontend-common/common_host_interface.cpp @@ -58,6 +58,14 @@ #include #endif +namespace FrontendCommon { + +#ifdef _WIN32 +std::unique_ptr CreateXAudio2AudioStream(); +#endif + +} // namespace FrontendCommon + Log_SetChannel(CommonHostInterface); static std::string s_settings_filename; @@ -628,6 +636,11 @@ std::unique_ptr CommonHostInterface::CreateAudioStream(AudioBackend case AudioBackend::Cubeb: return CubebAudioStream::Create(); +#ifdef _WIN32 + case AudioBackend::XAudio2: + return FrontendCommon::CreateXAudio2AudioStream(); +#endif + #ifdef WITH_SDL2 case AudioBackend::SDL: return SDLAudioStream::Create(); @@ -2049,7 +2062,7 @@ void CommonHostInterface::RegisterGeneralHotkeys() } } }); -#endif // !defined(__ANDROID__) && defined(WITH_CHEEVOS) +#endif // !defined(__ANDROID__) && defined(WITH_CHEEVOS) } void CommonHostInterface::RegisterSystemHotkeys() diff --git a/src/frontend-common/frontend-common.vcxproj b/src/frontend-common/frontend-common.vcxproj index 99ea93931..5da39efdb 100644 --- a/src/frontend-common/frontend-common.vcxproj +++ b/src/frontend-common/frontend-common.vcxproj @@ -114,6 +114,7 @@ + @@ -148,6 +149,7 @@ + diff --git a/src/frontend-common/frontend-common.vcxproj.filters b/src/frontend-common/frontend-common.vcxproj.filters index 59afb17c7..315f9f91f 100644 --- a/src/frontend-common/frontend-common.vcxproj.filters +++ b/src/frontend-common/frontend-common.vcxproj.filters @@ -33,6 +33,7 @@ + @@ -67,6 +68,7 @@ + diff --git a/src/frontend-common/xaudio2_audio_stream.cpp b/src/frontend-common/xaudio2_audio_stream.cpp new file mode 100644 index 000000000..d38f28ca2 --- /dev/null +++ b/src/frontend-common/xaudio2_audio_stream.cpp @@ -0,0 +1,209 @@ +#include "xaudio2_audio_stream.h" +#include "common/assert.h" +#include "common/log.h" +#include +#include +Log_SetChannel(XAudio2AudioStream); + +#if !WINAPI_FAMILY_PARTITION(WINAPI_PARTITION_DESKTOP) +#pragma comment(lib, "xaudio2.lib") +#endif + +XAudio2AudioStream::XAudio2AudioStream() = default; + +XAudio2AudioStream::~XAudio2AudioStream() +{ + if (IsOpen()) + XAudio2AudioStream::CloseDevice(); + +#if WINAPI_FAMILY_PARTITION(WINAPI_PARTITION_DESKTOP) + if (m_xaudio2_library) + FreeLibrary(m_xaudio2_library); + + if (m_com_initialized_by_us) + CoUninitialize(); +#endif +} + +bool XAudio2AudioStream::Initialize() +{ +#if WINAPI_FAMILY_PARTITION(WINAPI_PARTITION_DESKTOP) + m_xaudio2_library = LoadLibraryW(XAUDIO2_DLL_W); + if (!m_xaudio2_library) + { + Log_ErrorPrintf("Failed to load '%s', make sure you're using Windows 10", XAUDIO2_DLL_A); + return false; + } +#endif + + return true; +} + +bool XAudio2AudioStream::OpenDevice() +{ + DebugAssert(!IsOpen()); + + HRESULT hr; +#if WINAPI_FAMILY_PARTITION(WINAPI_PARTITION_DESKTOP) + using PFNXAUDIO2CREATE = + HRESULT(STDAPICALLTYPE*)(IXAudio2 * *ppXAudio2, UINT32 Flags, XAUDIO2_PROCESSOR XAudio2Processor); + PFNXAUDIO2CREATE xaudio2_create = + reinterpret_cast(GetProcAddress(m_xaudio2_library, "XAudio2Create")); + if (!xaudio2_create) + return false; + + hr = CoInitializeEx(nullptr, COINIT_MULTITHREADED); + m_com_initialized_by_us = SUCCEEDED(hr); + if (FAILED(hr) && hr != RPC_E_CHANGED_MODE && hr != S_FALSE) + { + Log_ErrorPrintf("Failed to initialize COM"); + return false; + } + + hr = xaudio2_create(m_xaudio.ReleaseAndGetAddressOf(), 0, XAUDIO2_DEFAULT_PROCESSOR); +#else + hr = XAudio2Create(m_xaudio.ReleaseAndGetAddressOf(), 0, XAUDIO2_DEFAULT_PROCESSOR); +#endif + if (FAILED(hr)) + { + Log_ErrorPrintf("XAudio2Create() failed: %08X", hr); + return false; + } + + hr = m_xaudio->CreateMasteringVoice(&m_mastering_voice, m_channels, m_output_sample_rate, 0, nullptr); + if (FAILED(hr)) + { + Log_ErrorPrintf("CreateMasteringVoice() failed: %08X", hr); + return false; + } + + WAVEFORMATEX wf = {}; + wf.cbSize = sizeof(wf); + wf.nAvgBytesPerSec = m_output_sample_rate * m_channels * sizeof(s16); + wf.nBlockAlign = static_cast(sizeof(s16) * m_channels); + wf.nChannels = static_cast(m_channels); + wf.nSamplesPerSec = m_output_sample_rate; + wf.wBitsPerSample = sizeof(s16) * 8; + wf.wFormatTag = WAVE_FORMAT_PCM; + hr = m_xaudio->CreateSourceVoice(&m_source_voice, &wf, 0, 1.0f, this); + if (FAILED(hr)) + { + Log_ErrorPrintf("CreateMasteringVoice() failed: %08X", hr); + return false; + } + + hr = m_source_voice->SetFrequencyRatio(1.0f); + if (FAILED(hr)) + { + Log_ErrorPrintf("SetFrequencyRatio() failed: %08X", hr); + return false; + } + + for (u32 i = 0; i < NUM_BUFFERS; i++) + m_buffers[i] = std::make_unique(m_buffer_size * m_channels); + + return true; +} + +void XAudio2AudioStream::PauseDevice(bool paused) +{ + if (m_paused == paused) + return; + + if (paused) + { + HRESULT hr = m_source_voice->Stop(0, 0); + if (FAILED(hr)) + Log_ErrorPrintf("Stop() failed: %08X", hr); + } + else + { + HRESULT hr = m_source_voice->Start(0, 0); + if (FAILED(hr)) + Log_ErrorPrintf("Start() failed: %08X", hr); + } + + m_paused = paused; +} + +void XAudio2AudioStream::CloseDevice() +{ + HRESULT hr; + if (!m_paused) + { + hr = m_source_voice->Stop(0, 0); + if (FAILED(hr)) + Log_ErrorPrintf("Stop() failed: %08X", hr); + } + + m_source_voice = nullptr; + m_mastering_voice = nullptr; + m_xaudio.Reset(); + m_buffers = {}; + m_current_buffer = 0; + m_paused = true; +} + +void XAudio2AudioStream::FramesAvailable() +{ + if (!m_buffer_enqueued) + { + m_buffer_enqueued = true; + EnqueueBuffer(); + } +} + +void XAudio2AudioStream::EnqueueBuffer() +{ + SampleType* samples = m_buffers[m_current_buffer].get(); + ReadFrames(samples, m_buffer_size, false); + + const XAUDIO2_BUFFER buf = { + static_cast(0), // flags + static_cast(sizeof(s16) * m_channels * m_buffer_size), // bytes + reinterpret_cast(samples) // data + }; + + HRESULT hr = m_source_voice->SubmitSourceBuffer(&buf, nullptr); + if (FAILED(hr)) + Log_ErrorPrintf("SubmitSourceBuffer() failed: %08X", hr); + + m_current_buffer = (m_current_buffer + 1) % NUM_BUFFERS; +} + +void XAudio2AudioStream::SetOutputVolume(u32 volume) +{ + HRESULT hr = m_source_voice->SetVolume(static_cast(m_output_volume) / 100.0f); + if (FAILED(hr)) + Log_ErrorPrintf("SetVolume() failed: %08X", hr); +} + +void __stdcall XAudio2AudioStream::OnVoiceProcessingPassStart(UINT32 BytesRequired) {} + +void __stdcall XAudio2AudioStream::OnVoiceProcessingPassEnd(void) {} + +void __stdcall XAudio2AudioStream::OnStreamEnd(void) {} + +void __stdcall XAudio2AudioStream::OnBufferStart(void* pBufferContext) {} + +void __stdcall XAudio2AudioStream::OnBufferEnd(void* pBufferContext) +{ + EnqueueBuffer(); +} + +void __stdcall XAudio2AudioStream::OnLoopEnd(void* pBufferContext) {} + +void __stdcall XAudio2AudioStream::OnVoiceError(void* pBufferContext, HRESULT Error) {} + +namespace FrontendCommon { + +std::unique_ptr CreateXAudio2AudioStream() +{ + std::unique_ptr stream = std::make_unique(); + if (!stream->Initialize()) + return {}; + + return stream; +} + +} // namespace FrontendCommon \ No newline at end of file diff --git a/src/frontend-common/xaudio2_audio_stream.h b/src/frontend-common/xaudio2_audio_stream.h new file mode 100644 index 000000000..ca8696ba8 --- /dev/null +++ b/src/frontend-common/xaudio2_audio_stream.h @@ -0,0 +1,61 @@ +#pragma once +#include "common/audio_stream.h" +#include "common/windows_headers.h" +#include +#include +#include +#include + +// We need to use the Windows 10 headers otherwise this won't compile. +#undef _WIN32_WINNT +#define _WIN32_WINNT _WIN32_WINNT_WIN10 +#include + +class XAudio2AudioStream final : public AudioStream, private IXAudio2VoiceCallback +{ +public: + XAudio2AudioStream(); + ~XAudio2AudioStream(); + + bool Initialize(); + + void SetOutputVolume(u32 volume) override; + +protected: + enum : u32 + { + NUM_BUFFERS = 2 + }; + + ALWAYS_INLINE bool IsOpen() const { return static_cast(m_xaudio); } + + bool OpenDevice() override; + void PauseDevice(bool paused) override; + void CloseDevice() override; + void FramesAvailable() override; + + // Inherited via IXAudio2VoiceCallback + virtual void __stdcall OnVoiceProcessingPassStart(UINT32 BytesRequired) override; + virtual void __stdcall OnVoiceProcessingPassEnd(void) override; + virtual void __stdcall OnStreamEnd(void) override; + virtual void __stdcall OnBufferStart(void* pBufferContext) override; + virtual void __stdcall OnBufferEnd(void* pBufferContext) override; + virtual void __stdcall OnLoopEnd(void* pBufferContext) override; + virtual void __stdcall OnVoiceError(void* pBufferContext, HRESULT Error) override; + + void EnqueueBuffer(); + + Microsoft::WRL::ComPtr m_xaudio; + IXAudio2MasteringVoice* m_mastering_voice = nullptr; + IXAudio2SourceVoice* m_source_voice = nullptr; + + std::array, NUM_BUFFERS> m_buffers; + u32 m_current_buffer = 0; + bool m_buffer_enqueued = false; + bool m_paused = true; + +#if WINAPI_FAMILY_PARTITION(WINAPI_PARTITION_DESKTOP) + HMODULE m_xaudio2_library = {}; + bool m_com_initialized_by_us = false; +#endif +};