]> git.sesse.net Git - vlc/blob - modules/access/wasapi.c
wasapi: add loopback mode
[vlc] / modules / access / wasapi.c
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
7  *
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.
12  *
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.
17  *
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  *****************************************************************************/
22
23 #ifdef HAVE_CONFIG_H
24 # include "config.h"
25 #endif
26
27 #define INITGUID
28 #define COBJMACROS
29 #define CONST_VTABLE
30
31 #include <assert.h>
32 #include <stdlib.h>
33
34 #include <vlc_common.h>
35 #include <vlc_aout.h>
36 #include <vlc_demux.h>
37 #include <vlc_plugin.h>
38 #include <mmdeviceapi.h>
39 #include <audioclient.h>
40
41 static LARGE_INTEGER freq; /* performance counters frequency */
42
43 BOOL WINAPI DllMain(HINSTANCE, DWORD, LPVOID); /* avoid warning */
44
45 BOOL WINAPI DllMain(HINSTANCE dll, DWORD reason, LPVOID reserved)
46 {
47     (void) dll;
48     (void) reserved;
49
50     switch (reason)
51     {
52         case DLL_PROCESS_ATTACH:
53             if (!QueryPerformanceFrequency(&freq))
54                 return FALSE;
55             break;
56     }
57     return TRUE;
58 }
59
60 static UINT64 GetQPC(void)
61 {
62     LARGE_INTEGER counter;
63
64     if (!QueryPerformanceCounter(&counter))
65         abort();
66
67     lldiv_t d = lldiv(counter.QuadPart, freq.QuadPart);
68     return (d.quot * 10000000) + ((d.rem * 10000000) / freq.QuadPart);
69 }
70
71 static_assert(CLOCK_FREQ * 10 == 10000000,
72               "REFERENCE_TIME conversion broken");
73
74 static EDataFlow GetDeviceFlow(IMMDevice *dev)
75 {
76     void *pv;
77
78     if (FAILED(IMMDevice_QueryInterface(dev, &IID_IMMEndpoint, &pv)))
79         return false;
80
81     IMMEndpoint *ep = pv;
82     EDataFlow flow;
83
84     if (SUCCEEDED(IMMEndpoint_GetDataFlow(ep, &flow)))
85         flow = eAll;
86     IMMEndpoint_Release(ep);
87     return flow;
88 }
89
90 static IAudioClient *GetClient(demux_t *demux, bool *restrict loopbackp)
91 {
92     IMMDeviceEnumerator *e;
93     IMMDevice *dev;
94     void *pv;
95     HRESULT hr;
96
97     hr = CoCreateInstance(&CLSID_MMDeviceEnumerator, NULL, CLSCTX_ALL,
98                           &IID_IMMDeviceEnumerator, &pv);
99     if (FAILED(hr))
100     {
101         msg_Err(demux, "cannot create device enumerator (error 0x%lx)", hr);
102         return NULL;
103     }
104     e = pv;
105
106     bool loopback = var_InheritBool(demux, "wasapi-loopback");
107     EDataFlow flow = loopback ? eRender : eCapture;
108     ERole role = loopback ? eConsole : eCommunications;
109
110     hr = IMMDeviceEnumerator_GetDefaultAudioEndpoint(e, flow, role, &dev);
111     IMMDeviceEnumerator_Release(e);
112     if (FAILED(hr))
113     {
114         msg_Err(demux, "cannot get default device (error 0x%lx)", hr);
115         return NULL;
116     }
117
118     hr = IMMDevice_Activate(dev, &IID_IAudioClient, CLSCTX_ALL, NULL, &pv);
119     *loopbackp = GetDeviceFlow(dev) == eRender;
120     IMMDevice_Release(dev);
121     if (FAILED(hr))
122         msg_Err(demux, "cannot activate device (error 0x%lx)", hr);
123     return pv;
124 }
125
126 static int vlc_FromWave(const WAVEFORMATEX *restrict wf,
127                         audio_sample_format_t *restrict fmt)
128 {
129     fmt->i_rate = wf->nSamplesPerSec;
130
131     /* As per MSDN, IAudioClient::GetMixFormat() always uses this format. */
132     assert(wf->wFormatTag == WAVE_FORMAT_EXTENSIBLE);
133
134     const WAVEFORMATEXTENSIBLE *wfe = (void *)wf;
135
136     fmt->i_physical_channels = 0;
137     if (wfe->dwChannelMask & SPEAKER_FRONT_LEFT)
138         fmt->i_physical_channels |= AOUT_CHAN_LEFT;
139     if (wfe->dwChannelMask & SPEAKER_FRONT_RIGHT)
140         fmt->i_physical_channels |= AOUT_CHAN_RIGHT;
141     if (wfe->dwChannelMask & SPEAKER_FRONT_CENTER)
142         fmt->i_physical_channels |= AOUT_CHAN_CENTER;
143     if (wfe->dwChannelMask & SPEAKER_LOW_FREQUENCY)
144         fmt->i_physical_channels |= AOUT_CHAN_LFE;
145
146     fmt->i_original_channels = fmt->i_physical_channels;
147     assert(popcount(wfe->dwChannelMask) == wf->nChannels);
148
149     if (IsEqualIID(&wfe->SubFormat, &KSDATAFORMAT_SUBTYPE_PCM))
150     {
151         switch (wf->wBitsPerSample)
152         {
153             case 32:
154                 switch (wfe->Samples.wValidBitsPerSample)
155                 {
156                     case 32:
157                         fmt->i_format = VLC_CODEC_S32N;
158                         break;
159                     case 24:
160 #ifdef WORDS_BIGENDIAN
161                         fmt->i_format = VLC_CODEC_S24B32;
162 #else
163                         fmt->i_format = VLC_CODEC_S24L32;
164 #endif
165                         break;
166                     default:
167                         return -1;
168                 }
169                 break;
170             case 24:
171                 if (wfe->Samples.wValidBitsPerSample == 24)
172                     fmt->i_format = VLC_CODEC_S24N;
173                 else
174                     return -1;
175                 break;
176             case 16:
177                 if (wfe->Samples.wValidBitsPerSample == 16)
178                     fmt->i_format = VLC_CODEC_S16N;
179                 else
180                     return -1;
181                 break;
182             case 8:
183                 if (wfe->Samples.wValidBitsPerSample == 8)
184                     fmt->i_format = VLC_CODEC_S8;
185                 else
186                     return -1;
187                 break;
188             default:
189                 return -1;
190         }
191     }
192     else if (IsEqualIID(&wfe->SubFormat, &KSDATAFORMAT_SUBTYPE_IEEE_FLOAT))
193     {
194         if (wf->wBitsPerSample != wfe->Samples.wValidBitsPerSample)
195             return -1;
196
197         switch (wf->wBitsPerSample)
198         {
199             case 64:
200                 fmt->i_format = VLC_CODEC_FL64;
201                 break;
202             case 32:
203                 fmt->i_format = VLC_CODEC_FL32;
204                 break;
205             default:
206                 return -1;
207         }
208     }
209     /*else if (IsEqualIID(&wfe->Subformat, &KSDATAFORMAT_SUBTYPE_DRM)) {} */
210     else if (IsEqualIID(&wfe->SubFormat, &KSDATAFORMAT_SUBTYPE_ALAW))
211         fmt->i_format = VLC_CODEC_ALAW;
212     else if (IsEqualIID(&wfe->SubFormat, &KSDATAFORMAT_SUBTYPE_MULAW))
213         fmt->i_format = VLC_CODEC_MULAW;
214     else if (IsEqualIID(&wfe->SubFormat, &KSDATAFORMAT_SUBTYPE_ADPCM))
215         fmt->i_format = VLC_CODEC_ADPCM_MS;
216     else
217         return -1;
218
219     aout_FormatPrepare(fmt);
220     if (wf->nChannels != fmt->i_channels)
221         return -1;
222
223     return 0;
224 }
225
226 static es_out_id_t *CreateES(demux_t *demux, IAudioClient *client, bool loop,
227                              mtime_t caching, size_t *restrict frame_size)
228 {
229     es_format_t fmt;
230     WAVEFORMATEX *pwf;
231     HRESULT hr;
232
233     hr = IAudioClient_GetMixFormat(client, &pwf);
234     if (FAILED(hr))
235     {
236         msg_Err(demux, "cannot get mix format (error 0x%lx)", hr);
237         return NULL;
238     }
239
240     es_format_Init(&fmt, AUDIO_ES, 0);
241     if (vlc_FromWave(pwf, &fmt.audio))
242     {
243         msg_Err(demux, "unsupported mix format");
244         CoTaskMemFree(pwf);
245         return NULL;
246     }
247
248     fmt.i_codec = fmt.audio.i_format;
249     fmt.i_bitrate = fmt.audio.i_bitspersample * fmt.audio.i_channels
250                                               * fmt.audio.i_rate;
251     *frame_size = fmt.audio.i_bitspersample * fmt.audio.i_channels / 8;
252
253     DWORD flags = AUDCLNT_STREAMFLAGS_EVENTCALLBACK;
254     if (loop)
255         flags |= AUDCLNT_STREAMFLAGS_LOOPBACK;
256
257     /* Request at least thrice the PTS delay */
258     REFERENCE_TIME bufsize = caching * INT64_C(10) * 3;
259
260     hr = IAudioClient_Initialize(client, AUDCLNT_SHAREMODE_SHARED, flags,
261                                  bufsize, 0, pwf, NULL);
262     CoTaskMemFree(pwf);
263     if (FAILED(hr))
264     {
265         msg_Err(demux, "cannot initialize audio client (error 0x%lx)", hr);
266         return NULL;
267     }
268     return es_out_Add(demux->out, &fmt);
269 }
270
271 struct demux_sys_t
272 {
273     IAudioClient *client;
274     es_out_id_t *es;
275
276     size_t frame_size;
277     mtime_t caching;
278     mtime_t start_time;
279
280     HANDLE events[2];
281     union {
282         HANDLE thread;
283         HANDLE ready;
284     };
285 };
286
287 static unsigned __stdcall Thread(void *data)
288 {
289     demux_t *demux = data;
290     demux_sys_t *sys = demux->p_sys;
291     IAudioCaptureClient *capture = NULL;
292     void *pv;
293     HRESULT hr;
294
295     hr = CoInitializeEx(NULL, COINIT_MULTITHREADED);
296     assert(SUCCEEDED(hr)); /* COM already allocated by parent thread */
297     SetEvent(sys->ready);
298
299     hr = IAudioClient_GetService(sys->client, &IID_IAudioCaptureClient, &pv);
300     if (FAILED(hr))
301     {
302         msg_Err(demux, "cannot get capture client (error 0x%lx)", hr);
303         goto out;
304     }
305     capture = pv;
306
307     hr = IAudioClient_Start(sys->client);
308     if (FAILED(hr))
309     {
310         msg_Err(demux, "cannot start client (error 0x%lx)", hr);
311         IAudioCaptureClient_Release(capture);
312         goto out;
313     }
314
315     while (WaitForMultipleObjects(2, sys->events, FALSE, INFINITE)
316             != WAIT_OBJECT_0)
317     {
318         BYTE *data;
319         UINT32 frames;
320         DWORD flags;
321         UINT64 qpc;
322         mtime_t pts;
323
324         hr = IAudioCaptureClient_GetBuffer(capture, &data, &frames, &flags,
325                                            NULL, &qpc);
326         if (hr != S_OK)
327             continue;
328
329         pts = mdate() - ((GetQPC() - qpc) / 10);
330
331         es_out_Control(demux->out, ES_OUT_SET_PCR, pts);
332
333         size_t bytes = frames * sys->frame_size;
334         block_t *block = block_Alloc(bytes);
335
336         if (likely(block != NULL)) {
337             memcpy(block->p_buffer, data, bytes);
338             block->i_nb_samples = frames;
339             block->i_pts = block->i_dts = pts;
340             es_out_Send(demux->out, sys->es, block);
341         }
342
343         IAudioCaptureClient_ReleaseBuffer(capture, frames);
344     }
345
346     IAudioClient_Stop(sys->client);
347     IAudioCaptureClient_Release(capture);
348 out:
349     CoUninitialize();
350     return 0;
351 }
352
353 static int Control(demux_t *demux, int query, va_list ap)
354 {
355     demux_sys_t *sys = demux->p_sys;
356
357     switch (query)
358     {
359         case DEMUX_GET_TIME:
360             *(va_arg(ap, int64_t *)) = mdate() - sys->start_time;
361             break;
362
363         case DEMUX_GET_PTS_DELAY:
364             *(va_arg(ap, int64_t *)) = sys->caching;
365             break;
366
367         case DEMUX_HAS_UNSUPPORTED_META:
368         case DEMUX_CAN_RECORD:
369         case DEMUX_CAN_PAUSE:
370         case DEMUX_CAN_CONTROL_PACE:
371         case DEMUX_CAN_CONTROL_RATE:
372         case DEMUX_CAN_SEEK:
373             *(va_arg(ap, bool *)) = false;
374             break;
375
376         default:
377             return VLC_EGENERIC;
378     }
379
380     return VLC_SUCCESS;
381 }
382
383 static int Open(vlc_object_t *obj)
384 {
385     demux_t *demux = (demux_t *)obj;
386     HRESULT hr;
387
388     if (demux->psz_location != NULL && demux->psz_location != '\0')
389         return VLC_EGENERIC; /* TODO non-default device */
390
391     demux_sys_t *sys = malloc(sizeof (*sys));
392     if (unlikely(sys == NULL))
393         return VLC_ENOMEM;
394
395     sys->client = NULL;
396     sys->es = NULL;
397     sys->caching = INT64_C(1000) * var_InheritInteger(obj, "live-caching");
398     sys->start_time = mdate();
399     for (unsigned i = 0; i < 2; i++)
400         sys->events[i] = NULL;
401
402     for (unsigned i = 0; i < 2; i++) {
403         sys->events[i] = CreateEvent(NULL, FALSE, FALSE, NULL);
404         if (sys->events[i] == NULL)
405             goto error;
406     }
407
408     hr = CoInitializeEx(NULL, COINIT_MULTITHREADED);
409     if (unlikely(FAILED(hr))) {
410         msg_Err(demux, "cannot initialize COM (error 0x%lx)", hr);
411         goto error;
412     }
413
414     bool loopback;
415     sys->client = GetClient(demux, &loopback);
416     if (sys->client == NULL) {
417         CoUninitialize();
418         goto error;
419     }
420
421     sys->es = CreateES(demux, sys->client, loopback, sys->caching,
422                        &sys->frame_size);
423     if (sys->es == NULL)
424         goto error;
425
426     hr = IAudioClient_SetEventHandle(sys->client, sys->events[1]);
427     if (FAILED(hr)) {
428         msg_Err(demux, "cannot set event handle (error 0x%lx)", hr);
429         goto error;
430     }
431
432     demux->p_sys = sys;
433
434     sys->ready = CreateEvent(NULL, FALSE, FALSE, NULL);
435     if (sys->ready == NULL)
436         goto error;
437
438     uintptr_t h = _beginthreadex(NULL, 0, Thread, demux, 0, NULL);
439     if (h != 0)
440         WaitForSingleObject(sys->ready, INFINITE);
441     CloseHandle(sys->ready);
442
443     sys->thread = (HANDLE)h;
444     if (sys->thread == NULL)
445         goto error;
446     CoUninitialize();
447
448     demux->pf_demux = NULL;
449     demux->pf_control = Control;
450     return VLC_SUCCESS;
451
452 error:
453     if (sys->es != NULL)
454         es_out_Del(demux->out, sys->es);
455     if (sys->client != NULL)
456     {
457         IAudioClient_Release(sys->client);
458         CoUninitialize();
459     }
460     for (unsigned i = 0; i < 2; i++)
461         if (sys->events[i] != NULL)
462             CloseHandle(sys->events[i]);
463     free(sys);
464     return VLC_ENOMEM;
465 }
466
467 static void Close (vlc_object_t *obj)
468 {
469     demux_t *demux = (demux_t *)obj;
470     demux_sys_t *sys = demux->p_sys;
471     HRESULT hr;
472
473     hr = CoInitializeEx(NULL, COINIT_MULTITHREADED);
474     assert(SUCCEEDED(hr));
475
476     SetEvent(sys->events[0]);
477     WaitForSingleObject(sys->thread, INFINITE);
478     CloseHandle(sys->thread);
479
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]);
485     free(sys);
486 }
487
488 #define LOOPBACK_TEXT N_("Loopback mode")
489 #define LOOPBACK_LONGTEXT N_("Record an audio rendering endpoint.")
490
491 vlc_module_begin()
492     set_shortname(N_("WASAPI"))
493     set_description(N_("Windows Audio Session API input"))
494     set_capability("access_demux", 0)
495     set_category(CAT_INPUT)
496     set_subcategory(SUBCAT_INPUT_ACCESS)
497
498     add_bool("wasapi-loopback", false, LOOPBACK_TEXT, LOOPBACK_LONGTEXT, true)
499
500     add_shortcut("wasapi")
501     set_callbacks(Open, Close)
502 vlc_module_end()