FreeRDP
Loading...
Searching...
No Matches
wf_wasapi.c
1
2#include "wf_wasapi.h"
3#include "wf_info.h"
4
5#include <initguid.h>
6#include <mmdeviceapi.h>
7#include <functiondiscoverykeys_devpkey.h>
8#include <audioclient.h>
9
10#include <freerdp/log.h>
11#define TAG SERVER_TAG("windows")
12
13//#define REFTIMES_PER_SEC 10000000
14//#define REFTIMES_PER_MILLISEC 10000
15
16#define REFTIMES_PER_SEC 100000
17#define REFTIMES_PER_MILLISEC 100
18
19//#define REFTIMES_PER_SEC 50000
20//#define REFTIMES_PER_MILLISEC 50
21
22#ifndef __MINGW32__
23DEFINE_GUID(CLSID_MMDeviceEnumerator, 0xBCDE0395, 0xE52F, 0x467C, 0x8E, 0x3D, 0xC4, 0x57, 0x92,
24 0x91, 0x69, 0x2E);
25DEFINE_GUID(IID_IMMDeviceEnumerator, 0xA95664D2, 0x9614, 0x4F35, 0xA7, 0x46, 0xDE, 0x8D, 0xB6, 0x36,
26 0x17, 0xE6);
27DEFINE_GUID(IID_IAudioClient, 0x1cb9ad4c, 0xdbfa, 0x4c32, 0xb1, 0x78, 0xc2, 0xf5, 0x68, 0xa7, 0x03,
28 0xb2);
29DEFINE_GUID(IID_IAudioCaptureClient, 0xc8adbd64, 0xe71e, 0x48a0, 0xa4, 0xde, 0x18, 0x5c, 0x39, 0x5c,
30 0xd3, 0x17);
31#endif
32
33LPWSTR devStr = NULL;
34wfPeerContext* latestPeer = NULL;
35
36int wf_rdpsnd_set_latest_peer(wfPeerContext* peer)
37{
38 latestPeer = peer;
39 return 0;
40}
41
42int wf_wasapi_activate(RdpsndServerContext* context)
43{
44 wchar_t* pattern = L"Stereo Mix";
45 HANDLE hThread;
46
47 wf_wasapi_get_device_string(pattern, &devStr);
48
49 if (devStr == NULL)
50 {
51 WLog_ERR(TAG, "Failed to match for output device! Disabling rdpsnd.");
52 return 1;
53 }
54
55 WLog_DBG(TAG, "RDPSND (WASAPI) Activated");
56 if (!(hThread = CreateThread(NULL, 0, wf_rdpsnd_wasapi_thread, latestPeer, 0, NULL)))
57 {
58 WLog_ERR(TAG, "CreateThread failed");
59 return 1;
60 }
61 (void)CloseHandle(hThread);
62
63 return 0;
64}
65
66int wf_wasapi_get_device_string(LPWSTR pattern, LPWSTR* deviceStr)
67{
68 HRESULT hr;
69 IMMDeviceEnumerator* pEnumerator = NULL;
70 IMMDeviceCollection* pCollection = NULL;
71 IMMDevice* pEndpoint = NULL;
72 IPropertyStore* pProps = NULL;
73 LPWSTR pwszID = NULL;
74 unsigned int count;
75
76 CoInitialize(NULL);
77 hr = CoCreateInstance(&CLSID_MMDeviceEnumerator, NULL, CLSCTX_ALL, &IID_IMMDeviceEnumerator,
78 (void**)&pEnumerator);
79 if (FAILED(hr))
80 {
81 WLog_ERR(TAG, "Failed to cocreate device enumerator");
82 exit(1);
83 }
84
85 hr = pEnumerator->lpVtbl->EnumAudioEndpoints(pEnumerator, eCapture, DEVICE_STATE_ACTIVE,
86 &pCollection);
87 if (FAILED(hr))
88 {
89 WLog_ERR(TAG, "Failed to create endpoint collection");
90 exit(1);
91 }
92
93 pCollection->lpVtbl->GetCount(pCollection, &count);
94 WLog_INFO(TAG, "Num endpoints: %u", count);
95
96 if (count == 0)
97 {
98 WLog_ERR(TAG, "No endpoints!");
99 exit(1);
100 }
101
102 for (unsigned int i = 0; i < count; ++i)
103 {
104 PROPVARIANT nameVar;
105 PropVariantInit(&nameVar);
106
107 hr = pCollection->lpVtbl->Item(pCollection, i, &pEndpoint);
108 if (FAILED(hr))
109 {
110 WLog_ERR(TAG, "Failed to get endpoint %u", i);
111 exit(1);
112 }
113
114 hr = pEndpoint->lpVtbl->GetId(pEndpoint, &pwszID);
115 if (FAILED(hr))
116 {
117 WLog_ERR(TAG, "Failed to get endpoint ID");
118 exit(1);
119 }
120
121 hr = pEndpoint->lpVtbl->OpenPropertyStore(pEndpoint, STGM_READ, &pProps);
122 if (FAILED(hr))
123 {
124 WLog_ERR(TAG, "Failed to open property store");
125 exit(1);
126 }
127
128 hr = pProps->lpVtbl->GetValue(pProps, &PKEY_Device_FriendlyName, &nameVar);
129 if (FAILED(hr))
130 {
131 WLog_ERR(TAG, "Failed to get device friendly name");
132 exit(1);
133 }
134
135 // do this a more reliable way
136 if (wcscmp(pattern, nameVar.pwszVal) < 0)
137 {
138 unsigned int devStrLen;
139 WLog_INFO(TAG, "Using sound output endpoint: [%s] (%s)", nameVar.pwszVal, pwszID);
140 // WLog_INFO(TAG, "matched %d characters", wcscmp(pattern, nameVar.pwszVal);
141 devStrLen = wcslen(pwszID);
142 *deviceStr = (LPWSTR)calloc(devStrLen + 1, 2);
143 if (!deviceStr)
144 return -1;
145 wcscpy_s(*deviceStr, devStrLen + 1, pwszID);
146 }
147 CoTaskMemFree(pwszID);
148 pwszID = NULL;
149 PropVariantClear(&nameVar);
150
151 pProps->lpVtbl->Release(pProps);
152 pProps = NULL;
153
154 pEndpoint->lpVtbl->Release(pEndpoint);
155 pEndpoint = NULL;
156 }
157
158 pCollection->lpVtbl->Release(pCollection);
159 pCollection = NULL;
160
161 pEnumerator->lpVtbl->Release(pEnumerator);
162 pEnumerator = NULL;
163 CoUninitialize();
164
165 return 0;
166}
167
168DWORD WINAPI wf_rdpsnd_wasapi_thread(LPVOID lpParam)
169{
170 IMMDeviceEnumerator* pEnumerator = NULL;
171 IMMDevice* pDevice = NULL;
172 IAudioClient* pAudioClient = NULL;
173 IAudioCaptureClient* pCaptureClient = NULL;
174 WAVEFORMATEX* pwfx = NULL;
175 HRESULT hr;
176 REFERENCE_TIME hnsRequestedDuration = REFTIMES_PER_SEC;
177 REFERENCE_TIME hnsActualDuration;
178 UINT32 bufferFrameCount;
179 UINT32 numFramesAvailable;
180 UINT32 packetLength = 0;
181 UINT32 dCount = 0;
182 BYTE* pData;
183
184 wfPeerContext* context;
185 wfInfo* wfi;
186
187 wfi = wf_info_get_instance();
188 context = (wfPeerContext*)lpParam;
189
190 CoInitialize(NULL);
191 hr = CoCreateInstance(&CLSID_MMDeviceEnumerator, NULL, CLSCTX_ALL, &IID_IMMDeviceEnumerator,
192 (void**)&pEnumerator);
193 if (FAILED(hr))
194 {
195 WLog_ERR(TAG, "Failed to cocreate device enumerator");
196 exit(1);
197 }
198
199 hr = pEnumerator->lpVtbl->GetDevice(pEnumerator, devStr, &pDevice);
200 if (FAILED(hr))
201 {
202 WLog_ERR(TAG, "Failed to cocreate get device");
203 exit(1);
204 }
205
206 hr = pDevice->lpVtbl->Activate(pDevice, &IID_IAudioClient, CLSCTX_ALL, NULL,
207 (void**)&pAudioClient);
208 if (FAILED(hr))
209 {
210 WLog_ERR(TAG, "Failed to activate audio client");
211 exit(1);
212 }
213
214 hr = pAudioClient->lpVtbl->GetMixFormat(pAudioClient, &pwfx);
215 if (FAILED(hr))
216 {
217 WLog_ERR(TAG, "Failed to get mix format");
218 exit(1);
219 }
220
221 pwfx->wFormatTag = wfi->agreed_format->wFormatTag;
222 pwfx->nChannels = wfi->agreed_format->nChannels;
223 pwfx->nSamplesPerSec = wfi->agreed_format->nSamplesPerSec;
224 pwfx->nAvgBytesPerSec = wfi->agreed_format->nAvgBytesPerSec;
225 pwfx->nBlockAlign = wfi->agreed_format->nBlockAlign;
226 pwfx->wBitsPerSample = wfi->agreed_format->wBitsPerSample;
227 pwfx->cbSize = wfi->agreed_format->cbSize;
228
229 hr = pAudioClient->lpVtbl->Initialize(pAudioClient, AUDCLNT_SHAREMODE_SHARED, 0,
230 hnsRequestedDuration, 0, pwfx, NULL);
231
232 if (FAILED(hr))
233 {
234 WLog_ERR(TAG, "Failed to initialize the audio client");
235 exit(1);
236 }
237
238 hr = pAudioClient->lpVtbl->GetBufferSize(pAudioClient, &bufferFrameCount);
239 if (FAILED(hr))
240 {
241 WLog_ERR(TAG, "Failed to get buffer size");
242 exit(1);
243 }
244
245 hr = pAudioClient->lpVtbl->GetService(pAudioClient, &IID_IAudioCaptureClient,
246 (void**)&pCaptureClient);
247 if (FAILED(hr))
248 {
249 WLog_ERR(TAG, "Failed to get the capture client");
250 exit(1);
251 }
252
253 hnsActualDuration = (UINT32)REFTIMES_PER_SEC * bufferFrameCount / pwfx->nSamplesPerSec;
254
255 hr = pAudioClient->lpVtbl->Start(pAudioClient);
256 if (FAILED(hr))
257 {
258 WLog_ERR(TAG, "Failed to start capture");
259 exit(1);
260 }
261
262 dCount = 0;
263
264 while (wfi->snd_stop == FALSE)
265 {
266 DWORD flags;
267
268 Sleep(hnsActualDuration / REFTIMES_PER_MILLISEC / 2);
269
270 hr = pCaptureClient->lpVtbl->GetNextPacketSize(pCaptureClient, &packetLength);
271 if (FAILED(hr))
272 {
273 WLog_ERR(TAG, "Failed to get packet length");
274 exit(1);
275 }
276
277 while (packetLength != 0)
278 {
279 hr = pCaptureClient->lpVtbl->GetBuffer(pCaptureClient, &pData, &numFramesAvailable,
280 &flags, NULL, NULL);
281 if (FAILED(hr))
282 {
283 WLog_ERR(TAG, "Failed to get buffer");
284 exit(1);
285 }
286
287 // Here we are writing the audio data
288 // not sure if this flag is ever set by the system; msdn is not clear about it
289 if (!(flags & AUDCLNT_BUFFERFLAGS_SILENT))
290 context->rdpsnd->SendSamples(context->rdpsnd, pData, packetLength,
291 (UINT16)(GetTickCount() & 0xffff));
292
293 hr = pCaptureClient->lpVtbl->ReleaseBuffer(pCaptureClient, numFramesAvailable);
294 if (FAILED(hr))
295 {
296 WLog_ERR(TAG, "Failed to release buffer");
297 exit(1);
298 }
299
300 hr = pCaptureClient->lpVtbl->GetNextPacketSize(pCaptureClient, &packetLength);
301 if (FAILED(hr))
302 {
303 WLog_ERR(TAG, "Failed to get packet length");
304 exit(1);
305 }
306 }
307 }
308
309 pAudioClient->lpVtbl->Stop(pAudioClient);
310 if (FAILED(hr))
311 {
312 WLog_ERR(TAG, "Failed to stop audio client");
313 exit(1);
314 }
315
316 CoTaskMemFree(pwfx);
317
318 if (pEnumerator != NULL)
319 pEnumerator->lpVtbl->Release(pEnumerator);
320
321 if (pDevice != NULL)
322 pDevice->lpVtbl->Release(pDevice);
323
324 if (pAudioClient != NULL)
325 pAudioClient->lpVtbl->Release(pAudioClient);
326
327 if (pCaptureClient != NULL)
328 pCaptureClient->lpVtbl->Release(pCaptureClient);
329
330 CoUninitialize();
331
332 return 0;
333}