FreeRDP
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__
23 DEFINE_GUID(CLSID_MMDeviceEnumerator, 0xBCDE0395, 0xE52F, 0x467C, 0x8E, 0x3D, 0xC4, 0x57, 0x92,
24  0x91, 0x69, 0x2E);
25 DEFINE_GUID(IID_IMMDeviceEnumerator, 0xA95664D2, 0x9614, 0x4F35, 0xA7, 0x46, 0xDE, 0x8D, 0xB6, 0x36,
26  0x17, 0xE6);
27 DEFINE_GUID(IID_IAudioClient, 0x1cb9ad4c, 0xdbfa, 0x4c32, 0xb1, 0x78, 0xc2, 0xf5, 0x68, 0xa7, 0x03,
28  0xb2);
29 DEFINE_GUID(IID_IAudioCaptureClient, 0xc8adbd64, 0xe71e, 0x48a0, 0xa4, 0xde, 0x18, 0x5c, 0x39, 0x5c,
30  0xd3, 0x17);
31 #endif
32 
33 LPWSTR devStr = NULL;
34 wfPeerContext* latestPeer = NULL;
35 
36 int wf_rdpsnd_set_latest_peer(wfPeerContext* peer)
37 {
38  latestPeer = peer;
39  return 0;
40 }
41 
42 int 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 
66 int 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 ouput 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 
168 DWORD 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 }