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 *****************************************************************************/
34 #include <vlc_common.h>
36 #include <vlc_demux.h>
37 #include <vlc_plugin.h>
38 #include <mmdeviceapi.h>
39 #include <audioclient.h>
41 static LARGE_INTEGER freq
; /* performance counters frequency */
43 BOOL WINAPI
DllMain(HINSTANCE
, DWORD
, LPVOID
); /* avoid warning */
45 BOOL WINAPI
DllMain(HINSTANCE dll
, DWORD reason
, LPVOID reserved
)
52 case DLL_PROCESS_ATTACH
:
53 if (!QueryPerformanceFrequency(&freq
))
60 static UINT64
GetQPC(void)
62 LARGE_INTEGER counter
;
64 if (!QueryPerformanceCounter(&counter
))
67 lldiv_t d
= lldiv(counter
.QuadPart
, freq
.QuadPart
);
68 return (d
.quot
* 10000000) + ((d
.rem
* 10000000) / freq
.QuadPart
);
71 static_assert(CLOCK_FREQ
* 10 == 10000000,
72 "REFERENCE_TIME conversion broken");
74 static EDataFlow
GetDeviceFlow(IMMDevice
*dev
)
78 if (FAILED(IMMDevice_QueryInterface(dev
, &IID_IMMEndpoint
, &pv
)))
84 if (SUCCEEDED(IMMEndpoint_GetDataFlow(ep
, &flow
)))
86 IMMEndpoint_Release(ep
);
90 static IAudioClient
*GetClient(demux_t
*demux
, bool *restrict loopbackp
)
92 IMMDeviceEnumerator
*e
;
97 hr
= CoCreateInstance(&CLSID_MMDeviceEnumerator
, NULL
, CLSCTX_ALL
,
98 &IID_IMMDeviceEnumerator
, &pv
);
101 msg_Err(demux
, "cannot create device enumerator (error 0x%lx)", hr
);
106 bool loopback
= var_InheritBool(demux
, "wasapi-loopback");
107 EDataFlow flow
= loopback
? eRender
: eCapture
;
108 ERole role
= loopback
? eConsole
: eCommunications
;
110 hr
= IMMDeviceEnumerator_GetDefaultAudioEndpoint(e
, flow
, role
, &dev
);
111 IMMDeviceEnumerator_Release(e
);
114 msg_Err(demux
, "cannot get default device (error 0x%lx)", hr
);
118 hr
= IMMDevice_Activate(dev
, &IID_IAudioClient
, CLSCTX_ALL
, NULL
, &pv
);
119 *loopbackp
= GetDeviceFlow(dev
) == eRender
;
120 IMMDevice_Release(dev
);
122 msg_Err(demux
, "cannot activate device (error 0x%lx)", hr
);
126 static int vlc_FromWave(const WAVEFORMATEX
*restrict wf
,
127 audio_sample_format_t
*restrict fmt
)
129 fmt
->i_rate
= wf
->nSamplesPerSec
;
131 /* As per MSDN, IAudioClient::GetMixFormat() always uses this format. */
132 assert(wf
->wFormatTag
== WAVE_FORMAT_EXTENSIBLE
);
134 const WAVEFORMATEXTENSIBLE
*wfe
= (void *)wf
;
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
;
146 fmt
->i_original_channels
= fmt
->i_physical_channels
;
147 assert(popcount(wfe
->dwChannelMask
) == wf
->nChannels
);
149 if (IsEqualIID(&wfe
->SubFormat
, &KSDATAFORMAT_SUBTYPE_PCM
))
151 switch (wf
->wBitsPerSample
)
154 switch (wfe
->Samples
.wValidBitsPerSample
)
157 fmt
->i_format
= VLC_CODEC_S32N
;
160 #ifdef WORDS_BIGENDIAN
161 fmt
->i_format
= VLC_CODEC_S24B32
;
163 fmt
->i_format
= VLC_CODEC_S24L32
;
171 if (wfe
->Samples
.wValidBitsPerSample
== 24)
172 fmt
->i_format
= VLC_CODEC_S24N
;
177 if (wfe
->Samples
.wValidBitsPerSample
== 16)
178 fmt
->i_format
= VLC_CODEC_S16N
;
183 if (wfe
->Samples
.wValidBitsPerSample
== 8)
184 fmt
->i_format
= VLC_CODEC_S8
;
192 else if (IsEqualIID(&wfe
->SubFormat
, &KSDATAFORMAT_SUBTYPE_IEEE_FLOAT
))
194 if (wf
->wBitsPerSample
!= wfe
->Samples
.wValidBitsPerSample
)
197 switch (wf
->wBitsPerSample
)
200 fmt
->i_format
= VLC_CODEC_FL64
;
203 fmt
->i_format
= VLC_CODEC_FL32
;
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
;
219 aout_FormatPrepare(fmt
);
220 if (wf
->nChannels
!= fmt
->i_channels
)
226 static es_out_id_t
*CreateES(demux_t
*demux
, IAudioClient
*client
, bool loop
,
227 mtime_t caching
, size_t *restrict frame_size
)
233 hr
= IAudioClient_GetMixFormat(client
, &pwf
);
236 msg_Err(demux
, "cannot get mix format (error 0x%lx)", hr
);
240 es_format_Init(&fmt
, AUDIO_ES
, 0);
241 if (vlc_FromWave(pwf
, &fmt
.audio
))
243 msg_Err(demux
, "unsupported mix format");
248 fmt
.i_codec
= fmt
.audio
.i_format
;
249 fmt
.i_bitrate
= fmt
.audio
.i_bitspersample
* fmt
.audio
.i_channels
251 *frame_size
= fmt
.audio
.i_bitspersample
* fmt
.audio
.i_channels
/ 8;
253 DWORD flags
= AUDCLNT_STREAMFLAGS_EVENTCALLBACK
;
255 flags
|= AUDCLNT_STREAMFLAGS_LOOPBACK
;
257 /* Request at least thrice the PTS delay */
258 REFERENCE_TIME bufsize
= caching
* INT64_C(10) * 3;
260 hr
= IAudioClient_Initialize(client
, AUDCLNT_SHAREMODE_SHARED
, flags
,
261 bufsize
, 0, pwf
, NULL
);
265 msg_Err(demux
, "cannot initialize audio client (error 0x%lx)", hr
);
268 return es_out_Add(demux
->out
, &fmt
);
273 IAudioClient
*client
;
287 static unsigned __stdcall
Thread(void *data
)
289 demux_t
*demux
= data
;
290 demux_sys_t
*sys
= demux
->p_sys
;
291 IAudioCaptureClient
*capture
= NULL
;
295 hr
= CoInitializeEx(NULL
, COINIT_MULTITHREADED
);
296 assert(SUCCEEDED(hr
)); /* COM already allocated by parent thread */
297 SetEvent(sys
->ready
);
299 hr
= IAudioClient_GetService(sys
->client
, &IID_IAudioCaptureClient
, &pv
);
302 msg_Err(demux
, "cannot get capture client (error 0x%lx)", hr
);
307 hr
= IAudioClient_Start(sys
->client
);
310 msg_Err(demux
, "cannot start client (error 0x%lx)", hr
);
311 IAudioCaptureClient_Release(capture
);
315 while (WaitForMultipleObjects(2, sys
->events
, FALSE
, INFINITE
)
324 hr
= IAudioCaptureClient_GetBuffer(capture
, &data
, &frames
, &flags
,
329 pts
= mdate() - ((GetQPC() - qpc
) / 10);
331 es_out_Control(demux
->out
, ES_OUT_SET_PCR
, pts
);
333 size_t bytes
= frames
* sys
->frame_size
;
334 block_t
*block
= block_Alloc(bytes
);
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
);
343 IAudioCaptureClient_ReleaseBuffer(capture
, frames
);
346 IAudioClient_Stop(sys
->client
);
347 IAudioCaptureClient_Release(capture
);
353 static int Control(demux_t
*demux
, int query
, va_list ap
)
355 demux_sys_t
*sys
= demux
->p_sys
;
360 *(va_arg(ap
, int64_t *)) = mdate() - sys
->start_time
;
363 case DEMUX_GET_PTS_DELAY
:
364 *(va_arg(ap
, int64_t *)) = sys
->caching
;
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
:
373 *(va_arg(ap
, bool *)) = false;
383 static int Open(vlc_object_t
*obj
)
385 demux_t
*demux
= (demux_t
*)obj
;
388 if (demux
->psz_location
!= NULL
&& demux
->psz_location
!= '\0')
389 return VLC_EGENERIC
; /* TODO non-default device */
391 demux_sys_t
*sys
= malloc(sizeof (*sys
));
392 if (unlikely(sys
== 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
;
402 for (unsigned i
= 0; i
< 2; i
++) {
403 sys
->events
[i
] = CreateEvent(NULL
, FALSE
, FALSE
, NULL
);
404 if (sys
->events
[i
] == NULL
)
408 hr
= CoInitializeEx(NULL
, COINIT_MULTITHREADED
);
409 if (unlikely(FAILED(hr
))) {
410 msg_Err(demux
, "cannot initialize COM (error 0x%lx)", hr
);
415 sys
->client
= GetClient(demux
, &loopback
);
416 if (sys
->client
== NULL
) {
421 sys
->es
= CreateES(demux
, sys
->client
, loopback
, sys
->caching
,
426 hr
= IAudioClient_SetEventHandle(sys
->client
, sys
->events
[1]);
428 msg_Err(demux
, "cannot set event handle (error 0x%lx)", hr
);
434 sys
->ready
= CreateEvent(NULL
, FALSE
, FALSE
, NULL
);
435 if (sys
->ready
== NULL
)
438 uintptr_t h
= _beginthreadex(NULL
, 0, Thread
, demux
, 0, NULL
);
440 WaitForSingleObject(sys
->ready
, INFINITE
);
441 CloseHandle(sys
->ready
);
443 sys
->thread
= (HANDLE
)h
;
444 if (sys
->thread
== NULL
)
448 demux
->pf_demux
= NULL
;
449 demux
->pf_control
= Control
;
454 es_out_Del(demux
->out
, sys
->es
);
455 if (sys
->client
!= NULL
)
457 IAudioClient_Release(sys
->client
);
460 for (unsigned i
= 0; i
< 2; i
++)
461 if (sys
->events
[i
] != NULL
)
462 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
]);
488 #define LOOPBACK_TEXT N_("Loopback mode")
489 #define LOOPBACK_LONGTEXT N_("Record an audio rendering endpoint.")
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
)
498 add_bool("wasapi-loopback", false, LOOPBACK_TEXT
, LOOPBACK_LONGTEXT
, true)
500 add_shortcut("wasapi")
501 set_callbacks(Open
, Close
)