diff --git a/Source/Core/AudioCommon/CubebStream.cpp b/Source/Core/AudioCommon/CubebStream.cpp index 16ec40fcda..afef4f467e 100644 --- a/Source/Core/AudioCommon/CubebStream.cpp +++ b/Source/Core/AudioCommon/CubebStream.cpp @@ -66,20 +66,26 @@ bool CubebStream::Init() bool CubebStream::SetRunning(bool running) { - if (running) - return cubeb_stream_start(m_stream) == CUBEB_OK; - else - return cubeb_stream_stop(m_stream) == CUBEB_OK; + bool return_value = false; + CubebUtils::RunInCubebContext([&] { + if (running) + return_value = cubeb_stream_start(m_stream) == CUBEB_OK; + else + return_value = cubeb_stream_stop(m_stream) == CUBEB_OK; + }); + return return_value; } CubebStream::~CubebStream() { - SetRunning(false); - cubeb_stream_destroy(m_stream); + CubebUtils::RunInCubebContext([&] { + SetRunning(false); + cubeb_stream_destroy(m_stream); + }); m_ctx.reset(); } void CubebStream::SetVolume(int volume) { - cubeb_stream_set_volume(m_stream, volume / 100.0f); + CubebUtils::RunInCubebContext([&] { cubeb_stream_set_volume(m_stream, volume / 100.0f); }); } diff --git a/Source/Core/AudioCommon/CubebUtils.cpp b/Source/Core/AudioCommon/CubebUtils.cpp index e4050a8bf5..3aff62cecf 100644 --- a/Source/Core/AudioCommon/CubebUtils.cpp +++ b/Source/Core/AudioCommon/CubebUtils.cpp @@ -6,7 +6,9 @@ #include #include #include +#include +#include "Common/Assert.h" #include "Common/CommonPaths.h" #include "Common/Logging/Log.h" #include "Common/Logging/LogManager.h" @@ -14,6 +16,49 @@ #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, ...) @@ -47,8 +92,38 @@ 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 718c5f39c8..bf5035d8a4 100644 --- a/Source/Core/AudioCommon/CubebUtils.h +++ b/Source/Core/AudioCommon/CubebUtils.h @@ -3,11 +3,13 @@ #pragma once +#include #include 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 6de3d42897..b4238bd6c2 100644 --- a/Source/Core/Core/HW/EXI/EXI_DeviceMic.cpp +++ b/Source/Core/Core/HW/EXI/EXI_DeviceMic.cpp @@ -68,47 +68,51 @@ void CEXIMic::StreamStart() if (!m_cubeb_ctx) return; - // Open stream with current parameters - stream_size = buff_size_samples * 500; - stream_buffer = new s16[stream_size]; + CubebUtils::RunInCubebContext([&] { + // Open stream with current parameters + stream_size = buff_size_samples * 500; + stream_buffer = new s16[stream_size]; - cubeb_stream_params params; - params.format = CUBEB_SAMPLE_S16LE; - params.rate = sample_rate; - params.channels = 1; - params.layout = CUBEB_LAYOUT_MONO; + cubeb_stream_params params{}; + params.format = CUBEB_SAMPLE_S16LE; + params.rate = sample_rate; + params.channels = 1; + params.layout = CUBEB_LAYOUT_MONO; - u32 minimum_latency; - if (cubeb_get_min_latency(m_cubeb_ctx.get(), ¶ms, &minimum_latency) != CUBEB_OK) - { - WARN_LOG_FMT(EXPANSIONINTERFACE, "Error getting minimum latency"); - } + u32 minimum_latency; + if (cubeb_get_min_latency(m_cubeb_ctx.get(), ¶ms, &minimum_latency) != CUBEB_OK) + { + WARN_LOG_FMT(EXPANSIONINTERFACE, "Error getting minimum latency"); + } - if (cubeb_stream_init(m_cubeb_ctx.get(), &m_cubeb_stream, "Dolphin Emulated GameCube Microphone", - nullptr, ¶ms, nullptr, nullptr, - std::max(buff_size_samples, minimum_latency), DataCallback, - state_callback, this) != CUBEB_OK) - { - ERROR_LOG_FMT(EXPANSIONINTERFACE, "Error initializing cubeb stream"); - return; - } + if (cubeb_stream_init(m_cubeb_ctx.get(), &m_cubeb_stream, + "Dolphin Emulated GameCube Microphone", nullptr, ¶ms, nullptr, + nullptr, std::max(buff_size_samples, minimum_latency), DataCallback, + state_callback, this) != CUBEB_OK) + { + ERROR_LOG_FMT(EXPANSIONINTERFACE, "Error initializing cubeb stream"); + return; + } - if (cubeb_stream_start(m_cubeb_stream) != CUBEB_OK) - { - ERROR_LOG_FMT(EXPANSIONINTERFACE, "Error starting cubeb stream"); - return; - } + if (cubeb_stream_start(m_cubeb_stream) != CUBEB_OK) + { + ERROR_LOG_FMT(EXPANSIONINTERFACE, "Error starting cubeb stream"); + return; + } - INFO_LOG_FMT(EXPANSIONINTERFACE, "started cubeb stream"); + INFO_LOG_FMT(EXPANSIONINTERFACE, "started cubeb stream"); + }); } void CEXIMic::StreamStop() { if (m_cubeb_stream) { - if (cubeb_stream_stop(m_cubeb_stream) != CUBEB_OK) - ERROR_LOG_FMT(EXPANSIONINTERFACE, "Error stopping cubeb stream"); - cubeb_stream_destroy(m_cubeb_stream); + CubebUtils::RunInCubebContext([&] { + 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; }