Merge branch 'v0.22.x'
This commit is contained in:
commit
e1b62fb90d
1
NEWS
1
NEWS
|
@ -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 @@
|
||||||
* 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:
|
||||||
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:
|
||||||
"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:
|
||||||
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:
|
||||||
"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 @@
|
||||||
#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
|
||||||
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),
|
||||||
|
|
|
@ -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 <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
|
|
@ -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 <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
|
|
@ -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
|
|
@ -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 <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
|
|
@ -16,10 +16,13 @@
|
||||||
* with this program; if not, write to the Free Software Foundation, Inc.,
|
* with this program; if not, write to the Free Software Foundation, Inc.,
|
||||||
* 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
|
* 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
|
||||||
*/
|
*/
|
||||||
#include <initguid.h>
|
|
||||||
|
|
||||||
#include "Log.hxx"
|
|
||||||
#include "WasapiOutputPlugin.hxx"
|
#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 "lib/icu/Win32.hxx"
|
||||||
#include "mixer/MixerList.hxx"
|
#include "mixer/MixerList.hxx"
|
||||||
#include "output/Error.hxx"
|
#include "output/Error.hxx"
|
||||||
|
@ -35,19 +38,26 @@
|
||||||
#include "util/ScopeExit.hxx"
|
#include "util/ScopeExit.hxx"
|
||||||
#include "util/StringBuffer.hxx"
|
#include "util/StringBuffer.hxx"
|
||||||
#include "win32/Com.hxx"
|
#include "win32/Com.hxx"
|
||||||
#include "win32/ComHeapPtr.hxx"
|
#include "win32/ComPtr.hxx"
|
||||||
#include "win32/ComWorker.hxx"
|
#include "win32/ComWorker.hxx"
|
||||||
#include "win32/HResult.hxx"
|
#include "win32/HResult.hxx"
|
||||||
#include "win32/WinEvent.hxx"
|
#include "win32/WinEvent.hxx"
|
||||||
|
#include "Log.hxx"
|
||||||
|
#include "config.h"
|
||||||
|
|
||||||
|
#include <boost/lockfree/spsc_queue.hpp>
|
||||||
|
|
||||||
#include <algorithm>
|
#include <algorithm>
|
||||||
#include <boost/lockfree/spsc_queue.hpp>
|
|
||||||
#include <cinttypes>
|
#include <cinttypes>
|
||||||
#include <cmath>
|
#include <cmath>
|
||||||
#include <functiondiscoverykeys_devpkey.h>
|
|
||||||
#include <optional>
|
#include <optional>
|
||||||
#include <variant>
|
#include <variant>
|
||||||
|
|
||||||
|
#include <audioclient.h>
|
||||||
|
#include <initguid.h>
|
||||||
|
#include <functiondiscoverykeys_devpkey.h>
|
||||||
|
#include <mmdeviceapi.h>
|
||||||
|
|
||||||
namespace {
|
namespace {
|
||||||
static constexpr Domain wasapi_output_domain("wasapi_output");
|
static constexpr Domain wasapi_output_domain("wasapi_output");
|
||||||
|
|
||||||
|
@ -80,8 +90,8 @@ inline bool SafeTry(Functor &&functor) {
|
||||||
try {
|
try {
|
||||||
functor();
|
functor();
|
||||||
return true;
|
return true;
|
||||||
} catch (std::runtime_error &err) {
|
} catch (...) {
|
||||||
FormatError(wasapi_output_domain, "%s", err.what());
|
FormatError(std::current_exception(), "%s");
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -91,7 +101,7 @@ inline bool SafeSilenceTry(Functor &&functor) {
|
||||||
try {
|
try {
|
||||||
functor();
|
functor();
|
||||||
return true;
|
return true;
|
||||||
} catch (std::runtime_error &err) {
|
} catch (...) {
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -195,7 +205,13 @@ public:
|
||||||
WasapiOutput(const ConfigBlock &block);
|
WasapiOutput(const ConfigBlock &block);
|
||||||
void Enable() override {
|
void Enable() override {
|
||||||
COMWorker::Aquire();
|
COMWorker::Aquire();
|
||||||
COMWorker::Async([&]() { OpenDevice(); }).get();
|
|
||||||
|
try {
|
||||||
|
COMWorker::Async([&]() { OpenDevice(); }).get();
|
||||||
|
} catch (...) {
|
||||||
|
COMWorker::Release();
|
||||||
|
throw;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
void Disable() noexcept override {
|
void Disable() noexcept override {
|
||||||
COMWorker::Async([&]() { DoDisable(); }).get();
|
COMWorker::Async([&]() { DoDisable(); }).get();
|
||||||
|
@ -223,7 +239,6 @@ private:
|
||||||
bool is_exclusive;
|
bool is_exclusive;
|
||||||
bool enumerate_devices;
|
bool enumerate_devices;
|
||||||
std::string device_config;
|
std::string device_config;
|
||||||
std::vector<std::pair<unsigned int, AllocatedString>> device_desc;
|
|
||||||
ComPtr<IMMDeviceEnumerator> enumerator;
|
ComPtr<IMMDeviceEnumerator> enumerator;
|
||||||
ComPtr<IMMDevice> device;
|
ComPtr<IMMDevice> device;
|
||||||
ComPtr<IAudioClient> client;
|
ComPtr<IAudioClient> client;
|
||||||
|
@ -244,9 +259,8 @@ private:
|
||||||
void FindExclusiveFormatSupported(AudioFormat &audio_format);
|
void FindExclusiveFormatSupported(AudioFormat &audio_format);
|
||||||
void FindSharedFormatSupported(AudioFormat &audio_format);
|
void FindSharedFormatSupported(AudioFormat &audio_format);
|
||||||
void EnumerateDevices();
|
void EnumerateDevices();
|
||||||
void GetDevice(unsigned int index);
|
ComPtr<IMMDevice> GetDevice(unsigned int index);
|
||||||
unsigned int SearchDevice(std::string_view name);
|
ComPtr<IMMDevice> SearchDevice(std::string_view name);
|
||||||
void GetDefaultDevice();
|
|
||||||
};
|
};
|
||||||
|
|
||||||
WasapiOutput &wasapi_output_downcast(AudioOutput &output) noexcept {
|
WasapiOutput &wasapi_output_downcast(AudioOutput &output) noexcept {
|
||||||
|
@ -280,13 +294,8 @@ void WasapiOutputThread::Work() noexcept {
|
||||||
|
|
||||||
UINT32 write_in_frames = buffer_size_in_frames;
|
UINT32 write_in_frames = buffer_size_in_frames;
|
||||||
if (!is_exclusive) {
|
if (!is_exclusive) {
|
||||||
UINT32 data_in_frames;
|
UINT32 data_in_frames =
|
||||||
if (HRESULT result =
|
GetCurrentPaddingFrames(*client);
|
||||||
client->GetCurrentPadding(&data_in_frames);
|
|
||||||
FAILED(result)) {
|
|
||||||
throw FormatHResultError(
|
|
||||||
result, "Failed to get current padding");
|
|
||||||
}
|
|
||||||
|
|
||||||
if (data_in_frames >= buffer_size_in_frames) {
|
if (data_in_frames >= buffer_size_in_frames) {
|
||||||
continue;
|
continue;
|
||||||
|
@ -358,20 +367,12 @@ void WasapiOutput::DoDisable() noexcept {
|
||||||
void WasapiOutput::DoOpen(AudioFormat &audio_format) {
|
void WasapiOutput::DoOpen(AudioFormat &audio_format) {
|
||||||
client.reset();
|
client.reset();
|
||||||
|
|
||||||
DWORD state;
|
if (GetState(*device) != DEVICE_STATE_ACTIVE) {
|
||||||
if (HRESULT result = device->GetState(&state); FAILED(result)) {
|
|
||||||
throw FormatHResultError(result, "Unable to get device status");
|
|
||||||
}
|
|
||||||
if (state != DEVICE_STATE_ACTIVE) {
|
|
||||||
device.reset();
|
device.reset();
|
||||||
OpenDevice();
|
OpenDevice();
|
||||||
}
|
}
|
||||||
|
|
||||||
if (HRESULT result = device->Activate(__uuidof(IAudioClient), CLSCTX_ALL, nullptr,
|
client = Activate<IAudioClient>(*device);
|
||||||
client.AddressCast());
|
|
||||||
FAILED(result)) {
|
|
||||||
throw FormatHResultError(result, "Unable to activate audio client");
|
|
||||||
}
|
|
||||||
|
|
||||||
if (audio_format.channels > 8) {
|
if (audio_format.channels > 8) {
|
||||||
audio_format.channels = 8;
|
audio_format.channels = 8;
|
||||||
|
@ -445,13 +446,8 @@ void WasapiOutput::DoOpen(AudioFormat &audio_format) {
|
||||||
FAILED(result)) {
|
FAILED(result)) {
|
||||||
if (result == AUDCLNT_E_BUFFER_SIZE_NOT_ALIGNED) {
|
if (result == AUDCLNT_E_BUFFER_SIZE_NOT_ALIGNED) {
|
||||||
// https://docs.microsoft.com/en-us/windows/win32/api/audioclient/nf-audioclient-iaudioclient-initialize
|
// https://docs.microsoft.com/en-us/windows/win32/api/audioclient/nf-audioclient-iaudioclient-initialize
|
||||||
UINT32 buffer_size_in_frames = 0;
|
UINT32 buffer_size_in_frames =
|
||||||
result = client->GetBufferSize(&buffer_size_in_frames);
|
GetBufferSizeInFrames(*client);
|
||||||
if (FAILED(result)) {
|
|
||||||
throw FormatHResultError(
|
|
||||||
result,
|
|
||||||
"Unable to get audio client buffer size");
|
|
||||||
}
|
|
||||||
buffer_duration =
|
buffer_duration =
|
||||||
std::ceil(double(buffer_size_in_frames *
|
std::ceil(double(buffer_size_in_frames *
|
||||||
hundred_ns(s(1)).count()) /
|
hundred_ns(s(1)).count()) /
|
||||||
|
@ -461,14 +457,7 @@ void WasapiOutput::DoOpen(AudioFormat &audio_format) {
|
||||||
"Aligned buffer duration: %I64u ns",
|
"Aligned buffer duration: %I64u ns",
|
||||||
size_t(ns(hundred_ns(buffer_duration)).count()));
|
size_t(ns(hundred_ns(buffer_duration)).count()));
|
||||||
client.reset();
|
client.reset();
|
||||||
result = device->Activate(__uuidof(IAudioClient),
|
client = Activate<IAudioClient>(*device);
|
||||||
CLSCTX_ALL, nullptr,
|
|
||||||
client.AddressCast());
|
|
||||||
if (FAILED(result)) {
|
|
||||||
throw FormatHResultError(
|
|
||||||
result,
|
|
||||||
"Unable to activate audio client");
|
|
||||||
}
|
|
||||||
result = client->Initialize(
|
result = client->Initialize(
|
||||||
AUDCLNT_SHAREMODE_EXCLUSIVE,
|
AUDCLNT_SHAREMODE_EXCLUSIVE,
|
||||||
AUDCLNT_STREAMFLAGS_EVENTCALLBACK,
|
AUDCLNT_STREAMFLAGS_EVENTCALLBACK,
|
||||||
|
@ -493,27 +482,15 @@ void WasapiOutput::DoOpen(AudioFormat &audio_format) {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
ComPtr<IAudioRenderClient> render_client;
|
auto render_client = GetService<IAudioRenderClient>(*client);
|
||||||
if (HRESULT result = client->GetService(IID_PPV_ARGS(render_client.Address()));
|
|
||||||
FAILED(result)) {
|
|
||||||
throw FormatHResultError(result, "Unable to get new render client");
|
|
||||||
}
|
|
||||||
|
|
||||||
UINT32 buffer_size_in_frames;
|
const UINT32 buffer_size_in_frames = GetBufferSizeInFrames(*client);
|
||||||
if (HRESULT result = client->GetBufferSize(&buffer_size_in_frames);
|
|
||||||
FAILED(result)) {
|
|
||||||
throw FormatHResultError(result,
|
|
||||||
"Unable to get audio client buffer size");
|
|
||||||
}
|
|
||||||
|
|
||||||
watermark = buffer_size_in_frames * 3 * FrameSize();
|
watermark = buffer_size_in_frames * 3 * FrameSize();
|
||||||
thread.emplace(client.get(), std::move(render_client), FrameSize(),
|
thread.emplace(client.get(), std::move(render_client), FrameSize(),
|
||||||
buffer_size_in_frames, is_exclusive);
|
buffer_size_in_frames, is_exclusive);
|
||||||
|
|
||||||
if (HRESULT result = client->SetEventHandle(thread->event.handle());
|
SetEventHandle(*client, thread->event.handle());
|
||||||
FAILED(result)) {
|
|
||||||
throw FormatHResultError(result, "Unable to set event handler");
|
|
||||||
}
|
|
||||||
|
|
||||||
thread->Start();
|
thread->Start();
|
||||||
}
|
}
|
||||||
|
@ -523,9 +500,7 @@ void WasapiOutput::Close() noexcept {
|
||||||
|
|
||||||
try {
|
try {
|
||||||
COMWorker::Async([&]() {
|
COMWorker::Async([&]() {
|
||||||
if (HRESULT result = client->Stop(); FAILED(result)) {
|
Stop(*client);
|
||||||
throw FormatHResultError(result, "Failed to stop client");
|
|
||||||
}
|
|
||||||
}).get();
|
}).get();
|
||||||
thread->CheckException();
|
thread->CheckException();
|
||||||
} catch (std::exception &err) {
|
} catch (std::exception &err) {
|
||||||
|
@ -587,10 +562,7 @@ size_t WasapiOutput::Play(const void *chunk, size_t size) {
|
||||||
is_started = true;
|
is_started = true;
|
||||||
thread->Play();
|
thread->Play();
|
||||||
COMWorker::Async([&]() {
|
COMWorker::Async([&]() {
|
||||||
if (HRESULT result = client->Start(); FAILED(result)) {
|
Start(*client);
|
||||||
throw FormatHResultError(
|
|
||||||
result, "Failed to start client");
|
|
||||||
}
|
|
||||||
}).wait();
|
}).wait();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -631,31 +603,26 @@ void WasapiOutput::OpenDevice() {
|
||||||
enumerator.CoCreateInstance(__uuidof(MMDeviceEnumerator), nullptr,
|
enumerator.CoCreateInstance(__uuidof(MMDeviceEnumerator), nullptr,
|
||||||
CLSCTX_INPROC_SERVER);
|
CLSCTX_INPROC_SERVER);
|
||||||
|
|
||||||
if (enumerate_devices && SafeTry([this]() { EnumerateDevices(); })) {
|
if (enumerate_devices) {
|
||||||
for (const auto &[device, desc] : device_desc) {
|
try {
|
||||||
FormatNotice(wasapi_output_domain,
|
EnumerateDevices();
|
||||||
"Device \"%u\" \"%s\"",
|
} catch (...) {
|
||||||
device,
|
LogError(std::current_exception());
|
||||||
desc.c_str());
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
unsigned int id = kErrorId;
|
unsigned int id = kErrorId;
|
||||||
if (!device_config.empty()) {
|
if (!device_config.empty()) {
|
||||||
if (!SafeSilenceTry([this, &id]() { id = std::stoul(device_config); })) {
|
if (!SafeSilenceTry([this, &id]() { id = std::stoul(device_config); })) {
|
||||||
id = SearchDevice(device_config);
|
device = SearchDevice(device_config);
|
||||||
}
|
if (!device)
|
||||||
|
throw FormatRuntimeError("Device '%s' not found",
|
||||||
|
device_config.c_str());
|
||||||
|
} else
|
||||||
|
device = GetDevice(id);
|
||||||
|
} else {
|
||||||
|
device = GetDefaultAudioEndpoint(*enumerator);
|
||||||
}
|
}
|
||||||
|
|
||||||
if (id != kErrorId) {
|
|
||||||
SafeTry([this, id]() { GetDevice(id); });
|
|
||||||
}
|
|
||||||
|
|
||||||
if (!device) {
|
|
||||||
GetDefaultDevice();
|
|
||||||
}
|
|
||||||
|
|
||||||
device_desc.clear();
|
|
||||||
}
|
}
|
||||||
|
|
||||||
/// run inside COMWorkerThread
|
/// run inside COMWorkerThread
|
||||||
|
@ -675,6 +642,9 @@ bool WasapiOutput::TryFormatExclusive(const AudioFormat &audio_format) {
|
||||||
device_format = test_format;
|
device_format = test_format;
|
||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if (result == AUDCLNT_E_EXCLUSIVE_MODE_NOT_ALLOWED)
|
||||||
|
throw std::runtime_error("Exclusive mode not allowed");
|
||||||
}
|
}
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
|
@ -727,13 +697,10 @@ void WasapiOutput::FindExclusiveFormatSupported(AudioFormat &audio_format) {
|
||||||
/// run inside COMWorkerThread
|
/// run inside COMWorkerThread
|
||||||
void WasapiOutput::FindSharedFormatSupported(AudioFormat &audio_format) {
|
void WasapiOutput::FindSharedFormatSupported(AudioFormat &audio_format) {
|
||||||
HRESULT result;
|
HRESULT result;
|
||||||
ComHeapPtr<WAVEFORMATEX> mixer_format;
|
|
||||||
|
|
||||||
// In shared mode, different sample rate is always unsupported.
|
// In shared mode, different sample rate is always unsupported.
|
||||||
result = client->GetMixFormat(mixer_format.Address());
|
auto mixer_format = GetMixFormat(*client);
|
||||||
if (FAILED(result)) {
|
|
||||||
throw FormatHResultError(result, "GetMixFormat failed");
|
|
||||||
}
|
|
||||||
audio_format.sample_rate = mixer_format->nSamplesPerSec;
|
audio_format.sample_rate = mixer_format->nSamplesPerSec;
|
||||||
device_format = GetFormats(audio_format).front();
|
device_format = GetFormats(audio_format).front();
|
||||||
|
|
||||||
|
@ -832,101 +799,50 @@ void WasapiOutput::FindSharedFormatSupported(AudioFormat &audio_format) {
|
||||||
|
|
||||||
/// run inside COMWorkerThread
|
/// run inside COMWorkerThread
|
||||||
void WasapiOutput::EnumerateDevices() {
|
void WasapiOutput::EnumerateDevices() {
|
||||||
if (!device_desc.empty()) {
|
const auto device_collection = EnumAudioEndpoints(*enumerator);
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
HRESULT result;
|
const UINT count = GetCount(*device_collection);
|
||||||
|
|
||||||
ComPtr<IMMDeviceCollection> device_collection;
|
|
||||||
result = enumerator->EnumAudioEndpoints(eRender, DEVICE_STATE_ACTIVE,
|
|
||||||
device_collection.Address());
|
|
||||||
if (FAILED(result)) {
|
|
||||||
throw FormatHResultError(result, "Unable to enumerate devices");
|
|
||||||
}
|
|
||||||
|
|
||||||
UINT count;
|
|
||||||
result = device_collection->GetCount(&count);
|
|
||||||
if (FAILED(result)) {
|
|
||||||
throw FormatHResultError(result, "Collection->GetCount failed");
|
|
||||||
}
|
|
||||||
|
|
||||||
device_desc.reserve(count);
|
|
||||||
for (UINT i = 0; i < count; ++i) {
|
for (UINT i = 0; i < count; ++i) {
|
||||||
ComPtr<IMMDevice> enumerated_device;
|
const auto enumerated_device = Item(*device_collection, i);
|
||||||
result = device_collection->Item(i, enumerated_device.Address());
|
|
||||||
if (FAILED(result)) {
|
|
||||||
throw FormatHResultError(result, "Collection->Item failed");
|
|
||||||
}
|
|
||||||
|
|
||||||
ComPtr<IPropertyStore> property_store;
|
const auto property_store =
|
||||||
result = enumerated_device->OpenPropertyStore(STGM_READ,
|
OpenPropertyStore(*enumerated_device);
|
||||||
property_store.Address());
|
|
||||||
if (FAILED(result)) {
|
|
||||||
throw FormatHResultError(result,
|
|
||||||
"Device->OpenPropertyStore failed");
|
|
||||||
}
|
|
||||||
|
|
||||||
PROPVARIANT var_name;
|
auto name = GetString(*property_store,
|
||||||
PropVariantInit(&var_name);
|
PKEY_Device_FriendlyName);
|
||||||
AtScopeExit(&) { PropVariantClear(&var_name); };
|
if (name == nullptr)
|
||||||
|
continue;
|
||||||
|
|
||||||
result = property_store->GetValue(PKEY_Device_FriendlyName, &var_name);
|
FormatNotice(wasapi_output_domain,
|
||||||
if (FAILED(result)) {
|
"Device \"%u\" \"%s\"", i, name.c_str());
|
||||||
throw FormatHResultError(result,
|
|
||||||
"PropertyStore->GetValue failed");
|
|
||||||
}
|
|
||||||
|
|
||||||
device_desc.emplace_back(
|
|
||||||
i, WideCharToMultiByte(CP_UTF8,
|
|
||||||
std::wstring_view(var_name.pwszVal)));
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
/// run inside COMWorkerThread
|
/// run inside COMWorkerThread
|
||||||
void WasapiOutput::GetDevice(unsigned int index) {
|
ComPtr<IMMDevice>
|
||||||
HRESULT result;
|
WasapiOutput::GetDevice(unsigned int index)
|
||||||
|
{
|
||||||
ComPtr<IMMDeviceCollection> device_collection;
|
const auto device_collection = EnumAudioEndpoints(*enumerator);
|
||||||
result = enumerator->EnumAudioEndpoints(eRender, DEVICE_STATE_ACTIVE,
|
return Item(*device_collection, index);
|
||||||
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");
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
/// run inside COMWorkerThread
|
/// run inside COMWorkerThread
|
||||||
unsigned int WasapiOutput::SearchDevice(std::string_view name) {
|
ComPtr<IMMDevice>
|
||||||
if (!SafeTry([this]() { EnumerateDevices(); })) {
|
WasapiOutput::SearchDevice(std::string_view name)
|
||||||
return kErrorId;
|
{
|
||||||
}
|
const auto device_collection = EnumAudioEndpoints(*enumerator);
|
||||||
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 kErrorId;
|
|
||||||
}
|
|
||||||
FormatInfo(wasapi_output_domain, "Select device \"%u\" \"%s\"", iter->first,
|
|
||||||
iter->second.c_str());
|
|
||||||
return iter->first;
|
|
||||||
}
|
|
||||||
|
|
||||||
/// run inside COMWorkerThread
|
const UINT count = GetCount(*device_collection);
|
||||||
void WasapiOutput::GetDefaultDevice() {
|
for (UINT i = 0; i < count; ++i) {
|
||||||
HRESULT result;
|
auto d = Item(*device_collection, i);
|
||||||
result = enumerator->GetDefaultAudioEndpoint(eRender, eMultimedia,
|
|
||||||
device.Address());
|
const auto property_store = OpenPropertyStore(*d);
|
||||||
if (FAILED(result)) {
|
auto n = GetString(*property_store, PKEY_Device_FriendlyName);
|
||||||
throw FormatHResultError(result,
|
if (n != nullptr && name.compare(n) == 0)
|
||||||
"Unable to get default device for multimedia");
|
return d;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
return nullptr;
|
||||||
}
|
}
|
||||||
|
|
||||||
static bool wasapi_output_test_default_device() { return true; }
|
static bool wasapi_output_test_default_device() { return true; }
|
|
@ -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>
|
||||||
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:
|
||||||
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,
|
||||||
|
|
|
@ -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<const char *>(pv.pszVal)};
|
||||||
|
|
||||||
|
case VT_LPWSTR:
|
||||||
|
return WideCharToMultiByte(CP_UTF8, pv.pwszVal);
|
||||||
|
|
||||||
|
default:
|
||||||
|
return nullptr;
|
||||||
|
}
|
||||||
|
}
|
|
@ -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 <propidl.h>
|
||||||
|
|
||||||
|
class AllocatedString;
|
||||||
|
|
||||||
|
[[gnu::pure]]
|
||||||
|
AllocatedString
|
||||||
|
ToString(const PROPVARIANT &pv) noexcept;
|
||||||
|
|
||||||
|
#endif
|
|
@ -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 @@
|
||||||
#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 @@
|
||||||
#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,
|
||||||
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)
|
||||||
|
|
||||||
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 */
|
||||||
|
|
||||||
|
|
Loading…
Reference in New Issue