/*
mswasapi.cpp

mediastreamer2 library - modular sound and video processing and streaming
Windows Audio Session API sound card plugin for mediastreamer2
Copyright (C) 2010-2013 Belledonne Communications, Grenoble, France

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 <bctoolbox/defs.h>
#include <mediastreamer2/mscommon.h>

#include "bctoolbox/charconv.h"
#include "mediastreamer2/msfilter.h"
#include "mediastreamer2/mssndcard.h"

#include "mswasapi.h"
#include "mswasapi_reader.h"
#include "mswasapi_writer.h"

#include <locale>
#include <string>

#define RELEASE_CLIENT(client)                                                                                         \
	if (client != NULL) {                                                                                              \
		client->Release();                                                                                             \
		client = NULL;                                                                                                 \
	}

#define FREE_PTR(ptr)                                                                                                  \
	if (ptr != NULL) {                                                                                                 \
		CoTaskMemFree((LPVOID)ptr);                                                                                    \
		ptr = NULL;                                                                                                    \
	}

const IID IID_IAudioCaptureClient = __uuidof(IAudioCaptureClient);
const IID IID_IAudioRenderClient = __uuidof(IAudioRenderClient);
const IID IID_ISimpleAudioVolume = __uuidof(ISimpleAudioVolume);
#if defined(MS2_WINDOWS_PHONE) || defined(MS2_WINDOWS_UNIVERSAL) || defined(MS2_WINDOWS_UWP)
const IID IID_IAudioClient2 = __uuidof(IAudioClient2);
#else
const IID IID_IAudioClient = __uuidof(IAudioClient);
const CLSID CLSID_MMDeviceEnumerator = __uuidof(MMDeviceEnumerator);
const IID IID_IMMDeviceEnumerator = __uuidof(IMMDeviceEnumerator);
#endif

// Workaround for issues on audio enhancements mode. Check void MSWASAPIReader::init(LPCWSTR id)
#if !defined(MS2_WINDOWS_PHONE) && !defined(MS2_WINDOWS_UNIVERSAL)
DEFINE_GUID(CLSID_PolicyConfig, 0x870af99c, 0x171d, 0x4f9e, 0xaf, 0x0d, 0xe6, 0x3d, 0xf4, 0x0c, 0x2b, 0xc9);
MIDL_INTERFACE("f8679f50-850a-41cf-9c72-430f290290c8")
IPolicyConfig : public IUnknown {
public:
	virtual HRESULT STDMETHODCALLTYPE GetMixFormat(PCWSTR pszDeviceName, WAVEFORMATEX * *ppFormat) = 0;
	virtual HRESULT STDMETHODCALLTYPE GetDeviceFormat(PCWSTR pszDeviceName, bool bDefault, WAVEFORMATEX **ppFormat) = 0;
	virtual HRESULT STDMETHODCALLTYPE ResetDeviceFormat(PCWSTR pszDeviceName) = 0;
	virtual HRESULT STDMETHODCALLTYPE SetDeviceFormat(PCWSTR pszDeviceName, WAVEFORMATEX * ppEndpointFormatFormat,
	                                                  WAVEFORMATEX * pMixFormat) = 0;
	virtual HRESULT STDMETHODCALLTYPE GetProcessingPeriod(PCWSTR pszDeviceName, bool bDefault, PINT64 pmftDefaultPeriod,
	                                                      PINT64 pmftMinimumPeriod) = 0;
	virtual HRESULT STDMETHODCALLTYPE SetProcessingPeriod(PCWSTR pszDeviceName, PINT64 pmftPeriod) = 0;
	virtual HRESULT STDMETHODCALLTYPE GetShareMode(PCWSTR pszDeviceName, struct DeviceShareMode * pMode) = 0;
	virtual HRESULT STDMETHODCALLTYPE SetShareMode(PCWSTR pszDeviceName, struct DeviceShareMode * pMode) = 0;
	virtual HRESULT STDMETHODCALLTYPE GetPropertyValue(PCWSTR pszDeviceName, BOOL bFxStore, const PROPERTYKEY &pKey,
	                                                   PROPVARIANT *pv) = 0;
	virtual HRESULT STDMETHODCALLTYPE SetPropertyValue(PCWSTR pszDeviceName, BOOL bFxStore, const PROPERTYKEY &pKey,
	                                                   PROPVARIANT *pv) = 0;
	virtual HRESULT STDMETHODCALLTYPE SetDefaultEndpoint(PCWSTR pszDeviceName, int eRole) = 0;
	virtual HRESULT STDMETHODCALLTYPE SetEndpointVisibility(PCWSTR pszDeviceName, bool bVisible) = 0;
};

void MSWasapi::changePolicies(IMMDevice *device) {
	HRESULT result;
	if (mDisableSysFx) { // Workaround: Remove enhancements mode as it can lead to break inputs on some systems
		LPWSTR endpointId = NULL;
		result = device->GetId(&endpointId);

		IPolicyConfig *policyConfig;
		result = CoCreateInstance(CLSID_PolicyConfig, NULL, CLSCTX_INPROC_SERVER, IID_PPV_ARGS(&policyConfig));
		REPORT_ERROR(("mswasapi: Disabling audio enhancements requested but could not get policy config for audio " +
		              mMediaDirectionStr + " [%x]")
		                 .c_str(),
		             result);
		PROPVARIANT var;
		PropVariantInit(&var);
		result = policyConfig->GetPropertyValue(endpointId, TRUE, PKEY_AudioEndpoint_Disable_SysFx, &var);
		var.uiVal = (USHORT)1;
		result = policyConfig->SetPropertyValue(endpointId, TRUE, PKEY_AudioEndpoint_Disable_SysFx, &var);
		policyConfig->Release();
	}
error:
	return;
}
#endif

MSWasapi::MSWasapi(MSFilter *filter, const std::string &mediaDirectionStr)
    : mAudioClient(NULL), mVolumeController(NULL), mIsActivated(false), mIsStarted(false), mBufferFrameCount(0),
      mFilter(filter) {
	mMediaDirectionStr = mediaDirectionStr;
	mTargetRate = 8000;
	mTargetNChannels = 1;
	mWBitsPerSample = 16; // The SDK limit to 16 bits
	mNBlockAlign = mWBitsPerSample * mTargetNChannels / 8;
	mDisableSysFx = false;
#ifndef MS2_WINDOWS_PHONE
	mActivationEvent = CreateEventEx(NULL, NULL, 0, EVENT_ALL_ACCESS);
	if (!mActivationEvent) {
		ms_error("mswasapi: Could not create activation event of the MSWASAPI audio %s interface [%i]",
		         mediaDirectionStr.c_str(), GetLastError());
		return;
	}
#endif
}

MSWasapi::~MSWasapi() {
	destroyAudioClient();
#ifdef MS2_WINDOWS_PHONE
	FREE_PTR(mDeviceId);
#else
	if (mActivationEvent != INVALID_HANDLE_VALUE) {
		CloseHandle(mActivationEvent);
		mActivationEvent = INVALID_HANDLE_VALUE;
	}
	if (mAudioSessionControl) {
		mAudioSessionControl->UnregisterAudioSessionNotification(this);
		RELEASE_CLIENT(mAudioSessionControl);
	}
#endif
}

void MSWasapi::start() {
	HRESULT result;

	if (!isStarted() && mIsActivated) {
		result = mAudioClient->Start();
		if (result != S_OK) {
			ms_error("mswasapi: Could not start MSWASAPI audio %s interface [%x]", mMediaDirectionStr.c_str(), result);
			return;
		}
		mIsStarted = true;
	}
}

void MSWasapi::stop() {
	HRESULT result;

	if (isStarted() && mIsActivated) {
		mIsStarted = false;
		result = mAudioClient->Stop();
		if (result != S_OK) {
			ms_error("mswasapi: Could not stop MSWASAPI audio %s interface [%x]", mMediaDirectionStr.c_str(), result);
		}
	}
}

float MSWasapi::getVolumeLevel() {
	HRESULT result;
	float volume;

	if (!mIsActivated || mVolumeController == nullptr) {
		ms_error("mswasapi: The %s instance is not started to get volume", mMediaDirectionStr.c_str());
		goto error;
	}
	result = mVolumeController->GetMasterVolume(&volume);
	REPORT_ERROR(("mswasapi: Could not get the " + mMediaDirectionStr + " master volume [%x]").c_str(), result);
	return volume;

error:
	return -1.0f;
}

void MSWasapi::setVolumeLevel(float volume) {
	HRESULT result;

	if (!mIsActivated || mVolumeController == nullptr) {
		ms_error("mswasapi: The %s instance is not started to set volume", mMediaDirectionStr.c_str());
		goto error;
	}
	result = mVolumeController->SetMasterVolume(volume, NULL);
	REPORT_ERROR(("mswasapi: Could not set the " + mMediaDirectionStr + " master volume [%x]").c_str(), result);

error:
	return;
}

int MSWasapi::getRate() const {
	return mTargetRate;
}

void MSWasapi::setRate(int rate) {
	if (mTargetRate != rate) {
		ms_filter_lock(mFilter);
		if (mAudioClient) {
			int currentRate = mTargetRate;
			mTargetRate = rate;
			if (isCurrentFormatUsable()) {
				mForceRate = OverwriteState::TO_DO;
				mScheduleUpdateFormat = true;
			} else {
				mTargetRate = currentRate;
				ms_warning("mswasapi: changing %s rate to %d Hz is not supported by the device. Keep %d Hz",
				           mMediaDirectionStr.c_str(), rate, mTargetRate);
			}
		} else
			ms_error("mswasapi: changing %s rate to %d Hz but AudioClient has not been initialized. It is not "
			         "implemented out of this scope. Keep %d Hz",
			         mMediaDirectionStr.c_str(), rate, mTargetRate);
		ms_filter_unlock(mFilter);
	}
}

int MSWasapi::getNChannels() const {
	return mTargetNChannels;
}

void MSWasapi::setNChannels(int channels) {
	if (mTargetNChannels != channels) {
		ms_filter_lock(mFilter);
		if (mAudioClient) {
			int currentChannels = mTargetNChannels;
			mTargetNChannels = channels;
			if (isCurrentFormatUsable()) {
				mForceNChannels = OverwriteState::TO_DO;
				mScheduleUpdateFormat = true;
			} else {
				mTargetNChannels = currentChannels;
				ms_warning(
				    "mswasapi: trying to change %s channel to %d is not supported by the device. Keep %d channels",
				    mMediaDirectionStr.c_str(), channels, mTargetNChannels);
			}
		} else
			ms_error("mswasapi: trying to change %s channel to %d but AudioClient has not been initialized. It is not "
			         "implemented out of this scope. Keep %d channels",
			         mMediaDirectionStr.c_str(), channels, mTargetNChannels);
		ms_filter_unlock(mFilter);
	}
}

void MSWasapi::init(MSSndCard *card, MSFilter *f) {
	WasapiSndCard *wasapicard = static_cast<WasapiSndCard *>(card->data);
	LPCWSTR id = wasapicard->id;
	bool useBestFormat = false;
	WAVEFORMATEX *pWfx = NULL;

	mDeviceName = card->name;
	mIsDefaultDevice = wasapicard->isDefault;
#if defined(MS2_WINDOWS_UNIVERSAL)
	mDeviceId = ref new Platform::String(id);

	if (mDeviceId == nullptr) {
		ms_error("mswasapi: Could not get the DeviceID of the MSWASAPI audio %s interface", mMediaDirectionStr.c_str());
		goto error;
	}
	if (isInstantiated()) {
		ms_error("mswasapi: An %s MSWASAPI is already instantiated. A second one can not be created.",
		         mMediaDirectionStr.c_str());
		goto error;
	}
#elif defined(MS2_WINDOWS_PHONE)
	mDeviceId = GetDefaultAudioRenderId(Communications);
	if (mDeviceId == NULL) {
		ms_error("mswasapi: Could not get the RenderId of the MSWASAPI audio %s interface", mMediaDirectionStr.c_str());
		goto error;
	}
	if (isInstantiated()) {
		ms_error("mswasapi: An %s MSWASAPI is already instantiated. A second one can not be created.",
		         mMediaDirectionStr.c_str());
		goto error;
	}
#else
	mDeviceId = id;
	if (mDeviceId == nullptr) {
		ms_error("mswasapi: Could not get the DeviceID of the MSWASAPI audio %s interface", mMediaDirectionStr.c_str());
		goto error;
	}
#endif

	if (createAudioClient()) goto error;
	useBestFormat = true;
error:
	updateFormat(useBestFormat);

#ifdef MS2_WINDOWS_UNIVERSAL
	setInstantiated(true);
#endif
	return;
}

//----------------------------------------------------------------------------------
//                                AUDIO CLIENT
//----------------------------------------------------------------------------------

int MSWasapi::createAudioClient() {
	if (mAudioClient != NULL) return -1;
#if defined(MS2_WINDOWS_UNIVERSAL)
	AudioClientProperties properties = {0};
	IActivateAudioInterfaceAsyncOperation *asyncOp;
	HRESULT result = ActivateAudioInterfaceAsync(mDeviceId->Data(), IID_IAudioClient2, NULL, this, &asyncOp);
	REPORT_ERROR(("mswasapi: Could not activate the MSWASAPI audio " + mMediaDirectionStr + " interface [%x]").c_str(),
	             result);
	WaitForSingleObjectEx(mActivationEvent, INFINITE, FALSE);
	if (mAudioClient == NULL) {
		ms_error("Could not create the MSWASAPI audio %s interface client", mMediaDirectionStr.c_str());
		goto error;
	}
#elif defined(MS2_WINDOWS_PHONE)
	AudioClientProperties properties = {0};
	HRESULT result = ActivateAudioInterface(mDeviceId, IID_IAudioClient2, (void **)&mAudioClient);
	REPORT_ERROR(("mswasapi: Could not activate the MSWASAPI audio " + mMediaDirectionStr + " interface [%x]").c_str(),
	             result);
#else
#ifdef ENABLE_MICROSOFT_STORE_APP
	CoInitializeEx(NULL, COINIT_MULTITHREADED);
#else
	CoInitialize(NULL);
#endif
	if (mIsDefaultDevice) {
		IActivateAudioInterfaceAsyncOperation *asyncOp;
		HRESULT result = ActivateAudioInterfaceAsync(mDeviceId, __uuidof(IAudioClient2), NULL, this, &asyncOp);
		REPORT_ERROR(
		    ("mswasapi: Could not activate the MSWASAPI audio " + mMediaDirectionStr + " interface [%x]").c_str(),
		    result);
		WaitForSingleObjectEx(mActivationEvent, INFINITE, FALSE);
		if (mAudioClient == NULL) {
			ms_error("Could not create the MSWASAPI audio %s interface client", mMediaDirectionStr.c_str());
			goto error;
		}
	} else { // On win32, ActivateAudioInterfaceAsync doesn't seem to work with specific ID other than default.
		IMMDeviceEnumerator *pEnumerator = NULL;
		IMMDevice *pDevice = NULL;
		HRESULT result = CoCreateInstance(CLSID_MMDeviceEnumerator, NULL, CLSCTX_ALL, IID_IMMDeviceEnumerator,
		                                  (void **)&pEnumerator);
		REPORT_ERROR("mswasapi: Could not create an instance of the device enumerator [%x]", result);
		result = pEnumerator->GetDevice(mDeviceId, &pDevice);
		SAFE_RELEASE(pEnumerator);
		REPORT_ERROR("mswasapi: Could not get the rendering device [%x]", result);
		changePolicies(pDevice);
		result = pDevice->Activate(__uuidof(IAudioClient2), CLSCTX_ALL, NULL, (void **)&mAudioClient);
		SAFE_RELEASE(pDevice);
		REPORT_ERROR("mswasapi: Could not activate the rendering device [%x]", result);
	}
#endif
#if defined(MS2_WINDOWS_PHONE) || defined(MS2_WINDOWS_UNIVERSAL)
	properties.cbSize = sizeof(AudioClientProperties);
	properties.bIsOffload = false;
	properties.eCategory = AudioCategory_Communications;
	result = mAudioClient->SetClientProperties(&properties);
	REPORT_ERROR(
	    ("mswasapi: Could not set properties of the MSWASAPI audio " + mMediaDirectionStr + " interface [%x]").c_str(),
	    result);
#endif
	return 0;
error:
	return -1;
}

void MSWasapi::destroyAudioClient() {
	RELEASE_CLIENT(mAudioClient);
}

int MSWasapi::restartAudioClient() {
	stop();
	deactivate();
	updateFormat(true);
	destroyAudioClient();
	if (createAudioClient()) return -1;
	if (activate()) return -1;
	start();
	return 0;
}

//----------------------------------------------------------------------------------
//                                      FORMAT
//----------------------------------------------------------------------------------

bool MSWasapi::isFormatUpdated() {
	bool changed = false;
	WAVEFORMATEX *pWfx = NULL;
	HRESULT result = mAudioClient->GetMixFormat(&pWfx); // Get best format for shared mode.
	REPORT_ERROR(
	    ("mswasapi: Could not get the mix format of the MSWASAPI audio " + mMediaDirectionStr + " interface [%x]")
	        .c_str(),
	    result);
	changed = mForceNChannels == OverwriteState::TO_DO || mForceRate == OverwriteState::TO_DO ||
	          (mForceRate == OverwriteState::DO_NOTHING && mTargetRate != pWfx->nSamplesPerSec) ||
	          (mForceNChannels == OverwriteState::DO_NOTHING && mTargetNChannels != pWfx->nChannels);
	FREE_PTR(pWfx);

error:
	return changed;
}

void MSWasapi::updateFormat(bool useBestFormat) {
	if (!mAudioClient) return;
	if (useBestFormat) {
		useBestFormat = false;
		WAVEFORMATEX *pWfx = NULL;
		HRESULT result = mAudioClient->GetMixFormat(&pWfx); // Get best format for shared mode.
		REPORT_ERROR(
		    ("mswasapi: Could not get the mix format of the MSWASAPI audio " + mMediaDirectionStr + " interface [%x]")
		        .c_str(),
		    result);
		if (mForceRate == OverwriteState::DO_NOTHING && mTargetRate != pWfx->nSamplesPerSec) {
			mTargetRate = pWfx->nSamplesPerSec;
		}
		if (mForceNChannels == OverwriteState::DO_NOTHING && mTargetNChannels != pWfx->nChannels) {
			mTargetNChannels = pWfx->nChannels;
		}
		FREE_PTR(pWfx);
		useBestFormat = true;
	}
error:
	if (mWBitsPerSample != 16) {
		mWBitsPerSample = 16; // The SDK limit to 16 bits
	}

	if (!useBestFormat) {
		// Initialize the frame rate and the number of channels to be able to generate silence.
		if (mTargetRate != 8000) { // Read
			mTargetRate = 8000;    // Write
		}                          // Avoid to use writting operation if not needed.
		if (mTargetNChannels != 1) {
			mTargetNChannels = 1;
		}
	}
	mNBlockAlign = mWBitsPerSample * mTargetNChannels / 8;
}

WAVEFORMATPCMEX MSWasapi::buildFormat() const {
	WAVEFORMATPCMEX wfx;
	wfx.Format.wFormatTag = WAVE_FORMAT_EXTENSIBLE;
	wfx.Format.nChannels = (WORD)mTargetNChannels;
	wfx.Format.nSamplesPerSec = mTargetRate;
	wfx.Format.wBitsPerSample = (WORD)mWBitsPerSample;
	wfx.Format.nAvgBytesPerSec = (DWORD)mTargetRate * mTargetNChannels * mWBitsPerSample / 8;
	wfx.Format.nBlockAlign = (WORD)mNBlockAlign;
	wfx.Format.cbSize = sizeof(WAVEFORMATEXTENSIBLE) - sizeof(WAVEFORMATEX);
	wfx.SubFormat = KSDATAFORMAT_SUBTYPE_PCM;
	wfx.Samples.wValidBitsPerSample = mWBitsPerSample;
	switch (mTargetNChannels) {
		case 1:
			wfx.dwChannelMask = SPEAKER_FRONT_CENTER;
			break;
		case 2:
			wfx.dwChannelMask = SPEAKER_FRONT_LEFT | SPEAKER_FRONT_RIGHT;
			break;
		case 4:
			wfx.dwChannelMask = SPEAKER_FRONT_LEFT | SPEAKER_FRONT_RIGHT | SPEAKER_BACK_LEFT | SPEAKER_BACK_RIGHT;
			break;
		case 6:
			wfx.dwChannelMask = SPEAKER_FRONT_LEFT | SPEAKER_FRONT_RIGHT | SPEAKER_FRONT_CENTER |
			                    SPEAKER_LOW_FREQUENCY | SPEAKER_BACK_LEFT | SPEAKER_BACK_RIGHT;
			break;
		case 8:
			wfx.dwChannelMask = SPEAKER_FRONT_LEFT | SPEAKER_FRONT_RIGHT | SPEAKER_FRONT_CENTER |
			                    SPEAKER_LOW_FREQUENCY | SPEAKER_BACK_LEFT | SPEAKER_BACK_RIGHT |
			                    SPEAKER_FRONT_LEFT_OF_CENTER | SPEAKER_FRONT_RIGHT_OF_CENTER;
			break;
		default:
			wfx.dwChannelMask = 0;
			break;
	}
	return wfx;
}

bool MSWasapi::isCurrentFormatUsable() const {
#if !defined(MS2_WINDOWS_UNIVERSAL)
#if defined(ENABLE_MICROSOFT_STORE_APP) || defined(MS2_WINDOWS_UNIVERSAL)
	CoInitializeEx(NULL, COINIT_MULTITHREADED);
#else
	CoInitialize(NULL);
#endif
#endif
	WAVEFORMATPCMEX proposedWfx = buildFormat();
	WAVEFORMATEX *pSupportedWfx = NULL;
	HRESULT result =
	    mAudioClient->IsFormatSupported(AUDCLNT_SHAREMODE_SHARED, (WAVEFORMATEX *)&proposedWfx, &pSupportedWfx);
	FREE_PTR(pSupportedWfx);
	return result == S_OK;
}

void MSWasapi::tryToUpdateFormat() {
	if (mScheduleUpdateFormat) {
		mScheduleUpdateFormat = false;
		if (isFormatUpdated()) {
			if (restartAudioClient()) {
				mScheduleUpdateFormat = true; // Couldn't restart. Reschedule the update.
				return;
			}
			if (mForceNChannels == OverwriteState::TO_DO) mForceNChannels = OverwriteState::DONE;
			if (mForceRate == OverwriteState::TO_DO) mForceRate = OverwriteState::DONE;
			ms_message("mswasapi: %s updated for [%s] at %i Hz, %i channels, with buffer size %i (%i ms), %i-bit "
			           "frames are on %i bits",
			           mMediaDirectionStr.c_str(), mDeviceName.c_str(), (int)mTargetRate, (int)mTargetNChannels,
			           (int)mBufferFrameCount, (int)1000 * mBufferFrameCount / mTargetRate, mWBitsPerSample,
			           mNBlockAlign * 8);
			ms_filter_notify_no_arg(mFilter, MS_FILTER_OUTPUT_FMT_CHANGED);
		}
	}
	return;
}

//----------------------------------------------------------------------------------

#ifndef MS2_WINDOWS_PHONE

//----------------------------------------------------------------------------------
//                  IActivateAudioInterfaceCompletionHandler
//----------------------------------------------------------------------------------

HRESULT MSWasapi::QueryInterface(REFIID riid, void **ppvObject) {
	if (riid == IID_IUnknown || riid == IID_IAgileObject) // Need IID_IAgileObject for ActivateAudioInterfaceAsync
		*ppvObject = this;
	else {
		*ppvObject = nullptr;
		return E_NOINTERFACE;
	}
	AddRef();
	return S_OK;
}

ULONG MSWasapi::AddRef(void) {
	return InterlockedIncrement(&m_refCount);
}

ULONG MSWasapi::Release(void) {
	ULONG count = InterlockedDecrement(&m_refCount);
	if (count == 0) delete this;
	return count;
}

// This will be called on MTA thread when results of the activation are available.
HRESULT MSWasapi::ActivateCompleted(IActivateAudioInterfaceAsyncOperation *operation) {
	HRESULT hr = S_OK;
	HRESULT hrActivateResult = S_OK;
	IUnknown *audioInterface = NULL;

	hr = operation->GetActivateResult(&hrActivateResult, &audioInterface);
	if (SUCCEEDED(hr) && SUCCEEDED(hrActivateResult)) {
		audioInterface->QueryInterface(IID_PPV_ARGS(&mAudioClient));
		if (mAudioClient == NULL) {
			hr = E_FAIL;
			goto exit;
		}
	} else
		ms_error("mswasapi: Could not activate the MSWASAPI audio %s interface [%x]", mMediaDirectionStr.c_str(),
		         hrActivateResult);
exit:
	SAFE_RELEASE(audioInterface);

	if (FAILED(hr)) {
		SAFE_RELEASE(mAudioClient);
	}

	SetEvent(mActivationEvent);
	return S_OK;
}

//----------------------------------------------------------------------------------
//                            IAudioSessionEvents
//----------------------------------------------------------------------------------

HRESULT MSWasapi::OnSessionDisconnected(AudioSessionDisconnectReason disconnectReason) {
	if (disconnectReason == DisconnectReasonFormatChanged) mScheduleUpdateFormat = true;
	return S_OK;
}
HRESULT MSWasapi::OnDisplayNameChanged(LPCWSTR NewDisplayName, LPCGUID EventContext) {
	return S_OK;
}

HRESULT MSWasapi::OnIconPathChanged(LPCWSTR NewIconPath, LPCGUID EventContext) {
	return S_OK;
}

HRESULT MSWasapi::OnSimpleVolumeChanged(float NewVolume, BOOL NewMute, LPCGUID EventContext) {
	return S_OK;
}

HRESULT MSWasapi::OnChannelVolumeChanged(DWORD ChannelCount,
                                         float NewChannelVolumeArray[],
                                         DWORD ChangedChannel,
                                         LPCGUID EventContext) {
	return S_OK;
}

HRESULT MSWasapi::OnGroupingParamChanged(LPCGUID NewGroupingParam, LPCGUID EventContext) {
	return S_OK;
}

HRESULT MSWasapi::OnStateChanged(AudioSessionState NewState) {
	mScheduleUpdateFormat = true;
	return S_OK;
}

//----------------------------------------------------------------------------------

#endif

/******************************************************************************
 * Methods to (de)initialize and run the WASAPI sound capture filter          *
 *****************************************************************************/

static void ms_wasapi_read_init(MSFilter *f) {
	MSWASAPIReaderPtr r = MSWASAPIReaderNew(f);
	f->data = r;
}

static void ms_wasapi_read_preprocess(MSFilter *f) {
	MSWASAPIReaderType r = MSWASAPI_READER(f->data);
	if (!r->activate()) r->start();
}

static void ms_wasapi_read_process(MSFilter *f) {
	MSWASAPIReaderType r = MSWASAPI_READER(f->data);
	ms_filter_lock(f);
	r->tryToUpdateFormat();
	r->feed(f);
	ms_filter_unlock(f);
}

static void ms_wasapi_read_postprocess(MSFilter *f) {
	MSWASAPIReaderType r = MSWASAPI_READER(f->data);
	r->stop();
	r->deactivate();
}

static void ms_wasapi_read_uninit(MSFilter *f) {
	MSWASAPIReaderDelete(static_cast<MSWASAPIReaderPtr>(f->data));
}

/******************************************************************************
 * Methods to configure the WASAPI sound capture filter                       *
 *****************************************************************************/

static int ms_wasapi_read_get_sample_rate(MSFilter *f, void *arg) {
	MSWASAPIReaderType r = MSWASAPI_READER(f->data);
	*((int *)arg) = r->getRate();
	return 0;
}

static int ms_wasapi_read_set_sample_rate(BCTBX_UNUSED(MSFilter *f), BCTBX_UNUSED(void *arg)) {
	MSWASAPIReaderType r = MSWASAPI_READER(f->data);
	int sampleRate = 0;
	r->setRate(*((int *)arg));
	sampleRate = r->getRate();
	if (sampleRate == *((int *)arg)) // We are setting what the Audio Client managed : do not considered it as an error
	                                 // to avoid misleading debug logs.
		return 0;
	else return -1;
}

static int ms_wasapi_read_get_nchannels(MSFilter *f, void *arg) {
	MSWASAPIReaderType r = MSWASAPI_READER(f->data);
	*((int *)arg) = r->getNChannels();
	return 0;
}

static int ms_wasapi_read_set_nchannels(BCTBX_UNUSED(MSFilter *f), BCTBX_UNUSED(void *arg)) {
	MSWASAPIReaderType r = MSWASAPI_READER(f->data);
	int channelCount = 2;
	r->setNChannels(*((int *)arg)); // Note that Mediastreamer use only 1 or 2 channels.
	channelCount = r->getNChannels();
	if (channelCount == *((int *)arg)) // We are setting what the Audio Client managed : do not considered it as an
	                                   // error to avoid misleading debug logs.
		return 0;
	else return -1;
}

static int ms_wasapi_read_set_volume_gain(MSFilter *f, void *arg) {
	MSWASAPIReaderType r = MSWASAPI_READER(f->data);
	r->setVolumeLevel(*(float *)arg);
	return 0;
}

static int ms_wasapi_read_get_volume_gain(MSFilter *f, void *arg) {
	MSWASAPIReaderType r = MSWASAPI_READER(f->data);
	float *volume = (float *)arg;
	*volume = r->getVolumeLevel();
	return *volume >= 0.0f ? 0 : -1;
}

static MSFilterMethod ms_wasapi_read_methods[] = {{MS_FILTER_SET_SAMPLE_RATE, ms_wasapi_read_set_sample_rate},
                                                  {MS_FILTER_GET_SAMPLE_RATE, ms_wasapi_read_get_sample_rate},
                                                  {MS_FILTER_SET_NCHANNELS, ms_wasapi_read_set_nchannels},
                                                  {MS_FILTER_GET_NCHANNELS, ms_wasapi_read_get_nchannels},
                                                  {MS_AUDIO_CAPTURE_SET_VOLUME_GAIN, ms_wasapi_read_set_volume_gain},
                                                  {MS_AUDIO_CAPTURE_GET_VOLUME_GAIN, ms_wasapi_read_get_volume_gain},
                                                  {0, NULL}};

/******************************************************************************
 * Definition of the WASAPI sound capture filter                              *
 *****************************************************************************/

#define MS_WASAPI_READ_ID MS_FILTER_PLUGIN_ID
#define MS_WASAPI_READ_NAME "MSWASAPIRead"
#define MS_WASAPI_READ_DESCRIPTION "Windows Audio Session sound capture"
#define MS_WASAPI_READ_CATEGORY MS_FILTER_OTHER
#define MS_WASAPI_READ_ENC_FMT NULL
#define MS_WASAPI_READ_NINPUTS 0
#define MS_WASAPI_READ_NOUTPUTS 1
#define MS_WASAPI_READ_FLAGS 0

#ifndef _MSC_VER

MSFilterDesc ms_wasapi_read_desc = {.id = MS_WASAPI_READ_ID,
                                    .name = MS_WASAPI_READ_NAME,
                                    .text = MS_WASAPI_READ_DESCRIPTION,
                                    .category = MS_WASAPI_READ_CATEGORY,
                                    .enc_fmt = MS_WASAPI_READ_ENC_FMT,
                                    .ninputs = MS_WASAPI_READ_NINPUTS,
                                    .noutputs = MS_WASAPI_READ_NOUTPUTS,
                                    .init = ms_wasapi_read_init,
                                    .preprocess = ms_wasapi_read_preprocess,
                                    .process = ms_wasapi_read_process,
                                    .postprocess = ms_wasapi_read_postprocess,
                                    .uninit = ms_wasapi_read_uninit,
                                    .methods = ms_wasapi_read_methods,
                                    .flags = MS_WASAPI_READ_FLAGS};

#else

MSFilterDesc ms_wasapi_read_desc = {MS_WASAPI_READ_ID,       MS_WASAPI_READ_NAME,        MS_WASAPI_READ_DESCRIPTION,
                                    MS_WASAPI_READ_CATEGORY, MS_WASAPI_READ_ENC_FMT,     MS_WASAPI_READ_NINPUTS,
                                    MS_WASAPI_READ_NOUTPUTS, ms_wasapi_read_init,        ms_wasapi_read_preprocess,
                                    ms_wasapi_read_process,  ms_wasapi_read_postprocess, ms_wasapi_read_uninit,
                                    ms_wasapi_read_methods,  MS_WASAPI_READ_FLAGS};

#endif

MS_FILTER_DESC_EXPORT(ms_wasapi_read_desc)

/******************************************************************************
 * Methods to (de)initialize and run the WASAPI sound output filter           *
 *****************************************************************************/

static void ms_wasapi_write_init(MSFilter *f) {
	MSWASAPIWriterPtr w = MSWASAPIWriterNew(f);
	f->data = w;
}

static void ms_wasapi_write_preprocess(MSFilter *f) {
	MSWASAPIWriterType w = MSWASAPI_WRITER(f->data);
	if (!w->activate()) w->start();
}

static void ms_wasapi_write_process(MSFilter *f) {
	MSWASAPIWriterType w = MSWASAPI_WRITER(f->data);
	ms_filter_lock(f);
	w->tryToUpdateFormat();
	w->feed(f);
	ms_filter_unlock(f);
}

static void ms_wasapi_write_postprocess(MSFilter *f) {
	MSWASAPIWriterType w = MSWASAPI_WRITER(f->data);
	w->stop();
	w->deactivate();
}

static void ms_wasapi_write_uninit(MSFilter *f) {
	MSWASAPIWriterDelete(static_cast<MSWASAPIWriterPtr>(f->data));
}

/******************************************************************************
 * Methods to configure the WASAPI sound output filter                        *
 *****************************************************************************/

static int ms_wasapi_write_get_sample_rate(MSFilter *f, void *arg) {
	MSWASAPIWriterType w = MSWASAPI_WRITER(f->data);
	*((int *)arg) = w->getRate();
	return 0;
}

static int ms_wasapi_write_set_sample_rate(BCTBX_UNUSED(MSFilter *f), BCTBX_UNUSED(void *arg)) {
	MSWASAPIWriterType w = MSWASAPI_WRITER(f->data);
	int sampleRate = 0;
	w->setRate(*((int *)arg));
	sampleRate = w->getRate();
	if (sampleRate == *((int *)arg)) // We are setting what the Audio Client managed : do not considered it as an error
	                                 // to avoid misleading debug logs.
		return 0;
	else return -1;
}

static int ms_wasapi_write_get_nchannels(MSFilter *f, void *arg) {
	MSWASAPIWriterType w = MSWASAPI_WRITER(f->data);
	*((int *)arg) = w->getNChannels();
	return 0;
}

static int ms_wasapi_write_set_nchannels(BCTBX_UNUSED(MSFilter *f), BCTBX_UNUSED(void *arg)) {
	MSWASAPIWriterType w = MSWASAPI_WRITER(f->data);
	int channelCount = 2;
	w->setNChannels(*((int *)arg)); // Note that Mediastreamer use only 1 or 2 channels.
	channelCount = w->getNChannels();
	if (channelCount == *((int *)arg)) // We are setting what the Audio Client managed : do not considered it as an
	                                   // error to avoid misleading debug logs.
		return 0;
	else return -1;
}

static int ms_wasapi_write_set_volume_gain(MSFilter *f, void *arg) {
	MSWASAPIWriterType w = MSWASAPI_WRITER(f->data);
	w->setVolumeLevel(*(float *)arg);
	return 0;
}

static int ms_wasapi_write_get_volume_gain(MSFilter *f, void *arg) {
	MSWASAPIWriterType w = MSWASAPI_WRITER(f->data);
	float *volume = (float *)arg;
	*volume = w->getVolumeLevel();
	return *volume >= 0.0f ? 0 : -1;
}

static MSFilterMethod ms_wasapi_write_methods[] = {{MS_FILTER_SET_SAMPLE_RATE, ms_wasapi_write_set_sample_rate},
                                                   {MS_FILTER_GET_SAMPLE_RATE, ms_wasapi_write_get_sample_rate},
                                                   {MS_FILTER_SET_NCHANNELS, ms_wasapi_write_set_nchannels},
                                                   {MS_FILTER_GET_NCHANNELS, ms_wasapi_write_get_nchannels},
                                                   {MS_AUDIO_PLAYBACK_SET_VOLUME_GAIN, ms_wasapi_write_set_volume_gain},
                                                   {MS_AUDIO_PLAYBACK_GET_VOLUME_GAIN, ms_wasapi_write_get_volume_gain},
                                                   {0, NULL}};

/******************************************************************************
 * Definition of the WASAPI sound output filter                               *
 *****************************************************************************/

#define MS_WASAPI_WRITE_ID MS_FILTER_PLUGIN_ID
#define MS_WASAPI_WRITE_NAME "MSWASAPIWrite"
#define MS_WASAPI_WRITE_DESCRIPTION "Windows Audio Session sound output"
#define MS_WASAPI_WRITE_CATEGORY MS_FILTER_OTHER
#define MS_WASAPI_WRITE_ENC_FMT NULL
#define MS_WASAPI_WRITE_NINPUTS 1
#define MS_WASAPI_WRITE_NOUTPUTS 0
#define MS_WASAPI_WRITE_FLAGS MS_FILTER_IS_PUMP

#ifndef _MSC_VER

MSFilterDesc ms_wasapi_write_desc = {.id = MS_WASAPI_WRITE_ID,
                                     .name = MS_WASAPI_WRITE_NAME,
                                     .text = MS_WASAPI_WRITE_DESCRIPTION,
                                     .category = MS_WASAPI_WRITE_CATEGORY,
                                     .enc_fmt = MS_WASAPI_WRITE_ENC_FMT,
                                     .ninputs = MS_WASAPI_WRITE_NINPUTS,
                                     .noutputs = MS_WASAPI_WRITE_NOUTPUTS,
                                     .init = ms_wasapi_write_init,
                                     .preprocess = ms_wasapi_write_preprocess,
                                     .process = ms_wasapi_write_process,
                                     .postprocess = ms_wasapi_write_postprocess,
                                     .uninit = ms_wasapi_write_uninit,
                                     .methods = ms_wasapi_write_methods,
                                     .flags = MS_WASAPI_WRITE_FLAGS};

#else

MSFilterDesc ms_wasapi_write_desc = {MS_WASAPI_WRITE_ID,       MS_WASAPI_WRITE_NAME,        MS_WASAPI_WRITE_DESCRIPTION,
                                     MS_WASAPI_WRITE_CATEGORY, MS_WASAPI_WRITE_ENC_FMT,     MS_WASAPI_WRITE_NINPUTS,
                                     MS_WASAPI_WRITE_NOUTPUTS, ms_wasapi_write_init,        ms_wasapi_write_preprocess,
                                     ms_wasapi_write_process,  ms_wasapi_write_postprocess, ms_wasapi_write_uninit,
                                     ms_wasapi_write_methods,  MS_WASAPI_WRITE_FLAGS};

#endif

MS_FILTER_DESC_EXPORT(ms_wasapi_write_desc)

static void ms_wasapi_snd_card_detect(MSSndCardManager *m);
static MSFilter *ms_wasapi_snd_card_create_reader(MSSndCard *card);
static MSFilter *ms_wasapi_snd_card_create_writer(MSSndCard *card);

static void ms_wasapi_snd_card_init(MSSndCard *card) {
	WasapiSndCard *c = static_cast<WasapiSndCard *>(ms_new0(WasapiSndCard, 1));
	card->data = c;
}

static void ms_wasapi_snd_card_uninit(MSSndCard *card) {
	WasapiSndCard *c = static_cast<WasapiSndCard *>(card->data);
	if (c->id_vector) delete c->id_vector;
	ms_free(c);
}

static MSSndCardDesc ms_wasapi_snd_card_desc = {"WASAPI",
                                                ms_wasapi_snd_card_detect,
                                                ms_wasapi_snd_card_init,
                                                NULL,
                                                NULL,
                                                NULL,
                                                NULL,
                                                NULL,
                                                ms_wasapi_snd_card_create_reader,
                                                ms_wasapi_snd_card_create_writer,
                                                ms_wasapi_snd_card_uninit,
                                                NULL,
                                                NULL};

#if defined(MS2_WINDOWS_PHONE)
static MSSndCard *ms_wasapi_phone_snd_card_new(void) {
	MSSndCard *card = ms_snd_card_new(&ms_wasapi_snd_card_desc);
	card->name = ms_strdup("WASAPI sound card");
	card->latency = 250;
	return card;
}

#elif defined(MS2_WINDOWS_UNIVERSAL)

class MSWASAPIDeviceEnumerator {
public:
	MSWASAPIDeviceEnumerator() : _l(NULL) {
		_DetectEvent = CreateEventEx(NULL, NULL, 0, EVENT_ALL_ACCESS);
		if (!_DetectEvent) {
			ms_error("mswasapi: Could not create detect event [%i]", GetLastError());
			return;
		}
	}
	~MSWASAPIDeviceEnumerator() {
		bctbx_list_free(_l);
		if (_DetectEvent != INVALID_HANDLE_VALUE) {
			CloseHandle(_DetectEvent);
			_DetectEvent = INVALID_HANDLE_VALUE;
		}
	}

	MSSndCard *NewCard(String ^ DeviceId, const char *name, uint8_t capabilities) {
		const wchar_t *id = DeviceId->Data();
		MSSndCard *card = ms_snd_card_new(&ms_wasapi_snd_card_desc);
		WasapiSndCard *wasapicard = static_cast<WasapiSndCard *>(card->data);
		card->name = ms_strdup(name);
		card->capabilities = capabilities;
		wasapicard->id_vector = new std::vector<wchar_t>(wcslen(id) + 1);
		wcscpy_s(&wasapicard->id_vector->front(), wasapicard->id_vector->size(), id);
		wasapicard->id = &wasapicard->id_vector->front();
		return card;
	}

	void AddOrUpdateCard(String ^ DeviceId, String ^ DeviceName, DeviceClass dc) {
		char *name;
		const bctbx_list_t *elem = _l;
		size_t inputlen;
		size_t returnlen;
		uint8_t capabilities = 0;
		int err;

		inputlen = wcslen(DeviceName->Data()) + 1;
		returnlen = inputlen * 2;
		name = (char *)ms_malloc(returnlen);
		UINT currentCodePage = bctbx_get_code_page(NULL);
		if ((err = WideCharToMultiByte(currentCodePage, 0, DeviceName->Data(), -1, name, (int)returnlen, NULL, NULL)) ==
		    0) {
			ms_error("mswasapi: Cannot convert card name to multi-byte string.");
			return;
		}
		switch (dc) {
			case DeviceClass::AudioRender:
				capabilities = MS_SND_CARD_CAP_PLAYBACK;
				break;
			case DeviceClass::AudioCapture:
				capabilities = MS_SND_CARD_CAP_CAPTURE;
				break;
			default:
				capabilities = MS_SND_CARD_CAP_PLAYBACK | MS_SND_CARD_CAP_CAPTURE;
				break;
		}

		for (; elem != NULL; elem = elem->next) {
			MSSndCard *card = static_cast<MSSndCard *>(elem->data);
			WasapiSndCard *d = static_cast<WasapiSndCard *>(card->data);
			if (wcscmp(d->id, DeviceId->Data()) == 0) {
				/* Update an existing card. */
				if (card->name != NULL) {
					ms_free(card->name);
					card->name = ms_strdup(name);
				}
				card->capabilities |= capabilities;
				ms_free(name);
				return;
			}
		}

		/* Add a new card. */
		_l = bctbx_list_append(_l, NewCard(DeviceId, name, capabilities));
		ms_free(name);
	}

	void Detect(DeviceClass dc) {
		_dc = dc;
		String ^ DefaultId;
		String ^ DefaultName = "Default audio card";
		if (_dc == DeviceClass::AudioCapture) {
			DefaultId = MediaDevice::GetDefaultAudioCaptureId(AudioDeviceRole::Communications);
		} else {
			DefaultId = MediaDevice::GetDefaultAudioRenderId(AudioDeviceRole::Communications);
		}
		if (DefaultId != "") AddOrUpdateCard(DefaultId, DefaultName, _dc);
		Windows::Foundation::IAsyncOperation<DeviceInformationCollection ^> ^ op = DeviceInformation::FindAllAsync(_dc);
		op->Completed = ref new Windows::Foundation::AsyncOperationCompletedHandler<DeviceInformationCollection ^>(
		    [this](Windows::Foundation::IAsyncOperation<DeviceInformationCollection ^> ^ asyncOp,
		           Windows::Foundation::AsyncStatus asyncStatus) {
			    if (asyncStatus == Windows::Foundation::AsyncStatus::Completed) {
				    DeviceInformationCollection ^ deviceInfoCollection = asyncOp->GetResults();
				    if ((deviceInfoCollection == nullptr) || (deviceInfoCollection->Size == 0)) {
					    ms_error("mswasapi: No audio device found");
				    } else {
					    try {
						    for (unsigned int i = 0; i < deviceInfoCollection->Size; i++) {
							    DeviceInformation ^ deviceInfo = deviceInfoCollection->GetAt(i);
							    AddOrUpdateCard(deviceInfo->Id, deviceInfo->Name, _dc);
						    }
					    } catch (Platform::Exception ^ e) {
						    ms_error("mswaspi: Error of audio device detection");
					    }
				    }
			    } else {
				    ms_error("mswasapi: DeviceInformation::FindAllAsync failed");
			    }
			    SetEvent(_DetectEvent);
		    });
		WaitForSingleObjectEx(_DetectEvent, INFINITE, FALSE);
	}

	void Detect() {
		Detect(DeviceClass::AudioCapture);
		Detect(DeviceClass::AudioRender);
	}

	bctbx_list_t *GetList() {
		return _l;
	}

private:
	bctbx_list_t *_l;
	DeviceClass _dc;
	HANDLE _DetectEvent;
};

#else

static MSSndCard *ms_wasapi_snd_card_new(LPWSTR id, const char *name, uint8_t capabilities, bool isDefault) {
	MSSndCard *card = ms_snd_card_new(&ms_wasapi_snd_card_desc);
	WasapiSndCard *wasapicard = static_cast<WasapiSndCard *>(card->data);
	card->name = ms_strdup(name);
	card->capabilities = capabilities;
	card->latency = (capabilities & MS_SND_CARD_CAP_CAPTURE) ? 70 : 0;
	wasapicard->id_vector = new std::vector<wchar_t>(wcslen(id) + 1);
	wcscpy_s(&wasapicard->id_vector->front(), wasapicard->id_vector->size(), id);
	wasapicard->id = &wasapicard->id_vector->front();
	wasapicard->isDefault = isDefault;
	return card;
}

static void add_or_update_card(
    MSSndCardManager *m, bctbx_list_t **l, LPWSTR id, LPWSTR wname, EDataFlow data_flow, bool isDefault) {
	MSSndCard *card;
	const bctbx_list_t *elem = *l;
	uint8_t capabilities = 0;
	char *idStr = NULL;
	char *nameStr = NULL;
	char *name = NULL;
	size_t inputlen = wcslen(wname) + 1;
	size_t returnlen;
	UINT currentCodePage = bctbx_get_code_page(NULL);
	int sizeNeeded = WideCharToMultiByte(currentCodePage, 0, wname, (int)inputlen, NULL, 0, NULL, NULL);
	std::string strConversion(sizeNeeded, 0);
	if (WideCharToMultiByte(currentCodePage, 0, wname, (int)inputlen, &strConversion[0], sizeNeeded, NULL, NULL)) {
		size_t size = strConversion.length() + 1;
		nameStr = (char *)ms_malloc(size);
		strcpy(nameStr, strConversion.c_str());
		nameStr[size - 1] = '\0';
	}
	if (!nameStr) {
		ms_error("mswasapi: Cannot convert card name to multi-byte string.");
		goto error;
	}
	inputlen = wcslen(id) + 1;
	idStr = (char *)ms_malloc(inputlen);
	if (!idStr || wcstombs_s(&returnlen, idStr, inputlen, id, inputlen) != 0) {
		ms_error("mswasapi: Cannot convert card id to multi-byte string.");
		goto error;
	}

	if (isDefault) {
		name = ms_strdup_printf("Default %s", (data_flow == eCapture ? "Capture" : "Playback"));
	} else name = ms_strdup(nameStr);
	switch (data_flow) {
		case eRender:
			capabilities = MS_SND_CARD_CAP_PLAYBACK;
			break;
		case eCapture:
			capabilities = MS_SND_CARD_CAP_CAPTURE;
			break;
		case eAll:
		default:
			capabilities = MS_SND_CARD_CAP_PLAYBACK | MS_SND_CARD_CAP_CAPTURE;
			break;
	}
	if (!isDefault) { // Update only on non default card because it is just redondant.
		for (; elem != NULL; elem = elem->next) {
			card = static_cast<MSSndCard *>(elem->data);
			WasapiSndCard *d = static_cast<WasapiSndCard *>(card->data);
			if (wcscmp(d->id, id) == 0) {
				/* Update an existing card. */
				card->capabilities |= capabilities;
				goto error; // Not really an error
			}
		}
	}

	/* Add a new card. */
	*l = bctbx_list_append(*l, ms_wasapi_snd_card_new(id, name, capabilities, isDefault));
error:
	if (nameStr) {
		ms_free(nameStr);
	}
	if (name) {
		ms_free(name);
	}
	if (idStr) {
		ms_free(idStr);
	}
}

static void
add_endpoint(MSSndCardManager *m, EDataFlow data_flow, bctbx_list_t **l, IMMDevice *pEndpoint, bool isDefault) {
	IPropertyStore *pProps = NULL;
	LPWSTR pwszID = NULL;
	HRESULT result;
	if (isDefault)
		result =
		    StringFromIID((data_flow == eRender ? DEVINTERFACE_AUDIO_RENDER : DEVINTERFACE_AUDIO_CAPTURE), &pwszID);
	else result = pEndpoint->GetId(&pwszID);
	REPORT_ERROR("mswasapi: Could not get ID of audio endpoint", result);
	result = pEndpoint->OpenPropertyStore(STGM_READ, &pProps);
	REPORT_ERROR("mswasapi: Could not open property store", result);
	PROPVARIANT varName;
	PropVariantInit(&varName);
	result = pProps->GetValue(PKEY_Device_FriendlyName, &varName);
	REPORT_ERROR("mswasapi: Could not get friendly-name of audio endpoint", result);
	add_or_update_card(m, l, pwszID, varName.pwszVal, data_flow, isDefault);
	CoTaskMemFree(pwszID);
	pwszID = NULL;
	PropVariantClear(&varName);
	SAFE_RELEASE(pProps);
error:
	CoTaskMemFree(pwszID);
	SAFE_RELEASE(pProps);
}

static void ms_wasapi_snd_card_detect_with_data_flow(MSSndCardManager *m, EDataFlow data_flow, bctbx_list_t **l) {
	IMMDeviceEnumerator *pEnumerator = NULL;
	IMMDeviceCollection *pCollection = NULL;
	IMMDevice *pEndpoint = NULL;
	HRESULT result;
#if !defined(MS2_WINDOWS_UNIVERSAL)
#ifdef ENABLE_MICROSOFT_STORE_APP
	CoInitializeEx(NULL, COINIT_MULTITHREADED);
#else
	CoInitialize(NULL);
#endif
#endif

	result =
	    CoCreateInstance(CLSID_MMDeviceEnumerator, NULL, CLSCTX_ALL, IID_IMMDeviceEnumerator, (void **)&pEnumerator);

	REPORT_ERROR("mswasapi: Could not create an instance of the device enumerator", result);
	result = pEnumerator->GetDefaultAudioEndpoint(data_flow, eCommunications, &pEndpoint);
	if (result == S_OK) {
		add_endpoint(m, data_flow, l, pEndpoint, true);
		SAFE_RELEASE(pEndpoint);
	}
	result = pEnumerator->EnumAudioEndpoints(data_flow, DEVICE_STATE_ACTIVE, &pCollection);
	REPORT_ERROR("mswasapi: Could not enumerate audio endpoints", result);
	UINT count;
	result = pCollection->GetCount(&count);
	REPORT_ERROR("mswasapi: Could not get the number of audio endpoints", result);
	if (count == 0) {
		ms_warning("mswasapi: No audio endpoint found");
		return;
	}
	for (ULONG i = 0; i < count; i++) {
		result = pCollection->Item(i, &pEndpoint);
		REPORT_ERROR("mswasapi: Could not get pointer to audio endpoint", result);
		add_endpoint(m, data_flow, l, pEndpoint, false);
		SAFE_RELEASE(pEndpoint);
	}
error:
	SAFE_RELEASE(pEnumerator);
	SAFE_RELEASE(pCollection);
	SAFE_RELEASE(pEndpoint);
}
#endif

static void ms_wasapi_snd_card_detect(MSSndCardManager *m) {
#if defined(MS2_WINDOWS_PHONE)
	MSSndCard *card = ms_wasapi_phone_snd_card_new();
	ms_snd_card_set_manager(m, card);
	ms_snd_card_manager_add_card(m, card);
#elif defined(MS2_WINDOWS_UNIVERSAL)
	MSWASAPIDeviceEnumerator *enumerator = new MSWASAPIDeviceEnumerator();
	enumerator->Detect();
	ms_snd_card_manager_prepend_cards(m, enumerator->GetList());
	delete enumerator;
#else
	bctbx_list_t *l = NULL;
	ms_wasapi_snd_card_detect_with_data_flow(m, eCapture, &l);
	ms_wasapi_snd_card_detect_with_data_flow(m, eRender, &l);
	ms_snd_card_manager_prepend_cards(m, l);
	bctbx_list_free(l);
#endif
}

static MSFilter *ms_wasapi_snd_card_create_reader(MSSndCard *card) {
	MSFilter *f = ms_factory_create_filter_from_desc(ms_snd_card_get_factory(card), &ms_wasapi_read_desc);
	MSWASAPIReaderType reader = MSWASAPI_READER(f->data);
	reader->init(card, f);
	return f;
}

static MSFilter *ms_wasapi_snd_card_create_writer(MSSndCard *card) {
	MSFilter *f = ms_factory_create_filter_from_desc(ms_snd_card_get_factory(card), &ms_wasapi_write_desc);
	MSWASAPIWriterType writer = MSWASAPI_WRITER(f->data);
	writer->init(card, f);
	return f;
}

#ifdef _MSC_VER
#define MS_PLUGIN_DECLARE(type) extern "C" __declspec(dllexport) type
#else
#define MS_PLUGIN_DECLARE(type) extern "C" type
#endif

MS_PLUGIN_DECLARE(void) libmswasapi_init(MSFactory *factory) {
	MSSndCardManager *manager = ms_factory_get_snd_card_manager(factory);
	ms_snd_card_manager_register_desc(manager, &ms_wasapi_snd_card_desc);
	ms_message("libmswasapi plugin loaded");
}
