linphone-sdk/mswasapi/mswasapi.cpp
2025-06-16 13:25:27 +07:00

1244 lines
46 KiB
C++

/*
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");
}