mirror of
https://github.com/RetroDECK/Duckstation.git
synced 2024-11-26 23:55:40 +00:00
FrontendCommon: Add XAudio2 audio backend
This commit is contained in:
parent
37e50c62f0
commit
5865064387
|
@ -828,24 +828,33 @@ float Settings::GetDisplayAspectRatioValue() const
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
static std::array<const char*, 3> s_audio_backend_names = {{
|
static const auto s_audio_backend_names = make_array("Null", "Cubeb"
|
||||||
"Null",
|
#ifdef _WIN32
|
||||||
"Cubeb",
|
,
|
||||||
#ifndef ANDROID
|
"XAudio2"
|
||||||
"SDL",
|
|
||||||
#else
|
|
||||||
"OpenSLES",
|
|
||||||
#endif
|
#endif
|
||||||
}};
|
|
||||||
static std::array<const char*, 3> s_audio_backend_display_names = {{
|
|
||||||
TRANSLATABLE("AudioBackend", "Null (No Output)"),
|
|
||||||
TRANSLATABLE("AudioBackend", "Cubeb"),
|
|
||||||
#ifndef ANDROID
|
#ifndef ANDROID
|
||||||
TRANSLATABLE("AudioBackend", "SDL"),
|
,
|
||||||
|
"SDL"
|
||||||
#else
|
#else
|
||||||
TRANSLATABLE("AudioBackend", "OpenSL ES"),
|
,
|
||||||
|
"OpenSLES"
|
||||||
#endif
|
#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<AudioBackend> Settings::ParseAudioBackend(const char* str)
|
std::optional<AudioBackend> Settings::ParseAudioBackend(const char* str)
|
||||||
{
|
{
|
||||||
|
|
|
@ -108,6 +108,9 @@ enum class AudioBackend : u8
|
||||||
{
|
{
|
||||||
Null,
|
Null,
|
||||||
Cubeb,
|
Cubeb,
|
||||||
|
#ifdef _WIN32
|
||||||
|
XAudio2,
|
||||||
|
#endif
|
||||||
#ifndef ANDROID
|
#ifndef ANDROID
|
||||||
SDL,
|
SDL,
|
||||||
#else
|
#else
|
||||||
|
|
|
@ -55,6 +55,8 @@ if(WIN32)
|
||||||
dinput_controller_interface.h
|
dinput_controller_interface.h
|
||||||
imgui_impl_dx11.cpp
|
imgui_impl_dx11.cpp
|
||||||
imgui_impl_dx11.h
|
imgui_impl_dx11.h
|
||||||
|
xaudio2_audio_stream.cpp
|
||||||
|
xaudio2_audio_stream.h
|
||||||
xinput_controller_interface.cpp
|
xinput_controller_interface.cpp
|
||||||
xinput_controller_interface.h
|
xinput_controller_interface.h
|
||||||
)
|
)
|
||||||
|
|
|
@ -58,6 +58,14 @@
|
||||||
#include <mmsystem.h>
|
#include <mmsystem.h>
|
||||||
#endif
|
#endif
|
||||||
|
|
||||||
|
namespace FrontendCommon {
|
||||||
|
|
||||||
|
#ifdef _WIN32
|
||||||
|
std::unique_ptr<AudioStream> CreateXAudio2AudioStream();
|
||||||
|
#endif
|
||||||
|
|
||||||
|
} // namespace FrontendCommon
|
||||||
|
|
||||||
Log_SetChannel(CommonHostInterface);
|
Log_SetChannel(CommonHostInterface);
|
||||||
|
|
||||||
static std::string s_settings_filename;
|
static std::string s_settings_filename;
|
||||||
|
@ -628,6 +636,11 @@ std::unique_ptr<AudioStream> CommonHostInterface::CreateAudioStream(AudioBackend
|
||||||
case AudioBackend::Cubeb:
|
case AudioBackend::Cubeb:
|
||||||
return CubebAudioStream::Create();
|
return CubebAudioStream::Create();
|
||||||
|
|
||||||
|
#ifdef _WIN32
|
||||||
|
case AudioBackend::XAudio2:
|
||||||
|
return FrontendCommon::CreateXAudio2AudioStream();
|
||||||
|
#endif
|
||||||
|
|
||||||
#ifdef WITH_SDL2
|
#ifdef WITH_SDL2
|
||||||
case AudioBackend::SDL:
|
case AudioBackend::SDL:
|
||||||
return SDLAudioStream::Create();
|
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()
|
void CommonHostInterface::RegisterSystemHotkeys()
|
||||||
|
|
|
@ -114,6 +114,7 @@
|
||||||
<ClCompile Include="sdl_controller_interface.cpp" />
|
<ClCompile Include="sdl_controller_interface.cpp" />
|
||||||
<ClCompile Include="sdl_initializer.cpp" />
|
<ClCompile Include="sdl_initializer.cpp" />
|
||||||
<ClCompile Include="vulkan_host_display.cpp" />
|
<ClCompile Include="vulkan_host_display.cpp" />
|
||||||
|
<ClCompile Include="xaudio2_audio_stream.cpp" />
|
||||||
<ClCompile Include="xinput_controller_interface.cpp" />
|
<ClCompile Include="xinput_controller_interface.cpp" />
|
||||||
</ItemGroup>
|
</ItemGroup>
|
||||||
<ItemGroup>
|
<ItemGroup>
|
||||||
|
@ -148,6 +149,7 @@
|
||||||
<ClInclude Include="sdl_controller_interface.h" />
|
<ClInclude Include="sdl_controller_interface.h" />
|
||||||
<ClInclude Include="sdl_initializer.h" />
|
<ClInclude Include="sdl_initializer.h" />
|
||||||
<ClInclude Include="vulkan_host_display.h" />
|
<ClInclude Include="vulkan_host_display.h" />
|
||||||
|
<ClInclude Include="xaudio2_audio_stream.h" />
|
||||||
<ClInclude Include="xinput_controller_interface.h" />
|
<ClInclude Include="xinput_controller_interface.h" />
|
||||||
</ItemGroup>
|
</ItemGroup>
|
||||||
<ItemGroup>
|
<ItemGroup>
|
||||||
|
|
|
@ -33,6 +33,7 @@
|
||||||
<ClCompile Include="input_overlay_ui.cpp" />
|
<ClCompile Include="input_overlay_ui.cpp" />
|
||||||
<ClCompile Include="game_database.cpp" />
|
<ClCompile Include="game_database.cpp" />
|
||||||
<ClCompile Include="inhibit_screensaver.cpp" />
|
<ClCompile Include="inhibit_screensaver.cpp" />
|
||||||
|
<ClCompile Include="xaudio2_audio_stream.cpp" />
|
||||||
</ItemGroup>
|
</ItemGroup>
|
||||||
<ItemGroup>
|
<ItemGroup>
|
||||||
<ClInclude Include="icon.h" />
|
<ClInclude Include="icon.h" />
|
||||||
|
@ -67,6 +68,7 @@
|
||||||
<ClInclude Include="input_overlay_ui.h" />
|
<ClInclude Include="input_overlay_ui.h" />
|
||||||
<ClInclude Include="game_database.h" />
|
<ClInclude Include="game_database.h" />
|
||||||
<ClInclude Include="inhibit_screensaver.h" />
|
<ClInclude Include="inhibit_screensaver.h" />
|
||||||
|
<ClInclude Include="xaudio2_audio_stream.h" />
|
||||||
</ItemGroup>
|
</ItemGroup>
|
||||||
<ItemGroup>
|
<ItemGroup>
|
||||||
<None Include="font_roboto_regular.inl" />
|
<None Include="font_roboto_regular.inl" />
|
||||||
|
|
209
src/frontend-common/xaudio2_audio_stream.cpp
Normal file
209
src/frontend-common/xaudio2_audio_stream.cpp
Normal file
|
@ -0,0 +1,209 @@
|
||||||
|
#include "xaudio2_audio_stream.h"
|
||||||
|
#include "common/assert.h"
|
||||||
|
#include "common/log.h"
|
||||||
|
#include <VersionHelpers.h>
|
||||||
|
#include <xaudio2.h>
|
||||||
|
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<PFNXAUDIO2CREATE>(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<WORD>(sizeof(s16) * m_channels);
|
||||||
|
wf.nChannels = static_cast<WORD>(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<SampleType[]>(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<UINT32>(0), // flags
|
||||||
|
static_cast<UINT32>(sizeof(s16) * m_channels * m_buffer_size), // bytes
|
||||||
|
reinterpret_cast<const BYTE*>(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<float>(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<AudioStream> CreateXAudio2AudioStream()
|
||||||
|
{
|
||||||
|
std::unique_ptr<XAudio2AudioStream> stream = std::make_unique<XAudio2AudioStream>();
|
||||||
|
if (!stream->Initialize())
|
||||||
|
return {};
|
||||||
|
|
||||||
|
return stream;
|
||||||
|
}
|
||||||
|
|
||||||
|
} // namespace FrontendCommon
|
61
src/frontend-common/xaudio2_audio_stream.h
Normal file
61
src/frontend-common/xaudio2_audio_stream.h
Normal file
|
@ -0,0 +1,61 @@
|
||||||
|
#pragma once
|
||||||
|
#include "common/audio_stream.h"
|
||||||
|
#include "common/windows_headers.h"
|
||||||
|
#include <array>
|
||||||
|
#include <cstdint>
|
||||||
|
#include <memory>
|
||||||
|
#include <wrl/client.h>
|
||||||
|
|
||||||
|
// We need to use the Windows 10 headers otherwise this won't compile.
|
||||||
|
#undef _WIN32_WINNT
|
||||||
|
#define _WIN32_WINNT _WIN32_WINNT_WIN10
|
||||||
|
#include <xaudio2.h>
|
||||||
|
|
||||||
|
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<bool>(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<IXAudio2> m_xaudio;
|
||||||
|
IXAudio2MasteringVoice* m_mastering_voice = nullptr;
|
||||||
|
IXAudio2SourceVoice* m_source_voice = nullptr;
|
||||||
|
|
||||||
|
std::array<std::unique_ptr<SampleType[]>, 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
|
||||||
|
};
|
Loading…
Reference in a new issue