src/win32: Add ComWorker to run all COM function on same thread
This commit is contained in:
parent
b1d7567226
commit
2974737746
|
@ -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();
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
|
|
|
@ -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,
|
||||||
|
|
Loading…
Reference in New Issue