[Windows] Possible bug with setDriver and getDriverInfo

Hello!, I am trying to change the current driver after detect a default device change on Windows, however every time I call in the main thread [setDriver] with the right driver somehow the drivers get changed, so when I call getDriver and getDriverInfo to know if changed to the right driver the device selected is wrong.

After hours debugging, it gives me these values, lets say we have 3 output devices detected by Fmod, after I change the default device to another, lets say it becomes the driver [2], so I call [setDriver(2)], however for some reason it is wrong even if [getDriverInfo] reported it isn’t(deviceGuid is correct, already checked that), so I call [getDriver] and [getDriverInfo] for check if everything is ok, [getDriver] gives as value [2], however [getDriverInfo] doesn’t give the same driver info it gave before, now everything is changed so it becomes a loop, would like to know if this is some mistake in my code, or maybe an bug in Fmod library.

So it become a loop like this:
getDriverNum -> getDriverInfo -> Found device -> setDriver -> Check If Correct -> getDriver -> getDriverInfo -> Wrong device repeat steps

I had to implement this because seems like Fmod doesn’t change the device when a [default device change] is detect, this feature would be great, like just a flag at initialize Fmod and allow to change it after initialize, something like FMOD_INIT_USEDEFAULTDEVICE.

Operating System : Windows 10 Pro Version 1803 (OS Build 17134.376)

Here is an example code:

void ChangeDevice(const FMOD_GUID &deviceGuid)
{
	int32_t numDrivers = 0;
	if (FmodSystem->getNumDrivers(&numDrivers) == FMOD_OK)
	{
		for (int32_t n = 0; n < numDrivers; ++n)
		{
			FMOD_GUID guid = {};

			if (FmodSystem->getDriverInfo(n, nullptr, 0, &guid, nullptr, nullptr, nullptr) == FMOD_OK)
			{
				if (memcmp(&deviceGuid, &guid, sizeof(FMOD_GUID)) == 0)
				{
					FMOD_RESULT result = FmodSystem->setDriver(n);
					assert(result == FMOD_OK);
				}
			}
		}
	}
}

// Per Frame function
void Update()
{
	DeviceCritical.Lock();
	if (DeviceAudioDefaultChanged == true)
	{
		do
		{
			int32_t driver = 0;
			if (FmodSystem->getDriver(&driver) == FMOD_OK)
			{
				FMOD_GUID guid = {};

				if (FmodSystem->getDriverInfo(driver, nullptr, 0, &guid, nullptr, nullptr, nullptr) == FMOD_OK)
				{
					if (memcmp(&DeviceAudioDefaultGUID, &guid, sizeof(guid)) == 0)
					{
						break;
					}

					ChangeDevice(DeviceAudioDefaultGUID);
				}
			}
		} while (true);

		DeviceAudioDefaultChanged = false;
	}
	DeviceCritical.Unlock();

	if (GetAudioSystemStatus(EAudioSystemFlag::ASF_Suspended) == false)
	{
		FmodSystem->update();
	}
}

// Called from OnDefaultDeviceChanged IMMNotificationClient (the deviceId is correct, not wrong at all, already checked)
void OnDefaultDeviceChanged(const FMOD_GUID &deviceId)
{
	DeviceCritical.Lock();
	DeviceAudioDefaultChanged = true;
	memcpy(&DeviceAudioDefaultGUID, &deviceId, sizeof(DeviceAudioDefaultGUID));
	DeviceCritical.Unlock();
}

Hi,
FMOD automatically does all of this already. There are 2 ways to handle it.

  1. do nothing and fmod switches to the next ‘default’ device in the windows sound device list after the first one has been disconnected. You must be calling System::update regularly to get this to work!
  2. Detect the device change with https://fmod.com/resources/documentation-api?page=content/generated/FMOD_SYSTEM_CALLBACK_TYPE.html#/ This already does the work that your update function does. The reason we put this callback here is so that you can call setDriver yourself to override any choice fmod might want to make (ie using the new windows default audio device)

Yes, that works perfectly, however what I am trying to do is change the current device when change the default device directly from windows sound playback panel(not disconnecting), that is why had to use IMMNotificationClient::OnDefaultDeviceChanged, however as explained when I set the new device it get wrong, what you described already works perfectly, no problem at all, only got this kind of problem when dealing with default device change without disconnecting.

About callback system, for some reason even if I change the default device, it doesn’t report anything, except if disable/disconnect the current default device.

PD: Deleted my last answer and marked it like spam by mistake, auto-click…

Ah, if you set the device without removing or adding, yes that is a use case that is not very common and we don’t cover it because enumerating device lists constantly can be quite expensive (we use waveOutGetNumDevs i think it is called which has no overhead).

Also IMMNotificationClient::OnDefaultDeviceChanged has a minimum OS requirement of Windows Vista where at the time (and still now) we have Windows XP and lower support. I think it is process wide as well, so we can’t touch OS notification / callback settings if the app itself might be using it.

Yes I perfectly understand, that is really reasonable, due that many projects doesn’t do this, however I am trying to do it manually since my project will require minimum Windows 7 and newer, and I am trying to add a [default device] in my audio list, but when I call [setDriver] with the new device it fails somehow to change to it, it just keep the same device, and when I call it again for change back to another device, it set as current device what I set in the last [setDriver] call.

Will leave an example here:

I have 3 devices, lets name them as [Device1], [Device2], and [Device3].

When the game start, [Device1] is the default device, then I change in Windows panel to [Device2], my application detects that change and call [setDriver] with the new driver index, however it makes no effect at all, so for confirm if was a bug in my application I called [getDriver], it reports the new device index, however still don’t change the current playback to this new device, so I change to [Device1] again, and somehow it start to playback from [Device2] like expected before, but now I expected it to continue with [Device1].

My example code does a [while] loop for check the current device, if was changed, and somehow it report a different device at the current driver.

Here is a list information about what it reports:
[Device1] is default, after it start and I change to [Device2], I detect this device index is [2], so I call setDriver(2) (detected using getDriverInfo), but after set the device when I call getDriver it reports index (2), but getDriverInfo(2) reports [Device1] instead of [Device2].

Hope this help to understand what is my current problem, not sure if the order of call might cause the problem, but already tried everything and keep doing the same thing, it is like the [getDriver] updates and become wrong, tried using callback but didn’t receive any report.

Here is a video that show what is happening when call [setDriver]: https://youtu.be/78EX81pSeiQ

Current code used for the video:

void ChangeDevice(const FMOD_GUID &deviceGuid)
{
	int32_t numDrivers = 0;
	if (FmodSystem->getNumDrivers(&numDrivers) == FMOD_OK)
	{
		for (int32_t n = 0; n < numDrivers; ++n)
		{
			FMOD_GUID guid = {};

			if (FmodSystem->getDriverInfo(n, nullptr, 0, &guid, nullptr, nullptr, nullptr) == FMOD_OK)
			{
				if (memcmp(&deviceGuid, &guid, sizeof(FMOD_GUID)) == 0)
				{
					FMOD_RESULT result = FmodSystem->setDriver(n);
					assert(result == FMOD_OK);
				}
			}
		}
	}
}

// Per Frame function
void Update()
{
	DeviceCritical.Lock();
	if (DeviceAudioDefaultChanged == true)
	{
		ChangeDevice(DeviceAudioDefaultGUID);
		DeviceAudioDefaultChanged = false;
	}
	DeviceCritical.Unlock();

	if (GetAudioSystemStatus(EAudioSystemFlag::ASF_Suspended) == false)
	{
		FmodSystem->update();
	}
}

// Called from OnDefaultDeviceChanged IMMNotificationClient (the deviceId is correct, not wrong at all, already checked)
void OnDefaultDeviceChanged(const FMOD_GUID &deviceId)
{
	DeviceCritical.Lock();
	DeviceAudioDefaultChanged = true;
	memcpy(&DeviceAudioDefaultGUID, &deviceId, sizeof(DeviceAudioDefaultGUID));
	DeviceCritical.Unlock();
}

This might help, just tested forcing the new driver without using Windows Sound Panel, and it works perfectly, it get bugged only if change the [default device] with Windows Sound Panel and try to change in my game with setDriver, seems like the device list changes and doesn’t report it at all to my callback.

Here is how I forced the device change in my code, inside my Update loop:

	static bool forceChange = false;
	static int newDeviceDriver = 0;
	if (forceChange)
	{
		FmodSystem->setDriver(newDeviceDriver);
		forceChange = false;
	}

I used the debugger for change forceChange to true, and newDeviceDriver to a known driver index.

I modified the 3D sample for reproduce the same behaviour so you can check it, here is the link of 3d.cpp, hope this help to find the cause of this weird behaviour, still not sure if a bug in FMOD or maybe I am doing some mistake with setDriver, I followed the documentation but not sure if need to do something else.

https://www.mediafire.com/file/xyedyz8pdc77w67/3d.cpp

Really thank you for your time.

Apologies, I understand your predicament better now, originally I thought it was device removal and insertion, then you did qualify that it was a default device change that was the problem, and I missed that.

I see what the issue is, FMOD caches the device list, so it doesnt have to hit the OS every time you call getNumDrivers or getDriverInfo, so that it doesnt slow the app down (as I mentioned before).

This means when the default device is changed, index 0 is always supposed to be the ‘new’ default device, but it doesn’t get updated because according to our logic, the number of devices didn’t change so the device list was not refreshed.

To get it to refresh in your code, you’ll need to reset the current output. The simplest way I can think of doing that is to set the output to nosound, and then back again.

Here is what I did to your 3d example

	EnterCriticalSection(&CriticalDevice);
	if (DeviceAudioDefaultChanged == true)
	{
		FMOD_OUTPUTTYPE output;
		
		result = system->getOutput(&output);
		ERRCHECK(result);
		result = system->setOutput(FMOD_OUTPUTTYPE_NOSOUND);
		ERRCHECK(result);
		result = system->setOutput(output);
		ERRCHECK(result);

I think it will be time to look at handling device change automatically again in fmod. If we can use OS notifications safely we’ll probably add this in.

1 Like

Thank you so much, it worked perfectly, with this I can continue without any problem.

Updating this thread with the source code with the patch above applied

/*==============================================================================
3D Example
Copyright (c), Firelight Technologies Pty, Ltd 2004-2018.

This example shows how to basic 3D positioning of sounds.
==============================================================================*/
#include "fmod.hpp"
#include "common.h"
#include <initguid.h>
#include <mmdeviceapi.h>

const int   INTERFACE_UPDATETIME = 50;      // 50ms update for interface
const float DISTANCEFACTOR = 1.0f;          // Units per meter.  I.e feet would = 3.28.  centimeters would = 100.

class ENotificationClient : public IMMNotificationClient
{
public:
	HRESULT STDMETHODCALLTYPE OnDefaultDeviceChanged(EDataFlow flow, ERole role, LPCWSTR pwstrDeviceId);

	HRESULT STDMETHODCALLTYPE OnDeviceAdded(LPCWSTR pwstrDeviceId)
	{
		UNREFERENCED_PARAMETER(pwstrDeviceId);
		return S_OK;
	}
	HRESULT STDMETHODCALLTYPE OnDeviceRemoved(LPCWSTR pwstrDeviceId)
	{
		UNREFERENCED_PARAMETER(pwstrDeviceId);
		return S_OK;
	}
	HRESULT STDMETHODCALLTYPE OnDeviceStateChanged(LPCWSTR pwstrDeviceId, DWORD dwNewState)
	{
		UNREFERENCED_PARAMETER(pwstrDeviceId);
		UNREFERENCED_PARAMETER(dwNewState);
		return S_OK;
	}
	HRESULT STDMETHODCALLTYPE OnPropertyValueChanged(LPCWSTR pwstrDeviceId, const PROPERTYKEY key)
	{
		UNREFERENCED_PARAMETER(pwstrDeviceId);
		UNREFERENCED_PARAMETER(key);
		return S_OK;
	}

	HRESULT STDMETHODCALLTYPE QueryInterface(REFIID riid, VOID **ppvInterface)
	{
		if (IID_IUnknown == riid)
		{
			*ppvInterface = (IUnknown*)this;
		}
		else if (__uuidof(IMMNotificationClient) == riid)
		{
			*ppvInterface = (IMMNotificationClient*)this;
		}
		else
		{
			*ppvInterface = nullptr;
			return E_NOINTERFACE;
		}
		return S_OK;
	}

	ULONG STDMETHODCALLTYPE AddRef()
	{
		return 1;
	}

	ULONG STDMETHODCALLTYPE Release()
	{
		return 0;
	}
};

#define EXIT_ON_ERROR(hres)  \
              if (FAILED(hres)) { goto Exit; }
#define SAFE_RELEASE(punk)  \
              if ((punk) != NULL)  \
                { (punk)->Release(); (punk) = NULL; }

const CLSID CLSID_MMDeviceEnumerator = __uuidof(MMDeviceEnumerator);
const IID IID_IMMDeviceEnumerator = __uuidof(IMMDeviceEnumerator);
ENotificationClient NotificationClient;

CRITICAL_SECTION CriticalDevice;
bool DeviceAudioDefaultChanged = false;
FMOD_GUID DeviceAudioDefaultGUID = {};

HRESULT ENotificationClient::OnDefaultDeviceChanged(EDataFlow flow, ERole role, LPCWSTR pwstrDeviceId)
{
	UNREFERENCED_PARAMETER(pwstrDeviceId);

	if (flow != EDataFlow::eRender ||
		role != ERole::eConsole)
	{
		return S_OK;
	}

	HRESULT hr = S_OK;
	IMMDeviceEnumerator *pEnumerator = NULL;
	IMMDevice *pEndpoint = NULL;
	IPropertyStore *pProps = NULL;
	LPWSTR pwszID = NULL;

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

	hr = pEnumerator->GetDefaultAudioEndpoint(flow, role, &pEndpoint);
	EXIT_ON_ERROR(hr);

	// Get the endpoint ID string.
	hr = pEndpoint->GetId(&pwszID);
	EXIT_ON_ERROR(hr);

	hr = pEndpoint->OpenPropertyStore(
		STGM_READ, &pProps);
	EXIT_ON_ERROR(hr);

	PROPVARIANT varGUID;
	// Initialize container for property value.
	PropVariantInit(&varGUID);

	hr = pProps->GetValue(
		PKEY_AudioEndpoint_GUID, &varGUID);
	EXIT_ON_ERROR(hr);

	CLSID deviceGuid;
	CLSIDFromString(varGUID.pwszVal, &deviceGuid);

	FMOD_GUID fmodDeviceID;
	fmodDeviceID.Data1 = deviceGuid.Data1;
	fmodDeviceID.Data2 = deviceGuid.Data2;
	fmodDeviceID.Data3 = deviceGuid.Data3;
	memcpy(fmodDeviceID.Data4, deviceGuid.Data4, sizeof(fmodDeviceID.Data4));

	EnterCriticalSection(&CriticalDevice);
	DeviceAudioDefaultChanged = true;
	memcpy(&DeviceAudioDefaultGUID, &fmodDeviceID, sizeof(DeviceAudioDefaultGUID));
	LeaveCriticalSection(&CriticalDevice);

	CoTaskMemFree(pwszID);
	pwszID = NULL;
	PropVariantClear(&varGUID);
	SAFE_RELEASE(pProps);
	SAFE_RELEASE(pEndpoint);

	SAFE_RELEASE(pEnumerator);

	return S_OK;

Exit:
	CoTaskMemFree(pwszID);
	SAFE_RELEASE(pEnumerator);
	SAFE_RELEASE(pEndpoint);
	SAFE_RELEASE(pProps);

	return S_OK;
}

int FMOD_Main()
{
    FMOD::System    *system;
    FMOD::Sound     *sound1, *sound2, *sound3;
    FMOD::Channel   *channel1 = 0, *channel2 = 0, *channel3 = 0;
    FMOD_RESULT      result;
    bool             listenerflag = true;
    FMOD_VECTOR      listenerpos  = { 0.0f, 0.0f, -1.0f * DISTANCEFACTOR };
    unsigned int     version;
    void            *extradriverdata = 0;

	InitializeCriticalSection(&CriticalDevice);

	if (FAILED(CoInitializeEx(nullptr, COINIT_MULTITHREADED)))
	{
		return 0;
	}

	IMMDeviceEnumerator *Enumerator = nullptr;
	HRESULT hr = CoCreateInstance(__uuidof(MMDeviceEnumerator), nullptr, CLSCTX_INPROC_SERVER, __uuidof(IMMDeviceEnumerator), (void**)&Enumerator);
	if (SUCCEEDED(hr))
	{
		Enumerator->RegisterEndpointNotificationCallback(&NotificationClient);
	}

    Common_Init(&extradriverdata);

    /*
        Create a System object and initialize.
    */
    result = FMOD::System_Create(&system);
    ERRCHECK(result);
    
    result = system->getVersion(&version);
    ERRCHECK(result);

    if (version < FMOD_VERSION)
    {
        Common_Fatal("FMOD lib version %08x doesn't match header version %08x", version, FMOD_VERSION);
    }
    
    result = system->init(100, FMOD_INIT_NORMAL, extradriverdata);
    ERRCHECK(result);
    
    /*
        Set the distance units. (meters/feet etc).
    */
    result = system->set3DSettings(1.0, DISTANCEFACTOR, 1.0f);
    ERRCHECK(result);

    /*
        Load some sounds
    */
    result = system->createSound(Common_MediaPath("drumloop.wav"), FMOD_3D, 0, &sound1);
    ERRCHECK(result);
    result = sound1->set3DMinMaxDistance(0.5f * DISTANCEFACTOR, 5000.0f * DISTANCEFACTOR);
    ERRCHECK(result);
    result = sound1->setMode(FMOD_LOOP_NORMAL);
    ERRCHECK(result);

    result = system->createSound(Common_MediaPath("jaguar.wav"), FMOD_3D, 0, &sound2);
    ERRCHECK(result);
    result = sound2->set3DMinMaxDistance(0.5f * DISTANCEFACTOR, 5000.0f * DISTANCEFACTOR);
    ERRCHECK(result);
    result = sound2->setMode(FMOD_LOOP_NORMAL);
    ERRCHECK(result);

    result = system->createSound(Common_MediaPath("swish.wav"), FMOD_2D, 0, &sound3);
    ERRCHECK(result);

    /*
        Play sounds at certain positions
    */
    {
        FMOD_VECTOR pos = { -10.0f * DISTANCEFACTOR, 0.0f, 0.0f };
        FMOD_VECTOR vel = {  0.0f, 0.0f, 0.0f };

        result = system->playSound(sound1, 0, true, &channel1);
        ERRCHECK(result);
        result = channel1->set3DAttributes(&pos, &vel);
        ERRCHECK(result);
        result = channel1->setPaused(false);
        ERRCHECK(result);
    }

    {
        FMOD_VECTOR pos = { 15.0f * DISTANCEFACTOR, 0.0f, 0.0f };
        FMOD_VECTOR vel = { 0.0f, 0.0f, 0.0f };

        result = system->playSound(sound2, 0, true, &channel2);
        ERRCHECK(result);
        result = channel2->set3DAttributes(&pos, &vel);
        ERRCHECK(result);
        result = channel2->setPaused(false);
        ERRCHECK(result);
    }

    /*
        Main loop
    */
    do
    {
		EnterCriticalSection(&CriticalDevice);
		if (DeviceAudioDefaultChanged == true)
		{
            FMOD_OUTPUTTYPE output;
            
            result = system->getOutput(&output);
            ERRCHECK(result);
            result = system->setOutput(FMOD_OUTPUTTYPE_NOSOUND);
            ERRCHECK(result);
            result = system->setOutput(output);
            ERRCHECK(result);
            
    		int numDrivers = 0;
			if (system->getNumDrivers(&numDrivers) == FMOD_OK)
			{
				for (int n = 0; n < numDrivers; ++n)
				{
					char name[256] = {};
					FMOD_GUID guid = {};
					int systemrate = 0;
					FMOD_SPEAKERMODE speakermode = FMOD_SPEAKERMODE_DEFAULT;
					int speakermodechannels = 0;

					if (system->getDriverInfo(n, name, sizeof(name), &guid, &systemrate, &speakermode, &speakermodechannels) == FMOD_OK)
					{
						if (memcmp(&DeviceAudioDefaultGUID, &guid, sizeof(FMOD_GUID)) == 0)
						{
							system->setDriver(n);
						}
					}
				}
			}

			DeviceAudioDefaultChanged = false;
		}
		LeaveCriticalSection(&CriticalDevice);

        Common_Update();

        if (Common_BtnPress(BTN_ACTION1))
        {
            bool paused;
            channel1->getPaused(&paused);
            channel1->setPaused(!paused);
        }

        if (Common_BtnPress(BTN_ACTION2))
        {
            bool paused;
            channel2->getPaused(&paused);
            channel2->setPaused(!paused);
        }

        if (Common_BtnPress(BTN_ACTION3))
        {
            result = system->playSound(sound3, 0, false, &channel3);
            ERRCHECK(result);
        }

        if (Common_BtnPress(BTN_MORE))
        {
            listenerflag = !listenerflag;
        }

        if (!listenerflag)
        {
            if (Common_BtnDown(BTN_LEFT))
            {
                listenerpos.x -= 1.0f * DISTANCEFACTOR;
                if (listenerpos.x < -24 * DISTANCEFACTOR)
                {
                    listenerpos.x = -24 * DISTANCEFACTOR;
                }
            }

            if (Common_BtnDown(BTN_RIGHT))
            {
                listenerpos.x += 1.0f * DISTANCEFACTOR;
                if (listenerpos.x > 23 * DISTANCEFACTOR)
                {
                    listenerpos.x = 23 * DISTANCEFACTOR;
                }
            }
        }

        // ==========================================================================================
        // UPDATE THE LISTENER
        // ==========================================================================================
        {
            static float t = 0;
            static FMOD_VECTOR lastpos = { 0.0f, 0.0f, 0.0f };
            FMOD_VECTOR forward        = { 0.0f, 0.0f, 1.0f };
            FMOD_VECTOR up             = { 0.0f, 1.0f, 0.0f };
            FMOD_VECTOR vel;

            if (listenerflag)
            {
                listenerpos.x = (float)sin(t * 0.05f) * 24.0f * DISTANCEFACTOR; // left right pingpong
            }

            // ********* NOTE ******* READ NEXT COMMENT!!!!!
            // vel = how far we moved last FRAME (m/f), then time compensate it to SECONDS (m/s).
            vel.x = (listenerpos.x - lastpos.x) * (1000 / INTERFACE_UPDATETIME);
            vel.y = (listenerpos.y - lastpos.y) * (1000 / INTERFACE_UPDATETIME);
            vel.z = (listenerpos.z - lastpos.z) * (1000 / INTERFACE_UPDATETIME);

            // store pos for next time
            lastpos = listenerpos;

            result = system->set3DListenerAttributes(0, &listenerpos, &vel, &forward, &up);
            ERRCHECK(result);

            t += (30 * (1.0f / (float)INTERFACE_UPDATETIME));    // t is just a time value .. it increments in 30m/s steps in this example
        }

        result = system->update();
        ERRCHECK(result);

        // Create small visual display.
        char s[80] = "|.............<1>......................<2>.......|";
        s[(int)(listenerpos.x / DISTANCEFACTOR) + 25] = 'L';

        Common_Draw("==================================================");
        Common_Draw("3D Example.");
        Common_Draw("Copyright (c) Firelight Technologies 2004-2018.");
        Common_Draw("==================================================");
        Common_Draw("");
        Common_Draw("Press %s to toggle sound 1 (16bit Mono 3D)", Common_BtnStr(BTN_ACTION1));
        Common_Draw("Press %s to toggle sound 2 (8bit Mono 3D)", Common_BtnStr(BTN_ACTION2));
        Common_Draw("Press %s to play a sound (16bit Stereo 2D)", Common_BtnStr(BTN_ACTION3));
        Common_Draw("Press %s or %s to move listener in still mode", Common_BtnStr(BTN_LEFT), Common_BtnStr(BTN_RIGHT));
        Common_Draw("Press %s to toggle listener auto movement", Common_BtnStr(BTN_MORE));
        Common_Draw("Press %s to quit", Common_BtnStr(BTN_QUIT));
        Common_Draw("");
        Common_Draw(s);

        Common_Sleep(INTERFACE_UPDATETIME - 1);
    } while (!Common_BtnPress(BTN_QUIT));

    /*
        Shut down
    */
    result = sound1->release();
    ERRCHECK(result);
    result = sound2->release();
    ERRCHECK(result);
    result = sound3->release();
    ERRCHECK(result);

    result = system->close();
    ERRCHECK(result);
    result = system->release();
    ERRCHECK(result);

    Common_Close();

	if (Enumerator)
	{
		Enumerator->UnregisterEndpointNotificationCallback(&NotificationClient);
		Enumerator->Release();
		Enumerator = nullptr;
	}

	CoUninitialize();

    return 0;
}