src/win32: Add ComWorker to run all COM function on same thread

This commit is contained in:
Shen-Ta Hsieh 2020-12-02 07:14:51 +08:00 committed by Max Kellermann
parent b1d7567226
commit 2974737746
2 changed files with 149 additions and 128 deletions

View File

@ -19,7 +19,7 @@
#include "mixer/MixerInternal.hxx" #include "mixer/MixerInternal.hxx"
#include "output/plugins/WasapiOutputPlugin.hxx" #include "output/plugins/WasapiOutputPlugin.hxx"
#include "win32/Com.hxx" #include "win32/ComWorker.hxx"
#include "win32/HResult.hxx" #include "win32/HResult.hxx"
#include <cmath> #include <cmath>
@ -28,92 +28,103 @@
class WasapiMixer final : public Mixer { class WasapiMixer final : public Mixer {
WasapiOutput &output; WasapiOutput &output;
std::optional<COM> com;
public: public:
WasapiMixer(WasapiOutput &_output, MixerListener &_listener) WasapiMixer(WasapiOutput &_output, MixerListener &_listener)
: Mixer(wasapi_mixer_plugin, _listener), output(_output) {} : Mixer(wasapi_mixer_plugin, _listener), output(_output) {}
void Open() override { com.emplace(); } void Open() override {}
void Close() noexcept override { com.reset(); } void Close() noexcept override {}
int GetVolume() override { int GetVolume() override {
HRESULT result; auto future = COMWorker::Async([&]() -> int {
float volume_level; HRESULT result;
float volume_level;
if (wasapi_is_exclusive(output)) { if (wasapi_is_exclusive(output)) {
ComPtr<IAudioEndpointVolume> endpoint_volume; ComPtr<IAudioEndpointVolume> endpoint_volume;
result = wasapi_output_get_device(output)->Activate( result = wasapi_output_get_device(output)->Activate(
__uuidof(IAudioEndpointVolume), CLSCTX_ALL, nullptr, __uuidof(IAudioEndpointVolume), CLSCTX_ALL,
endpoint_volume.AddressCast()); nullptr, endpoint_volume.AddressCast());
if (FAILED(result)) { if (FAILED(result)) {
throw FormatHResultError( throw FormatHResultError(result,
result, "Unable to get device endpoint volume"); "Unable to get device "
"endpoint volume");
}
result = endpoint_volume->GetMasterVolumeLevelScalar(
&volume_level);
if (FAILED(result)) {
throw FormatHResultError(result,
"Unable to get master "
"volume level");
}
} else {
ComPtr<ISimpleAudioVolume> session_volume;
result = wasapi_output_get_client(output)->GetService(
__uuidof(ISimpleAudioVolume),
session_volume.AddressCast<void>());
if (FAILED(result)) {
throw FormatHResultError(result,
"Unable to get client "
"session volume");
}
result = session_volume->GetMasterVolume(&volume_level);
if (FAILED(result)) {
throw FormatHResultError(
result, "Unable to get master volume");
}
} }
result = endpoint_volume->GetMasterVolumeLevelScalar( return std::lround(volume_level * 100.0f);
&volume_level); });
if (FAILED(result)) { return future.get();
throw FormatHResultError(
result, "Unable to get master volume level");
}
} else {
ComPtr<ISimpleAudioVolume> session_volume;
result = wasapi_output_get_client(output)->GetService(
__uuidof(ISimpleAudioVolume),
session_volume.AddressCast<void>());
if (FAILED(result)) {
throw FormatHResultError(
result, "Unable to get client session volume");
}
result = session_volume->GetMasterVolume(&volume_level);
if (FAILED(result)) {
throw FormatHResultError(result,
"Unable to get master volume");
}
}
return std::lround(volume_level * 100.0f);
} }
void SetVolume(unsigned volume) override { void SetVolume(unsigned volume) override {
HRESULT result; COMWorker::Async([&]() {
const float volume_level = volume / 100.0f; HRESULT result;
const float volume_level = volume / 100.0f;
if (wasapi_is_exclusive(output)) { if (wasapi_is_exclusive(output)) {
ComPtr<IAudioEndpointVolume> endpoint_volume; ComPtr<IAudioEndpointVolume> endpoint_volume;
result = wasapi_output_get_device(output)->Activate( result = wasapi_output_get_device(output)->Activate(
__uuidof(IAudioEndpointVolume), CLSCTX_ALL, nullptr, __uuidof(IAudioEndpointVolume), CLSCTX_ALL,
endpoint_volume.AddressCast()); nullptr, endpoint_volume.AddressCast());
if (FAILED(result)) { if (FAILED(result)) {
throw FormatHResultError( throw FormatHResultError(
result, "Unable to get device endpoint volume"); result,
} "Unable to get device endpoint volume");
}
result = endpoint_volume->SetMasterVolumeLevelScalar(volume_level, result = endpoint_volume->SetMasterVolumeLevelScalar(
nullptr); volume_level, nullptr);
if (FAILED(result)) { if (FAILED(result)) {
throw FormatHResultError( throw FormatHResultError(
result, "Unable to set master volume level"); result,
} "Unable to set master volume level");
} else { }
ComPtr<ISimpleAudioVolume> session_volume; } else {
result = wasapi_output_get_client(output)->GetService( ComPtr<ISimpleAudioVolume> session_volume;
__uuidof(ISimpleAudioVolume), result = wasapi_output_get_client(output)->GetService(
session_volume.AddressCast<void>()); __uuidof(ISimpleAudioVolume),
if (FAILED(result)) { session_volume.AddressCast<void>());
throw FormatHResultError( if (FAILED(result)) {
result, "Unable to get client session volume"); throw FormatHResultError(
} result,
"Unable to get client session volume");
}
result = session_volume->SetMasterVolume(volume_level, nullptr); result = session_volume->SetMasterVolume(volume_level,
if (FAILED(result)) { nullptr);
throw FormatHResultError(result, if (FAILED(result)) {
"Unable to set master volume"); throw FormatHResultError(
result, "Unable to set master volume");
}
} }
} }).get();
} }
}; };

View File

@ -32,6 +32,7 @@
#include "util/ScopeExit.hxx" #include "util/ScopeExit.hxx"
#include "win32/Com.hxx" #include "win32/Com.hxx"
#include "win32/ComHeapPtr.hxx" #include "win32/ComHeapPtr.hxx"
#include "win32/ComWorker.hxx"
#include "win32/HResult.hxx" #include "win32/HResult.hxx"
#include "win32/WinEvent.hxx" #include "win32/WinEvent.hxx"
@ -144,7 +145,6 @@ public:
private: private:
std::shared_ptr<WinEvent> event; std::shared_ptr<WinEvent> event;
std::optional<COM> com;
ComPtr<IAudioClient> client; ComPtr<IAudioClient> client;
ComPtr<IAudioRenderClient> render_client; ComPtr<IAudioRenderClient> render_client;
const UINT32 frame_size; const UINT32 frame_size;
@ -174,9 +174,17 @@ class WasapiOutput final : public AudioOutput {
public: public:
static AudioOutput *Create(EventLoop &, const ConfigBlock &block); static AudioOutput *Create(EventLoop &, const ConfigBlock &block);
WasapiOutput(const ConfigBlock &block); WasapiOutput(const ConfigBlock &block);
void Enable() override; void Enable() override {
void Disable() noexcept override; COMWorker::Aquire();
void Open(AudioFormat &audio_format) override; COMWorker::Async([&]() { OpenDevice(); }).get();
}
void Disable() noexcept override {
COMWorker::Async([&]() { DoDisable(); }).get();
COMWorker::Release();
}
void Open(AudioFormat &audio_format) override {
COMWorker::Async([&]() { DoOpen(audio_format); }).get();
}
void Close() noexcept override; void Close() noexcept override;
std::chrono::steady_clock::duration Delay() const noexcept override; std::chrono::steady_clock::duration Delay() const noexcept override;
size_t Play(const void *chunk, size_t size) override; size_t Play(const void *chunk, size_t size) override;
@ -196,7 +204,6 @@ private:
std::string device_config; std::string device_config;
std::vector<std::pair<unsigned int, AllocatedString>> device_desc; std::vector<std::pair<unsigned int, AllocatedString>> device_desc;
std::shared_ptr<WinEvent> event; std::shared_ptr<WinEvent> event;
std::optional<COM> com;
ComPtr<IMMDeviceEnumerator> enumerator; ComPtr<IMMDeviceEnumerator> enumerator;
ComPtr<IMMDevice> device; ComPtr<IMMDevice> device;
ComPtr<IAudioClient> client; ComPtr<IAudioClient> client;
@ -209,6 +216,10 @@ private:
friend IMMDevice *wasapi_output_get_device(WasapiOutput &output) noexcept; friend IMMDevice *wasapi_output_get_device(WasapiOutput &output) noexcept;
friend IAudioClient *wasapi_output_get_client(WasapiOutput &output) noexcept; friend IAudioClient *wasapi_output_get_client(WasapiOutput &output) noexcept;
void DoDisable() noexcept;
void DoOpen(AudioFormat &audio_format);
void OpenDevice();
void FindExclusiveFormatSupported(AudioFormat &audio_format); void FindExclusiveFormatSupported(AudioFormat &audio_format);
void FindSharedFormatSupported(AudioFormat &audio_format); void FindSharedFormatSupported(AudioFormat &audio_format);
void EnumerateDevices(); void EnumerateDevices();
@ -234,15 +245,7 @@ IAudioClient *wasapi_output_get_client(WasapiOutput &output) noexcept {
void WasapiOutputThread::Work() noexcept { void WasapiOutputThread::Work() noexcept {
SetThreadName("Wasapi Output Worker"); SetThreadName("Wasapi Output Worker");
FormatDebug(wasapi_output_domain, "Working thread started"); FormatDebug(wasapi_output_domain, "Working thread started");
try { COM com{true};
com.emplace();
} catch (...) {
std::unique_lock<Mutex> lock(error.mutex);
error.error_ptr = std::current_exception();
error.cond.wait(lock);
assert(error.error_ptr == nullptr);
return;
}
while (true) { while (true) {
try { try {
event->Wait(INFINITE); event->Wait(INFINITE);
@ -316,41 +319,8 @@ WasapiOutput::WasapiOutput(const ConfigBlock &block)
enumerate_devices(block.GetBlockValue("enumerate", false)), enumerate_devices(block.GetBlockValue("enumerate", false)),
device_config(block.GetBlockValue("device", "")) {} device_config(block.GetBlockValue("device", "")) {}
void WasapiOutput::Enable() { /// run inside COMWorkerThread
com.emplace(); void WasapiOutput::DoDisable() noexcept {
event = std::make_shared<WinEvent>();
enumerator.CoCreateInstance(__uuidof(MMDeviceEnumerator), nullptr,
CLSCTX_INPROC_SERVER);
device_desc.clear();
device.reset();
if (enumerate_devices && SafeTry([this]() { EnumerateDevices(); })) {
for (const auto &desc : device_desc) {
FormatNotice(wasapi_output_domain, "Device \"%u\" \"%s\"",
desc.first, desc.second.c_str());
}
}
unsigned int id = kErrorId;
if (!device_config.empty()) {
if (!SafeSilenceTry([this, &id]() { id = std::stoul(device_config); })) {
id = SearchDevice(device_config);
}
}
if (id != kErrorId) {
SafeTry([this, id]() { GetDevice(id); });
}
if (!device) {
GetDefaultDevice();
}
device_desc.clear();
}
void WasapiOutput::Disable() noexcept {
if (thread) { if (thread) {
try { try {
thread->Finish(); thread->Finish();
@ -369,7 +339,8 @@ void WasapiOutput::Disable() noexcept {
event.reset(); event.reset();
} }
void WasapiOutput::Open(AudioFormat &audio_format) { /// run inside COMWorkerThread
void WasapiOutput::DoOpen(AudioFormat &audio_format) {
if (audio_format.channels == 0) { if (audio_format.channels == 0) {
throw FormatInvalidArgument("channels should > 0"); throw FormatInvalidArgument("channels should > 0");
} }
@ -497,9 +468,11 @@ void WasapiOutput::Close() noexcept {
Pause(); Pause();
thread->Finish(); thread->Finish();
thread->Join(); thread->Join();
thread.reset(); COMWorker::Async([&]() {
thread.reset();
client.reset();
}).get();
spsc_buffer.reset(); spsc_buffer.reset();
client.reset();
} }
std::chrono::steady_clock::duration WasapiOutput::Delay() const noexcept { std::chrono::steady_clock::duration WasapiOutput::Delay() const noexcept {
@ -534,13 +507,14 @@ size_t WasapiOutput::Play(const void *chunk, size_t size) {
is_started = true; is_started = true;
thread->Play(); thread->Play();
COMWorker::Async([&]() {
HRESULT result; HRESULT result;
result = client->Start(); result = client->Start();
if (FAILED(result)) { if (FAILED(result)) {
throw FormatHResultError(result, throw FormatHResultError(
"Failed to start client"); result, "Failed to start client");
} }
}).wait();
} }
thread->CheckException(); thread->CheckException();
@ -575,6 +549,37 @@ bool WasapiOutput::Pause() {
return true; return true;
} }
/// run inside COMWorkerThread
void WasapiOutput::OpenDevice() {
enumerator.CoCreateInstance(__uuidof(MMDeviceEnumerator), nullptr,
CLSCTX_INPROC_SERVER);
if (enumerate_devices && SafeTry([this]() { EnumerateDevices(); })) {
for (const auto &desc : device_desc) {
FormatNotice(wasapi_output_domain, "Device \"%u\" \"%s\"",
desc.first, desc.second.c_str());
}
}
unsigned int id = kErrorId;
if (!device_config.empty()) {
if (!SafeSilenceTry([this, &id]() { id = std::stoul(device_config); })) {
id = SearchDevice(device_config);
}
}
if (id != kErrorId) {
SafeTry([this, id]() { GetDevice(id); });
}
if (!device) {
GetDefaultDevice();
}
device_desc.clear();
}
/// run inside COMWorkerThread
void WasapiOutput::FindExclusiveFormatSupported(AudioFormat &audio_format) { void WasapiOutput::FindExclusiveFormatSupported(AudioFormat &audio_format) {
SetFormat(device_format, audio_format); SetFormat(device_format, audio_format);
@ -641,6 +646,7 @@ void WasapiOutput::FindExclusiveFormatSupported(AudioFormat &audio_format) {
} while (true); } while (true);
} }
/// run inside COMWorkerThread
void WasapiOutput::FindSharedFormatSupported(AudioFormat &audio_format) { void WasapiOutput::FindSharedFormatSupported(AudioFormat &audio_format) {
HRESULT result; HRESULT result;
ComHeapPtr<WAVEFORMATEX> mixer_format; ComHeapPtr<WAVEFORMATEX> mixer_format;
@ -724,6 +730,7 @@ void WasapiOutput::FindSharedFormatSupported(AudioFormat &audio_format) {
} }
} }
/// run inside COMWorkerThread
void WasapiOutput::EnumerateDevices() { void WasapiOutput::EnumerateDevices() {
if (!device_desc.empty()) { if (!device_desc.empty()) {
return; return;
@ -776,6 +783,7 @@ void WasapiOutput::EnumerateDevices() {
} }
} }
/// run inside COMWorkerThread
void WasapiOutput::GetDevice(unsigned int index) { void WasapiOutput::GetDevice(unsigned int index) {
HRESULT result; HRESULT result;
@ -792,6 +800,7 @@ void WasapiOutput::GetDevice(unsigned int index) {
} }
} }
/// run inside COMWorkerThread
unsigned int WasapiOutput::SearchDevice(std::string_view name) { unsigned int WasapiOutput::SearchDevice(std::string_view name) {
if (!SafeTry([this]() { EnumerateDevices(); })) { if (!SafeTry([this]() { EnumerateDevices(); })) {
return kErrorId; return kErrorId;
@ -809,6 +818,7 @@ unsigned int WasapiOutput::SearchDevice(std::string_view name) {
return iter->first; return iter->first;
} }
/// run inside COMWorkerThread
void WasapiOutput::GetDefaultDevice() { void WasapiOutput::GetDefaultDevice() {
HRESULT result; HRESULT result;
result = enumerator->GetDefaultAudioEndpoint(eRender, eMultimedia, result = enumerator->GetDefaultAudioEndpoint(eRender, eMultimedia,