3 * \brief Windows Audio Session API capture plugin for VLC
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 *****************************************************************************/
35 #include <vlc_common.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
)
51 case DLL_PROCESS_ATTACH
:
52 if (!QueryPerformanceFrequency(&freq
))
59 static msftime_t
GetQPC(void)
61 LARGE_INTEGER counter
;
63 if (!QueryPerformanceCounter(&counter
))
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
)
77 if (FAILED(IMMDevice_QueryInterface(dev
, &IID_IMMEndpoint
, &pv
)))
83 if (SUCCEEDED(IMMEndpoint_GetDataFlow(ep
, &flow
)))
85 IMMEndpoint_Release(ep
);
89 static IAudioClient
*GetClient(demux_t
*demux
, bool *restrict loopbackp
)
91 IMMDeviceEnumerator
*e
;
96 hr
= CoCreateInstance(&CLSID_MMDeviceEnumerator
, NULL
, CLSCTX_ALL
,
97 &IID_IMMDeviceEnumerator
, &pv
);
100 msg_Err(demux
, "cannot create device enumerator (error 0x%lX)", hr
);
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
);
113 msg_Err(demux
, "cannot get default device (error 0x%lX)", hr
);
117 hr
= IMMDevice_Activate(dev
, &IID_IAudioClient
, CLSCTX_ALL
, NULL
, &pv
);
118 *loopbackp
= GetDeviceFlow(dev
) == eRender
;
119 IMMDevice_Release(dev
);
121 msg_Err(demux
, "cannot activate device (error 0x%lX)", hr
);
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
)
152 switch (wfe
->Samples
.wValidBitsPerSample
)
155 fmt
->i_format
= VLC_CODEC_S32N
;
158 #ifdef WORDS_BIGENDIAN
159 fmt
->i_format
= VLC_CODEC_S24B32
;
161 fmt
->i_format
= VLC_CODEC_S24L32
;
169 if (wfe
->Samples
.wValidBitsPerSample
== 24)
170 fmt
->i_format
= VLC_CODEC_S24N
;
175 if (wfe
->Samples
.wValidBitsPerSample
== 16)
176 fmt
->i_format
= VLC_CODEC_S16N
;
181 if (wfe
->Samples
.wValidBitsPerSample
== 8)
182 fmt
->i_format
= VLC_CODEC_S8
;
190 else if (IsEqualIID(&wfe
->SubFormat
, &KSDATAFORMAT_SUBTYPE_IEEE_FLOAT
))
192 if (wf
->wBitsPerSample
!= wfe
->Samples
.wValidBitsPerSample
)
195 switch (wf
->wBitsPerSample
)
198 fmt
->i_format
= VLC_CODEC_FL64
;
201 fmt
->i_format
= VLC_CODEC_FL32
;
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
;
217 aout_FormatPrepare(fmt
);
218 if (wf
->nChannels
!= fmt
->i_channels
)
224 static es_out_id_t
*CreateES(demux_t
*demux
, IAudioClient
*client
, bool loop
,
225 vlc_tick_t caching
, size_t *restrict frame_size
)
231 hr
= IAudioClient_GetMixFormat(client
, &pwf
);
234 msg_Err(demux
, "cannot get mix format (error 0x%lX)", hr
);
238 es_format_Init(&fmt
, AUDIO_ES
, 0);
239 if (vlc_FromWave(pwf
, &fmt
.audio
))
241 msg_Err(demux
, "unsupported mix format");
246 fmt
.i_codec
= fmt
.audio
.i_format
;
247 fmt
.i_bitrate
= fmt
.audio
.i_bitspersample
* fmt
.audio
.i_channels
249 *frame_size
= fmt
.audio
.i_bitspersample
* fmt
.audio
.i_channels
/ 8;
251 DWORD flags
= AUDCLNT_STREAMFLAGS_EVENTCALLBACK
;
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
);
263 msg_Err(demux
, "cannot initialize audio client (error 0x%lX)", hr
);
266 return es_out_Add(demux
->out
, &fmt
);
271 IAudioClient
*client
;
276 vlc_tick_t start_time
;
285 static unsigned __stdcall
Thread(void *data
)
287 demux_t
*demux
= data
;
288 demux_sys_t
*sys
= demux
->p_sys
;
289 IAudioCaptureClient
*capture
= NULL
;
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
);
300 msg_Err(demux
, "cannot get capture client (error 0x%lX)", hr
);
305 hr
= IAudioClient_Start(sys
->client
);
308 msg_Err(demux
, "cannot start client (error 0x%lX)", hr
);
309 IAudioCaptureClient_Release(capture
);
313 while (WaitForMultipleObjects(2, sys
->events
, FALSE
, INFINITE
)
322 hr
= IAudioCaptureClient_GetBuffer(capture
, &buf
, &frames
, &flags
,
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
);
351 static int Control(demux_t
*demux
, int query
, va_list ap
)
353 demux_sys_t
*sys
= demux
->p_sys
;
358 *(va_arg(ap
, vlc_tick_t
*)) = vlc_tick_now() - sys
->start_time
;
361 case DEMUX_GET_PTS_DELAY
:
362 *(va_arg(ap
, vlc_tick_t
*)) = sys
->caching
;
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
:
371 *(va_arg(ap
, bool *)) = false;
381 static int Open(vlc_object_t
*obj
)
383 demux_t
*demux
= (demux_t
*)obj
;
386 if (demux
->out
== NULL
)
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
))
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
)
409 hr
= CoInitializeEx(NULL
, COINIT_MULTITHREADED
);
410 if (unlikely(FAILED(hr
))) {
411 msg_Err(demux
, "cannot initialize COM (error 0x%lX)", hr
);
416 sys
->client
= GetClient(demux
, &loopback
);
417 if (sys
->client
== NULL
) {
422 sys
->es
= CreateES(demux
, sys
->client
, loopback
, sys
->caching
,
427 hr
= IAudioClient_SetEventHandle(sys
->client
, sys
->events
[1]);
429 msg_Err(demux
, "cannot set event handle (error 0x%lX)", hr
);
435 sys
->ready
= CreateEvent(NULL
, FALSE
, FALSE
, NULL
);
436 if (sys
->ready
== NULL
)
439 uintptr_t h
= _beginthreadex(NULL
, 0, Thread
, demux
, 0, NULL
);
441 WaitForSingleObject(sys
->ready
, INFINITE
);
442 CloseHandle(sys
->ready
);
444 sys
->thread
= (HANDLE
)h
;
445 if (sys
->thread
== NULL
)
449 demux
->pf_demux
= NULL
;
450 demux
->pf_control
= Control
;
455 es_out_Del(demux
->out
, sys
->es
);
456 if (sys
->client
!= NULL
)
458 IAudioClient_Release(sys
->client
);
461 for (unsigned i
= 0; i
< 2; i
++)
462 if (sys
->events
[i
] != NULL
)
463 CloseHandle(sys
->events
[i
]);
467 static void Close (vlc_object_t
*obj
)
469 demux_t
*demux
= (demux_t
*)obj
;
470 demux_sys_t
*sys
= demux
->p_sys
;
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
);
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.")
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
)