summaryrefslogtreecommitdiff
path: root/Source
diff options
context:
space:
mode:
authorspycrab <spycrab@users.noreply.github.com>2018-02-10 21:03:27 +0100
committerspycrab <spycrab@users.noreply.github.com>2018-05-26 13:08:10 +0200
commita196dfe50d1d8a2fcd2ffbe5c8eea10687f1698f (patch)
tree74a4c496557048d4f505e66ec5337f82aed44f89 /Source
parent92ec97f8997fbd511406c09d83a79728cae5ec08 (diff)
AudioCommon: Implement WASAPI
Diffstat (limited to 'Source')
-rw-r--r--Source/Core/AudioCommon/AudioCommon.cpp13
-rw-r--r--Source/Core/AudioCommon/AudioCommon.vcxproj4
-rw-r--r--Source/Core/AudioCommon/AudioCommon.vcxproj.filters8
-rw-r--r--Source/Core/AudioCommon/CMakeLists.txt1
-rw-r--r--Source/Core/AudioCommon/WASAPIStream.cpp451
-rw-r--r--Source/Core/AudioCommon/WASAPIStream.h52
-rw-r--r--Source/Core/Core/ConfigManager.cpp8
-rw-r--r--Source/Core/Core/ConfigManager.h10
-rw-r--r--Source/Core/DolphinQt2/Settings/AudioPane.cpp56
-rw-r--r--Source/Core/DolphinQt2/Settings/AudioPane.h4
10 files changed, 600 insertions, 7 deletions
diff --git a/Source/Core/AudioCommon/AudioCommon.cpp b/Source/Core/AudioCommon/AudioCommon.cpp
index ceecd4b909..14bef2ce62 100644
--- a/Source/Core/AudioCommon/AudioCommon.cpp
+++ b/Source/Core/AudioCommon/AudioCommon.cpp
@@ -10,6 +10,7 @@
#include "AudioCommon/OpenALStream.h"
#include "AudioCommon/OpenSLESStream.h"
#include "AudioCommon/PulseAudioStream.h"
+#include "AudioCommon/WASAPIStream.h"
#include "AudioCommon/XAudio2Stream.h"
#include "AudioCommon/XAudio2_7Stream.h"
#include "Common/Common.h"
@@ -50,6 +51,8 @@ void InitSoundStream()
g_sound_stream = std::make_unique<PulseAudio>();
else if (backend == BACKEND_OPENSLES && OpenSLESStream::isValid())
g_sound_stream = std::make_unique<OpenSLESStream>();
+ else if (backend == BACKEND_WASAPI && WASAPIStream::isValid())
+ g_sound_stream = std::make_unique<WASAPIStream>();
if (!g_sound_stream || !g_sound_stream->Init())
{
@@ -110,6 +113,9 @@ std::vector<std::string> GetSoundBackends()
backends.push_back(BACKEND_OPENAL);
if (OpenSLESStream::isValid())
backends.push_back(BACKEND_OPENSLES);
+ if (WASAPIStream::isValid())
+ backends.push_back(BACKEND_WASAPI);
+
return backends;
}
@@ -128,7 +134,7 @@ bool SupportsDPL2Decoder(const std::string& backend)
bool SupportsLatencyControl(const std::string& backend)
{
- return backend == BACKEND_OPENAL;
+ return backend == BACKEND_OPENAL || backend == BACKEND_WASAPI;
}
bool SupportsVolumeChanges(const std::string& backend)
@@ -136,7 +142,8 @@ bool SupportsVolumeChanges(const std::string& backend)
// FIXME: this one should ask the backend whether it supports it.
// but getting the backend from string etc. is probably
// too much just to enable/disable a stupid slider...
- return backend == BACKEND_CUBEB || backend == BACKEND_OPENAL || backend == BACKEND_XAUDIO2;
+ return backend == BACKEND_CUBEB || backend == BACKEND_OPENAL || backend == BACKEND_XAUDIO2 ||
+ backend == BACKEND_WASAPI;
}
void UpdateSoundStream()
@@ -231,4 +238,4 @@ void ToggleMuteVolume()
isMuted = !isMuted;
UpdateSoundStream();
}
-}
+} // namespace AudioCommon
diff --git a/Source/Core/AudioCommon/AudioCommon.vcxproj b/Source/Core/AudioCommon/AudioCommon.vcxproj
index 5040cb6c6c..0e53a7d37d 100644
--- a/Source/Core/AudioCommon/AudioCommon.vcxproj
+++ b/Source/Core/AudioCommon/AudioCommon.vcxproj
@@ -44,6 +44,7 @@
<ClCompile Include="Mixer.cpp" />
<ClCompile Include="NullSoundStream.cpp" />
<ClCompile Include="OpenALStream.cpp" />
+ <ClCompile Include="WASAPIStream.cpp" />
<ClCompile Include="WaveFile.cpp" />
<ClCompile Include="XAudio2Stream.cpp" />
<ClCompile Include="XAudio2_7Stream.cpp">
@@ -63,6 +64,7 @@
<ClInclude Include="OpenSLESStream.h" />
<ClInclude Include="PulseAudioStream.h" />
<ClInclude Include="SoundStream.h" />
+ <ClInclude Include="WASAPIStream.h" />
<ClInclude Include="WaveFile.h" />
<ClInclude Include="XAudio2Stream.h" />
<ClInclude Include="XAudio2_7Stream.h" />
@@ -81,4 +83,4 @@
<Import Project="$(VCTargetsPath)\Microsoft.Cpp.targets" />
<ImportGroup Label="ExtensionTargets">
</ImportGroup>
-</Project>
+</Project> \ No newline at end of file
diff --git a/Source/Core/AudioCommon/AudioCommon.vcxproj.filters b/Source/Core/AudioCommon/AudioCommon.vcxproj.filters
index 48c93879be..c710630f4a 100644
--- a/Source/Core/AudioCommon/AudioCommon.vcxproj.filters
+++ b/Source/Core/AudioCommon/AudioCommon.vcxproj.filters
@@ -27,6 +27,9 @@
<ClCompile Include="CubebStream.cpp">
<Filter>SoundStreams</Filter>
</ClCompile>
+ <ClCompile Include="WASAPIStream.cpp">
+ <Filter>SoundStreams</Filter>
+ </ClCompile>
</ItemGroup>
<ItemGroup>
<ClInclude Include="AudioCommon.h" />
@@ -62,8 +65,11 @@
<ClInclude Include="CubebStream.h">
<Filter>SoundStreams</Filter>
</ClInclude>
+ <ClInclude Include="WASAPIStream.h">
+ <Filter>SoundStreams</Filter>
+ </ClInclude>
</ItemGroup>
<ItemGroup>
<Text Include="CMakeLists.txt" />
</ItemGroup>
-</Project>
+</Project> \ No newline at end of file
diff --git a/Source/Core/AudioCommon/CMakeLists.txt b/Source/Core/AudioCommon/CMakeLists.txt
index 67d643789d..8664d4bf70 100644
--- a/Source/Core/AudioCommon/CMakeLists.txt
+++ b/Source/Core/AudioCommon/CMakeLists.txt
@@ -47,6 +47,7 @@ else()
endif()
if(WIN32)
+ target_sources(audiocommon PRIVATE WASAPIStream.cpp)
target_sources(audiocommon PRIVATE XAudio2Stream.cpp)
add_library(audiocommon_xaudio27 "XAudio2_7Stream.cpp")
diff --git a/Source/Core/AudioCommon/WASAPIStream.cpp b/Source/Core/AudioCommon/WASAPIStream.cpp
new file mode 100644
index 0000000000..dd83ddb657
--- /dev/null
+++ b/Source/Core/AudioCommon/WASAPIStream.cpp
@@ -0,0 +1,451 @@
+// Copyright 2018 Dolphin Emulator Project
+// Licensed under GPLv2+
+// Refer to the license.txt file included.
+
+#include "AudioCommon/WASAPIStream.h"
+
+#ifdef _WIN32
+
+// clang-format off
+#include <Audioclient.h>
+#include <comdef.h>
+#include <mmdeviceapi.h>
+#include <devpkey.h>
+#include <functiondiscoverykeys_devpkey.h>
+#include <thread>
+// clang-format on
+
+#include "Common/Logging/Log.h"
+#include "Common/StringUtil.h"
+#include "Common/Thread.h"
+#include "Core/ConfigManager.h"
+#include "VideoCommon/OnScreenDisplay.h"
+
+WASAPIStream::WASAPIStream()
+{
+ CoInitialize(nullptr);
+
+ m_format.Format.wFormatTag = WAVE_FORMAT_EXTENSIBLE;
+ m_format.Format.nChannels = 2;
+ m_format.Format.nSamplesPerSec = GetMixer()->GetSampleRate();
+ m_format.Format.nAvgBytesPerSec = m_format.Format.nSamplesPerSec * 4;
+ m_format.Format.nBlockAlign = 4;
+ m_format.Format.wBitsPerSample = 16;
+ m_format.Format.cbSize = sizeof(WAVEFORMATEXTENSIBLE) - sizeof(WAVEFORMATEX);
+ m_format.Samples.wValidBitsPerSample = m_format.Format.wBitsPerSample;
+ m_format.dwChannelMask = SPEAKER_FRONT_LEFT | SPEAKER_FRONT_RIGHT;
+ m_format.SubFormat = KSDATAFORMAT_SUBTYPE_PCM;
+}
+
+WASAPIStream::~WASAPIStream()
+{
+ if (m_enumerator)
+ m_enumerator->Release();
+
+ if (m_need_data_event)
+ CloseHandle(m_need_data_event);
+
+ if (m_running)
+ {
+ m_running = false;
+ if (m_thread.joinable())
+ m_thread.join();
+ }
+
+ CoUninitialize();
+}
+
+bool WASAPIStream::isValid()
+{
+ return true;
+}
+
+static bool HandleWinAPI(std::string message, HRESULT result)
+{
+ if (result != S_OK)
+ {
+ _com_error err(result);
+ std::string error = TStrToUTF8(err.ErrorMessage()).c_str();
+
+ switch (result)
+ {
+ case AUDCLNT_E_DEVICE_IN_USE:
+ error = "Audio endpoint already in use!";
+ break;
+ }
+
+ ERROR_LOG(AUDIO, "WASAPI: %s: %s", message.c_str(), error.c_str());
+ }
+
+ return result == S_OK;
+}
+
+std::vector<std::string> WASAPIStream::GetAvailableDevices()
+{
+ CoInitialize(nullptr);
+
+ IMMDeviceEnumerator* enumerator = nullptr;
+
+ HRESULT result =
+ CoCreateInstance(__uuidof(MMDeviceEnumerator), nullptr, CLSCTX_INPROC_SERVER,
+ __uuidof(IMMDeviceEnumerator), reinterpret_cast<LPVOID*>(&enumerator));
+
+ if (!HandleWinAPI("Failed to create MMDeviceEnumerator", result))
+ return {};
+
+ std::vector<std::string> device_names;
+ IMMDeviceCollection* devices;
+ result = enumerator->EnumAudioEndpoints(eRender, DEVICE_STATE_ACTIVE, &devices);
+
+ if (!HandleWinAPI("Failed to get available devices", result))
+ {
+ CoUninitialize();
+ return {};
+ }
+
+ UINT count;
+ devices->GetCount(&count);
+
+ for (u32 i = 0; i < count; i++)
+ {
+ IMMDevice* device;
+ devices->Item(i, &device);
+ if (!HandleWinAPI("Failed to get device " + std::to_string(i), result))
+ continue;
+
+ LPWSTR device_id;
+ device->GetId(&device_id);
+
+ IPropertyStore* device_properties;
+
+ result = device->OpenPropertyStore(STGM_READ, &device_properties);
+
+ if (!HandleWinAPI("Failed to initialize IPropertyStore", result))
+ continue;
+
+ PROPVARIANT device_name;
+ PropVariantInit(&device_name);
+
+ device_properties->GetValue(PKEY_Device_FriendlyName, &device_name);
+
+ device_names.push_back(TStrToUTF8(device_name.pwszVal));
+
+ PropVariantClear(&device_name);
+ }
+
+ devices->Release();
+ enumerator->Release();
+
+ CoUninitialize();
+
+ return device_names;
+}
+
+IMMDevice* WASAPIStream::GetDeviceByName(std::string name)
+{
+ CoInitialize(nullptr);
+
+ IMMDeviceEnumerator* enumerator = nullptr;
+
+ HRESULT result =
+ CoCreateInstance(__uuidof(MMDeviceEnumerator), nullptr, CLSCTX_INPROC_SERVER,
+ __uuidof(IMMDeviceEnumerator), reinterpret_cast<LPVOID*>(&enumerator));
+
+ if (!HandleWinAPI("Failed to create MMDeviceEnumerator", result))
+ return false;
+
+ IMMDeviceCollection* devices;
+ result = enumerator->EnumAudioEndpoints(eRender, DEVICE_STATE_ACTIVE, &devices);
+
+ if (!HandleWinAPI("Failed to get available devices", result))
+ {
+ return {};
+ }
+
+ UINT count;
+ devices->GetCount(&count);
+
+ for (u32 i = 0; i < count; i++)
+ {
+ IMMDevice* device;
+ devices->Item(i, &device);
+ if (!HandleWinAPI("Failed to get device " + std::to_string(i), result))
+ continue;
+
+ LPWSTR device_id;
+ device->GetId(&device_id);
+
+ IPropertyStore* device_properties;
+
+ result = device->OpenPropertyStore(STGM_READ, &device_properties);
+
+ if (!HandleWinAPI("Failed to initialize IPropertyStore", result))
+ continue;
+
+ PROPVARIANT device_name;
+ PropVariantInit(&device_name);
+
+ device_properties->GetValue(PKEY_Device_FriendlyName, &device_name);
+
+ if (TStrToUTF8(device_name.pwszVal) == name)
+ {
+ devices->Release();
+ enumerator->Release();
+ CoUninitialize();
+ return device;
+ }
+
+ PropVariantClear(&device_name);
+ }
+
+ devices->Release();
+ enumerator->Release();
+ CoUninitialize();
+
+ return nullptr;
+}
+
+bool WASAPIStream::Init()
+{
+ HRESULT result =
+ CoCreateInstance(__uuidof(MMDeviceEnumerator), nullptr, CLSCTX_INPROC_SERVER,
+ __uuidof(IMMDeviceEnumerator), reinterpret_cast<LPVOID*>(&m_enumerator));
+
+ if (!HandleWinAPI("Failed to create MMDeviceEnumerator", result))
+ return false;
+
+ return true;
+}
+
+bool WASAPIStream::SetRunning(bool running)
+{
+ if (running)
+ {
+ IMMDevice* device = nullptr;
+
+ HRESULT result;
+
+ if (SConfig::GetInstance().sWASAPIDevice == "default")
+ {
+ result = m_enumerator->GetDefaultAudioEndpoint(eRender, eConsole, &device);
+ }
+ else
+ {
+ result = S_OK;
+ device = GetDeviceByName(SConfig::GetInstance().sWASAPIDevice);
+
+ if (!device)
+ {
+ ERROR_LOG(AUDIO, "Can't find device '%s', falling back to default",
+ SConfig::GetInstance().sWASAPIDevice.c_str());
+ result = m_enumerator->GetDefaultAudioEndpoint(eRender, eConsole, &device);
+ }
+ }
+
+ if (!HandleWinAPI("Failed to obtain default endpoint", result))
+ return false;
+
+ LPWSTR device_id;
+ device->GetId(&device_id);
+
+ // Show a friendly name in the log
+ IPropertyStore* device_properties;
+
+ result = device->OpenPropertyStore(STGM_READ, &device_properties);
+
+ if (!HandleWinAPI("Failed to initialize IPropertyStore", result))
+ return false;
+
+ PROPVARIANT device_name;
+ PropVariantInit(&device_name);
+
+ device_properties->GetValue(PKEY_Device_FriendlyName, &device_name);
+
+ INFO_LOG(AUDIO, "Using audio endpoint '%s'", TStrToUTF8(device_name.pwszVal).c_str());
+
+ PropVariantClear(&device_name);
+
+ // Get IAudioDevice
+ result = device->Activate(__uuidof(IAudioClient), CLSCTX_INPROC_SERVER, nullptr,
+ reinterpret_cast<LPVOID*>(&m_audio_client));
+
+ if (!HandleWinAPI("Failed to activate IAudioClient", result))
+ {
+ device->Release();
+ return false;
+ }
+
+ REFERENCE_TIME device_period = 0;
+
+ result = m_audio_client->GetDevicePeriod(nullptr, &device_period);
+
+ device_period += SConfig::GetInstance().iLatency * (10000 / m_format.Format.nChannels);
+ INFO_LOG(AUDIO, "Audio period set to %d", device_period);
+
+ if (!HandleWinAPI("Failed to obtain device period", result))
+ {
+ device->Release();
+ m_audio_client->Release();
+ m_audio_client = nullptr;
+ return false;
+ }
+
+ result = m_audio_client->Initialize(
+ AUDCLNT_SHAREMODE_EXCLUSIVE,
+ AUDCLNT_STREAMFLAGS_EVENTCALLBACK | AUDCLNT_STREAMFLAGS_NOPERSIST, device_period,
+ device_period, reinterpret_cast<WAVEFORMATEX*>(&m_format), nullptr);
+
+ if (result == AUDCLNT_E_UNSUPPORTED_FORMAT)
+ {
+ OSD::AddMessage("Your current audio device doesn't support 16-bit 48000 hz PCM audio. WASAPI "
+ "exclusive mode won't work.",
+ 6000U);
+ return false;
+ }
+
+ if (result == AUDCLNT_E_BUFFER_SIZE_NOT_ALIGNED)
+ {
+ result = m_audio_client->GetBufferSize(&m_frames_in_buffer);
+ m_audio_client->Release();
+
+ if (!HandleWinAPI("Failed to get aligned buffer size", result))
+ {
+ device->Release();
+ m_audio_client->Release();
+ m_audio_client = nullptr;
+ return false;
+ }
+
+ // Get IAudioDevice
+ result = device->Activate(__uuidof(IAudioClient), CLSCTX_INPROC_SERVER, nullptr,
+ reinterpret_cast<LPVOID*>(&m_audio_client));
+
+ if (!HandleWinAPI("Failed to reactivate IAudioClient", result))
+ {
+ device->Release();
+ return false;
+ }
+
+ device_period =
+ static_cast<REFERENCE_TIME>(
+ 10000.0 * 1000 * m_frames_in_buffer / m_format.Format.nSamplesPerSec + 0.5) +
+ SConfig::GetInstance().iLatency * 10000;
+
+ result = m_audio_client->Initialize(
+ AUDCLNT_SHAREMODE_EXCLUSIVE,
+ AUDCLNT_STREAMFLAGS_EVENTCALLBACK | AUDCLNT_STREAMFLAGS_NOPERSIST, device_period,
+ device_period, reinterpret_cast<WAVEFORMATEX*>(&m_format), nullptr);
+ }
+
+ if (!HandleWinAPI("Failed to initialize IAudioClient", result))
+ {
+ device->Release();
+ m_audio_client->Release();
+ m_audio_client = nullptr;
+ return false;
+ }
+
+ result = m_audio_client->GetBufferSize(&m_frames_in_buffer);
+
+ if (!HandleWinAPI("Failed to get buffer size from IAudioClient", result))
+ {
+ device->Release();
+ m_audio_client->Release();
+ m_audio_client = nullptr;
+ return false;
+ }
+
+ result = m_audio_client->GetService(__uuidof(IAudioRenderClient),
+ reinterpret_cast<LPVOID*>(&m_audio_renderer));
+
+ if (!HandleWinAPI("Failed to get IAudioRenderClient from IAudioClient", result))
+ {
+ device->Release();
+ m_audio_client->Release();
+ m_audio_client = nullptr;
+ return false;
+ }
+
+ m_need_data_event = CreateEvent(nullptr, FALSE, FALSE, nullptr);
+ m_audio_client->SetEventHandle(m_need_data_event);
+
+ result = m_audio_client->Start();
+
+ if (!HandleWinAPI("Failed to get IAudioRenderClient from IAudioClient", result))
+ {
+ device->Release();
+ m_audio_renderer->Release();
+ m_audio_renderer = nullptr;
+ m_audio_client->Release();
+ m_audio_client = nullptr;
+ CloseHandle(m_need_data_event);
+ return false;
+ }
+
+ device->Release();
+
+ INFO_LOG(AUDIO, "WASAPI: Successfully initialized!");
+
+ m_running = true;
+ m_thread = std::thread([this] { SoundLoop(); });
+ m_thread.detach();
+ }
+ else
+ {
+ m_running = false;
+
+ if (m_thread.joinable())
+ m_thread.join();
+
+ while (!m_stopped)
+ {
+ }
+
+ if (m_audio_client)
+ {
+ m_audio_renderer->Release();
+ m_audio_renderer = nullptr;
+
+ m_audio_client->Release();
+ m_audio_client = nullptr;
+ }
+ }
+
+ return true;
+}
+
+void WASAPIStream::SoundLoop()
+{
+ Common::SetCurrentThreadName("WASAPI Handler");
+ BYTE* data;
+
+ if (m_audio_renderer)
+ {
+ m_audio_renderer->GetBuffer(m_frames_in_buffer, &data);
+ m_audio_renderer->ReleaseBuffer(m_frames_in_buffer, AUDCLNT_BUFFERFLAGS_SILENT);
+ }
+
+ m_stopped = false;
+
+ while (m_running)
+ {
+ if (!m_audio_renderer)
+ continue;
+
+ WaitForSingleObject(m_need_data_event, 1000);
+
+ m_audio_renderer->GetBuffer(m_frames_in_buffer, &data);
+ GetMixer()->Mix(reinterpret_cast<s16*>(data), m_frames_in_buffer);
+
+ float volume = SConfig::GetInstance().m_IsMuted ? 0 : SConfig::GetInstance().m_Volume / 100.;
+
+ for (u32 i = 0; i < m_frames_in_buffer * 2; i++)
+ reinterpret_cast<s16*>(data)[i] = static_cast<s16>(reinterpret_cast<s16*>(data)[i] * volume);
+
+ m_audio_renderer->ReleaseBuffer(m_frames_in_buffer, 0);
+ }
+
+ m_stopped = true;
+}
+
+#endif // _WIN32
diff --git a/Source/Core/AudioCommon/WASAPIStream.h b/Source/Core/AudioCommon/WASAPIStream.h
new file mode 100644
index 0000000000..54becb68c0
--- /dev/null
+++ b/Source/Core/AudioCommon/WASAPIStream.h
@@ -0,0 +1,52 @@
+// Copyright 2018 Dolphin Emulator Project
+// Licensed under GPLv2+
+// Refer to the license.txt file included.
+
+#pragma once
+
+#ifdef _WIN32
+// clang-format off
+#include <Windows.h>
+#include <mmreg.h>
+// clang-format on
+#endif
+
+#include <atomic>
+#include <string>
+#include <thread>
+#include <vector>
+
+#include "AudioCommon/SoundStream.h"
+
+struct IAudioClient;
+struct IAudioRenderClient;
+struct IMMDevice;
+struct IMMDeviceEnumerator;
+
+class WASAPIStream final : public SoundStream
+{
+#ifdef _WIN32
+public:
+ explicit WASAPIStream();
+ ~WASAPIStream();
+ bool Init() override;
+ bool SetRunning(bool running) override;
+ void SoundLoop() override;
+
+ static bool isValid();
+ static std::vector<std::string> GetAvailableDevices();
+ static IMMDevice* GetDeviceByName(std::string name);
+
+private:
+ u32 m_frames_in_buffer = 0;
+ std::atomic<bool> m_running = false;
+ std::atomic<bool> m_stopped = false;
+ std::thread m_thread;
+
+ IAudioClient* m_audio_client = nullptr;
+ IAudioRenderClient* m_audio_renderer = nullptr;
+ IMMDeviceEnumerator* m_enumerator = nullptr;
+ HANDLE m_need_data_event = nullptr;
+ WAVEFORMATEXTENSIBLE m_format;
+#endif // _WIN32
+};
diff --git a/Source/Core/Core/ConfigManager.cpp b/Source/Core/Core/ConfigManager.cpp
index e7c4f42e0f..a88e6bc7fa 100644
--- a/Source/Core/Core/ConfigManager.cpp
+++ b/Source/Core/Core/ConfigManager.cpp
@@ -292,6 +292,10 @@ void SConfig::SaveDSPSettings(IniFile& ini)
dsp->Set("Backend", sBackend);
dsp->Set("Volume", m_Volume);
dsp->Set("CaptureLog", m_DSPCaptureLog);
+
+#ifdef _WIN32
+ dsp->Set("WASAPIDevice", sWASAPIDevice);
+#endif
}
void SConfig::SaveInputSettings(IniFile& ini)
@@ -589,6 +593,10 @@ void SConfig::LoadDSPSettings(IniFile& ini)
dsp->Get("Volume", &m_Volume, 100);
dsp->Get("CaptureLog", &m_DSPCaptureLog, false);
+#ifdef _WIN32
+ dsp->Get("WASAPIDevice", &sWASAPIDevice, "default");
+#endif
+
m_IsMuted = false;
}
diff --git a/Source/Core/Core/ConfigManager.h b/Source/Core/Core/ConfigManager.h
index f82598aa6e..8c2ae27461 100644
--- a/Source/Core/Core/ConfigManager.h
+++ b/Source/Core/Core/ConfigManager.h
@@ -22,14 +22,14 @@ enum class Language;
enum class Region;
struct Partition;
class Volume;
-}
+} // namespace DiscIO
namespace IOS
{
namespace ES
{
class TMDReader;
}
-}
+} // namespace IOS
// DSP Backend Types
#define BACKEND_NULLSOUND _trans("No Audio Output")
@@ -39,6 +39,7 @@ class TMDReader;
#define BACKEND_PULSEAUDIO "Pulse"
#define BACKEND_XAUDIO2 "XAudio2"
#define BACKEND_OPENSLES "OpenSLES"
+#define BACKEND_WASAPI "WASAPI (Exclusive Mode)"
enum GPUDeterminismMode
{
@@ -298,6 +299,11 @@ struct SConfig
int m_Volume;
std::string sBackend;
+#ifdef _WIN32
+ // WSAPI settings
+ std::string sWASAPIDevice;
+#endif
+
// Input settings
bool m_BackgroundInput;
bool m_AdapterRumble[4];
diff --git a/Source/Core/DolphinQt2/Settings/AudioPane.cpp b/Source/Core/DolphinQt2/Settings/AudioPane.cpp
index b8948ef5c9..bbdc7b51fc 100644
--- a/Source/Core/DolphinQt2/Settings/AudioPane.cpp
+++ b/Source/Core/DolphinQt2/Settings/AudioPane.cpp
@@ -17,6 +17,7 @@
#include <QVBoxLayout>
#include "AudioCommon/AudioCommon.h"
+#include "AudioCommon/WASAPIStream.h"
#include "Core/ConfigManager.h"
#include "Core/Core.h"
#include "DolphinQt2/Config/SettingsWindow.h"
@@ -88,6 +89,14 @@ void AudioPane::CreateWidgets()
backend_layout->addRow(m_backend_label, m_backend_combo);
if (m_latency_control_supported)
backend_layout->addRow(m_latency_label, m_latency_spin);
+
+#ifdef _WIN32
+ m_wasapi_device_label = new QLabel(tr("Device:"));
+ m_wasapi_device_combo = new QComboBox;
+
+ backend_layout->addRow(m_wasapi_device_label, m_wasapi_device_combo);
+#endif
+
backend_layout->addRow(m_dolby_pro_logic);
auto* stretching_box = new QGroupBox(tr("Audio Stretching Settings"));
@@ -140,6 +149,12 @@ void AudioPane::ConnectWidgets()
connect(m_dsp_hle, &QRadioButton::toggled, this, &AudioPane::SaveSettings);
connect(m_dsp_lle, &QRadioButton::toggled, this, &AudioPane::SaveSettings);
connect(m_dsp_interpreter, &QRadioButton::toggled, this, &AudioPane::SaveSettings);
+
+#ifdef _WIN32
+ connect(m_wasapi_device_combo,
+ static_cast<void (QComboBox::*)(int)>(&QComboBox::currentIndexChanged), this,
+ &AudioPane::SaveSettings);
+#endif
}
void AudioPane::LoadSettings()
@@ -183,6 +198,18 @@ void AudioPane::LoadSettings()
m_stretching_buffer_slider->setValue(SConfig::GetInstance().m_audio_stretch_max_latency);
m_stretching_buffer_slider->setEnabled(m_stretching_enable->isChecked());
m_stretching_buffer_indicator->setText(tr("%1 ms").arg(m_stretching_buffer_slider->value()));
+
+#ifdef _WIN32
+ if (SConfig::GetInstance().sWASAPIDevice == "default")
+ {
+ m_wasapi_device_combo->setCurrentIndex(0);
+ }
+ else
+ {
+ m_wasapi_device_combo->setCurrentText(
+ QString::fromStdString(SConfig::GetInstance().sWASAPIDevice));
+ }
+#endif
}
void AudioPane::SaveSettings()
@@ -227,6 +254,15 @@ void AudioPane::SaveSettings()
m_stretching_buffer_indicator->setText(
tr("%1 ms").arg(SConfig::GetInstance().m_audio_stretch_max_latency));
+#ifdef _WIN32
+ std::string device = "default";
+
+ if (m_wasapi_device_combo->currentIndex() != 0)
+ device = m_wasapi_device_combo->currentText().toStdString();
+
+ SConfig::GetInstance().sWASAPIDevice = device;
+#endif
+
AudioCommon::UpdateSoundStream();
}
@@ -240,6 +276,22 @@ void AudioPane::OnBackendChanged()
m_latency_label->setEnabled(AudioCommon::SupportsLatencyControl(backend));
m_latency_spin->setEnabled(AudioCommon::SupportsLatencyControl(backend));
}
+
+#ifdef _WIN32
+ bool is_wasapi = backend == BACKEND_WASAPI;
+ m_wasapi_device_label->setHidden(!is_wasapi);
+ m_wasapi_device_combo->setHidden(!is_wasapi);
+
+ if (is_wasapi)
+ {
+ m_wasapi_device_combo->clear();
+ m_wasapi_device_combo->addItem(tr("Default Device"));
+
+ for (const auto device : WASAPIStream::GetAvailableDevices())
+ m_wasapi_device_combo->addItem(QString::fromStdString(device));
+ }
+#endif
+
m_volume_slider->setEnabled(AudioCommon::SupportsVolumeChanges(backend));
m_volume_indicator->setEnabled(AudioCommon::SupportsVolumeChanges(backend));
}
@@ -257,6 +309,10 @@ void AudioPane::OnEmulationStateChanged(bool running)
m_latency_label->setEnabled(!running);
m_latency_spin->setEnabled(!running);
}
+
+#ifdef _WIN32
+ m_wasapi_device_combo->setEnabled(!running);
+#endif
}
void AudioPane::OnVolumeChanged(int volume)
diff --git a/Source/Core/DolphinQt2/Settings/AudioPane.h b/Source/Core/DolphinQt2/Settings/AudioPane.h
index cdef7c0566..45bd6d6c60 100644
--- a/Source/Core/DolphinQt2/Settings/AudioPane.h
+++ b/Source/Core/DolphinQt2/Settings/AudioPane.h
@@ -52,6 +52,10 @@ private:
QCheckBox* m_dolby_pro_logic;
QLabel* m_latency_label;
QSpinBox* m_latency_spin;
+#ifdef _WIN32
+ QLabel* m_wasapi_device_label;
+ QComboBox* m_wasapi_device_combo;
+#endif
// Audio Stretching
QCheckBox* m_stretching_enable;