Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
126 changes: 71 additions & 55 deletions src/mixer/plugins/WasapiMixerPlugin.cxx
Original file line number Diff line number Diff line change
Expand Up @@ -19,6 +19,11 @@
#include <endpointvolume.h>
#include <mmdeviceapi.h>

#include "Log.hxx"
#include "util/Domain.hxx"

static constexpr Domain wasapi_mixer_domain("wasapi_mixer");

class WasapiMixer final : public Mixer {
WasapiOutput &output;

Expand All @@ -35,69 +40,80 @@ class WasapiMixer final : public Mixer {
if (!com_worker)
return -1;

auto future = com_worker->Async([&]() -> int {
HRESULT result;
float volume_level;

if (wasapi_is_exclusive(output)) {
auto endpoint_volume =
Activate<IAudioEndpointVolume>(*wasapi_output_get_device(output));

result = endpoint_volume->GetMasterVolumeLevelScalar(
&volume_level);
if (FAILED(result)) {
throw MakeHResultError(result,
"Unable to get master "
"volume level");
try {
auto future = com_worker->Async([&]() -> int {
HRESULT result;
float volume_level;

if (wasapi_is_exclusive(output)) {
auto endpoint_volume =
Activate<IAudioEndpointVolume>(*wasapi_output_get_device(output));

result = endpoint_volume->GetMasterVolumeLevelScalar(
&volume_level);
if (FAILED(result)) {
throw MakeHResultError(result,
"Unable to get master "
"volume level");
}
} else {
auto session_volume =
GetService<ISimpleAudioVolume>(*wasapi_output_get_client(output));

result = session_volume->GetMasterVolume(&volume_level);
if (FAILED(result)) {
throw MakeHResultError(
result, "Unable to get master volume");
}
}
} else {
auto session_volume =
GetService<ISimpleAudioVolume>(*wasapi_output_get_client(output));

result = session_volume->GetMasterVolume(&volume_level);
if (FAILED(result)) {
throw MakeHResultError(
result, "Unable to get master volume");
}
}

return std::lround(volume_level * 100.0f);
});
return future.get();
return std::lround(volume_level * 100.0f);
});
return future.get();
} catch (...) {
LogDebug(wasapi_mixer_domain,
"Failed to get volume, device may be disconnected");
return -1;
}
}

void SetVolume(unsigned volume) override {
auto com_worker = wasapi_output_get_com_worker(output);
if (!com_worker)
throw std::runtime_error("Cannot set WASAPI volume");

com_worker->Async([&]() {
HRESULT result;
const float volume_level = volume / 100.0f;

if (wasapi_is_exclusive(output)) {
auto endpoint_volume =
Activate<IAudioEndpointVolume>(*wasapi_output_get_device(output));

result = endpoint_volume->SetMasterVolumeLevelScalar(
volume_level, nullptr);
if (FAILED(result)) {
throw MakeHResultError(
result,
"Unable to set master volume level");
}
} else {
auto session_volume =
GetService<ISimpleAudioVolume>(*wasapi_output_get_client(output));

result = session_volume->SetMasterVolume(volume_level,
nullptr);
if (FAILED(result)) {
throw MakeHResultError(
result, "Unable to set master volume");
return;

try {
com_worker->Async([&]() {
HRESULT result;
const float volume_level = volume / 100.0f;

if (wasapi_is_exclusive(output)) {
auto endpoint_volume =
Activate<IAudioEndpointVolume>(*wasapi_output_get_device(output));

result = endpoint_volume->SetMasterVolumeLevelScalar(
volume_level, nullptr);
if (FAILED(result)) {
throw MakeHResultError(
result,
"Unable to set master volume level");
}
} else {
auto session_volume =
GetService<ISimpleAudioVolume>(*wasapi_output_get_client(output));

result = session_volume->SetMasterVolume(volume_level,
nullptr);
if (FAILED(result)) {
throw MakeHResultError(
result, "Unable to set master volume");
}
}
}
}).get();
}).get();
} catch (...) {
LogDebug(wasapi_mixer_domain,
"Failed to set volume, device may be disconnected");
}
}
};

Expand Down
5 changes: 1 addition & 4 deletions src/output/Control.hxx
Original file line number Diff line number Diff line change
Expand Up @@ -586,10 +586,7 @@ private:
/**
* An error has occurred, and this output must be closed.
*/
void InternalCloseError(std::exception_ptr e) noexcept {
Failure(e);
InternalClose(false);
}
void InternalCloseError(std::exception_ptr e) noexcept;

/**
* Runs inside the OutputThread.
Expand Down
10 changes: 10 additions & 0 deletions src/output/Interface.hxx
Original file line number Diff line number Diff line change
Expand Up @@ -198,6 +198,16 @@ public:
*/
virtual void Cancel() noexcept {}

/**
* Returns true if the most recent error was caused by an
* audio device change (e.g. default device switched). The
* output framework may use this to skip the fail timer and
* reopen the output immediately.
*/
virtual bool IsDeviceChange() const noexcept {
return false;
}

/**
* Pause the device. If supported, it may perform a special
* action, which keeps the device open, but does not play
Expand Down
15 changes: 15 additions & 0 deletions src/output/Thread.cxx
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,7 @@
#include "Control.hxx"
#include "Error.hxx"
#include "Filtered.hxx"
#include "Interface.hxx"
#include "Client.hxx"
#include "Domain.hxx"
#include "lib/fmt/AudioFormatFormatter.hxx"
Expand Down Expand Up @@ -183,6 +184,20 @@ AudioOutputControl::InternalClose(bool drain) noexcept
source.Close();
}

void
AudioOutputControl::InternalCloseError(std::exception_ptr e) noexcept
{
Failure(e);

/* If the plugin reports a device change (e.g. default
output switched on Windows), bypass the fail timer so
the output gets reopened immediately. */
if (output->output->IsDeviceChange())
fail_timer.Reset();

InternalClose(false);
}

inline void
AudioOutputControl::InternalCheckClose(bool drain) noexcept
{
Expand Down
85 changes: 85 additions & 0 deletions src/output/plugins/wasapi/DeviceNotification.hxx
Original file line number Diff line number Diff line change
@@ -0,0 +1,85 @@
// SPDX-License-Identifier: GPL-2.0-or-later
// Copyright The Music Player Daemon Project

#ifndef MPD_WASAPI_DEVICE_NOTIFICATION_HXX
#define MPD_WASAPI_DEVICE_NOTIFICATION_HXX

#include <atomic>
#include <mmdeviceapi.h>

#include <handleapi.h>
#include <synchapi.h>

/**
* IMMNotificationClient implementation that detects default audio
* device changes on Windows.
*/
class WasapiDeviceNotification final : public IMMNotificationClient {
std::atomic_bool &device_changed;
HANDLE device_event;
LONG ref_count = 1;

public:
WasapiDeviceNotification(std::atomic_bool &_device_changed,
HANDLE _device_event) noexcept
:device_changed(_device_changed),
device_event(_device_event) {}

/* IUnknown */
HRESULT STDMETHODCALLTYPE QueryInterface(REFIID riid,
void **ppv) noexcept override {
if (riid == IID_IUnknown ||
riid == __uuidof(IMMNotificationClient)) {
*ppv = static_cast<IMMNotificationClient *>(this);
AddRef();
return S_OK;
}
*ppv = nullptr;
return E_NOINTERFACE;
}

ULONG STDMETHODCALLTYPE AddRef() noexcept override {
return InterlockedIncrement(&ref_count);
}

ULONG STDMETHODCALLTYPE Release() noexcept override {
ULONG count = InterlockedDecrement(&ref_count);
if (count == 0)
delete this;
return count;
}

/* IMMNotificationClient */
HRESULT STDMETHODCALLTYPE
OnDeviceStateChanged(LPCWSTR, DWORD) noexcept override {
return S_OK;
}

HRESULT STDMETHODCALLTYPE
OnDeviceAdded(LPCWSTR) noexcept override {
return S_OK;
}

HRESULT STDMETHODCALLTYPE
OnDeviceRemoved(LPCWSTR) noexcept override {
return S_OK;
}

HRESULT STDMETHODCALLTYPE
OnDefaultDeviceChanged(EDataFlow flow, ERole role,
LPCWSTR) noexcept override {
if (flow == eRender && role == eMultimedia) {
device_changed.store(true, std::memory_order_release);
SetEvent(device_event);
}
return S_OK;
}

HRESULT STDMETHODCALLTYPE
OnPropertyValueChanged(LPCWSTR,
const PROPERTYKEY) noexcept override {
return S_OK;
}
};

#endif
Loading