|
Packit |
1fb8d4 |
|
|
Packit |
1fb8d4 |
#include "wf_wasapi.h"
|
|
Packit |
1fb8d4 |
#include "wf_info.h"
|
|
Packit |
1fb8d4 |
|
|
Packit |
1fb8d4 |
#include <initguid.h>
|
|
Packit |
1fb8d4 |
#include <mmdeviceapi.h>
|
|
Packit |
1fb8d4 |
#include <Functiondiscoverykeys_devpkey.h>
|
|
Packit |
1fb8d4 |
#include <Audioclient.h>
|
|
Packit |
1fb8d4 |
|
|
Packit |
1fb8d4 |
#define TAG SERVER_TAG("windows")
|
|
Packit |
1fb8d4 |
|
|
Packit |
1fb8d4 |
//#define REFTIMES_PER_SEC 10000000
|
|
Packit |
1fb8d4 |
//#define REFTIMES_PER_MILLISEC 10000
|
|
Packit |
1fb8d4 |
|
|
Packit Service |
5a9772 |
#define REFTIMES_PER_SEC 100000
|
|
Packit Service |
5a9772 |
#define REFTIMES_PER_MILLISEC 100
|
|
Packit |
1fb8d4 |
|
|
Packit |
1fb8d4 |
//#define REFTIMES_PER_SEC 50000
|
|
Packit |
1fb8d4 |
//#define REFTIMES_PER_MILLISEC 50
|
|
Packit |
1fb8d4 |
|
|
Packit Service |
5a9772 |
DEFINE_GUID(CLSID_MMDeviceEnumerator, 0xBCDE0395, 0xE52F, 0x467C, 0x8E, 0x3D, 0xC4, 0x57, 0x92,
|
|
Packit Service |
5a9772 |
0x91, 0x69, 0x2E);
|
|
Packit Service |
5a9772 |
DEFINE_GUID(IID_IMMDeviceEnumerator, 0xA95664D2, 0x9614, 0x4F35, 0xA7, 0x46, 0xDE, 0x8D, 0xB6, 0x36,
|
|
Packit Service |
5a9772 |
0x17, 0xE6);
|
|
Packit Service |
5a9772 |
DEFINE_GUID(IID_IAudioClient, 0x1cb9ad4c, 0xdbfa, 0x4c32, 0xb1, 0x78, 0xc2, 0xf5, 0x68, 0xa7, 0x03,
|
|
Packit Service |
5a9772 |
0xb2);
|
|
Packit Service |
5a9772 |
DEFINE_GUID(IID_IAudioCaptureClient, 0xc8adbd64, 0xe71e, 0x48a0, 0xa4, 0xde, 0x18, 0x5c, 0x39, 0x5c,
|
|
Packit Service |
5a9772 |
0xd3, 0x17);
|
|
Packit |
1fb8d4 |
|
|
Packit |
1fb8d4 |
LPWSTR devStr = NULL;
|
|
Packit |
1fb8d4 |
wfPeerContext* latestPeer = NULL;
|
|
Packit |
1fb8d4 |
|
|
Packit |
1fb8d4 |
int wf_rdpsnd_set_latest_peer(wfPeerContext* peer)
|
|
Packit |
1fb8d4 |
{
|
|
Packit |
1fb8d4 |
latestPeer = peer;
|
|
Packit |
1fb8d4 |
return 0;
|
|
Packit |
1fb8d4 |
}
|
|
Packit |
1fb8d4 |
|
|
Packit |
1fb8d4 |
int wf_wasapi_activate(RdpsndServerContext* context)
|
|
Packit |
1fb8d4 |
{
|
|
Packit Service |
5a9772 |
wchar_t* pattern = L"Stereo Mix";
|
|
Packit |
1fb8d4 |
HANDLE hThread;
|
|
Packit |
1fb8d4 |
|
|
Packit |
1fb8d4 |
wf_wasapi_get_device_string(pattern, &devStr);
|
|
Packit |
1fb8d4 |
|
|
Packit |
1fb8d4 |
if (devStr == NULL)
|
|
Packit |
1fb8d4 |
{
|
|
Packit |
1fb8d4 |
WLog_ERR(TAG, "Failed to match for output device! Disabling rdpsnd.");
|
|
Packit |
1fb8d4 |
return 1;
|
|
Packit |
1fb8d4 |
}
|
|
Packit |
1fb8d4 |
|
|
Packit |
1fb8d4 |
WLog_DBG(TAG, "RDPSND (WASAPI) Activated");
|
|
Packit |
1fb8d4 |
if (!(hThread = CreateThread(NULL, 0, wf_rdpsnd_wasapi_thread, latestPeer, 0, NULL)))
|
|
Packit |
1fb8d4 |
{
|
|
Packit |
1fb8d4 |
WLog_ERR(TAG, "CreateThread failed");
|
|
Packit |
1fb8d4 |
return 1;
|
|
Packit |
1fb8d4 |
}
|
|
Packit |
1fb8d4 |
CloseHandle(hThread);
|
|
Packit |
1fb8d4 |
|
|
Packit |
1fb8d4 |
return 0;
|
|
Packit |
1fb8d4 |
}
|
|
Packit |
1fb8d4 |
|
|
Packit Service |
5a9772 |
int wf_wasapi_get_device_string(LPWSTR pattern, LPWSTR* deviceStr)
|
|
Packit |
1fb8d4 |
{
|
|
Packit |
1fb8d4 |
HRESULT hr;
|
|
Packit Service |
5a9772 |
IMMDeviceEnumerator* pEnumerator = NULL;
|
|
Packit Service |
5a9772 |
IMMDeviceCollection* pCollection = NULL;
|
|
Packit Service |
5a9772 |
IMMDevice* pEndpoint = NULL;
|
|
Packit Service |
5a9772 |
IPropertyStore* pProps = NULL;
|
|
Packit |
1fb8d4 |
LPWSTR pwszID = NULL;
|
|
Packit |
1fb8d4 |
unsigned int count, i;
|
|
Packit |
1fb8d4 |
|
|
Packit |
1fb8d4 |
CoInitialize(NULL);
|
|
Packit Service |
5a9772 |
hr = CoCreateInstance(&CLSID_MMDeviceEnumerator, NULL, CLSCTX_ALL, &IID_IMMDeviceEnumerator,
|
|
Packit Service |
5a9772 |
(void**)&pEnumerator);
|
|
Packit |
1fb8d4 |
if (FAILED(hr))
|
|
Packit |
1fb8d4 |
{
|
|
Packit |
1fb8d4 |
WLog_ERR(TAG, "Failed to cocreate device enumerator");
|
|
Packit |
1fb8d4 |
exit(1);
|
|
Packit |
1fb8d4 |
}
|
|
Packit |
1fb8d4 |
|
|
Packit Service |
5a9772 |
hr = pEnumerator->lpVtbl->EnumAudioEndpoints(pEnumerator, eCapture, DEVICE_STATE_ACTIVE,
|
|
Packit Service |
5a9772 |
&pCollection);
|
|
Packit Service |
5a9772 |
if (FAILED(hr))
|
|
Packit |
1fb8d4 |
{
|
|
Packit |
1fb8d4 |
WLog_ERR(TAG, "Failed to create endpoint collection");
|
|
Packit |
1fb8d4 |
exit(1);
|
|
Packit |
1fb8d4 |
}
|
|
Packit |
1fb8d4 |
|
|
Packit |
1fb8d4 |
pCollection->lpVtbl->GetCount(pCollection, &count);
|
|
Packit |
1fb8d4 |
WLog_INFO(TAG, "Num endpoints: %u", count);
|
|
Packit |
1fb8d4 |
|
|
Packit |
1fb8d4 |
if (count == 0)
|
|
Packit |
1fb8d4 |
{
|
|
Packit |
1fb8d4 |
WLog_ERR(TAG, "No endpoints!");
|
|
Packit |
1fb8d4 |
exit(1);
|
|
Packit |
1fb8d4 |
}
|
|
Packit |
1fb8d4 |
|
|
Packit |
1fb8d4 |
for (i = 0; i < count; ++i)
|
|
Packit |
1fb8d4 |
{
|
|
Packit |
1fb8d4 |
PROPVARIANT nameVar;
|
|
Packit |
1fb8d4 |
PropVariantInit(&nameVar);
|
|
Packit |
1fb8d4 |
|
|
Packit |
1fb8d4 |
hr = pCollection->lpVtbl->Item(pCollection, i, &pEndpoint);
|
|
Packit Service |
5a9772 |
if (FAILED(hr))
|
|
Packit |
1fb8d4 |
{
|
|
Packit |
1fb8d4 |
WLog_ERR(TAG, "Failed to get endpoint %u", i);
|
|
Packit |
1fb8d4 |
exit(1);
|
|
Packit |
1fb8d4 |
}
|
|
Packit |
1fb8d4 |
|
|
Packit |
1fb8d4 |
hr = pEndpoint->lpVtbl->GetId(pEndpoint, &pwszID);
|
|
Packit Service |
5a9772 |
if (FAILED(hr))
|
|
Packit |
1fb8d4 |
{
|
|
Packit |
1fb8d4 |
WLog_ERR(TAG, "Failed to get endpoint ID");
|
|
Packit |
1fb8d4 |
exit(1);
|
|
Packit |
1fb8d4 |
}
|
|
Packit |
1fb8d4 |
|
|
Packit |
1fb8d4 |
hr = pEndpoint->lpVtbl->OpenPropertyStore(pEndpoint, STGM_READ, &pProps);
|
|
Packit Service |
5a9772 |
if (FAILED(hr))
|
|
Packit |
1fb8d4 |
{
|
|
Packit |
1fb8d4 |
WLog_ERR(TAG, "Failed to open property store");
|
|
Packit |
1fb8d4 |
exit(1);
|
|
Packit |
1fb8d4 |
}
|
|
Packit |
1fb8d4 |
|
|
Packit |
1fb8d4 |
hr = pProps->lpVtbl->GetValue(pProps, &PKEY_Device_FriendlyName, &nameVar);
|
|
Packit Service |
5a9772 |
if (FAILED(hr))
|
|
Packit |
1fb8d4 |
{
|
|
Packit |
1fb8d4 |
WLog_ERR(TAG, "Failed to get device friendly name");
|
|
Packit |
1fb8d4 |
exit(1);
|
|
Packit |
1fb8d4 |
}
|
|
Packit |
1fb8d4 |
|
|
Packit Service |
5a9772 |
// do this a more reliable way
|
|
Packit |
1fb8d4 |
if (wcscmp(pattern, nameVar.pwszVal) < 0)
|
|
Packit |
1fb8d4 |
{
|
|
Packit |
1fb8d4 |
unsigned int devStrLen;
|
|
Packit |
1fb8d4 |
WLog_INFO(TAG, "Using sound ouput endpoint: [%s] (%s)", nameVar.pwszVal, pwszID);
|
|
Packit Service |
5a9772 |
// WLog_INFO(TAG, "matched %d characters", wcscmp(pattern, nameVar.pwszVal);
|
|
Packit |
1fb8d4 |
devStrLen = wcslen(pwszID);
|
|
Packit Service |
5a9772 |
*deviceStr = (LPWSTR)calloc(devStrLen + 1, 2);
|
|
Packit |
1fb8d4 |
if (!deviceStr)
|
|
Packit |
1fb8d4 |
return -1;
|
|
Packit Service |
5a9772 |
wcscpy_s(*deviceStr, devStrLen + 1, pwszID);
|
|
Packit |
1fb8d4 |
}
|
|
Packit |
1fb8d4 |
CoTaskMemFree(pwszID);
|
|
Packit |
1fb8d4 |
pwszID = NULL;
|
|
Packit |
1fb8d4 |
PropVariantClear(&nameVar);
|
|
Packit |
1fb8d4 |
|
|
Packit |
1fb8d4 |
pProps->lpVtbl->Release(pProps);
|
|
Packit |
1fb8d4 |
pProps = NULL;
|
|
Packit |
1fb8d4 |
|
|
Packit |
1fb8d4 |
pEndpoint->lpVtbl->Release(pEndpoint);
|
|
Packit |
1fb8d4 |
pEndpoint = NULL;
|
|
Packit |
1fb8d4 |
}
|
|
Packit |
1fb8d4 |
|
|
Packit |
1fb8d4 |
pCollection->lpVtbl->Release(pCollection);
|
|
Packit |
1fb8d4 |
pCollection = NULL;
|
|
Packit |
1fb8d4 |
|
|
Packit |
1fb8d4 |
pEnumerator->lpVtbl->Release(pEnumerator);
|
|
Packit |
1fb8d4 |
pEnumerator = NULL;
|
|
Packit |
1fb8d4 |
CoUninitialize();
|
|
Packit |
1fb8d4 |
|
|
Packit |
1fb8d4 |
return 0;
|
|
Packit |
1fb8d4 |
}
|
|
Packit |
1fb8d4 |
|
|
Packit |
1fb8d4 |
static DWORD WINAPI wf_rdpsnd_wasapi_thread(LPVOID lpParam)
|
|
Packit |
1fb8d4 |
{
|
|
Packit Service |
5a9772 |
IMMDeviceEnumerator* pEnumerator = NULL;
|
|
Packit Service |
5a9772 |
IMMDevice* pDevice = NULL;
|
|
Packit Service |
5a9772 |
IAudioClient* pAudioClient = NULL;
|
|
Packit Service |
5a9772 |
IAudioCaptureClient* pCaptureClient = NULL;
|
|
Packit Service |
5a9772 |
WAVEFORMATEX* pwfx = NULL;
|
|
Packit |
1fb8d4 |
HRESULT hr;
|
|
Packit |
1fb8d4 |
REFERENCE_TIME hnsRequestedDuration = REFTIMES_PER_SEC;
|
|
Packit |
1fb8d4 |
REFERENCE_TIME hnsActualDuration;
|
|
Packit |
1fb8d4 |
UINT32 bufferFrameCount;
|
|
Packit |
1fb8d4 |
UINT32 numFramesAvailable;
|
|
Packit |
1fb8d4 |
UINT32 packetLength = 0;
|
|
Packit |
1fb8d4 |
UINT32 dCount = 0;
|
|
Packit Service |
5a9772 |
BYTE* pData;
|
|
Packit |
1fb8d4 |
|
|
Packit |
1fb8d4 |
wfPeerContext* context;
|
|
Packit |
1fb8d4 |
wfInfo* wfi;
|
|
Packit |
1fb8d4 |
|
|
Packit |
1fb8d4 |
wfi = wf_info_get_instance();
|
|
Packit |
1fb8d4 |
context = (wfPeerContext*)lpParam;
|
|
Packit |
1fb8d4 |
|
|
Packit |
1fb8d4 |
CoInitialize(NULL);
|
|
Packit Service |
5a9772 |
hr = CoCreateInstance(&CLSID_MMDeviceEnumerator, NULL, CLSCTX_ALL, &IID_IMMDeviceEnumerator,
|
|
Packit Service |
5a9772 |
(void**)&pEnumerator);
|
|
Packit |
1fb8d4 |
if (FAILED(hr))
|
|
Packit |
1fb8d4 |
{
|
|
Packit |
1fb8d4 |
WLog_ERR(TAG, "Failed to cocreate device enumerator");
|
|
Packit |
1fb8d4 |
exit(1);
|
|
Packit |
1fb8d4 |
}
|
|
Packit |
1fb8d4 |
|
|
Packit |
1fb8d4 |
hr = pEnumerator->lpVtbl->GetDevice(pEnumerator, devStr, &pDevice);
|
|
Packit |
1fb8d4 |
if (FAILED(hr))
|
|
Packit |
1fb8d4 |
{
|
|
Packit |
1fb8d4 |
WLog_ERR(TAG, "Failed to cocreate get device");
|
|
Packit |
1fb8d4 |
exit(1);
|
|
Packit |
1fb8d4 |
}
|
|
Packit |
1fb8d4 |
|
|
Packit Service |
5a9772 |
hr = pDevice->lpVtbl->Activate(pDevice, &IID_IAudioClient, CLSCTX_ALL, NULL,
|
|
Packit Service |
5a9772 |
(void**)&pAudioClient);
|
|
Packit |
1fb8d4 |
if (FAILED(hr))
|
|
Packit |
1fb8d4 |
{
|
|
Packit |
1fb8d4 |
WLog_ERR(TAG, "Failed to activate audio client");
|
|
Packit |
1fb8d4 |
exit(1);
|
|
Packit |
1fb8d4 |
}
|
|
Packit |
1fb8d4 |
|
|
Packit |
1fb8d4 |
hr = pAudioClient->lpVtbl->GetMixFormat(pAudioClient, &pwfx);
|
|
Packit |
1fb8d4 |
if (FAILED(hr))
|
|
Packit |
1fb8d4 |
{
|
|
Packit |
1fb8d4 |
WLog_ERR(TAG, "Failed to get mix format");
|
|
Packit |
1fb8d4 |
exit(1);
|
|
Packit |
1fb8d4 |
}
|
|
Packit |
1fb8d4 |
|
|
Packit |
1fb8d4 |
pwfx->wFormatTag = wfi->agreed_format->wFormatTag;
|
|
Packit |
1fb8d4 |
pwfx->nChannels = wfi->agreed_format->nChannels;
|
|
Packit |
1fb8d4 |
pwfx->nSamplesPerSec = wfi->agreed_format->nSamplesPerSec;
|
|
Packit |
1fb8d4 |
pwfx->nAvgBytesPerSec = wfi->agreed_format->nAvgBytesPerSec;
|
|
Packit |
1fb8d4 |
pwfx->nBlockAlign = wfi->agreed_format->nBlockAlign;
|
|
Packit |
1fb8d4 |
pwfx->wBitsPerSample = wfi->agreed_format->wBitsPerSample;
|
|
Packit |
1fb8d4 |
pwfx->cbSize = wfi->agreed_format->cbSize;
|
|
Packit |
1fb8d4 |
|
|
Packit Service |
5a9772 |
hr = pAudioClient->lpVtbl->Initialize(pAudioClient, AUDCLNT_SHAREMODE_SHARED, 0,
|
|
Packit Service |
5a9772 |
hnsRequestedDuration, 0, pwfx, NULL);
|
|
Packit |
1fb8d4 |
|
|
Packit |
1fb8d4 |
if (FAILED(hr))
|
|
Packit |
1fb8d4 |
{
|
|
Packit |
1fb8d4 |
WLog_ERR(TAG, "Failed to initialize the audio client");
|
|
Packit |
1fb8d4 |
exit(1);
|
|
Packit |
1fb8d4 |
}
|
|
Packit |
1fb8d4 |
|
|
Packit |
1fb8d4 |
hr = pAudioClient->lpVtbl->GetBufferSize(pAudioClient, &bufferFrameCount);
|
|
Packit |
1fb8d4 |
if (FAILED(hr))
|
|
Packit |
1fb8d4 |
{
|
|
Packit |
1fb8d4 |
WLog_ERR(TAG, "Failed to get buffer size");
|
|
Packit |
1fb8d4 |
exit(1);
|
|
Packit |
1fb8d4 |
}
|
|
Packit |
1fb8d4 |
|
|
Packit Service |
5a9772 |
hr = pAudioClient->lpVtbl->GetService(pAudioClient, &IID_IAudioCaptureClient,
|
|
Packit Service |
5a9772 |
(void**)&pCaptureClient);
|
|
Packit |
1fb8d4 |
if (FAILED(hr))
|
|
Packit |
1fb8d4 |
{
|
|
Packit |
1fb8d4 |
WLog_ERR(TAG, "Failed to get the capture client");
|
|
Packit |
1fb8d4 |
exit(1);
|
|
Packit |
1fb8d4 |
}
|
|
Packit |
1fb8d4 |
|
|
Packit |
1fb8d4 |
hnsActualDuration = (UINT32)REFTIMES_PER_SEC * bufferFrameCount / pwfx->nSamplesPerSec;
|
|
Packit |
1fb8d4 |
|
|
Packit |
1fb8d4 |
hr = pAudioClient->lpVtbl->Start(pAudioClient);
|
|
Packit |
1fb8d4 |
if (FAILED(hr))
|
|
Packit |
1fb8d4 |
{
|
|
Packit |
1fb8d4 |
WLog_ERR(TAG, "Failed to start capture");
|
|
Packit |
1fb8d4 |
exit(1);
|
|
Packit |
1fb8d4 |
}
|
|
Packit |
1fb8d4 |
|
|
Packit |
1fb8d4 |
dCount = 0;
|
|
Packit |
1fb8d4 |
|
|
Packit |
1fb8d4 |
while (wfi->snd_stop == FALSE)
|
|
Packit |
1fb8d4 |
{
|
|
Packit |
1fb8d4 |
DWORD flags;
|
|
Packit |
1fb8d4 |
|
|
Packit Service |
5a9772 |
Sleep(hnsActualDuration / REFTIMES_PER_MILLISEC / 2);
|
|
Packit |
1fb8d4 |
|
|
Packit |
1fb8d4 |
hr = pCaptureClient->lpVtbl->GetNextPacketSize(pCaptureClient, &packetLength);
|
|
Packit |
1fb8d4 |
if (FAILED(hr))
|
|
Packit |
1fb8d4 |
{
|
|
Packit |
1fb8d4 |
WLog_ERR(TAG, "Failed to get packet length");
|
|
Packit |
1fb8d4 |
exit(1);
|
|
Packit |
1fb8d4 |
}
|
|
Packit |
1fb8d4 |
|
|
Packit |
1fb8d4 |
while (packetLength != 0)
|
|
Packit |
1fb8d4 |
{
|
|
Packit Service |
5a9772 |
hr = pCaptureClient->lpVtbl->GetBuffer(pCaptureClient, &pData, &numFramesAvailable,
|
|
Packit Service |
5a9772 |
&flags, NULL, NULL);
|
|
Packit |
1fb8d4 |
if (FAILED(hr))
|
|
Packit |
1fb8d4 |
{
|
|
Packit |
1fb8d4 |
WLog_ERR(TAG, "Failed to get buffer");
|
|
Packit |
1fb8d4 |
exit(1);
|
|
Packit |
1fb8d4 |
}
|
|
Packit |
1fb8d4 |
|
|
Packit Service |
5a9772 |
// Here we are writing the audio data
|
|
Packit Service |
5a9772 |
// not sure if this flag is ever set by the system; msdn is not clear about it
|
|
Packit |
1fb8d4 |
if (!(flags & AUDCLNT_BUFFERFLAGS_SILENT))
|
|
Packit Service |
5a9772 |
context->rdpsnd->SendSamples(context->rdpsnd, pData, packetLength,
|
|
Packit Service |
5a9772 |
(UINT16)(GetTickCount() & 0xffff));
|
|
Packit |
1fb8d4 |
|
|
Packit |
1fb8d4 |
hr = pCaptureClient->lpVtbl->ReleaseBuffer(pCaptureClient, numFramesAvailable);
|
|
Packit |
1fb8d4 |
if (FAILED(hr))
|
|
Packit |
1fb8d4 |
{
|
|
Packit |
1fb8d4 |
WLog_ERR(TAG, "Failed to release buffer");
|
|
Packit |
1fb8d4 |
exit(1);
|
|
Packit |
1fb8d4 |
}
|
|
Packit |
1fb8d4 |
|
|
Packit |
1fb8d4 |
hr = pCaptureClient->lpVtbl->GetNextPacketSize(pCaptureClient, &packetLength);
|
|
Packit |
1fb8d4 |
if (FAILED(hr))
|
|
Packit |
1fb8d4 |
{
|
|
Packit |
1fb8d4 |
WLog_ERR(TAG, "Failed to get packet length");
|
|
Packit |
1fb8d4 |
exit(1);
|
|
Packit |
1fb8d4 |
}
|
|
Packit |
1fb8d4 |
}
|
|
Packit |
1fb8d4 |
}
|
|
Packit |
1fb8d4 |
|
|
Packit |
1fb8d4 |
pAudioClient->lpVtbl->Stop(pAudioClient);
|
|
Packit |
1fb8d4 |
if (FAILED(hr))
|
|
Packit |
1fb8d4 |
{
|
|
Packit |
1fb8d4 |
WLog_ERR(TAG, "Failed to stop audio client");
|
|
Packit |
1fb8d4 |
exit(1);
|
|
Packit |
1fb8d4 |
}
|
|
Packit |
1fb8d4 |
|
|
Packit |
1fb8d4 |
CoTaskMemFree(pwfx);
|
|
Packit |
1fb8d4 |
|
|
Packit |
1fb8d4 |
if (pEnumerator != NULL)
|
|
Packit |
1fb8d4 |
pEnumerator->lpVtbl->Release(pEnumerator);
|
|
Packit |
1fb8d4 |
|
|
Packit |
1fb8d4 |
if (pDevice != NULL)
|
|
Packit |
1fb8d4 |
pDevice->lpVtbl->Release(pDevice);
|
|
Packit |
1fb8d4 |
|
|
Packit |
1fb8d4 |
if (pAudioClient != NULL)
|
|
Packit |
1fb8d4 |
pAudioClient->lpVtbl->Release(pAudioClient);
|
|
Packit |
1fb8d4 |
|
|
Packit |
1fb8d4 |
if (pCaptureClient != NULL)
|
|
Packit |
1fb8d4 |
pCaptureClient->lpVtbl->Release(pCaptureClient);
|
|
Packit |
1fb8d4 |
|
|
Packit |
1fb8d4 |
CoUninitialize();
|
|
Packit |
1fb8d4 |
|
|
Packit |
1fb8d4 |
return 0;
|
|
Packit |
1fb8d4 |
}
|