21#include <mmdeviceapi.h>
22#include <audioclient.h>
25#include <winpr/stream.h>
27#include <freerdp/log.h>
29#include "win_wasapi.h"
31#define TAG SERVER_TAG("shadow.win")
33#ifndef AUDCLNT_STREAMFLAGS_AUTOCONVERTPCM
34#define AUDCLNT_STREAMFLAGS_AUTOCONVERTPCM 0x80000000
36#ifndef AUDCLNT_STREAMFLAGS_SRC_DEFAULT_QUALITY
37#define AUDCLNT_STREAMFLAGS_SRC_DEFAULT_QUALITY 0x08000000
42static const CLSID win_wasapi_CLSID_MMDeviceEnumerator = {
43 0xBCDE0395, 0xE52F, 0x467C, { 0x8E, 0x3D, 0xC4, 0x57, 0x92, 0x91, 0x69, 0x2E }
45static const IID win_wasapi_IID_IMMDeviceEnumerator = {
46 0xA95664D2, 0x9614, 0x4F35, { 0xA7, 0x46, 0xDE, 0x8D, 0xB6, 0x36, 0x17, 0xE6 }
48static const IID win_wasapi_IID_IAudioClient = {
49 0x1CB9AD4C, 0xDBFA, 0x4C32, { 0xB1, 0x78, 0xC2, 0xF5, 0x68, 0xA7, 0x03, 0xB2 }
51static const IID win_wasapi_IID_IAudioCaptureClient = {
52 0xC8ADBD64, 0xE71E, 0x48A0, { 0xA4, 0xDE, 0x18, 0x5C, 0x39, 0x5C, 0xD3, 0x17 }
65struct win_wasapi_loopback
67 IMMDeviceEnumerator* enumerator;
70 IAudioCaptureClient* capture;
71 winWasapiSampleType type;
76static void win_wasapi_release(winWasapiLoopback* cap)
81 cap->capture->lpVtbl->Release(cap->capture);
83 cap->client->lpVtbl->Release(cap->client);
85 cap->device->lpVtbl->Release(cap->device);
87 cap->enumerator->lpVtbl->Release(cap->enumerator);
89 const winWasapiLoopback empty = WINPR_C_ARRAY_INIT;
95WINPR_ATTR_NODISCARD
static BOOL win_wasapi_use_mix_format(winWasapiLoopback* cap,
96 const WAVEFORMATEX* mix)
101 UINT16 tag = mix->wFormatTag;
102 if ((tag == WAVE_FORMAT_EXTENSIBLE) && (mix->cbSize >= 22))
105 const WAVEFORMATEXTENSIBLE* ext = (
const WAVEFORMATEXTENSIBLE*)mix;
106 tag = (UINT16)ext->SubFormat.Data1;
109 if ((mix->nSamplesPerSec != WIN_WASAPI_RATE) || (mix->nChannels == 0))
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;
123 cap->channels = mix->nChannels;
124 cap->frameBytes = mix->nBlockAlign;
128WINPR_ATTR_NODISCARD
static HRESULT win_wasapi_activate(winWasapiLoopback* cap)
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);
139WINPR_ATTR_NODISCARD
static BOOL win_wasapi_start(winWasapiLoopback* cap, BOOL log)
141 const DWORD level = log ? WLOG_WARN : WLOG_DEBUG;
142 const REFERENCE_TIME bufferDuration = 2000000;
143 WAVEFORMATEX* mix =
nullptr;
144 WAVEFORMATEX want = WINPR_C_ARRAY_INIT;
148 HRESULT hr = CoCreateInstance(&win_wasapi_CLSID_MMDeviceEnumerator,
nullptr, CLSCTX_ALL,
149 &win_wasapi_IID_IMMDeviceEnumerator, (
void**)&cap->enumerator);
152 WLog_Print(WLog_Get(TAG), level,
"audio: no MMDeviceEnumerator (0x%08lX): no sound", hr);
156 hr = cap->enumerator->lpVtbl->GetDefaultAudioEndpoint(cap->enumerator, eRender, eConsole,
160 WLog_Print(WLog_Get(TAG), level,
161 "audio: no render endpoint (0x%08lX): no sound until one appears", hr);
165 hr = win_wasapi_activate(cap);
168 WLog_Print(WLog_Get(TAG), level,
"audio: IMMDevice::Activate failed (0x%08lX)", hr);
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);
186 cap->type = WIN_WASAPI_AS_IS;
187 cap->channels = WIN_WASAPI_CHANNELS;
188 cap->frameBytes = WIN_WASAPI_FRAME_BYTES;
193 const HRESULT autoconvert = hr;
194 hr = win_wasapi_activate(cap);
196 hr = cap->client->lpVtbl->GetMixFormat(cap->client, &mix);
199 WLog_Print(WLog_Get(TAG), level,
"audio: no mix format (0x%08lX)", hr);
203 if (!win_wasapi_use_mix_format(cap, mix))
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);
214 hr = cap->client->lpVtbl->Initialize(cap->client, AUDCLNT_SHAREMODE_SHARED,
215 AUDCLNT_STREAMFLAGS_LOOPBACK, bufferDuration, 0, mix,
219 WLog_Print(WLog_Get(TAG), level,
"audio: IAudioClient::Initialize failed (0x%08lX)",
224 if (cap->channels > WIN_WASAPI_CHANNELS)
225 WLog_INFO(TAG,
"audio: the mix has %" PRIu32
" channels, the clients get the first two",
229 hr = cap->client->lpVtbl->GetService(cap->client, &win_wasapi_IID_IAudioCaptureClient,
230 (
void**)&cap->capture);
233 WLog_Print(WLog_Get(TAG), level,
"audio: no IAudioCaptureClient (0x%08lX)", hr);
237 hr = cap->client->lpVtbl->Start(cap->client);
240 WLog_Print(WLog_Get(TAG), level,
"audio: IAudioClient::Start failed (0x%08lX)", hr);
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");
252 win_wasapi_release(cap);
256WINPR_ATTR_NODISCARD
static INT16 win_wasapi_sample(
const winWasapiLoopback* cap,
const BYTE* src)
260 case WIN_WASAPI_FLOAT32:
263 memcpy(&f, src,
sizeof(f));
268 return (INT16)(f * 32767.0f);
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:
277 return (INT16)(src[0] | (src[1] << 8));
282WINPR_ATTR_NODISCARD
static BOOL win_wasapi_append(
const winWasapiLoopback* cap,
wStream* s,
283 const BYTE* data, UINT32 frames, DWORD flags)
287 const size_t size = 1ull * frames * WIN_WASAPI_FRAME_BYTES;
288 if (!Stream_EnsureRemainingCapacity(s, size))
291 if (flags & AUDCLNT_BUFFERFLAGS_SILENT)
293 Stream_Zero(s, size);
297 if (cap->type == WIN_WASAPI_AS_IS)
299 Stream_Write(s, data, size);
303 const size_t sampleBytes = cap->frameBytes / cap->channels;
304 for (UINT32 x = 0; x < frames; x++)
306 const BYTE* frame = &data[1ull * x * cap->frameBytes];
307 const INT16 left = win_wasapi_sample(cap, frame);
309 (cap->channels > 1) ? win_wasapi_sample(cap, &frame[sampleBytes]) : left;
310 Stream_Write_INT16(s, left);
311 Stream_Write_INT16(s, right);
316winWasapiLoopback* win_wasapi_loopback_open(BOOL log)
318 winWasapiLoopback* loopback = (winWasapiLoopback*)calloc(1,
sizeof(winWasapiLoopback));
322 if (!win_wasapi_start(loopback, log))
330void win_wasapi_loopback_close(winWasapiLoopback* loopback)
335 (void)loopback->client->lpVtbl->Stop(loopback->client);
336 win_wasapi_release(loopback);
342BOOL win_wasapi_loopback_read(winWasapiLoopback* loopback,
wStream* s)
344 WINPR_ASSERT(loopback);
347 HRESULT hr = loopback->capture->lpVtbl->GetNextPacketSize(loopback->capture, &packet);
349 while (SUCCEEDED(hr) && (packet > 0))
351 BYTE* data =
nullptr;
355 hr = loopback->capture->lpVtbl->GetBuffer(loopback->capture, &data, &frames, &flags,
360 const BOOL appended = win_wasapi_append(loopback, s, data, frames, flags);
361 hr = loopback->capture->lpVtbl->ReleaseBuffer(loopback->capture, frames);
364 WLog_ERR(TAG,
"audio: out of memory");
368 hr = loopback->capture->lpVtbl->GetNextPacketSize(loopback->capture, &packet);
373 WLog_INFO(TAG,
"audio: capture stopped (0x%08lX)", hr);