Commit ef1acb4e authored by Max Kellermann's avatar Max Kellermann

Merge branch 'v0.22.x'

parents d4bbb8c8 da642b28
...@@ -7,8 +7,14 @@ ver 0.23 (not yet released) ...@@ -7,8 +7,14 @@ ver 0.23 (not yet released)
- snapcast: new plugin - snapcast: new plugin
ver 0.22.7 (not yet released) ver 0.22.7 (not yet released)
* protocol
- don't use glibc extension to parse time stamps
* decoder * decoder
- ffmpeg: fix build problem with FFmpeg 3.4 - ffmpeg: fix build problem with FFmpeg 3.4
* storage
- curl: don't use glibc extension
* output
- wasapi: add algorithm for finding usable audio format
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
......
...@@ -141,6 +141,15 @@ Basically, there are two ways to compile :program:`MPD` for Windows: ...@@ -141,6 +141,15 @@ Basically, there are two ways to compile :program:`MPD` for Windows:
This section is about the latter. This section is about the latter.
You need:
* `mingw-w64 <http://mingw-w64.org/doku.php>`__
* `Meson 0.49.0 <http://mesonbuild.com/>`__ and `Ninja
<https://ninja-build.org/>`__
* cmake
* pkg-config
* quilt
Just like with the native build, unpack the :program:`MPD` source Just like with the native build, unpack the :program:`MPD` source
tarball and change into the directory. Then, instead of tarball and change into the directory. Then, instead of
:program:`meson`, type: :program:`meson`, type:
...@@ -168,6 +177,11 @@ You need: ...@@ -168,6 +177,11 @@ You need:
* Android SDK * Android SDK
* `Android NDK r22 <https://developer.android.com/ndk/downloads>`_ * `Android NDK r22 <https://developer.android.com/ndk/downloads>`_
* `Meson 0.49.0 <http://mesonbuild.com/>`__ and `Ninja
<https://ninja-build.org/>`__
* cmake
* pkg-config
* quilt
Just like with the native build, unpack the :program:`MPD` source Just like with the native build, unpack the :program:`MPD` source
tarball and change into the directory. Then, instead of tarball and change into the directory. Then, instead of
......
...@@ -261,7 +261,6 @@ sources = [ ...@@ -261,7 +261,6 @@ sources = [
'src/LogInit.cxx', 'src/LogInit.cxx',
'src/ls.cxx', 'src/ls.cxx',
'src/Instance.cxx', 'src/Instance.cxx',
'src/win32/Win32Main.cxx',
'src/MusicBuffer.cxx', 'src/MusicBuffer.cxx',
'src/MusicPipe.cxx', 'src/MusicPipe.cxx',
'src/MusicChunk.cxx', 'src/MusicChunk.cxx',
...@@ -309,6 +308,13 @@ sources = [ ...@@ -309,6 +308,13 @@ sources = [
'src/PlaylistFile.cxx', 'src/PlaylistFile.cxx',
] ]
if is_windows
sources += [
'src/win32/Win32Main.cxx',
'src/win32/ComWorker.cxx',
]
endif
if not is_android if not is_android
sources += [ sources += [
'src/CommandLine.cxx', 'src/CommandLine.cxx',
......
...@@ -19,7 +19,7 @@ ...@@ -19,7 +19,7 @@
#include "mixer/MixerInternal.hxx" #include "mixer/MixerInternal.hxx"
#include "output/plugins/WasapiOutputPlugin.hxx" #include "output/plugins/WasapiOutputPlugin.hxx"
#include "win32/Com.hxx" #include "win32/ComWorker.hxx"
#include "win32/HResult.hxx" #include "win32/HResult.hxx"
#include <cmath> #include <cmath>
...@@ -28,92 +28,103 @@ ...@@ -28,92 +28,103 @@
class WasapiMixer final : public Mixer { class WasapiMixer final : public Mixer {
WasapiOutput &output; WasapiOutput &output;
std::optional<COM> com;
public: public:
WasapiMixer(WasapiOutput &_output, MixerListener &_listener) WasapiMixer(WasapiOutput &_output, MixerListener &_listener)
: Mixer(wasapi_mixer_plugin, _listener), output(_output) {} : Mixer(wasapi_mixer_plugin, _listener), output(_output) {}
void Open() override { com.emplace(); } void Open() override {}
void Close() noexcept override { com.reset(); } void Close() noexcept override {}
int GetVolume() override { int GetVolume() override {
HRESULT result; auto future = COMWorker::Async([&]() -> int {
float volume_level; HRESULT result;
float volume_level;
if (wasapi_is_exclusive(output)) {
ComPtr<IAudioEndpointVolume> endpoint_volume; if (wasapi_is_exclusive(output)) {
result = wasapi_output_get_device(output)->Activate( ComPtr<IAudioEndpointVolume> endpoint_volume;
__uuidof(IAudioEndpointVolume), CLSCTX_ALL, nullptr, result = wasapi_output_get_device(output)->Activate(
endpoint_volume.AddressCast()); __uuidof(IAudioEndpointVolume), CLSCTX_ALL,
if (FAILED(result)) { nullptr, endpoint_volume.AddressCast());
throw FormatHResultError( if (FAILED(result)) {
result, "Unable to get device endpoint volume"); throw FormatHResultError(result,
"Unable to get device "
"endpoint volume");
}
result = endpoint_volume->GetMasterVolumeLevelScalar(
&volume_level);
if (FAILED(result)) {
throw FormatHResultError(result,
"Unable to get master "
"volume level");
}
} else {
ComPtr<ISimpleAudioVolume> session_volume;
result = wasapi_output_get_client(output)->GetService(
__uuidof(ISimpleAudioVolume),
session_volume.AddressCast<void>());
if (FAILED(result)) {
throw FormatHResultError(result,
"Unable to get client "
"session volume");
}
result = session_volume->GetMasterVolume(&volume_level);
if (FAILED(result)) {
throw FormatHResultError(
result, "Unable to get master volume");
}
} }
result = endpoint_volume->GetMasterVolumeLevelScalar( return std::lround(volume_level * 100.0f);
&volume_level); });
if (FAILED(result)) { return future.get();
throw FormatHResultError(
result, "Unable to get master volume level");
}
} else {
ComPtr<ISimpleAudioVolume> session_volume;
result = wasapi_output_get_client(output)->GetService(
__uuidof(ISimpleAudioVolume),
session_volume.AddressCast<void>());
if (FAILED(result)) {
throw FormatHResultError(
result, "Unable to get client session volume");
}
result = session_volume->GetMasterVolume(&volume_level);
if (FAILED(result)) {
throw FormatHResultError(result,
"Unable to get master volume");
}
}
return std::lround(volume_level * 100.0f);
} }
void SetVolume(unsigned volume) override { void SetVolume(unsigned volume) override {
HRESULT result; COMWorker::Async([&]() {
const float volume_level = volume / 100.0f; HRESULT result;
const float volume_level = volume / 100.0f;
if (wasapi_is_exclusive(output)) {
ComPtr<IAudioEndpointVolume> endpoint_volume; if (wasapi_is_exclusive(output)) {
result = wasapi_output_get_device(output)->Activate( ComPtr<IAudioEndpointVolume> endpoint_volume;
__uuidof(IAudioEndpointVolume), CLSCTX_ALL, nullptr, result = wasapi_output_get_device(output)->Activate(
endpoint_volume.AddressCast()); __uuidof(IAudioEndpointVolume), CLSCTX_ALL,
if (FAILED(result)) { nullptr, endpoint_volume.AddressCast());
throw FormatHResultError( if (FAILED(result)) {
result, "Unable to get device endpoint volume"); throw FormatHResultError(
} result,
"Unable to get device endpoint volume");
result = endpoint_volume->SetMasterVolumeLevelScalar(volume_level, }
nullptr);
if (FAILED(result)) { result = endpoint_volume->SetMasterVolumeLevelScalar(
throw FormatHResultError( volume_level, nullptr);
result, "Unable to set master volume level"); if (FAILED(result)) {
} throw FormatHResultError(
} else { result,
ComPtr<ISimpleAudioVolume> session_volume; "Unable to set master volume level");
result = wasapi_output_get_client(output)->GetService( }
__uuidof(ISimpleAudioVolume), } else {
session_volume.AddressCast<void>()); ComPtr<ISimpleAudioVolume> session_volume;
if (FAILED(result)) { result = wasapi_output_get_client(output)->GetService(
throw FormatHResultError( __uuidof(ISimpleAudioVolume),
result, "Unable to get client session volume"); session_volume.AddressCast<void>());
} if (FAILED(result)) {
throw FormatHResultError(
result = session_volume->SetMasterVolume(volume_level, nullptr); result,
if (FAILED(result)) { "Unable to get client session volume");
throw FormatHResultError(result, }
"Unable to set master volume");
result = session_volume->SetMasterVolume(volume_level,
nullptr);
if (FAILED(result)) {
throw FormatHResultError(
result, "Unable to set master volume");
}
} }
} }).get();
} }
}; };
......
...@@ -193,7 +193,7 @@ ParseTimeStamp(const char *s) ...@@ -193,7 +193,7 @@ ParseTimeStamp(const char *s)
{ {
try { try {
// TODO: make this more robust // TODO: make this more robust
return ParseTimePoint(s, "%a, %d %b %Y %T %Z"); return ParseTimePoint(s, "%a, %d %b %Y %T");
} catch (...) { } catch (...) {
return std::chrono::system_clock::time_point::min(); return std::chrono::system_clock::time_point::min();
} }
......
/*
* 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 THREAD_FUTURE_HXX
#define THREAD_FUTURE_HXX
#ifdef _WIN32
#include "WindowsFuture.hxx"
template <typename R>
using Future = WinFuture<R>;
template <typename R>
using Promise = WinPromise<R>;
#else
#include <future>
template <typename R>
using Future = std::future<R>;
template <typename R>
using Promise = std::promise<R>;
#endif
#endif
...@@ -202,7 +202,7 @@ ParseISO8601(const char *s) ...@@ -202,7 +202,7 @@ ParseISO8601(const char *s)
} }
/* parse the date */ /* parse the date */
const char *end = strptime(s, "%F", &tm); const char *end = strptime(s, "%Y-%m-%d", &tm);
if (end == nullptr) { if (end == nullptr) {
/* try without field separators */ /* try without field separators */
end = strptime(s, "%Y%m%d", &tm); end = strptime(s, "%Y%m%d", &tm);
......
...@@ -29,9 +29,19 @@ ...@@ -29,9 +29,19 @@
class COM { class COM {
public: public:
COM() { COM() {
HRESULT result = CoInitializeEx(nullptr, COINIT_MULTITHREADED); if (HRESULT result = CoInitializeEx(nullptr, COINIT_MULTITHREADED);
if (FAILED(result)) { FAILED(result)) {
throw FormatHResultError(result, "Unable to initialize COM"); throw FormatHResultError(
result,
"Unable to initialize COM with COINIT_MULTITHREADED");
}
}
COM(bool) {
if (HRESULT result = CoInitializeEx(nullptr, COINIT_APARTMENTTHREADED);
FAILED(result)) {
throw FormatHResultError(
result,
"Unable to initialize COM with COINIT_APARTMENTTHREADED");
} }
} }
~COM() noexcept { CoUninitialize(); } ~COM() noexcept { CoUninitialize(); }
......
/*
* Copyright 2020 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 "ComWorker.hxx"
#include "Log.hxx"
#include "thread/Name.hxx"
#include "util/Domain.hxx"
#include "win32/Com.hxx"
namespace {
static constexpr Domain com_worker_domain("com_worker");
}
Mutex COMWorker::mutex;
unsigned int COMWorker::reference_count = 0;
std::optional<COMWorker::COMWorkerThread> COMWorker::thread;
void COMWorker::COMWorkerThread::Work() noexcept {
FormatDebug(com_worker_domain, "Working thread started");
SetThreadName("COM Worker");
COM com{true};
while (true) {
if (!running_flag.test_and_set()) {
FormatDebug(com_worker_domain, "Working thread ended");
return;
}
while (!spsc_buffer.empty()) {
std::function<void()> function;
spsc_buffer.pop(function);
function();
}
event.Wait(200);
}
}
/*
* Copyright 2020 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_COM_WORKER_HXX
#define MPD_WIN32_COM_WORKER_HXX
#include <boost/lockfree/spsc_queue.hpp>
#include <condition_variable>
#include <mutex>
#include <optional>
#include "thread/Future.hxx"
#include "thread/Mutex.hxx"
#include "thread/Thread.hxx"
#include "win32/WinEvent.hxx"
#include <objbase.h>
#include <windows.h>
// Worker thread for all COM operation
class COMWorker {
private:
class COMWorkerThread : public Thread {
public:
COMWorkerThread() : Thread{BIND_THIS_METHOD(Work)} {}
private:
friend class COMWorker;
void Work() noexcept;
void Finish() noexcept {
running_flag.clear();
event.Set();
}
void Push(const std::function<void()> &function) {
spsc_buffer.push(function);
event.Set();
}
boost::lockfree::spsc_queue<std::function<void()>> spsc_buffer{32};
std::atomic_flag running_flag = true;
WinEvent event{};
};
public:
static void Aquire() {
std::unique_lock locker(mutex);
if (reference_count == 0) {
thread.emplace();
thread->Start();
}
++reference_count;
}
static void Release() noexcept {
std::unique_lock locker(mutex);
--reference_count;
if (reference_count == 0) {
thread->Finish();
thread->Join();
thread.reset();
}
}
template <typename Function, typename... Args>
static auto Async(Function &&function, Args &&...args) {
using R = std::invoke_result_t<std::decay_t<Function>,
std::decay_t<Args>...>;
auto promise = std::make_shared<Promise<R>>();
auto future = promise->get_future();
thread->Push([function = std::forward<Function>(function),
args = std::make_tuple(std::forward<Args>(args)...),
promise = std::move(promise)]() mutable {
try {
if constexpr (std::is_void_v<R>) {
std::apply(std::forward<Function>(function),
std::move(args));
promise->set_value();
} else {
promise->set_value(std::apply(
std::forward<Function>(function),
std::move(args)));
}
} catch (...) {
promise->set_exception(std::current_exception());
}
});
return future;
}
private:
static Mutex mutex;
static unsigned int reference_count;
static std::optional<COMWorkerThread> thread;
};
#endif
...@@ -18,9 +18,6 @@ ...@@ -18,9 +18,6 @@
*/ */
#include "Main.hxx" #include "Main.hxx"
#ifdef _WIN32
#include "util/Compiler.h" #include "util/Compiler.h"
#include "Instance.hxx" #include "Instance.hxx"
#include "system/FatalError.hxx" #include "system/FatalError.hxx"
...@@ -155,5 +152,3 @@ void win32_app_stopping() ...@@ -155,5 +152,3 @@ void win32_app_stopping()
else else
running.store(false); running.store(false);
} }
#endif
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