From a196dfe50d1d8a2fcd2ffbe5c8eea10687f1698f Mon Sep 17 00:00:00 2001 From: spycrab Date: Sat, 10 Feb 2018 21:03:27 +0100 Subject: [PATCH] AudioCommon: Implement WASAPI --- Source/Core/AudioCommon/AudioCommon.cpp | 13 +- Source/Core/AudioCommon/AudioCommon.vcxproj | 4 +- .../AudioCommon/AudioCommon.vcxproj.filters | 8 +- Source/Core/AudioCommon/CMakeLists.txt | 1 + Source/Core/AudioCommon/WASAPIStream.cpp | 451 ++++++++++++++++++ Source/Core/AudioCommon/WASAPIStream.h | 52 ++ Source/Core/Core/ConfigManager.cpp | 8 + Source/Core/Core/ConfigManager.h | 10 +- Source/Core/DolphinQt2/Settings/AudioPane.cpp | 56 +++ Source/Core/DolphinQt2/Settings/AudioPane.h | 4 + 10 files changed, 600 insertions(+), 7 deletions(-) create mode 100644 Source/Core/AudioCommon/WASAPIStream.cpp create mode 100644 Source/Core/AudioCommon/WASAPIStream.h 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(); else if (backend == BACKEND_OPENSLES && OpenSLESStream::isValid()) g_sound_stream = std::make_unique(); + else if (backend == BACKEND_WASAPI && WASAPIStream::isValid()) + g_sound_stream = std::make_unique(); if (!g_sound_stream || !g_sound_stream->Init()) { @@ -110,6 +113,9 @@ std::vector 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 @@ + @@ -63,6 +64,7 @@ + @@ -81,4 +83,4 @@ - + \ 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 @@ SoundStreams + + SoundStreams + @@ -62,8 +65,11 @@ SoundStreams + + SoundStreams + - + \ 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 +#include +#include +#include +#include +#include +// 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 WASAPIStream::GetAvailableDevices() +{ + CoInitialize(nullptr); + + IMMDeviceEnumerator* enumerator = nullptr; + + HRESULT result = + CoCreateInstance(__uuidof(MMDeviceEnumerator), nullptr, CLSCTX_INPROC_SERVER, + __uuidof(IMMDeviceEnumerator), reinterpret_cast(&enumerator)); + + if (!HandleWinAPI("Failed to create MMDeviceEnumerator", result)) + return {}; + + std::vector 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(&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(&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(&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(&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(&m_audio_client)); + + if (!HandleWinAPI("Failed to reactivate IAudioClient", result)) + { + device->Release(); + return false; + } + + device_period = + static_cast( + 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(&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(&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(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(data)[i] = static_cast(reinterpret_cast(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 +#include +// clang-format on +#endif + +#include +#include +#include +#include + +#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 GetAvailableDevices(); + static IMMDevice* GetDeviceByName(std::string name); + +private: + u32 m_frames_in_buffer = 0; + std::atomic m_running = false; + std::atomic 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 #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(&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;