From afe621c25ce2bec46ed54785b0f609c3938a7c65 Mon Sep 17 00:00:00 2001 From: Max Kellermann Date: Fri, 5 Mar 2021 16:26:57 +0100 Subject: [PATCH 01/18] output/wasapi: move to separate directory --- src/mixer/plugins/WasapiMixerPlugin.cxx | 2 +- src/output/Registry.cxx | 2 +- src/output/plugins/meson.build | 2 +- src/output/plugins/{ => wasapi}/WasapiOutputPlugin.cxx | 0 src/output/plugins/{ => wasapi}/WasapiOutputPlugin.hxx | 2 +- 5 files changed, 4 insertions(+), 4 deletions(-) rename src/output/plugins/{ => wasapi}/WasapiOutputPlugin.cxx (100%) rename src/output/plugins/{ => wasapi}/WasapiOutputPlugin.hxx (97%) diff --git a/src/mixer/plugins/WasapiMixerPlugin.cxx b/src/mixer/plugins/WasapiMixerPlugin.cxx index bc2604633..d4d7626ff 100644 --- a/src/mixer/plugins/WasapiMixerPlugin.cxx +++ b/src/mixer/plugins/WasapiMixerPlugin.cxx @@ -18,7 +18,7 @@ */ #include "mixer/MixerInternal.hxx" -#include "output/plugins/WasapiOutputPlugin.hxx" +#include "output/plugins/wasapi/WasapiOutputPlugin.hxx" #include "win32/ComWorker.hxx" #include "win32/HResult.hxx" diff --git a/src/output/Registry.cxx b/src/output/Registry.cxx index 7252aa47c..f0f999895 100644 --- a/src/output/Registry.cxx +++ b/src/output/Registry.cxx @@ -42,7 +42,7 @@ #include "plugins/WinmmOutputPlugin.hxx" #endif #ifdef ENABLE_WASAPI_OUTPUT -#include "plugins/WasapiOutputPlugin.hxx" +#include "plugins/wasapi/WasapiOutputPlugin.hxx" #endif #include "util/StringAPI.hxx" diff --git a/src/output/plugins/meson.build b/src/output/plugins/meson.build index 53c92b9f4..0f420d8d0 100644 --- a/src/output/plugins/meson.build +++ b/src/output/plugins/meson.build @@ -136,7 +136,7 @@ endif output_features.set('ENABLE_WASAPI_OUTPUT', is_windows) if is_windows output_plugins_sources += [ - 'WasapiOutputPlugin.cxx', + 'wasapi/WasapiOutputPlugin.cxx', ] wasapi_dep = [ c_compiler.find_library('ksuser', required: true), diff --git a/src/output/plugins/WasapiOutputPlugin.cxx b/src/output/plugins/wasapi/WasapiOutputPlugin.cxx similarity index 100% rename from src/output/plugins/WasapiOutputPlugin.cxx rename to src/output/plugins/wasapi/WasapiOutputPlugin.cxx diff --git a/src/output/plugins/WasapiOutputPlugin.hxx b/src/output/plugins/wasapi/WasapiOutputPlugin.hxx similarity index 97% rename from src/output/plugins/WasapiOutputPlugin.hxx rename to src/output/plugins/wasapi/WasapiOutputPlugin.hxx index 3b17bcb9f..904caeba0 100644 --- a/src/output/plugins/WasapiOutputPlugin.hxx +++ b/src/output/plugins/wasapi/WasapiOutputPlugin.hxx @@ -22,7 +22,7 @@ #include "output/Features.h" -#include "../OutputAPI.hxx" +#include "output/OutputAPI.hxx" #include "util/Compiler.h" #include "win32/ComPtr.hxx" From 052f64d6484b23bb2b384693321db8665ccb0033 Mon Sep 17 00:00:00 2001 From: Max Kellermann Date: Fri, 5 Mar 2021 16:32:16 +0100 Subject: [PATCH 02/18] output/wasapi: include config.h for ENABLE_DSD --- src/output/plugins/wasapi/WasapiOutputPlugin.cxx | 1 + 1 file changed, 1 insertion(+) diff --git a/src/output/plugins/wasapi/WasapiOutputPlugin.cxx b/src/output/plugins/wasapi/WasapiOutputPlugin.cxx index 84d23dd12..9c0ffd555 100644 --- a/src/output/plugins/wasapi/WasapiOutputPlugin.cxx +++ b/src/output/plugins/wasapi/WasapiOutputPlugin.cxx @@ -39,6 +39,7 @@ #include "win32/ComWorker.hxx" #include "win32/HResult.hxx" #include "win32/WinEvent.hxx" +#include "config.h" #include #include From 681956a963390f2a6edeebdc028efbf335f558a2 Mon Sep 17 00:00:00 2001 From: Max Kellermann Date: Fri, 5 Mar 2021 16:31:39 +0100 Subject: [PATCH 03/18] output/wasapi: include cleanup --- src/mixer/plugins/WasapiMixerPlugin.cxx | 6 ++++-- src/output/plugins/wasapi/WasapiOutputPlugin.cxx | 11 +++++++---- src/output/plugins/wasapi/WasapiOutputPlugin.hxx | 3 --- 3 files changed, 11 insertions(+), 9 deletions(-) diff --git a/src/mixer/plugins/WasapiMixerPlugin.cxx b/src/mixer/plugins/WasapiMixerPlugin.cxx index d4d7626ff..d940ca279 100644 --- a/src/mixer/plugins/WasapiMixerPlugin.cxx +++ b/src/mixer/plugins/WasapiMixerPlugin.cxx @@ -17,15 +17,17 @@ * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. */ -#include "mixer/MixerInternal.hxx" #include "output/plugins/wasapi/WasapiOutputPlugin.hxx" +#include "mixer/MixerInternal.hxx" +#include "win32/ComPtr.hxx" #include "win32/ComWorker.hxx" #include "win32/HResult.hxx" #include -#include #include +#include + class WasapiMixer final : public Mixer { WasapiOutput &output; diff --git a/src/output/plugins/wasapi/WasapiOutputPlugin.cxx b/src/output/plugins/wasapi/WasapiOutputPlugin.cxx index 9c0ffd555..4ec50db50 100644 --- a/src/output/plugins/wasapi/WasapiOutputPlugin.cxx +++ b/src/output/plugins/wasapi/WasapiOutputPlugin.cxx @@ -16,9 +16,7 @@ * with this program; if not, write to the Free Software Foundation, Inc., * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. */ -#include -#include "Log.hxx" #include "WasapiOutputPlugin.hxx" #include "lib/icu/Win32.hxx" #include "mixer/MixerList.hxx" @@ -36,19 +34,24 @@ #include "util/StringBuffer.hxx" #include "win32/Com.hxx" #include "win32/ComHeapPtr.hxx" +#include "win32/ComPtr.hxx" #include "win32/ComWorker.hxx" #include "win32/HResult.hxx" #include "win32/WinEvent.hxx" +#include "Log.hxx" #include "config.h" -#include #include + +#include #include #include -#include #include #include +#include +#include + namespace { static constexpr Domain wasapi_output_domain("wasapi_output"); diff --git a/src/output/plugins/wasapi/WasapiOutputPlugin.hxx b/src/output/plugins/wasapi/WasapiOutputPlugin.hxx index 904caeba0..eaf063e3b 100644 --- a/src/output/plugins/wasapi/WasapiOutputPlugin.hxx +++ b/src/output/plugins/wasapi/WasapiOutputPlugin.hxx @@ -20,11 +20,8 @@ #ifndef MPD_WASAPI_OUTPUT_PLUGIN_HXX #define MPD_WASAPI_OUTPUT_PLUGIN_HXX -#include "output/Features.h" - #include "output/OutputAPI.hxx" #include "util/Compiler.h" -#include "win32/ComPtr.hxx" #include #include From 6d65cc48d789ef7d44ea843b82fdbd3e4252d616 Mon Sep 17 00:00:00 2001 From: Max Kellermann Date: Fri, 5 Mar 2021 16:37:49 +0100 Subject: [PATCH 04/18] output/wasapi: use [[gnu::pure]] --- .../plugins/wasapi/WasapiOutputPlugin.hxx | 17 ++++++++++++----- 1 file changed, 12 insertions(+), 5 deletions(-) diff --git a/src/output/plugins/wasapi/WasapiOutputPlugin.hxx b/src/output/plugins/wasapi/WasapiOutputPlugin.hxx index eaf063e3b..77545c699 100644 --- a/src/output/plugins/wasapi/WasapiOutputPlugin.hxx +++ b/src/output/plugins/wasapi/WasapiOutputPlugin.hxx @@ -21,7 +21,6 @@ #define MPD_WASAPI_OUTPUT_PLUGIN_HXX #include "output/OutputAPI.hxx" -#include "util/Compiler.h" #include #include @@ -30,12 +29,20 @@ extern const struct AudioOutputPlugin wasapi_output_plugin; class WasapiOutput; -gcc_pure WasapiOutput &wasapi_output_downcast(AudioOutput &output) noexcept; +[[gnu::pure]] +WasapiOutput & +wasapi_output_downcast(AudioOutput &output) noexcept; -gcc_pure bool wasapi_is_exclusive(WasapiOutput &output) noexcept; +[[gnu::pure]] +bool +wasapi_is_exclusive(WasapiOutput &output) noexcept; -gcc_pure IMMDevice *wasapi_output_get_device(WasapiOutput &output) noexcept; +[[gnu::pure]] +IMMDevice * +wasapi_output_get_device(WasapiOutput &output) noexcept; -gcc_pure IAudioClient *wasapi_output_get_client(WasapiOutput &output) noexcept; +[[gnu::pure]] +IAudioClient * +wasapi_output_get_client(WasapiOutput &output) noexcept; #endif From 9521c1ad588114baaf7902cb257481a11c2a269b Mon Sep 17 00:00:00 2001 From: Max Kellermann Date: Fri, 5 Mar 2021 16:30:22 +0100 Subject: [PATCH 05/18] output/wasapi: use forward declarations in the header --- src/mixer/plugins/WasapiMixerPlugin.cxx | 2 ++ src/output/plugins/wasapi/WasapiOutputPlugin.cxx | 3 +++ src/output/plugins/wasapi/WasapiOutputPlugin.hxx | 8 +++----- 3 files changed, 8 insertions(+), 5 deletions(-) diff --git a/src/mixer/plugins/WasapiMixerPlugin.cxx b/src/mixer/plugins/WasapiMixerPlugin.cxx index d940ca279..786427c69 100644 --- a/src/mixer/plugins/WasapiMixerPlugin.cxx +++ b/src/mixer/plugins/WasapiMixerPlugin.cxx @@ -26,7 +26,9 @@ #include #include +#include #include +#include class WasapiMixer final : public Mixer { WasapiOutput &output; diff --git a/src/output/plugins/wasapi/WasapiOutputPlugin.cxx b/src/output/plugins/wasapi/WasapiOutputPlugin.cxx index 4ec50db50..f9a509386 100644 --- a/src/output/plugins/wasapi/WasapiOutputPlugin.cxx +++ b/src/output/plugins/wasapi/WasapiOutputPlugin.cxx @@ -18,6 +18,7 @@ */ #include "WasapiOutputPlugin.hxx" +#include "output/OutputAPI.hxx" #include "lib/icu/Win32.hxx" #include "mixer/MixerList.hxx" #include "output/Error.hxx" @@ -49,8 +50,10 @@ #include #include +#include #include #include +#include namespace { static constexpr Domain wasapi_output_domain("wasapi_output"); diff --git a/src/output/plugins/wasapi/WasapiOutputPlugin.hxx b/src/output/plugins/wasapi/WasapiOutputPlugin.hxx index 77545c699..67ebaf426 100644 --- a/src/output/plugins/wasapi/WasapiOutputPlugin.hxx +++ b/src/output/plugins/wasapi/WasapiOutputPlugin.hxx @@ -20,13 +20,11 @@ #ifndef MPD_WASAPI_OUTPUT_PLUGIN_HXX #define MPD_WASAPI_OUTPUT_PLUGIN_HXX -#include "output/OutputAPI.hxx" - -#include -#include - extern const struct AudioOutputPlugin wasapi_output_plugin; +struct IMMDevice; +struct IAudioClient; +class AudioOutput; class WasapiOutput; [[gnu::pure]] From e6a81bb95c67773210979f0b20ce00954d8e210b Mon Sep 17 00:00:00 2001 From: Max Kellermann Date: Fri, 5 Mar 2021 16:29:35 +0100 Subject: [PATCH 06/18] output/wasapi: split the header Reduce header dependencies. --- src/mixer/plugins/WasapiMixerPlugin.cxx | 2 +- src/output/plugins/wasapi/ForMixer.hxx | 44 +++++++++++++++++++ .../plugins/wasapi/WasapiOutputPlugin.cxx | 1 + .../plugins/wasapi/WasapiOutputPlugin.hxx | 21 --------- 4 files changed, 46 insertions(+), 22 deletions(-) create mode 100644 src/output/plugins/wasapi/ForMixer.hxx diff --git a/src/mixer/plugins/WasapiMixerPlugin.cxx b/src/mixer/plugins/WasapiMixerPlugin.cxx index 786427c69..1b8190c12 100644 --- a/src/mixer/plugins/WasapiMixerPlugin.cxx +++ b/src/mixer/plugins/WasapiMixerPlugin.cxx @@ -17,7 +17,7 @@ * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. */ -#include "output/plugins/wasapi/WasapiOutputPlugin.hxx" +#include "output/plugins/wasapi/ForMixer.hxx" #include "mixer/MixerInternal.hxx" #include "win32/ComPtr.hxx" #include "win32/ComWorker.hxx" diff --git a/src/output/plugins/wasapi/ForMixer.hxx b/src/output/plugins/wasapi/ForMixer.hxx new file mode 100644 index 000000000..2d815ce61 --- /dev/null +++ b/src/output/plugins/wasapi/ForMixer.hxx @@ -0,0 +1,44 @@ +/* + * Copyright 2020-2021 The Music Player Daemon Project + * http://www.musicpd.org + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License along + * with this program; if not, write to the Free Software Foundation, Inc., + * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. + */ + +#ifndef MPD_WASAPI_OUTPUT_FOR_MIXER_HXX +#define MPD_WASAPI_OUTPUT_FOR_MIXER_HXX + +struct IMMDevice; +struct IAudioClient; +class AudioOutput; +class WasapiOutput; + +[[gnu::pure]] +WasapiOutput & +wasapi_output_downcast(AudioOutput &output) noexcept; + +[[gnu::pure]] +bool +wasapi_is_exclusive(WasapiOutput &output) noexcept; + +[[gnu::pure]] +IMMDevice * +wasapi_output_get_device(WasapiOutput &output) noexcept; + +[[gnu::pure]] +IAudioClient * +wasapi_output_get_client(WasapiOutput &output) noexcept; + +#endif diff --git a/src/output/plugins/wasapi/WasapiOutputPlugin.cxx b/src/output/plugins/wasapi/WasapiOutputPlugin.cxx index f9a509386..70add3c08 100644 --- a/src/output/plugins/wasapi/WasapiOutputPlugin.cxx +++ b/src/output/plugins/wasapi/WasapiOutputPlugin.cxx @@ -18,6 +18,7 @@ */ #include "WasapiOutputPlugin.hxx" +#include "ForMixer.hxx" #include "output/OutputAPI.hxx" #include "lib/icu/Win32.hxx" #include "mixer/MixerList.hxx" diff --git a/src/output/plugins/wasapi/WasapiOutputPlugin.hxx b/src/output/plugins/wasapi/WasapiOutputPlugin.hxx index 67ebaf426..9194b5059 100644 --- a/src/output/plugins/wasapi/WasapiOutputPlugin.hxx +++ b/src/output/plugins/wasapi/WasapiOutputPlugin.hxx @@ -22,25 +22,4 @@ extern const struct AudioOutputPlugin wasapi_output_plugin; -struct IMMDevice; -struct IAudioClient; -class AudioOutput; -class WasapiOutput; - -[[gnu::pure]] -WasapiOutput & -wasapi_output_downcast(AudioOutput &output) noexcept; - -[[gnu::pure]] -bool -wasapi_is_exclusive(WasapiOutput &output) noexcept; - -[[gnu::pure]] -IMMDevice * -wasapi_output_get_device(WasapiOutput &output) noexcept; - -[[gnu::pure]] -IAudioClient * -wasapi_output_get_client(WasapiOutput &output) noexcept; - #endif From b1a9958c662723dfbe3e1717e3bc135da157ce7b Mon Sep 17 00:00:00 2001 From: Max Kellermann Date: Fri, 5 Mar 2021 17:33:55 +0100 Subject: [PATCH 07/18] test/run_output: add struct CommandLine --- test/run_output.cxx | 46 +++++++++++++++++++++++++++++---------------- 1 file changed, 30 insertions(+), 16 deletions(-) diff --git a/test/run_output.cxx b/test/run_output.cxx index db567cab9..ad00c621f 100644 --- a/test/run_output.cxx +++ b/test/run_output.cxx @@ -39,6 +39,31 @@ #include #include +struct CommandLine { + FromNarrowPath config_path; + + const char *output_name = nullptr; + + AudioFormat audio_format{44100, SampleFormat::S16, 2}; +}; + +static CommandLine +ParseCommandLine(int argc, char **argv) +{ + CommandLine c; + + if (argc < 3 || argc > 4) + throw std::runtime_error("Usage: run_output CONFIG NAME [FORMAT] 3) + c.audio_format = ParseAudioFormat(argv[3], false); + + return c; +} + static std::unique_ptr LoadAudioOutput(const ConfigData &config, EventLoop &event_loop, const char *name) @@ -107,34 +132,23 @@ run_output(AudioOutput &ao, AudioFormat audio_format) int main(int argc, char **argv) try { - if (argc < 3 || argc > 4) { - fprintf(stderr, "Usage: run_output CONFIG NAME [FORMAT] 3) - audio_format = ParseAudioFormat(argv[3], false); + auto ao = LoadAudioOutput(config, io_thread.GetEventLoop(), + c.output_name); /* do it */ - run_output(*ao, audio_format); + run_output(*ao, c.audio_format); /* cleanup and exit */ From bc2988144e6b598e2f84c89418bfdd36292fda25 Mon Sep 17 00:00:00 2001 From: Max Kellermann Date: Fri, 5 Mar 2021 17:37:10 +0100 Subject: [PATCH 08/18] test/run_output: use OptionParser, add option "--verbose" --- test/run_output.cxx | 36 +++++++++++++++++++++++++++++++----- 1 file changed, 31 insertions(+), 5 deletions(-) diff --git a/test/run_output.cxx b/test/run_output.cxx index ad00c621f..bb7ba71b8 100644 --- a/test/run_output.cxx +++ b/test/run_output.cxx @@ -26,10 +26,13 @@ #include "fs/NarrowPath.hxx" #include "pcm/AudioParser.hxx" #include "pcm/AudioFormat.hxx" +#include "util/OptionDef.hxx" +#include "util/OptionParser.hxx" #include "util/StringBuffer.hxx" #include "util/RuntimeError.hxx" #include "util/ScopeExit.hxx" #include "util/PrintException.hxx" +#include "LogBackend.hxx" #include #include @@ -45,6 +48,16 @@ struct CommandLine { const char *output_name = nullptr; AudioFormat audio_format{44100, SampleFormat::S16, 2}; + + bool verbose = false; +}; + +enum Option { + OPTION_VERBOSE, +}; + +static constexpr OptionDef option_defs[] = { + {"verbose", 'v', false, "Verbose logging"}, }; static CommandLine @@ -52,14 +65,24 @@ ParseCommandLine(int argc, char **argv) { CommandLine c; - if (argc < 3 || argc > 4) + OptionParser option_parser(option_defs, argc, argv); + while (auto o = option_parser.Next()) { + switch (Option(o.index)) { + case OPTION_VERBOSE: + c.verbose = true; + break; + } + } + + auto args = option_parser.GetRemaining(); + if (args.size < 2 || args.size > 3) throw std::runtime_error("Usage: run_output CONFIG NAME [FORMAT] 3) - c.audio_format = ParseAudioFormat(argv[3], false); + if (args.size > 2) + c.audio_format = ParseAudioFormat(args[2], false); return c; } @@ -82,6 +105,8 @@ LoadAudioOutput(const ConfigData &config, EventLoop &event_loop, if (plugin == nullptr) throw FormatRuntimeError("No such audio output plugin: %s", plugin_name); +#include "util/OptionDef.hxx" +#include "util/OptionParser.hxx" return std::unique_ptr(ao_plugin_init(event_loop, *plugin, *block)); @@ -133,6 +158,7 @@ run_output(AudioOutput &ao, AudioFormat audio_format) int main(int argc, char **argv) try { const auto c = ParseCommandLine(argc, argv); + SetLogThreshold(c.verbose ? LogLevel::DEBUG : LogLevel::INFO); /* read configuration file (mpd.conf) */ From ebc1fe2821e152aca453466978848f0d698697cd Mon Sep 17 00:00:00 2001 From: Max Kellermann Date: Fri, 5 Mar 2021 16:53:15 +0100 Subject: [PATCH 09/18] win32/ComPtr: operator*() returns reference --- src/win32/ComPtr.hxx | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/src/win32/ComPtr.hxx b/src/win32/ComPtr.hxx index 325c06c10..01a8ef525 100644 --- a/src/win32/ComPtr.hxx +++ b/src/win32/ComPtr.hxx @@ -32,6 +32,7 @@ template class ComPtr { public: using pointer = T *; + using reference = T &; using element_type = T; constexpr ComPtr() noexcept : ptr(nullptr) {} @@ -75,7 +76,7 @@ public: pointer get() const noexcept { return ptr; } explicit operator bool() const noexcept { return ptr; } - auto operator*() const { return *ptr; } + reference operator*() const noexcept { return *ptr; } pointer operator->() const noexcept { return ptr; } void CoCreateInstance(REFCLSID class_id, LPUNKNOWN unknown_outer = nullptr, From 9ff790b7bb794b0828e034f14ae532680f349d0c Mon Sep 17 00:00:00 2001 From: Max Kellermann Date: Fri, 5 Mar 2021 16:47:26 +0100 Subject: [PATCH 10/18] output/wasapi: move COM utilities to separate headers --- src/mixer/plugins/WasapiMixerPlugin.cxx | 46 ++---- src/output/plugins/wasapi/AudioClient.hxx | 103 ++++++++++++ src/output/plugins/wasapi/Device.hxx | 117 ++++++++++++++ src/output/plugins/wasapi/PropertyStore.hxx | 44 +++++ .../plugins/wasapi/WasapiOutputPlugin.cxx | 151 ++++-------------- src/win32/PropVariant.cxx | 40 +++++ src/win32/PropVariant.hxx | 31 ++++ src/win32/meson.build | 1 + 8 files changed, 376 insertions(+), 157 deletions(-) create mode 100644 src/output/plugins/wasapi/AudioClient.hxx create mode 100644 src/output/plugins/wasapi/Device.hxx create mode 100644 src/output/plugins/wasapi/PropertyStore.hxx create mode 100644 src/win32/PropVariant.cxx create mode 100644 src/win32/PropVariant.hxx diff --git a/src/mixer/plugins/WasapiMixerPlugin.cxx b/src/mixer/plugins/WasapiMixerPlugin.cxx index 1b8190c12..18c862f29 100644 --- a/src/mixer/plugins/WasapiMixerPlugin.cxx +++ b/src/mixer/plugins/WasapiMixerPlugin.cxx @@ -18,6 +18,8 @@ */ #include "output/plugins/wasapi/ForMixer.hxx" +#include "output/plugins/wasapi/AudioClient.hxx" +#include "output/plugins/wasapi/Device.hxx" #include "mixer/MixerInternal.hxx" #include "win32/ComPtr.hxx" #include "win32/ComWorker.hxx" @@ -47,15 +49,8 @@ public: float volume_level; if (wasapi_is_exclusive(output)) { - ComPtr endpoint_volume; - result = wasapi_output_get_device(output)->Activate( - __uuidof(IAudioEndpointVolume), CLSCTX_ALL, - nullptr, endpoint_volume.AddressCast()); - if (FAILED(result)) { - throw FormatHResultError(result, - "Unable to get device " - "endpoint volume"); - } + auto endpoint_volume = + Activate(*wasapi_output_get_device(output)); result = endpoint_volume->GetMasterVolumeLevelScalar( &volume_level); @@ -65,15 +60,8 @@ public: "volume level"); } } else { - ComPtr session_volume; - result = wasapi_output_get_client(output)->GetService( - __uuidof(ISimpleAudioVolume), - session_volume.AddressCast()); - if (FAILED(result)) { - throw FormatHResultError(result, - "Unable to get client " - "session volume"); - } + auto session_volume = + GetService(*wasapi_output_get_client(output)); result = session_volume->GetMasterVolume(&volume_level); if (FAILED(result)) { @@ -93,15 +81,8 @@ public: const float volume_level = volume / 100.0f; if (wasapi_is_exclusive(output)) { - ComPtr endpoint_volume; - result = wasapi_output_get_device(output)->Activate( - __uuidof(IAudioEndpointVolume), CLSCTX_ALL, - nullptr, endpoint_volume.AddressCast()); - if (FAILED(result)) { - throw FormatHResultError( - result, - "Unable to get device endpoint volume"); - } + auto endpoint_volume = + Activate(*wasapi_output_get_device(output)); result = endpoint_volume->SetMasterVolumeLevelScalar( volume_level, nullptr); @@ -111,15 +92,8 @@ public: "Unable to set master volume level"); } } else { - ComPtr session_volume; - result = wasapi_output_get_client(output)->GetService( - __uuidof(ISimpleAudioVolume), - session_volume.AddressCast()); - if (FAILED(result)) { - throw FormatHResultError( - result, - "Unable to get client session volume"); - } + auto session_volume = + GetService(*wasapi_output_get_client(output)); result = session_volume->SetMasterVolume(volume_level, nullptr); diff --git a/src/output/plugins/wasapi/AudioClient.hxx b/src/output/plugins/wasapi/AudioClient.hxx new file mode 100644 index 000000000..3ed3da319 --- /dev/null +++ b/src/output/plugins/wasapi/AudioClient.hxx @@ -0,0 +1,103 @@ +/* + * Copyright 2020-2021 The Music Player Daemon Project + * http://www.musicpd.org + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License along + * with this program; if not, write to the Free Software Foundation, Inc., + * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. + */ + +#ifndef MPD_WASAPI_AUDIO_CLIENT_HXX +#define MPD_WASAPI_AUDIO_CLIENT_HXX + +#include "win32/ComHeapPtr.hxx" +#include "win32/ComPtr.hxx" +#include "win32/HResult.hxx" + +#include + +inline UINT32 +GetBufferSizeInFrames(IAudioClient &client) +{ + UINT32 buffer_size_in_frames; + + HRESULT result = client.GetBufferSize(&buffer_size_in_frames); + if (FAILED(result)) + throw FormatHResultError(result, + "Unable to get audio client buffer size"); + + return buffer_size_in_frames; +} + +inline UINT32 +GetCurrentPaddingFrames(IAudioClient &client) +{ + UINT32 padding_frames; + + HRESULT result = client.GetCurrentPadding(&padding_frames); + if (FAILED(result)) + throw FormatHResultError(result, + "Failed to get current padding"); + + return padding_frames; +} + +inline ComHeapPtr +GetMixFormat(IAudioClient &client) +{ + WAVEFORMATEX *f; + + HRESULT result = client.GetMixFormat(&f); + if (FAILED(result)) + throw FormatHResultError(result, "GetMixFormat failed"); + + return ComHeapPtr{f}; +} + +inline void +Start(IAudioClient &client) +{ + HRESULT result = client.Start(); + if (FAILED(result)) + throw FormatHResultError(result, "Failed to start client"); +} + +inline void +Stop(IAudioClient &client) +{ + HRESULT result = client.Stop(); + if (FAILED(result)) + throw FormatHResultError(result, "Failed to stop client"); +} + +inline void +SetEventHandle(IAudioClient &client, HANDLE h) +{ + HRESULT result = client.SetEventHandle(h); + if (FAILED(result)) + throw FormatHResultError(result, "Unable to set event handle"); +} + +template +inline ComPtr +GetService(IAudioClient &client) +{ + T *p = nullptr; + HRESULT result = client.GetService(IID_PPV_ARGS(&p)); + if (FAILED(result)) + throw FormatHResultError(result, "Unable to get service"); + + return ComPtr{p}; +} + +#endif diff --git a/src/output/plugins/wasapi/Device.hxx b/src/output/plugins/wasapi/Device.hxx new file mode 100644 index 000000000..863f327a7 --- /dev/null +++ b/src/output/plugins/wasapi/Device.hxx @@ -0,0 +1,117 @@ +/* + * Copyright 2020-2021 The Music Player Daemon Project + * http://www.musicpd.org + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License along + * with this program; if not, write to the Free Software Foundation, Inc., + * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. + */ + +#ifndef MPD_WASAPI_DEVICE_COLLECTION_HXX +#define MPD_WASAPI_DEVICE_COLLECTION_HXX + +#include "win32/ComPtr.hxx" +#include "win32/HResult.hxx" + +#include + +inline ComPtr +GetDefaultAudioEndpoint(IMMDeviceEnumerator &e) +{ + IMMDevice *device = nullptr; + + HRESULT result = e.GetDefaultAudioEndpoint(eRender, eMultimedia, + &device); + if (FAILED(result)) + throw FormatHResultError(result, + "Unable to get default device for multimedia"); + + return ComPtr{device}; +} + +inline ComPtr +EnumAudioEndpoints(IMMDeviceEnumerator &e) +{ + IMMDeviceCollection *dc = nullptr; + + HRESULT result = e.EnumAudioEndpoints(eRender, DEVICE_STATE_ACTIVE, + &dc); + if (FAILED(result)) + throw FormatHResultError(result, "Unable to enumerate devices"); + + return ComPtr{dc}; +} + +inline UINT +GetCount(IMMDeviceCollection &dc) +{ + UINT count; + + HRESULT result = dc.GetCount(&count); + if (FAILED(result)) + throw FormatHResultError(result, "Collection->GetCount failed"); + + return count; +} + +inline ComPtr +Item(IMMDeviceCollection &dc, UINT i) +{ + IMMDevice *device = nullptr; + + auto result = dc.Item(i, &device); + if (FAILED(result)) + throw FormatHResultError(result, "Collection->Item failed"); + + return ComPtr{device}; +} + +inline DWORD +GetState(IMMDevice &device) +{ + DWORD state; + + HRESULT result = device.GetState(&state);; + if (FAILED(result)) + throw FormatHResultError(result, "Unable to get device status"); + + return state; +} + +template +inline ComPtr +Activate(IMMDevice &device) +{ + T *p = nullptr; + HRESULT result = device.Activate(__uuidof(T), CLSCTX_ALL, + nullptr, (void **)&p); + if (FAILED(result)) + throw FormatHResultError(result, "Unable to activate device"); + + return ComPtr{p}; +} + +inline ComPtr +OpenPropertyStore(IMMDevice &device) +{ + IPropertyStore *property_store = nullptr; + + HRESULT result = device.OpenPropertyStore(STGM_READ, &property_store); + if (FAILED(result)) + throw FormatHResultError(result, + "Device->OpenPropertyStore failed"); + + return ComPtr{property_store}; +} + +#endif diff --git a/src/output/plugins/wasapi/PropertyStore.hxx b/src/output/plugins/wasapi/PropertyStore.hxx new file mode 100644 index 000000000..6d974087f --- /dev/null +++ b/src/output/plugins/wasapi/PropertyStore.hxx @@ -0,0 +1,44 @@ +/* + * Copyright 2020-2021 The Music Player Daemon Project + * http://www.musicpd.org + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License along + * with this program; if not, write to the Free Software Foundation, Inc., + * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. + */ + +#ifndef MPD_WASAPI_PROPERTY_STORE_HXX +#define MPD_WASAPI_PROPERTY_STORE_HXX + +#include "win32/PropVariant.hxx" +#include "util/AllocatedString.hxx" +#include "util/ScopeExit.hxx" + +#include + +[[gnu::pure]] +inline AllocatedString +GetString(IPropertyStore &ps, REFPROPERTYKEY key) noexcept +{ + PROPVARIANT pv; + PropVariantInit(&pv); + + HRESULT result = ps.GetValue(key, &pv); + if (FAILED(result)) + return nullptr; + + AtScopeExit(&) { PropVariantClear(&pv); }; + return ToString(pv); +} + +#endif diff --git a/src/output/plugins/wasapi/WasapiOutputPlugin.cxx b/src/output/plugins/wasapi/WasapiOutputPlugin.cxx index 70add3c08..58a12918d 100644 --- a/src/output/plugins/wasapi/WasapiOutputPlugin.cxx +++ b/src/output/plugins/wasapi/WasapiOutputPlugin.cxx @@ -19,6 +19,9 @@ #include "WasapiOutputPlugin.hxx" #include "ForMixer.hxx" +#include "AudioClient.hxx" +#include "Device.hxx" +#include "PropertyStore.hxx" #include "output/OutputAPI.hxx" #include "lib/icu/Win32.hxx" #include "mixer/MixerList.hxx" @@ -35,7 +38,6 @@ #include "util/ScopeExit.hxx" #include "util/StringBuffer.hxx" #include "win32/Com.hxx" -#include "win32/ComHeapPtr.hxx" #include "win32/ComPtr.hxx" #include "win32/ComWorker.hxx" #include "win32/HResult.hxx" @@ -254,7 +256,6 @@ private: void EnumerateDevices(); void GetDevice(unsigned int index); unsigned int SearchDevice(std::string_view name); - void GetDefaultDevice(); }; WasapiOutput &wasapi_output_downcast(AudioOutput &output) noexcept { @@ -288,13 +289,8 @@ void WasapiOutputThread::Work() noexcept { UINT32 write_in_frames = buffer_size_in_frames; if (!is_exclusive) { - UINT32 data_in_frames; - if (HRESULT result = - client->GetCurrentPadding(&data_in_frames); - FAILED(result)) { - throw FormatHResultError( - result, "Failed to get current padding"); - } + UINT32 data_in_frames = + GetCurrentPaddingFrames(*client); if (data_in_frames >= buffer_size_in_frames) { continue; @@ -366,20 +362,12 @@ void WasapiOutput::DoDisable() noexcept { void WasapiOutput::DoOpen(AudioFormat &audio_format) { client.reset(); - DWORD state; - if (HRESULT result = device->GetState(&state); FAILED(result)) { - throw FormatHResultError(result, "Unable to get device status"); - } - if (state != DEVICE_STATE_ACTIVE) { + if (GetState(*device) != DEVICE_STATE_ACTIVE) { device.reset(); OpenDevice(); } - if (HRESULT result = device->Activate(__uuidof(IAudioClient), CLSCTX_ALL, nullptr, - client.AddressCast()); - FAILED(result)) { - throw FormatHResultError(result, "Unable to activate audio client"); - } + client = Activate(*device); if (audio_format.channels > 8) { audio_format.channels = 8; @@ -453,13 +441,8 @@ void WasapiOutput::DoOpen(AudioFormat &audio_format) { FAILED(result)) { if (result == AUDCLNT_E_BUFFER_SIZE_NOT_ALIGNED) { // https://docs.microsoft.com/en-us/windows/win32/api/audioclient/nf-audioclient-iaudioclient-initialize - UINT32 buffer_size_in_frames = 0; - result = client->GetBufferSize(&buffer_size_in_frames); - if (FAILED(result)) { - throw FormatHResultError( - result, - "Unable to get audio client buffer size"); - } + UINT32 buffer_size_in_frames = + GetBufferSizeInFrames(*client); buffer_duration = std::ceil(double(buffer_size_in_frames * hundred_ns(s(1)).count()) / @@ -469,14 +452,7 @@ void WasapiOutput::DoOpen(AudioFormat &audio_format) { "Aligned buffer duration: %I64u ns", size_t(ns(hundred_ns(buffer_duration)).count())); client.reset(); - result = device->Activate(__uuidof(IAudioClient), - CLSCTX_ALL, nullptr, - client.AddressCast()); - if (FAILED(result)) { - throw FormatHResultError( - result, - "Unable to activate audio client"); - } + client = Activate(*device); result = client->Initialize( AUDCLNT_SHAREMODE_EXCLUSIVE, AUDCLNT_STREAMFLAGS_EVENTCALLBACK, @@ -501,27 +477,15 @@ void WasapiOutput::DoOpen(AudioFormat &audio_format) { } } - ComPtr render_client; - if (HRESULT result = client->GetService(IID_PPV_ARGS(render_client.Address())); - FAILED(result)) { - throw FormatHResultError(result, "Unable to get new render client"); - } + auto render_client = GetService(*client); - UINT32 buffer_size_in_frames; - if (HRESULT result = client->GetBufferSize(&buffer_size_in_frames); - FAILED(result)) { - throw FormatHResultError(result, - "Unable to get audio client buffer size"); - } + const UINT32 buffer_size_in_frames = GetBufferSizeInFrames(*client); watermark = buffer_size_in_frames * 3 * FrameSize(); thread.emplace(client.get(), std::move(render_client), FrameSize(), buffer_size_in_frames, is_exclusive); - if (HRESULT result = client->SetEventHandle(thread->event.handle()); - FAILED(result)) { - throw FormatHResultError(result, "Unable to set event handler"); - } + SetEventHandle(*client, thread->event.handle()); thread->Start(); } @@ -531,9 +495,7 @@ void WasapiOutput::Close() noexcept { try { COMWorker::Async([&]() { - if (HRESULT result = client->Stop(); FAILED(result)) { - throw FormatHResultError(result, "Failed to stop client"); - } + Stop(*client); }).get(); thread->CheckException(); } catch (std::exception &err) { @@ -595,10 +557,7 @@ size_t WasapiOutput::Play(const void *chunk, size_t size) { is_started = true; thread->Play(); COMWorker::Async([&]() { - if (HRESULT result = client->Start(); FAILED(result)) { - throw FormatHResultError( - result, "Failed to start client"); - } + Start(*client); }).wait(); } @@ -660,7 +619,7 @@ void WasapiOutput::OpenDevice() { } if (!device) { - GetDefaultDevice(); + device = GetDefaultAudioEndpoint(*enumerator); } device_desc.clear(); @@ -735,13 +694,10 @@ void WasapiOutput::FindExclusiveFormatSupported(AudioFormat &audio_format) { /// run inside COMWorkerThread void WasapiOutput::FindSharedFormatSupported(AudioFormat &audio_format) { HRESULT result; - ComHeapPtr mixer_format; // In shared mode, different sample rate is always unsupported. - result = client->GetMixFormat(mixer_format.Address()); - if (FAILED(result)) { - throw FormatHResultError(result, "GetMixFormat failed"); - } + auto mixer_format = GetMixFormat(*client); + audio_format.sample_rate = mixer_format->nSamplesPerSec; device_format = GetFormats(audio_format).front(); @@ -846,66 +802,30 @@ void WasapiOutput::EnumerateDevices() { HRESULT result; - ComPtr device_collection; - result = enumerator->EnumAudioEndpoints(eRender, DEVICE_STATE_ACTIVE, - device_collection.Address()); - if (FAILED(result)) { - throw FormatHResultError(result, "Unable to enumerate devices"); - } + const auto device_collection = EnumAudioEndpoints(*enumerator); - UINT count; - result = device_collection->GetCount(&count); - if (FAILED(result)) { - throw FormatHResultError(result, "Collection->GetCount failed"); - } + const UINT count = GetCount(*device_collection); device_desc.reserve(count); for (UINT i = 0; i < count; ++i) { - ComPtr enumerated_device; - result = device_collection->Item(i, enumerated_device.Address()); - if (FAILED(result)) { - throw FormatHResultError(result, "Collection->Item failed"); - } + const auto enumerated_device = Item(*device_collection, i); - ComPtr property_store; - result = enumerated_device->OpenPropertyStore(STGM_READ, - property_store.Address()); - if (FAILED(result)) { - throw FormatHResultError(result, - "Device->OpenPropertyStore failed"); - } + const auto property_store = + OpenPropertyStore(*enumerated_device); - PROPVARIANT var_name; - PropVariantInit(&var_name); - AtScopeExit(&) { PropVariantClear(&var_name); }; + auto name = GetString(*property_store, + PKEY_Device_FriendlyName); + if (name == nullptr) + continue; - result = property_store->GetValue(PKEY_Device_FriendlyName, &var_name); - if (FAILED(result)) { - throw FormatHResultError(result, - "PropertyStore->GetValue failed"); - } - - device_desc.emplace_back( - i, WideCharToMultiByte(CP_UTF8, - std::wstring_view(var_name.pwszVal))); + device_desc.emplace_back(i, std::move(name)); } } /// run inside COMWorkerThread void WasapiOutput::GetDevice(unsigned int index) { - HRESULT result; - - ComPtr device_collection; - result = enumerator->EnumAudioEndpoints(eRender, DEVICE_STATE_ACTIVE, - device_collection.Address()); - if (FAILED(result)) { - throw FormatHResultError(result, "Unable to enumerate devices"); - } - - result = device_collection->Item(index, device.Address()); - if (FAILED(result)) { - throw FormatHResultError(result, "Collection->Item failed"); - } + const auto device_collection = EnumAudioEndpoints(*enumerator); + device = Item(*device_collection, index); } /// run inside COMWorkerThread @@ -926,17 +846,6 @@ unsigned int WasapiOutput::SearchDevice(std::string_view name) { return iter->first; } -/// run inside COMWorkerThread -void WasapiOutput::GetDefaultDevice() { - HRESULT result; - result = enumerator->GetDefaultAudioEndpoint(eRender, eMultimedia, - device.Address()); - if (FAILED(result)) { - throw FormatHResultError(result, - "Unable to get default device for multimedia"); - } -} - static bool wasapi_output_test_default_device() { return true; } const struct AudioOutputPlugin wasapi_output_plugin = { diff --git a/src/win32/PropVariant.cxx b/src/win32/PropVariant.cxx new file mode 100644 index 000000000..5fa728eeb --- /dev/null +++ b/src/win32/PropVariant.cxx @@ -0,0 +1,40 @@ +/* + * Copyright 2020-2021 The Music Player Daemon Project + * http://www.musicpd.org + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License along + * with this program; if not, write to the Free Software Foundation, Inc., + * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. + */ + +#include "PropVariant.hxx" +#include "lib/icu/Win32.hxx" +#include "util/AllocatedString.hxx" +#include "util/ScopeExit.hxx" + +AllocatedString +ToString(const PROPVARIANT &pv) noexcept +{ + // TODO: VT_BSTR + + switch (pv.vt) { + case VT_LPSTR: + return AllocatedString{static_cast(pv.pszVal)}; + + case VT_LPWSTR: + return WideCharToMultiByte(CP_UTF8, pv.pwszVal); + + default: + return nullptr; + } +} diff --git a/src/win32/PropVariant.hxx b/src/win32/PropVariant.hxx new file mode 100644 index 000000000..54da5e988 --- /dev/null +++ b/src/win32/PropVariant.hxx @@ -0,0 +1,31 @@ +/* + * Copyright 2020-2021 The Music Player Daemon Project + * http://www.musicpd.org + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License along + * with this program; if not, write to the Free Software Foundation, Inc., + * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. + */ + +#ifndef MPD_WIN32_PROPVARIANT_HXX +#define MPD_WIN32_PROPVARIANT_HXX + +#include + +class AllocatedString; + +[[gnu::pure]] +AllocatedString +ToString(const PROPVARIANT &pv) noexcept; + +#endif diff --git a/src/win32/meson.build b/src/win32/meson.build index 5f8eaf6b7..28f958a8a 100644 --- a/src/win32/meson.build +++ b/src/win32/meson.build @@ -7,6 +7,7 @@ win32 = static_library( 'win32', 'ComWorker.cxx', 'HResult.cxx', + 'PropVariant.cxx', 'WinEvent.cxx', include_directories: inc, ) From 17f7098e2754d916da6ee1e48909af8ab2f3e574 Mon Sep 17 00:00:00 2001 From: Max Kellermann Date: Fri, 5 Mar 2021 19:11:57 +0100 Subject: [PATCH 11/18] output/wasapi: SafeTry() catches all exceptions Fixes crash due to std::stoul() throwing std::invalid_argument. --- src/output/plugins/wasapi/WasapiOutputPlugin.cxx | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/src/output/plugins/wasapi/WasapiOutputPlugin.cxx b/src/output/plugins/wasapi/WasapiOutputPlugin.cxx index 58a12918d..13ddb3f1e 100644 --- a/src/output/plugins/wasapi/WasapiOutputPlugin.cxx +++ b/src/output/plugins/wasapi/WasapiOutputPlugin.cxx @@ -90,8 +90,8 @@ inline bool SafeTry(Functor &&functor) { try { functor(); return true; - } catch (std::runtime_error &err) { - FormatError(wasapi_output_domain, "%s", err.what()); + } catch (...) { + FormatError(std::current_exception(), "%s"); return false; } } @@ -101,7 +101,7 @@ inline bool SafeSilenceTry(Functor &&functor) { try { functor(); return true; - } catch (std::runtime_error &err) { + } catch (...) { return false; } } From 8b41c4f3848c646425548ad19aeb245d2b5acc6e Mon Sep 17 00:00:00 2001 From: Max Kellermann Date: Fri, 5 Mar 2021 19:13:38 +0100 Subject: [PATCH 12/18] output/wasapi: release the COMWorker if OpenDevice() fails Fixes assertion failure in the Thread destructor. --- src/output/plugins/wasapi/WasapiOutputPlugin.cxx | 8 +++++++- 1 file changed, 7 insertions(+), 1 deletion(-) diff --git a/src/output/plugins/wasapi/WasapiOutputPlugin.cxx b/src/output/plugins/wasapi/WasapiOutputPlugin.cxx index 13ddb3f1e..4c306989e 100644 --- a/src/output/plugins/wasapi/WasapiOutputPlugin.cxx +++ b/src/output/plugins/wasapi/WasapiOutputPlugin.cxx @@ -205,7 +205,13 @@ public: WasapiOutput(const ConfigBlock &block); void Enable() override { COMWorker::Aquire(); - COMWorker::Async([&]() { OpenDevice(); }).get(); + + try { + COMWorker::Async([&]() { OpenDevice(); }).get(); + } catch (...) { + COMWorker::Release(); + throw; + } } void Disable() noexcept override { COMWorker::Async([&]() { DoDisable(); }).get(); From 17d4873b60e7b151ef8844f6bf35197061f8cdb6 Mon Sep 17 00:00:00 2001 From: Max Kellermann Date: Fri, 5 Mar 2021 19:06:02 +0100 Subject: [PATCH 13/18] output/wasapi: use default device only if none was configured --- NEWS | 1 + src/output/plugins/wasapi/WasapiOutputPlugin.cxx | 11 +++++------ 2 files changed, 6 insertions(+), 6 deletions(-) diff --git a/NEWS b/NEWS index 4c0e93818..e139092bf 100644 --- a/NEWS +++ b/NEWS @@ -7,6 +7,7 @@ ver 0.22.7 (not yet released) - curl: don't use glibc extension * output - wasapi: add algorithm for finding usable audio format + - wasapi: use default device only if none was configured ver 0.22.6 (2021/02/16) * fix missing tags on songs in queue diff --git a/src/output/plugins/wasapi/WasapiOutputPlugin.cxx b/src/output/plugins/wasapi/WasapiOutputPlugin.cxx index 4c306989e..5c234db56 100644 --- a/src/output/plugins/wasapi/WasapiOutputPlugin.cxx +++ b/src/output/plugins/wasapi/WasapiOutputPlugin.cxx @@ -617,14 +617,13 @@ void WasapiOutput::OpenDevice() { if (!device_config.empty()) { if (!SafeSilenceTry([this, &id]() { id = std::stoul(device_config); })) { id = SearchDevice(device_config); + if (id == kErrorId) + throw FormatRuntimeError("Device '%s' not found", + device_config.c_str()); } - } - if (id != kErrorId) { - SafeTry([this, id]() { GetDevice(id); }); - } - - if (!device) { + GetDevice(id); + } else { device = GetDefaultAudioEndpoint(*enumerator); } From 2ff6a9ad2bc1de412f1233bd70856a6bad11ef44 Mon Sep 17 00:00:00 2001 From: Max Kellermann Date: Fri, 5 Mar 2021 19:07:18 +0100 Subject: [PATCH 14/18] output/wasapi: GetDevice() returns IMMDevice --- src/output/plugins/wasapi/WasapiOutputPlugin.cxx | 10 ++++++---- 1 file changed, 6 insertions(+), 4 deletions(-) diff --git a/src/output/plugins/wasapi/WasapiOutputPlugin.cxx b/src/output/plugins/wasapi/WasapiOutputPlugin.cxx index 5c234db56..741846444 100644 --- a/src/output/plugins/wasapi/WasapiOutputPlugin.cxx +++ b/src/output/plugins/wasapi/WasapiOutputPlugin.cxx @@ -260,7 +260,7 @@ private: void FindExclusiveFormatSupported(AudioFormat &audio_format); void FindSharedFormatSupported(AudioFormat &audio_format); void EnumerateDevices(); - void GetDevice(unsigned int index); + ComPtr GetDevice(unsigned int index); unsigned int SearchDevice(std::string_view name); }; @@ -622,7 +622,7 @@ void WasapiOutput::OpenDevice() { device_config.c_str()); } - GetDevice(id); + device = GetDevice(id); } else { device = GetDefaultAudioEndpoint(*enumerator); } @@ -828,9 +828,11 @@ void WasapiOutput::EnumerateDevices() { } /// run inside COMWorkerThread -void WasapiOutput::GetDevice(unsigned int index) { +ComPtr +WasapiOutput::GetDevice(unsigned int index) +{ const auto device_collection = EnumAudioEndpoints(*enumerator); - device = Item(*device_collection, index); + return Item(*device_collection, index); } /// run inside COMWorkerThread From 2f2b3f1cdc72a1afadbc151f12285669ca83fe39 Mon Sep 17 00:00:00 2001 From: Max Kellermann Date: Fri, 5 Mar 2021 19:01:45 +0100 Subject: [PATCH 15/18] output/wasapi: SearchDevice() returns IMMDevice --- .../plugins/wasapi/WasapiOutputPlugin.cxx | 21 ++++++++++--------- 1 file changed, 11 insertions(+), 10 deletions(-) diff --git a/src/output/plugins/wasapi/WasapiOutputPlugin.cxx b/src/output/plugins/wasapi/WasapiOutputPlugin.cxx index 741846444..78c7c461f 100644 --- a/src/output/plugins/wasapi/WasapiOutputPlugin.cxx +++ b/src/output/plugins/wasapi/WasapiOutputPlugin.cxx @@ -261,7 +261,7 @@ private: void FindSharedFormatSupported(AudioFormat &audio_format); void EnumerateDevices(); ComPtr GetDevice(unsigned int index); - unsigned int SearchDevice(std::string_view name); + ComPtr SearchDevice(std::string_view name); }; WasapiOutput &wasapi_output_downcast(AudioOutput &output) noexcept { @@ -616,13 +616,12 @@ void WasapiOutput::OpenDevice() { unsigned int id = kErrorId; if (!device_config.empty()) { if (!SafeSilenceTry([this, &id]() { id = std::stoul(device_config); })) { - id = SearchDevice(device_config); - if (id == kErrorId) + device = SearchDevice(device_config); + if (!device) throw FormatRuntimeError("Device '%s' not found", device_config.c_str()); - } - - device = GetDevice(id); + } else + device = GetDevice(id); } else { device = GetDefaultAudioEndpoint(*enumerator); } @@ -836,9 +835,11 @@ WasapiOutput::GetDevice(unsigned int index) } /// run inside COMWorkerThread -unsigned int WasapiOutput::SearchDevice(std::string_view name) { +ComPtr +WasapiOutput::SearchDevice(std::string_view name) +{ if (!SafeTry([this]() { EnumerateDevices(); })) { - return kErrorId; + return nullptr; } auto iter = std::find_if(device_desc.cbegin(), device_desc.cend(), @@ -846,11 +847,11 @@ unsigned int WasapiOutput::SearchDevice(std::string_view name) { if (iter == device_desc.cend()) { FormatError(wasapi_output_domain, "Device %.*s not founded.", int(name.size()), name.data()); - return kErrorId; + return nullptr; } FormatInfo(wasapi_output_domain, "Select device \"%u\" \"%s\"", iter->first, iter->second.c_str()); - return iter->first; + return GetDevice(iter->first); } static bool wasapi_output_test_default_device() { return true; } From a4257e51d5c5ad6f23e63cde38f14d72a8efd85c Mon Sep 17 00:00:00 2001 From: Max Kellermann Date: Fri, 5 Mar 2021 19:20:32 +0100 Subject: [PATCH 16/18] output/wasapi: reimplement SearchDevice() without EnumerateDevices() --- .../plugins/wasapi/WasapiOutputPlugin.cxx | 27 +++++++++---------- 1 file changed, 12 insertions(+), 15 deletions(-) diff --git a/src/output/plugins/wasapi/WasapiOutputPlugin.cxx b/src/output/plugins/wasapi/WasapiOutputPlugin.cxx index 78c7c461f..ea470f1a1 100644 --- a/src/output/plugins/wasapi/WasapiOutputPlugin.cxx +++ b/src/output/plugins/wasapi/WasapiOutputPlugin.cxx @@ -804,8 +804,6 @@ void WasapiOutput::EnumerateDevices() { return; } - HRESULT result; - const auto device_collection = EnumAudioEndpoints(*enumerator); const UINT count = GetCount(*device_collection); @@ -838,20 +836,19 @@ WasapiOutput::GetDevice(unsigned int index) ComPtr WasapiOutput::SearchDevice(std::string_view name) { - if (!SafeTry([this]() { EnumerateDevices(); })) { - return nullptr; + const auto device_collection = EnumAudioEndpoints(*enumerator); + + const UINT count = GetCount(*device_collection); + for (UINT i = 0; i < count; ++i) { + auto d = Item(*device_collection, i); + + const auto property_store = OpenPropertyStore(*d); + auto n = GetString(*property_store, PKEY_Device_FriendlyName); + if (n != nullptr && name.compare(n) == 0) + return d; } - auto iter = - std::find_if(device_desc.cbegin(), device_desc.cend(), - [&name](const auto &desc) { return desc.second == name; }); - if (iter == device_desc.cend()) { - FormatError(wasapi_output_domain, "Device %.*s not founded.", - int(name.size()), name.data()); - return nullptr; - } - FormatInfo(wasapi_output_domain, "Select device \"%u\" \"%s\"", iter->first, - iter->second.c_str()); - return GetDevice(iter->first); + + return nullptr; } static bool wasapi_output_test_default_device() { return true; } From fc20a1f10a79a84a081c177eeb2b74c91660d8d1 Mon Sep 17 00:00:00 2001 From: Max Kellermann Date: Fri, 5 Mar 2021 19:26:38 +0100 Subject: [PATCH 17/18] output/wasapi: EnumerateDevices() logs, no std::vector --- .../plugins/wasapi/WasapiOutputPlugin.cxx | 23 ++++++------------- 1 file changed, 7 insertions(+), 16 deletions(-) diff --git a/src/output/plugins/wasapi/WasapiOutputPlugin.cxx b/src/output/plugins/wasapi/WasapiOutputPlugin.cxx index ea470f1a1..23c0e88e2 100644 --- a/src/output/plugins/wasapi/WasapiOutputPlugin.cxx +++ b/src/output/plugins/wasapi/WasapiOutputPlugin.cxx @@ -239,7 +239,6 @@ private: bool is_exclusive; bool enumerate_devices; std::string device_config; - std::vector> device_desc; ComPtr enumerator; ComPtr device; ComPtr client; @@ -604,12 +603,11 @@ void WasapiOutput::OpenDevice() { enumerator.CoCreateInstance(__uuidof(MMDeviceEnumerator), nullptr, CLSCTX_INPROC_SERVER); - if (enumerate_devices && SafeTry([this]() { EnumerateDevices(); })) { - for (const auto &[device, desc] : device_desc) { - FormatNotice(wasapi_output_domain, - "Device \"%u\" \"%s\"", - device, - desc.c_str()); + if (enumerate_devices) { + try { + EnumerateDevices(); + } catch (...) { + LogError(std::current_exception()); } } @@ -625,8 +623,6 @@ void WasapiOutput::OpenDevice() { } else { device = GetDefaultAudioEndpoint(*enumerator); } - - device_desc.clear(); } /// run inside COMWorkerThread @@ -800,15 +796,9 @@ void WasapiOutput::FindSharedFormatSupported(AudioFormat &audio_format) { /// run inside COMWorkerThread void WasapiOutput::EnumerateDevices() { - if (!device_desc.empty()) { - return; - } - const auto device_collection = EnumAudioEndpoints(*enumerator); const UINT count = GetCount(*device_collection); - - device_desc.reserve(count); for (UINT i = 0; i < count; ++i) { const auto enumerated_device = Item(*device_collection, i); @@ -820,7 +810,8 @@ void WasapiOutput::EnumerateDevices() { if (name == nullptr) continue; - device_desc.emplace_back(i, std::move(name)); + FormatNotice(wasapi_output_domain, + "Device \"%u\" \"%s\"", i, name.c_str()); } } From 93016ac6aba0053f1d740527c6c123a052d5e40d Mon Sep 17 00:00:00 2001 From: Max Kellermann Date: Fri, 5 Mar 2021 19:33:22 +0100 Subject: [PATCH 18/18] output/wasapi: check AUDCLNT_E_EXCLUSIVE_MODE_NOT_ALLOWED Stop early, don't try more formats if it is clear that we have no chance. --- src/output/plugins/wasapi/WasapiOutputPlugin.cxx | 3 +++ 1 file changed, 3 insertions(+) diff --git a/src/output/plugins/wasapi/WasapiOutputPlugin.cxx b/src/output/plugins/wasapi/WasapiOutputPlugin.cxx index 23c0e88e2..9a3616817 100644 --- a/src/output/plugins/wasapi/WasapiOutputPlugin.cxx +++ b/src/output/plugins/wasapi/WasapiOutputPlugin.cxx @@ -642,6 +642,9 @@ bool WasapiOutput::TryFormatExclusive(const AudioFormat &audio_format) { device_format = test_format; return true; } + + if (result == AUDCLNT_E_EXCLUSIVE_MODE_NOT_ALLOWED) + throw std::runtime_error("Exclusive mode not allowed"); } return false; }