Commit e1b62fb9 authored by Max Kellermann's avatar Max Kellermann

Merge branch 'v0.22.x'

parents 422cf5f1 93016ac6
...@@ -15,6 +15,7 @@ ver 0.22.7 (not yet released) ...@@ -15,6 +15,7 @@ ver 0.22.7 (not yet released)
- curl: don't use glibc extension - curl: don't use glibc extension
* output * output
- wasapi: add algorithm for finding usable audio format - wasapi: add algorithm for finding usable audio format
- wasapi: use default device only if none was configured
ver 0.22.6 (2021/02/16) ver 0.22.6 (2021/02/16)
* fix missing tags on songs in queue * fix missing tags on songs in queue
......
...@@ -17,15 +17,21 @@ ...@@ -17,15 +17,21 @@
* 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
*/ */
#include "output/plugins/wasapi/ForMixer.hxx"
#include "output/plugins/wasapi/AudioClient.hxx"
#include "output/plugins/wasapi/Device.hxx"
#include "mixer/MixerInternal.hxx" #include "mixer/MixerInternal.hxx"
#include "output/plugins/WasapiOutputPlugin.hxx" #include "win32/ComPtr.hxx"
#include "win32/ComWorker.hxx" #include "win32/ComWorker.hxx"
#include "win32/HResult.hxx" #include "win32/HResult.hxx"
#include <cmath> #include <cmath>
#include <endpointvolume.h>
#include <optional> #include <optional>
#include <audioclient.h>
#include <endpointvolume.h>
#include <mmdeviceapi.h>
class WasapiMixer final : public Mixer { class WasapiMixer final : public Mixer {
WasapiOutput &output; WasapiOutput &output;
...@@ -43,15 +49,8 @@ public: ...@@ -43,15 +49,8 @@ public:
float volume_level; float volume_level;
if (wasapi_is_exclusive(output)) { if (wasapi_is_exclusive(output)) {
ComPtr<IAudioEndpointVolume> endpoint_volume; auto endpoint_volume =
result = wasapi_output_get_device(output)->Activate( Activate<IAudioEndpointVolume>(*wasapi_output_get_device(output));
__uuidof(IAudioEndpointVolume), CLSCTX_ALL,
nullptr, endpoint_volume.AddressCast());
if (FAILED(result)) {
throw FormatHResultError(result,
"Unable to get device "
"endpoint volume");
}
result = endpoint_volume->GetMasterVolumeLevelScalar( result = endpoint_volume->GetMasterVolumeLevelScalar(
&volume_level); &volume_level);
...@@ -61,15 +60,8 @@ public: ...@@ -61,15 +60,8 @@ public:
"volume level"); "volume level");
} }
} else { } else {
ComPtr<ISimpleAudioVolume> session_volume; auto session_volume =
result = wasapi_output_get_client(output)->GetService( GetService<ISimpleAudioVolume>(*wasapi_output_get_client(output));
__uuidof(ISimpleAudioVolume),
session_volume.AddressCast<void>());
if (FAILED(result)) {
throw FormatHResultError(result,
"Unable to get client "
"session volume");
}
result = session_volume->GetMasterVolume(&volume_level); result = session_volume->GetMasterVolume(&volume_level);
if (FAILED(result)) { if (FAILED(result)) {
...@@ -89,15 +81,8 @@ public: ...@@ -89,15 +81,8 @@ public:
const float volume_level = volume / 100.0f; const float volume_level = volume / 100.0f;
if (wasapi_is_exclusive(output)) { if (wasapi_is_exclusive(output)) {
ComPtr<IAudioEndpointVolume> endpoint_volume; auto endpoint_volume =
result = wasapi_output_get_device(output)->Activate( Activate<IAudioEndpointVolume>(*wasapi_output_get_device(output));
__uuidof(IAudioEndpointVolume), CLSCTX_ALL,
nullptr, endpoint_volume.AddressCast());
if (FAILED(result)) {
throw FormatHResultError(
result,
"Unable to get device endpoint volume");
}
result = endpoint_volume->SetMasterVolumeLevelScalar( result = endpoint_volume->SetMasterVolumeLevelScalar(
volume_level, nullptr); volume_level, nullptr);
...@@ -107,15 +92,8 @@ public: ...@@ -107,15 +92,8 @@ public:
"Unable to set master volume level"); "Unable to set master volume level");
} }
} else { } else {
ComPtr<ISimpleAudioVolume> session_volume; auto session_volume =
result = wasapi_output_get_client(output)->GetService( GetService<ISimpleAudioVolume>(*wasapi_output_get_client(output));
__uuidof(ISimpleAudioVolume),
session_volume.AddressCast<void>());
if (FAILED(result)) {
throw FormatHResultError(
result,
"Unable to get client session volume");
}
result = session_volume->SetMasterVolume(volume_level, result = session_volume->SetMasterVolume(volume_level,
nullptr); nullptr);
......
...@@ -44,7 +44,7 @@ ...@@ -44,7 +44,7 @@
#include "plugins/WinmmOutputPlugin.hxx" #include "plugins/WinmmOutputPlugin.hxx"
#endif #endif
#ifdef ENABLE_WASAPI_OUTPUT #ifdef ENABLE_WASAPI_OUTPUT
#include "plugins/WasapiOutputPlugin.hxx" #include "plugins/wasapi/WasapiOutputPlugin.hxx"
#endif #endif
#include "util/StringAPI.hxx" #include "util/StringAPI.hxx"
......
...@@ -154,7 +154,7 @@ endif ...@@ -154,7 +154,7 @@ endif
output_features.set('ENABLE_WASAPI_OUTPUT', is_windows) output_features.set('ENABLE_WASAPI_OUTPUT', is_windows)
if is_windows if is_windows
output_plugins_sources += [ output_plugins_sources += [
'WasapiOutputPlugin.cxx', 'wasapi/WasapiOutputPlugin.cxx',
] ]
wasapi_dep = [ wasapi_dep = [
c_compiler.find_library('ksuser', required: true), c_compiler.find_library('ksuser', required: true),
......
/*
* 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 <audioclient.h>
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<WAVEFORMATEX>
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<typename T>
inline ComPtr<T>
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
/*
* 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 <mmdeviceapi.h>
inline ComPtr<IMMDevice>
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<IMMDeviceCollection>
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<IMMDevice>
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<typename T>
inline ComPtr<T>
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<IPropertyStore>
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
/*
* 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
/*
* 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 <propsys.h>
[[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
...@@ -20,25 +20,6 @@ ...@@ -20,25 +20,6 @@
#ifndef MPD_WASAPI_OUTPUT_PLUGIN_HXX #ifndef MPD_WASAPI_OUTPUT_PLUGIN_HXX
#define MPD_WASAPI_OUTPUT_PLUGIN_HXX #define MPD_WASAPI_OUTPUT_PLUGIN_HXX
#include "output/Features.h"
#include "../OutputAPI.hxx"
#include "util/Compiler.h"
#include "win32/ComPtr.hxx"
#include <audioclient.h>
#include <mmdeviceapi.h>
extern const struct AudioOutputPlugin wasapi_output_plugin; extern const struct AudioOutputPlugin wasapi_output_plugin;
class WasapiOutput;
gcc_pure WasapiOutput &wasapi_output_downcast(AudioOutput &output) noexcept;
gcc_pure bool wasapi_is_exclusive(WasapiOutput &output) noexcept;
gcc_pure IMMDevice *wasapi_output_get_device(WasapiOutput &output) noexcept;
gcc_pure IAudioClient *wasapi_output_get_client(WasapiOutput &output) noexcept;
#endif #endif
...@@ -32,6 +32,7 @@ template <typename T> ...@@ -32,6 +32,7 @@ template <typename T>
class ComPtr { class ComPtr {
public: public:
using pointer = T *; using pointer = T *;
using reference = T &;
using element_type = T; using element_type = T;
constexpr ComPtr() noexcept : ptr(nullptr) {} constexpr ComPtr() noexcept : ptr(nullptr) {}
...@@ -75,7 +76,7 @@ public: ...@@ -75,7 +76,7 @@ public:
pointer get() const noexcept { return ptr; } pointer get() const noexcept { return ptr; }
explicit operator bool() 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; } pointer operator->() const noexcept { return ptr; }
void CoCreateInstance(REFCLSID class_id, LPUNKNOWN unknown_outer = nullptr, void CoCreateInstance(REFCLSID class_id, LPUNKNOWN unknown_outer = nullptr,
......
/*
* 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<const char *>(pv.pszVal)};
case VT_LPWSTR:
return WideCharToMultiByte(CP_UTF8, pv.pwszVal);
default:
return nullptr;
}
}
/*
* 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 <propidl.h>
class AllocatedString;
[[gnu::pure]]
AllocatedString
ToString(const PROPVARIANT &pv) noexcept;
#endif
...@@ -7,6 +7,7 @@ win32 = static_library( ...@@ -7,6 +7,7 @@ win32 = static_library(
'win32', 'win32',
'ComWorker.cxx', 'ComWorker.cxx',
'HResult.cxx', 'HResult.cxx',
'PropVariant.cxx',
'WinEvent.cxx', 'WinEvent.cxx',
include_directories: inc, include_directories: inc,
) )
......
...@@ -26,10 +26,13 @@ ...@@ -26,10 +26,13 @@
#include "fs/NarrowPath.hxx" #include "fs/NarrowPath.hxx"
#include "pcm/AudioParser.hxx" #include "pcm/AudioParser.hxx"
#include "pcm/AudioFormat.hxx" #include "pcm/AudioFormat.hxx"
#include "util/OptionDef.hxx"
#include "util/OptionParser.hxx"
#include "util/StringBuffer.hxx" #include "util/StringBuffer.hxx"
#include "util/RuntimeError.hxx" #include "util/RuntimeError.hxx"
#include "util/ScopeExit.hxx" #include "util/ScopeExit.hxx"
#include "util/PrintException.hxx" #include "util/PrintException.hxx"
#include "LogBackend.hxx"
#include <cassert> #include <cassert>
#include <memory> #include <memory>
...@@ -39,6 +42,51 @@ ...@@ -39,6 +42,51 @@
#include <stdlib.h> #include <stdlib.h>
#include <stdio.h> #include <stdio.h>
struct CommandLine {
FromNarrowPath config_path;
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
ParseCommandLine(int argc, char **argv)
{
CommandLine c;
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] <IN");
c.config_path = args[0];
c.output_name = args[1];
if (args.size > 2)
c.audio_format = ParseAudioFormat(args[2], false);
return c;
}
static std::unique_ptr<AudioOutput> static std::unique_ptr<AudioOutput>
LoadAudioOutput(const ConfigData &config, EventLoop &event_loop, LoadAudioOutput(const ConfigData &config, EventLoop &event_loop,
const char *name) const char *name)
...@@ -57,6 +105,8 @@ LoadAudioOutput(const ConfigData &config, EventLoop &event_loop, ...@@ -57,6 +105,8 @@ LoadAudioOutput(const ConfigData &config, EventLoop &event_loop,
if (plugin == nullptr) if (plugin == nullptr)
throw FormatRuntimeError("No such audio output plugin: %s", throw FormatRuntimeError("No such audio output plugin: %s",
plugin_name); plugin_name);
#include "util/OptionDef.hxx"
#include "util/OptionParser.hxx"
return std::unique_ptr<AudioOutput>(ao_plugin_init(event_loop, *plugin, return std::unique_ptr<AudioOutput>(ao_plugin_init(event_loop, *plugin,
*block)); *block));
...@@ -107,34 +157,24 @@ run_output(AudioOutput &ao, AudioFormat audio_format) ...@@ -107,34 +157,24 @@ run_output(AudioOutput &ao, AudioFormat audio_format)
int main(int argc, char **argv) int main(int argc, char **argv)
try { try {
if (argc < 3 || argc > 4) { const auto c = ParseCommandLine(argc, argv);
fprintf(stderr, "Usage: run_output CONFIG NAME [FORMAT] <IN\n"); SetLogThreshold(c.verbose ? LogLevel::DEBUG : LogLevel::INFO);
return EXIT_FAILURE;
}
const FromNarrowPath config_path = argv[1];
AudioFormat audio_format(44100, SampleFormat::S16, 2);
/* read configuration file (mpd.conf) */ /* read configuration file (mpd.conf) */
const auto config = AutoLoadConfigFile(config_path); const auto config = AutoLoadConfigFile(c.config_path);
EventThread io_thread; EventThread io_thread;
io_thread.Start(); io_thread.Start();
/* initialize the audio output */ /* initialize the audio output */
auto ao = LoadAudioOutput(config, io_thread.GetEventLoop(), argv[2]); auto ao = LoadAudioOutput(config, io_thread.GetEventLoop(),
c.output_name);
/* parse the audio format */
if (argc > 3)
audio_format = ParseAudioFormat(argv[3], false);
/* do it */ /* do it */
run_output(*ao, audio_format); run_output(*ao, c.audio_format);
/* cleanup and exit */ /* cleanup and exit */
......
Markdown is supported
0% or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment