57dfaea5fd
fix configs not getting execed properly when not run from the automatically detected basedir. fix some silly bugs. qcc: remove deprecated %5 support without qccx syntax. qcc: added -Fembedsrc to embed the sourcecode into the progs in a form that can be read with various standard unzip programs. This works without breaking any engines. server: fix memory explosion issue with fitz666 clients. git-svn-id: https://svn.code.sf.net/p/fteqw/code/trunk@5016 fc73d0e0-1445-4013-8a0c-d673dee63da5
504 lines
No EOL
18 KiB
C
504 lines
No EOL
18 KiB
C
#include "quakedef.h"
|
|
|
|
#if defined(AVAIL_WASAPI) && !defined(SERVERONLY)
|
|
#include "winquake.h"
|
|
|
|
#include <audioclient.h>
|
|
#include <mmdeviceapi.h>
|
|
#include <audiopolicy.h>
|
|
|
|
#define AUDIODRIVERNAME "WASAPI"
|
|
|
|
|
|
#define REFTIMES_PER_SEC 10000000
|
|
|
|
#define FORCE_DEFINE_GUID(name, l, w1, w2, b1, b2, b3, b4, b5, b6, b7, b8) \
|
|
const GUID DECLSPEC_SELECTANY name \
|
|
= { l, w1, w2, { b1, b2, b3, b4, b5, b6, b7, b8 } }
|
|
#define FORCE_DEFINE_PROPERTYKEY(name, l, w1, w2, b1, b2, b3, b4, b5, b6, b7, b8, pid) const PROPERTYKEY name = { { l, w1, w2, { b1, b2, b3, b4, b5, b6, b7, b8 } }, pid }
|
|
|
|
FORCE_DEFINE_GUID(CLSID_MMDeviceEnumerator, 0xBCDE0395, 0xE52F, 0x467C, 0x8E, 0x3D, 0xC4, 0x57, 0x92, 0x91, 0x69, 0x2E);
|
|
FORCE_DEFINE_GUID(IID_IMMDeviceEnumerator, 0xA95664D2, 0x9614, 0x4F35, 0xA7, 0x46, 0xDE, 0x8D, 0xB6, 0x36, 0x17, 0xE6);
|
|
FORCE_DEFINE_GUID(IID_IAudioClient, 0x1CB9AD4C, 0xDBFA, 0x4c32, 0xB1, 0x78, 0xC2, 0xF5, 0x68, 0xA7, 0x03, 0xB2);
|
|
FORCE_DEFINE_GUID(IID_IAudioRenderClient, 0xF294ACFC, 0x3146, 0x4483, 0xA7, 0xBF, 0xAD, 0xDC, 0xA7, 0xC2, 0x60, 0xE2);
|
|
|
|
FORCE_DEFINE_GUID(KSDATAFORMAT_SUBTYPE_PCM, 0x00000001, 0x0000, 0x0010, 0x80, 0x00, 0x00, 0xaa, 0x00, 0x38, 0x9b, 0x71);
|
|
FORCE_DEFINE_GUID(KSDATAFORMAT_SUBTYPE_IEEE_FLOAT, 0x00000003, 0x0000, 0x0010, 0x80, 0x00, 0x00, 0xaa, 0x00, 0x38, 0x9b, 0x71);
|
|
|
|
static void *WASAPI_Lock(soundcardinfo_t *sc, unsigned int *startoffset)
|
|
{
|
|
return sc->sn.buffer;
|
|
}
|
|
static void WASAPI_Unlock(soundcardinfo_t *sc, void *buffer)
|
|
{
|
|
//no need to do anything
|
|
}
|
|
static void WASAPI_Submit(soundcardinfo_t *sc, int start, int end)
|
|
{
|
|
//submit happens outside the mixer
|
|
}
|
|
static unsigned int WASAPI_GetDMAPos(soundcardinfo_t *sc)
|
|
{
|
|
sc->sn.samplepos = sc->snd_sent;
|
|
return sc->sn.samplepos;
|
|
}
|
|
static void WASAPI_Shutdown(soundcardinfo_t *sc)
|
|
{
|
|
sc->Shutdown = NULL;
|
|
Sys_WaitOnThread(sc->thread);
|
|
sc->thread = NULL;
|
|
}
|
|
|
|
static qboolean WASAPI_AcceptableFormat(soundcardinfo_t *sc, IAudioClient *dev, WAVEFORMATEX *pwfx)
|
|
{
|
|
if (pwfx->wFormatTag == WAVE_FORMAT_EXTENSIBLE && !memcmp(&((WAVEFORMATEXTENSIBLE*)pwfx)->SubFormat, &KSDATAFORMAT_SUBTYPE_IEEE_FLOAT, sizeof(GUID)) && pwfx->wBitsPerSample == 32)
|
|
{
|
|
sc->sn.samplebits = 32;//oo, floating point audio. I guess this means we can have fun with clamping, right?
|
|
}
|
|
else if (pwfx->wFormatTag == WAVE_FORMAT_EXTENSIBLE && memcmp(&((WAVEFORMATEXTENSIBLE*)pwfx)->SubFormat, &KSDATAFORMAT_SUBTYPE_PCM, sizeof(GUID)))
|
|
{
|
|
Con_Printf("WASAPI: unsupported sample type\n");
|
|
return false; //we only support pcm / floats
|
|
}
|
|
else if (pwfx->wBitsPerSample == 8 || pwfx->wBitsPerSample == 16)
|
|
sc->sn.samplebits = pwfx->wBitsPerSample; //these sample sizes work
|
|
else
|
|
{
|
|
Con_Printf("WASAPI: unsupported sample size\n");
|
|
return false; //unsupported bit formats
|
|
}
|
|
|
|
if (pwfx->nChannels > MAXSOUNDCHANNELS)
|
|
{
|
|
Con_Printf("WASAPI: too many channels\n");
|
|
return false;
|
|
}
|
|
|
|
sc->sn.numchannels = pwfx->nChannels;
|
|
sc->sn.speed = pwfx->nSamplesPerSec;
|
|
|
|
Con_Printf("WASAPI: %i channel %ibit %ukhz\n", sc->sn.numchannels, pwfx->wBitsPerSample, (unsigned int)pwfx->nSamplesPerSec);
|
|
return true;
|
|
}
|
|
static qboolean WASAPI_DetermineFormat(soundcardinfo_t *sc, IAudioClient *dev, qboolean exclusive, WAVEFORMATEX **ret)
|
|
{
|
|
WAVEFORMATEX *pwfx;
|
|
|
|
if (!SUCCEEDED(dev->lpVtbl->GetMixFormat(dev, &pwfx)))
|
|
return false;
|
|
|
|
if (snd_speed || Cvar_Get("wasapi_forcerate", "0", 0, "WASAPI audio output")->ival)
|
|
{ //if some other driver has already committed us to a set rate, we need to drive wasapi at that rate too.
|
|
//this may cause failures later in this function.
|
|
Con_Printf("WASAPI: overriding sampler rate\n");
|
|
pwfx->nSamplesPerSec = snd_speed;
|
|
pwfx->nAvgBytesPerSec = pwfx->nSamplesPerSec * pwfx->nBlockAlign;
|
|
}
|
|
|
|
if (Cvar_Get("wasapi_forcechannels", "0", 0, "WASAPI audio output")->ival)
|
|
{
|
|
Con_Printf("WASAPI: overriding channels\n");
|
|
|
|
if (sc->sn.numchannels >= 8)
|
|
{
|
|
pwfx->nChannels = 8;
|
|
if (pwfx->wFormatTag == WAVE_FORMAT_EXTENSIBLE)
|
|
((WAVEFORMATEXTENSIBLE*)pwfx)->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;
|
|
}
|
|
if (sc->sn.numchannels >= 6)
|
|
{
|
|
pwfx->nChannels = 6;
|
|
if (pwfx->wFormatTag == WAVE_FORMAT_EXTENSIBLE)
|
|
((WAVEFORMATEXTENSIBLE*)pwfx)->dwChannelMask = SPEAKER_FRONT_LEFT | SPEAKER_FRONT_RIGHT | SPEAKER_FRONT_CENTER | SPEAKER_LOW_FREQUENCY | SPEAKER_BACK_LEFT | SPEAKER_BACK_RIGHT;
|
|
}
|
|
else if (sc->sn.numchannels >= 4)
|
|
{
|
|
pwfx->nChannels = 4;
|
|
if (pwfx->wFormatTag == WAVE_FORMAT_EXTENSIBLE)
|
|
((WAVEFORMATEXTENSIBLE*)pwfx)->dwChannelMask = SPEAKER_FRONT_LEFT | SPEAKER_FRONT_RIGHT | SPEAKER_BACK_LEFT | SPEAKER_BACK_RIGHT;
|
|
}
|
|
else if (sc->sn.numchannels >= 2)
|
|
{
|
|
pwfx->nChannels = 2;
|
|
if (pwfx->wFormatTag == WAVE_FORMAT_EXTENSIBLE)
|
|
((WAVEFORMATEXTENSIBLE*)pwfx)->dwChannelMask = SPEAKER_FRONT_LEFT | SPEAKER_FRONT_RIGHT;
|
|
}
|
|
else
|
|
{
|
|
pwfx->nChannels = 1;
|
|
if (pwfx->wFormatTag == WAVE_FORMAT_EXTENSIBLE)
|
|
((WAVEFORMATEXTENSIBLE*)pwfx)->dwChannelMask = SPEAKER_FRONT_CENTER;
|
|
}
|
|
pwfx->nBlockAlign = pwfx->wBitsPerSample/8 * pwfx->nChannels;
|
|
pwfx->nAvgBytesPerSec = pwfx->nSamplesPerSec * pwfx->nBlockAlign;
|
|
}
|
|
|
|
if (!exclusive)
|
|
{
|
|
WAVEFORMATEX *pwfx2;
|
|
if (SUCCEEDED(dev->lpVtbl->IsFormatSupported(dev, exclusive?AUDCLNT_SHAREMODE_EXCLUSIVE:AUDCLNT_SHAREMODE_SHARED, pwfx, &pwfx2)))
|
|
{
|
|
if (pwfx2)
|
|
{
|
|
CoTaskMemFree(pwfx);
|
|
pwfx = pwfx2;
|
|
}
|
|
}
|
|
|
|
if (!WASAPI_AcceptableFormat(sc, dev, pwfx))
|
|
return false;
|
|
}
|
|
else
|
|
{
|
|
if (FAILED(dev->lpVtbl->IsFormatSupported(dev, exclusive?AUDCLNT_SHAREMODE_EXCLUSIVE:AUDCLNT_SHAREMODE_SHARED, pwfx, NULL)))
|
|
{
|
|
//try to switch over to 16bit pcm
|
|
pwfx->wBitsPerSample = 16;
|
|
pwfx->nBlockAlign = pwfx->wBitsPerSample/8 * pwfx->nChannels;
|
|
pwfx->nAvgBytesPerSec = pwfx->nSamplesPerSec * pwfx->nBlockAlign;
|
|
if (pwfx->wFormatTag == WAVE_FORMAT_EXTENSIBLE)
|
|
{
|
|
((WAVEFORMATEXTENSIBLE*)pwfx)->SubFormat = KSDATAFORMAT_SUBTYPE_PCM;
|
|
((WAVEFORMATEXTENSIBLE*)pwfx)->Samples.wValidBitsPerSample = pwfx->wBitsPerSample;
|
|
}
|
|
|
|
if (FAILED(dev->lpVtbl->IsFormatSupported(dev, exclusive?AUDCLNT_SHAREMODE_EXCLUSIVE:AUDCLNT_SHAREMODE_SHARED, pwfx, NULL)))
|
|
{
|
|
//try to switch over to 24bit pcm (although with no more 16bit audio)
|
|
/* pwfx->wBitsPerSample = 24;
|
|
pwfx->nBlockAlign = pwfx->wBitsPerSample/8 * pwfx->nChannels;
|
|
pwfx->nAvgBytesPerSec = pwfx->nSamplesPerSec * pwfx->nBlockAlign;
|
|
if (pwfx->wFormatTag == WAVE_FORMAT_EXTENSIBLE)
|
|
((WAVEFORMATEXTENSIBLE*)pwfx)->Samples.wValidBitsPerSample = 16;
|
|
if (FAILED(dev->lpVtbl->IsFormatSupported(dev, exclusive?AUDCLNT_SHAREMODE_EXCLUSIVE:AUDCLNT_SHAREMODE_SHARED, pwfx, NULL)))
|
|
*/
|
|
{
|
|
Con_Printf("WASAPI: IsFormatSupported failed\n");
|
|
return false;
|
|
}
|
|
}
|
|
}
|
|
|
|
if (!WASAPI_AcceptableFormat(sc, dev, pwfx))
|
|
return false;
|
|
}
|
|
|
|
*ret = pwfx;
|
|
return true;
|
|
}
|
|
|
|
static int WASAPI_Thread(void *arg)
|
|
{
|
|
soundcardinfo_t *sc = arg;
|
|
qboolean inited = false;
|
|
|
|
// REFERENCE_TIME hnsRequestedDuration = REFTIMES_PER_SEC;
|
|
IMMDeviceEnumerator *pEnumerator = NULL;
|
|
IMMDevice *pDevice = NULL;
|
|
IAudioClient *pAudioClient = NULL;
|
|
IAudioRenderClient *pRenderClient = NULL;
|
|
UINT32 bufferFrameCount;
|
|
HANDLE hEvent = NULL;
|
|
WAVEFORMATEX *pwfx;
|
|
|
|
qboolean exclusive = Cvar_Get("wasapi_exclusive", "1", 0, "WASAPI audio output")->ival;
|
|
|
|
void *cond = sc->handle;
|
|
|
|
//main thread will wait for us to finish initing, so lets do that...
|
|
CoInitialize(NULL);
|
|
if (SUCCEEDED(CoCreateInstance(&CLSID_MMDeviceEnumerator, NULL, CLSCTX_ALL, &IID_IMMDeviceEnumerator, (void**)&pEnumerator)))
|
|
if (SUCCEEDED(pEnumerator->lpVtbl->GetDefaultAudioEndpoint(pEnumerator, eRender, eConsole, &pDevice)))
|
|
if (SUCCEEDED(pDevice->lpVtbl->Activate(pDevice, &IID_IAudioClient, CLSCTX_ALL, NULL, (void**)&pAudioClient)))
|
|
{
|
|
if (!WASAPI_DetermineFormat(sc, pAudioClient, exclusive, &pwfx))
|
|
{
|
|
Con_Printf("WASAPI: unable to determine mutually supported audio format\n");
|
|
}
|
|
else
|
|
{
|
|
|
|
|
|
if (sc->sn.samplebits && (!snd_speed || sc->sn.speed == snd_speed))
|
|
{
|
|
HRESULT hr;
|
|
REFERENCE_TIME buffersize = REFTIMES_PER_SEC * Cvar_Get("wasapi_buffersize", "0.01", 0, "WASAPI audio output")->ival;
|
|
if (exclusive)
|
|
pAudioClient->lpVtbl->GetDevicePeriod(pAudioClient, NULL, &buffersize);
|
|
|
|
hr = pAudioClient->lpVtbl->Initialize(pAudioClient, exclusive?AUDCLNT_SHAREMODE_EXCLUSIVE:AUDCLNT_SHAREMODE_SHARED, AUDCLNT_STREAMFLAGS_EVENTCALLBACK, buffersize, (exclusive?buffersize:0), pwfx, NULL);
|
|
|
|
if (hr == AUDCLNT_E_BUFFER_SIZE_NOT_ALIGNED)
|
|
{ //this is stupid, but does what the documentation says should be done.
|
|
if (SUCCEEDED(pAudioClient->lpVtbl->GetBufferSize(pAudioClient, &bufferFrameCount)))
|
|
{
|
|
if (pAudioClient)
|
|
pAudioClient->lpVtbl->Release(pAudioClient);
|
|
pAudioClient = NULL;
|
|
if (SUCCEEDED(pDevice->lpVtbl->Activate(pDevice, &IID_IAudioClient, CLSCTX_ALL, NULL, (void**)&pAudioClient)))
|
|
{
|
|
buffersize = (REFERENCE_TIME)((10000.0 * 1000 / pwfx->nSamplesPerSec * bufferFrameCount) + 0.5);
|
|
hr = pAudioClient->lpVtbl->Initialize(pAudioClient, exclusive?AUDCLNT_SHAREMODE_EXCLUSIVE:AUDCLNT_SHAREMODE_SHARED, AUDCLNT_STREAMFLAGS_EVENTCALLBACK, buffersize, (exclusive?buffersize:0), pwfx, NULL);
|
|
}
|
|
}
|
|
}
|
|
|
|
if (SUCCEEDED(hr))
|
|
{
|
|
hEvent = CreateEvent(NULL, FALSE, FALSE, NULL);
|
|
if (hEvent)
|
|
{
|
|
pAudioClient->lpVtbl->SetEventHandle(pAudioClient, hEvent);
|
|
if (SUCCEEDED(pAudioClient->lpVtbl->GetBufferSize(pAudioClient, &bufferFrameCount)))
|
|
if (SUCCEEDED(pAudioClient->lpVtbl->GetService(pAudioClient, &IID_IAudioRenderClient, (void**)&pRenderClient)))
|
|
inited = true;
|
|
}
|
|
}
|
|
else
|
|
{
|
|
switch(hr)
|
|
{
|
|
case AUDCLNT_E_UNSUPPORTED_FORMAT:
|
|
Con_Printf("WASAPI Initialize: AUDCLNT_E_UNSUPPORTED_FORMAT\n");
|
|
break;
|
|
case AUDCLNT_E_EXCLUSIVE_MODE_NOT_ALLOWED:
|
|
Con_Printf("WASAPI Initialize: AUDCLNT_E_EXCLUSIVE_MODE_NOT_ALLOWED\n");
|
|
break;
|
|
case AUDCLNT_E_EXCLUSIVE_MODE_ONLY:
|
|
Con_Printf("WASAPI Initialize: AUDCLNT_E_EXCLUSIVE_MODE_ONLY\n");
|
|
break;
|
|
case AUDCLNT_E_DEVICE_IN_USE:
|
|
Con_Printf("WASAPI Initialize: AUDCLNT_E_DEVICE_IN_USE\n");
|
|
break;
|
|
case AUDCLNT_E_BUFFER_SIZE_NOT_ALIGNED:
|
|
Con_Printf("WASAPI Initialize: AUDCLNT_E_BUFFER_SIZE_NOT_ALIGNED\n");
|
|
break;
|
|
case E_INVALIDARG:
|
|
Con_Printf("WASAPI Initialize: E_INVALIDARG\n");
|
|
break;
|
|
default:
|
|
Con_Printf("pAudioClient->lpVtbl->Initialize failed (%x)\n", (unsigned int)hr);
|
|
}
|
|
}
|
|
}
|
|
|
|
CoTaskMemFree(pwfx);
|
|
}
|
|
}
|
|
|
|
if (inited)
|
|
sc->Shutdown = WASAPI_Shutdown;
|
|
else
|
|
Con_Printf("Unable to initialise WASAPI\n");
|
|
sc->Lock = WASAPI_Lock;
|
|
sc->Unlock = WASAPI_Unlock;
|
|
sc->Submit = WASAPI_Submit;
|
|
sc->GetDMAPos = WASAPI_GetDMAPos;
|
|
|
|
//wake up the main thread now that we know if it worked.
|
|
Sys_ConditionSignal(cond);
|
|
|
|
//extra crap to get the OS to favour waking us up on demand.
|
|
{
|
|
HANDLE (WINAPI *pAvSetMmThreadCharacteristics)(LPCTSTR TaskName, LPDWORD TaskIndex);
|
|
dllfunction_t funcs[] = {{(void*)&pAvSetMmThreadCharacteristics, "AvSetMmThreadCharacteristics"}, {NULL}};
|
|
DWORD taskIndex = 0;
|
|
|
|
if (Sys_LoadLibrary("avrt.dll", funcs))
|
|
pAvSetMmThreadCharacteristics(TEXT("Pro Audio"), &taskIndex);
|
|
}
|
|
|
|
while(sc->Shutdown != NULL)
|
|
{
|
|
UINT32 numFramesPadding = 0;
|
|
if (exclusive || SUCCEEDED(pAudioClient->lpVtbl->GetCurrentPadding(pAudioClient, &numFramesPadding)))
|
|
{
|
|
UINT32 numFramesAvailable = bufferFrameCount - numFramesPadding;
|
|
BYTE *pData;
|
|
if (SUCCEEDED(pRenderClient->lpVtbl->GetBuffer(pRenderClient, numFramesAvailable, &pData)))
|
|
{
|
|
sc->sn.buffer = pData;
|
|
sc->sn.samples = numFramesAvailable * sc->sn.numchannels;
|
|
sc->samplequeue = sc->sn.samples;
|
|
S_MixerThread(sc);
|
|
sc->snd_sent += numFramesAvailable * sc->sn.numchannels;
|
|
|
|
pRenderClient->lpVtbl->ReleaseBuffer(pRenderClient, numFramesAvailable, 0);
|
|
}
|
|
}
|
|
|
|
if (inited)
|
|
{
|
|
pAudioClient->lpVtbl->Start(pAudioClient);
|
|
inited = false;
|
|
}
|
|
|
|
if (hEvent && WaitForSingleObject(hEvent, 2000) != WAIT_OBJECT_0)
|
|
{
|
|
Con_Printf("WASAPI timeout\n");
|
|
break;
|
|
}
|
|
|
|
/* Quote:
|
|
On NT (Win2K and XP) the cursors in SW buffers (and HW buffers on some devices) move in 10ms increments, so calling GetCurrentPosition() every 10ms is ideal.
|
|
Calling it more often than every 5ms will cause some perf degradation.
|
|
*/
|
|
// Sleep(10);
|
|
}
|
|
|
|
if (inited)
|
|
pAudioClient->lpVtbl->Stop(pAudioClient);
|
|
|
|
if (pRenderClient)
|
|
pRenderClient->lpVtbl->Release(pRenderClient);
|
|
if (pAudioClient)
|
|
pAudioClient->lpVtbl->Release(pAudioClient);
|
|
if (pDevice)
|
|
pDevice->lpVtbl->Release(pDevice);
|
|
if (pEnumerator)
|
|
pEnumerator->lpVtbl->Release(pEnumerator);
|
|
return 0;
|
|
}
|
|
|
|
static qboolean QDECL WASAPI_InitCard (soundcardinfo_t *sc, const char *cardname)
|
|
{
|
|
void *cond;
|
|
if (cardname && *cardname)
|
|
return false; //we don't support explicit devices at this time
|
|
|
|
Q_strncpyz(sc->name, cardname?cardname:"", sizeof(sc->name));
|
|
|
|
sc->selfpainting = true;
|
|
sc->handle = cond = Sys_CreateConditional();
|
|
Sys_LockConditional(cond);
|
|
sc->thread = Sys_CreateThread("wasapimixer", WASAPI_Thread, sc, THREADP_NORMAL, 0);
|
|
if (!sc->thread)
|
|
{
|
|
Con_Printf ("Unable to create sound mixing thread\n");
|
|
return false;
|
|
}
|
|
// MessageBox(0,"main thread waiting", "...", 0);
|
|
|
|
//wait for the thread to finish (along with all its error con printfs etc
|
|
if (!Sys_ConditionWait(cond))
|
|
Con_Printf ("Looks like the sound thread isn't starting up\n");
|
|
Sys_UnlockConditional(cond);
|
|
Sys_DestroyConditional(cond);
|
|
COM_MainThreadWork(); //flush any prints from the worker thread, so that things make sense
|
|
|
|
if (sc->Shutdown == NULL)
|
|
{
|
|
Sys_WaitOnThread(sc->thread);
|
|
sc->thread = NULL;
|
|
return false;
|
|
}
|
|
return true;
|
|
}
|
|
|
|
|
|
/*I HATE C++ APIS! THEY'RE ANNOYING AS HELL*/
|
|
static void WASAPI_DeviceChanged(void *ctx, void *data, size_t a, size_t b)
|
|
{
|
|
S_EnumerateDevices();
|
|
if (data)
|
|
{
|
|
char *msg = data;
|
|
Con_Printf("%s", msg);
|
|
Cbuf_AddText("\nsnd_restart\n", RESTRICT_LOCAL);
|
|
}
|
|
}
|
|
static HRESULT STDMETHODCALLTYPE WASAPI_Notifications_QueryInterface(IMMNotificationClient * This,REFIID riid,void **ppvObject) {*ppvObject = NULL;return E_NOINTERFACE;}
|
|
static ULONG STDMETHODCALLTYPE WASAPI_Notifications_AddRef(IMMNotificationClient * This) {return 1;}
|
|
static ULONG STDMETHODCALLTYPE WASAPI_Notifications_Release(IMMNotificationClient * This) {return 1;}
|
|
static HRESULT STDMETHODCALLTYPE WASAPI_Notifications_OnDeviceStateChanged(IMMNotificationClient * This,LPCWSTR pwstrDeviceId,DWORD dwNewState) {COM_AddWork(WG_MAIN, WASAPI_DeviceChanged, NULL, NULL, 0, 0); return S_OK;}
|
|
static HRESULT STDMETHODCALLTYPE WASAPI_Notifications_OnDeviceAdded(IMMNotificationClient * This,LPCWSTR pwstrDeviceId) {COM_AddWork(WG_MAIN, WASAPI_DeviceChanged, NULL, NULL, 0, 0); return S_OK;}
|
|
static HRESULT STDMETHODCALLTYPE WASAPI_Notifications_OnDeviceRemoved(IMMNotificationClient * This,LPCWSTR pwstrDeviceId) {COM_AddWork(WG_MAIN, WASAPI_DeviceChanged, NULL, NULL, 0, 0); return S_OK;}
|
|
static HRESULT STDMETHODCALLTYPE WASAPI_Notifications_OnDefaultDeviceChanged(IMMNotificationClient * This,EDataFlow flow,ERole role,LPCWSTR pwstrDefaultDeviceId) {COM_AddWork(WG_MAIN, WASAPI_DeviceChanged, NULL, "Default audio device changed. Restarting audio.\n", 0, 0); return S_OK;}
|
|
static HRESULT STDMETHODCALLTYPE WASAPI_Notifications_OnPropertyValueChanged(IMMNotificationClient * This,LPCWSTR pwstrDeviceId,const PROPERTYKEY key) {COM_AddWork(WG_MAIN, WASAPI_DeviceChanged, NULL, NULL, 0, 0); return S_OK;}
|
|
static CONST_VTBL IMMNotificationClientVtbl WASAPI_NotificationsVtbl =
|
|
{
|
|
WASAPI_Notifications_QueryInterface,
|
|
WASAPI_Notifications_AddRef,
|
|
WASAPI_Notifications_Release,
|
|
WASAPI_Notifications_OnDeviceStateChanged,
|
|
WASAPI_Notifications_OnDeviceAdded,
|
|
WASAPI_Notifications_OnDeviceRemoved,
|
|
WASAPI_Notifications_OnDefaultDeviceChanged,
|
|
WASAPI_Notifications_OnPropertyValueChanged
|
|
};
|
|
static IMMNotificationClient WASAPI_Notifications =
|
|
{
|
|
&WASAPI_NotificationsVtbl
|
|
};
|
|
|
|
static qboolean QDECL WASAPI_Enumerate (void (QDECL *callback) (const char *drivername, const char *devicecode, const char *readablename))
|
|
{
|
|
FORCE_DEFINE_PROPERTYKEY(PKEY_Device_FriendlyName, 0xa45c254e, 0xdf1c, 0x4efd, 0x80, 0x20, 0x67, 0xd1, 0x46, 0xa8, 0x50, 0xe0, 14); // DEVPROP_TYPE_STRING
|
|
|
|
static IMMDeviceEnumerator *pEnumerator = NULL;
|
|
IMMDeviceCollection *pCollection = NULL;
|
|
CoInitialize(NULL);
|
|
if (!pEnumerator)
|
|
{
|
|
if (SUCCEEDED(CoCreateInstance(&CLSID_MMDeviceEnumerator, NULL, CLSCTX_ALL, &IID_IMMDeviceEnumerator, (void**)&pEnumerator)))
|
|
{
|
|
pEnumerator->lpVtbl->RegisterEndpointNotificationCallback(pEnumerator, &WASAPI_Notifications);
|
|
}
|
|
}
|
|
|
|
if (pEnumerator)
|
|
{
|
|
if (SUCCEEDED(pEnumerator->lpVtbl->EnumAudioEndpoints(pEnumerator, eRender, DEVICE_STATE_ACTIVE, &pCollection)))
|
|
{
|
|
IMMDevice *pEndpoint;
|
|
IPropertyStore *pProps;
|
|
LPWSTR pwszID;
|
|
UINT count, i;
|
|
if (FAILED(pCollection->lpVtbl->GetCount(pCollection, &count)))
|
|
count = 0;
|
|
for (i = 0; i < count; i++)
|
|
{
|
|
if (SUCCEEDED(pCollection->lpVtbl->Item(pCollection, i, &pEndpoint)))
|
|
{
|
|
if (SUCCEEDED(pEndpoint->lpVtbl->GetId(pEndpoint, &pwszID)))
|
|
{
|
|
if (SUCCEEDED(pEndpoint->lpVtbl->OpenPropertyStore(pEndpoint, STGM_READ, &pProps)))
|
|
{
|
|
PROPVARIANT varName;
|
|
PropVariantInit(&varName);
|
|
if (SUCCEEDED(pProps->lpVtbl->GetValue(pProps, &PKEY_Device_FriendlyName, &varName)))
|
|
{
|
|
char nicename[256];
|
|
char internalname[256];
|
|
strcpy(nicename, AUDIODRIVERNAME ": ");
|
|
narrowen(nicename+strlen(AUDIODRIVERNAME)+2, sizeof(nicename)-(strlen(AUDIODRIVERNAME)+2), varName.pwszVal);
|
|
narrowen(internalname, sizeof(internalname), pwszID);
|
|
callback(AUDIODRIVERNAME, internalname, nicename);
|
|
}
|
|
PropVariantClear(&varName);
|
|
pProps->lpVtbl->Release(pProps);
|
|
}
|
|
CoTaskMemFree(pwszID);
|
|
}
|
|
pEndpoint->lpVtbl->Release(pEndpoint);
|
|
}
|
|
}
|
|
|
|
pCollection->lpVtbl->Release(pCollection);
|
|
}
|
|
|
|
// pEnumerator->lpVtbl->Release(pEnumerator);
|
|
// pEnumerator = NULL;
|
|
return true;
|
|
}
|
|
return true; //if we couldn't enumerate stuff, we won't be able to initialise anything anyway, so there's no point in doing any default device crap
|
|
}
|
|
|
|
sounddriver_t WASAPI_Output =
|
|
{
|
|
AUDIODRIVERNAME,
|
|
WASAPI_InitCard,
|
|
WASAPI_Enumerate
|
|
};
|
|
|
|
#endif |