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)
- snapcast: new plugin
ver 0.22.7 (not yet released)
* protocol
- don't use glibc extension to parse time stamps
* decoder
- 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)
* fix missing tags on songs in queue
......
......@@ -141,6 +141,15 @@ Basically, there are two ways to compile :program:`MPD` for Windows:
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
tarball and change into the directory. Then, instead of
:program:`meson`, type:
......@@ -168,6 +177,11 @@ You need:
* Android SDK
* `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
tarball and change into the directory. Then, instead of
......
......@@ -261,7 +261,6 @@ sources = [
'src/LogInit.cxx',
'src/ls.cxx',
'src/Instance.cxx',
'src/win32/Win32Main.cxx',
'src/MusicBuffer.cxx',
'src/MusicPipe.cxx',
'src/MusicChunk.cxx',
......@@ -309,6 +308,13 @@ sources = [
'src/PlaylistFile.cxx',
]
if is_windows
sources += [
'src/win32/Win32Main.cxx',
'src/win32/ComWorker.cxx',
]
endif
if not is_android
sources += [
'src/CommandLine.cxx',
......
......@@ -19,7 +19,7 @@
#include "mixer/MixerInternal.hxx"
#include "output/plugins/WasapiOutputPlugin.hxx"
#include "win32/Com.hxx"
#include "win32/ComWorker.hxx"
#include "win32/HResult.hxx"
#include <cmath>
......@@ -28,35 +28,37 @@
class WasapiMixer final : public Mixer {
WasapiOutput &output;
std::optional<COM> com;
public:
WasapiMixer(WasapiOutput &_output, MixerListener &_listener)
: 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 {
auto future = COMWorker::Async([&]() -> int {
HRESULT result;
float volume_level;
if (wasapi_is_exclusive(output)) {
ComPtr<IAudioEndpointVolume> endpoint_volume;
result = wasapi_output_get_device(output)->Activate(
__uuidof(IAudioEndpointVolume), CLSCTX_ALL, nullptr,
endpoint_volume.AddressCast());
__uuidof(IAudioEndpointVolume), CLSCTX_ALL,
nullptr, endpoint_volume.AddressCast());
if (FAILED(result)) {
throw FormatHResultError(
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");
throw FormatHResultError(result,
"Unable to get master "
"volume level");
}
} else {
ComPtr<ISimpleAudioVolume> session_volume;
......@@ -64,39 +66,45 @@ public:
__uuidof(ISimpleAudioVolume),
session_volume.AddressCast<void>());
if (FAILED(result)) {
throw FormatHResultError(
result, "Unable to get client session volume");
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");
throw FormatHResultError(
result, "Unable to get master volume");
}
}
return std::lround(volume_level * 100.0f);
});
return future.get();
}
void SetVolume(unsigned volume) override {
COMWorker::Async([&]() {
HRESULT result;
const float volume_level = volume / 100.0f;
if (wasapi_is_exclusive(output)) {
ComPtr<IAudioEndpointVolume> endpoint_volume;
result = wasapi_output_get_device(output)->Activate(
__uuidof(IAudioEndpointVolume), CLSCTX_ALL, nullptr,
endpoint_volume.AddressCast());
__uuidof(IAudioEndpointVolume), CLSCTX_ALL,
nullptr, endpoint_volume.AddressCast());
if (FAILED(result)) {
throw FormatHResultError(
result, "Unable to get device endpoint volume");
result,
"Unable to get device endpoint volume");
}
result = endpoint_volume->SetMasterVolumeLevelScalar(volume_level,
nullptr);
result = endpoint_volume->SetMasterVolumeLevelScalar(
volume_level, nullptr);
if (FAILED(result)) {
throw FormatHResultError(
result, "Unable to set master volume level");
result,
"Unable to set master volume level");
}
} else {
ComPtr<ISimpleAudioVolume> session_volume;
......@@ -105,15 +113,18 @@ public:
session_volume.AddressCast<void>());
if (FAILED(result)) {
throw FormatHResultError(
result, "Unable to get client session volume");
result,
"Unable to get client session volume");
}
result = session_volume->SetMasterVolume(volume_level, nullptr);
result = session_volume->SetMasterVolume(volume_level,
nullptr);
if (FAILED(result)) {
throw FormatHResultError(result,
"Unable to set master volume");
throw FormatHResultError(
result, "Unable to set master volume");
}
}
}).get();
}
};
......
......@@ -22,16 +22,21 @@
#include "WasapiOutputPlugin.hxx"
#include "lib/icu/Win32.hxx"
#include "mixer/MixerList.hxx"
#include "output/Error.hxx"
#include "pcm/Export.hxx"
#include "thread/Cond.hxx"
#include "thread/Mutex.hxx"
#include "thread/Name.hxx"
#include "thread/Thread.hxx"
#include "util/AllocatedString.hxx"
#include "util/ConstBuffer.hxx"
#include "util/Domain.hxx"
#include "util/RuntimeError.hxx"
#include "util/ScopeExit.hxx"
#include "util/StringBuffer.hxx"
#include "win32/Com.hxx"
#include "win32/ComHeapPtr.hxx"
#include "win32/ComWorker.hxx"
#include "win32/HResult.hxx"
#include "win32/WinEvent.hxx"
......@@ -91,24 +96,46 @@ inline bool SafeSilenceTry(Functor &&functor) {
}
}
inline void SetFormat(WAVEFORMATEXTENSIBLE &device_format,
const AudioFormat &audio_format) noexcept {
device_format.dwChannelMask = GetChannelMask(audio_format.channels);
std::vector<WAVEFORMATEXTENSIBLE> GetFormats(const AudioFormat &audio_format) noexcept {
std::vector<WAVEFORMATEXTENSIBLE> Result;
if (audio_format.format == SampleFormat::S24_P32) {
Result.resize(2);
Result[0].Format.wBitsPerSample = 24;
Result[0].Samples.wValidBitsPerSample = 24;
Result[1].Format.wBitsPerSample = 32;
Result[1].Samples.wValidBitsPerSample = 24;
} else {
Result.resize(1);
Result[0].Format.wBitsPerSample = audio_format.GetSampleSize() * 8;
Result[0].Samples.wValidBitsPerSample = audio_format.GetSampleSize() * 8;
}
const DWORD mask = GetChannelMask(audio_format.channels);
const GUID guid = audio_format.format == SampleFormat::FLOAT
? KSDATAFORMAT_SUBTYPE_IEEE_FLOAT
: KSDATAFORMAT_SUBTYPE_PCM;
for (auto &device_format : Result) {
device_format.dwChannelMask = mask;
device_format.Format.wFormatTag = WAVE_FORMAT_EXTENSIBLE;
device_format.Format.nChannels = audio_format.channels;
device_format.Format.nSamplesPerSec = audio_format.sample_rate;
device_format.Format.nBlockAlign = audio_format.GetFrameSize();
device_format.Format.cbSize =
sizeof(WAVEFORMATEXTENSIBLE) - sizeof(WAVEFORMATEX);
device_format.SubFormat = guid;
device_format.Format.nBlockAlign = device_format.Format.nChannels *
device_format.Format.wBitsPerSample /
8;
device_format.Format.nAvgBytesPerSec =
audio_format.sample_rate * audio_format.GetFrameSize();
device_format.Format.wBitsPerSample = audio_format.GetSampleSize() * 8;
device_format.Format.cbSize = sizeof(WAVEFORMATEXTENSIBLE) - sizeof(WAVEFORMATEX);
device_format.Samples.wValidBitsPerSample = audio_format.GetSampleSize() * 8;
if (audio_format.format == SampleFormat::FLOAT) {
device_format.SubFormat = KSDATAFORMAT_SUBTYPE_IEEE_FLOAT;
} else {
device_format.SubFormat = KSDATAFORMAT_SUBTYPE_PCM;
audio_format.sample_rate * device_format.Format.nBlockAlign;
}
return Result;
}
#ifdef ENABLE_DSD
void SetDSDFallback(AudioFormat &audio_format) noexcept {
audio_format.format = SampleFormat::FLOAT;
audio_format.sample_rate = 384000;
}
#endif
inline constexpr const unsigned int kErrorId = -1;
......@@ -117,55 +144,47 @@ inline constexpr const unsigned int kErrorId = -1;
class WasapiOutputThread : public Thread {
public:
enum class Status : uint32_t { FINISH, PLAY, PAUSE };
WasapiOutputThread(std::shared_ptr<WinEvent> _event, ComPtr<IAudioClient> _client,
WasapiOutputThread(IAudioClient *_client,
ComPtr<IAudioRenderClient> &&_render_client,
const UINT32 _frame_size, const UINT32 _buffer_size_in_frames,
bool _is_exclusive,
boost::lockfree::spsc_queue<BYTE> &_spsc_buffer)
: Thread(BIND_THIS_METHOD(Work)), event(std::move(_event)),
client(std::move(_client)), render_client(std::move(_render_client)),
frame_size(_frame_size), buffer_size_in_frames(_buffer_size_in_frames),
is_exclusive(_is_exclusive), spsc_buffer(_spsc_buffer) {}
bool _is_exclusive)
: Thread(BIND_THIS_METHOD(Work)), client(_client),
render_client(std::move(_render_client)), frame_size(_frame_size),
buffer_size_in_frames(_buffer_size_in_frames), is_exclusive(_is_exclusive),
spsc_buffer(_buffer_size_in_frames * 4 * _frame_size) {}
void Finish() noexcept { return SetStatus(Status::FINISH); }
void Play() noexcept { return SetStatus(Status::PLAY); }
void Pause() noexcept { return SetStatus(Status::PAUSE); }
void WaitWrite() noexcept {
std::unique_lock<Mutex> lock(write.mutex);
write.cond.wait(lock);
}
void WaitDataPoped() noexcept { data_poped.Wait(INFINITE); }
void CheckException() {
std::unique_lock<Mutex> lock(error.mutex);
if (error.error_ptr) {
std::exception_ptr err = std::exchange(error.error_ptr, nullptr);
error.cond.notify_all();
if (error.occur.load()) {
auto err = std::exchange(error.ptr, nullptr);
error.thrown.Set();
std::rethrow_exception(err);
}
}
private:
std::shared_ptr<WinEvent> event;
std::optional<COM> com;
ComPtr<IAudioClient> client;
friend class WasapiOutput;
WinEvent event;
WinEvent data_poped;
IAudioClient *client;
ComPtr<IAudioRenderClient> render_client;
const UINT32 frame_size;
const UINT32 buffer_size_in_frames;
bool is_exclusive;
boost::lockfree::spsc_queue<BYTE> &spsc_buffer;
alignas(BOOST_LOCKFREE_CACHELINE_BYTES) std::atomic<Status> status =
Status::PAUSE;
alignas(BOOST_LOCKFREE_CACHELINE_BYTES) struct {
Mutex mutex;
Cond cond;
} write{};
alignas(BOOST_LOCKFREE_CACHELINE_BYTES) struct {
Mutex mutex;
Cond cond;
std::exception_ptr error_ptr = nullptr;
} error{};
std::atomic_bool occur = false;
std::exception_ptr ptr = nullptr;
WinEvent thrown;
} error;
boost::lockfree::spsc_queue<BYTE> spsc_buffer;
void SetStatus(Status s) noexcept {
status.store(s);
event->Set();
event.Set();
}
void Work() noexcept;
};
......@@ -174,14 +193,23 @@ class WasapiOutput final : public AudioOutput {
public:
static AudioOutput *Create(EventLoop &, const ConfigBlock &block);
WasapiOutput(const ConfigBlock &block);
void Enable() override;
void Disable() noexcept override;
void Open(AudioFormat &audio_format) override;
void Enable() override {
COMWorker::Aquire();
COMWorker::Async([&]() { OpenDevice(); }).get();
}
void Disable() noexcept override {
COMWorker::Async([&]() { DoDisable(); }).get();
COMWorker::Release();
}
void Open(AudioFormat &audio_format) override {
COMWorker::Async([&]() { DoOpen(audio_format); }).get();
}
void Close() noexcept override;
std::chrono::steady_clock::duration Delay() const noexcept override;
size_t Play(const void *chunk, size_t size) override;
void Drain() override;
bool Pause() override;
void Interrupt() noexcept override;
constexpr bool Exclusive() const { return is_exclusive; }
constexpr size_t FrameSize() const { return device_format.Format.nBlockAlign; }
......@@ -190,25 +218,29 @@ public:
}
private:
std::atomic_flag not_interrupted = true;
bool is_started = false;
bool is_exclusive;
bool enumerate_devices;
std::string device_config;
std::vector<std::pair<unsigned int, AllocatedString>> device_desc;
std::shared_ptr<WinEvent> event;
std::optional<COM> com;
ComPtr<IMMDeviceEnumerator> enumerator;
ComPtr<IMMDevice> device;
ComPtr<IAudioClient> client;
WAVEFORMATEXTENSIBLE device_format;
std::unique_ptr<WasapiOutputThread> thread;
std::unique_ptr<boost::lockfree::spsc_queue<BYTE>> spsc_buffer;
std::optional<WasapiOutputThread> thread;
std::size_t watermark;
std::optional<PcmExport> pcm_export;
friend bool wasapi_is_exclusive(WasapiOutput &output) noexcept;
friend IMMDevice *wasapi_output_get_device(WasapiOutput &output) noexcept;
friend IAudioClient *wasapi_output_get_client(WasapiOutput &output) noexcept;
void DoDisable() noexcept;
void DoOpen(AudioFormat &audio_format);
void OpenDevice();
bool TryFormatExclusive(const AudioFormat &audio_format);
void FindExclusiveFormatSupported(AudioFormat &audio_format);
void FindSharedFormatSupported(AudioFormat &audio_format);
void EnumerateDevices();
......@@ -234,18 +266,10 @@ IAudioClient *wasapi_output_get_client(WasapiOutput &output) noexcept {
void WasapiOutputThread::Work() noexcept {
SetThreadName("Wasapi Output Worker");
FormatDebug(wasapi_output_domain, "Working thread started");
try {
com.emplace();
} catch (...) {
std::unique_lock<Mutex> lock(error.mutex);
error.error_ptr = std::current_exception();
error.cond.wait(lock);
assert(error.error_ptr == nullptr);
return;
}
COM com{true};
while (true) {
try {
event->Wait(INFINITE);
event.Wait(INFINITE);
Status current_state = status.load();
if (current_state == Status::FINISH) {
......@@ -254,51 +278,51 @@ void WasapiOutputThread::Work() noexcept {
return;
}
AtScopeExit(&) { write.cond.notify_all(); };
HRESULT result;
UINT32 write_in_frames = buffer_size_in_frames;
if (!is_exclusive) {
UINT32 data_in_frames;
result = client->GetCurrentPadding(&data_in_frames);
if (FAILED(result)) {
throw FormatHResultError(result,
"Failed to get current padding");
if (HRESULT result =
client->GetCurrentPadding(&data_in_frames);
FAILED(result)) {
throw FormatHResultError(
result, "Failed to get current padding");
}
UINT32 write_in_frames = buffer_size_in_frames;
if (!is_exclusive) {
if (data_in_frames >= buffer_size_in_frames) {
continue;
}
write_in_frames -= data_in_frames;
} else if (data_in_frames >= buffer_size_in_frames * 2) {
continue;
}
BYTE *data;
DWORD mode = 0;
result = render_client->GetBuffer(write_in_frames, &data);
if (FAILED(result)) {
if (HRESULT result =
render_client->GetBuffer(write_in_frames, &data);
FAILED(result)) {
throw FormatHResultError(result, "Failed to get buffer");
}
AtScopeExit(&) {
render_client->ReleaseBuffer(write_in_frames, 0);
render_client->ReleaseBuffer(write_in_frames, mode);
};
if (current_state == Status::PLAY) {
const UINT32 write_size = write_in_frames * frame_size;
UINT32 new_data_size = 0;
if (current_state == Status::PLAY) {
new_data_size = spsc_buffer.pop(data, write_size);
std::fill_n(data + new_data_size,
write_size - new_data_size, 0);
data_poped.Set();
} else {
mode = AUDCLNT_BUFFERFLAGS_SILENT;
FormatDebug(wasapi_output_domain,
"Working thread paused");
}
std::fill_n(data + new_data_size, write_size - new_data_size, 0);
} catch (...) {
std::unique_lock<Mutex> lock(error.mutex);
error.error_ptr = std::current_exception();
error.cond.wait(lock);
assert(error.error_ptr == nullptr);
error.ptr = std::current_exception();
error.occur.store(true);
error.thrown.Wait(INFINITE);
}
}
}
......@@ -313,43 +337,8 @@ WasapiOutput::WasapiOutput(const ConfigBlock &block)
enumerate_devices(block.GetBlockValue("enumerate", false)),
device_config(block.GetBlockValue("device", "")) {}
void WasapiOutput::Enable() {
com.emplace();
event = std::make_shared<WinEvent>();
enumerator.CoCreateInstance(__uuidof(MMDeviceEnumerator), nullptr,
CLSCTX_INPROC_SERVER);
device_desc.clear();
device.reset();
if (enumerate_devices && SafeTry([this]() { EnumerateDevices(); })) {
for (const auto &[device, desc] : device_desc) {
FormatNotice(wasapi_output_domain,
"Device \"%u\" \"%s\"",
device,
desc.c_str());
}
}
unsigned int id = kErrorId;
if (!device_config.empty()) {
if (!SafeSilenceTry([this, &id]() { id = std::stoul(device_config); })) {
id = SearchDevice(device_config);
}
}
if (id != kErrorId) {
SafeTry([this, id]() { GetDevice(id); });
}
if (!device) {
GetDefaultDevice();
}
device_desc.clear();
}
void WasapiOutput::Disable() noexcept {
/// run inside COMWorkerThread
void WasapiOutput::DoDisable() noexcept {
if (thread) {
try {
thread->Finish();
......@@ -359,41 +348,64 @@ void WasapiOutput::Disable() noexcept {
err.what());
}
thread.reset();
spsc_buffer.reset();
client.reset();
}
device.reset();
enumerator.reset();
com.reset();
event.reset();
}
void WasapiOutput::Open(AudioFormat &audio_format) {
if (audio_format.channels == 0) {
throw FormatInvalidArgument("channels should > 0");
}
/// run inside COMWorkerThread
void WasapiOutput::DoOpen(AudioFormat &audio_format) {
client.reset();
HRESULT result;
result = device->Activate(__uuidof(IAudioClient), CLSCTX_ALL, nullptr,
DWORD state;
if (HRESULT result = device->GetState(&state); FAILED(result)) {
throw FormatHResultError(result, "Unable to get device status");
}
if (state != DEVICE_STATE_ACTIVE) {
device.reset();
OpenDevice();
}
if (HRESULT result = device->Activate(__uuidof(IAudioClient), CLSCTX_ALL, nullptr,
client.AddressCast());
if (FAILED(result)) {
FAILED(result)) {
throw FormatHResultError(result, "Unable to activate audio client");
}
if (audio_format.format == SampleFormat::S24_P32) {
audio_format.format = SampleFormat::S32;
}
if (audio_format.channels > 8) {
audio_format.channels = 8;
}
#ifdef ENABLE_DSD
if (audio_format.format == SampleFormat::DSD) {
SetDSDFallback(audio_format);
}
#endif
if (Exclusive()) {
FindExclusiveFormatSupported(audio_format);
} else {
FindSharedFormatSupported(audio_format);
}
bool require_export = audio_format.format == SampleFormat::S24_P32;
if (require_export) {
PcmExport::Params params;
params.dsd_mode = PcmExport::DsdMode::NONE;
params.shift8 = false;
params.pack24 = false;
if (device_format.Format.wBitsPerSample == 32 &&
device_format.Samples.wValidBitsPerSample == 24) {
params.shift8 = true;
}
if (device_format.Format.wBitsPerSample == 24) {
params.pack24 = true;
}
FormatDebug(wasapi_output_domain, "Packing data: shift8=%d pack24=%d",
int(params.shift8), int(params.pack24));
pcm_export.emplace();
pcm_export->Open(audio_format.format, audio_format.channels, params);
}
using s = std::chrono::seconds;
using ms = std::chrono::milliseconds;
......@@ -401,112 +413,144 @@ void WasapiOutput::Open(AudioFormat &audio_format) {
using hundred_ns = std::chrono::duration<uint64_t, std::ratio<1, 10000000>>;
// The unit in REFERENCE_TIME is hundred nanoseconds
REFERENCE_TIME device_period;
result = client->GetDevicePeriod(&device_period, nullptr);
if (FAILED(result)) {
REFERENCE_TIME default_device_period, min_device_period;
if (HRESULT result =
client->GetDevicePeriod(&default_device_period, &min_device_period);
FAILED(result)) {
throw FormatHResultError(result, "Unable to get device period");
}
FormatDebug(wasapi_output_domain, "Device period: %I64u ns",
size_t(ns(hundred_ns(device_period)).count()));
FormatDebug(wasapi_output_domain,
"Default device period: %I64u ns, Minimum device period: "
"%I64u ns",
ns(hundred_ns(default_device_period)).count(),
ns(hundred_ns(min_device_period)).count());
REFERENCE_TIME buffer_duration = device_period;
if (!Exclusive()) {
REFERENCE_TIME buffer_duration;
if (Exclusive()) {
buffer_duration = default_device_period;
} else {
const REFERENCE_TIME align = hundred_ns(ms(50)).count();
buffer_duration = (align / device_period) * device_period;
buffer_duration = (align / default_device_period) * default_device_period;
}
FormatDebug(wasapi_output_domain, "Buffer duration: %I64u ns",
size_t(ns(hundred_ns(buffer_duration)).count()));
if (Exclusive()) {
result = client->Initialize(
if (HRESULT result = client->Initialize(
AUDCLNT_SHAREMODE_EXCLUSIVE,
AUDCLNT_STREAMFLAGS_NOPERSIST | AUDCLNT_STREAMFLAGS_EVENTCALLBACK,
buffer_duration, buffer_duration,
AUDCLNT_STREAMFLAGS_EVENTCALLBACK, buffer_duration,
buffer_duration,
reinterpret_cast<WAVEFORMATEX *>(&device_format), nullptr);
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");
result,
"Unable to get audio client buffer size");
}
buffer_duration = std::ceil(
double(buffer_size_in_frames * hundred_ns(s(1)).count()) /
buffer_duration =
std::ceil(double(buffer_size_in_frames *
hundred_ns(s(1)).count()) /
SampleRate());
FormatDebug(wasapi_output_domain,
FormatDebug(
wasapi_output_domain,
"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());
result = device->Activate(__uuidof(IAudioClient),
CLSCTX_ALL, nullptr,
client.AddressCast());
if (FAILED(result)) {
throw FormatHResultError(
result, "Unable to activate audio client");
result,
"Unable to activate audio client");
}
result = client->Initialize(
AUDCLNT_SHAREMODE_EXCLUSIVE,
AUDCLNT_STREAMFLAGS_NOPERSIST |
AUDCLNT_STREAMFLAGS_EVENTCALLBACK,
buffer_duration, buffer_duration,
reinterpret_cast<WAVEFORMATEX *>(&device_format),
nullptr);
}
if (FAILED(result)) {
throw FormatHResultError(
result, "Unable to initialize audio client");
}
}
} else {
result = client->Initialize(
AUDCLNT_SHAREMODE_SHARED,
AUDCLNT_STREAMFLAGS_NOPERSIST | AUDCLNT_STREAMFLAGS_EVENTCALLBACK,
if (HRESULT result = client->Initialize(
AUDCLNT_SHAREMODE_SHARED, AUDCLNT_STREAMFLAGS_EVENTCALLBACK,
buffer_duration, 0,
reinterpret_cast<WAVEFORMATEX *>(&device_format), nullptr);
FAILED(result)) {
throw FormatHResultError(result,
"Unable to initialize audio client");
}
if (FAILED(result)) {
throw FormatHResultError(result, "Unable to initialize audio client");
}
ComPtr<IAudioRenderClient> render_client;
result = client->GetService(IID_PPV_ARGS(render_client.Address()));
if (FAILED(result)) {
if (HRESULT result = client->GetService(IID_PPV_ARGS(render_client.Address()));
FAILED(result)) {
throw FormatHResultError(result, "Unable to get new render client");
}
result = client->SetEventHandle(event->handle());
if (FAILED(result)) {
throw FormatHResultError(result, "Unable to set event handler");
}
UINT32 buffer_size_in_frames;
result = client->GetBufferSize(&buffer_size_in_frames);
if (FAILED(result)) {
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();
spsc_buffer = std::make_unique<boost::lockfree::spsc_queue<BYTE>>(
buffer_size_in_frames * 4 * FrameSize());
thread = std::make_unique<WasapiOutputThread>(
event, client, std::move(render_client), FrameSize(),
buffer_size_in_frames, is_exclusive, *spsc_buffer);
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");
}
thread->Start();
}
void WasapiOutput::Close() noexcept {
assert(client && thread);
Pause();
assert(thread);
try {
COMWorker::Async([&]() {
if (HRESULT result = client->Stop(); FAILED(result)) {
throw FormatHResultError(result, "Failed to stop client");
}
}).get();
thread->CheckException();
} catch (std::exception &err) {
FormatError(wasapi_output_domain, "exception while stoping: %s",
err.what());
}
is_started = false;
thread->Finish();
thread->Join();
COMWorker::Async([&]() {
thread.reset();
spsc_buffer.reset();
client.reset();
}).get();
pcm_export.reset();
}
std::chrono::steady_clock::duration WasapiOutput::Delay() const noexcept {
if (!client || !is_started) {
return std::chrono::steady_clock::duration::zero();
if (!is_started) {
// idle while paused
return std::chrono::seconds(1);
}
const size_t data_size = spsc_buffer->read_available();
assert(thread);
const size_t data_size = thread->spsc_buffer.read_available();
const size_t delay_size = std::max(data_size, watermark) - watermark;
using s = std::chrono::seconds;
......@@ -516,130 +560,171 @@ std::chrono::steady_clock::duration WasapiOutput::Delay() const noexcept {
}
size_t WasapiOutput::Play(const void *chunk, size_t size) {
if (!client || !thread) {
return 0;
assert(thread);
not_interrupted.test_and_set();
ConstBuffer<void> input(chunk, size);
if (pcm_export) {
input = pcm_export->Export(input);
}
if (input.empty())
return size;
do {
const size_t consumed_size =
spsc_buffer->push(static_cast<const BYTE *>(chunk), size);
const size_t consumed_size = thread->spsc_buffer.push(
static_cast<const BYTE *>(input.data), input.size);
if (consumed_size == 0) {
assert(is_started);
thread->WaitWrite();
thread->WaitDataPoped();
if (!not_interrupted.test_and_set()) {
throw AudioOutputInterrupted{};
}
continue;
}
if (!is_started) {
is_started = true;
thread->Play();
HRESULT result;
result = client->Start();
if (FAILED(result)) {
throw FormatHResultError(result,
"Failed to start client");
COMWorker::Async([&]() {
if (HRESULT result = client->Start(); FAILED(result)) {
throw FormatHResultError(
result, "Failed to start client");
}
}).wait();
}
thread->CheckException();
if (pcm_export) {
return pcm_export->CalcInputSize(consumed_size);
}
return consumed_size;
} while (true);
}
void WasapiOutput::Drain() {
spsc_buffer->consume_all([](auto &&) {});
thread->CheckException();
}
bool WasapiOutput::Pause() {
if (!client || !thread) {
return false;
if (is_started) {
thread->Pause();
is_started = false;
}
if (!is_started) {
thread->CheckException();
return true;
}
}
HRESULT result;
result = client->Stop();
if (FAILED(result)) {
throw FormatHResultError(result, "Failed to stop client");
void WasapiOutput::Interrupt() noexcept {
if (thread) {
not_interrupted.clear();
thread->data_poped.Set();
}
}
is_started = false;
thread->Pause();
thread->CheckException();
void WasapiOutput::Drain() {
assert(thread);
return true;
thread->spsc_buffer.consume_all([](auto &&) {});
thread->CheckException();
}
void WasapiOutput::FindExclusiveFormatSupported(AudioFormat &audio_format) {
SetFormat(device_format, audio_format);
do {
HRESULT result;
result = client->IsFormatSupported(
AUDCLNT_SHAREMODE_EXCLUSIVE,
reinterpret_cast<WAVEFORMATEX *>(&device_format), nullptr);
/// run inside COMWorkerThread
void WasapiOutput::OpenDevice() {
enumerator.CoCreateInstance(__uuidof(MMDeviceEnumerator), nullptr,
CLSCTX_INPROC_SERVER);
switch (result) {
case S_OK:
return;
case AUDCLNT_E_UNSUPPORTED_FORMAT:
break;
default:
throw FormatHResultError(result, "IsFormatSupported failed");
if (enumerate_devices && SafeTry([this]() { EnumerateDevices(); })) {
for (const auto &[device, desc] : device_desc) {
FormatNotice(wasapi_output_domain,
"Device \"%u\" \"%s\"",
device,
desc.c_str());
}
// Trying PCM fallback.
if (audio_format.format == SampleFormat::FLOAT) {
audio_format.format = SampleFormat::S32;
continue;
}
// Trying sample rate fallback.
if (audio_format.sample_rate > 96000) {
audio_format.sample_rate = 96000;
continue;
unsigned int id = kErrorId;
if (!device_config.empty()) {
if (!SafeSilenceTry([this, &id]() { id = std::stoul(device_config); })) {
id = SearchDevice(device_config);
}
}
if (audio_format.sample_rate > 88200) {
audio_format.sample_rate = 88200;
continue;
if (id != kErrorId) {
SafeTry([this, id]() { GetDevice(id); });
}
if (audio_format.sample_rate > 64000) {
audio_format.sample_rate = 64000;
continue;
if (!device) {
GetDefaultDevice();
}
if (audio_format.sample_rate > 48000) {
audio_format.sample_rate = 48000;
continue;
device_desc.clear();
}
/// run inside COMWorkerThread
bool WasapiOutput::TryFormatExclusive(const AudioFormat &audio_format) {
for (auto test_format : GetFormats(audio_format)) {
HRESULT result = client->IsFormatSupported(
AUDCLNT_SHAREMODE_EXCLUSIVE,
reinterpret_cast<WAVEFORMATEX *>(&test_format), nullptr);
const auto format_string = ToString(audio_format);
const auto result_string = std::string(HRESULTToString(result));
FormatDebug(wasapi_output_domain, "Trying %s %lu %u-%u (exclusive) -> %s",
format_string.c_str(), test_format.Format.nSamplesPerSec,
test_format.Format.wBitsPerSample,
test_format.Samples.wValidBitsPerSample,
result_string.c_str());
if (SUCCEEDED(result)) {
device_format = test_format;
return true;
}
}
return false;
}
// Trying 2 channels fallback.
if (audio_format.channels > 2) {
audio_format.channels = 2;
/// run inside COMWorkerThread
void WasapiOutput::FindExclusiveFormatSupported(AudioFormat &audio_format) {
for (uint8_t channels : {0, 2, 6, 8, 7, 1, 4, 5, 3}) {
if (audio_format.channels == channels) {
continue;
}
// Trying S16 fallback.
if (audio_format.format == SampleFormat::S32) {
audio_format.format = SampleFormat::S16;
if (channels == 0) {
channels = audio_format.channels;
}
auto old_channels = std::exchange(audio_format.channels, channels);
for (uint32_t rate : {0, 384000, 352800, 192000, 176400, 96000, 88200,
48000, 44100, 32000, 22050, 16000, 11025, 8000}) {
if (audio_format.sample_rate <= rate) {
continue;
}
if (audio_format.sample_rate > 41100) {
audio_format.sample_rate = 41100;
if (rate == 0) {
rate = audio_format.sample_rate;
}
auto old_rate = std::exchange(audio_format.sample_rate, rate);
for (SampleFormat format : {
SampleFormat::UNDEFINED,
SampleFormat::S32,
SampleFormat::S24_P32,
SampleFormat::S16,
SampleFormat::S8,
}) {
if (audio_format.format == format) {
continue;
}
throw FormatHResultError(result, "Format is not supported");
} while (true);
if (format == SampleFormat::UNDEFINED) {
format = audio_format.format;
}
auto old_format =
std::exchange(audio_format.format, format);
if (TryFormatExclusive(audio_format)) {
return;
}
audio_format.format = old_format;
}
audio_format.sample_rate = old_rate;
}
audio_format.channels = old_channels;
}
}
/// run inside COMWorkerThread
void WasapiOutput::FindSharedFormatSupported(AudioFormat &audio_format) {
HRESULT result;
ComHeapPtr<WAVEFORMATEX> mixer_format;
......@@ -649,15 +734,23 @@ void WasapiOutput::FindSharedFormatSupported(AudioFormat &audio_format) {
if (FAILED(result)) {
throw FormatHResultError(result, "GetMixFormat failed");
}
audio_format.sample_rate = device_format.Format.nSamplesPerSec;
SetFormat(device_format, audio_format);
audio_format.sample_rate = mixer_format->nSamplesPerSec;
device_format = GetFormats(audio_format).front();
ComHeapPtr<WAVEFORMATEXTENSIBLE> closest_format;
result = client->IsFormatSupported(
AUDCLNT_SHAREMODE_SHARED,
reinterpret_cast<WAVEFORMATEX *>(&device_format),
closest_format.AddressCast<WAVEFORMATEX>());
{
const auto format_string = ToString(audio_format);
const auto result_string = std::string(HRESULTToString(result));
FormatDebug(wasapi_output_domain, "Trying %s %lu %u-%u (shared) -> %s",
format_string.c_str(), device_format.Format.nSamplesPerSec,
device_format.Format.wBitsPerSample,
device_format.Samples.wValidBitsPerSample,
result_string.c_str());
}
if (FAILED(result) && result != AUDCLNT_E_UNSUPPORTED_FORMAT) {
throw FormatHResultError(result, "IsFormatSupported failed");
......@@ -671,12 +764,23 @@ void WasapiOutput::FindSharedFormatSupported(AudioFormat &audio_format) {
// Trying channels fallback.
audio_format.channels = mixer_format->nChannels;
SetFormat(device_format, audio_format);
device_format = GetFormats(audio_format).front();
result = client->IsFormatSupported(
AUDCLNT_SHAREMODE_SHARED,
reinterpret_cast<WAVEFORMATEX *>(&device_format),
closest_format.AddressCast<WAVEFORMATEX>());
{
const auto format_string = ToString(audio_format);
const auto result_string = std::string(HRESULTToString(result));
FormatDebug(wasapi_output_domain,
"Trying %s %lu %u-%u (shared) -> %s",
format_string.c_str(),
device_format.Format.nSamplesPerSec,
device_format.Format.wBitsPerSample,
device_format.Samples.wValidBitsPerSample,
result_string.c_str());
}
if (FAILED(result)) {
throw FormatHResultError(result, "Format is not supported");
}
......@@ -715,7 +819,10 @@ void WasapiOutput::FindSharedFormatSupported(AudioFormat &audio_format) {
audio_format.format = SampleFormat::S16;
break;
case 32:
audio_format.format = SampleFormat::S32;
audio_format.format =
device_format.Samples.wValidBitsPerSample == 32
? SampleFormat::S32
: SampleFormat::S24_P32;
break;
}
} else if (device_format.SubFormat == KSDATAFORMAT_SUBTYPE_IEEE_FLOAT) {
......@@ -723,6 +830,7 @@ void WasapiOutput::FindSharedFormatSupported(AudioFormat &audio_format) {
}
}
/// run inside COMWorkerThread
void WasapiOutput::EnumerateDevices() {
if (!device_desc.empty()) {
return;
......@@ -775,6 +883,7 @@ void WasapiOutput::EnumerateDevices() {
}
}
/// run inside COMWorkerThread
void WasapiOutput::GetDevice(unsigned int index) {
HRESULT result;
......@@ -791,6 +900,7 @@ void WasapiOutput::GetDevice(unsigned int index) {
}
}
/// run inside COMWorkerThread
unsigned int WasapiOutput::SearchDevice(std::string_view name) {
if (!SafeTry([this]() { EnumerateDevices(); })) {
return kErrorId;
......@@ -808,6 +918,7 @@ unsigned int WasapiOutput::SearchDevice(std::string_view name) {
return iter->first;
}
/// run inside COMWorkerThread
void WasapiOutput::GetDefaultDevice() {
HRESULT result;
result = enumerator->GetDefaultAudioEndpoint(eRender, eMultimedia,
......
......@@ -193,7 +193,7 @@ ParseTimeStamp(const char *s)
{
try {
// TODO: make this more robust
return ParseTimePoint(s, "%a, %d %b %Y %T %Z");
return ParseTimePoint(s, "%a, %d %b %Y %T");
} catch (...) {
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
/*
* 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_WINDOWS_FUTURE_HXX
#define THREAD_WINDOWS_FUTURE_HXX
#include "CriticalSection.hxx"
#include "WindowsCond.hxx"
#include <atomic>
#include <memory>
#include <variant>
enum class WinFutureErrc : int {
future_already_retrieved = 1,
promise_already_satisfied,
no_state,
broken_promise,
};
enum class WinFutureStatus { ready, timeout, deferred };
static inline const std::error_category &win_future_category() noexcept;
class WinFutureCategory : public std::error_category {
public:
const char *name() const noexcept override { return "win_future"; }
std::string message(int Errcode) const override {
using namespace std::literals;
switch (static_cast<WinFutureErrc>(Errcode)) {
case WinFutureErrc::broken_promise:
return "Broken promise"s;
case WinFutureErrc::future_already_retrieved:
return "Future already retrieved"s;
case WinFutureErrc::promise_already_satisfied:
return "Promise already satisfied"s;
case WinFutureErrc::no_state:
return "No associated state"s;
default:
return "Unknown error"s;
}
}
std::error_condition default_error_condition(int code) const noexcept override {
return std::error_condition(code, win_future_category());
}
};
static inline const std::error_category &win_future_category() noexcept {
static const WinFutureCategory win_future_category_instance{};
return win_future_category_instance;
}
class WinFutureError : public std::logic_error {
public:
WinFutureError(WinFutureErrc errcode)
: WinFutureError(
std::error_code(static_cast<int>(errcode), win_future_category())) {}
private:
explicit WinFutureError(std::error_code errcode)
: std::logic_error("WinFutureError: " + errcode.message()), code(errcode) {}
std::error_code code;
};
template <typename T>
class WinFutureState {
private:
mutable CriticalSection mutex;
WindowsCond condition;
std::variant<std::monostate, T, std::exception_ptr> result;
bool retrieved = false;
bool ready = false;
public:
bool is_ready() const noexcept {
std::unique_lock<CriticalSection> lock(mutex);
return ready;
}
bool already_retrieved() const noexcept {
std::unique_lock<CriticalSection> lock(mutex);
return retrieved;
}
void wait() {
std::unique_lock<CriticalSection> lock(mutex);
condition.wait(lock, [this]() { return ready; });
}
template <class Rep, class Period>
WinFutureStatus
wait_for(const std::chrono::duration<Rep, Period> &timeout_duration) const {
std::unique_lock<CriticalSection> lock(mutex);
// deferred function not support yet
if (condition.wait_for(lock, timeout_duration,
[this]() { return ready; })) {
return WinFutureStatus::ready;
}
return WinFutureStatus::timeout;
}
virtual T &get_value() {
std::unique_lock<CriticalSection> lock(mutex);
if (retrieved) {
throw WinFutureError(WinFutureErrc::future_already_retrieved);
}
if (auto eptr = std::get_if<std::exception_ptr>(&result)) {
std::rethrow_exception(*eptr);
}
retrieved = true;
condition.wait(lock, [this]() { return ready; });
if (auto eptr = std::get_if<std::exception_ptr>(&result)) {
std::rethrow_exception(*eptr);
}
return *std::get_if<T>(&result);
}
void set_value(const T &value) {
std::unique_lock<CriticalSection> lock(mutex);
if (!std::holds_alternative<std::monostate>(&result)) {
throw WinFutureError(WinFutureErrc::promise_already_satisfied);
}
result.template emplace<T>(value);
ready = true;
condition.notify_all();
}
void set_value(T &&value) {
std::unique_lock<CriticalSection> lock(mutex);
if (!std::holds_alternative<std::monostate>(result)) {
throw WinFutureError(WinFutureErrc::promise_already_satisfied);
}
result.template emplace<T>(std::move(value));
ready = true;
condition.notify_all();
}
void set_exception(std::exception_ptr eptr) {
std::unique_lock<CriticalSection> lock(mutex);
if (!std::holds_alternative<std::monostate>(result)) {
throw WinFutureError(WinFutureErrc::promise_already_satisfied);
}
result.template emplace<std::exception_ptr>(eptr);
ready = true;
condition.notify_all();
}
};
template <typename T>
class WinFutureStateManager {
public:
WinFutureStateManager() = default;
WinFutureStateManager(std::shared_ptr<WinFutureState<T>> new_state)
: state(std::move(new_state)) {}
WinFutureStateManager(const WinFutureStateManager &) = default;
WinFutureStateManager &operator=(const WinFutureStateManager &) = default;
WinFutureStateManager(WinFutureStateManager &&) = default;
WinFutureStateManager &operator=(WinFutureStateManager &&) = default;
[[nodiscard]] bool valid() const noexcept { return static_cast<bool>(state); }
void wait() const {
if (!valid()) {
throw WinFutureError(WinFutureErrc::no_state);
}
state->wait();
}
template <class Rep, class Period>
WinFutureStatus
wait_for(const std::chrono::duration<Rep, Period> &timeout_duration) const {
if (!valid()) {
throw WinFutureError(WinFutureErrc::no_state);
}
return state->wait_for(timeout_duration);
}
T &get_value() const {
if (!valid()) {
throw WinFutureError(WinFutureErrc::no_state);
}
return state->get_value();
}
void set_value(const T &value) {
if (!valid()) {
throw WinFutureError(WinFutureErrc::no_state);
}
state->set_value(value);
}
void set_value(T &&value) {
if (!valid()) {
throw WinFutureError(WinFutureErrc::no_state);
}
state->set_value(std::move(value));
}
void set_exception(std::exception_ptr eptr) {
if (!valid()) {
throw WinFutureError(WinFutureErrc::no_state);
}
state->set_exception(eptr);
}
private:
std::shared_ptr<WinFutureState<T>> state;
};
template <typename T>
class WinFuture : public WinFutureStateManager<T> {
using Base = WinFutureStateManager<T>;
static_assert(!std::is_array_v<T> && std::is_object_v<T> &&
std::is_destructible_v<T>,
"T in future<T> must meet the Cpp17Destructible requirements "
"(N4878 [futures.unique.future]/4).");
public:
WinFuture() noexcept = default;
WinFuture(WinFuture &&) noexcept = default;
WinFuture &operator=(WinFuture &&) noexcept = default;
WinFuture(const WinFuture &) noexcept = delete;
WinFuture &operator=(const WinFuture &) noexcept = delete;
WinFuture(const Base &base, std::monostate) : Base(base) {}
~WinFuture() noexcept = default;
T get() {
WinFuture local(std::move(*this));
return std::move(local.get_value());
}
private:
using Base::get_value;
using Base::set_exception;
using Base::set_value;
};
template <typename T>
class WinFuture<T &> : public WinFutureStateManager<T *> {
using Base = WinFutureStateManager<T *>;
public:
WinFuture() noexcept = default;
WinFuture(WinFuture &&) noexcept = default;
WinFuture &operator=(WinFuture &&) noexcept = default;
WinFuture(const WinFuture &) noexcept = delete;
WinFuture &operator=(const WinFuture &) noexcept = delete;
WinFuture(const Base &base, std::monostate) : Base(base) {}
~WinFuture() noexcept = default;
T &get() {
WinFuture local(std::move(*this));
return *local.get_value();
}
private:
using Base::get_value;
using Base::set_exception;
using Base::set_value;
};
template <>
class WinFuture<void> : public WinFutureStateManager<int> {
using Base = WinFutureStateManager<int>;
public:
WinFuture() noexcept = default;
WinFuture(WinFuture &&) noexcept = default;
WinFuture &operator=(WinFuture &&) noexcept = default;
WinFuture(const WinFuture &) noexcept = delete;
WinFuture &operator=(const WinFuture &) noexcept = delete;
WinFuture(const Base &base, std::monostate) : Base(base) {}
~WinFuture() noexcept = default;
void get() {
WinFuture local(std::move(*this));
local.get_value();
}
private:
using Base::get_value;
using Base::set_exception;
using Base::set_value;
};
template <typename T>
class WinPromiseBase {
public:
WinPromiseBase(std::shared_ptr<WinFutureState<T>> new_state)
: state(std::move(new_state)) {}
WinPromiseBase(WinPromiseBase &&) = default;
WinPromiseBase &operator=(WinPromiseBase &&) = default;
WinPromiseBase(const WinPromiseBase &) = delete;
WinPromiseBase &operator=(const WinPromiseBase &) = delete;
WinFutureStateManager<T> &get_state_for_set() {
if (!state.valid()) {
throw WinFutureError(WinFutureErrc::no_state);
}
return state;
}
WinFutureStateManager<T> &get_state_for_future() {
if (!state.valid()) {
throw WinFutureError(WinFutureErrc::no_state);
}
if (future_retrieved) {
throw WinFutureError(WinFutureErrc::future_already_retrieved);
}
future_retrieved = true;
return state;
}
private:
WinFutureStateManager<T> state;
bool future_retrieved = false;
};
template <typename T>
class WinPromise {
public:
WinPromise() : base(std::make_shared<WinFutureState<T>>()) {}
WinPromise(WinPromise &&) = default;
WinPromise(const WinPromise &) = delete;
~WinPromise() noexcept {}
[[nodiscard]] WinFuture<T> get_future() {
return WinFuture<T>(base.get_state_for_future(), std::monostate());
}
void set_value(const T &value) { base.get_state_for_set().set_value(value); }
void set_value(T &&value) {
base.get_state_for_set().set_value(std::forward<T>(value));
}
void set_exception(std::exception_ptr eptr) {
base.get_state_for_set().set_exception(eptr);
}
private:
WinPromiseBase<T> base;
};
template <typename T>
class WinPromise<T &> {
public:
WinPromise() : base(std::make_shared<WinFutureState<T *>>()) {}
WinPromise(WinPromise &&) = default;
WinPromise(const WinPromise &) = delete;
~WinPromise() noexcept {}
[[nodiscard]] WinFuture<T &> get_future() {
return WinFuture<T>(base.get_state_for_future(), std::monostate());
}
void set_value(T &value) {
base.get_state_for_set().set_value(std::addressof(value));
}
void set_exception(std::exception_ptr eptr) {
base.get_state_for_set().set_exception(eptr);
}
private:
WinPromiseBase<T *> base;
};
template <>
class WinPromise<void> {
public:
WinPromise() : base(std::make_shared<WinFutureState<int>>()) {}
WinPromise(WinPromise &&) = default;
WinPromise(const WinPromise &) = delete;
~WinPromise() noexcept {}
[[nodiscard]] WinFuture<void> get_future() {
return WinFuture<void>(base.get_state_for_future(), std::monostate());
}
void set_value() { base.get_state_for_set().set_value(0); }
void set_exception(std::exception_ptr eptr) {
base.get_state_for_set().set_exception(eptr);
}
private:
WinPromiseBase<int> base;
};
#endif
......@@ -202,7 +202,7 @@ ParseISO8601(const char *s)
}
/* parse the date */
const char *end = strptime(s, "%F", &tm);
const char *end = strptime(s, "%Y-%m-%d", &tm);
if (end == nullptr) {
/* try without field separators */
end = strptime(s, "%Y%m%d", &tm);
......
......@@ -29,9 +29,19 @@
class COM {
public:
COM() {
HRESULT result = CoInitializeEx(nullptr, COINIT_MULTITHREADED);
if (FAILED(result)) {
throw FormatHResultError(result, "Unable to initialize COM");
if (HRESULT result = CoInitializeEx(nullptr, COINIT_MULTITHREADED);
FAILED(result)) {
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(); }
......
/*
* 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 @@
*/
#include "Main.hxx"
#ifdef _WIN32
#include "util/Compiler.h"
#include "Instance.hxx"
#include "system/FatalError.hxx"
......@@ -155,5 +152,3 @@ void win32_app_stopping()
else
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