FreeRDP
Loading...
Searching...
No Matches
win_wasapi.c
1
19#include <windows.h>
20#include <objbase.h>
21#include <mmdeviceapi.h>
22#include <audioclient.h>
23
24#include <winpr/crt.h>
25#include <winpr/stream.h>
26
27#include <freerdp/log.h>
28
29#include "win_wasapi.h"
30
31#define TAG SERVER_TAG("shadow.win")
32
33#ifndef AUDCLNT_STREAMFLAGS_AUTOCONVERTPCM
34#define AUDCLNT_STREAMFLAGS_AUTOCONVERTPCM 0x80000000
35#endif
36#ifndef AUDCLNT_STREAMFLAGS_SRC_DEFAULT_QUALITY
37#define AUDCLNT_STREAMFLAGS_SRC_DEFAULT_QUALITY 0x08000000
38#endif
39
40/* GUIDs under names of our own: MinGW defines the system ones with
41 * initguid.h, the Windows SDK in uuid.lib for some of them only */
42static const CLSID win_wasapi_CLSID_MMDeviceEnumerator = {
43 0xBCDE0395, 0xE52F, 0x467C, { 0x8E, 0x3D, 0xC4, 0x57, 0x92, 0x91, 0x69, 0x2E }
44};
45static const IID win_wasapi_IID_IMMDeviceEnumerator = {
46 0xA95664D2, 0x9614, 0x4F35, { 0xA7, 0x46, 0xDE, 0x8D, 0xB6, 0x36, 0x17, 0xE6 }
47};
48static const IID win_wasapi_IID_IAudioClient = {
49 0x1CB9AD4C, 0xDBFA, 0x4C32, { 0xB1, 0x78, 0xC2, 0xF5, 0x68, 0xA7, 0x03, 0xB2 }
50};
51static const IID win_wasapi_IID_IAudioCaptureClient = {
52 0xC8ADBD64, 0xE71E, 0x48A0, { 0xA4, 0xDE, 0x18, 0x5C, 0x39, 0x5C, 0xD3, 0x17 }
53};
54
55/* How the captured samples become 16-bit stereo */
56typedef enum
57{
58 WIN_WASAPI_AS_IS, /* the audio engine converts (AUTOCONVERTPCM) */
59 WIN_WASAPI_FLOAT32,
60 WIN_WASAPI_INT16,
61 WIN_WASAPI_INT24,
62 WIN_WASAPI_INT32
63} winWasapiSampleType;
64
65struct win_wasapi_loopback
66{
67 IMMDeviceEnumerator* enumerator;
68 IMMDevice* device;
69 IAudioClient* client;
70 IAudioCaptureClient* capture;
71 winWasapiSampleType type;
72 UINT32 channels; /* in the captured frames */
73 UINT32 frameBytes; /* of a captured frame */
74};
75
76static void win_wasapi_release(winWasapiLoopback* cap)
77{
78 WINPR_ASSERT(cap);
79
80 if (cap->capture)
81 cap->capture->lpVtbl->Release(cap->capture);
82 if (cap->client)
83 cap->client->lpVtbl->Release(cap->client);
84 if (cap->device)
85 cap->device->lpVtbl->Release(cap->device);
86 if (cap->enumerator)
87 cap->enumerator->lpVtbl->Release(cap->enumerator);
88
89 const winWasapiLoopback empty = WINPR_C_ARRAY_INIT;
90 *cap = empty;
91}
92
93/* The mix format, when the audio engine did not convert it for us: 48 kHz
94 * only (there is no resampler), float or integer samples */
95WINPR_ATTR_NODISCARD static BOOL win_wasapi_use_mix_format(winWasapiLoopback* cap,
96 const WAVEFORMATEX* mix)
97{
98 WINPR_ASSERT(cap);
99 WINPR_ASSERT(mix);
100
101 UINT16 tag = mix->wFormatTag;
102 if ((tag == WAVE_FORMAT_EXTENSIBLE) && (mix->cbSize >= 22))
103 {
104 /* the KSDATAFORMAT_SUBTYPE GUIDs start with the format tag */
105 const WAVEFORMATEXTENSIBLE* ext = (const WAVEFORMATEXTENSIBLE*)mix;
106 tag = (UINT16)ext->SubFormat.Data1;
107 }
108
109 if ((mix->nSamplesPerSec != WIN_WASAPI_RATE) || (mix->nChannels == 0))
110 return FALSE;
111
112 if ((tag == WAVE_FORMAT_IEEE_FLOAT) && (mix->wBitsPerSample == 32))
113 cap->type = WIN_WASAPI_FLOAT32;
114 else if ((tag == WAVE_FORMAT_PCM) && (mix->wBitsPerSample == 16))
115 cap->type = WIN_WASAPI_INT16;
116 else if ((tag == WAVE_FORMAT_PCM) && (mix->wBitsPerSample == 24))
117 cap->type = WIN_WASAPI_INT24;
118 else if ((tag == WAVE_FORMAT_PCM) && (mix->wBitsPerSample == 32))
119 cap->type = WIN_WASAPI_INT32;
120 else
121 return FALSE;
122
123 cap->channels = mix->nChannels;
124 cap->frameBytes = mix->nBlockAlign;
125 return TRUE;
126}
127
128WINPR_ATTR_NODISCARD static HRESULT win_wasapi_activate(winWasapiLoopback* cap)
129{
130 WINPR_ASSERT(cap);
131
132 if (cap->client)
133 cap->client->lpVtbl->Release(cap->client);
134 cap->client = nullptr;
135 return cap->device->lpVtbl->Activate(cap->device, &win_wasapi_IID_IAudioClient, CLSCTX_ALL,
136 nullptr, (void**)&cap->client);
137}
138
139WINPR_ATTR_NODISCARD static BOOL win_wasapi_start(winWasapiLoopback* cap, BOOL log)
140{
141 const DWORD level = log ? WLOG_WARN : WLOG_DEBUG;
142 const REFERENCE_TIME bufferDuration = 2000000; /* 200 ms, in 100 ns units */
143 WAVEFORMATEX* mix = nullptr;
144 WAVEFORMATEX want = WINPR_C_ARRAY_INIT;
145
146 WINPR_ASSERT(cap);
147
148 HRESULT hr = CoCreateInstance(&win_wasapi_CLSID_MMDeviceEnumerator, nullptr, CLSCTX_ALL,
149 &win_wasapi_IID_IMMDeviceEnumerator, (void**)&cap->enumerator);
150 if (FAILED(hr))
151 {
152 WLog_Print(WLog_Get(TAG), level, "audio: no MMDeviceEnumerator (0x%08lX): no sound", hr);
153 goto fail;
154 }
155
156 hr = cap->enumerator->lpVtbl->GetDefaultAudioEndpoint(cap->enumerator, eRender, eConsole,
157 &cap->device);
158 if (FAILED(hr))
159 {
160 WLog_Print(WLog_Get(TAG), level,
161 "audio: no render endpoint (0x%08lX): no sound until one appears", hr);
162 goto fail;
163 }
164
165 hr = win_wasapi_activate(cap);
166 if (FAILED(hr))
167 {
168 WLog_Print(WLog_Get(TAG), level, "audio: IMMDevice::Activate failed (0x%08lX)", hr);
169 goto fail;
170 }
171
172 /* 16-bit stereo at 48 kHz from the audio engine, which converts the mix */
173 want.wFormatTag = WAVE_FORMAT_PCM;
174 want.nChannels = WIN_WASAPI_CHANNELS;
175 want.nSamplesPerSec = WIN_WASAPI_RATE;
176 want.wBitsPerSample = 16;
177 want.nBlockAlign = WIN_WASAPI_FRAME_BYTES;
178 want.nAvgBytesPerSec = WIN_WASAPI_RATE * WIN_WASAPI_FRAME_BYTES;
179 hr = cap->client->lpVtbl->Initialize(cap->client, AUDCLNT_SHAREMODE_SHARED,
180 AUDCLNT_STREAMFLAGS_LOOPBACK |
181 AUDCLNT_STREAMFLAGS_AUTOCONVERTPCM |
182 AUDCLNT_STREAMFLAGS_SRC_DEFAULT_QUALITY,
183 bufferDuration, 0, &want, nullptr);
184 if (SUCCEEDED(hr))
185 {
186 cap->type = WIN_WASAPI_AS_IS;
187 cap->channels = WIN_WASAPI_CHANNELS;
188 cap->frameBytes = WIN_WASAPI_FRAME_BYTES;
189 }
190 else
191 {
192 /* else the mix format, converted here */
193 const HRESULT autoconvert = hr;
194 hr = win_wasapi_activate(cap);
195 if (SUCCEEDED(hr))
196 hr = cap->client->lpVtbl->GetMixFormat(cap->client, &mix);
197 if (FAILED(hr))
198 {
199 WLog_Print(WLog_Get(TAG), level, "audio: no mix format (0x%08lX)", hr);
200 goto fail;
201 }
202
203 if (!win_wasapi_use_mix_format(cap, mix))
204 {
205 WLog_Print(WLog_Get(TAG), level,
206 "audio: the mix format (tag 0x%04" PRIX16 ", %" PRIu32 " Hz, %" PRIu16
207 " channels, %" PRIu16 " bits) is not one we convert, and the audio "
208 "engine refused to (0x%08lX): no sound",
209 mix->wFormatTag, (UINT32)mix->nSamplesPerSec, mix->nChannels,
210 mix->wBitsPerSample, autoconvert);
211 goto fail;
212 }
213
214 hr = cap->client->lpVtbl->Initialize(cap->client, AUDCLNT_SHAREMODE_SHARED,
215 AUDCLNT_STREAMFLAGS_LOOPBACK, bufferDuration, 0, mix,
216 nullptr);
217 if (FAILED(hr))
218 {
219 WLog_Print(WLog_Get(TAG), level, "audio: IAudioClient::Initialize failed (0x%08lX)",
220 hr);
221 goto fail;
222 }
223
224 if (cap->channels > WIN_WASAPI_CHANNELS)
225 WLog_INFO(TAG, "audio: the mix has %" PRIu32 " channels, the clients get the first two",
226 cap->channels);
227 }
228
229 hr = cap->client->lpVtbl->GetService(cap->client, &win_wasapi_IID_IAudioCaptureClient,
230 (void**)&cap->capture);
231 if (FAILED(hr))
232 {
233 WLog_Print(WLog_Get(TAG), level, "audio: no IAudioCaptureClient (0x%08lX)", hr);
234 goto fail;
235 }
236
237 hr = cap->client->lpVtbl->Start(cap->client);
238 if (FAILED(hr))
239 {
240 WLog_Print(WLog_Get(TAG), level, "audio: IAudioClient::Start failed (0x%08lX)", hr);
241 goto fail;
242 }
243
244 WLog_INFO(TAG, "audio: capturing what the default render endpoint plays (%s)",
245 (cap->type == WIN_WASAPI_AS_IS) ? "converted by the audio engine"
246 : "converted from the mix format");
247 CoTaskMemFree(mix);
248 return TRUE;
249
250fail:
251 CoTaskMemFree(mix);
252 win_wasapi_release(cap);
253 return FALSE;
254}
255
256WINPR_ATTR_NODISCARD static INT16 win_wasapi_sample(const winWasapiLoopback* cap, const BYTE* src)
257{
258 switch (cap->type)
259 {
260 case WIN_WASAPI_FLOAT32:
261 {
262 float f = 0.0f;
263 memcpy(&f, src, sizeof(f));
264 if (f >= 1.0f)
265 return INT16_MAX;
266 if (f <= -1.0f)
267 return INT16_MIN;
268 return (INT16)(f * 32767.0f);
269 }
270 case WIN_WASAPI_INT24:
271 return (INT16)(src[1] | (src[2] << 8));
272 case WIN_WASAPI_INT32:
273 return (INT16)(src[2] | (src[3] << 8));
274 case WIN_WASAPI_INT16:
275 case WIN_WASAPI_AS_IS:
276 default:
277 return (INT16)(src[0] | (src[1] << 8));
278 }
279}
280
281/* Appends frames to s as 16-bit stereo; silence when the engine says so */
282WINPR_ATTR_NODISCARD static BOOL win_wasapi_append(const winWasapiLoopback* cap, wStream* s,
283 const BYTE* data, UINT32 frames, DWORD flags)
284{
285 WINPR_ASSERT(cap);
286
287 const size_t size = 1ull * frames * WIN_WASAPI_FRAME_BYTES;
288 if (!Stream_EnsureRemainingCapacity(s, size))
289 return FALSE;
290
291 if (flags & AUDCLNT_BUFFERFLAGS_SILENT)
292 {
293 Stream_Zero(s, size);
294 return TRUE;
295 }
296
297 if (cap->type == WIN_WASAPI_AS_IS)
298 {
299 Stream_Write(s, data, size);
300 return TRUE;
301 }
302
303 const size_t sampleBytes = cap->frameBytes / cap->channels;
304 for (UINT32 x = 0; x < frames; x++)
305 {
306 const BYTE* frame = &data[1ull * x * cap->frameBytes];
307 const INT16 left = win_wasapi_sample(cap, frame);
308 const INT16 right =
309 (cap->channels > 1) ? win_wasapi_sample(cap, &frame[sampleBytes]) : left;
310 Stream_Write_INT16(s, left);
311 Stream_Write_INT16(s, right);
312 }
313 return TRUE;
314}
315
316winWasapiLoopback* win_wasapi_loopback_open(BOOL log)
317{
318 winWasapiLoopback* loopback = (winWasapiLoopback*)calloc(1, sizeof(winWasapiLoopback));
319 if (!loopback)
320 return nullptr;
321
322 if (!win_wasapi_start(loopback, log))
323 {
324 free(loopback);
325 return nullptr;
326 }
327 return loopback;
328}
329
330void win_wasapi_loopback_close(winWasapiLoopback* loopback)
331{
332 if (!loopback)
333 return;
334
335 (void)loopback->client->lpVtbl->Stop(loopback->client);
336 win_wasapi_release(loopback);
337 free(loopback);
338}
339
340/* Loopback capture only has packets while something plays: the caller
341 * polls, since an event never fires before Windows 10 1703 */
342BOOL win_wasapi_loopback_read(winWasapiLoopback* loopback, wStream* s)
343{
344 WINPR_ASSERT(loopback);
345
346 UINT32 packet = 0;
347 HRESULT hr = loopback->capture->lpVtbl->GetNextPacketSize(loopback->capture, &packet);
348
349 while (SUCCEEDED(hr) && (packet > 0))
350 {
351 BYTE* data = nullptr;
352 UINT32 frames = 0;
353 DWORD flags = 0;
354
355 hr = loopback->capture->lpVtbl->GetBuffer(loopback->capture, &data, &frames, &flags,
356 nullptr, nullptr);
357 if (FAILED(hr))
358 break;
359
360 const BOOL appended = win_wasapi_append(loopback, s, data, frames, flags);
361 hr = loopback->capture->lpVtbl->ReleaseBuffer(loopback->capture, frames);
362 if (!appended)
363 {
364 WLog_ERR(TAG, "audio: out of memory");
365 return FALSE;
366 }
367 if (SUCCEEDED(hr))
368 hr = loopback->capture->lpVtbl->GetNextPacketSize(loopback->capture, &packet);
369 }
370
371 if (FAILED(hr))
372 {
373 WLog_INFO(TAG, "audio: capture stopped (0x%08lX)", hr);
374 return FALSE;
375 }
376 return TRUE;
377}