[vlc-devel] [PATCH 2/2] wasapi: audio capture client module (fixes #7205)
Rémi Denis-Courmont
remi at remlab.net
Sun Mar 22 13:35:07 CET 2015
---
modules/access/Makefile.am | 6 +
modules/access/wasapi.c | 467 +++++++++++++++++++++++++++++++++++++++++++++
2 files changed, 473 insertions(+)
create mode 100644 modules/access/wasapi.c
diff --git a/modules/access/Makefile.am b/modules/access/Makefile.am
index 3088fdb..2522a29 100644
--- a/modules/access/Makefile.am
+++ b/modules/access/Makefile.am
@@ -105,6 +105,12 @@ if HAVE_QTKIT
access_LTLIBRARIES += libqtsound_plugin.la
endif
+libaccess_wasapi_plugin_la_SOURCES = access/wasapi.c
+libaccess_wasapi_plugin_la_LIBADD = -lole32 -lksuser
+if HAVE_WASAPI
+access_LTLIBRARIES += libaccess_wasapi_plugin.la
+endif
+
### Video capture ###
diff --git a/modules/access/wasapi.c b/modules/access/wasapi.c
new file mode 100644
index 0000000..387546c
--- /dev/null
+++ b/modules/access/wasapi.c
@@ -0,0 +1,467 @@
+/**
+ * \file wasapi.c
+ * \brief Windows Audio Session API capture plugin for VLC
+ */
+/*****************************************************************************
+ * Copyright (C) 2014-2015 Rémi Denis-Courmont
+ *
+ * This program is free software; you can redistribute it and/or modify it
+ * under the terms of the GNU Lesser General Public License as published by
+ * the Free Software Foundation; either version 2.1 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU Lesser General Public License for more details.
+ *
+ * You should have received a copy of the GNU Lesser General Public License
+ * along with this program; if not, write to the Free Software Foundation,
+ * Inc., 51 Franklin Street, Fifth Floor, Boston MA 02110-1301, USA.
+ *****************************************************************************/
+
+#ifdef HAVE_CONFIG_H
+# include "config.h"
+#endif
+
+#define INITGUID
+#define COBJMACROS
+#define CONST_VTABLE
+
+#include <assert.h>
+#include <stdlib.h>
+
+#include <vlc_common.h>
+#include <vlc_aout.h>
+#include <vlc_demux.h>
+#include <vlc_plugin.h>
+#include <mmdeviceapi.h>
+#include <audioclient.h>
+
+static LARGE_INTEGER freq; /* performance counters frequency */
+
+BOOL WINAPI DllMain(HINSTANCE, DWORD, LPVOID); /* avoid warning */
+
+BOOL WINAPI DllMain(HINSTANCE dll, DWORD reason, LPVOID reserved)
+{
+ (void) dll;
+ (void) reserved;
+
+ switch (reason)
+ {
+ case DLL_PROCESS_ATTACH:
+ if (!QueryPerformanceFrequency(&freq))
+ return FALSE;
+ break;
+ }
+ return TRUE;
+}
+
+static UINT64 GetQPC(void)
+{
+ LARGE_INTEGER counter;
+
+ if (!QueryPerformanceCounter(&counter))
+ abort();
+
+ lldiv_t d = lldiv(counter.QuadPart, freq.QuadPart);
+ return (d.quot * 10000000) + ((d.rem * 10000000) / freq.QuadPart);
+}
+
+struct demux_sys_t
+{
+ IAudioClient *client;
+ es_out_id_t *es;
+
+ size_t frame_size;
+ mtime_t caching;
+ mtime_t start_time;
+
+ HANDLE events[2];
+ HANDLE thread;
+ SYNCHRONIZATION_BARRIER ready;
+};
+
+static IAudioClient *GetClient(demux_t *demux)
+{
+ IMMDeviceEnumerator *e;
+ IMMDevice *dev;
+ void *pv;
+ HRESULT hr;
+
+ hr = CoCreateInstance(&CLSID_MMDeviceEnumerator, NULL, CLSCTX_ALL,
+ &IID_IMMDeviceEnumerator, &pv);
+ if (FAILED(hr))
+ {
+ msg_Err(demux, "cannot create device enumerator (error 0x%lx)", hr);
+ return NULL;
+ }
+ e = pv;
+
+ hr = IMMDeviceEnumerator_GetDefaultAudioEndpoint(e, eCapture,
+ eCommunications, &dev);
+ IMMDeviceEnumerator_Release(e);
+ if (FAILED(hr))
+ {
+ msg_Err(demux, "cannot get default device (error 0x%lx)", hr);
+ return NULL;
+ }
+
+ hr = IMMDevice_Activate(dev, &IID_IAudioClient, CLSCTX_ALL, NULL, &pv);
+ IMMDevice_Release(dev);
+ if (FAILED(hr))
+ msg_Err(demux, "cannot activate device (error 0x%lx)", hr);
+ return pv;
+}
+
+static int vlc_FromWave(const WAVEFORMATEX *restrict wf,
+ audio_sample_format_t *restrict fmt)
+{
+ fmt->i_rate = wf->nSamplesPerSec;
+
+ /* As per MSDN, IAudioClient::GetMixFormat() always uses this format. */
+ assert(wf->wFormatTag == WAVE_FORMAT_EXTENSIBLE);
+
+ const WAVEFORMATEXTENSIBLE *wfe = (void *)wf;
+
+ fmt->i_physical_channels = 0;
+ if (wfe->dwChannelMask & SPEAKER_FRONT_LEFT)
+ fmt->i_physical_channels |= AOUT_CHAN_LEFT;
+ if (wfe->dwChannelMask & SPEAKER_FRONT_RIGHT)
+ fmt->i_physical_channels |= AOUT_CHAN_RIGHT;
+ if (wfe->dwChannelMask & SPEAKER_FRONT_CENTER)
+ fmt->i_physical_channels |= AOUT_CHAN_CENTER;
+ if (wfe->dwChannelMask & SPEAKER_LOW_FREQUENCY)
+ fmt->i_physical_channels |= AOUT_CHAN_LFE;
+
+ fmt->i_original_channels = fmt->i_physical_channels;
+ assert(popcount(wfe->dwChannelMask) == wf->nChannels);
+
+ if (IsEqualIID(&wfe->SubFormat, &KSDATAFORMAT_SUBTYPE_PCM))
+ {
+ switch (wf->wBitsPerSample)
+ {
+ case 32:
+ switch (wfe->Samples.wValidBitsPerSample)
+ {
+ case 32:
+ fmt->i_format = VLC_CODEC_S32N;
+ break;
+ case 24:
+#ifdef WORDS_BIGENDIAN
+ fmt->i_format = VLC_CODEC_S24B32;
+#else
+ fmt->i_format = VLC_CODEC_S24L32;
+#endif
+ break;
+ default:
+ return -1;
+ }
+ break;
+ case 24:
+ if (wfe->Samples.wValidBitsPerSample == 24)
+ fmt->i_format = VLC_CODEC_S24N;
+ else
+ return -1;
+ break;
+ case 16:
+ if (wfe->Samples.wValidBitsPerSample == 16)
+ fmt->i_format = VLC_CODEC_S16N;
+ else
+ return -1;
+ break;
+ case 8:
+ if (wfe->Samples.wValidBitsPerSample == 8)
+ fmt->i_format = VLC_CODEC_S8;
+ else
+ return -1;
+ break;
+ default:
+ return -1;
+ }
+ }
+ else if (IsEqualIID(&wfe->SubFormat, &KSDATAFORMAT_SUBTYPE_IEEE_FLOAT))
+ {
+ if (wf->wBitsPerSample != wfe->Samples.wValidBitsPerSample)
+ return -1;
+
+ switch (wf->wBitsPerSample)
+ {
+ case 64:
+ fmt->i_format = VLC_CODEC_FL64;
+ break;
+ case 32:
+ fmt->i_format = VLC_CODEC_FL32;
+ break;
+ default:
+ return -1;
+ }
+ }
+ /*else if (IsEqualIID(&wfe->Subformat, &KSDATAFORMAT_SUBTYPE_DRM)) {} */
+ else if (IsEqualIID(&wfe->SubFormat, &KSDATAFORMAT_SUBTYPE_ALAW))
+ fmt->i_format = VLC_CODEC_ALAW;
+ else if (IsEqualIID(&wfe->SubFormat, &KSDATAFORMAT_SUBTYPE_MULAW))
+ fmt->i_format = VLC_CODEC_MULAW;
+ else if (IsEqualIID(&wfe->SubFormat, &KSDATAFORMAT_SUBTYPE_ADPCM))
+ fmt->i_format = VLC_CODEC_ADPCM_MS;
+ else
+ return -1;
+
+ aout_FormatPrepare(fmt);
+ if (wf->nChannels != fmt->i_channels)
+ return -1;
+
+ return 0;
+}
+
+static es_out_id_t *CreateES(demux_t *demux, IAudioClient *client,
+ mtime_t caching, size_t *restrict frame_size)
+{
+ es_format_t fmt;
+ WAVEFORMATEX *pwf;
+ HRESULT hr;
+
+ hr = IAudioClient_GetMixFormat(client, &pwf);
+ if (FAILED(hr))
+ {
+ msg_Err(demux, "cannot get mix format (error 0x%lx)", hr);
+ return NULL;
+ }
+
+ es_format_Init(&fmt, AUDIO_ES, 0);
+ if (vlc_FromWave(pwf, &fmt.audio))
+ {
+ msg_Err(demux, "unsupported mix format");
+ CoTaskMemFree(pwf);
+ return NULL;
+ }
+
+ fmt.i_codec = fmt.audio.i_format;
+ fmt.i_bitrate = fmt.audio.i_bitspersample * fmt.audio.i_channels
+ * fmt.audio.i_rate;
+ *frame_size = fmt.audio.i_bitspersample * fmt.audio.i_channels / 8;
+
+ DWORD flags = AUDCLNT_STREAMFLAGS_EVENTCALLBACK; /* TODO: loopback */
+ /* Request at least thrice the PTS delay */
+ REFERENCE_TIME bufsize = caching * INT64_C(10000) * 3;
+
+ hr = IAudioClient_Initialize(client, AUDCLNT_SHAREMODE_SHARED, flags,
+ bufsize, 0, pwf, NULL);
+ CoTaskMemFree(pwf);
+ if (FAILED(hr))
+ {
+ msg_Err(demux, "cannot initialize audio client (error 0x%lx)", hr);
+ return NULL;
+ }
+ return es_out_Add(demux->out, &fmt);
+}
+
+static unsigned __stdcall Thread(void *data)
+{
+ demux_t *demux = data;
+ demux_sys_t *sys = demux->p_sys;
+ IAudioCaptureClient *capture = NULL;
+ void *pv;
+ HRESULT hr;
+
+ hr = CoInitializeEx(NULL, COINIT_MULTITHREADED);
+ assert(SUCCEEDED(hr)); /* COM already allocated by parent thread */
+ EnterSynchronizationBarrier(&sys->ready, 0);
+
+ hr = IAudioClient_GetService(sys->client, &IID_IAudioCaptureClient, &pv);
+ if (FAILED(hr))
+ {
+ msg_Err(demux, "cannot get capture client (error 0x%lx)", hr);
+ goto out;
+ }
+ capture = pv;
+
+ hr = IAudioClient_Start(sys->client);
+ if (FAILED(hr))
+ {
+ msg_Err(demux, "cannot start client (error 0x%lx)", hr);
+ IAudioCaptureClient_Release(capture);
+ goto out;
+ }
+
+ while (WaitForMultipleObjects(2, sys->events, FALSE, INFINITE)
+ != WAIT_OBJECT_0)
+ {
+ BYTE *data;
+ UINT32 frames;
+ DWORD flags;
+ UINT64 qpc;
+ mtime_t pts;
+
+ hr = IAudioCaptureClient_GetBuffer(capture, &data, &frames, &flags,
+ NULL, &qpc);
+ if (hr != S_OK)
+ continue;
+
+ static_assert((10000000 % CLOCK_FREQ) == 0,
+ "Frequency conversion broken");
+ pts = mdate() - ((GetQPC() - qpc) / (10000000 / CLOCK_FREQ));
+
+ es_out_Control(demux->out, ES_OUT_SET_PCR, pts);
+
+ size_t bytes = frames * sys->frame_size;
+ block_t *block = block_Alloc(bytes);
+
+ if (likely(block != NULL)) {
+ memcpy(block->p_buffer, data, bytes);
+ block->i_nb_samples = frames;
+ block->i_pts = block->i_dts = pts;
+ es_out_Send(demux->out, sys->es, block);
+ }
+
+ IAudioCaptureClient_ReleaseBuffer(capture, frames);
+ }
+
+ IAudioClient_Stop(sys->client);
+ IAudioCaptureClient_Release(capture);
+out:
+ CoUninitialize();
+ return 0;
+}
+
+static int Control(demux_t *demux, int query, va_list ap)
+{
+ demux_sys_t *sys = demux->p_sys;
+
+ switch (query)
+ {
+ case DEMUX_GET_TIME:
+ *(va_arg(ap, int64_t *)) = mdate() - sys->start_time;
+ break;
+
+ case DEMUX_GET_PTS_DELAY:
+ *(va_arg(ap, int64_t *)) = sys->caching;
+ break;
+
+ case DEMUX_HAS_UNSUPPORTED_META:
+ case DEMUX_CAN_RECORD:
+ case DEMUX_CAN_PAUSE:
+ case DEMUX_CAN_CONTROL_PACE:
+ case DEMUX_CAN_CONTROL_RATE:
+ case DEMUX_CAN_SEEK:
+ *(va_arg(ap, bool *)) = false;
+ break;
+
+ default:
+ return VLC_EGENERIC;
+ }
+
+ return VLC_SUCCESS;
+}
+
+static int Open(vlc_object_t *obj)
+{
+ demux_t *demux = (demux_t *)obj;
+ HRESULT hr;
+
+ if (demux->psz_location != NULL && demux->psz_location != '\0')
+ return VLC_EGENERIC; /* TODO non-default device */
+
+ demux_sys_t *sys = malloc(sizeof (*sys));
+ if (unlikely(sys == NULL))
+ return VLC_ENOMEM;
+
+ sys->client = NULL;
+ sys->es = NULL;
+ sys->caching = INT64_C(1000) * var_InheritInteger(obj, "live-caching");
+ sys->start_time = mdate();
+ for (unsigned i = 0; i < 2; i++)
+ sys->events[i] = NULL;
+
+ for (unsigned i = 0; i < 2; i++) {
+ sys->events[i] = CreateEvent(NULL, FALSE, FALSE, NULL);
+ if (sys->events[i] == NULL)
+ goto error;
+ }
+
+ hr = CoInitializeEx(NULL, COINIT_MULTITHREADED);
+ if (unlikely(FAILED(hr))) {
+ msg_Err(demux, "cannot initialize COM (error 0x%lx)", hr);
+ goto error;
+ }
+
+ sys->client = GetClient(demux);
+ if (sys->client == NULL) {
+ CoUninitialize();
+ goto error;
+ }
+
+ sys->es = CreateES(demux, sys->client, sys->caching, &sys->frame_size);
+ if (sys->es == NULL)
+ goto error;
+
+ hr = IAudioClient_SetEventHandle(sys->client, sys->events[1]);
+ if (FAILED(hr)) {
+ msg_Err(demux, "cannot set event handle (error 0x%lx)", hr);
+ goto error;
+ }
+
+ demux->p_sys = sys;
+
+ InitializeSynchronizationBarrier(&sys->ready, 2, 0);
+
+ uintptr_t h = _beginthreadex(NULL, 0, Thread, demux, 0, NULL);
+ if (h == 0)
+ goto error;
+
+ EnterSynchronizationBarrier(&sys->ready, 0);
+ CoUninitialize();
+
+ sys->thread = (HANDLE)h;
+ DeleteSynchronizationBarrier(&sys->ready);
+
+ demux->pf_demux = NULL;
+ demux->pf_control = Control;
+ return VLC_SUCCESS;
+
+error:
+ if (sys->es != NULL)
+ es_out_Del(demux->out, sys->es);
+ if (sys->client != NULL)
+ {
+ IAudioClient_Release(sys->client);
+ CoUninitialize();
+ }
+ for (unsigned i = 0; i < 2; i++)
+ if (sys->events[i] != NULL)
+ CloseHandle(sys->events[i]);
+ free(sys);
+ return VLC_ENOMEM;
+}
+
+static void Close (vlc_object_t *obj)
+{
+ demux_t *demux = (demux_t *)obj;
+ demux_sys_t *sys = demux->p_sys;
+ HRESULT hr;
+
+ hr = CoInitializeEx(NULL, COINIT_MULTITHREADED);
+ assert(SUCCEEDED(hr));
+
+ SetEvent(sys->events[0]);
+ WaitForSingleObject(sys->thread, INFINITE);
+ CloseHandle(sys->thread);
+
+ es_out_Del(demux->out, sys->es);
+ IAudioClient_Release(sys->client);
+ CoUninitialize();
+ for (unsigned i = 0; i < 2; i++)
+ CloseHandle(sys->events[i]);
+ free(sys);
+}
+
+vlc_module_begin()
+ set_shortname(N_("WASAPI"))
+ set_description(N_("Windows Audio Session API input"))
+ set_capability("access_demux", 0)
+ set_category(CAT_INPUT)
+ set_subcategory(SUBCAT_INPUT_ACCESS)
+
+ add_shortcut("wasapi")
+ set_callbacks(Open, Close)
+vlc_module_end()
--
2.1.4
More information about the vlc-devel
mailing list