contrib: cargo: use the 0.6.13 cargo-c version
[vlc.git] / modules / access / wasapi.c
blob6377a3c7ff1c32cdfaf35c841372f7794f686723
1 /**
2 * \file wasapi.c
3 * \brief Windows Audio Session API capture plugin for VLC
4 */
5 /*****************************************************************************
6 * Copyright (C) 2014-2015 RĂ©mi Denis-Courmont
8 * This program is free software; you can redistribute it and/or modify it
9 * under the terms of the GNU Lesser General Public License as published by
10 * the Free Software Foundation; either version 2.1 of the License, or
11 * (at your option) any later version.
13 * This program is distributed in the hope that it will be useful,
14 * but WITHOUT ANY WARRANTY; without even the implied warranty of
15 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
16 * GNU Lesser General Public License for more details.
18 * You should have received a copy of the GNU Lesser General Public License
19 * along with this program; if not, write to the Free Software Foundation,
20 * Inc., 51 Franklin Street, Fifth Floor, Boston MA 02110-1301, USA.
21 *****************************************************************************/
23 #ifdef HAVE_CONFIG_H
24 # include "config.h"
25 #endif
27 #define INITGUID
28 #define COBJMACROS
29 #define CONST_VTABLE
31 #include <assert.h>
32 #include <stdlib.h>
34 #define _DECL_DLLMAIN
35 #include <vlc_common.h>
36 #include <vlc_aout.h>
37 #include <vlc_demux.h>
38 #include <vlc_plugin.h>
39 #include <mmdeviceapi.h>
40 #include <audioclient.h>
42 static LARGE_INTEGER freq; /* performance counters frequency */
44 BOOL WINAPI DllMain(HANDLE dll, DWORD reason, LPVOID reserved)
46 (void) dll;
47 (void) reserved;
49 switch (reason)
51 case DLL_PROCESS_ATTACH:
52 if (!QueryPerformanceFrequency(&freq))
53 return FALSE;
54 break;
56 return TRUE;
59 static msftime_t GetQPC(void)
61 LARGE_INTEGER counter;
63 if (!QueryPerformanceCounter(&counter))
64 abort();
66 lldiv_t d = lldiv(counter.QuadPart, freq.QuadPart);
67 return (d.quot * 10000000) + ((d.rem * 10000000) / freq.QuadPart);
70 static_assert(CLOCK_FREQ * 10 == 10000000,
71 "REFERENCE_TIME conversion broken");
73 static EDataFlow GetDeviceFlow(IMMDevice *dev)
75 void *pv;
77 if (FAILED(IMMDevice_QueryInterface(dev, &IID_IMMEndpoint, &pv)))
78 return false;
80 IMMEndpoint *ep = pv;
81 EDataFlow flow;
83 if (SUCCEEDED(IMMEndpoint_GetDataFlow(ep, &flow)))
84 flow = eAll;
85 IMMEndpoint_Release(ep);
86 return flow;
89 static IAudioClient *GetClient(demux_t *demux, bool *restrict loopbackp)
91 IMMDeviceEnumerator *e;
92 IMMDevice *dev;
93 void *pv;
94 HRESULT hr;
96 hr = CoCreateInstance(&CLSID_MMDeviceEnumerator, NULL, CLSCTX_ALL,
97 &IID_IMMDeviceEnumerator, &pv);
98 if (FAILED(hr))
100 msg_Err(demux, "cannot create device enumerator (error 0x%lX)", hr);
101 return NULL;
103 e = pv;
105 bool loopback = var_InheritBool(demux, "wasapi-loopback");
106 EDataFlow flow = loopback ? eRender : eCapture;
107 ERole role = loopback ? eConsole : eCommunications;
109 hr = IMMDeviceEnumerator_GetDefaultAudioEndpoint(e, flow, role, &dev);
110 IMMDeviceEnumerator_Release(e);
111 if (FAILED(hr))
113 msg_Err(demux, "cannot get default device (error 0x%lX)", hr);
114 return NULL;
117 hr = IMMDevice_Activate(dev, &IID_IAudioClient, CLSCTX_ALL, NULL, &pv);
118 *loopbackp = GetDeviceFlow(dev) == eRender;
119 IMMDevice_Release(dev);
120 if (FAILED(hr))
121 msg_Err(demux, "cannot activate device (error 0x%lX)", hr);
122 return pv;
125 static int vlc_FromWave(const WAVEFORMATEX *restrict wf,
126 audio_sample_format_t *restrict fmt)
128 fmt->i_rate = wf->nSamplesPerSec;
130 /* As per MSDN, IAudioClient::GetMixFormat() always uses this format. */
131 assert(wf->wFormatTag == WAVE_FORMAT_EXTENSIBLE);
133 const WAVEFORMATEXTENSIBLE *wfe = (void *)wf;
135 fmt->i_physical_channels = 0;
136 if (wfe->dwChannelMask & SPEAKER_FRONT_LEFT)
137 fmt->i_physical_channels |= AOUT_CHAN_LEFT;
138 if (wfe->dwChannelMask & SPEAKER_FRONT_RIGHT)
139 fmt->i_physical_channels |= AOUT_CHAN_RIGHT;
140 if (wfe->dwChannelMask & SPEAKER_FRONT_CENTER)
141 fmt->i_physical_channels |= AOUT_CHAN_CENTER;
142 if (wfe->dwChannelMask & SPEAKER_LOW_FREQUENCY)
143 fmt->i_physical_channels |= AOUT_CHAN_LFE;
145 assert(vlc_popcount(wfe->dwChannelMask) == wf->nChannels);
147 if (IsEqualIID(&wfe->SubFormat, &KSDATAFORMAT_SUBTYPE_PCM))
149 switch (wf->wBitsPerSample)
151 case 32:
152 switch (wfe->Samples.wValidBitsPerSample)
154 case 32:
155 fmt->i_format = VLC_CODEC_S32N;
156 break;
157 case 24:
158 #ifdef WORDS_BIGENDIAN
159 fmt->i_format = VLC_CODEC_S24B32;
160 #else
161 fmt->i_format = VLC_CODEC_S24L32;
162 #endif
163 break;
164 default:
165 return -1;
167 break;
168 case 24:
169 if (wfe->Samples.wValidBitsPerSample == 24)
170 fmt->i_format = VLC_CODEC_S24N;
171 else
172 return -1;
173 break;
174 case 16:
175 if (wfe->Samples.wValidBitsPerSample == 16)
176 fmt->i_format = VLC_CODEC_S16N;
177 else
178 return -1;
179 break;
180 case 8:
181 if (wfe->Samples.wValidBitsPerSample == 8)
182 fmt->i_format = VLC_CODEC_S8;
183 else
184 return -1;
185 break;
186 default:
187 return -1;
190 else if (IsEqualIID(&wfe->SubFormat, &KSDATAFORMAT_SUBTYPE_IEEE_FLOAT))
192 if (wf->wBitsPerSample != wfe->Samples.wValidBitsPerSample)
193 return -1;
195 switch (wf->wBitsPerSample)
197 case 64:
198 fmt->i_format = VLC_CODEC_FL64;
199 break;
200 case 32:
201 fmt->i_format = VLC_CODEC_FL32;
202 break;
203 default:
204 return -1;
207 /*else if (IsEqualIID(&wfe->Subformat, &KSDATAFORMAT_SUBTYPE_DRM)) {} */
208 else if (IsEqualIID(&wfe->SubFormat, &KSDATAFORMAT_SUBTYPE_ALAW))
209 fmt->i_format = VLC_CODEC_ALAW;
210 else if (IsEqualIID(&wfe->SubFormat, &KSDATAFORMAT_SUBTYPE_MULAW))
211 fmt->i_format = VLC_CODEC_MULAW;
212 else if (IsEqualIID(&wfe->SubFormat, &KSDATAFORMAT_SUBTYPE_ADPCM))
213 fmt->i_format = VLC_CODEC_ADPCM_MS;
214 else
215 return -1;
217 aout_FormatPrepare(fmt);
218 if (wf->nChannels != fmt->i_channels)
219 return -1;
221 return 0;
224 static es_out_id_t *CreateES(demux_t *demux, IAudioClient *client, bool loop,
225 vlc_tick_t caching, size_t *restrict frame_size)
227 es_format_t fmt;
228 WAVEFORMATEX *pwf;
229 HRESULT hr;
231 hr = IAudioClient_GetMixFormat(client, &pwf);
232 if (FAILED(hr))
234 msg_Err(demux, "cannot get mix format (error 0x%lX)", hr);
235 return NULL;
238 es_format_Init(&fmt, AUDIO_ES, 0);
239 if (vlc_FromWave(pwf, &fmt.audio))
241 msg_Err(demux, "unsupported mix format");
242 CoTaskMemFree(pwf);
243 return NULL;
246 fmt.i_codec = fmt.audio.i_format;
247 fmt.i_bitrate = fmt.audio.i_bitspersample * fmt.audio.i_channels
248 * fmt.audio.i_rate;
249 *frame_size = fmt.audio.i_bitspersample * fmt.audio.i_channels / 8;
251 DWORD flags = AUDCLNT_STREAMFLAGS_EVENTCALLBACK;
252 if (loop)
253 flags |= AUDCLNT_STREAMFLAGS_LOOPBACK;
255 /* Request at least thrice the PTS delay */
256 REFERENCE_TIME bufsize = MSFTIME_FROM_VLC_TICK( caching ) * 3;
258 hr = IAudioClient_Initialize(client, AUDCLNT_SHAREMODE_SHARED, flags,
259 bufsize, 0, pwf, NULL);
260 CoTaskMemFree(pwf);
261 if (FAILED(hr))
263 msg_Err(demux, "cannot initialize audio client (error 0x%lX)", hr);
264 return NULL;
266 return es_out_Add(demux->out, &fmt);
269 typedef struct
271 IAudioClient *client;
272 es_out_id_t *es;
274 size_t frame_size;
275 vlc_tick_t caching;
276 vlc_tick_t start_time;
278 HANDLE events[2];
279 union {
280 HANDLE thread;
281 HANDLE ready;
283 } demux_sys_t;
285 static unsigned __stdcall Thread(void *data)
287 demux_t *demux = data;
288 demux_sys_t *sys = demux->p_sys;
289 IAudioCaptureClient *capture = NULL;
290 void *pv;
291 HRESULT hr;
293 hr = CoInitializeEx(NULL, COINIT_MULTITHREADED);
294 assert(SUCCEEDED(hr)); /* COM already allocated by parent thread */
295 SetEvent(sys->ready);
297 hr = IAudioClient_GetService(sys->client, &IID_IAudioCaptureClient, &pv);
298 if (FAILED(hr))
300 msg_Err(demux, "cannot get capture client (error 0x%lX)", hr);
301 goto out;
303 capture = pv;
305 hr = IAudioClient_Start(sys->client);
306 if (FAILED(hr))
308 msg_Err(demux, "cannot start client (error 0x%lX)", hr);
309 IAudioCaptureClient_Release(capture);
310 goto out;
313 while (WaitForMultipleObjects(2, sys->events, FALSE, INFINITE)
314 != WAIT_OBJECT_0)
316 BYTE *buf;
317 UINT32 frames;
318 DWORD flags;
319 UINT64 qpc;
320 vlc_tick_t pts;
322 hr = IAudioCaptureClient_GetBuffer(capture, &buf, &frames, &flags,
323 NULL, &qpc);
324 if (hr != S_OK)
325 continue;
327 pts = vlc_tick_now() - VLC_TICK_FROM_MSFTIME(GetQPC() - qpc);
329 es_out_SetPCR(demux->out, pts);
331 size_t bytes = frames * sys->frame_size;
332 block_t *block = block_Alloc(bytes);
334 if (likely(block != NULL)) {
335 memcpy(block->p_buffer, buf, bytes);
336 block->i_nb_samples = frames;
337 block->i_pts = block->i_dts = pts;
338 es_out_Send(demux->out, sys->es, block);
341 IAudioCaptureClient_ReleaseBuffer(capture, frames);
344 IAudioClient_Stop(sys->client);
345 IAudioCaptureClient_Release(capture);
346 out:
347 CoUninitialize();
348 return 0;
351 static int Control(demux_t *demux, int query, va_list ap)
353 demux_sys_t *sys = demux->p_sys;
355 switch (query)
357 case DEMUX_GET_TIME:
358 *(va_arg(ap, vlc_tick_t *)) = vlc_tick_now() - sys->start_time;
359 break;
361 case DEMUX_GET_PTS_DELAY:
362 *(va_arg(ap, vlc_tick_t *)) = sys->caching;
363 break;
365 case DEMUX_HAS_UNSUPPORTED_META:
366 case DEMUX_CAN_RECORD:
367 case DEMUX_CAN_PAUSE:
368 case DEMUX_CAN_CONTROL_PACE:
369 case DEMUX_CAN_CONTROL_RATE:
370 case DEMUX_CAN_SEEK:
371 *(va_arg(ap, bool *)) = false;
372 break;
374 default:
375 return VLC_EGENERIC;
378 return VLC_SUCCESS;
381 static int Open(vlc_object_t *obj)
383 demux_t *demux = (demux_t *)obj;
384 HRESULT hr;
386 if (demux->out == NULL)
387 return VLC_EGENERIC;
389 if (demux->psz_location != NULL && *demux->psz_location != '\0')
390 return VLC_EGENERIC; /* TODO non-default device */
392 demux_sys_t *sys = vlc_obj_malloc(obj, sizeof (*sys));
393 if (unlikely(sys == NULL))
394 return VLC_ENOMEM;
396 sys->client = NULL;
397 sys->es = NULL;
398 sys->caching = VLC_TICK_FROM_MS( var_InheritInteger(obj, "live-caching") );
399 sys->start_time = vlc_tick_now();
400 for (unsigned i = 0; i < 2; i++)
401 sys->events[i] = NULL;
403 for (unsigned i = 0; i < 2; i++) {
404 sys->events[i] = CreateEvent(NULL, FALSE, FALSE, NULL);
405 if (sys->events[i] == NULL)
406 goto error;
409 hr = CoInitializeEx(NULL, COINIT_MULTITHREADED);
410 if (unlikely(FAILED(hr))) {
411 msg_Err(demux, "cannot initialize COM (error 0x%lX)", hr);
412 goto error;
415 bool loopback;
416 sys->client = GetClient(demux, &loopback);
417 if (sys->client == NULL) {
418 CoUninitialize();
419 goto error;
422 sys->es = CreateES(demux, sys->client, loopback, sys->caching,
423 &sys->frame_size);
424 if (sys->es == NULL)
425 goto error;
427 hr = IAudioClient_SetEventHandle(sys->client, sys->events[1]);
428 if (FAILED(hr)) {
429 msg_Err(demux, "cannot set event handle (error 0x%lX)", hr);
430 goto error;
433 demux->p_sys = sys;
435 sys->ready = CreateEvent(NULL, FALSE, FALSE, NULL);
436 if (sys->ready == NULL)
437 goto error;
439 uintptr_t h = _beginthreadex(NULL, 0, Thread, demux, 0, NULL);
440 if (h != 0)
441 WaitForSingleObject(sys->ready, INFINITE);
442 CloseHandle(sys->ready);
444 sys->thread = (HANDLE)h;
445 if (sys->thread == NULL)
446 goto error;
447 CoUninitialize();
449 demux->pf_demux = NULL;
450 demux->pf_control = Control;
451 return VLC_SUCCESS;
453 error:
454 if (sys->es != NULL)
455 es_out_Del(demux->out, sys->es);
456 if (sys->client != NULL)
458 IAudioClient_Release(sys->client);
459 CoUninitialize();
461 for (unsigned i = 0; i < 2; i++)
462 if (sys->events[i] != NULL)
463 CloseHandle(sys->events[i]);
464 return VLC_ENOMEM;
467 static void Close (vlc_object_t *obj)
469 demux_t *demux = (demux_t *)obj;
470 demux_sys_t *sys = demux->p_sys;
471 HRESULT hr;
473 hr = CoInitializeEx(NULL, COINIT_MULTITHREADED);
474 assert(SUCCEEDED(hr));
476 SetEvent(sys->events[0]);
477 WaitForSingleObject(sys->thread, INFINITE);
478 CloseHandle(sys->thread);
480 es_out_Del(demux->out, sys->es);
481 IAudioClient_Release(sys->client);
482 CoUninitialize();
483 for (unsigned i = 0; i < 2; i++)
484 CloseHandle(sys->events[i]);
487 #define LOOPBACK_TEXT N_("Loopback mode")
488 #define LOOPBACK_LONGTEXT N_("Record an audio rendering endpoint.")
490 vlc_module_begin()
491 set_shortname(N_("WASAPI"))
492 set_description(N_("Windows Audio Session API input"))
493 set_capability("access", 0)
494 set_category(CAT_INPUT)
495 set_subcategory(SUBCAT_INPUT_ACCESS)
497 add_bool("wasapi-loopback", false, LOOPBACK_TEXT, LOOPBACK_LONGTEXT, true)
499 add_shortcut("wasapi")
500 set_callbacks(Open, Close)
501 vlc_module_end()