From 72f0637efe3935ab558130dc66e39b8afad9c48b Mon Sep 17 00:00:00 2001 From: Megamouse Date: Mon, 23 Aug 2021 21:33:20 +0200 Subject: [PATCH] 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. --- Utilities/StrFmt.cpp | 2 +- Utilities/StrUtil.h | 2 +- rpcs3/Emu/Audio/XAudio2/XAudio2Backend.cpp | 14 ++- rpcs3/Emu/Audio/audio_device_listener.cpp | 107 +++++++++++++++++++++ rpcs3/Emu/Audio/audio_device_listener.h | 31 ++++++ rpcs3/Emu/CMakeLists.txt | 1 + rpcs3/Emu/Cell/Modules/cellAudio.cpp | 2 +- rpcs3/Emu/Cell/Modules/cellAudio.h | 2 + rpcs3/emucore.vcxproj | 2 + rpcs3/emucore.vcxproj.filters | 6 ++ 10 files changed, 161 insertions(+), 8 deletions(-) create mode 100644 rpcs3/Emu/Audio/audio_device_listener.cpp create mode 100644 rpcs3/Emu/Audio/audio_device_listener.h diff --git a/Utilities/StrFmt.cpp b/Utilities/StrFmt.cpp index 689b71de7b..63eccf8883 100644 --- a/Utilities/StrFmt.cpp +++ b/Utilities/StrFmt.cpp @@ -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); diff --git a/Utilities/StrUtil.h b/Utilities/StrUtil.h index 922b0fba16..f305fecf1d 100644 --- a/Utilities/StrUtil.h +++ b/Utilities/StrUtil.h @@ -7,7 +7,7 @@ #include #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 diff --git a/rpcs3/Emu/Audio/XAudio2/XAudio2Backend.cpp b/rpcs3/Emu/Audio/XAudio2/XAudio2Backend.cpp index 2ecba851f5..defd8a3cc3 100644 --- a/rpcs3/Emu/Audio/XAudio2/XAudio2Backend.cpp +++ b/rpcs3/Emu/Audio/XAudio2/XAudio2Backend.cpp @@ -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(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(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; diff --git a/rpcs3/Emu/Audio/audio_device_listener.cpp b/rpcs3/Emu/Audio/audio_device_listener.cpp new file mode 100644 index 0000000000..03f86d620c --- /dev/null +++ b/rpcs3/Emu/Audio/audio_device_listener.cpp @@ -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(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::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::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(); g_fxo->is_init()) + { + g_audio.m_update_configuration = true; + } + } + + return S_OK; +} +#endif diff --git a/rpcs3/Emu/Audio/audio_device_listener.h b/rpcs3/Emu/Audio/audio_device_listener.h new file mode 100644 index 0000000000..4d5e51fe01 --- /dev/null +++ b/rpcs3/Emu/Audio/audio_device_listener.h @@ -0,0 +1,31 @@ +#pragma once + +#ifdef _WIN32 +#include +#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 +}; diff --git a/rpcs3/Emu/CMakeLists.txt b/rpcs3/Emu/CMakeLists.txt index 457a7ccf75..38c8f29a53 100644 --- a/rpcs3/Emu/CMakeLists.txt +++ b/rpcs3/Emu/CMakeLists.txt @@ -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 diff --git a/rpcs3/Emu/Cell/Modules/cellAudio.cpp b/rpcs3/Emu/Cell/Modules/cellAudio.cpp index a596e6db4f..670d40f41c 100644 --- a/rpcs3/Emu/Cell/Modules/cellAudio.cpp +++ b/rpcs3/Emu/Cell/Modules/cellAudio.cpp @@ -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; } diff --git a/rpcs3/Emu/Cell/Modules/cellAudio.h b/rpcs3/Emu/Cell/Modules/cellAudio.h index d18f3094af..c3636939d2 100644 --- a/rpcs3/Emu/Cell/Modules/cellAudio.h +++ b/rpcs3/Emu/Cell/Modules/cellAudio.h @@ -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 ringbuffer; + audio_device_listener listener; void reset_ports(s32 offset = 0); void advance(u64 timestamp, bool reset = true); diff --git a/rpcs3/emucore.vcxproj b/rpcs3/emucore.vcxproj index e45bd20c48..54c12d901b 100644 --- a/rpcs3/emucore.vcxproj +++ b/rpcs3/emucore.vcxproj @@ -53,6 +53,7 @@ true + true @@ -428,6 +429,7 @@ true + true diff --git a/rpcs3/emucore.vcxproj.filters b/rpcs3/emucore.vcxproj.filters index a5bef0104d..6f766a03b8 100644 --- a/rpcs3/emucore.vcxproj.filters +++ b/rpcs3/emucore.vcxproj.filters @@ -999,6 +999,9 @@ Emu\Cell\Modules + + Emu\Audio + @@ -1974,6 +1977,9 @@ Emu\Cell\Modules + + Emu\Audio +