AudioCommon: Implement WASAPI
This commit is contained in:
parent
92ec97f899
commit
a196dfe50d
|
@ -10,6 +10,7 @@
|
||||||
#include "AudioCommon/OpenALStream.h"
|
#include "AudioCommon/OpenALStream.h"
|
||||||
#include "AudioCommon/OpenSLESStream.h"
|
#include "AudioCommon/OpenSLESStream.h"
|
||||||
#include "AudioCommon/PulseAudioStream.h"
|
#include "AudioCommon/PulseAudioStream.h"
|
||||||
|
#include "AudioCommon/WASAPIStream.h"
|
||||||
#include "AudioCommon/XAudio2Stream.h"
|
#include "AudioCommon/XAudio2Stream.h"
|
||||||
#include "AudioCommon/XAudio2_7Stream.h"
|
#include "AudioCommon/XAudio2_7Stream.h"
|
||||||
#include "Common/Common.h"
|
#include "Common/Common.h"
|
||||||
|
@ -50,6 +51,8 @@ void InitSoundStream()
|
||||||
g_sound_stream = std::make_unique<PulseAudio>();
|
g_sound_stream = std::make_unique<PulseAudio>();
|
||||||
else if (backend == BACKEND_OPENSLES && OpenSLESStream::isValid())
|
else if (backend == BACKEND_OPENSLES && OpenSLESStream::isValid())
|
||||||
g_sound_stream = std::make_unique<OpenSLESStream>();
|
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())
|
if (!g_sound_stream || !g_sound_stream->Init())
|
||||||
{
|
{
|
||||||
|
@ -110,6 +113,9 @@ std::vector<std::string> GetSoundBackends()
|
||||||
backends.push_back(BACKEND_OPENAL);
|
backends.push_back(BACKEND_OPENAL);
|
||||||
if (OpenSLESStream::isValid())
|
if (OpenSLESStream::isValid())
|
||||||
backends.push_back(BACKEND_OPENSLES);
|
backends.push_back(BACKEND_OPENSLES);
|
||||||
|
if (WASAPIStream::isValid())
|
||||||
|
backends.push_back(BACKEND_WASAPI);
|
||||||
|
|
||||||
return backends;
|
return backends;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -128,7 +134,7 @@ bool SupportsDPL2Decoder(const std::string& backend)
|
||||||
|
|
||||||
bool SupportsLatencyControl(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)
|
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.
|
// FIXME: this one should ask the backend whether it supports it.
|
||||||
// but getting the backend from string etc. is probably
|
// but getting the backend from string etc. is probably
|
||||||
// too much just to enable/disable a stupid slider...
|
// 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()
|
void UpdateSoundStream()
|
||||||
|
@ -231,4 +238,4 @@ void ToggleMuteVolume()
|
||||||
isMuted = !isMuted;
|
isMuted = !isMuted;
|
||||||
UpdateSoundStream();
|
UpdateSoundStream();
|
||||||
}
|
}
|
||||||
}
|
} // namespace AudioCommon
|
||||||
|
|
|
@ -44,6 +44,7 @@
|
||||||
<ClCompile Include="Mixer.cpp" />
|
<ClCompile Include="Mixer.cpp" />
|
||||||
<ClCompile Include="NullSoundStream.cpp" />
|
<ClCompile Include="NullSoundStream.cpp" />
|
||||||
<ClCompile Include="OpenALStream.cpp" />
|
<ClCompile Include="OpenALStream.cpp" />
|
||||||
|
<ClCompile Include="WASAPIStream.cpp" />
|
||||||
<ClCompile Include="WaveFile.cpp" />
|
<ClCompile Include="WaveFile.cpp" />
|
||||||
<ClCompile Include="XAudio2Stream.cpp" />
|
<ClCompile Include="XAudio2Stream.cpp" />
|
||||||
<ClCompile Include="XAudio2_7Stream.cpp">
|
<ClCompile Include="XAudio2_7Stream.cpp">
|
||||||
|
@ -63,6 +64,7 @@
|
||||||
<ClInclude Include="OpenSLESStream.h" />
|
<ClInclude Include="OpenSLESStream.h" />
|
||||||
<ClInclude Include="PulseAudioStream.h" />
|
<ClInclude Include="PulseAudioStream.h" />
|
||||||
<ClInclude Include="SoundStream.h" />
|
<ClInclude Include="SoundStream.h" />
|
||||||
|
<ClInclude Include="WASAPIStream.h" />
|
||||||
<ClInclude Include="WaveFile.h" />
|
<ClInclude Include="WaveFile.h" />
|
||||||
<ClInclude Include="XAudio2Stream.h" />
|
<ClInclude Include="XAudio2Stream.h" />
|
||||||
<ClInclude Include="XAudio2_7Stream.h" />
|
<ClInclude Include="XAudio2_7Stream.h" />
|
||||||
|
@ -81,4 +83,4 @@
|
||||||
<Import Project="$(VCTargetsPath)\Microsoft.Cpp.targets" />
|
<Import Project="$(VCTargetsPath)\Microsoft.Cpp.targets" />
|
||||||
<ImportGroup Label="ExtensionTargets">
|
<ImportGroup Label="ExtensionTargets">
|
||||||
</ImportGroup>
|
</ImportGroup>
|
||||||
</Project>
|
</Project>
|
|
@ -27,6 +27,9 @@
|
||||||
<ClCompile Include="CubebStream.cpp">
|
<ClCompile Include="CubebStream.cpp">
|
||||||
<Filter>SoundStreams</Filter>
|
<Filter>SoundStreams</Filter>
|
||||||
</ClCompile>
|
</ClCompile>
|
||||||
|
<ClCompile Include="WASAPIStream.cpp">
|
||||||
|
<Filter>SoundStreams</Filter>
|
||||||
|
</ClCompile>
|
||||||
</ItemGroup>
|
</ItemGroup>
|
||||||
<ItemGroup>
|
<ItemGroup>
|
||||||
<ClInclude Include="AudioCommon.h" />
|
<ClInclude Include="AudioCommon.h" />
|
||||||
|
@ -62,8 +65,11 @@
|
||||||
<ClInclude Include="CubebStream.h">
|
<ClInclude Include="CubebStream.h">
|
||||||
<Filter>SoundStreams</Filter>
|
<Filter>SoundStreams</Filter>
|
||||||
</ClInclude>
|
</ClInclude>
|
||||||
|
<ClInclude Include="WASAPIStream.h">
|
||||||
|
<Filter>SoundStreams</Filter>
|
||||||
|
</ClInclude>
|
||||||
</ItemGroup>
|
</ItemGroup>
|
||||||
<ItemGroup>
|
<ItemGroup>
|
||||||
<Text Include="CMakeLists.txt" />
|
<Text Include="CMakeLists.txt" />
|
||||||
</ItemGroup>
|
</ItemGroup>
|
||||||
</Project>
|
</Project>
|
|
@ -47,6 +47,7 @@ else()
|
||||||
endif()
|
endif()
|
||||||
|
|
||||||
if(WIN32)
|
if(WIN32)
|
||||||
|
target_sources(audiocommon PRIVATE WASAPIStream.cpp)
|
||||||
target_sources(audiocommon PRIVATE XAudio2Stream.cpp)
|
target_sources(audiocommon PRIVATE XAudio2Stream.cpp)
|
||||||
|
|
||||||
add_library(audiocommon_xaudio27 "XAudio2_7Stream.cpp")
|
add_library(audiocommon_xaudio27 "XAudio2_7Stream.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
|
|
@ -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
|
||||||
|
};
|
|
@ -292,6 +292,10 @@ void SConfig::SaveDSPSettings(IniFile& ini)
|
||||||
dsp->Set("Backend", sBackend);
|
dsp->Set("Backend", sBackend);
|
||||||
dsp->Set("Volume", m_Volume);
|
dsp->Set("Volume", m_Volume);
|
||||||
dsp->Set("CaptureLog", m_DSPCaptureLog);
|
dsp->Set("CaptureLog", m_DSPCaptureLog);
|
||||||
|
|
||||||
|
#ifdef _WIN32
|
||||||
|
dsp->Set("WASAPIDevice", sWASAPIDevice);
|
||||||
|
#endif
|
||||||
}
|
}
|
||||||
|
|
||||||
void SConfig::SaveInputSettings(IniFile& ini)
|
void SConfig::SaveInputSettings(IniFile& ini)
|
||||||
|
@ -589,6 +593,10 @@ void SConfig::LoadDSPSettings(IniFile& ini)
|
||||||
dsp->Get("Volume", &m_Volume, 100);
|
dsp->Get("Volume", &m_Volume, 100);
|
||||||
dsp->Get("CaptureLog", &m_DSPCaptureLog, false);
|
dsp->Get("CaptureLog", &m_DSPCaptureLog, false);
|
||||||
|
|
||||||
|
#ifdef _WIN32
|
||||||
|
dsp->Get("WASAPIDevice", &sWASAPIDevice, "default");
|
||||||
|
#endif
|
||||||
|
|
||||||
m_IsMuted = false;
|
m_IsMuted = false;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -22,14 +22,14 @@ enum class Language;
|
||||||
enum class Region;
|
enum class Region;
|
||||||
struct Partition;
|
struct Partition;
|
||||||
class Volume;
|
class Volume;
|
||||||
}
|
} // namespace DiscIO
|
||||||
namespace IOS
|
namespace IOS
|
||||||
{
|
{
|
||||||
namespace ES
|
namespace ES
|
||||||
{
|
{
|
||||||
class TMDReader;
|
class TMDReader;
|
||||||
}
|
}
|
||||||
}
|
} // namespace IOS
|
||||||
|
|
||||||
// DSP Backend Types
|
// DSP Backend Types
|
||||||
#define BACKEND_NULLSOUND _trans("No Audio Output")
|
#define BACKEND_NULLSOUND _trans("No Audio Output")
|
||||||
|
@ -39,6 +39,7 @@ class TMDReader;
|
||||||
#define BACKEND_PULSEAUDIO "Pulse"
|
#define BACKEND_PULSEAUDIO "Pulse"
|
||||||
#define BACKEND_XAUDIO2 "XAudio2"
|
#define BACKEND_XAUDIO2 "XAudio2"
|
||||||
#define BACKEND_OPENSLES "OpenSLES"
|
#define BACKEND_OPENSLES "OpenSLES"
|
||||||
|
#define BACKEND_WASAPI "WASAPI (Exclusive Mode)"
|
||||||
|
|
||||||
enum GPUDeterminismMode
|
enum GPUDeterminismMode
|
||||||
{
|
{
|
||||||
|
@ -298,6 +299,11 @@ struct SConfig
|
||||||
int m_Volume;
|
int m_Volume;
|
||||||
std::string sBackend;
|
std::string sBackend;
|
||||||
|
|
||||||
|
#ifdef _WIN32
|
||||||
|
// WSAPI settings
|
||||||
|
std::string sWASAPIDevice;
|
||||||
|
#endif
|
||||||
|
|
||||||
// Input settings
|
// Input settings
|
||||||
bool m_BackgroundInput;
|
bool m_BackgroundInput;
|
||||||
bool m_AdapterRumble[4];
|
bool m_AdapterRumble[4];
|
||||||
|
|
|
@ -17,6 +17,7 @@
|
||||||
#include <QVBoxLayout>
|
#include <QVBoxLayout>
|
||||||
|
|
||||||
#include "AudioCommon/AudioCommon.h"
|
#include "AudioCommon/AudioCommon.h"
|
||||||
|
#include "AudioCommon/WASAPIStream.h"
|
||||||
#include "Core/ConfigManager.h"
|
#include "Core/ConfigManager.h"
|
||||||
#include "Core/Core.h"
|
#include "Core/Core.h"
|
||||||
#include "DolphinQt2/Config/SettingsWindow.h"
|
#include "DolphinQt2/Config/SettingsWindow.h"
|
||||||
|
@ -88,6 +89,14 @@ void AudioPane::CreateWidgets()
|
||||||
backend_layout->addRow(m_backend_label, m_backend_combo);
|
backend_layout->addRow(m_backend_label, m_backend_combo);
|
||||||
if (m_latency_control_supported)
|
if (m_latency_control_supported)
|
||||||
backend_layout->addRow(m_latency_label, m_latency_spin);
|
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);
|
backend_layout->addRow(m_dolby_pro_logic);
|
||||||
|
|
||||||
auto* stretching_box = new QGroupBox(tr("Audio Stretching Settings"));
|
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_hle, &QRadioButton::toggled, this, &AudioPane::SaveSettings);
|
||||||
connect(m_dsp_lle, &QRadioButton::toggled, this, &AudioPane::SaveSettings);
|
connect(m_dsp_lle, &QRadioButton::toggled, this, &AudioPane::SaveSettings);
|
||||||
connect(m_dsp_interpreter, &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()
|
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->setValue(SConfig::GetInstance().m_audio_stretch_max_latency);
|
||||||
m_stretching_buffer_slider->setEnabled(m_stretching_enable->isChecked());
|
m_stretching_buffer_slider->setEnabled(m_stretching_enable->isChecked());
|
||||||
m_stretching_buffer_indicator->setText(tr("%1 ms").arg(m_stretching_buffer_slider->value()));
|
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()
|
void AudioPane::SaveSettings()
|
||||||
|
@ -227,6 +254,15 @@ void AudioPane::SaveSettings()
|
||||||
m_stretching_buffer_indicator->setText(
|
m_stretching_buffer_indicator->setText(
|
||||||
tr("%1 ms").arg(SConfig::GetInstance().m_audio_stretch_max_latency));
|
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();
|
AudioCommon::UpdateSoundStream();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -240,6 +276,22 @@ void AudioPane::OnBackendChanged()
|
||||||
m_latency_label->setEnabled(AudioCommon::SupportsLatencyControl(backend));
|
m_latency_label->setEnabled(AudioCommon::SupportsLatencyControl(backend));
|
||||||
m_latency_spin->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_slider->setEnabled(AudioCommon::SupportsVolumeChanges(backend));
|
||||||
m_volume_indicator->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_label->setEnabled(!running);
|
||||||
m_latency_spin->setEnabled(!running);
|
m_latency_spin->setEnabled(!running);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
#ifdef _WIN32
|
||||||
|
m_wasapi_device_combo->setEnabled(!running);
|
||||||
|
#endif
|
||||||
}
|
}
|
||||||
|
|
||||||
void AudioPane::OnVolumeChanged(int volume)
|
void AudioPane::OnVolumeChanged(int volume)
|
||||||
|
|
|
@ -52,6 +52,10 @@ private:
|
||||||
QCheckBox* m_dolby_pro_logic;
|
QCheckBox* m_dolby_pro_logic;
|
||||||
QLabel* m_latency_label;
|
QLabel* m_latency_label;
|
||||||
QSpinBox* m_latency_spin;
|
QSpinBox* m_latency_spin;
|
||||||
|
#ifdef _WIN32
|
||||||
|
QLabel* m_wasapi_device_label;
|
||||||
|
QComboBox* m_wasapi_device_combo;
|
||||||
|
#endif
|
||||||
|
|
||||||
// Audio Stretching
|
// Audio Stretching
|
||||||
QCheckBox* m_stretching_enable;
|
QCheckBox* m_stretching_enable;
|
||||||
|
|
Loading…
Reference in New Issue