diff options
author | Daniel Baumann <daniel.baumann@progress-linux.org> | 2024-05-04 01:24:41 +0000 |
---|---|---|
committer | Daniel Baumann <daniel.baumann@progress-linux.org> | 2024-05-04 01:24:41 +0000 |
commit | a9bcc81f821d7c66f623779fa5147e728eb3c388 (patch) | |
tree | 98676963bcdd537ae5908a067a8eb110b93486a6 /server/Windows/wf_wasapi.c | |
parent | Initial commit. (diff) | |
download | freerdp3-a9bcc81f821d7c66f623779fa5147e728eb3c388.tar.xz freerdp3-a9bcc81f821d7c66f623779fa5147e728eb3c388.zip |
Adding upstream version 3.3.0+dfsg1.upstream/3.3.0+dfsg1
Signed-off-by: Daniel Baumann <daniel.baumann@progress-linux.org>
Diffstat (limited to 'server/Windows/wf_wasapi.c')
-rw-r--r-- | server/Windows/wf_wasapi.c | 333 |
1 files changed, 333 insertions, 0 deletions
diff --git a/server/Windows/wf_wasapi.c b/server/Windows/wf_wasapi.c new file mode 100644 index 0000000..3925f99 --- /dev/null +++ b/server/Windows/wf_wasapi.c @@ -0,0 +1,333 @@ + +#include "wf_wasapi.h" +#include "wf_info.h" + +#include <initguid.h> +#include <mmdeviceapi.h> +#include <functiondiscoverykeys_devpkey.h> +#include <audioclient.h> + +#include <freerdp/log.h> +#define TAG SERVER_TAG("windows") + +//#define REFTIMES_PER_SEC 10000000 +//#define REFTIMES_PER_MILLISEC 10000 + +#define REFTIMES_PER_SEC 100000 +#define REFTIMES_PER_MILLISEC 100 + +//#define REFTIMES_PER_SEC 50000 +//#define REFTIMES_PER_MILLISEC 50 + +#ifndef __MINGW32__ +DEFINE_GUID(CLSID_MMDeviceEnumerator, 0xBCDE0395, 0xE52F, 0x467C, 0x8E, 0x3D, 0xC4, 0x57, 0x92, + 0x91, 0x69, 0x2E); +DEFINE_GUID(IID_IMMDeviceEnumerator, 0xA95664D2, 0x9614, 0x4F35, 0xA7, 0x46, 0xDE, 0x8D, 0xB6, 0x36, + 0x17, 0xE6); +DEFINE_GUID(IID_IAudioClient, 0x1cb9ad4c, 0xdbfa, 0x4c32, 0xb1, 0x78, 0xc2, 0xf5, 0x68, 0xa7, 0x03, + 0xb2); +DEFINE_GUID(IID_IAudioCaptureClient, 0xc8adbd64, 0xe71e, 0x48a0, 0xa4, 0xde, 0x18, 0x5c, 0x39, 0x5c, + 0xd3, 0x17); +#endif + +LPWSTR devStr = NULL; +wfPeerContext* latestPeer = NULL; + +int wf_rdpsnd_set_latest_peer(wfPeerContext* peer) +{ + latestPeer = peer; + return 0; +} + +int wf_wasapi_activate(RdpsndServerContext* context) +{ + wchar_t* pattern = L"Stereo Mix"; + HANDLE hThread; + + wf_wasapi_get_device_string(pattern, &devStr); + + if (devStr == NULL) + { + WLog_ERR(TAG, "Failed to match for output device! Disabling rdpsnd."); + return 1; + } + + WLog_DBG(TAG, "RDPSND (WASAPI) Activated"); + if (!(hThread = CreateThread(NULL, 0, wf_rdpsnd_wasapi_thread, latestPeer, 0, NULL))) + { + WLog_ERR(TAG, "CreateThread failed"); + return 1; + } + CloseHandle(hThread); + + return 0; +} + +int wf_wasapi_get_device_string(LPWSTR pattern, LPWSTR* deviceStr) +{ + HRESULT hr; + IMMDeviceEnumerator* pEnumerator = NULL; + IMMDeviceCollection* pCollection = NULL; + IMMDevice* pEndpoint = NULL; + IPropertyStore* pProps = NULL; + LPWSTR pwszID = NULL; + unsigned int count; + + CoInitialize(NULL); + hr = CoCreateInstance(&CLSID_MMDeviceEnumerator, NULL, CLSCTX_ALL, &IID_IMMDeviceEnumerator, + (void**)&pEnumerator); + if (FAILED(hr)) + { + WLog_ERR(TAG, "Failed to cocreate device enumerator"); + exit(1); + } + + hr = pEnumerator->lpVtbl->EnumAudioEndpoints(pEnumerator, eCapture, DEVICE_STATE_ACTIVE, + &pCollection); + if (FAILED(hr)) + { + WLog_ERR(TAG, "Failed to create endpoint collection"); + exit(1); + } + + pCollection->lpVtbl->GetCount(pCollection, &count); + WLog_INFO(TAG, "Num endpoints: %u", count); + + if (count == 0) + { + WLog_ERR(TAG, "No endpoints!"); + exit(1); + } + + for (unsigned int i = 0; i < count; ++i) + { + PROPVARIANT nameVar; + PropVariantInit(&nameVar); + + hr = pCollection->lpVtbl->Item(pCollection, i, &pEndpoint); + if (FAILED(hr)) + { + WLog_ERR(TAG, "Failed to get endpoint %u", i); + exit(1); + } + + hr = pEndpoint->lpVtbl->GetId(pEndpoint, &pwszID); + if (FAILED(hr)) + { + WLog_ERR(TAG, "Failed to get endpoint ID"); + exit(1); + } + + hr = pEndpoint->lpVtbl->OpenPropertyStore(pEndpoint, STGM_READ, &pProps); + if (FAILED(hr)) + { + WLog_ERR(TAG, "Failed to open property store"); + exit(1); + } + + hr = pProps->lpVtbl->GetValue(pProps, &PKEY_Device_FriendlyName, &nameVar); + if (FAILED(hr)) + { + WLog_ERR(TAG, "Failed to get device friendly name"); + exit(1); + } + + // do this a more reliable way + if (wcscmp(pattern, nameVar.pwszVal) < 0) + { + unsigned int devStrLen; + WLog_INFO(TAG, "Using sound ouput endpoint: [%s] (%s)", nameVar.pwszVal, pwszID); + // WLog_INFO(TAG, "matched %d characters", wcscmp(pattern, nameVar.pwszVal); + devStrLen = wcslen(pwszID); + *deviceStr = (LPWSTR)calloc(devStrLen + 1, 2); + if (!deviceStr) + return -1; + wcscpy_s(*deviceStr, devStrLen + 1, pwszID); + } + CoTaskMemFree(pwszID); + pwszID = NULL; + PropVariantClear(&nameVar); + + pProps->lpVtbl->Release(pProps); + pProps = NULL; + + pEndpoint->lpVtbl->Release(pEndpoint); + pEndpoint = NULL; + } + + pCollection->lpVtbl->Release(pCollection); + pCollection = NULL; + + pEnumerator->lpVtbl->Release(pEnumerator); + pEnumerator = NULL; + CoUninitialize(); + + return 0; +} + +DWORD WINAPI wf_rdpsnd_wasapi_thread(LPVOID lpParam) +{ + IMMDeviceEnumerator* pEnumerator = NULL; + IMMDevice* pDevice = NULL; + IAudioClient* pAudioClient = NULL; + IAudioCaptureClient* pCaptureClient = NULL; + WAVEFORMATEX* pwfx = NULL; + HRESULT hr; + REFERENCE_TIME hnsRequestedDuration = REFTIMES_PER_SEC; + REFERENCE_TIME hnsActualDuration; + UINT32 bufferFrameCount; + UINT32 numFramesAvailable; + UINT32 packetLength = 0; + UINT32 dCount = 0; + BYTE* pData; + + wfPeerContext* context; + wfInfo* wfi; + + wfi = wf_info_get_instance(); + context = (wfPeerContext*)lpParam; + + CoInitialize(NULL); + hr = CoCreateInstance(&CLSID_MMDeviceEnumerator, NULL, CLSCTX_ALL, &IID_IMMDeviceEnumerator, + (void**)&pEnumerator); + if (FAILED(hr)) + { + WLog_ERR(TAG, "Failed to cocreate device enumerator"); + exit(1); + } + + hr = pEnumerator->lpVtbl->GetDevice(pEnumerator, devStr, &pDevice); + if (FAILED(hr)) + { + WLog_ERR(TAG, "Failed to cocreate get device"); + exit(1); + } + + hr = pDevice->lpVtbl->Activate(pDevice, &IID_IAudioClient, CLSCTX_ALL, NULL, + (void**)&pAudioClient); + if (FAILED(hr)) + { + WLog_ERR(TAG, "Failed to activate audio client"); + exit(1); + } + + hr = pAudioClient->lpVtbl->GetMixFormat(pAudioClient, &pwfx); + if (FAILED(hr)) + { + WLog_ERR(TAG, "Failed to get mix format"); + exit(1); + } + + pwfx->wFormatTag = wfi->agreed_format->wFormatTag; + pwfx->nChannels = wfi->agreed_format->nChannels; + pwfx->nSamplesPerSec = wfi->agreed_format->nSamplesPerSec; + pwfx->nAvgBytesPerSec = wfi->agreed_format->nAvgBytesPerSec; + pwfx->nBlockAlign = wfi->agreed_format->nBlockAlign; + pwfx->wBitsPerSample = wfi->agreed_format->wBitsPerSample; + pwfx->cbSize = wfi->agreed_format->cbSize; + + hr = pAudioClient->lpVtbl->Initialize(pAudioClient, AUDCLNT_SHAREMODE_SHARED, 0, + hnsRequestedDuration, 0, pwfx, NULL); + + if (FAILED(hr)) + { + WLog_ERR(TAG, "Failed to initialize the audio client"); + exit(1); + } + + hr = pAudioClient->lpVtbl->GetBufferSize(pAudioClient, &bufferFrameCount); + if (FAILED(hr)) + { + WLog_ERR(TAG, "Failed to get buffer size"); + exit(1); + } + + hr = pAudioClient->lpVtbl->GetService(pAudioClient, &IID_IAudioCaptureClient, + (void**)&pCaptureClient); + if (FAILED(hr)) + { + WLog_ERR(TAG, "Failed to get the capture client"); + exit(1); + } + + hnsActualDuration = (UINT32)REFTIMES_PER_SEC * bufferFrameCount / pwfx->nSamplesPerSec; + + hr = pAudioClient->lpVtbl->Start(pAudioClient); + if (FAILED(hr)) + { + WLog_ERR(TAG, "Failed to start capture"); + exit(1); + } + + dCount = 0; + + while (wfi->snd_stop == FALSE) + { + DWORD flags; + + Sleep(hnsActualDuration / REFTIMES_PER_MILLISEC / 2); + + hr = pCaptureClient->lpVtbl->GetNextPacketSize(pCaptureClient, &packetLength); + if (FAILED(hr)) + { + WLog_ERR(TAG, "Failed to get packet length"); + exit(1); + } + + while (packetLength != 0) + { + hr = pCaptureClient->lpVtbl->GetBuffer(pCaptureClient, &pData, &numFramesAvailable, + &flags, NULL, NULL); + if (FAILED(hr)) + { + WLog_ERR(TAG, "Failed to get buffer"); + exit(1); + } + + // Here we are writing the audio data + // not sure if this flag is ever set by the system; msdn is not clear about it + if (!(flags & AUDCLNT_BUFFERFLAGS_SILENT)) + context->rdpsnd->SendSamples(context->rdpsnd, pData, packetLength, + (UINT16)(GetTickCount() & 0xffff)); + + hr = pCaptureClient->lpVtbl->ReleaseBuffer(pCaptureClient, numFramesAvailable); + if (FAILED(hr)) + { + WLog_ERR(TAG, "Failed to release buffer"); + exit(1); + } + + hr = pCaptureClient->lpVtbl->GetNextPacketSize(pCaptureClient, &packetLength); + if (FAILED(hr)) + { + WLog_ERR(TAG, "Failed to get packet length"); + exit(1); + } + } + } + + pAudioClient->lpVtbl->Stop(pAudioClient); + if (FAILED(hr)) + { + WLog_ERR(TAG, "Failed to stop audio client"); + exit(1); + } + + CoTaskMemFree(pwfx); + + if (pEnumerator != NULL) + pEnumerator->lpVtbl->Release(pEnumerator); + + if (pDevice != NULL) + pDevice->lpVtbl->Release(pDevice); + + if (pAudioClient != NULL) + pAudioClient->lpVtbl->Release(pAudioClient); + + if (pCaptureClient != NULL) + pCaptureClient->lpVtbl->Release(pCaptureClient); + + CoUninitialize(); + + return 0; +} |