Windows/Audio: add listener for device change

For some reason XAudio2 doesn't automatically change the device anymore.
So let's just listen for the OnDefaultDeviceChanged event and update the cell audio thread if necessary.
This commit is contained in:
Megamouse 2021-08-23 21:33:20 +02:00
parent 4e139ee080
commit 72f0637efe
10 changed files with 161 additions and 8 deletions

View File

@ -16,7 +16,7 @@
#endif
#ifdef _WIN32
std::string wchar_to_utf8(wchar_t *src)
std::string wchar_to_utf8(const wchar_t *src)
{
std::string utf8_string;
const auto tmp_size = WideCharToMultiByte(CP_UTF8, 0, src, -1, nullptr, 0, nullptr, nullptr);

View File

@ -7,7 +7,7 @@
#include <string_view>
#ifdef _WIN32
std::string wchar_to_utf8(wchar_t *src);
std::string wchar_to_utf8(const wchar_t *src);
std::string wchar_path_to_ansi_path(const std::wstring& src);
std::string utf8_path_to_ansi_path(const std::string& src);
#endif

View File

@ -21,9 +21,14 @@ XAudio2Backend::XAudio2Backend()
// In order to prevent errors on CreateMasteringVoice, apparently we need CoInitializeEx according to:
// https://docs.microsoft.com/en-us/windows/win32/api/xaudio2fx/nf-xaudio2fx-xaudio2createvolumemeter
CoInitializeEx(nullptr, COINIT_MULTITHREADED);
HRESULT hr = CoInitializeEx(nullptr, COINIT_MULTITHREADED);
if (FAILED(hr) && hr != RPC_E_CHANGED_MODE)
{
XAudio.error("CoInitializeEx() failed: %s (0x%08x)", std::system_category().message(hr), static_cast<u32>(hr));
return;
}
HRESULT hr = XAudio2Create(instance.GetAddressOf(), 0, XAUDIO2_DEFAULT_PROCESSOR);
hr = XAudio2Create(instance.GetAddressOf(), 0, XAUDIO2_DEFAULT_PROCESSOR);
if (FAILED(hr))
{
XAudio.error("XAudio2Create() failed: %s (0x%08x)", std::system_category().message(hr), static_cast<u32>(hr));
@ -90,7 +95,7 @@ void XAudio2Backend::Pause()
void XAudio2Backend::Open(u32 /* num_buffers */)
{
WAVEFORMATEX waveformatex;
WAVEFORMATEX waveformatex{};
waveformatex.wFormatTag = m_convert_to_u16 ? WAVE_FORMAT_PCM : WAVE_FORMAT_IEEE_FLOAT;
waveformatex.nChannels = m_channels;
waveformatex.nSamplesPerSec = m_sampling_rate;
@ -133,8 +138,7 @@ bool XAudio2Backend::AddData(const void* src, u32 num_samples)
return false;
}
XAUDIO2_BUFFER buffer;
XAUDIO2_BUFFER buffer{};
buffer.AudioBytes = num_samples * m_sample_size;
buffer.Flags = 0;
buffer.LoopBegin = XAUDIO2_NO_LOOP_REGION;

View File

@ -0,0 +1,107 @@
#include "stdafx.h"
#include "audio_device_listener.h"
#include "util/logs.hpp"
#include "Utilities/StrUtil.h"
#include "Emu/Cell/Modules/cellAudio.h"
#include "Emu/IdManager.h"
LOG_CHANNEL(IO);
audio_device_listener::audio_device_listener()
{
#ifdef _WIN32
// Try to register a listener for device changes
HRESULT hr = CoCreateInstance(__uuidof(MMDeviceEnumerator), nullptr, CLSCTX_INPROC_SERVER, IID_PPV_ARGS(&m_device_enumerator));
if (hr != S_OK)
{
IO.error("CoCreateInstance() failed: %s (0x%08x)", std::system_category().message(hr), static_cast<u32>(hr));
}
else if (m_device_enumerator)
{
m_device_enumerator->RegisterEndpointNotificationCallback(&m_listener);
}
else
{
IO.error("Device enumerator invalid");
}
#endif
}
audio_device_listener::~audio_device_listener()
{
#ifdef _WIN32
if (m_device_enumerator != nullptr)
{
m_device_enumerator->Release();
}
#endif
}
#ifdef _WIN32
template <>
void fmt_class_string<ERole>::format(std::string& out, u64 arg)
{
format_enum(out, arg, [](auto value)
{
switch (value)
{
case eConsole: return "eConsole";
case eMultimedia: return "eMultimedia";
case eCommunications: return "eCommunications";
}
return unknown;
});
}
template <>
void fmt_class_string<EDataFlow>::format(std::string& out, u64 arg)
{
format_enum(out, arg, [](auto value)
{
switch (value)
{
case eRender: return "eRender";
case eCapture: return "eCapture";
case eAll: return "eAll";
}
return unknown;
});
}
HRESULT audio_device_listener::listener::OnDefaultDeviceChanged(EDataFlow flow, ERole role, LPCWSTR new_default_device_id)
{
IO.notice("OnDefaultDeviceChanged(flow=%s, role=%s, new_default_device_id=0x%x)", flow, role, new_default_device_id);
if (!new_default_device_id)
{
IO.notice("OnDefaultDeviceChanged(): new_default_device_id empty");
return S_OK;
}
// Only listen for console and communication device changes.
if ((role != eConsole && role != eCommunications) || (flow != eRender && flow != eCapture))
{
IO.notice("OnDefaultDeviceChanged(): we don't care about this device");
return S_OK;
}
const std::wstring tmp(new_default_device_id);
const std::string new_device_id = wchar_to_utf8(tmp.c_str());
if (device_id != new_device_id)
{
device_id = new_device_id;
IO.warning("Default device changed: new device = '%s'", device_id);
if (auto& g_audio = g_fxo->get<cell_audio>(); g_fxo->is_init<cell_audio>())
{
g_audio.m_update_configuration = true;
}
}
return S_OK;
}
#endif

View File

@ -0,0 +1,31 @@
#pragma once
#ifdef _WIN32
#include <MMDeviceAPI.h>
#endif
class audio_device_listener
{
public:
audio_device_listener();
~audio_device_listener();
private:
#ifdef _WIN32
struct listener : public IMMNotificationClient
{
std::string device_id;
IFACEMETHODIMP_(ULONG) AddRef() override { return 1; };
IFACEMETHODIMP_(ULONG) Release() override { return 1; };
IFACEMETHODIMP QueryInterface(REFIID iid, void** object) override { return S_OK; };
IFACEMETHODIMP OnPropertyValueChanged(LPCWSTR device_id, const PROPERTYKEY key) override { return S_OK; };
IFACEMETHODIMP OnDeviceAdded(LPCWSTR device_id) override { return S_OK; };
IFACEMETHODIMP OnDeviceRemoved(LPCWSTR device_id) override { return S_OK; };
IFACEMETHODIMP OnDeviceStateChanged(LPCWSTR device_id, DWORD new_state) override { return S_OK; };
IFACEMETHODIMP OnDefaultDeviceChanged(EDataFlow flow, ERole role, LPCWSTR new_default_device_id) override;
} m_listener;
IMMDeviceEnumerator* m_device_enumerator = nullptr;
#endif
};

View File

@ -106,6 +106,7 @@ target_sources(rpcs3_emu PRIVATE
# Audio
target_sources(rpcs3_emu PRIVATE
Audio/audio_device_listener.cpp
Audio/AudioDumper.cpp
Audio/AudioBackend.cpp
Audio/AL/OpenALBackend.cpp

View File

@ -305,7 +305,6 @@ u64 audio_ringbuffer::update()
//cellAudio.error("play_delta=%llu delta_samples=%llu", play_delta, delta_samples);
if (delta_samples > 0)
{
if (enqueued_samples < delta_samples)
{
enqueued_samples = 0;
@ -618,6 +617,7 @@ void cell_audio_thread::operator()()
{
if (m_update_configuration)
{
cellAudio.warning("Updating cell_audio_thread configuration");
update_config();
m_update_configuration = false;
}

View File

@ -3,6 +3,7 @@
#include "Emu/Memory/vm_ptr.h"
#include "Utilities/Thread.h"
#include "Emu/Memory/vm.h"
#include "Emu/Audio/audio_device_listener.h"
#include "Emu/Audio/AudioBackend.h"
#include "Emu/Audio/AudioDumper.h"
#include "Emu/system_config_types.h"
@ -353,6 +354,7 @@ class cell_audio_thread
{
private:
std::unique_ptr<audio_ringbuffer> ringbuffer;
audio_device_listener listener;
void reset_ports(s32 offset = 0);
void advance(u64 timestamp, bool reset = true);

View File

@ -53,6 +53,7 @@
<ClCompile Include="Emu\Audio\ALSA\ALSABackend.cpp">
<ExcludedFromBuild>true</ExcludedFromBuild>
</ClCompile>
<ClCompile Include="Emu\Audio\audio_device_listener.cpp" />
<ClCompile Include="Emu\Audio\FAudio\FAudioBackend.cpp">
<ExcludedFromBuild>true</ExcludedFromBuild>
</ClCompile>
@ -428,6 +429,7 @@
<ClInclude Include="Emu\Audio\ALSA\ALSABackend.h">
<ExcludedFromBuild>true</ExcludedFromBuild>
</ClInclude>
<ClInclude Include="Emu\Audio\audio_device_listener.h" />
<ClInclude Include="Emu\Audio\FAudio\FAudioBackend.h">
<ExcludedFromBuild>true</ExcludedFromBuild>
</ClInclude>

View File

@ -999,6 +999,9 @@
<ClCompile Include="Emu\Cell\Modules\libfs_utility_init.cpp">
<Filter>Emu\Cell\Modules</Filter>
</ClCompile>
<ClCompile Include="Emu\Audio\audio_device_listener.cpp">
<Filter>Emu\Audio</Filter>
</ClCompile>
</ItemGroup>
<ItemGroup>
<ClInclude Include="Crypto\aes.h">
@ -1974,6 +1977,9 @@
<ClInclude Include="Emu\Cell\Modules\libfs_utility_init.h">
<Filter>Emu\Cell\Modules</Filter>
</ClInclude>
<ClInclude Include="Emu\Audio\audio_device_listener.h">
<Filter>Emu\Audio</Filter>
</ClInclude>
</ItemGroup>
<ItemGroup>
<None Include="Emu\RSX\Common\Interpreter\FragmentInterpreter.glsl">