From e085bf14f99c52eff514e2317af740869691ba82 Mon Sep 17 00:00:00 2001 From: "Admiral H. Curtiss" Date: Sat, 18 Jun 2022 05:31:14 +0200 Subject: [PATCH] Core: Use extra thread for Cubeb on Windows to not disturb the CoInitialize state of whatever thread happens to call a Cubeb function. --- Source/Core/AudioCommon/CubebStream.cpp | 130 +++++++++++++++++----- Source/Core/AudioCommon/CubebStream.h | 12 ++ Source/Core/AudioCommon/CubebUtils.cpp | 73 ------------ Source/Core/AudioCommon/CubebUtils.h | 1 - Source/Core/Core/HW/EXI/EXI_DeviceMic.cpp | 77 +++++++++++-- Source/Core/Core/HW/EXI/EXI_DeviceMic.h | 6 + 6 files changed, 190 insertions(+), 109 deletions(-) diff --git a/Source/Core/AudioCommon/CubebStream.cpp b/Source/Core/AudioCommon/CubebStream.cpp index afef4f467e..5d3d70d9cb 100644 --- a/Source/Core/AudioCommon/CubebStream.cpp +++ b/Source/Core/AudioCommon/CubebStream.cpp @@ -7,10 +7,16 @@ #include "AudioCommon/CubebUtils.h" #include "Common/CommonTypes.h" +#include "Common/Event.h" #include "Common/Logging/Log.h" +#include "Common/ScopeGuard.h" #include "Common/Thread.h" #include "Core/Config/MainSettings.h" +#ifdef _WIN32 +#include +#endif + // ~10 ms - needs to be at least 240 for surround constexpr u32 BUFFER_SAMPLES = 512; @@ -31,61 +37,129 @@ void CubebStream::StateCallback(cubeb_stream* stream, void* user_data, cubeb_sta { } +CubebStream::CubebStream() +#ifdef _WIN32 + : m_work_queue([](const std::function& func) { func(); }) +{ + Common::Event sync_event; + m_work_queue.EmplaceItem([this, &sync_event] { + Common::ScopeGuard sync_event_guard([&sync_event] { sync_event.Set(); }); + auto result = ::CoInitializeEx(nullptr, COINIT_MULTITHREADED | COINIT_DISABLE_OLE1DDE); + m_coinit_success = result == S_OK; + }); + sync_event.Wait(); +} +#else + = default; +#endif + bool CubebStream::Init() { - m_ctx = CubebUtils::GetContext(); - if (!m_ctx) + bool return_value = false; + +#ifdef _WIN32 + if (!m_coinit_success) return false; + Common::Event sync_event; + m_work_queue.EmplaceItem([this, &return_value, &sync_event] { + Common::ScopeGuard sync_event_guard([&sync_event] { sync_event.Set(); }); +#endif - m_stereo = !Config::ShouldUseDPL2Decoder(); + m_ctx = CubebUtils::GetContext(); + if (m_ctx) + { + m_stereo = !Config::ShouldUseDPL2Decoder(); - cubeb_stream_params params{}; - params.rate = m_mixer->GetSampleRate(); - if (m_stereo) - { - params.channels = 2; - params.format = CUBEB_SAMPLE_S16NE; - params.layout = CUBEB_LAYOUT_STEREO; - } - else - { - params.channels = 6; - params.format = CUBEB_SAMPLE_FLOAT32NE; - params.layout = CUBEB_LAYOUT_3F2_LFE; - } + cubeb_stream_params params{}; + params.rate = m_mixer->GetSampleRate(); + if (m_stereo) + { + params.channels = 2; + params.format = CUBEB_SAMPLE_S16NE; + params.layout = CUBEB_LAYOUT_STEREO; + } + else + { + params.channels = 6; + params.format = CUBEB_SAMPLE_FLOAT32NE; + params.layout = CUBEB_LAYOUT_3F2_LFE; + } - u32 minimum_latency = 0; - if (cubeb_get_min_latency(m_ctx.get(), ¶ms, &minimum_latency) != CUBEB_OK) - ERROR_LOG_FMT(AUDIO, "Error getting minimum latency"); - INFO_LOG_FMT(AUDIO, "Minimum latency: {} frames", minimum_latency); + u32 minimum_latency = 0; + if (cubeb_get_min_latency(m_ctx.get(), ¶ms, &minimum_latency) != CUBEB_OK) + ERROR_LOG_FMT(AUDIO, "Error getting minimum latency"); + INFO_LOG_FMT(AUDIO, "Minimum latency: {} frames", minimum_latency); - return cubeb_stream_init(m_ctx.get(), &m_stream, "Dolphin Audio Output", nullptr, nullptr, - nullptr, ¶ms, std::max(BUFFER_SAMPLES, minimum_latency), - DataCallback, StateCallback, this) == CUBEB_OK; + return_value = + cubeb_stream_init(m_ctx.get(), &m_stream, "Dolphin Audio Output", nullptr, nullptr, + nullptr, ¶ms, std::max(BUFFER_SAMPLES, minimum_latency), + DataCallback, StateCallback, this) == CUBEB_OK; + } + +#ifdef _WIN32 + }); + sync_event.Wait(); +#endif + + return return_value; } bool CubebStream::SetRunning(bool running) { bool return_value = false; - CubebUtils::RunInCubebContext([&] { + +#ifdef _WIN32 + if (!m_coinit_success) + return false; + Common::Event sync_event; + m_work_queue.EmplaceItem([this, running, &return_value, &sync_event] { + Common::ScopeGuard sync_event_guard([&sync_event] { sync_event.Set(); }); +#endif if (running) return_value = cubeb_stream_start(m_stream) == CUBEB_OK; else return_value = cubeb_stream_stop(m_stream) == CUBEB_OK; +#ifdef _WIN32 }); + sync_event.Wait(); +#endif + return return_value; } CubebStream::~CubebStream() { - CubebUtils::RunInCubebContext([&] { - SetRunning(false); +#ifdef _WIN32 + Common::Event sync_event; + m_work_queue.EmplaceItem([this, &sync_event] { + Common::ScopeGuard sync_event_guard([&sync_event] { sync_event.Set(); }); +#endif + cubeb_stream_stop(m_stream); cubeb_stream_destroy(m_stream); +#ifdef _WIN32 + if (m_coinit_success) + { + m_coinit_success = false; + CoUninitialize(); + } }); + sync_event.Wait(); +#endif m_ctx.reset(); } void CubebStream::SetVolume(int volume) { - CubebUtils::RunInCubebContext([&] { cubeb_stream_set_volume(m_stream, volume / 100.0f); }); +#ifdef _WIN32 + if (!m_coinit_success) + return; + Common::Event sync_event; + m_work_queue.EmplaceItem([this, volume, &sync_event] { + Common::ScopeGuard sync_event_guard([&sync_event] { sync_event.Set(); }); +#endif + cubeb_stream_set_volume(m_stream, volume / 100.0f); +#ifdef _WIN32 + }); + sync_event.Wait(); +#endif } diff --git a/Source/Core/AudioCommon/CubebStream.h b/Source/Core/AudioCommon/CubebStream.h index dd2d46c138..aaf5b82f8f 100644 --- a/Source/Core/AudioCommon/CubebStream.h +++ b/Source/Core/AudioCommon/CubebStream.h @@ -4,16 +4,23 @@ #pragma once #include +#include #include #include #include "AudioCommon/SoundStream.h" +#include "Common/WorkQueueThread.h" #include class CubebStream final : public SoundStream { public: + CubebStream(); + CubebStream(const CubebStream& other) = delete; + CubebStream(CubebStream&& other) = delete; + CubebStream& operator=(const CubebStream& other) = delete; + CubebStream& operator=(CubebStream&& other) = delete; ~CubebStream() override; bool Init() override; bool SetRunning(bool running) override; @@ -27,6 +34,11 @@ private: std::vector m_short_buffer; std::vector m_floatstereo_buffer; +#ifdef _WIN32 + Common::WorkQueueThread> m_work_queue; + bool m_coinit_success = false; +#endif + static long DataCallback(cubeb_stream* stream, void* user_data, const void* /*input_buffer*/, void* output_buffer, long num_frames); static void StateCallback(cubeb_stream* stream, void* user_data, cubeb_state state); diff --git a/Source/Core/AudioCommon/CubebUtils.cpp b/Source/Core/AudioCommon/CubebUtils.cpp index 3aff62cecf..f7682f6ade 100644 --- a/Source/Core/AudioCommon/CubebUtils.cpp +++ b/Source/Core/AudioCommon/CubebUtils.cpp @@ -16,49 +16,6 @@ #include -#ifdef _WIN32 -#include -#endif - -// On Windows, we must manually ensure that COM is initialized in MTA mode on every thread that -// accesses the cubeb API. See the comment on cubeb_init in cubeb.h -// We do this with a thread-local variable that keeps track of whether COM is initialized or not, -// and initialize it if it isn't. When the thread ends COM is uninitialized again. -#ifdef _WIN32 -namespace -{ -class auto_com -{ -public: - auto_com() = default; - auto_com(const auto_com&) = delete; - auto_com(auto_com&&) = delete; - auto_com& operator=(const auto_com&) = delete; - auto_com& operator=(auto_com&&) = delete; - ~auto_com() - { - if (m_initialized) - { - CoUninitialize(); - } - } - bool initialize() - { - if (!m_initialized) - { - HRESULT result = CoInitializeEx(nullptr, COINIT_MULTITHREADED | COINIT_DISABLE_OLE1DDE); - m_initialized = SUCCEEDED(result); - } - return m_initialized; - } - -private: - bool m_initialized = false; -}; -} // namespace -static thread_local auto_com tls_com_context; -#endif - static ptrdiff_t s_path_cutoff_point = 0; static void LogCallback(const char* format, ...) @@ -92,38 +49,8 @@ static void DestroyContext(cubeb* ctx) } } -static bool EnsureCubebCallable() -{ -#ifdef _WIN32 - if (!tls_com_context.initialize()) - return false; -#endif - return true; -} - -void CubebUtils::RunInCubebContext(const std::function& func) -{ - // Cubeb is documented to require MTA COM mode, so if the current thread was initialized in STA - // mode, we make a temporary thread to execute the cubeb call. - if (EnsureCubebCallable()) - { - func(); - } - else - { - std::thread([&] { - // this should never fail, so yell loudly if it does - ASSERT(EnsureCubebCallable()); - func(); - }).join(); - } -} - std::shared_ptr CubebUtils::GetContext() { - if (!EnsureCubebCallable()) - return nullptr; - static std::weak_ptr weak; std::shared_ptr shared = weak.lock(); diff --git a/Source/Core/AudioCommon/CubebUtils.h b/Source/Core/AudioCommon/CubebUtils.h index bf5035d8a4..f0effc2e8f 100644 --- a/Source/Core/AudioCommon/CubebUtils.h +++ b/Source/Core/AudioCommon/CubebUtils.h @@ -10,6 +10,5 @@ struct cubeb; namespace CubebUtils { -void RunInCubebContext(const std::function& func); std::shared_ptr GetContext(); } // namespace CubebUtils diff --git a/Source/Core/Core/HW/EXI/EXI_DeviceMic.cpp b/Source/Core/Core/HW/EXI/EXI_DeviceMic.cpp index b4238bd6c2..408dfe5bc8 100644 --- a/Source/Core/Core/HW/EXI/EXI_DeviceMic.cpp +++ b/Source/Core/Core/HW/EXI/EXI_DeviceMic.cpp @@ -12,27 +12,59 @@ #include "AudioCommon/CubebUtils.h" #include "Common/Common.h" #include "Common/CommonTypes.h" +#include "Common/Event.h" #include "Common/Logging/Log.h" +#include "Common/ScopeGuard.h" #include "Core/CoreTiming.h" #include "Core/HW/EXI/EXI.h" #include "Core/HW/GCPad.h" #include "Core/HW/SystemTimers.h" +#ifdef _WIN32 +#include +#endif + namespace ExpansionInterface { void CEXIMic::StreamInit() { - m_cubeb_ctx = CubebUtils::GetContext(); - stream_buffer = nullptr; samples_avail = stream_wpos = stream_rpos = 0; + +#ifdef _WIN32 + if (!m_coinit_success) + return; + Common::Event sync_event; + m_work_queue.EmplaceItem([this, &sync_event] { + Common::ScopeGuard sync_event_guard([&sync_event] { sync_event.Set(); }); +#endif + m_cubeb_ctx = CubebUtils::GetContext(); +#ifdef _WIN32 + }); + sync_event.Wait(); +#endif } void CEXIMic::StreamTerminate() { StreamStop(); - m_cubeb_ctx.reset(); + + if (m_cubeb_ctx) + { +#ifdef _WIN32 + if (!m_coinit_success) + return; + Common::Event sync_event; + m_work_queue.EmplaceItem([this, &sync_event] { + Common::ScopeGuard sync_event_guard([&sync_event] { sync_event.Set(); }); +#endif + m_cubeb_ctx.reset(); +#ifdef _WIN32 + }); + sync_event.Wait(); +#endif + } } static void state_callback(cubeb_stream* stream, void* user_data, cubeb_state state) @@ -68,7 +100,13 @@ void CEXIMic::StreamStart() if (!m_cubeb_ctx) return; - CubebUtils::RunInCubebContext([&] { +#ifdef _WIN32 + if (!m_coinit_success) + return; + Common::Event sync_event; + m_work_queue.EmplaceItem([this, &sync_event] { + Common::ScopeGuard sync_event_guard([&sync_event] { sync_event.Set(); }); +#endif // Open stream with current parameters stream_size = buff_size_samples * 500; stream_buffer = new s16[stream_size]; @@ -101,19 +139,29 @@ void CEXIMic::StreamStart() } INFO_LOG_FMT(EXPANSIONINTERFACE, "started cubeb stream"); +#ifdef _WIN32 }); + sync_event.Wait(); +#endif } void CEXIMic::StreamStop() { if (m_cubeb_stream) { - CubebUtils::RunInCubebContext([&] { +#ifdef _WIN32 + Common::Event sync_event; + m_work_queue.EmplaceItem([this, &sync_event] { + Common::ScopeGuard sync_event_guard([&sync_event] { sync_event.Set(); }); +#endif if (cubeb_stream_stop(m_cubeb_stream) != CUBEB_OK) ERROR_LOG_FMT(EXPANSIONINTERFACE, "Error stopping cubeb stream"); cubeb_stream_destroy(m_cubeb_stream); + m_cubeb_stream = nullptr; +#ifdef _WIN32 }); - m_cubeb_stream = nullptr; + sync_event.Wait(); +#endif } samples_avail = stream_wpos = stream_rpos = 0; @@ -147,7 +195,12 @@ void CEXIMic::StreamReadOne() u8 const CEXIMic::exi_id[] = {0, 0x0a, 0, 0, 0}; -CEXIMic::CEXIMic(int index) : slot(index) +CEXIMic::CEXIMic(int index) + : slot(index) +#ifdef _WIN32 + , + m_work_queue([](const std::function& func) { func(); }) +#endif { m_position = 0; command = 0; @@ -162,6 +215,16 @@ CEXIMic::CEXIMic(int index) : slot(index) next_int_ticks = 0; +#ifdef _WIN32 + Common::Event sync_event; + m_work_queue.EmplaceItem([this, &sync_event] { + Common::ScopeGuard sync_event_guard([&sync_event] { sync_event.Set(); }); + auto result = ::CoInitializeEx(nullptr, COINIT_MULTITHREADED | COINIT_DISABLE_OLE1DDE); + m_coinit_success = result == S_OK; + }); + sync_event.Wait(); +#endif + StreamInit(); } diff --git a/Source/Core/Core/HW/EXI/EXI_DeviceMic.h b/Source/Core/Core/HW/EXI/EXI_DeviceMic.h index 5ca159f50d..5342190905 100644 --- a/Source/Core/Core/HW/EXI/EXI_DeviceMic.h +++ b/Source/Core/Core/HW/EXI/EXI_DeviceMic.h @@ -6,6 +6,7 @@ #include #include "Common/CommonTypes.h" +#include "Common/WorkQueueThread.h" #include "Core/HW/EXI/EXI_Device.h" struct cubeb; @@ -99,5 +100,10 @@ private: int stream_wpos; int stream_rpos; int samples_avail; + +#ifdef _WIN32 + Common::WorkQueueThread> m_work_queue; + bool m_coinit_success = false; +#endif }; } // namespace ExpansionInterface