In our game (on windows ) we would like to handle audio driver output this way
- either the player select “auto”, and we detect when the player changes default output in windows ( using IMMNotificationClient )
- or the player is explicit about the output device, and we try to keep it, even if he changes the default output in Windows
For the first case, we are using this code ( [Windows] Possible bug with setDriver and getDriverInfo ) and everything is working
For the second case, we store the driver guid, and when an output change , we try to refind our driver and set it
Repro step in the sample provided sampling code:
- Launch sample ( with 3 audio output ) ( sound ok )
- Cycle from “DEFAULT” to the 2nd output ( sound ok )
- In Windows, change default output to the 3th output
- Note that the sound is now outputed to the default ( 0 ) driver, despite being setDriver to 2
Am I missing something ?
Thanks you
here is a sample code:
#include "fmod.hpp"
#include "common.h"
#include <initguid.h>
#include <mmdeviceapi.h>
#include <vector>
using namespace std;
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 = { -1.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 = { 1.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
*/
int wantedDeviceIndex = -1;
char wantedDeviceName[256];
strcpy( wantedDeviceName, "DEFAULT" );
FMOD_GUID wantedDeviceGUID = {};
DeviceAudioDefaultChanged = true;
struct DeviceCache
{
int id;
char name[256];
FMOD_GUID guid;
};
vector< DeviceCache > deviceCache;
do
{
bool needToBuildDeviceCache = false;
bool needToChangeDriver = false;
EnterCriticalSection(&CriticalDevice);
if( DeviceAudioDefaultChanged == true ) {
printf( "DeviceAudioDefaultChanged" );
FMOD_OUTPUTTYPE output;
result = system->getOutput(&output);
ERRCHECK(result);
result = system->setOutput(FMOD_OUTPUTTYPE_NOSOUND);
ERRCHECK(result);
result = system->setOutput(output);
ERRCHECK(result);
needToBuildDeviceCache = true;
DeviceAudioDefaultChanged = false;
}
LeaveCriticalSection( &CriticalDevice );
if( needToBuildDeviceCache ) {
deviceCache.clear();
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 ) {
DeviceCache dc;
dc.id = n;
dc.guid = guid;
strcpy( dc.name, name );
deviceCache.push_back( dc );
}
}
}
printf( "Device cache rebuild" );
needToChangeDriver = true;
}
if( Common_BtnPress( BTN_ACTION1 ) )
{
wantedDeviceIndex++;
if( wantedDeviceIndex >= deviceCache.size() )
wantedDeviceIndex = -1;
if( wantedDeviceIndex == -1 ) {
//default
strcpy( wantedDeviceName, "DEFAULT" );
memset( &wantedDeviceGUID, 0, sizeof( FMOD_GUID ) );
} else {
strcpy( wantedDeviceName, deviceCache[wantedDeviceIndex].name );
memcpy( &wantedDeviceGUID, &deviceCache[wantedDeviceIndex].guid, sizeof( FMOD_GUID ) );
}
needToChangeDriver = true;
}
if( needToChangeDriver ) {
DeviceCache* pDC = NULL;
for( int i=0; i<deviceCache.size(); i++ ) {
if( memcmp( &deviceCache[i].guid, &wantedDeviceGUID, sizeof(FMOD_GUID) ) == 0 ) {
pDC = &deviceCache[i];
}
}
system->setDriver( pDC==NULL ? 0 : pDC->id );
needToChangeDriver = false;
}
Common_Update();
result = system->update();
ERRCHECK(result);
Common_Draw("==================================================");
int currentDriver;
if( system->getDriver( ¤tDriver) == FMOD_OK ) {
char name[256] = {};
FMOD_GUID guid = {};
if( system->getDriverInfo( currentDriver, name, sizeof( name ), &guid, NULL, NULL, NULL ) == FMOD_OK ) {
Common_Draw( "Current Driver (%d) %s", currentDriver, name );
}
}
Common_Draw( "Wanted Driver (%d) %s", wantedDeviceIndex, wantedDeviceName );
Common_Draw("Press %s to cycle wanted driver", Common_BtnStr(BTN_ACTION1));
Common_Draw("");
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;
}