diff options
| author | spycrab <spycrab@users.noreply.github.com> | 2018-02-10 21:03:27 +0100 |
|---|---|---|
| committer | spycrab <spycrab@users.noreply.github.com> | 2018-05-26 13:08:10 +0200 |
| commit | a196dfe50d1d8a2fcd2ffbe5c8eea10687f1698f (patch) | |
| tree | 74a4c496557048d4f505e66ec5337f82aed44f89 /Source/Core/AudioCommon | |
| parent | 92ec97f8997fbd511406c09d83a79728cae5ec08 (diff) | |
AudioCommon: Implement WASAPI
Diffstat (limited to 'Source/Core/AudioCommon')
| -rw-r--r-- | Source/Core/AudioCommon/AudioCommon.cpp | 13 | ||||
| -rw-r--r-- | Source/Core/AudioCommon/AudioCommon.vcxproj | 4 | ||||
| -rw-r--r-- | Source/Core/AudioCommon/AudioCommon.vcxproj.filters | 8 | ||||
| -rw-r--r-- | Source/Core/AudioCommon/CMakeLists.txt | 1 | ||||
| -rw-r--r-- | Source/Core/AudioCommon/WASAPIStream.cpp | 451 | ||||
| -rw-r--r-- | Source/Core/AudioCommon/WASAPIStream.h | 52 |
6 files changed, 524 insertions, 5 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 +}; |
