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