summaryrefslogtreecommitdiff
path: root/sys/wasapi
diff options
context:
space:
mode:
authorThibault Saunier <tsaunier@igalia.com>2021-09-24 16:14:03 -0300
committerThibault Saunier <tsaunier@igalia.com>2021-09-24 16:14:04 -0300
commit07ad3439ce3859244d67bfd19b4c578400a2274c (patch)
treecd47f60172d8b31ed04124e75cc56b0bc70ebc47 /sys/wasapi
parent5ff769d731e97fa05796f3ea32e039d267ad4f2f (diff)
parentca8068c6d793d7aaa6f2e2cc6324fdedfe2f33fa (diff)
Merging gst-plugins-bad
Diffstat (limited to 'sys/wasapi')
-rw-r--r--sys/wasapi/gstaudioclient3.h236
-rw-r--r--sys/wasapi/gstmmdeviceenumerator.cpp472
-rw-r--r--sys/wasapi/gstmmdeviceenumerator.h69
-rw-r--r--sys/wasapi/gstwasapi.c57
-rw-r--r--sys/wasapi/gstwasapidevice.c362
-rw-r--r--sys/wasapi/gstwasapidevice.h81
-rw-r--r--sys/wasapi/gstwasapisink.c763
-rw-r--r--sys/wasapi/gstwasapisink.h81
-rw-r--r--sys/wasapi/gstwasapisrc.c934
-rw-r--r--sys/wasapi/gstwasapisrc.h90
-rw-r--r--sys/wasapi/gstwasapiutil.c964
-rw-r--r--sys/wasapi/gstwasapiutil.h139
-rw-r--r--sys/wasapi/meson.build41
13 files changed, 4289 insertions, 0 deletions
diff --git a/sys/wasapi/gstaudioclient3.h b/sys/wasapi/gstaudioclient3.h
new file mode 100644
index 0000000000..784f478d6b
--- /dev/null
+++ b/sys/wasapi/gstaudioclient3.h
@@ -0,0 +1,236 @@
+/*
+ * Structure and enum definitions are from audioclient.h in the Windows 10 SDK
+ *
+ * These should be defined by MinGW, but they aren't yet since they're very new
+ * so we keep a copy in our tree. All definitions are guarded, so it should be
+ * fine to always include this even when building with MSVC.
+ */
+#pragma once
+
+#ifndef __IAudioClient3_FWD_DEFINED__
+#define __IAudioClient3_FWD_DEFINED__
+typedef interface IAudioClient3 IAudioClient3;
+
+#endif /* __IAudioClient3_FWD_DEFINED__ */
+
+#ifndef __IAudioClient3_INTERFACE_DEFINED__
+#define __IAudioClient3_INTERFACE_DEFINED__
+
+#ifndef HAVE_AUDCLNT_STREAMOPTIONS
+typedef enum AUDCLNT_STREAMOPTIONS
+{
+ AUDCLNT_STREAMOPTIONS_NONE = 0,
+ AUDCLNT_STREAMOPTIONS_RAW = 0x1,
+ AUDCLNT_STREAMOPTIONS_MATCH_FORMAT = 0x2
+} AUDCLNT_STREAMOPTIONS;
+#endif
+
+/* These should be available when the IAudioClient2 interface is defined */
+#ifndef __IAudioClient2_FWD_DEFINED__
+typedef enum _AUDIO_STREAM_CATEGORY {
+ AudioCategory_Other = 0,
+ AudioCategory_ForegroundOnlyMedia,
+ AudioCategory_BackgroundCapableMedia,
+ AudioCategory_Communications,
+ AudioCategory_Alerts,
+ AudioCategory_SoundEffects,
+ AudioCategory_GameEffects,
+ AudioCategory_GameMedia,
+ AudioCategory_GameChat,
+ AudioCategory_Speech,
+ AudioCategory_Movie,
+ AudioCategory_Media
+} AUDIO_STREAM_CATEGORY;
+
+typedef struct AudioClientProperties
+{
+ UINT32 cbSize;
+ BOOL bIsOffload;
+ AUDIO_STREAM_CATEGORY eCategory;
+ AUDCLNT_STREAMOPTIONS Options;
+} AudioClientProperties;
+#endif /* __IAudioClient2_FWD_DEFINED__ */
+
+EXTERN_C const IID IID_IAudioClient3;
+
+typedef struct IAudioClient3Vtbl
+{
+ BEGIN_INTERFACE
+
+ HRESULT ( STDMETHODCALLTYPE *QueryInterface )(
+ IAudioClient3 * This,
+ REFIID riid,
+ void **ppvObject);
+
+ ULONG ( STDMETHODCALLTYPE *AddRef )(
+ IAudioClient3 * This);
+
+ ULONG ( STDMETHODCALLTYPE *Release )(
+ IAudioClient3 * This);
+
+ HRESULT ( STDMETHODCALLTYPE *Initialize )(
+ IAudioClient3 * This,
+ AUDCLNT_SHAREMODE ShareMode,
+ DWORD StreamFlags,
+ REFERENCE_TIME hnsBufferDuration,
+ REFERENCE_TIME hnsPeriodicity,
+ const WAVEFORMATEX *pFormat,
+ LPCGUID AudioSessionGuid);
+
+ HRESULT ( STDMETHODCALLTYPE *GetBufferSize )(
+ IAudioClient3 * This,
+ UINT32 *pNumBufferFrames);
+
+ HRESULT ( STDMETHODCALLTYPE *GetStreamLatency )(
+ IAudioClient3 * This,
+ REFERENCE_TIME *phnsLatency);
+
+ HRESULT ( STDMETHODCALLTYPE *GetCurrentPadding )(
+ IAudioClient3 * This,
+ UINT32 *pNumPaddingFrames);
+
+ HRESULT ( STDMETHODCALLTYPE *IsFormatSupported )(
+ IAudioClient3 * This,
+ AUDCLNT_SHAREMODE ShareMode,
+ const WAVEFORMATEX *pFormat,
+ WAVEFORMATEX **ppClosestMatch);
+
+ HRESULT ( STDMETHODCALLTYPE *GetMixFormat )(
+ IAudioClient3 * This,
+ WAVEFORMATEX **ppDeviceFormat);
+
+ HRESULT ( STDMETHODCALLTYPE *GetDevicePeriod )(
+ IAudioClient3 * This,
+ REFERENCE_TIME *phnsDefaultDevicePeriod,
+ REFERENCE_TIME *phnsMinimumDevicePeriod);
+
+ HRESULT ( STDMETHODCALLTYPE *Start )(
+ IAudioClient3 * This);
+
+ HRESULT ( STDMETHODCALLTYPE *Stop )(
+ IAudioClient3 * This);
+
+ HRESULT ( STDMETHODCALLTYPE *Reset )(
+ IAudioClient3 * This);
+
+ HRESULT ( STDMETHODCALLTYPE *SetEventHandle )(
+ IAudioClient3 * This,
+ HANDLE eventHandle);
+
+ HRESULT ( STDMETHODCALLTYPE *GetService )(
+ IAudioClient3 * This,
+ REFIID riid,
+ void **ppv);
+
+ HRESULT ( STDMETHODCALLTYPE *IsOffloadCapable )(
+ IAudioClient3 * This,
+ AUDIO_STREAM_CATEGORY Category,
+ BOOL *pbOffloadCapable);
+
+ HRESULT ( STDMETHODCALLTYPE *SetClientProperties )(
+ IAudioClient3 * This,
+ const AudioClientProperties *pProperties);
+
+ HRESULT ( STDMETHODCALLTYPE *GetBufferSizeLimits )(
+ IAudioClient3 * This,
+ const WAVEFORMATEX *pFormat,
+ BOOL bEventDriven,
+ REFERENCE_TIME *phnsMinBufferDuration,
+ REFERENCE_TIME *phnsMaxBufferDuration);
+
+ HRESULT ( STDMETHODCALLTYPE *GetSharedModeEnginePeriod )(
+ IAudioClient3 * This,
+ const WAVEFORMATEX *pFormat,
+ UINT32 *pDefaultPeriodInFrames,
+ UINT32 *pFundamentalPeriodInFrames,
+ UINT32 *pMinPeriodInFrames,
+ UINT32 *pMaxPeriodInFrames);
+
+ HRESULT ( STDMETHODCALLTYPE *GetCurrentSharedModeEnginePeriod )(
+ IAudioClient3 * This,
+ WAVEFORMATEX **ppFormat,
+ UINT32 *pCurrentPeriodInFrames);
+
+ HRESULT ( STDMETHODCALLTYPE *InitializeSharedAudioStream )(
+ IAudioClient3 * This,
+ DWORD StreamFlags,
+ UINT32 PeriodInFrames,
+ const WAVEFORMATEX *pFormat,
+ LPCGUID AudioSessionGuid);
+
+ END_INTERFACE
+} IAudioClient3Vtbl;
+
+interface IAudioClient3
+{
+ CONST_VTBL struct IAudioClient3Vtbl *lpVtbl;
+};
+
+#define IAudioClient3_QueryInterface(This,riid,ppvObject) \
+ ( (This)->lpVtbl -> QueryInterface(This,riid,ppvObject) )
+
+#define IAudioClient3_AddRef(This) \
+ ( (This)->lpVtbl -> AddRef(This) )
+
+#define IAudioClient3_Release(This) \
+ ( (This)->lpVtbl -> Release(This) )
+
+
+#define IAudioClient3_Initialize(This,ShareMode,StreamFlags,hnsBufferDuration,hnsPeriodicity,pFormat,AudioSessionGuid) \
+ ( (This)->lpVtbl -> Initialize(This,ShareMode,StreamFlags,hnsBufferDuration,hnsPeriodicity,pFormat,AudioSessionGuid) )
+
+#define IAudioClient3_GetBufferSize(This,pNumBufferFrames) \
+ ( (This)->lpVtbl -> GetBufferSize(This,pNumBufferFrames) )
+
+#define IAudioClient3_GetStreamLatency(This,phnsLatency) \
+ ( (This)->lpVtbl -> GetStreamLatency(This,phnsLatency) )
+
+#define IAudioClient3_GetCurrentPadding(This,pNumPaddingFrames) \
+ ( (This)->lpVtbl -> GetCurrentPadding(This,pNumPaddingFrames) )
+
+#define IAudioClient3_IsFormatSupported(This,ShareMode,pFormat,ppClosestMatch) \
+ ( (This)->lpVtbl -> IsFormatSupported(This,ShareMode,pFormat,ppClosestMatch) )
+
+#define IAudioClient3_GetMixFormat(This,ppDeviceFormat) \
+ ( (This)->lpVtbl -> GetMixFormat(This,ppDeviceFormat) )
+
+#define IAudioClient3_GetDevicePeriod(This,phnsDefaultDevicePeriod,phnsMinimumDevicePeriod) \
+ ( (This)->lpVtbl -> GetDevicePeriod(This,phnsDefaultDevicePeriod,phnsMinimumDevicePeriod) )
+
+#define IAudioClient3_Start(This) \
+ ( (This)->lpVtbl -> Start(This) )
+
+#define IAudioClient3_Stop(This) \
+ ( (This)->lpVtbl -> Stop(This) )
+
+#define IAudioClient3_Reset(This) \
+ ( (This)->lpVtbl -> Reset(This) )
+
+#define IAudioClient3_SetEventHandle(This,eventHandle) \
+ ( (This)->lpVtbl -> SetEventHandle(This,eventHandle) )
+
+#define IAudioClient3_GetService(This,riid,ppv) \
+ ( (This)->lpVtbl -> GetService(This,riid,ppv) )
+
+
+#define IAudioClient3_IsOffloadCapable(This,Category,pbOffloadCapable) \
+ ( (This)->lpVtbl -> IsOffloadCapable(This,Category,pbOffloadCapable) )
+
+#define IAudioClient3_SetClientProperties(This,pProperties) \
+ ( (This)->lpVtbl -> SetClientProperties(This,pProperties) )
+
+#define IAudioClient3_GetBufferSizeLimits(This,pFormat,bEventDriven,phnsMinBufferDuration,phnsMaxBufferDuration) \
+ ( (This)->lpVtbl -> GetBufferSizeLimits(This,pFormat,bEventDriven,phnsMinBufferDuration,phnsMaxBufferDuration) )
+
+
+#define IAudioClient3_GetSharedModeEnginePeriod(This,pFormat,pDefaultPeriodInFrames,pFundamentalPeriodInFrames,pMinPeriodInFrames,pMaxPeriodInFrames) \
+ ( (This)->lpVtbl -> GetSharedModeEnginePeriod(This,pFormat,pDefaultPeriodInFrames,pFundamentalPeriodInFrames,pMinPeriodInFrames,pMaxPeriodInFrames) )
+
+#define IAudioClient3_GetCurrentSharedModeEnginePeriod(This,ppFormat,pCurrentPeriodInFrames) \
+ ( (This)->lpVtbl -> GetCurrentSharedModeEnginePeriod(This,ppFormat,pCurrentPeriodInFrames) )
+
+#define IAudioClient3_InitializeSharedAudioStream(This,StreamFlags,PeriodInFrames,pFormat,AudioSessionGuid) \
+ ( (This)->lpVtbl -> InitializeSharedAudioStream(This,StreamFlags,PeriodInFrames,pFormat,AudioSessionGuid) )
+
+
+#endif /* __IAudioClient3_INTERFACE_DEFINED__ */
diff --git a/sys/wasapi/gstmmdeviceenumerator.cpp b/sys/wasapi/gstmmdeviceenumerator.cpp
new file mode 100644
index 0000000000..1826b7fe1b
--- /dev/null
+++ b/sys/wasapi/gstmmdeviceenumerator.cpp
@@ -0,0 +1,472 @@
+/* GStreamer
+ * Copyright (C) 2021 Seungha Yang <seungha@centricular.com>
+ *
+ * This library is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU Library General Public
+ * License as published by the Free Software Foundation; either
+ * version 2 of the License, or (at your option) any later version.
+ *
+ * This library 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
+ * Library General Public License for more details.
+ *
+ * You should have received a copy of the GNU Library General Public
+ * License along with this library; if not, write to the
+ * Free Software Foundation, Inc., 51 Franklin St, Fifth Floor,
+ * Boston, MA 02110-1301, USA.
+ */
+
+#ifdef HAVE_CONFIG_H
+#include "config.h"
+#endif
+
+#include "gstmmdeviceenumerator.h"
+
+#ifndef INITGUID
+#include <initguid.h>
+#endif
+
+/* *INDENT-OFF* */
+G_BEGIN_DECLS
+
+GST_DEBUG_CATEGORY_EXTERN (gst_wasapi_debug);
+#define GST_CAT_DEFAULT gst_wasapi_debug
+
+G_END_DECLS
+
+/* IMMNotificationClient implementation */
+class GstIMMNotificationClient : public IMMNotificationClient
+{
+public:
+ static HRESULT
+ CreateInstance (GstMMDeviceEnumerator * enumerator,
+ const GstMMNotificationClientCallbacks * callbacks,
+ gpointer user_data,
+ IMMNotificationClient ** client)
+ {
+ GstIMMNotificationClient *self;
+
+ self = new GstIMMNotificationClient ();
+
+ self->callbacks_ = *callbacks;
+ self->user_data_ = user_data;
+ g_weak_ref_set (&self->enumerator_, enumerator);
+
+ *client = (IMMNotificationClient *) self;
+
+ return S_OK;
+ }
+
+ /* IUnknown */
+ STDMETHODIMP
+ QueryInterface (REFIID riid, void ** object)
+ {
+ if (!object)
+ return E_POINTER;
+
+ if (riid == IID_IUnknown) {
+ *object = static_cast<IUnknown *> (this);
+ } else if (riid == __uuidof(IMMNotificationClient)) {
+ *object = static_cast<IMMNotificationClient *> (this);
+ } else {
+ *object = nullptr;
+ return E_NOINTERFACE;
+ }
+
+ AddRef ();
+
+ return S_OK;
+ }
+
+ STDMETHODIMP_ (ULONG)
+ AddRef (void)
+ {
+ GST_TRACE ("%p, %d", this, (guint) ref_count_);
+ return InterlockedIncrement (&ref_count_);
+ }
+
+ STDMETHODIMP_ (ULONG)
+ Release (void)
+ {
+ ULONG ref_count;
+
+ GST_TRACE ("%p, %d", this, (guint) ref_count_);
+ ref_count = InterlockedDecrement (&ref_count_);
+
+ if (ref_count == 0) {
+ GST_TRACE ("Delete instance %p", this);
+ delete this;
+ }
+
+ return ref_count;
+ }
+
+ /* IMMNotificationClient */
+ STDMETHODIMP
+ OnDeviceStateChanged (LPCWSTR device_id, DWORD new_state)
+ {
+ GstMMDeviceEnumerator *listener;
+ HRESULT hr;
+
+ if (!callbacks_.device_state_changed)
+ return S_OK;
+
+ listener = (GstMMDeviceEnumerator *) g_weak_ref_get (&enumerator_);
+ if (!listener)
+ return S_OK;
+
+ hr = callbacks_.device_state_changed (listener, device_id, new_state,
+ user_data_);
+ gst_object_unref (listener);
+
+ return hr;
+ }
+
+ STDMETHODIMP
+ OnDeviceAdded (LPCWSTR device_id)
+ {
+ GstMMDeviceEnumerator *listener;
+ HRESULT hr;
+
+ if (!callbacks_.device_added)
+ return S_OK;
+
+ listener = (GstMMDeviceEnumerator *) g_weak_ref_get (&enumerator_);
+ if (!listener)
+ return S_OK;
+
+ hr = callbacks_.device_added (listener, device_id, user_data_);
+ gst_object_unref (listener);
+
+ return hr;
+ }
+
+ STDMETHODIMP
+ OnDeviceRemoved (LPCWSTR device_id)
+ {
+ GstMMDeviceEnumerator *listener;
+ HRESULT hr;
+
+ if (!callbacks_.device_removed)
+ return S_OK;
+
+ listener = (GstMMDeviceEnumerator *) g_weak_ref_get (&enumerator_);
+ if (!listener)
+ return S_OK;
+
+ hr = callbacks_.device_removed (listener, device_id, user_data_);
+ gst_object_unref (listener);
+
+ return hr;
+ }
+
+ STDMETHODIMP
+ OnDefaultDeviceChanged (EDataFlow flow, ERole role, LPCWSTR default_device_id)
+ {
+ GstMMDeviceEnumerator *listener;
+ HRESULT hr;
+
+ if (!callbacks_.default_device_changed)
+ return S_OK;
+
+ listener = (GstMMDeviceEnumerator *) g_weak_ref_get (&enumerator_);
+ if (!listener)
+ return S_OK;
+
+ hr = callbacks_.default_device_changed (listener,
+ flow, role, default_device_id, user_data_);
+ gst_object_unref (listener);
+
+ return hr;
+ }
+
+ STDMETHODIMP
+ OnPropertyValueChanged (LPCWSTR device_id, const PROPERTYKEY key)
+ {
+ GstMMDeviceEnumerator *listener;
+ HRESULT hr;
+
+ if (!callbacks_.property_value_changed)
+ return S_OK;
+
+ listener = (GstMMDeviceEnumerator *) g_weak_ref_get (&enumerator_);
+ if (!device_id)
+ return S_OK;
+
+ hr = callbacks_.property_value_changed (listener,
+ device_id, key, user_data_);
+ gst_object_unref (listener);
+
+ return hr;
+ }
+
+private:
+ GstIMMNotificationClient ()
+ : ref_count_ (1)
+ {
+ g_weak_ref_init (&enumerator_, nullptr);
+ }
+
+ virtual ~GstIMMNotificationClient ()
+ {
+ g_weak_ref_clear (&enumerator_);
+ }
+
+private:
+ ULONG ref_count_;
+ GstMMNotificationClientCallbacks callbacks_;
+ gpointer user_data_;
+ GWeakRef enumerator_;
+};
+/* *INDENT-ON* */
+
+struct _GstMMDeviceEnumerator
+{
+ GstObject parent;
+
+ IMMDeviceEnumerator *handle;
+ IMMNotificationClient *client;
+
+ GMutex lock;
+ GCond cond;
+
+ GThread *thread;
+ GMainContext *context;
+ GMainLoop *loop;
+
+ gboolean running;
+};
+
+static void gst_mm_device_enumerator_constructed (GObject * object);
+static void gst_mm_device_enumerator_finalize (GObject * object);
+
+static gpointer
+gst_mm_device_enumerator_thread_func (GstMMDeviceEnumerator * self);
+
+#define gst_mm_device_enumerator_parent_class parent_class
+G_DEFINE_TYPE (GstMMDeviceEnumerator,
+ gst_mm_device_enumerator, GST_TYPE_OBJECT);
+
+static void
+gst_mm_device_enumerator_class_init (GstMMDeviceEnumeratorClass * klass)
+{
+ GObjectClass *gobject_class = G_OBJECT_CLASS (klass);
+
+ gobject_class->constructed = gst_mm_device_enumerator_constructed;
+ gobject_class->finalize = gst_mm_device_enumerator_finalize;
+}
+
+static void
+gst_mm_device_enumerator_init (GstMMDeviceEnumerator * self)
+{
+ g_mutex_init (&self->lock);
+ g_cond_init (&self->cond);
+ self->context = g_main_context_new ();
+ self->loop = g_main_loop_new (self->context, FALSE);
+}
+
+static void
+gst_mm_device_enumerator_constructed (GObject * object)
+{
+ GstMMDeviceEnumerator *self = GST_MM_DEVICE_ENUMERATOR (object);
+
+ g_mutex_lock (&self->lock);
+ self->thread = g_thread_new ("GstMMDeviceEnumerator",
+ (GThreadFunc) gst_mm_device_enumerator_thread_func, self);
+ while (!g_main_loop_is_running (self->loop))
+ g_cond_wait (&self->cond, &self->lock);
+ g_mutex_unlock (&self->lock);
+}
+
+static void
+gst_mm_device_enumerator_finalize (GObject * object)
+{
+ GstMMDeviceEnumerator *self = GST_MM_DEVICE_ENUMERATOR (object);
+
+ g_main_loop_quit (self->loop);
+ g_thread_join (self->thread);
+ g_main_loop_unref (self->loop);
+ g_main_context_unref (self->context);
+
+ g_mutex_clear (&self->lock);
+ g_cond_clear (&self->cond);
+
+ G_OBJECT_CLASS (parent_class)->finalize (object);
+}
+
+static gboolean
+loop_running_cb (GstMMDeviceEnumerator * self)
+{
+ g_mutex_lock (&self->lock);
+ g_cond_signal (&self->cond);
+ g_mutex_unlock (&self->lock);
+
+ return G_SOURCE_REMOVE;
+}
+
+static gpointer
+gst_mm_device_enumerator_thread_func (GstMMDeviceEnumerator * self)
+{
+ GSource *idle_source;
+ IMMDeviceEnumerator *enumerator = nullptr;
+ HRESULT hr;
+
+ CoInitializeEx (NULL, COINIT_MULTITHREADED);
+ g_main_context_push_thread_default (self->context);
+
+ idle_source = g_idle_source_new ();
+ g_source_set_callback (idle_source,
+ (GSourceFunc) loop_running_cb, self, nullptr);
+ g_source_attach (idle_source, self->context);
+ g_source_unref (idle_source);
+
+ hr = CoCreateInstance (__uuidof (MMDeviceEnumerator),
+ nullptr, CLSCTX_ALL, IID_PPV_ARGS (&enumerator));
+ if (FAILED (hr)) {
+ GST_ERROR_OBJECT (self, "Failed to create IMMDeviceEnumerator instance");
+ goto run_loop;
+ }
+
+ self->handle = enumerator;
+
+run_loop:
+ GST_INFO_OBJECT (self, "Starting loop");
+ g_main_loop_run (self->loop);
+ GST_INFO_OBJECT (self, "Stopped loop");
+
+ if (self->client && self->handle) {
+ self->handle->UnregisterEndpointNotificationCallback (self->client);
+
+ self->client->Release ();
+ }
+
+ if (self->handle)
+ self->handle->Release ();
+
+ g_main_context_pop_thread_default (self->context);
+ CoUninitialize ();
+
+ return nullptr;
+}
+
+GstMMDeviceEnumerator *
+gst_mm_device_enumerator_new (void)
+{
+ GstMMDeviceEnumerator *self;
+
+ self = (GstMMDeviceEnumerator *) g_object_new (GST_TYPE_MM_DEVICE_ENUMERATOR,
+ nullptr);
+
+ if (!self->handle) {
+ gst_object_unref (self);
+ return nullptr;
+ }
+
+ gst_object_ref_sink (self);
+
+ return self;
+}
+
+IMMDeviceEnumerator *
+gst_mm_device_enumerator_get_handle (GstMMDeviceEnumerator * enumerator)
+{
+ g_return_val_if_fail (GST_IS_MM_DEVICE_ENUMERATOR (enumerator), nullptr);
+
+ return enumerator->handle;
+}
+
+typedef struct
+{
+ GstMMDeviceEnumerator *self;
+ GstMMNotificationClientCallbacks *callbacks;
+ gpointer user_data;
+
+ gboolean handled;
+ GMutex lock;
+ GCond cond;
+
+ gboolean ret;
+} SetNotificationCallbackData;
+
+static gboolean
+set_notification_callback (SetNotificationCallbackData * data)
+{
+ GstMMDeviceEnumerator *self = data->self;
+ HRESULT hr;
+
+ g_mutex_lock (&data->lock);
+ g_mutex_lock (&self->lock);
+
+ data->ret = TRUE;
+
+ if (self->client) {
+ self->handle->UnregisterEndpointNotificationCallback (self->client);
+ self->client->Release ();
+ self->client = nullptr;
+ }
+
+ if (data->callbacks) {
+ IMMNotificationClient *client;
+
+ hr = GstIMMNotificationClient::CreateInstance (self, data->callbacks,
+ data->user_data, &client);
+ if (FAILED (hr)) {
+ GST_ERROR_OBJECT (self,
+ "Failed to create IMMNotificationClient instance");
+ data->ret = FALSE;
+ goto out;
+ }
+
+ hr = self->handle->RegisterEndpointNotificationCallback (client);
+ if (FAILED (hr)) {
+ GST_ERROR_OBJECT (self, "Failed to register callback");
+ client->Release ();
+ data->ret = FALSE;
+ goto out;
+ }
+
+ self->client = client;
+ }
+
+out:
+ data->handled = TRUE;
+ g_cond_signal (&data->cond);
+ g_mutex_unlock (&self->lock);
+ g_mutex_unlock (&data->lock);
+
+ return G_SOURCE_REMOVE;
+}
+
+gboolean
+gst_mm_device_enumerator_set_notification_callback (GstMMDeviceEnumerator *
+ enumerator, GstMMNotificationClientCallbacks * callbacks,
+ gpointer user_data)
+{
+ SetNotificationCallbackData data;
+ gboolean ret;
+
+ g_return_val_if_fail (GST_IS_MM_DEVICE_ENUMERATOR (enumerator), FALSE);
+
+ data.self = enumerator;
+ data.callbacks = callbacks;
+ data.user_data = user_data;
+ data.handled = FALSE;
+
+ g_mutex_init (&data.lock);
+ g_cond_init (&data.cond);
+
+ g_main_context_invoke (enumerator->context,
+ (GSourceFunc) set_notification_callback, &data);
+ g_mutex_lock (&data.lock);
+ while (!data.handled)
+ g_cond_wait (&data.cond, &data.lock);
+ g_mutex_unlock (&data.lock);
+
+ ret = data.ret;
+
+ g_mutex_clear (&data.lock);
+ g_cond_clear (&data.cond);
+
+ return ret;
+}
diff --git a/sys/wasapi/gstmmdeviceenumerator.h b/sys/wasapi/gstmmdeviceenumerator.h
new file mode 100644
index 0000000000..d3f7b07a9c
--- /dev/null
+++ b/sys/wasapi/gstmmdeviceenumerator.h
@@ -0,0 +1,69 @@
+/*
+ * Copyright (C) 2021 Seungha Yang <seungha@centricular.com>
+ *
+ * This library is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU Library General Public
+ * License as published by the Free Software Foundation; either
+ * version 2 of the License, or (at your option) any later version.
+ *
+ * This library 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
+ * Library General Public License for more details.
+ *
+ * You should have received a copy of the GNU Library General Public
+ * License along with this library; if not, write to the
+ * Free Software Foundation, Inc., 51 Franklin St, Fifth Floor,
+ * Boston, MA 02110-1301, USA.
+ */
+
+#ifndef __GST_MM_DEVICE_ENUMERATOR_H__
+#define __GST_MM_DEVICE_ENUMERATOR_H__
+
+#include <gst/gst.h>
+#include <mmdeviceapi.h>
+
+G_BEGIN_DECLS
+
+#define GST_TYPE_MM_DEVICE_ENUMERATOR (gst_mm_device_enumerator_get_type ())
+G_DECLARE_FINAL_TYPE (GstMMDeviceEnumerator, gst_mm_device_enumerator,
+ GST, MM_DEVICE_ENUMERATOR, GstObject);
+
+typedef struct
+{
+ HRESULT (*device_state_changed) (GstMMDeviceEnumerator * enumerator,
+ LPCWSTR device_id,
+ DWORD new_state,
+ gpointer user_data);
+
+ HRESULT (*device_added) (GstMMDeviceEnumerator * enumerator,
+ LPCWSTR device_id,
+ gpointer user_data);
+
+ HRESULT (*device_removed) (GstMMDeviceEnumerator * provider,
+ LPCWSTR device_id,
+ gpointer user_data);
+
+ HRESULT (*default_device_changed) (GstMMDeviceEnumerator * provider,
+ EDataFlow flow,
+ ERole role,
+ LPCWSTR default_device_id,
+ gpointer user_data);
+
+ HRESULT (*property_value_changed) (GstMMDeviceEnumerator * provider,
+ LPCWSTR device_id,
+ const PROPERTYKEY key,
+ gpointer user_data);
+} GstMMNotificationClientCallbacks;
+
+GstMMDeviceEnumerator * gst_mm_device_enumerator_new (void);
+
+IMMDeviceEnumerator * gst_mm_device_enumerator_get_handle (GstMMDeviceEnumerator * enumerator);
+
+gboolean gst_mm_device_enumerator_set_notification_callback (GstMMDeviceEnumerator * enumerator,
+ GstMMNotificationClientCallbacks * callbacks,
+ gpointer user_data);
+
+G_END_DECLS
+
+#endif /* __GST_MM_DEVICE_ENUMERATOR_H__ */
diff --git a/sys/wasapi/gstwasapi.c b/sys/wasapi/gstwasapi.c
new file mode 100644
index 0000000000..72ebd53221
--- /dev/null
+++ b/sys/wasapi/gstwasapi.c
@@ -0,0 +1,57 @@
+/*
+ * Copyright (C) 2008 Ole André Vadla Ravnås <ole.andre.ravnas@tandberg.com>
+ * Copyright (C) 2018 Centricular Ltd.
+ * Author: Nirbheek Chauhan <nirbheek@centricular.com>
+ *
+ * This library is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU Library General Public
+ * License as published by the Free Software Foundation; either
+ * version 2 of the License, or (at your option) any later version.
+ *
+ * This library 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
+ * Library General Public License for more details.
+ *
+ * You should have received a copy of the GNU Library General Public
+ * License along with this library; if not, write to the
+ * Free Software Foundation, Inc., 51 Franklin St, Fifth Floor,
+ * Boston, MA 02110-1301, USA.
+ */
+
+#ifdef HAVE_CONFIG_H
+# include <config.h>
+#endif
+
+#include "gstwasapisink.h"
+#include "gstwasapisrc.h"
+#include "gstwasapidevice.h"
+
+GST_DEBUG_CATEGORY (gst_wasapi_debug);
+
+static gboolean
+plugin_init (GstPlugin * plugin)
+{
+ if (!gst_element_register (plugin, "wasapisink", GST_RANK_PRIMARY,
+ GST_TYPE_WASAPI_SINK))
+ return FALSE;
+
+ if (!gst_element_register (plugin, "wasapisrc", GST_RANK_PRIMARY,
+ GST_TYPE_WASAPI_SRC))
+ return FALSE;
+
+ if (!gst_device_provider_register (plugin, "wasapideviceprovider",
+ GST_RANK_PRIMARY, GST_TYPE_WASAPI_DEVICE_PROVIDER))
+ return FALSE;
+
+ GST_DEBUG_CATEGORY_INIT (gst_wasapi_debug, "wasapi",
+ 0, "Windows audio session API generic");
+
+ return TRUE;
+}
+
+GST_PLUGIN_DEFINE (GST_VERSION_MAJOR,
+ GST_VERSION_MINOR,
+ wasapi,
+ "Windows audio session API plugin",
+ plugin_init, VERSION, "LGPL", GST_PACKAGE_NAME, GST_PACKAGE_ORIGIN)
diff --git a/sys/wasapi/gstwasapidevice.c b/sys/wasapi/gstwasapidevice.c
new file mode 100644
index 0000000000..c13fc6a8aa
--- /dev/null
+++ b/sys/wasapi/gstwasapidevice.c
@@ -0,0 +1,362 @@
+/* GStreamer
+ * Copyright (C) 2018 Nirbheek Chauhan <nirbheek@centricular.com>
+ * Copyright (C) 2021 Seungha Yang <seungha@centricular.com>
+ *
+ * This library is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU Library General Public
+ * License as published by the Free Software Foundation; either
+ * version 2 of the License, or (at your option) any later version.
+ *
+ * This library 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
+ * Library General Public License for more details.
+ *
+ * You should have received a copy of the GNU Library General Public
+ * License along with this library; if not, write to the
+ * Free Software Foundation, Inc., 59 Temple Place - Suite 330,
+ * Boston, MA 02111-1307, USA.
+ */
+
+#ifdef HAVE_CONFIG_H
+#include "config.h"
+#endif
+
+#include "gstwasapidevice.h"
+
+GST_DEBUG_CATEGORY_EXTERN (gst_wasapi_debug);
+#define GST_CAT_DEFAULT gst_wasapi_debug
+
+G_DEFINE_TYPE (GstWasapiDeviceProvider, gst_wasapi_device_provider,
+ GST_TYPE_DEVICE_PROVIDER);
+
+static void gst_wasapi_device_provider_finalize (GObject * object);
+static GList *gst_wasapi_device_provider_probe (GstDeviceProvider * provider);
+static gboolean gst_wasapi_device_provider_start (GstDeviceProvider * provider);
+static void gst_wasapi_device_provider_stop (GstDeviceProvider * provider);
+
+static HRESULT
+gst_wasapi_device_provider_device_added (GstMMDeviceEnumerator * enumerator,
+ LPCWSTR device_id, gpointer user_data);
+static HRESULT
+gst_wasapi_device_provider_device_removed (GstMMDeviceEnumerator * enumerator,
+ LPCWSTR device_id, gpointer user_data);
+static HRESULT
+gst_wasapi_device_provider_default_device_changed (GstMMDeviceEnumerator *
+ enumerator, EDataFlow flow, ERole role, LPCWSTR device_id,
+ gpointer user_data);
+
+static void
+gst_wasapi_device_provider_class_init (GstWasapiDeviceProviderClass * klass)
+{
+ GObjectClass *gobject_class = G_OBJECT_CLASS (klass);
+ GstDeviceProviderClass *dm_class = GST_DEVICE_PROVIDER_CLASS (klass);
+
+ gobject_class->finalize = gst_wasapi_device_provider_finalize;
+
+ dm_class->probe = gst_wasapi_device_provider_probe;
+ dm_class->start = gst_wasapi_device_provider_start;
+ dm_class->stop = gst_wasapi_device_provider_stop;
+
+ gst_device_provider_class_set_static_metadata (dm_class,
+ "WASAPI (Windows Audio Session API) Device Provider",
+ "Source/Sink/Audio", "List WASAPI source and sink devices",
+ "Nirbheek Chauhan <nirbheek@centricular.com>");
+}
+
+static void
+gst_wasapi_device_provider_init (GstWasapiDeviceProvider * self)
+{
+ self->enumerator = gst_mm_device_enumerator_new ();
+}
+
+static gboolean
+gst_wasapi_device_provider_start (GstDeviceProvider * provider)
+{
+ GstWasapiDeviceProvider *self = GST_WASAPI_DEVICE_PROVIDER (provider);
+ GstMMNotificationClientCallbacks callbacks = { NULL, };
+ GList *devices = NULL;
+ GList *iter;
+
+ if (!self->enumerator) {
+ GST_WARNING_OBJECT (self, "Enumerator wasn't configured");
+ return FALSE;
+ }
+
+ callbacks.device_added = gst_wasapi_device_provider_device_added;
+ callbacks.device_removed = gst_wasapi_device_provider_device_removed;
+ callbacks.default_device_changed =
+ gst_wasapi_device_provider_default_device_changed;
+
+ if (!gst_mm_device_enumerator_set_notification_callback (self->enumerator,
+ &callbacks, self)) {
+ GST_WARNING_OBJECT (self, "Failed to set callbacks");
+ return FALSE;
+ }
+
+ /* baseclass will not call probe() once it's started, but we can get
+ * notification only add/remove or change case. To this manually */
+ devices = gst_wasapi_device_provider_probe (provider);
+ if (devices) {
+ for (iter = devices; iter; iter = g_list_next (iter)) {
+ gst_device_provider_device_add (provider, GST_DEVICE (iter->data));
+ }
+
+ g_list_free (devices);
+ }
+
+ return TRUE;
+}
+
+static void
+gst_wasapi_device_provider_stop (GstDeviceProvider * provider)
+{
+ GstWasapiDeviceProvider *self = GST_WASAPI_DEVICE_PROVIDER (provider);
+
+ if (self->enumerator) {
+ gst_mm_device_enumerator_set_notification_callback (self->enumerator,
+ NULL, NULL);
+ }
+}
+
+static void
+gst_wasapi_device_provider_finalize (GObject * object)
+{
+ GstWasapiDeviceProvider *self = GST_WASAPI_DEVICE_PROVIDER (object);
+
+ gst_clear_object (&self->enumerator);
+
+ G_OBJECT_CLASS (gst_wasapi_device_provider_parent_class)->finalize (object);
+}
+
+static GList *
+gst_wasapi_device_provider_probe (GstDeviceProvider * provider)
+{
+ GstWasapiDeviceProvider *self = GST_WASAPI_DEVICE_PROVIDER (provider);
+ GList *devices = NULL;
+
+ if (!gst_wasapi_util_get_devices (self->enumerator, TRUE, &devices))
+ GST_ERROR_OBJECT (self, "Failed to enumerate devices");
+
+ return devices;
+}
+
+static gboolean
+gst_wasapi_device_is_in_list (GList * list, GstDevice * device)
+{
+ GList *iter;
+ GstStructure *s;
+ const gchar *device_id;
+ gboolean found = FALSE;
+
+ s = gst_device_get_properties (device);
+ g_assert (s);
+
+ device_id = gst_structure_get_string (s, "device.strid");
+ g_assert (device_id);
+
+ for (iter = list; iter; iter = g_list_next (iter)) {
+ GstStructure *other_s;
+ const gchar *other_id;
+
+ other_s = gst_device_get_properties (GST_DEVICE (iter->data));
+ g_assert (other_s);
+
+ other_id = gst_structure_get_string (other_s, "device.strid");
+ g_assert (other_id);
+
+ if (g_ascii_strcasecmp (device_id, other_id) == 0) {
+ found = TRUE;
+ }
+
+ gst_structure_free (other_s);
+ if (found)
+ break;
+ }
+
+ gst_structure_free (s);
+
+ return found;
+}
+
+static void
+gst_wasapi_device_provider_update_devices (GstWasapiDeviceProvider * self)
+{
+ GstDeviceProvider *provider = GST_DEVICE_PROVIDER_CAST (self);
+ GList *prev_devices = NULL;
+ GList *new_devices = NULL;
+ GList *to_add = NULL;
+ GList *to_remove = NULL;
+ GList *iter;
+
+ GST_OBJECT_LOCK (self);
+ prev_devices = g_list_copy_deep (provider->devices,
+ (GCopyFunc) gst_object_ref, NULL);
+ GST_OBJECT_UNLOCK (self);
+
+ new_devices = gst_wasapi_device_provider_probe (provider);
+
+ /* Ownership of GstDevice for gst_device_provider_device_add()
+ * and gst_device_provider_device_remove() is a bit complicated.
+ * Remove floating reference here for things to be clear */
+ for (iter = new_devices; iter; iter = g_list_next (iter))
+ gst_object_ref_sink (iter->data);
+
+ /* Check newly added devices */
+ for (iter = new_devices; iter; iter = g_list_next (iter)) {
+ if (!gst_wasapi_device_is_in_list (prev_devices, GST_DEVICE (iter->data))) {
+ to_add = g_list_prepend (to_add, gst_object_ref (iter->data));
+ }
+ }
+
+ /* Check removed device */
+ for (iter = prev_devices; iter; iter = g_list_next (iter)) {
+ if (!gst_wasapi_device_is_in_list (new_devices, GST_DEVICE (iter->data))) {
+ to_remove = g_list_prepend (to_remove, gst_object_ref (iter->data));
+ }
+ }
+
+ for (iter = to_remove; iter; iter = g_list_next (iter))
+ gst_device_provider_device_remove (provider, GST_DEVICE (iter->data));
+
+ for (iter = to_add; iter; iter = g_list_next (iter))
+ gst_device_provider_device_add (provider, GST_DEVICE (iter->data));
+
+ if (prev_devices)
+ g_list_free_full (prev_devices, (GDestroyNotify) gst_object_unref);
+
+ if (to_add)
+ g_list_free_full (to_add, (GDestroyNotify) gst_object_unref);
+
+ if (to_remove)
+ g_list_free_full (to_remove, (GDestroyNotify) gst_object_unref);
+}
+
+static HRESULT
+gst_wasapi_device_provider_device_added (GstMMDeviceEnumerator * enumerator,
+ LPCWSTR device_id, gpointer user_data)
+{
+ GstWasapiDeviceProvider *self = GST_WASAPI_DEVICE_PROVIDER (user_data);
+
+ gst_wasapi_device_provider_update_devices (self);
+
+ return S_OK;
+}
+
+static HRESULT
+gst_wasapi_device_provider_device_removed (GstMMDeviceEnumerator * enumerator,
+ LPCWSTR device_id, gpointer user_data)
+{
+ GstWasapiDeviceProvider *self = GST_WASAPI_DEVICE_PROVIDER (user_data);
+
+ gst_wasapi_device_provider_update_devices (self);
+
+ return S_OK;
+}
+
+static HRESULT
+gst_wasapi_device_provider_default_device_changed (GstMMDeviceEnumerator *
+ enumerator, EDataFlow flow, ERole role, LPCWSTR device_id,
+ gpointer user_data)
+{
+ GstWasapiDeviceProvider *self = GST_WASAPI_DEVICE_PROVIDER (user_data);
+
+ gst_wasapi_device_provider_update_devices (self);
+
+ return S_OK;
+}
+
+/* GstWasapiDevice begins */
+
+enum
+{
+ PROP_DEVICE_STRID = 1,
+};
+
+G_DEFINE_TYPE (GstWasapiDevice, gst_wasapi_device, GST_TYPE_DEVICE);
+
+static void gst_wasapi_device_get_property (GObject * object,
+ guint prop_id, GValue * value, GParamSpec * pspec);
+static void gst_wasapi_device_set_property (GObject * object,
+ guint prop_id, const GValue * value, GParamSpec * pspec);
+static void gst_wasapi_device_finalize (GObject * object);
+static GstElement *gst_wasapi_device_create_element (GstDevice * device,
+ const gchar * name);
+
+static void
+gst_wasapi_device_class_init (GstWasapiDeviceClass * klass)
+{
+ GstDeviceClass *dev_class = GST_DEVICE_CLASS (klass);
+ GObjectClass *object_class = G_OBJECT_CLASS (klass);
+
+ dev_class->create_element = gst_wasapi_device_create_element;
+
+ object_class->get_property = gst_wasapi_device_get_property;
+ object_class->set_property = gst_wasapi_device_set_property;
+ object_class->finalize = gst_wasapi_device_finalize;
+
+ g_object_class_install_property (object_class, PROP_DEVICE_STRID,
+ g_param_spec_string ("device", "Device string ID",
+ "Device strId", NULL,
+ G_PARAM_STATIC_STRINGS | G_PARAM_READWRITE | G_PARAM_CONSTRUCT_ONLY));
+}
+
+static void
+gst_wasapi_device_init (GstWasapiDevice * device)
+{
+}
+
+static void
+gst_wasapi_device_finalize (GObject * object)
+{
+ GstWasapiDevice *device = GST_WASAPI_DEVICE (object);
+
+ g_free (device->strid);
+
+ G_OBJECT_CLASS (gst_wasapi_device_parent_class)->finalize (object);
+}
+
+static GstElement *
+gst_wasapi_device_create_element (GstDevice * device, const gchar * name)
+{
+ GstWasapiDevice *wasapi_dev = GST_WASAPI_DEVICE (device);
+ GstElement *elem;
+
+ elem = gst_element_factory_make (wasapi_dev->element, name);
+
+ g_object_set (elem, "device", wasapi_dev->strid, NULL);
+
+ return elem;
+}
+
+static void
+gst_wasapi_device_get_property (GObject * object, guint prop_id,
+ GValue * value, GParamSpec * pspec)
+{
+ GstWasapiDevice *device = GST_WASAPI_DEVICE_CAST (object);
+
+ switch (prop_id) {
+ case PROP_DEVICE_STRID:
+ g_value_set_string (value, device->strid);
+ break;
+ default:
+ G_OBJECT_WARN_INVALID_PROPERTY_ID (object, prop_id, pspec);
+ break;
+ }
+}
+
+static void
+gst_wasapi_device_set_property (GObject * object, guint prop_id,
+ const GValue * value, GParamSpec * pspec)
+{
+ GstWasapiDevice *device = GST_WASAPI_DEVICE_CAST (object);
+
+ switch (prop_id) {
+ case PROP_DEVICE_STRID:
+ device->strid = g_value_dup_string (value);
+ break;
+ default:
+ G_OBJECT_WARN_INVALID_PROPERTY_ID (object, prop_id, pspec);
+ break;
+ }
+}
diff --git a/sys/wasapi/gstwasapidevice.h b/sys/wasapi/gstwasapidevice.h
new file mode 100644
index 0000000000..55eddd06b5
--- /dev/null
+++ b/sys/wasapi/gstwasapidevice.h
@@ -0,0 +1,81 @@
+/* GStreamer
+ * Copyright (C) 2018 Nirbheek Chauhan <nirbheek@centricular.com>
+ *
+ * This library is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU Library General Public
+ * License as published by the Free Software Foundation; either
+ * version 2 of the License, or (at your option) any later version.
+ *
+ * This library 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
+ * Library General Public License for more details.
+ *
+ * You should have received a copy of the GNU Library General Public
+ * License along with this library; if not, write to the
+ * Free Software Foundation, Inc., 59 Temple Place - Suite 330,
+ * Boston, MA 02111-1307, USA.
+ */
+
+#ifndef __GST_WASAPI_DEVICE_H__
+#define __GST_WASAPI_DEVICE_H__
+
+#include "gstwasapiutil.h"
+
+G_BEGIN_DECLS
+
+typedef struct _GstWasapiDeviceProvider GstWasapiDeviceProvider;
+typedef struct _GstWasapiDeviceProviderClass GstWasapiDeviceProviderClass;
+
+#define GST_TYPE_WASAPI_DEVICE_PROVIDER (gst_wasapi_device_provider_get_type())
+#define GST_IS_WASAPI_DEVICE_PROVIDER(obj) (G_TYPE_CHECK_INSTANCE_TYPE ((obj), GST_TYPE_WASAPI_DEVICE_PROVIDER))
+#define GST_IS_WASAPI_DEVICE_PROVIDER_CLASS(klass) (G_TYPE_CHECK_CLASS_TYPE ((klass), GST_TYPE_WASAPI_DEVICE_PROVIDER))
+#define GST_WASAPI_DEVICE_PROVIDER_GET_CLASS(obj) (G_TYPE_INSTANCE_GET_CLASS ((obj), GST_TYPE_WASAPI_DEVICE_PROVIDER, GstWasapiDeviceProviderClass))
+#define GST_WASAPI_DEVICE_PROVIDER(obj) (G_TYPE_CHECK_INSTANCE_CAST ((obj), GST_TYPE_WASAPI_DEVICE_PROVIDER, GstWasapiDeviceProvider))
+#define GST_WASAPI_DEVICE_PROVIDER_CLASS(klass) (G_TYPE_CHECK_CLASS_CAST ((klass), GST_TYPE_DEVICE_PROVIDER, GstWasapiDeviceProviderClass))
+#define GST_WASAPI_DEVICE_PROVIDER_CAST(obj) ((GstWasapiDeviceProvider *)(obj))
+
+struct _GstWasapiDeviceProvider
+{
+ GstDeviceProvider parent;
+
+ GstMMDeviceEnumerator *enumerator;
+};
+
+struct _GstWasapiDeviceProviderClass
+{
+ GstDeviceProviderClass parent_class;
+};
+
+GType gst_wasapi_device_provider_get_type (void);
+
+
+typedef struct _GstWasapiDevice GstWasapiDevice;
+typedef struct _GstWasapiDeviceClass GstWasapiDeviceClass;
+
+#define GST_TYPE_WASAPI_DEVICE (gst_wasapi_device_get_type())
+#define GST_IS_WASAPI_DEVICE(obj) (G_TYPE_CHECK_INSTANCE_TYPE ((obj), GST_TYPE_WASAPI_DEVICE))
+#define GST_IS_WASAPI_DEVICE_CLASS(klass) (G_TYPE_CHECK_CLASS_TYPE ((klass), GST_TYPE_WASAPI_DEVICE))
+#define GST_WASAPI_DEVICE_GET_CLASS(obj) (G_TYPE_INSTANCE_GET_CLASS ((obj), GST_TYPE_WASAPI_DEVICE, GstWasapiDeviceClass))
+#define GST_WASAPI_DEVICE(obj) (G_TYPE_CHECK_INSTANCE_CAST ((obj), GST_TYPE_WASAPI_DEVICE, GstWasapiDevice))
+#define GST_WASAPI_DEVICE_CLASS(klass) (G_TYPE_CHECK_CLASS_CAST ((klass), GST_TYPE_DEVICE, GstWasapiDeviceClass))
+#define GST_WASAPI_DEVICE_CAST(obj) ((GstWasapiDevice *)(obj))
+
+struct _GstWasapiDevice
+{
+ GstDevice parent;
+
+ gchar *strid;
+ const gchar *element;
+};
+
+struct _GstWasapiDeviceClass
+{
+ GstDeviceClass parent_class;
+};
+
+GType gst_wasapi_device_get_type (void);
+
+G_END_DECLS
+
+#endif /* __GST_WASAPI_DEVICE_H__ */
diff --git a/sys/wasapi/gstwasapisink.c b/sys/wasapi/gstwasapisink.c
new file mode 100644
index 0000000000..b10d391104
--- /dev/null
+++ b/sys/wasapi/gstwasapisink.c
@@ -0,0 +1,763 @@
+/*
+ * Copyright (C) 2008 Ole André Vadla Ravnås <ole.andre.ravnas@tandberg.com>
+ * Copyright (C) 2013 Collabora Ltd.
+ * Author: Sebastian Dröge <sebastian.droege@collabora.co.uk>
+ * Copyright (C) 2018 Centricular Ltd.
+ * Author: Nirbheek Chauhan <nirbheek@centricular.com>
+ *
+ * This library is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU Library General Public
+ * License as published by the Free Software Foundation; either
+ * version 2 of the License, or (at your option) any later version.
+ *
+ * This library 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
+ * Library General Public License for more details.
+ *
+ * You should have received a copy of the GNU Library General Public
+ * License along with this library; if not, write to the
+ * Free Software Foundation, Inc., 51 Franklin St, Fifth Floor,
+ * Boston, MA 02110-1301, USA.
+ */
+
+/**
+ * SECTION:element-wasapisink
+ * @title: wasapisink
+ *
+ * Provides audio playback using the Windows Audio Session API available with
+ * Vista and newer.
+ *
+ * ## Example pipelines
+ * |[
+ * gst-launch-1.0 -v audiotestsrc samplesperbuffer=160 ! wasapisink
+ * ]| Generate 20 ms buffers and render to the default audio device.
+ *
+ * |[
+ * gst-launch-1.0 -v audiotestsrc samplesperbuffer=160 ! wasapisink low-latency=true
+ * ]| Same as above, but with the minimum possible latency
+ *
+ */
+#ifdef HAVE_CONFIG_H
+# include <config.h>
+#endif
+
+#include "gstwasapisink.h"
+
+#include <avrt.h>
+
+GST_DEBUG_CATEGORY_STATIC (gst_wasapi_sink_debug);
+#define GST_CAT_DEFAULT gst_wasapi_sink_debug
+
+static GstStaticPadTemplate sink_template = GST_STATIC_PAD_TEMPLATE ("sink",
+ GST_PAD_SINK,
+ GST_PAD_ALWAYS,
+ GST_STATIC_CAPS (GST_WASAPI_STATIC_CAPS));
+
+#define DEFAULT_ROLE GST_WASAPI_DEVICE_ROLE_CONSOLE
+#define DEFAULT_MUTE FALSE
+#define DEFAULT_EXCLUSIVE FALSE
+#define DEFAULT_LOW_LATENCY FALSE
+#define DEFAULT_AUDIOCLIENT3 TRUE
+
+enum
+{
+ PROP_0,
+ PROP_ROLE,
+ PROP_MUTE,
+ PROP_DEVICE,
+ PROP_EXCLUSIVE,
+ PROP_LOW_LATENCY,
+ PROP_AUDIOCLIENT3
+};
+
+static void gst_wasapi_sink_dispose (GObject * object);
+static void gst_wasapi_sink_finalize (GObject * object);
+static void gst_wasapi_sink_set_property (GObject * object, guint prop_id,
+ const GValue * value, GParamSpec * pspec);
+static void gst_wasapi_sink_get_property (GObject * object, guint prop_id,
+ GValue * value, GParamSpec * pspec);
+
+static GstCaps *gst_wasapi_sink_get_caps (GstBaseSink * bsink,
+ GstCaps * filter);
+
+static gboolean gst_wasapi_sink_prepare (GstAudioSink * asink,
+ GstAudioRingBufferSpec * spec);
+static gboolean gst_wasapi_sink_unprepare (GstAudioSink * asink);
+static gboolean gst_wasapi_sink_open (GstAudioSink * asink);
+static gboolean gst_wasapi_sink_close (GstAudioSink * asink);
+static gint gst_wasapi_sink_write (GstAudioSink * asink,
+ gpointer data, guint length);
+static guint gst_wasapi_sink_delay (GstAudioSink * asink);
+static void gst_wasapi_sink_reset (GstAudioSink * asink);
+
+#define gst_wasapi_sink_parent_class parent_class
+G_DEFINE_TYPE (GstWasapiSink, gst_wasapi_sink, GST_TYPE_AUDIO_SINK);
+
+static void
+gst_wasapi_sink_class_init (GstWasapiSinkClass * klass)
+{
+ GObjectClass *gobject_class = G_OBJECT_CLASS (klass);
+ GstElementClass *gstelement_class = GST_ELEMENT_CLASS (klass);
+ GstBaseSinkClass *gstbasesink_class = GST_BASE_SINK_CLASS (klass);
+ GstAudioSinkClass *gstaudiosink_class = GST_AUDIO_SINK_CLASS (klass);
+
+ gobject_class->dispose = gst_wasapi_sink_dispose;
+ gobject_class->finalize = gst_wasapi_sink_finalize;
+ gobject_class->set_property = gst_wasapi_sink_set_property;
+ gobject_class->get_property = gst_wasapi_sink_get_property;
+
+ g_object_class_install_property (gobject_class,
+ PROP_ROLE,
+ g_param_spec_enum ("role", "Role",
+ "Role of the device: communications, multimedia, etc",
+ GST_WASAPI_DEVICE_TYPE_ROLE, DEFAULT_ROLE, G_PARAM_READWRITE |
+ G_PARAM_STATIC_STRINGS | GST_PARAM_MUTABLE_READY));
+
+ g_object_class_install_property (gobject_class,
+ PROP_MUTE,
+ g_param_spec_boolean ("mute", "Mute", "Mute state of this stream",
+ DEFAULT_MUTE, G_PARAM_READWRITE | G_PARAM_STATIC_STRINGS |
+ GST_PARAM_MUTABLE_PLAYING));
+
+ g_object_class_install_property (gobject_class,
+ PROP_DEVICE,
+ g_param_spec_string ("device", "Device",
+ "WASAPI playback device as a GUID string",
+ NULL, G_PARAM_READWRITE | G_PARAM_STATIC_STRINGS));
+
+ g_object_class_install_property (gobject_class,
+ PROP_EXCLUSIVE,
+ g_param_spec_boolean ("exclusive", "Exclusive mode",
+ "Open the device in exclusive mode",
+ DEFAULT_EXCLUSIVE, G_PARAM_READWRITE | G_PARAM_STATIC_STRINGS));
+
+ g_object_class_install_property (gobject_class,
+ PROP_LOW_LATENCY,
+ g_param_spec_boolean ("low-latency", "Low latency",
+ "Optimize all settings for lowest latency. Always safe to enable.",
+ DEFAULT_LOW_LATENCY, G_PARAM_READWRITE | G_PARAM_STATIC_STRINGS));
+
+ g_object_class_install_property (gobject_class,
+ PROP_AUDIOCLIENT3,
+ g_param_spec_boolean ("use-audioclient3", "Use the AudioClient3 API",
+ "Use the Windows 10 AudioClient3 API when available and if the "
+ "low-latency property is set to TRUE",
+ DEFAULT_AUDIOCLIENT3, G_PARAM_READWRITE | G_PARAM_STATIC_STRINGS));
+
+ gst_element_class_add_static_pad_template (gstelement_class, &sink_template);
+ gst_element_class_set_static_metadata (gstelement_class, "WasapiSrc",
+ "Sink/Audio/Hardware",
+ "Stream audio to an audio capture device through WASAPI",
+ "Nirbheek Chauhan <nirbheek@centricular.com>, "
+ "Ole André Vadla Ravnås <ole.andre.ravnas@tandberg.com>");
+
+ gstbasesink_class->get_caps = GST_DEBUG_FUNCPTR (gst_wasapi_sink_get_caps);
+
+ gstaudiosink_class->prepare = GST_DEBUG_FUNCPTR (gst_wasapi_sink_prepare);
+ gstaudiosink_class->unprepare = GST_DEBUG_FUNCPTR (gst_wasapi_sink_unprepare);
+ gstaudiosink_class->open = GST_DEBUG_FUNCPTR (gst_wasapi_sink_open);
+ gstaudiosink_class->close = GST_DEBUG_FUNCPTR (gst_wasapi_sink_close);
+ gstaudiosink_class->write = GST_DEBUG_FUNCPTR (gst_wasapi_sink_write);
+ gstaudiosink_class->delay = GST_DEBUG_FUNCPTR (gst_wasapi_sink_delay);
+ gstaudiosink_class->reset = GST_DEBUG_FUNCPTR (gst_wasapi_sink_reset);
+
+ GST_DEBUG_CATEGORY_INIT (gst_wasapi_sink_debug, "wasapisink",
+ 0, "Windows audio session API sink");
+
+ gst_type_mark_as_plugin_api (GST_WASAPI_DEVICE_TYPE_ROLE, 0);
+}
+
+static void
+gst_wasapi_sink_init (GstWasapiSink * self)
+{
+ self->role = DEFAULT_ROLE;
+ self->mute = DEFAULT_MUTE;
+ self->sharemode = AUDCLNT_SHAREMODE_SHARED;
+ self->low_latency = DEFAULT_LOW_LATENCY;
+ self->try_audioclient3 = DEFAULT_AUDIOCLIENT3;
+ self->event_handle = CreateEvent (NULL, FALSE, FALSE, NULL);
+ self->cancellable = CreateEvent (NULL, TRUE, FALSE, NULL);
+ self->client_needs_restart = FALSE;
+
+ self->enumerator = gst_mm_device_enumerator_new ();
+}
+
+static void
+gst_wasapi_sink_dispose (GObject * object)
+{
+ GstWasapiSink *self = GST_WASAPI_SINK (object);
+
+ if (self->event_handle != NULL) {
+ CloseHandle (self->event_handle);
+ self->event_handle = NULL;
+ }
+
+ if (self->cancellable != NULL) {
+ CloseHandle (self->cancellable);
+ self->cancellable = NULL;
+ }
+
+ if (self->client != NULL) {
+ IUnknown_Release (self->client);
+ self->client = NULL;
+ }
+
+ if (self->render_client != NULL) {
+ IUnknown_Release (self->render_client);
+ self->render_client = NULL;
+ }
+
+ gst_clear_object (&self->enumerator);
+
+ G_OBJECT_CLASS (gst_wasapi_sink_parent_class)->dispose (object);
+}
+
+static void
+gst_wasapi_sink_finalize (GObject * object)
+{
+ GstWasapiSink *self = GST_WASAPI_SINK (object);
+
+ CoTaskMemFree (self->mix_format);
+ self->mix_format = NULL;
+
+ if (self->cached_caps != NULL) {
+ gst_caps_unref (self->cached_caps);
+ self->cached_caps = NULL;
+ }
+
+ g_clear_pointer (&self->positions, g_free);
+ g_clear_pointer (&self->device_strid, g_free);
+ self->mute = FALSE;
+
+ G_OBJECT_CLASS (gst_wasapi_sink_parent_class)->finalize (object);
+}
+
+static void
+gst_wasapi_sink_set_property (GObject * object, guint prop_id,
+ const GValue * value, GParamSpec * pspec)
+{
+ GstWasapiSink *self = GST_WASAPI_SINK (object);
+
+ switch (prop_id) {
+ case PROP_ROLE:
+ self->role = gst_wasapi_device_role_to_erole (g_value_get_enum (value));
+ break;
+ case PROP_MUTE:
+ self->mute = g_value_get_boolean (value);
+ break;
+ case PROP_DEVICE:
+ {
+ const gchar *device = g_value_get_string (value);
+ g_free (self->device_strid);
+ self->device_strid =
+ device ? g_utf8_to_utf16 (device, -1, NULL, NULL, NULL) : NULL;
+ break;
+ }
+ case PROP_EXCLUSIVE:
+ self->sharemode = g_value_get_boolean (value)
+ ? AUDCLNT_SHAREMODE_EXCLUSIVE : AUDCLNT_SHAREMODE_SHARED;
+ break;
+ case PROP_LOW_LATENCY:
+ self->low_latency = g_value_get_boolean (value);
+ break;
+ case PROP_AUDIOCLIENT3:
+ self->try_audioclient3 = g_value_get_boolean (value);
+ break;
+ default:
+ G_OBJECT_WARN_INVALID_PROPERTY_ID (object, prop_id, pspec);
+ break;
+ }
+}
+
+static void
+gst_wasapi_sink_get_property (GObject * object, guint prop_id,
+ GValue * value, GParamSpec * pspec)
+{
+ GstWasapiSink *self = GST_WASAPI_SINK (object);
+
+ switch (prop_id) {
+ case PROP_ROLE:
+ g_value_set_enum (value, gst_wasapi_erole_to_device_role (self->role));
+ break;
+ case PROP_MUTE:
+ g_value_set_boolean (value, self->mute);
+ break;
+ case PROP_DEVICE:
+ g_value_take_string (value, self->device_strid ?
+ g_utf16_to_utf8 (self->device_strid, -1, NULL, NULL, NULL) : NULL);
+ break;
+ case PROP_EXCLUSIVE:
+ g_value_set_boolean (value,
+ self->sharemode == AUDCLNT_SHAREMODE_EXCLUSIVE);
+ break;
+ case PROP_LOW_LATENCY:
+ g_value_set_boolean (value, self->low_latency);
+ break;
+ case PROP_AUDIOCLIENT3:
+ g_value_set_boolean (value, self->try_audioclient3);
+ break;
+ default:
+ G_OBJECT_WARN_INVALID_PROPERTY_ID (object, prop_id, pspec);
+ break;
+ }
+}
+
+static gboolean
+gst_wasapi_sink_can_audioclient3 (GstWasapiSink * self)
+{
+ /* AudioClient3 API only makes sense in shared mode */
+ if (self->sharemode != AUDCLNT_SHAREMODE_SHARED)
+ return FALSE;
+
+ if (!self->try_audioclient3) {
+ GST_INFO_OBJECT (self, "AudioClient3 disabled by user");
+ return FALSE;
+ }
+
+ if (!gst_wasapi_util_have_audioclient3 ()) {
+ GST_INFO_OBJECT (self, "AudioClient3 not available on this OS");
+ return FALSE;
+ }
+
+ /* Only use audioclient3 when low-latency is requested because otherwise
+ * very slow machines and VMs with 1 CPU allocated will get glitches:
+ * https://bugzilla.gnome.org/show_bug.cgi?id=794497 */
+ if (!self->low_latency) {
+ GST_INFO_OBJECT (self, "AudioClient3 disabled because low-latency mode "
+ "was not requested");
+ return FALSE;
+ }
+
+ return TRUE;
+}
+
+static GstCaps *
+gst_wasapi_sink_get_caps (GstBaseSink * bsink, GstCaps * filter)
+{
+ GstWasapiSink *self = GST_WASAPI_SINK (bsink);
+ WAVEFORMATEX *format = NULL;
+ GstCaps *caps = NULL;
+
+ GST_DEBUG_OBJECT (self, "entering get caps");
+
+ if (self->cached_caps) {
+ caps = gst_caps_ref (self->cached_caps);
+ } else {
+ GstCaps *template_caps;
+ gboolean ret;
+
+ template_caps = gst_pad_get_pad_template_caps (bsink->sinkpad);
+
+ if (!self->client) {
+ caps = template_caps;
+ goto out;
+ }
+
+ ret = gst_wasapi_util_get_device_format (GST_ELEMENT (self),
+ self->sharemode, self->device, self->client, &format);
+ if (!ret) {
+ GST_ELEMENT_ERROR (self, STREAM, FORMAT, (NULL),
+ ("failed to detect format"));
+ gst_caps_unref (template_caps);
+ return NULL;
+ }
+
+ gst_wasapi_util_parse_waveformatex ((WAVEFORMATEXTENSIBLE *) format,
+ template_caps, &caps, &self->positions);
+ if (caps == NULL) {
+ GST_ELEMENT_ERROR (self, STREAM, FORMAT, (NULL), ("unknown format"));
+ gst_caps_unref (template_caps);
+ return NULL;
+ }
+
+ {
+ gchar *pos_str = gst_audio_channel_positions_to_string (self->positions,
+ format->nChannels);
+ GST_INFO_OBJECT (self, "positions are: %s", pos_str);
+ g_free (pos_str);
+ }
+
+ self->mix_format = format;
+ gst_caps_replace (&self->cached_caps, caps);
+ gst_caps_unref (template_caps);
+ }
+
+ if (filter) {
+ GstCaps *filtered =
+ gst_caps_intersect_full (filter, caps, GST_CAPS_INTERSECT_FIRST);
+ gst_caps_unref (caps);
+ caps = filtered;
+ }
+
+out:
+ GST_DEBUG_OBJECT (self, "returning caps %" GST_PTR_FORMAT, caps);
+ return caps;
+}
+
+static gboolean
+gst_wasapi_sink_open (GstAudioSink * asink)
+{
+ GstWasapiSink *self = GST_WASAPI_SINK (asink);
+ gboolean res = FALSE;
+ IMMDevice *device = NULL;
+ IAudioClient *client = NULL;
+
+ GST_DEBUG_OBJECT (self, "opening device");
+
+ if (self->client)
+ return TRUE;
+
+ /* FIXME: Switching the default device does not switch the stream to it,
+ * even if the old device was unplugged. We need to handle this somehow.
+ * For example, perhaps we should automatically switch to the new device if
+ * the default device is changed and a device isn't explicitly selected. */
+ if (!gst_wasapi_util_get_device (self->enumerator, eRender,
+ self->role, self->device_strid, &device)
+ || !gst_wasapi_util_get_audio_client (GST_ELEMENT (self),
+ device, &client)) {
+ if (!self->device_strid)
+ GST_ELEMENT_ERROR (self, RESOURCE, OPEN_WRITE, (NULL),
+ ("Failed to get default device"));
+ else
+ GST_ELEMENT_ERROR (self, RESOURCE, OPEN_WRITE, (NULL),
+ ("Failed to open device %S", self->device_strid));
+ goto beach;
+ }
+
+ self->client = client;
+ self->device = device;
+ res = TRUE;
+
+beach:
+
+ return res;
+}
+
+static gboolean
+gst_wasapi_sink_close (GstAudioSink * asink)
+{
+ GstWasapiSink *self = GST_WASAPI_SINK (asink);
+
+ if (self->device != NULL) {
+ IUnknown_Release (self->device);
+ self->device = NULL;
+ }
+
+ if (self->client != NULL) {
+ IUnknown_Release (self->client);
+ self->client = NULL;
+ }
+
+ return TRUE;
+}
+
+/* Get the empty space in the buffer that we have to write to */
+static gint
+gst_wasapi_sink_get_can_frames (GstWasapiSink * self)
+{
+ HRESULT hr;
+ guint n_frames_padding;
+
+ /* There is no padding in exclusive mode since there is no ringbuffer */
+ if (self->sharemode == AUDCLNT_SHAREMODE_EXCLUSIVE) {
+ GST_DEBUG_OBJECT (self, "exclusive mode, can write: %i",
+ self->buffer_frame_count);
+ return self->buffer_frame_count;
+ }
+
+ /* Frames the card hasn't rendered yet */
+ hr = IAudioClient_GetCurrentPadding (self->client, &n_frames_padding);
+ HR_FAILED_ELEMENT_ERROR_RET (hr, IAudioClient::GetCurrentPadding, self, -1);
+
+ GST_DEBUG_OBJECT (self, "%i unread frames (padding)", n_frames_padding);
+
+ /* We can write out these many frames */
+ return self->buffer_frame_count - n_frames_padding;
+}
+
+static gboolean
+gst_wasapi_sink_prepare (GstAudioSink * asink, GstAudioRingBufferSpec * spec)
+{
+ GstWasapiSink *self = GST_WASAPI_SINK (asink);
+ gboolean res = FALSE;
+ REFERENCE_TIME latency_rt;
+ guint bpf, rate, devicep_frames;
+ HRESULT hr;
+
+ if (!self->client) {
+ GST_DEBUG_OBJECT (self, "no IAudioClient, creating a new one");
+ if (!gst_wasapi_util_get_audio_client (GST_ELEMENT (self),
+ self->device, &self->client))
+ goto beach;
+ }
+
+ if (gst_wasapi_sink_can_audioclient3 (self)) {
+ if (!gst_wasapi_util_initialize_audioclient3 (GST_ELEMENT (self), spec,
+ (IAudioClient3 *) self->client, self->mix_format, self->low_latency,
+ FALSE, &devicep_frames))
+ goto beach;
+ } else {
+ if (!gst_wasapi_util_initialize_audioclient (GST_ELEMENT (self), spec,
+ self->client, self->mix_format, self->sharemode, self->low_latency,
+ FALSE, &devicep_frames))
+ goto beach;
+ }
+
+ bpf = GST_AUDIO_INFO_BPF (&spec->info);
+ rate = GST_AUDIO_INFO_RATE (&spec->info);
+
+ /* Total size of the allocated buffer that we will write to */
+ hr = IAudioClient_GetBufferSize (self->client, &self->buffer_frame_count);
+ HR_FAILED_GOTO (hr, IAudioClient::GetBufferSize, beach);
+
+ GST_INFO_OBJECT (self, "buffer size is %i frames, device period is %i "
+ "frames, bpf is %i bytes, rate is %i Hz", self->buffer_frame_count,
+ devicep_frames, bpf, rate);
+
+ /* Actual latency-time/buffer-time will be different now */
+ spec->segsize = devicep_frames * bpf;
+
+ /* We need a minimum of 2 segments to ensure glitch-free playback */
+ spec->segtotal = MAX (self->buffer_frame_count * bpf / spec->segsize, 2);
+
+ GST_INFO_OBJECT (self, "segsize is %i, segtotal is %i", spec->segsize,
+ spec->segtotal);
+
+ /* Get latency for logging */
+ hr = IAudioClient_GetStreamLatency (self->client, &latency_rt);
+ HR_FAILED_GOTO (hr, IAudioClient::GetStreamLatency, beach);
+
+ GST_INFO_OBJECT (self, "wasapi stream latency: %" G_GINT64_FORMAT " (%"
+ G_GINT64_FORMAT "ms)", latency_rt, latency_rt / 10000);
+
+ /* Set the event handler which will trigger writes */
+ hr = IAudioClient_SetEventHandle (self->client, self->event_handle);
+ HR_FAILED_GOTO (hr, IAudioClient::SetEventHandle, beach);
+
+ /* Get render sink client and start it up */
+ if (!gst_wasapi_util_get_render_client (GST_ELEMENT (self), self->client,
+ &self->render_client)) {
+ goto beach;
+ }
+
+ GST_INFO_OBJECT (self, "got render client");
+
+ /* To avoid start-up glitches, before starting the streaming, we fill the
+ * buffer with silence as recommended by the documentation:
+ * https://msdn.microsoft.com/en-us/library/windows/desktop/dd370879%28v=vs.85%29.aspx */
+ {
+ gint n_frames, len;
+ gint16 *dst = NULL;
+
+ n_frames = gst_wasapi_sink_get_can_frames (self);
+ if (n_frames < 1) {
+ GST_ELEMENT_ERROR (self, RESOURCE, WRITE, (NULL),
+ ("should have more than %i frames to write", n_frames));
+ goto beach;
+ }
+
+ len = n_frames * self->mix_format->nBlockAlign;
+
+ hr = IAudioRenderClient_GetBuffer (self->render_client, n_frames,
+ (BYTE **) & dst);
+ HR_FAILED_GOTO (hr, IAudioRenderClient::GetBuffer, beach);
+
+ GST_DEBUG_OBJECT (self, "pre-wrote %i bytes of silence", len);
+
+ hr = IAudioRenderClient_ReleaseBuffer (self->render_client, n_frames,
+ AUDCLNT_BUFFERFLAGS_SILENT);
+ HR_FAILED_GOTO (hr, IAudioRenderClient::ReleaseBuffer, beach);
+ }
+
+ hr = IAudioClient_Start (self->client);
+ HR_FAILED_GOTO (hr, IAudioClient::Start, beach);
+ self->client_needs_restart = FALSE;
+
+ gst_audio_ring_buffer_set_channel_positions (GST_AUDIO_BASE_SINK
+ (self)->ringbuffer, self->positions);
+
+ res = TRUE;
+
+ /* reset cancellable event handle */
+ ResetEvent (self->cancellable);
+
+beach:
+ /* unprepare() is not called if prepare() fails, but we want it to be, so call
+ * it manually when needed */
+ if (!res)
+ gst_wasapi_sink_unprepare (asink);
+
+ return res;
+}
+
+static gboolean
+gst_wasapi_sink_unprepare (GstAudioSink * asink)
+{
+ GstWasapiSink *self = GST_WASAPI_SINK (asink);
+
+ if (self->client != NULL) {
+ IUnknown_Release (self->client);
+ self->client = NULL;
+ }
+
+ if (self->render_client != NULL) {
+ IUnknown_Release (self->render_client);
+ self->render_client = NULL;
+ }
+
+ return TRUE;
+}
+
+static gint
+gst_wasapi_sink_write (GstAudioSink * asink, gpointer data, guint length)
+{
+ GstWasapiSink *self = GST_WASAPI_SINK (asink);
+ HRESULT hr;
+ gint16 *dst = NULL;
+ DWORD dwWaitResult;
+ guint can_frames, have_frames, n_frames, write_len, written_len = 0;
+ HANDLE event_handle[2];
+
+ event_handle[0] = self->event_handle;
+ event_handle[1] = self->cancellable;
+
+ GST_OBJECT_LOCK (self);
+ if (self->client_needs_restart) {
+ hr = IAudioClient_Start (self->client);
+ HR_FAILED_ELEMENT_ERROR_AND (hr, IAudioClient::Start, self,
+ GST_OBJECT_UNLOCK (self); goto err);
+ self->client_needs_restart = FALSE;
+ ResetEvent (self->cancellable);
+ }
+ GST_OBJECT_UNLOCK (self);
+
+ /* We have N frames to be written out */
+ have_frames = length / (self->mix_format->nBlockAlign);
+
+ if (self->sharemode == AUDCLNT_SHAREMODE_EXCLUSIVE) {
+ /* In exclusive mode we have to wait always */
+ dwWaitResult = WaitForMultipleObjects (2, event_handle, FALSE, INFINITE);
+ if (dwWaitResult != WAIT_OBJECT_0 && dwWaitResult != WAIT_OBJECT_0 + 1) {
+ GST_ERROR_OBJECT (self, "Error waiting for event handle: %x",
+ (guint) dwWaitResult);
+ goto err;
+ }
+
+ /* ::reset was requested */
+ if (dwWaitResult == WAIT_OBJECT_0 + 1) {
+ GST_DEBUG_OBJECT (self, "operation was cancelled");
+ return -1;
+ }
+
+ can_frames = gst_wasapi_sink_get_can_frames (self);
+ if (can_frames < 0) {
+ GST_ERROR_OBJECT (self, "Error getting frames to write to");
+ goto err;
+ }
+ /* In exclusive mode we need to fill the whole buffer in one go or
+ * GetBuffer will error out */
+ if (can_frames != have_frames) {
+ GST_ERROR_OBJECT (self,
+ "Need at %i frames to write for exclusive mode, but got %i",
+ can_frames, have_frames);
+ goto err;
+ }
+ } else {
+ /* In shared mode we can write parts of the buffer, so only wait
+ * in case we can't write anything */
+ can_frames = gst_wasapi_sink_get_can_frames (self);
+ if (can_frames < 0) {
+ GST_ERROR_OBJECT (self, "Error getting frames to write to");
+ goto err;
+ }
+
+ if (can_frames == 0) {
+ dwWaitResult = WaitForMultipleObjects (2, event_handle, FALSE, INFINITE);
+ if (dwWaitResult != WAIT_OBJECT_0 && dwWaitResult != WAIT_OBJECT_0 + 1) {
+ GST_ERROR_OBJECT (self, "Error waiting for event handle: %x",
+ (guint) dwWaitResult);
+ goto err;
+ }
+
+ /* ::reset was requested */
+ if (dwWaitResult == WAIT_OBJECT_0 + 1) {
+ GST_DEBUG_OBJECT (self, "operation was cancelled");
+ return -1;
+ }
+
+ can_frames = gst_wasapi_sink_get_can_frames (self);
+ if (can_frames < 0) {
+ GST_ERROR_OBJECT (self, "Error getting frames to write to");
+ goto err;
+ }
+ }
+ }
+
+ /* We will write out these many frames, and this much length */
+ n_frames = MIN (can_frames, have_frames);
+ write_len = n_frames * self->mix_format->nBlockAlign;
+
+ GST_DEBUG_OBJECT (self, "total: %i, have_frames: %i (%i bytes), "
+ "can_frames: %i, will write: %i (%i bytes)", self->buffer_frame_count,
+ have_frames, length, can_frames, n_frames, write_len);
+
+ hr = IAudioRenderClient_GetBuffer (self->render_client, n_frames,
+ (BYTE **) & dst);
+ HR_FAILED_ELEMENT_ERROR_AND (hr, IAudioRenderClient::GetBuffer, self,
+ goto err);
+
+ memcpy (dst, data, write_len);
+
+ hr = IAudioRenderClient_ReleaseBuffer (self->render_client, n_frames,
+ self->mute ? AUDCLNT_BUFFERFLAGS_SILENT : 0);
+ HR_FAILED_ELEMENT_ERROR_AND (hr, IAudioRenderClient::ReleaseBuffer, self,
+ goto err);
+
+ written_len = write_len;
+
+out:
+ return written_len;
+
+err:
+ written_len = -1;
+ goto out;
+}
+
+static guint
+gst_wasapi_sink_delay (GstAudioSink * asink)
+{
+ GstWasapiSink *self = GST_WASAPI_SINK (asink);
+ guint delay = 0;
+ HRESULT hr;
+
+ hr = IAudioClient_GetCurrentPadding (self->client, &delay);
+ HR_FAILED_RET (hr, IAudioClient::GetCurrentPadding, 0);
+
+ return delay;
+}
+
+static void
+gst_wasapi_sink_reset (GstAudioSink * asink)
+{
+ GstWasapiSink *self = GST_WASAPI_SINK (asink);
+ HRESULT hr;
+
+ GST_INFO_OBJECT (self, "reset called");
+
+ if (!self->client)
+ return;
+
+ SetEvent (self->cancellable);
+
+ GST_OBJECT_LOCK (self);
+ hr = IAudioClient_Stop (self->client);
+ HR_FAILED_AND (hr, IAudioClient::Stop, goto err);
+
+ hr = IAudioClient_Reset (self->client);
+ HR_FAILED_AND (hr, IAudioClient::Reset, goto err);
+
+err:
+ self->client_needs_restart = TRUE;
+ GST_OBJECT_UNLOCK (self);
+}
diff --git a/sys/wasapi/gstwasapisink.h b/sys/wasapi/gstwasapisink.h
new file mode 100644
index 0000000000..4aac69efc7
--- /dev/null
+++ b/sys/wasapi/gstwasapisink.h
@@ -0,0 +1,81 @@
+/*
+ * Copyright (C) 2008 Ole André Vadla Ravnås <ole.andre.ravnas@tandberg.com>
+ *
+ * This library is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU Library General Public
+ * License as published by the Free Software Foundation; either
+ * version 2 of the License, or (at your option) any later version.
+ *
+ * This library 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
+ * Library General Public License for more details.
+ *
+ * You should have received a copy of the GNU Library General Public
+ * License along with this library; if not, write to the
+ * Free Software Foundation, Inc., 51 Franklin St, Fifth Floor,
+ * Boston, MA 02110-1301, USA.
+ */
+
+#ifndef __GST_WASAPI_SINK_H__
+#define __GST_WASAPI_SINK_H__
+
+#include "gstwasapiutil.h"
+
+G_BEGIN_DECLS
+#define GST_TYPE_WASAPI_SINK \
+ (gst_wasapi_sink_get_type ())
+#define GST_WASAPI_SINK(obj) \
+ (G_TYPE_CHECK_INSTANCE_CAST ((obj), GST_TYPE_WASAPI_SINK, GstWasapiSink))
+#define GST_WASAPI_SINK_CLASS(klass) \
+ (G_TYPE_CHECK_CLASS_CAST ((klass), GST_TYPE_WASAPI_SINK, GstWasapiSinkClass))
+#define GST_IS_WASAPI_SINK(obj) \
+ (G_TYPE_CHECK_INSTANCE_TYPE ((obj), GST_TYPE_WASAPI_SINK))
+#define GST_IS_WASAPI_SINK_CLASS(klass) \
+ (G_TYPE_CHECK_CLASS_TYPE ((klass), GST_TYPE_WASAPI_SINK))
+typedef struct _GstWasapiSink GstWasapiSink;
+typedef struct _GstWasapiSinkClass GstWasapiSinkClass;
+
+struct _GstWasapiSink
+{
+ GstAudioSink parent;
+
+ GstMMDeviceEnumerator *enumerator;
+
+ IMMDevice *device;
+ IAudioClient *client;
+ IAudioRenderClient *render_client;
+ HANDLE event_handle;
+ HANDLE cancellable;
+ /* Client was reset, so it needs to be started again */
+ gboolean client_needs_restart;
+
+ /* Actual size of the allocated buffer */
+ guint buffer_frame_count;
+ /* The mix format that wasapi prefers in shared mode */
+ WAVEFORMATEX *mix_format;
+ /* The probed caps that we can accept */
+ GstCaps *cached_caps;
+ /* The channel positions in the data to be written to the device we
+ * will pass this to GstAudioRingbuffer so it can to it translate
+ * from the native GStreamer channel layout. */
+ GstAudioChannelPosition *positions;
+
+ /* properties */
+ gint role;
+ gint sharemode;
+ gboolean mute;
+ gboolean low_latency;
+ gboolean try_audioclient3;
+ wchar_t *device_strid;
+};
+
+struct _GstWasapiSinkClass
+{
+ GstAudioSinkClass parent_class;
+};
+
+GType gst_wasapi_sink_get_type (void);
+
+G_END_DECLS
+#endif /* __GST_WASAPI_SINK_H__ */
diff --git a/sys/wasapi/gstwasapisrc.c b/sys/wasapi/gstwasapisrc.c
new file mode 100644
index 0000000000..8bdff3be08
--- /dev/null
+++ b/sys/wasapi/gstwasapisrc.c
@@ -0,0 +1,934 @@
+/*
+ * Copyright (C) 2008 Ole André Vadla Ravnås <ole.andre.ravnas@tandberg.com>
+ * Copyright (C) 2018 Centricular Ltd.
+ * Author: Nirbheek Chauhan <nirbheek@centricular.com>
+ *
+ * This library is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU Library General Public
+ * License as published by the Free Software Foundation; either
+ * version 2 of the License, or (at your option) any later version.
+ *
+ * This library 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
+ * Library General Public License for more details.
+ *
+ * You should have received a copy of the GNU Library General Public
+ * License along with this library; if not, write to the
+ * Free Software Foundation, Inc., 51 Franklin St, Fifth Floor,
+ * Boston, MA 02110-1301, USA.
+ */
+
+/**
+ * SECTION:element-wasapisrc
+ * @title: wasapisrc
+ *
+ * Provides audio capture from the Windows Audio Session API available with
+ * Vista and newer.
+ *
+ * ## Example pipelines
+ * |[
+ * gst-launch-1.0 -v wasapisrc ! fakesink
+ * ]| Capture from the default audio device and render to fakesink.
+ *
+ * |[
+ * gst-launch-1.0 -v wasapisrc low-latency=true ! fakesink
+ * ]| Capture from the default audio device with the minimum possible latency and render to fakesink.
+ *
+ */
+#ifdef HAVE_CONFIG_H
+# include <config.h>
+#endif
+
+#include "gstwasapisrc.h"
+
+#include <avrt.h>
+
+GST_DEBUG_CATEGORY_STATIC (gst_wasapi_src_debug);
+#define GST_CAT_DEFAULT gst_wasapi_src_debug
+
+static GstStaticPadTemplate src_template = GST_STATIC_PAD_TEMPLATE ("src",
+ GST_PAD_SRC,
+ GST_PAD_ALWAYS,
+ GST_STATIC_CAPS (GST_WASAPI_STATIC_CAPS));
+
+#define DEFAULT_ROLE GST_WASAPI_DEVICE_ROLE_CONSOLE
+#define DEFAULT_LOOPBACK FALSE
+#define DEFAULT_EXCLUSIVE FALSE
+#define DEFAULT_LOW_LATENCY FALSE
+#define DEFAULT_AUDIOCLIENT3 FALSE
+/* The clock provided by WASAPI is always off and causes buffers to be late
+ * very quickly on the sink. Disable pending further investigation. */
+#define DEFAULT_PROVIDE_CLOCK FALSE
+
+enum
+{
+ PROP_0,
+ PROP_ROLE,
+ PROP_DEVICE,
+ PROP_LOOPBACK,
+ PROP_EXCLUSIVE,
+ PROP_LOW_LATENCY,
+ PROP_AUDIOCLIENT3
+};
+
+static void gst_wasapi_src_dispose (GObject * object);
+static void gst_wasapi_src_finalize (GObject * object);
+static void gst_wasapi_src_set_property (GObject * object, guint prop_id,
+ const GValue * value, GParamSpec * pspec);
+static void gst_wasapi_src_get_property (GObject * object, guint prop_id,
+ GValue * value, GParamSpec * pspec);
+
+static GstCaps *gst_wasapi_src_get_caps (GstBaseSrc * bsrc, GstCaps * filter);
+
+static gboolean gst_wasapi_src_open (GstAudioSrc * asrc);
+static gboolean gst_wasapi_src_close (GstAudioSrc * asrc);
+static gboolean gst_wasapi_src_prepare (GstAudioSrc * asrc,
+ GstAudioRingBufferSpec * spec);
+static gboolean gst_wasapi_src_unprepare (GstAudioSrc * asrc);
+static guint gst_wasapi_src_read (GstAudioSrc * asrc, gpointer data,
+ guint length, GstClockTime * timestamp);
+static guint gst_wasapi_src_delay (GstAudioSrc * asrc);
+static void gst_wasapi_src_reset (GstAudioSrc * asrc);
+
+#if DEFAULT_PROVIDE_CLOCK
+static GstClockTime gst_wasapi_src_get_time (GstClock * clock,
+ gpointer user_data);
+#endif
+
+#define gst_wasapi_src_parent_class parent_class
+G_DEFINE_TYPE (GstWasapiSrc, gst_wasapi_src, GST_TYPE_AUDIO_SRC);
+
+static void
+gst_wasapi_src_class_init (GstWasapiSrcClass * klass)
+{
+ GObjectClass *gobject_class = G_OBJECT_CLASS (klass);
+ GstElementClass *gstelement_class = GST_ELEMENT_CLASS (klass);
+ GstBaseSrcClass *gstbasesrc_class = GST_BASE_SRC_CLASS (klass);
+ GstAudioSrcClass *gstaudiosrc_class = GST_AUDIO_SRC_CLASS (klass);
+
+ gobject_class->dispose = gst_wasapi_src_dispose;
+ gobject_class->finalize = gst_wasapi_src_finalize;
+ gobject_class->set_property = gst_wasapi_src_set_property;
+ gobject_class->get_property = gst_wasapi_src_get_property;
+
+ g_object_class_install_property (gobject_class,
+ PROP_ROLE,
+ g_param_spec_enum ("role", "Role",
+ "Role of the device: communications, multimedia, etc",
+ GST_WASAPI_DEVICE_TYPE_ROLE, DEFAULT_ROLE, G_PARAM_READWRITE |
+ G_PARAM_STATIC_STRINGS | GST_PARAM_MUTABLE_READY));
+
+ g_object_class_install_property (gobject_class,
+ PROP_DEVICE,
+ g_param_spec_string ("device", "Device",
+ "WASAPI playback device as a GUID string",
+ NULL, G_PARAM_READWRITE | G_PARAM_STATIC_STRINGS));
+
+ g_object_class_install_property (gobject_class,
+ PROP_LOOPBACK,
+ g_param_spec_boolean ("loopback", "Loopback recording",
+ "Open the sink device for loopback recording",
+ DEFAULT_LOOPBACK, G_PARAM_READWRITE | G_PARAM_STATIC_STRINGS));
+
+ g_object_class_install_property (gobject_class,
+ PROP_EXCLUSIVE,
+ g_param_spec_boolean ("exclusive", "Exclusive mode",
+ "Open the device in exclusive mode",
+ DEFAULT_EXCLUSIVE, G_PARAM_READWRITE | G_PARAM_STATIC_STRINGS));
+
+ g_object_class_install_property (gobject_class,
+ PROP_LOW_LATENCY,
+ g_param_spec_boolean ("low-latency", "Low latency",
+ "Optimize all settings for lowest latency. Always safe to enable.",
+ DEFAULT_LOW_LATENCY, G_PARAM_READWRITE | G_PARAM_STATIC_STRINGS));
+
+ g_object_class_install_property (gobject_class,
+ PROP_AUDIOCLIENT3,
+ g_param_spec_boolean ("use-audioclient3", "Use the AudioClient3 API",
+ "Whether to use the Windows 10 AudioClient3 API when available",
+ DEFAULT_AUDIOCLIENT3, G_PARAM_READWRITE | G_PARAM_STATIC_STRINGS));
+
+ gst_element_class_add_static_pad_template (gstelement_class, &src_template);
+ gst_element_class_set_static_metadata (gstelement_class, "WasapiSrc",
+ "Source/Audio/Hardware",
+ "Stream audio from an audio capture device through WASAPI",
+ "Nirbheek Chauhan <nirbheek@centricular.com>, "
+ "Ole André Vadla Ravnås <ole.andre.ravnas@tandberg.com>");
+
+ gstbasesrc_class->get_caps = GST_DEBUG_FUNCPTR (gst_wasapi_src_get_caps);
+
+ gstaudiosrc_class->open = GST_DEBUG_FUNCPTR (gst_wasapi_src_open);
+ gstaudiosrc_class->close = GST_DEBUG_FUNCPTR (gst_wasapi_src_close);
+ gstaudiosrc_class->read = GST_DEBUG_FUNCPTR (gst_wasapi_src_read);
+ gstaudiosrc_class->prepare = GST_DEBUG_FUNCPTR (gst_wasapi_src_prepare);
+ gstaudiosrc_class->unprepare = GST_DEBUG_FUNCPTR (gst_wasapi_src_unprepare);
+ gstaudiosrc_class->delay = GST_DEBUG_FUNCPTR (gst_wasapi_src_delay);
+ gstaudiosrc_class->reset = GST_DEBUG_FUNCPTR (gst_wasapi_src_reset);
+
+ GST_DEBUG_CATEGORY_INIT (gst_wasapi_src_debug, "wasapisrc",
+ 0, "Windows audio session API source");
+
+ gst_type_mark_as_plugin_api (GST_WASAPI_DEVICE_TYPE_ROLE, 0);
+}
+
+static void
+gst_wasapi_src_init (GstWasapiSrc * self)
+{
+#if DEFAULT_PROVIDE_CLOCK
+ /* override with a custom clock */
+ if (GST_AUDIO_BASE_SRC (self)->clock)
+ gst_object_unref (GST_AUDIO_BASE_SRC (self)->clock);
+
+ GST_AUDIO_BASE_SRC (self)->clock = gst_audio_clock_new ("GstWasapiSrcClock",
+ gst_wasapi_src_get_time, gst_object_ref (self),
+ (GDestroyNotify) gst_object_unref);
+#endif
+
+ self->role = DEFAULT_ROLE;
+ self->sharemode = AUDCLNT_SHAREMODE_SHARED;
+ self->loopback = DEFAULT_LOOPBACK;
+ self->low_latency = DEFAULT_LOW_LATENCY;
+ self->try_audioclient3 = DEFAULT_AUDIOCLIENT3;
+ self->event_handle = CreateEvent (NULL, FALSE, FALSE, NULL);
+ self->cancellable = CreateEvent (NULL, TRUE, FALSE, NULL);
+ self->client_needs_restart = FALSE;
+ self->adapter = gst_adapter_new ();
+
+ /* Extra event handles used for loopback */
+ self->loopback_event_handle = CreateEvent (NULL, FALSE, FALSE, NULL);
+ self->loopback_cancellable = CreateEvent (NULL, TRUE, FALSE, NULL);
+
+ self->enumerator = gst_mm_device_enumerator_new ();
+}
+
+static void
+gst_wasapi_src_dispose (GObject * object)
+{
+ GstWasapiSrc *self = GST_WASAPI_SRC (object);
+
+ if (self->event_handle != NULL) {
+ CloseHandle (self->event_handle);
+ self->event_handle = NULL;
+ }
+
+ if (self->cancellable != NULL) {
+ CloseHandle (self->cancellable);
+ self->cancellable = NULL;
+ }
+
+ if (self->client_clock != NULL) {
+ IUnknown_Release (self->client_clock);
+ self->client_clock = NULL;
+ }
+
+ if (self->client != NULL) {
+ IUnknown_Release (self->client);
+ self->client = NULL;
+ }
+
+ if (self->capture_client != NULL) {
+ IUnknown_Release (self->capture_client);
+ self->capture_client = NULL;
+ }
+
+ if (self->loopback_client != NULL) {
+ IUnknown_Release (self->loopback_client);
+ self->loopback_client = NULL;
+ }
+
+ if (self->loopback_event_handle != NULL) {
+ CloseHandle (self->loopback_event_handle);
+ self->loopback_event_handle = NULL;
+ }
+
+ if (self->loopback_cancellable != NULL) {
+ CloseHandle (self->loopback_cancellable);
+ self->loopback_cancellable = NULL;
+ }
+
+ gst_clear_object (&self->enumerator);
+
+ G_OBJECT_CLASS (parent_class)->dispose (object);
+}
+
+static void
+gst_wasapi_src_finalize (GObject * object)
+{
+ GstWasapiSrc *self = GST_WASAPI_SRC (object);
+
+ CoTaskMemFree (self->mix_format);
+ self->mix_format = NULL;
+
+ g_clear_pointer (&self->cached_caps, gst_caps_unref);
+ g_clear_pointer (&self->positions, g_free);
+ g_clear_pointer (&self->device_strid, g_free);
+
+ g_object_unref (self->adapter);
+ self->adapter = NULL;
+
+ G_OBJECT_CLASS (parent_class)->finalize (object);
+}
+
+static void
+gst_wasapi_src_set_property (GObject * object, guint prop_id,
+ const GValue * value, GParamSpec * pspec)
+{
+ GstWasapiSrc *self = GST_WASAPI_SRC (object);
+
+ switch (prop_id) {
+ case PROP_ROLE:
+ self->role = gst_wasapi_device_role_to_erole (g_value_get_enum (value));
+ break;
+ case PROP_DEVICE:
+ {
+ const gchar *device = g_value_get_string (value);
+ g_free (self->device_strid);
+ self->device_strid =
+ device ? g_utf8_to_utf16 (device, -1, NULL, NULL, NULL) : NULL;
+ break;
+ }
+ case PROP_LOOPBACK:
+ self->loopback = g_value_get_boolean (value);
+ break;
+ case PROP_EXCLUSIVE:
+ self->sharemode = g_value_get_boolean (value)
+ ? AUDCLNT_SHAREMODE_EXCLUSIVE : AUDCLNT_SHAREMODE_SHARED;
+ break;
+ case PROP_LOW_LATENCY:
+ self->low_latency = g_value_get_boolean (value);
+ break;
+ case PROP_AUDIOCLIENT3:
+ self->try_audioclient3 = g_value_get_boolean (value);
+ break;
+ default:
+ G_OBJECT_WARN_INVALID_PROPERTY_ID (object, prop_id, pspec);
+ break;
+ }
+}
+
+static void
+gst_wasapi_src_get_property (GObject * object, guint prop_id,
+ GValue * value, GParamSpec * pspec)
+{
+ GstWasapiSrc *self = GST_WASAPI_SRC (object);
+
+ switch (prop_id) {
+ case PROP_ROLE:
+ g_value_set_enum (value, gst_wasapi_erole_to_device_role (self->role));
+ break;
+ case PROP_DEVICE:
+ g_value_take_string (value, self->device_strid ?
+ g_utf16_to_utf8 (self->device_strid, -1, NULL, NULL, NULL) : NULL);
+ break;
+ case PROP_LOOPBACK:
+ g_value_set_boolean (value, self->loopback);
+ break;
+ case PROP_EXCLUSIVE:
+ g_value_set_boolean (value,
+ self->sharemode == AUDCLNT_SHAREMODE_EXCLUSIVE);
+ break;
+ case PROP_LOW_LATENCY:
+ g_value_set_boolean (value, self->low_latency);
+ break;
+ case PROP_AUDIOCLIENT3:
+ g_value_set_boolean (value, self->try_audioclient3);
+ break;
+ default:
+ G_OBJECT_WARN_INVALID_PROPERTY_ID (object, prop_id, pspec);
+ break;
+ }
+}
+
+static gboolean
+gst_wasapi_src_can_audioclient3 (GstWasapiSrc * self)
+{
+ return (self->sharemode == AUDCLNT_SHAREMODE_SHARED &&
+ self->try_audioclient3 && gst_wasapi_util_have_audioclient3 ());
+}
+
+static GstCaps *
+gst_wasapi_src_get_caps (GstBaseSrc * bsrc, GstCaps * filter)
+{
+ GstWasapiSrc *self = GST_WASAPI_SRC (bsrc);
+ WAVEFORMATEX *format = NULL;
+ GstCaps *caps = NULL;
+
+ GST_DEBUG_OBJECT (self, "entering get caps");
+
+ if (self->cached_caps) {
+ caps = gst_caps_ref (self->cached_caps);
+ } else {
+ GstCaps *template_caps;
+ gboolean ret;
+
+ template_caps = gst_pad_get_pad_template_caps (bsrc->srcpad);
+
+ if (!self->client) {
+ caps = template_caps;
+ goto out;
+ }
+
+ ret = gst_wasapi_util_get_device_format (GST_ELEMENT (self),
+ self->sharemode, self->device, self->client, &format);
+ if (!ret) {
+ GST_ELEMENT_ERROR (self, STREAM, FORMAT, (NULL),
+ ("failed to detect format"));
+ gst_caps_unref (template_caps);
+ return NULL;
+ }
+
+ gst_wasapi_util_parse_waveformatex ((WAVEFORMATEXTENSIBLE *) format,
+ template_caps, &caps, &self->positions);
+ if (caps == NULL) {
+ GST_ELEMENT_ERROR (self, STREAM, FORMAT, (NULL), ("unknown format"));
+ gst_caps_unref (template_caps);
+ return NULL;
+ }
+
+ {
+ gchar *pos_str = gst_audio_channel_positions_to_string (self->positions,
+ format->nChannels);
+ GST_INFO_OBJECT (self, "positions are: %s", pos_str);
+ g_free (pos_str);
+ }
+
+ self->mix_format = format;
+ gst_caps_replace (&self->cached_caps, caps);
+ gst_caps_unref (template_caps);
+ }
+
+ if (filter) {
+ GstCaps *filtered =
+ gst_caps_intersect_full (filter, caps, GST_CAPS_INTERSECT_FIRST);
+ gst_caps_unref (caps);
+ caps = filtered;
+ }
+
+out:
+ GST_DEBUG_OBJECT (self, "returning caps %" GST_PTR_FORMAT, caps);
+ return caps;
+}
+
+static gboolean
+gst_wasapi_src_open (GstAudioSrc * asrc)
+{
+ GstWasapiSrc *self = GST_WASAPI_SRC (asrc);
+ gboolean res = FALSE;
+ IAudioClient *client = NULL;
+ IMMDevice *device = NULL;
+ IMMDevice *loopback_device = NULL;
+
+ if (self->client)
+ return TRUE;
+
+ /* FIXME: Switching the default device does not switch the stream to it,
+ * even if the old device was unplugged. We need to handle this somehow.
+ * For example, perhaps we should automatically switch to the new device if
+ * the default device is changed and a device isn't explicitly selected. */
+ if (!gst_wasapi_util_get_device (self->enumerator,
+ self->loopback ? eRender : eCapture, self->role, self->device_strid,
+ &device)
+ || !gst_wasapi_util_get_audio_client (GST_ELEMENT (self),
+ device, &client)) {
+ if (!self->device_strid)
+ GST_ELEMENT_ERROR (self, RESOURCE, OPEN_READ, (NULL),
+ ("Failed to get default device"));
+ else
+ GST_ELEMENT_ERROR (self, RESOURCE, OPEN_READ, (NULL),
+ ("Failed to open device %S", self->device_strid));
+ goto beach;
+ }
+
+ /* An oddness of wasapi loopback feature is that capture client will not
+ * provide any audio data if there is no outputting sound.
+ * To workaround this problem, probably we can add timeout around loop
+ * in this case but it's glitch prone. So, instead of timeout,
+ * we will keep pusing silence data to into wasapi client so that make audio
+ * client report audio data in any case
+ */
+ if (!gst_wasapi_util_get_device (self->enumerator,
+ eRender, self->role, self->device_strid, &loopback_device)
+ || !gst_wasapi_util_get_audio_client (GST_ELEMENT (self),
+ loopback_device, &self->loopback_client)) {
+ if (!self->device_strid)
+ GST_ELEMENT_ERROR (self, RESOURCE, OPEN_READ, (NULL),
+ ("Failed to get default device for loopback"));
+ else
+ GST_ELEMENT_ERROR (self, RESOURCE, OPEN_READ, (NULL),
+ ("Failed to open device %S", self->device_strid));
+ goto beach;
+
+ /* no need to hold this object */
+ IUnknown_Release (loopback_device);
+ }
+
+ self->client = client;
+ self->device = device;
+ res = TRUE;
+
+beach:
+
+ return res;
+}
+
+static gboolean
+gst_wasapi_src_close (GstAudioSrc * asrc)
+{
+ GstWasapiSrc *self = GST_WASAPI_SRC (asrc);
+
+ if (self->device != NULL) {
+ IUnknown_Release (self->device);
+ self->device = NULL;
+ }
+
+ if (self->client != NULL) {
+ IUnknown_Release (self->client);
+ self->client = NULL;
+ }
+
+ if (self->loopback_client != NULL) {
+ IUnknown_Release (self->loopback_client);
+ self->loopback_client = NULL;
+ }
+
+ return TRUE;
+}
+
+static gpointer
+gst_wasapi_src_loopback_silence_feeding_thread (GstWasapiSrc * self)
+{
+ HRESULT hr;
+ UINT32 buffer_frames;
+ gboolean res G_GNUC_UNUSED = FALSE;
+ BYTE *data;
+ DWORD dwWaitResult;
+ HANDLE event_handle[2];
+ UINT32 padding;
+ UINT32 n_frames;
+
+ /* NOTE: if this task cause glitch, we need to consider thread priority
+ * adjusing. See gstaudioutilsprivate.c (e.g., AvSetMmThreadCharacteristics)
+ * for this context */
+
+ GST_INFO_OBJECT (self, "Run loopback silence feeding thread");
+
+ event_handle[0] = self->loopback_event_handle;
+ event_handle[1] = self->loopback_cancellable;
+
+ hr = IAudioClient_GetBufferSize (self->loopback_client, &buffer_frames);
+ HR_FAILED_GOTO (hr, IAudioClient::GetBufferSize, beach);
+
+ hr = IAudioClient_SetEventHandle (self->loopback_client,
+ self->loopback_event_handle);
+ HR_FAILED_GOTO (hr, IAudioClient::SetEventHandle, beach);
+
+ /* To avoid start-up glitches, before starting the streaming, we fill the
+ * buffer with silence as recommended by the documentation:
+ * https://msdn.microsoft.com/en-us/library/windows/desktop/dd370879%28v=vs.85%29.aspx */
+ hr = IAudioRenderClient_GetBuffer (self->loopback_render_client,
+ buffer_frames, &data);
+ HR_FAILED_GOTO (hr, IAudioRenderClient::GetBuffer, beach);
+
+ hr = IAudioRenderClient_ReleaseBuffer (self->loopback_render_client,
+ buffer_frames, AUDCLNT_BUFFERFLAGS_SILENT);
+ HR_FAILED_GOTO (hr, IAudioRenderClient::ReleaseBuffer, beach);
+
+ hr = IAudioClient_Start (self->loopback_client);
+ HR_FAILED_GOTO (hr, IAudioClock::Start, beach);
+
+ /* There is an OS bug prior to Windows 10, that is loopback capture client
+ * will not receive event (in case of event-driven mode).
+ * A guide for workaround this case is that signal it whenever render client
+ * writes data.
+ * See https://docs.microsoft.com/en-us/windows/win32/api/audioclient/nf-audioclient-iaudioclient-initialize
+ */
+
+ /* Signal for read thread to wakeup */
+ SetEvent (self->event_handle);
+
+ /* Ok, now we are ready for running for feeding silence data */
+ while (1) {
+ dwWaitResult = WaitForMultipleObjects (2, event_handle, FALSE, INFINITE);
+ if (dwWaitResult != WAIT_OBJECT_0 && dwWaitResult != WAIT_OBJECT_0 + 1) {
+ GST_ERROR_OBJECT (self, "Error waiting for event handle: %x",
+ (guint) dwWaitResult);
+ goto stop;
+ }
+
+ /* Stopping was requested from unprepare() */
+ if (dwWaitResult == WAIT_OBJECT_0 + 1) {
+ GST_DEBUG_OBJECT (self, "operation was cancelled");
+ goto stop;
+ }
+
+ hr = IAudioClient_GetCurrentPadding (self->loopback_client, &padding);
+ HR_FAILED_GOTO (hr, IAudioClock::Start, stop);
+
+ if (buffer_frames < padding) {
+ GST_WARNING_OBJECT (self,
+ "Current padding %d is too large (buffer size %d)",
+ padding, buffer_frames);
+ n_frames = 0;
+ } else {
+ n_frames = buffer_frames - padding;
+ }
+
+ hr = IAudioRenderClient_GetBuffer (self->loopback_render_client, n_frames,
+ &data);
+ HR_FAILED_GOTO (hr, IAudioRenderClient::GetBuffer, stop);
+
+ hr = IAudioRenderClient_ReleaseBuffer (self->loopback_render_client,
+ n_frames, AUDCLNT_BUFFERFLAGS_SILENT);
+ HR_FAILED_GOTO (hr, IAudioRenderClient::ReleaseBuffer, stop);
+
+ /* Signal for read thread to wakeup */
+ SetEvent (self->event_handle);
+ }
+
+stop:
+ IAudioClient_Stop (self->loopback_client);
+
+beach:
+ GST_INFO_OBJECT (self, "Terminate loopback silence feeding thread");
+
+ return NULL;
+}
+
+static gboolean
+gst_wasapi_src_prepare (GstAudioSrc * asrc, GstAudioRingBufferSpec * spec)
+{
+ GstWasapiSrc *self = GST_WASAPI_SRC (asrc);
+ gboolean res = FALSE;
+ REFERENCE_TIME latency_rt;
+ guint bpf, rate, devicep_frames, buffer_frames;
+ HRESULT hr;
+
+ if (gst_wasapi_src_can_audioclient3 (self)) {
+ if (!gst_wasapi_util_initialize_audioclient3 (GST_ELEMENT (self), spec,
+ (IAudioClient3 *) self->client, self->mix_format, self->low_latency,
+ self->loopback, &devicep_frames))
+ goto beach;
+ } else {
+ if (!gst_wasapi_util_initialize_audioclient (GST_ELEMENT (self), spec,
+ self->client, self->mix_format, self->sharemode, self->low_latency,
+ self->loopback, &devicep_frames))
+ goto beach;
+ }
+
+ bpf = GST_AUDIO_INFO_BPF (&spec->info);
+ rate = GST_AUDIO_INFO_RATE (&spec->info);
+
+ /* Total size in frames of the allocated buffer that we will read from */
+ hr = IAudioClient_GetBufferSize (self->client, &buffer_frames);
+ HR_FAILED_GOTO (hr, IAudioClient::GetBufferSize, beach);
+
+ GST_INFO_OBJECT (self, "buffer size is %i frames, device period is %i "
+ "frames, bpf is %i bytes, rate is %i Hz", buffer_frames,
+ devicep_frames, bpf, rate);
+
+ /* Actual latency-time/buffer-time will be different now */
+ spec->segsize = devicep_frames * bpf;
+
+ /* We need a minimum of 2 segments to ensure glitch-free playback */
+ spec->segtotal = MAX (buffer_frames * bpf / spec->segsize, 2);
+
+ GST_INFO_OBJECT (self, "segsize is %i, segtotal is %i", spec->segsize,
+ spec->segtotal);
+
+ /* Get WASAPI latency for logging */
+ hr = IAudioClient_GetStreamLatency (self->client, &latency_rt);
+ HR_FAILED_GOTO (hr, IAudioClient::GetStreamLatency, beach);
+
+ GST_INFO_OBJECT (self, "wasapi stream latency: %" G_GINT64_FORMAT " (%"
+ G_GINT64_FORMAT " ms)", latency_rt, latency_rt / 10000);
+
+ /* Set the event handler which will trigger reads */
+ hr = IAudioClient_SetEventHandle (self->client, self->event_handle);
+ HR_FAILED_GOTO (hr, IAudioClient::SetEventHandle, beach);
+
+ /* Get the clock and the clock freq */
+ if (!gst_wasapi_util_get_clock (GST_ELEMENT (self), self->client,
+ &self->client_clock))
+ goto beach;
+
+ hr = IAudioClock_GetFrequency (self->client_clock, &self->client_clock_freq);
+ HR_FAILED_GOTO (hr, IAudioClock::GetFrequency, beach);
+
+ GST_INFO_OBJECT (self, "wasapi clock freq is %" G_GUINT64_FORMAT,
+ self->client_clock_freq);
+
+ /* Get capture source client and start it up */
+ if (!gst_wasapi_util_get_capture_client (GST_ELEMENT (self), self->client,
+ &self->capture_client)) {
+ goto beach;
+ }
+
+ /* In case loopback, spawn another dedicated thread for feeding silence data
+ * into wasapi render client */
+ if (self->loopback) {
+ /* don't need to be audioclient3 or low-latency since we will keep pushing
+ * silence data which is not varying over entire playback */
+ if (!gst_wasapi_util_initialize_audioclient (GST_ELEMENT (self), spec,
+ self->loopback_client, self->mix_format, self->sharemode,
+ FALSE, FALSE, &devicep_frames))
+ goto beach;
+
+ if (!gst_wasapi_util_get_render_client (GST_ELEMENT (self),
+ self->loopback_client, &self->loopback_render_client)) {
+ goto beach;
+ }
+
+ self->loopback_thread = g_thread_new ("wasapi-loopback",
+ (GThreadFunc) gst_wasapi_src_loopback_silence_feeding_thread, self);
+ }
+
+ hr = IAudioClient_Start (self->client);
+ HR_FAILED_GOTO (hr, IAudioClock::Start, beach);
+ self->client_needs_restart = FALSE;
+
+ gst_audio_ring_buffer_set_channel_positions (GST_AUDIO_BASE_SRC
+ (self)->ringbuffer, self->positions);
+
+ res = TRUE;
+
+ /* reset cancellable event handle */
+ ResetEvent (self->cancellable);
+
+beach:
+
+ /* unprepare() is not called if prepare() fails, but we want it to be, so call
+ * it manually when needed */
+ if (!res)
+ gst_wasapi_src_unprepare (asrc);
+
+ return res;
+}
+
+static gboolean
+gst_wasapi_src_unprepare (GstAudioSrc * asrc)
+{
+ GstWasapiSrc *self = GST_WASAPI_SRC (asrc);
+
+ if (self->client != NULL) {
+ IAudioClient_Stop (self->client);
+ }
+
+ if (self->capture_client != NULL) {
+ IUnknown_Release (self->capture_client);
+ self->capture_client = NULL;
+ }
+
+ if (self->client_clock != NULL) {
+ IUnknown_Release (self->client_clock);
+ self->client_clock = NULL;
+ }
+
+ if (self->loopback_thread) {
+ GST_DEBUG_OBJECT (self, "loopback task thread is stopping");
+
+ SetEvent (self->loopback_cancellable);
+
+ g_thread_join (self->loopback_thread);
+ self->loopback_thread = NULL;
+ ResetEvent (self->loopback_cancellable);
+ GST_DEBUG_OBJECT (self, "loopback task thread has been stopped");
+ }
+
+ if (self->loopback_render_client != NULL) {
+ IUnknown_Release (self->loopback_render_client);
+ self->loopback_render_client = NULL;
+ }
+
+ self->client_clock_freq = 0;
+
+ return TRUE;
+}
+
+static guint
+gst_wasapi_src_read (GstAudioSrc * asrc, gpointer data, guint length,
+ GstClockTime * timestamp)
+{
+ GstWasapiSrc *self = GST_WASAPI_SRC (asrc);
+ HRESULT hr;
+ gint16 *from = NULL;
+ guint wanted = length;
+ guint bpf;
+ DWORD flags;
+
+ GST_OBJECT_LOCK (self);
+ if (self->client_needs_restart) {
+ hr = IAudioClient_Start (self->client);
+ HR_FAILED_ELEMENT_ERROR_AND (hr, IAudioClient::Start, self,
+ GST_OBJECT_UNLOCK (self); goto err);
+ self->client_needs_restart = FALSE;
+ ResetEvent (self->cancellable);
+ gst_adapter_clear (self->adapter);
+ }
+
+ bpf = self->mix_format->nBlockAlign;
+ GST_OBJECT_UNLOCK (self);
+
+ /* If we've accumulated enough data, return it immediately */
+ if (gst_adapter_available (self->adapter) >= wanted) {
+ memcpy (data, gst_adapter_map (self->adapter, wanted), wanted);
+ gst_adapter_flush (self->adapter, wanted);
+ GST_DEBUG_OBJECT (self, "Adapter has enough data, returning %i", wanted);
+ goto out;
+ }
+
+ while (wanted > 0) {
+ DWORD dwWaitResult;
+ guint got_frames, avail_frames, n_frames, want_frames, read_len;
+ HANDLE event_handle[2];
+
+ event_handle[0] = self->event_handle;
+ event_handle[1] = self->cancellable;
+
+ /* Wait for data to become available */
+ dwWaitResult = WaitForMultipleObjects (2, event_handle, FALSE, INFINITE);
+ if (dwWaitResult != WAIT_OBJECT_0 && dwWaitResult != WAIT_OBJECT_0 + 1) {
+ GST_ERROR_OBJECT (self, "Error waiting for event handle: %x",
+ (guint) dwWaitResult);
+ goto err;
+ }
+
+ /* ::reset was requested */
+ if (dwWaitResult == WAIT_OBJECT_0 + 1) {
+ GST_DEBUG_OBJECT (self, "operation was cancelled");
+ return -1;
+ }
+
+ hr = IAudioCaptureClient_GetBuffer (self->capture_client,
+ (BYTE **) & from, &got_frames, &flags, NULL, NULL);
+ if (hr != S_OK) {
+ if (hr == AUDCLNT_S_BUFFER_EMPTY) {
+ gchar *msg = gst_wasapi_util_hresult_to_string (hr);
+ GST_WARNING_OBJECT (self, "IAudioCaptureClient::GetBuffer failed: %s"
+ ", retrying", msg);
+ g_free (msg);
+ length = 0;
+ goto out;
+ }
+ HR_FAILED_ELEMENT_ERROR_AND (hr, IAudioCaptureClient::GetBuffer, self,
+ goto err);
+ }
+
+ if (G_UNLIKELY (flags != 0)) {
+ /* https://docs.microsoft.com/en-us/windows/win32/api/audioclient/ne-audioclient-_audclnt_bufferflags */
+ if (flags & AUDCLNT_BUFFERFLAGS_DATA_DISCONTINUITY)
+ GST_DEBUG_OBJECT (self, "WASAPI reported discontinuity (glitch?)");
+ if (flags & AUDCLNT_BUFFERFLAGS_TIMESTAMP_ERROR)
+ GST_DEBUG_OBJECT (self, "WASAPI reported a timestamp error");
+ }
+
+ /* Copy all the frames we got into the adapter, and then extract at most
+ * @wanted size of frames from it. This helps when ::GetBuffer returns more
+ * data than we can handle right now. */
+ {
+ GstBuffer *tmp = gst_buffer_new_allocate (NULL, got_frames * bpf, NULL);
+ /* If flags has AUDCLNT_BUFFERFLAGS_SILENT, we will ignore the actual
+ * data and write out silence, see:
+ * https://docs.microsoft.com/en-us/windows/win32/api/audioclient/ne-audioclient-_audclnt_bufferflags */
+ if (flags & AUDCLNT_BUFFERFLAGS_SILENT)
+ memset (from, 0, got_frames * bpf);
+ gst_buffer_fill (tmp, 0, from, got_frames * bpf);
+ gst_adapter_push (self->adapter, tmp);
+ }
+
+ /* Release all captured buffers; we copied them above */
+ hr = IAudioCaptureClient_ReleaseBuffer (self->capture_client, got_frames);
+ from = NULL;
+ HR_FAILED_ELEMENT_ERROR_AND (hr, IAudioCaptureClient::ReleaseBuffer, self,
+ goto err);
+
+ want_frames = wanted / bpf;
+ avail_frames = gst_adapter_available (self->adapter) / bpf;
+
+ /* Only copy data that will fit into the allocated buffer of size @length */
+ n_frames = MIN (avail_frames, want_frames);
+ read_len = n_frames * bpf;
+
+ GST_DEBUG_OBJECT (self, "frames captured: %i (%i bytes), "
+ "can read: %i (%i bytes), will read: %i (%i bytes), "
+ "adapter has: %i (%i bytes)", got_frames, got_frames * bpf, want_frames,
+ wanted, n_frames, read_len, avail_frames, avail_frames * bpf);
+
+ memcpy (data, gst_adapter_map (self->adapter, read_len), read_len);
+ gst_adapter_flush (self->adapter, read_len);
+ wanted -= read_len;
+ }
+
+
+out:
+ return length;
+
+err:
+ length = -1;
+ goto out;
+}
+
+static guint
+gst_wasapi_src_delay (GstAudioSrc * asrc)
+{
+ GstWasapiSrc *self = GST_WASAPI_SRC (asrc);
+ guint delay = 0;
+ HRESULT hr;
+
+ hr = IAudioClient_GetCurrentPadding (self->client, &delay);
+ HR_FAILED_RET (hr, IAudioClock::GetCurrentPadding, 0);
+
+ return delay;
+}
+
+static void
+gst_wasapi_src_reset (GstAudioSrc * asrc)
+{
+ GstWasapiSrc *self = GST_WASAPI_SRC (asrc);
+ HRESULT hr;
+
+ if (!self->client)
+ return;
+
+ SetEvent (self->cancellable);
+
+ GST_OBJECT_LOCK (self);
+ hr = IAudioClient_Stop (self->client);
+ HR_FAILED_AND (hr, IAudioClock::Stop, goto err);
+
+ hr = IAudioClient_Reset (self->client);
+ HR_FAILED_AND (hr, IAudioClock::Reset, goto err);
+
+err:
+ self->client_needs_restart = TRUE;
+ GST_OBJECT_UNLOCK (self);
+}
+
+#if DEFAULT_PROVIDE_CLOCK
+static GstClockTime
+gst_wasapi_src_get_time (GstClock * clock, gpointer user_data)
+{
+ GstWasapiSrc *self = GST_WASAPI_SRC (user_data);
+ HRESULT hr;
+ guint64 devpos;
+ GstClockTime result;
+
+ if (G_UNLIKELY (self->client_clock == NULL))
+ return GST_CLOCK_TIME_NONE;
+
+ hr = IAudioClock_GetPosition (self->client_clock, &devpos, NULL);
+ HR_FAILED_RET (hr, IAudioClock::GetPosition, GST_CLOCK_TIME_NONE);
+
+ result = gst_util_uint64_scale_int (devpos, GST_SECOND,
+ self->client_clock_freq);
+
+ /*
+ GST_DEBUG_OBJECT (self, "devpos = %" G_GUINT64_FORMAT
+ " frequency = %" G_GUINT64_FORMAT
+ " result = %" G_GUINT64_FORMAT " ms",
+ devpos, self->client_clock_freq, GST_TIME_AS_MSECONDS (result));
+ */
+
+ return result;
+}
+#endif
diff --git a/sys/wasapi/gstwasapisrc.h b/sys/wasapi/gstwasapisrc.h
new file mode 100644
index 0000000000..8b78b7f173
--- /dev/null
+++ b/sys/wasapi/gstwasapisrc.h
@@ -0,0 +1,90 @@
+/*
+ * Copyright (C) 2008 Ole André Vadla Ravnås <ole.andre.ravnas@tandberg.com>
+ *
+ * This library is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU Library General Public
+ * License as published by the Free Software Foundation; either
+ * version 2 of the License, or (at your option) any later version.
+ *
+ * This library 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
+ * Library General Public License for more details.
+ *
+ * You should have received a copy of the GNU Library General Public
+ * License along with this library; if not, write to the
+ * Free Software Foundation, Inc., 51 Franklin St, Fifth Floor,
+ * Boston, MA 02110-1301, USA.
+ */
+
+#ifndef __GST_WASAPI_SRC_H__
+#define __GST_WASAPI_SRC_H__
+
+#include "gstwasapiutil.h"
+
+G_BEGIN_DECLS
+#define GST_TYPE_WASAPI_SRC \
+ (gst_wasapi_src_get_type ())
+#define GST_WASAPI_SRC(obj) \
+ (G_TYPE_CHECK_INSTANCE_CAST ((obj), GST_TYPE_WASAPI_SRC, GstWasapiSrc))
+#define GST_WASAPI_SRC_CLASS(klass) \
+ (G_TYPE_CHECK_CLASS_CAST ((klass), GST_TYPE_WASAPI_SRC, GstWasapiSrcClass))
+#define GST_IS_WASAPI_SRC(obj) \
+ (G_TYPE_CHECK_INSTANCE_TYPE ((obj), GST_TYPE_WASAPI_SRC))
+#define GST_IS_WASAPI_SRC_CLASS(klass) \
+ (G_TYPE_CHECK_CLASS_TYPE ((klass), GST_TYPE_WASAPI_SRC))
+typedef struct _GstWasapiSrc GstWasapiSrc;
+typedef struct _GstWasapiSrcClass GstWasapiSrcClass;
+
+struct _GstWasapiSrc
+{
+ GstAudioSrc parent;
+
+ GstMMDeviceEnumerator *enumerator;
+
+ IMMDevice *device;
+ IAudioClient *client;
+ IAudioClock *client_clock;
+ guint64 client_clock_freq;
+ IAudioCaptureClient *capture_client;
+ HANDLE event_handle;
+ HANDLE cancellable;
+ /* Smooth frames captured from WASAPI, which can be irregular sometimes */
+ GstAdapter *adapter;
+ /* Client was reset, so it needs to be started again */
+ gboolean client_needs_restart;
+
+ /* The mix format that wasapi prefers in shared mode */
+ WAVEFORMATEX *mix_format;
+ /* The probed caps that we can accept */
+ GstCaps *cached_caps;
+ /* The channel positions in the data read from the device
+ * we will pass this to GstAudioRingbuffer so it can
+ * translate it to the native GStreamer channel layout. */
+ GstAudioChannelPosition *positions;
+
+ /* Used for loopback use case in order to keep feeding silence into client */
+ IAudioClient *loopback_client;
+ IAudioRenderClient *loopback_render_client;
+ GThread *loopback_thread;
+ HANDLE loopback_event_handle;
+ HANDLE loopback_cancellable;
+
+ /* properties */
+ gint role;
+ gint sharemode;
+ gboolean loopback;
+ gboolean low_latency;
+ gboolean try_audioclient3;
+ wchar_t *device_strid;
+};
+
+struct _GstWasapiSrcClass
+{
+ GstAudioSrcClass parent_class;
+};
+
+GType gst_wasapi_src_get_type (void);
+
+G_END_DECLS
+#endif /* __GST_WASAPI_SRC_H__ */
diff --git a/sys/wasapi/gstwasapiutil.c b/sys/wasapi/gstwasapiutil.c
new file mode 100644
index 0000000000..b59f5f1936
--- /dev/null
+++ b/sys/wasapi/gstwasapiutil.c
@@ -0,0 +1,964 @@
+/*
+ * Copyright (C) 2008 Ole André Vadla Ravnås <ole.andre.ravnas@tandberg.com>
+ * Copyright (C) 2018 Centricular Ltd.
+ * Author: Nirbheek Chauhan <nirbheek@centricular.com>
+ *
+ * This library is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU Library General Public
+ * License as published by the Free Software Foundation; either
+ * version 2 of the License, or (at your option) any later version.
+ *
+ * This library 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
+ * Library General Public License for more details.
+ *
+ * You should have received a copy of the GNU Library General Public
+ * License along with this library; if not, write to the
+ * Free Software Foundation, Inc., 51 Franklin St, Fifth Floor,
+ * Boston, MA 02110-1301, USA.
+ */
+#ifdef HAVE_CONFIG_H
+# include <config.h>
+#endif
+
+/* Note: initguid.h can not be included in gstwasapiutil.h, otherwise a
+ * symbol redefinition error will be raised.
+ * initguid.h must be included in the C file before mmdeviceapi.h
+ * which is included in gstwasapiutil.h.
+ */
+#ifdef _MSC_VER
+#include <initguid.h>
+#endif
+#include "gstwasapiutil.h"
+#include "gstwasapidevice.h"
+
+GST_DEBUG_CATEGORY_EXTERN (gst_wasapi_debug);
+#define GST_CAT_DEFAULT gst_wasapi_debug
+
+/* This was only added to MinGW in ~2015 and our Cerbero toolchain is too old */
+#if defined(_MSC_VER)
+#include <functiondiscoverykeys_devpkey.h>
+#elif !defined(PKEY_Device_FriendlyName)
+#include <initguid.h>
+#include <propkey.h>
+DEFINE_PROPERTYKEY (PKEY_Device_FriendlyName, 0xa45c254e, 0xdf1c, 0x4efd, 0x80,
+ 0x20, 0x67, 0xd1, 0x46, 0xa8, 0x50, 0xe0, 14);
+DEFINE_PROPERTYKEY (PKEY_AudioEngine_DeviceFormat, 0xf19f064d, 0x82c, 0x4e27,
+ 0xbc, 0x73, 0x68, 0x82, 0xa1, 0xbb, 0x8e, 0x4c, 0);
+#endif
+
+/* __uuidof is only available in C++, so we hard-code the GUID values for all
+ * these. This is ok because these are ABI. */
+const CLSID CLSID_MMDeviceEnumerator = { 0xbcde0395, 0xe52f, 0x467c,
+ {0x8e, 0x3d, 0xc4, 0x57, 0x92, 0x91, 0x69, 0x2e}
+};
+
+const IID IID_IMMDeviceEnumerator = { 0xa95664d2, 0x9614, 0x4f35,
+ {0xa7, 0x46, 0xde, 0x8d, 0xb6, 0x36, 0x17, 0xe6}
+};
+
+const IID IID_IMMEndpoint = { 0x1be09788, 0x6894, 0x4089,
+ {0x85, 0x86, 0x9a, 0x2a, 0x6c, 0x26, 0x5a, 0xc5}
+};
+
+const IID IID_IAudioClient = { 0x1cb9ad4c, 0xdbfa, 0x4c32,
+ {0xb1, 0x78, 0xc2, 0xf5, 0x68, 0xa7, 0x03, 0xb2}
+};
+
+const IID IID_IAudioClient3 = { 0x7ed4ee07, 0x8e67, 0x4cd4,
+ {0x8c, 0x1a, 0x2b, 0x7a, 0x59, 0x87, 0xad, 0x42}
+};
+
+const IID IID_IAudioClock = { 0xcd63314f, 0x3fba, 0x4a1b,
+ {0x81, 0x2c, 0xef, 0x96, 0x35, 0x87, 0x28, 0xe7}
+};
+
+const IID IID_IAudioCaptureClient = { 0xc8adbd64, 0xe71e, 0x48a0,
+ {0xa4, 0xde, 0x18, 0x5c, 0x39, 0x5c, 0xd3, 0x17}
+};
+
+const IID IID_IAudioRenderClient = { 0xf294acfc, 0x3146, 0x4483,
+ {0xa7, 0xbf, 0xad, 0xdc, 0xa7, 0xc2, 0x60, 0xe2}
+};
+
+/* *INDENT-OFF* */
+static struct
+{
+ guint64 wasapi_pos;
+ GstAudioChannelPosition gst_pos;
+} wasapi_to_gst_pos[] = {
+ {SPEAKER_FRONT_LEFT, GST_AUDIO_CHANNEL_POSITION_FRONT_LEFT},
+ {SPEAKER_FRONT_RIGHT, GST_AUDIO_CHANNEL_POSITION_FRONT_RIGHT},
+ {SPEAKER_FRONT_CENTER, GST_AUDIO_CHANNEL_POSITION_FRONT_CENTER},
+ {SPEAKER_LOW_FREQUENCY, GST_AUDIO_CHANNEL_POSITION_LFE1},
+ {SPEAKER_BACK_LEFT, GST_AUDIO_CHANNEL_POSITION_REAR_LEFT},
+ {SPEAKER_BACK_RIGHT, GST_AUDIO_CHANNEL_POSITION_REAR_RIGHT},
+ {SPEAKER_FRONT_LEFT_OF_CENTER,
+ GST_AUDIO_CHANNEL_POSITION_FRONT_LEFT_OF_CENTER},
+ {SPEAKER_FRONT_RIGHT_OF_CENTER,
+ GST_AUDIO_CHANNEL_POSITION_FRONT_RIGHT_OF_CENTER},
+ {SPEAKER_BACK_CENTER, GST_AUDIO_CHANNEL_POSITION_REAR_CENTER},
+ /* Enum values diverge from this point onwards */
+ {SPEAKER_SIDE_LEFT, GST_AUDIO_CHANNEL_POSITION_SIDE_LEFT},
+ {SPEAKER_SIDE_RIGHT, GST_AUDIO_CHANNEL_POSITION_SIDE_RIGHT},
+ {SPEAKER_TOP_CENTER, GST_AUDIO_CHANNEL_POSITION_TOP_CENTER},
+ {SPEAKER_TOP_FRONT_LEFT, GST_AUDIO_CHANNEL_POSITION_TOP_FRONT_LEFT},
+ {SPEAKER_TOP_FRONT_CENTER, GST_AUDIO_CHANNEL_POSITION_TOP_FRONT_CENTER},
+ {SPEAKER_TOP_FRONT_RIGHT, GST_AUDIO_CHANNEL_POSITION_TOP_FRONT_RIGHT},
+ {SPEAKER_TOP_BACK_LEFT, GST_AUDIO_CHANNEL_POSITION_TOP_REAR_LEFT},
+ {SPEAKER_TOP_BACK_CENTER, GST_AUDIO_CHANNEL_POSITION_TOP_REAR_CENTER},
+ {SPEAKER_TOP_BACK_RIGHT, GST_AUDIO_CHANNEL_POSITION_TOP_REAR_RIGHT}
+};
+/* *INDENT-ON* */
+
+static int windows_major_version = 0;
+
+gboolean
+gst_wasapi_util_have_audioclient3 (void)
+{
+ if (windows_major_version > 0)
+ return windows_major_version == 10;
+
+ if (g_getenv ("GST_WASAPI_DISABLE_AUDIOCLIENT3") != NULL) {
+ windows_major_version = 6;
+ return FALSE;
+ }
+
+ /* https://msdn.microsoft.com/en-us/library/windows/desktop/ms724834(v=vs.85).aspx */
+ windows_major_version = 6;
+ if (g_win32_check_windows_version (10, 0, 0, G_WIN32_OS_ANY))
+ windows_major_version = 10;
+
+ return windows_major_version == 10;
+}
+
+GType
+gst_wasapi_device_role_get_type (void)
+{
+ static const GEnumValue values[] = {
+ {GST_WASAPI_DEVICE_ROLE_CONSOLE,
+ "Games, system notifications, voice commands", "console"},
+ {GST_WASAPI_DEVICE_ROLE_MULTIMEDIA, "Music, movies, recorded media",
+ "multimedia"},
+ {GST_WASAPI_DEVICE_ROLE_COMMS, "Voice communications", "comms"},
+ {0, NULL, NULL}
+ };
+ static GType id = 0;
+
+ if (g_once_init_enter ((gsize *) & id)) {
+ GType _id;
+
+ _id = g_enum_register_static ("GstWasapiDeviceRole", values);
+
+ g_once_init_leave ((gsize *) & id, _id);
+ }
+
+ return id;
+}
+
+gint
+gst_wasapi_device_role_to_erole (gint role)
+{
+ switch (role) {
+ case GST_WASAPI_DEVICE_ROLE_CONSOLE:
+ return eConsole;
+ case GST_WASAPI_DEVICE_ROLE_MULTIMEDIA:
+ return eMultimedia;
+ case GST_WASAPI_DEVICE_ROLE_COMMS:
+ return eCommunications;
+ default:
+ g_assert_not_reached ();
+ }
+
+ return -1;
+}
+
+gint
+gst_wasapi_erole_to_device_role (gint erole)
+{
+ switch (erole) {
+ case eConsole:
+ return GST_WASAPI_DEVICE_ROLE_CONSOLE;
+ case eMultimedia:
+ return GST_WASAPI_DEVICE_ROLE_MULTIMEDIA;
+ case eCommunications:
+ return GST_WASAPI_DEVICE_ROLE_COMMS;
+ default:
+ g_assert_not_reached ();
+ }
+
+ return -1;
+}
+
+static const gchar *
+hresult_to_string_fallback (HRESULT hr)
+{
+ const gchar *s = "unknown error";
+
+ switch (hr) {
+ case AUDCLNT_E_NOT_INITIALIZED:
+ s = "AUDCLNT_E_NOT_INITIALIZED";
+ break;
+ case AUDCLNT_E_ALREADY_INITIALIZED:
+ s = "AUDCLNT_E_ALREADY_INITIALIZED";
+ break;
+ case AUDCLNT_E_WRONG_ENDPOINT_TYPE:
+ s = "AUDCLNT_E_WRONG_ENDPOINT_TYPE";
+ break;
+ case AUDCLNT_E_DEVICE_INVALIDATED:
+ s = "AUDCLNT_E_DEVICE_INVALIDATED";
+ break;
+ case AUDCLNT_E_NOT_STOPPED:
+ s = "AUDCLNT_E_NOT_STOPPED";
+ break;
+ case AUDCLNT_E_BUFFER_TOO_LARGE:
+ s = "AUDCLNT_E_BUFFER_TOO_LARGE";
+ break;
+ case AUDCLNT_E_OUT_OF_ORDER:
+ s = "AUDCLNT_E_OUT_OF_ORDER";
+ break;
+ case AUDCLNT_E_UNSUPPORTED_FORMAT:
+ s = "AUDCLNT_E_UNSUPPORTED_FORMAT";
+ break;
+ case AUDCLNT_E_INVALID_DEVICE_PERIOD:
+ s = "AUDCLNT_E_INVALID_DEVICE_PERIOD";
+ break;
+ case AUDCLNT_E_INVALID_SIZE:
+ s = "AUDCLNT_E_INVALID_SIZE";
+ break;
+ case AUDCLNT_E_DEVICE_IN_USE:
+ s = "AUDCLNT_E_DEVICE_IN_USE";
+ break;
+ case AUDCLNT_E_BUFFER_OPERATION_PENDING:
+ s = "AUDCLNT_E_BUFFER_OPERATION_PENDING";
+ break;
+ case AUDCLNT_E_BUFFER_SIZE_ERROR:
+ s = "AUDCLNT_E_BUFFER_SIZE_ERROR";
+ break;
+ case AUDCLNT_E_BUFFER_SIZE_NOT_ALIGNED:
+ s = "AUDCLNT_E_BUFFER_SIZE_NOT_ALIGNED";
+ break;
+ case AUDCLNT_E_THREAD_NOT_REGISTERED:
+ s = "AUDCLNT_E_THREAD_NOT_REGISTERED";
+ break;
+ case AUDCLNT_E_EXCLUSIVE_MODE_NOT_ALLOWED:
+ s = "AUDCLNT_E_EXCLUSIVE_MODE_NOT_ALLOWED";
+ break;
+ case AUDCLNT_E_ENDPOINT_CREATE_FAILED:
+ s = "AUDCLNT_E_ENDPOINT_CREATE_FAILED";
+ break;
+ case AUDCLNT_E_SERVICE_NOT_RUNNING:
+ s = "AUDCLNT_E_SERVICE_NOT_RUNNING";
+ break;
+ case AUDCLNT_E_EVENTHANDLE_NOT_EXPECTED:
+ s = "AUDCLNT_E_EVENTHANDLE_NOT_EXPECTED";
+ break;
+ case AUDCLNT_E_EXCLUSIVE_MODE_ONLY:
+ s = "AUDCLNT_E_EXCLUSIVE_MODE_ONLY";
+ break;
+ case AUDCLNT_E_BUFDURATION_PERIOD_NOT_EQUAL:
+ s = "AUDCLNT_E_BUFDURATION_PERIOD_NOT_EQUAL";
+ break;
+ case AUDCLNT_E_EVENTHANDLE_NOT_SET:
+ s = "AUDCLNT_E_EVENTHANDLE_NOT_SET";
+ break;
+ case AUDCLNT_E_INCORRECT_BUFFER_SIZE:
+ s = "AUDCLNT_E_INCORRECT_BUFFER_SIZE";
+ break;
+ case AUDCLNT_E_CPUUSAGE_EXCEEDED:
+ s = "AUDCLNT_E_CPUUSAGE_EXCEEDED";
+ break;
+ case AUDCLNT_S_BUFFER_EMPTY:
+ s = "AUDCLNT_S_BUFFER_EMPTY";
+ break;
+ case AUDCLNT_S_THREAD_ALREADY_REGISTERED:
+ s = "AUDCLNT_S_THREAD_ALREADY_REGISTERED";
+ break;
+ case AUDCLNT_S_POSITION_STALLED:
+ s = "AUDCLNT_S_POSITION_STALLED";
+ break;
+ case E_POINTER:
+ s = "E_POINTER";
+ break;
+ case E_INVALIDARG:
+ s = "E_INVALIDARG";
+ break;
+ }
+
+ return s;
+}
+
+gchar *
+gst_wasapi_util_hresult_to_string (HRESULT hr)
+{
+ gchar *error_text = NULL;
+
+ error_text = g_win32_error_message ((gint) hr);
+ /* g_win32_error_message() seems to be returning empty string for
+ * AUDCLNT_* cases */
+ if (!error_text || strlen (error_text) == 0) {
+ g_free (error_text);
+ error_text = g_strdup (hresult_to_string_fallback (hr));
+ }
+
+ return error_text;
+}
+
+gboolean
+gst_wasapi_util_get_devices (GstMMDeviceEnumerator * self,
+ gboolean active, GList ** devices)
+{
+ gboolean res = FALSE;
+ static GstStaticCaps scaps = GST_STATIC_CAPS (GST_WASAPI_STATIC_CAPS);
+ DWORD dwStateMask = active ? DEVICE_STATE_ACTIVE : DEVICE_STATEMASK_ALL;
+ IMMDeviceCollection *device_collection = NULL;
+ IMMDeviceEnumerator *enum_handle = NULL;
+ const gchar *device_class, *element_name;
+ guint ii, count;
+ HRESULT hr;
+
+ *devices = NULL;
+
+ if (!self)
+ return FALSE;
+
+ enum_handle = gst_mm_device_enumerator_get_handle (self);
+ if (!enum_handle)
+ return FALSE;
+
+ hr = IMMDeviceEnumerator_EnumAudioEndpoints (enum_handle, eAll, dwStateMask,
+ &device_collection);
+ HR_FAILED_GOTO (hr, IMMDeviceEnumerator::EnumAudioEndpoints, err);
+
+ hr = IMMDeviceCollection_GetCount (device_collection, &count);
+ HR_FAILED_GOTO (hr, IMMDeviceCollection::GetCount, err);
+
+ /* Create a GList of GstDevices* to return */
+ for (ii = 0; ii < count; ii++) {
+ IMMDevice *item = NULL;
+ IMMEndpoint *endpoint = NULL;
+ IAudioClient *client = NULL;
+ IPropertyStore *prop_store = NULL;
+ WAVEFORMATEX *format = NULL;
+ gchar *description = NULL;
+ gchar *strid = NULL;
+ EDataFlow dataflow;
+ PROPVARIANT var;
+ wchar_t *wstrid;
+ GstDevice *device;
+ GstStructure *props;
+ GstCaps *caps;
+
+ hr = IMMDeviceCollection_Item (device_collection, ii, &item);
+ if (hr != S_OK)
+ continue;
+
+ hr = IMMDevice_QueryInterface (item, &IID_IMMEndpoint, (void **) &endpoint);
+ if (hr != S_OK)
+ goto next;
+
+ hr = IMMEndpoint_GetDataFlow (endpoint, &dataflow);
+ if (hr != S_OK)
+ goto next;
+
+ if (dataflow == eRender) {
+ device_class = "Audio/Sink";
+ element_name = "wasapisink";
+ } else {
+ device_class = "Audio/Source";
+ element_name = "wasapisrc";
+ }
+
+ PropVariantInit (&var);
+
+ hr = IMMDevice_GetId (item, &wstrid);
+ if (hr != S_OK)
+ goto next;
+ strid = g_utf16_to_utf8 (wstrid, -1, NULL, NULL, NULL);
+ CoTaskMemFree (wstrid);
+
+ hr = IMMDevice_OpenPropertyStore (item, STGM_READ, &prop_store);
+ if (hr != S_OK)
+ goto next;
+
+ /* NOTE: More properties can be added as needed from here:
+ * https://msdn.microsoft.com/en-us/library/windows/desktop/dd370794(v=vs.85).aspx */
+ hr = IPropertyStore_GetValue (prop_store, &PKEY_Device_FriendlyName, &var);
+ if (hr != S_OK)
+ goto next;
+ description = g_utf16_to_utf8 (var.pwszVal, -1, NULL, NULL, NULL);
+ PropVariantClear (&var);
+
+ /* Get the audio client so we can fetch the mix format for shared mode
+ * to get the device format for exclusive mode (or something close to that)
+ * fetch PKEY_AudioEngine_DeviceFormat from the property store. */
+ hr = IMMDevice_Activate (item, &IID_IAudioClient, CLSCTX_ALL, NULL,
+ (void **) &client);
+ if (hr != S_OK) {
+ gchar *msg = gst_wasapi_util_hresult_to_string (hr);
+ GST_ERROR_OBJECT (self, "IMMDevice::Activate (IID_IAudioClient) failed"
+ "on %s: %s", strid, msg);
+ g_free (msg);
+ goto next;
+ }
+
+ hr = IAudioClient_GetMixFormat (client, &format);
+ if (hr != S_OK || format == NULL) {
+ gchar *msg = gst_wasapi_util_hresult_to_string (hr);
+ GST_ERROR_OBJECT (self, "GetMixFormat failed on %s: %s", strid, msg);
+ g_free (msg);
+ goto next;
+ }
+
+ if (!gst_wasapi_util_parse_waveformatex ((WAVEFORMATEXTENSIBLE *) format,
+ gst_static_caps_get (&scaps), &caps, NULL))
+ goto next;
+
+ /* Set some useful properties */
+ props = gst_structure_new ("wasapi-proplist",
+ "device.api", G_TYPE_STRING, "wasapi",
+ "device.strid", G_TYPE_STRING, GST_STR_NULL (strid),
+ "wasapi.device.description", G_TYPE_STRING, description, NULL);
+
+ device = g_object_new (GST_TYPE_WASAPI_DEVICE, "device", strid,
+ "display-name", description, "caps", caps,
+ "device-class", device_class, "properties", props, NULL);
+ GST_WASAPI_DEVICE (device)->element = element_name;
+
+ gst_structure_free (props);
+ gst_caps_unref (caps);
+ *devices = g_list_prepend (*devices, device);
+
+ next:
+ PropVariantClear (&var);
+ if (prop_store)
+ IUnknown_Release (prop_store);
+ if (endpoint)
+ IUnknown_Release (endpoint);
+ if (client)
+ IUnknown_Release (client);
+ if (item)
+ IUnknown_Release (item);
+ if (description)
+ g_free (description);
+ if (strid)
+ g_free (strid);
+ }
+
+ res = TRUE;
+
+err:
+ if (device_collection)
+ IUnknown_Release (device_collection);
+ return res;
+}
+
+gboolean
+gst_wasapi_util_get_device_format (GstElement * self,
+ gint device_mode, IMMDevice * device, IAudioClient * client,
+ WAVEFORMATEX ** ret_format)
+{
+ WAVEFORMATEX *format;
+ HRESULT hr;
+
+ *ret_format = NULL;
+
+ hr = IAudioClient_GetMixFormat (client, &format);
+ HR_FAILED_RET (hr, IAudioClient::GetMixFormat, FALSE);
+
+ /* WASAPI always accepts the format returned by GetMixFormat in shared mode */
+ if (device_mode == AUDCLNT_SHAREMODE_SHARED)
+ goto out;
+
+ /* WASAPI may or may not support this format in exclusive mode */
+ hr = IAudioClient_IsFormatSupported (client, AUDCLNT_SHAREMODE_EXCLUSIVE,
+ format, NULL);
+ if (hr == S_OK)
+ goto out;
+
+ CoTaskMemFree (format);
+
+ /* Open the device property store, and get the format that WASAPI has been
+ * using for sending data to the device */
+ {
+ PROPVARIANT var;
+ IPropertyStore *prop_store = NULL;
+
+ hr = IMMDevice_OpenPropertyStore (device, STGM_READ, &prop_store);
+ HR_FAILED_RET (hr, IMMDevice::OpenPropertyStore, FALSE);
+
+ hr = IPropertyStore_GetValue (prop_store, &PKEY_AudioEngine_DeviceFormat,
+ &var);
+ if (hr != S_OK) {
+ gchar *msg = gst_wasapi_util_hresult_to_string (hr);
+ GST_ERROR_OBJECT (self, "GetValue failed: %s", msg);
+ g_free (msg);
+ IUnknown_Release (prop_store);
+ return FALSE;
+ }
+
+ format = malloc (var.blob.cbSize);
+ memcpy (format, var.blob.pBlobData, var.blob.cbSize);
+
+ PropVariantClear (&var);
+ IUnknown_Release (prop_store);
+ }
+
+ /* WASAPI may or may not support this format in exclusive mode */
+ hr = IAudioClient_IsFormatSupported (client, AUDCLNT_SHAREMODE_EXCLUSIVE,
+ format, NULL);
+ if (hr == S_OK)
+ goto out;
+
+ GST_ERROR_OBJECT (self, "AudioEngine DeviceFormat not supported");
+ free (format);
+ return FALSE;
+
+out:
+ *ret_format = format;
+ return TRUE;
+}
+
+gboolean
+gst_wasapi_util_get_device (GstMMDeviceEnumerator * self,
+ gint data_flow, gint role, const wchar_t * device_strid,
+ IMMDevice ** ret_device)
+{
+ gboolean res = FALSE;
+ HRESULT hr;
+ IMMDeviceEnumerator *enum_handle = NULL;
+ IMMDevice *device = NULL;
+
+ if (!self)
+ return FALSE;
+
+ enum_handle = gst_mm_device_enumerator_get_handle (self);
+ if (!enum_handle)
+ return FALSE;
+
+ if (!device_strid) {
+ hr = IMMDeviceEnumerator_GetDefaultAudioEndpoint (enum_handle, data_flow,
+ role, &device);
+ HR_FAILED_GOTO (hr, IMMDeviceEnumerator::GetDefaultAudioEndpoint, beach);
+ } else {
+ hr = IMMDeviceEnumerator_GetDevice (enum_handle, device_strid, &device);
+ if (hr != S_OK) {
+ gchar *msg = gst_wasapi_util_hresult_to_string (hr);
+ GST_ERROR_OBJECT (self, "IMMDeviceEnumerator::GetDevice (%S) failed"
+ ": %s", device_strid, msg);
+ g_free (msg);
+ goto beach;
+ }
+ }
+
+ IUnknown_AddRef (device);
+ *ret_device = device;
+
+ res = TRUE;
+
+beach:
+ if (device != NULL)
+ IUnknown_Release (device);
+
+ return res;
+}
+
+gboolean
+gst_wasapi_util_get_audio_client (GstElement * self,
+ IMMDevice * device, IAudioClient ** ret_client)
+{
+ IAudioClient *client = NULL;
+ gboolean res = FALSE;
+ HRESULT hr;
+
+ if (gst_wasapi_util_have_audioclient3 ())
+ hr = IMMDevice_Activate (device, &IID_IAudioClient3, CLSCTX_ALL, NULL,
+ (void **) &client);
+ else
+ hr = IMMDevice_Activate (device, &IID_IAudioClient, CLSCTX_ALL, NULL,
+ (void **) &client);
+ HR_FAILED_GOTO (hr, IMMDevice::Activate (IID_IAudioClient), beach);
+
+ IUnknown_AddRef (client);
+ *ret_client = client;
+
+ res = TRUE;
+
+beach:
+ if (client != NULL)
+ IUnknown_Release (client);
+
+ return res;
+}
+
+gboolean
+gst_wasapi_util_get_render_client (GstElement * self, IAudioClient * client,
+ IAudioRenderClient ** ret_render_client)
+{
+ gboolean res = FALSE;
+ HRESULT hr;
+ IAudioRenderClient *render_client = NULL;
+
+ hr = IAudioClient_GetService (client, &IID_IAudioRenderClient,
+ (void **) &render_client);
+ HR_FAILED_GOTO (hr, IAudioClient::GetService, beach);
+
+ *ret_render_client = render_client;
+ res = TRUE;
+
+beach:
+ return res;
+}
+
+gboolean
+gst_wasapi_util_get_capture_client (GstElement * self, IAudioClient * client,
+ IAudioCaptureClient ** ret_capture_client)
+{
+ gboolean res = FALSE;
+ HRESULT hr;
+ IAudioCaptureClient *capture_client = NULL;
+
+ hr = IAudioClient_GetService (client, &IID_IAudioCaptureClient,
+ (void **) &capture_client);
+ HR_FAILED_GOTO (hr, IAudioClient::GetService, beach);
+
+ *ret_capture_client = capture_client;
+ res = TRUE;
+
+beach:
+ return res;
+}
+
+gboolean
+gst_wasapi_util_get_clock (GstElement * self, IAudioClient * client,
+ IAudioClock ** ret_clock)
+{
+ gboolean res = FALSE;
+ HRESULT hr;
+ IAudioClock *clock = NULL;
+
+ hr = IAudioClient_GetService (client, &IID_IAudioClock, (void **) &clock);
+ HR_FAILED_GOTO (hr, IAudioClient::GetService, beach);
+
+ *ret_clock = clock;
+ res = TRUE;
+
+beach:
+ return res;
+}
+
+static const gchar *
+gst_waveformatex_to_audio_format (WAVEFORMATEXTENSIBLE * format)
+{
+ const gchar *fmt_str = NULL;
+ GstAudioFormat fmt = GST_AUDIO_FORMAT_UNKNOWN;
+
+ if (format->Format.wFormatTag == WAVE_FORMAT_PCM) {
+ fmt = gst_audio_format_build_integer (TRUE, G_LITTLE_ENDIAN,
+ format->Format.wBitsPerSample, format->Format.wBitsPerSample);
+ } else if (format->Format.wFormatTag == WAVE_FORMAT_IEEE_FLOAT) {
+ if (format->Format.wBitsPerSample == 32)
+ fmt = GST_AUDIO_FORMAT_F32LE;
+ else if (format->Format.wBitsPerSample == 64)
+ fmt = GST_AUDIO_FORMAT_F64LE;
+ } else if (format->Format.wFormatTag == WAVE_FORMAT_EXTENSIBLE) {
+ if (IsEqualGUID (&format->SubFormat, &KSDATAFORMAT_SUBTYPE_PCM)) {
+ fmt = gst_audio_format_build_integer (TRUE, G_LITTLE_ENDIAN,
+ format->Format.wBitsPerSample, format->Samples.wValidBitsPerSample);
+ } else if (IsEqualGUID (&format->SubFormat,
+ &KSDATAFORMAT_SUBTYPE_IEEE_FLOAT)) {
+ if (format->Format.wBitsPerSample == 32
+ && format->Samples.wValidBitsPerSample == 32)
+ fmt = GST_AUDIO_FORMAT_F32LE;
+ else if (format->Format.wBitsPerSample == 64 &&
+ format->Samples.wValidBitsPerSample == 64)
+ fmt = GST_AUDIO_FORMAT_F64LE;
+ }
+ }
+
+ if (fmt != GST_AUDIO_FORMAT_UNKNOWN)
+ fmt_str = gst_audio_format_to_string (fmt);
+
+ return fmt_str;
+}
+
+static void
+gst_wasapi_util_channel_position_all_none (guint channels,
+ GstAudioChannelPosition * position)
+{
+ int ii;
+ for (ii = 0; ii < channels; ii++)
+ position[ii] = GST_AUDIO_CHANNEL_POSITION_NONE;
+}
+
+/* Parse WAVEFORMATEX to get the gstreamer channel mask, and the wasapi channel
+ * positions so GstAudioRingbuffer can reorder the audio data to match the
+ * gstreamer channel order. */
+static guint64
+gst_wasapi_util_waveformatex_to_channel_mask (WAVEFORMATEXTENSIBLE * format,
+ GstAudioChannelPosition ** out_position)
+{
+ int ii, ch;
+ guint64 mask = 0;
+ WORD nChannels = format->Format.nChannels;
+ DWORD dwChannelMask = format->dwChannelMask;
+ GstAudioChannelPosition *pos = NULL;
+
+ pos = g_new (GstAudioChannelPosition, nChannels);
+ gst_wasapi_util_channel_position_all_none (nChannels, pos);
+
+ /* Too many channels, have to assume that they are all non-positional */
+ if (nChannels > G_N_ELEMENTS (wasapi_to_gst_pos)) {
+ GST_INFO ("Got too many (%i) channels, assuming non-positional", nChannels);
+ goto out;
+ }
+
+ /* Too many bits in the channel mask, and the bits don't match nChannels */
+ if (dwChannelMask >> (G_N_ELEMENTS (wasapi_to_gst_pos) + 1) != 0) {
+ GST_WARNING ("Too many bits in channel mask (%lu), assuming "
+ "non-positional", dwChannelMask);
+ goto out;
+ }
+
+ /* Map WASAPI's channel mask to Gstreamer's channel mask and positions.
+ * If the no. of bits in the mask > nChannels, we will ignore the extra. */
+ for (ii = 0, ch = 0; ii < G_N_ELEMENTS (wasapi_to_gst_pos) && ch < nChannels;
+ ii++) {
+ if (!(dwChannelMask & wasapi_to_gst_pos[ii].wasapi_pos))
+ /* no match, try next */
+ continue;
+ mask |= G_GUINT64_CONSTANT (1) << wasapi_to_gst_pos[ii].gst_pos;
+ pos[ch++] = wasapi_to_gst_pos[ii].gst_pos;
+ }
+
+ /* XXX: Warn if some channel masks couldn't be mapped? */
+
+ GST_DEBUG ("Converted WASAPI mask 0x%" G_GINT64_MODIFIER "x -> 0x%"
+ G_GINT64_MODIFIER "x", (guint64) dwChannelMask, (guint64) mask);
+
+out:
+ if (out_position)
+ *out_position = pos;
+ return mask;
+}
+
+gboolean
+gst_wasapi_util_parse_waveformatex (WAVEFORMATEXTENSIBLE * format,
+ GstCaps * template_caps, GstCaps ** out_caps,
+ GstAudioChannelPosition ** out_positions)
+{
+ int ii;
+ const gchar *afmt;
+ guint64 channel_mask;
+
+ *out_caps = NULL;
+
+ /* TODO: handle SPDIF and other encoded formats */
+
+ /* 1 or 2 channels <= 16 bits sample size OR
+ * 1 or 2 channels > 16 bits sample size or >2 channels */
+ if (format->Format.wFormatTag != WAVE_FORMAT_PCM &&
+ format->Format.wFormatTag != WAVE_FORMAT_IEEE_FLOAT &&
+ format->Format.wFormatTag != WAVE_FORMAT_EXTENSIBLE)
+ /* Unhandled format tag */
+ return FALSE;
+
+ /* WASAPI can only tell us one canonical mix format that it will accept. The
+ * alternative is calling IsFormatSupported on all combinations of formats.
+ * Instead, it's simpler and faster to require conversion inside gstreamer */
+ afmt = gst_waveformatex_to_audio_format (format);
+ if (afmt == NULL)
+ return FALSE;
+
+ *out_caps = gst_caps_copy (template_caps);
+
+ /* This will always return something that might be usable */
+ channel_mask =
+ gst_wasapi_util_waveformatex_to_channel_mask (format, out_positions);
+
+ for (ii = 0; ii < gst_caps_get_size (*out_caps); ii++) {
+ GstStructure *s = gst_caps_get_structure (*out_caps, ii);
+
+ gst_structure_set (s,
+ "format", G_TYPE_STRING, afmt,
+ "channels", G_TYPE_INT, format->Format.nChannels,
+ "rate", G_TYPE_INT, format->Format.nSamplesPerSec, NULL);
+
+ if (channel_mask) {
+ gst_structure_set (s,
+ "channel-mask", GST_TYPE_BITMASK, channel_mask, NULL);
+ }
+ }
+
+ return TRUE;
+}
+
+void
+gst_wasapi_util_get_best_buffer_sizes (GstAudioRingBufferSpec * spec,
+ gboolean exclusive, REFERENCE_TIME default_period,
+ REFERENCE_TIME min_period, REFERENCE_TIME * ret_period,
+ REFERENCE_TIME * ret_buffer_duration)
+{
+ REFERENCE_TIME use_period, use_buffer;
+
+ /* Figure out what integral device period to use as the base */
+ if (exclusive) {
+ /* Exclusive mode can run at multiples of either the minimum period or the
+ * default period; these are on the hardware ringbuffer */
+ if (spec->latency_time * 10 > default_period)
+ use_period = default_period;
+ else
+ use_period = min_period;
+ } else {
+ /* Shared mode always runs at the default period, so if we want a larger
+ * period (for lower CPU usage), we do it as a multiple of that */
+ use_period = default_period;
+ }
+
+ /* Ensure that the period (latency_time) used is an integral multiple of
+ * either the default period or the minimum period */
+ use_period = use_period * MAX ((spec->latency_time * 10) / use_period, 1);
+
+ if (exclusive) {
+ /* Buffer duration is the same as the period in exclusive mode. The
+ * hardware is always writing out one buffer (of size *ret_period), and
+ * we're writing to the other one. */
+ use_buffer = use_period;
+ } else {
+ /* Ask WASAPI to create a software ringbuffer of at least this size; it may
+ * be larger so the actual buffer time may be different, which is why after
+ * initialization we read the buffer duration actually in-use and set
+ * segsize/segtotal from that. */
+ use_buffer = spec->buffer_time * 10;
+ /* Has to be at least twice the period */
+ if (use_buffer < 2 * use_period)
+ use_buffer = 2 * use_period;
+ }
+
+ *ret_period = use_period;
+ *ret_buffer_duration = use_buffer;
+}
+
+gboolean
+gst_wasapi_util_initialize_audioclient (GstElement * self,
+ GstAudioRingBufferSpec * spec, IAudioClient * client,
+ WAVEFORMATEX * format, guint sharemode, gboolean low_latency,
+ gboolean loopback, guint * ret_devicep_frames)
+{
+ REFERENCE_TIME default_period, min_period;
+ REFERENCE_TIME device_period, device_buffer_duration;
+ guint rate, stream_flags;
+ guint32 n_frames;
+ HRESULT hr;
+
+ hr = IAudioClient_GetDevicePeriod (client, &default_period, &min_period);
+ HR_FAILED_RET (hr, IAudioClient::GetDevicePeriod, FALSE);
+
+ GST_INFO_OBJECT (self, "wasapi default period: %" G_GINT64_FORMAT
+ ", min period: %" G_GINT64_FORMAT, default_period, min_period);
+
+ rate = GST_AUDIO_INFO_RATE (&spec->info);
+
+ if (low_latency) {
+ if (sharemode == AUDCLNT_SHAREMODE_SHARED) {
+ device_period = default_period;
+ device_buffer_duration = 0;
+ } else {
+ device_period = min_period;
+ device_buffer_duration = min_period;
+ }
+ } else {
+ /* Clamp values to integral multiples of an appropriate period */
+ gst_wasapi_util_get_best_buffer_sizes (spec,
+ sharemode == AUDCLNT_SHAREMODE_EXCLUSIVE, default_period,
+ min_period, &device_period, &device_buffer_duration);
+ }
+
+ stream_flags = AUDCLNT_STREAMFLAGS_EVENTCALLBACK;
+ if (loopback)
+ stream_flags |= AUDCLNT_STREAMFLAGS_LOOPBACK;
+
+ hr = IAudioClient_Initialize (client, sharemode, stream_flags,
+ device_buffer_duration,
+ /* This must always be 0 in shared mode */
+ sharemode == AUDCLNT_SHAREMODE_SHARED ? 0 : device_period, format, NULL);
+
+ if (hr == AUDCLNT_E_BUFFER_SIZE_NOT_ALIGNED &&
+ sharemode == AUDCLNT_SHAREMODE_EXCLUSIVE) {
+ GST_WARNING_OBJECT (self, "initialize failed due to unaligned period %i",
+ (int) device_period);
+
+ /* Calculate a new aligned period. First get the aligned buffer size. */
+ hr = IAudioClient_GetBufferSize (client, &n_frames);
+ HR_FAILED_RET (hr, IAudioClient::GetBufferSize, FALSE);
+
+ device_period = (GST_SECOND / 100) * n_frames / rate;
+
+ GST_WARNING_OBJECT (self, "trying to re-initialize with period %i "
+ "(%i frames, %i rate)", (int) device_period, n_frames, rate);
+
+ hr = IAudioClient_Initialize (client, sharemode, stream_flags,
+ device_period, device_period, format, NULL);
+ }
+ HR_FAILED_RET (hr, IAudioClient::Initialize, FALSE);
+
+ if (sharemode == AUDCLNT_SHAREMODE_EXCLUSIVE) {
+ /* We use the device period for the segment size and that needs to match
+ * the buffer size exactly when we write into it */
+ hr = IAudioClient_GetBufferSize (client, &n_frames);
+ HR_FAILED_RET (hr, IAudioClient::GetBufferSize, FALSE);
+
+ *ret_devicep_frames = n_frames;
+ } else {
+ /* device_period can be a non-power-of-10 value so round while converting */
+ *ret_devicep_frames =
+ gst_util_uint64_scale_round (device_period, rate * 100, GST_SECOND);
+ }
+
+ return TRUE;
+}
+
+gboolean
+gst_wasapi_util_initialize_audioclient3 (GstElement * self,
+ GstAudioRingBufferSpec * spec, IAudioClient3 * client,
+ WAVEFORMATEX * format, gboolean low_latency, gboolean loopback,
+ guint * ret_devicep_frames)
+{
+ HRESULT hr;
+ gint stream_flags;
+ guint devicep_frames;
+ guint defaultp_frames, fundp_frames, minp_frames, maxp_frames;
+ WAVEFORMATEX *tmpf;
+
+ hr = IAudioClient3_GetSharedModeEnginePeriod (client, format,
+ &defaultp_frames, &fundp_frames, &minp_frames, &maxp_frames);
+ HR_FAILED_RET (hr, IAudioClient3::GetSharedModeEnginePeriod, FALSE);
+
+ GST_INFO_OBJECT (self, "Using IAudioClient3, default period %i frames, "
+ "fundamental period %i frames, minimum period %i frames, maximum period "
+ "%i frames", defaultp_frames, fundp_frames, minp_frames, maxp_frames);
+
+ if (low_latency)
+ devicep_frames = minp_frames;
+ else
+ /* Just pick the max period, because lower values can cause glitches
+ * https://bugzilla.gnome.org/show_bug.cgi?id=794497 */
+ devicep_frames = maxp_frames;
+
+ stream_flags = AUDCLNT_STREAMFLAGS_EVENTCALLBACK;
+ if (loopback)
+ stream_flags |= AUDCLNT_STREAMFLAGS_LOOPBACK;
+
+ hr = IAudioClient3_InitializeSharedAudioStream (client, stream_flags,
+ devicep_frames, format, NULL);
+ HR_FAILED_RET (hr, IAudioClient3::InitializeSharedAudioStream, FALSE);
+
+ hr = IAudioClient3_GetCurrentSharedModeEnginePeriod (client, &tmpf,
+ &devicep_frames);
+ CoTaskMemFree (tmpf);
+ HR_FAILED_RET (hr, IAudioClient3::GetCurrentSharedModeEnginePeriod, FALSE);
+
+ *ret_devicep_frames = devicep_frames;
+ return TRUE;
+}
diff --git a/sys/wasapi/gstwasapiutil.h b/sys/wasapi/gstwasapiutil.h
new file mode 100644
index 0000000000..70f241980c
--- /dev/null
+++ b/sys/wasapi/gstwasapiutil.h
@@ -0,0 +1,139 @@
+/*
+ * Copyright (C) 2008 Ole André Vadla Ravnås <ole.andre.ravnas@tandberg.com>
+ *
+ * This library is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU Library General Public
+ * License as published by the Free Software Foundation; either
+ * version 2 of the License, or (at your option) any later version.
+ *
+ * This library 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
+ * Library General Public License for more details.
+ *
+ * You should have received a copy of the GNU Library General Public
+ * License along with this library; if not, write to the
+ * Free Software Foundation, Inc., 51 Franklin St, Fifth Floor,
+ * Boston, MA 02110-1301, USA.
+ */
+
+#ifndef __GST_WASAPI_UTIL_H__
+#define __GST_WASAPI_UTIL_H__
+
+#include <gst/gst.h>
+#include <gst/audio/audio.h>
+#include <gst/audio/gstaudiosrc.h>
+#include <gst/audio/gstaudiosink.h>
+
+#include <mmdeviceapi.h>
+#include <audioclient.h>
+
+#include "gstaudioclient3.h"
+#include "gstmmdeviceenumerator.h"
+
+/* Static Caps shared between source, sink, and device provider */
+#define GST_WASAPI_STATIC_CAPS "audio/x-raw, " \
+ "format = (string) " GST_AUDIO_FORMATS_ALL ", " \
+ "layout = (string) interleaved, " \
+ "rate = " GST_AUDIO_RATE_RANGE ", " \
+ "channels = " GST_AUDIO_CHANNELS_RANGE
+
+/* Standard error path */
+#define HR_FAILED_AND(hr,func,and) \
+ G_STMT_START { \
+ if (FAILED (hr)) { \
+ gchar *msg = gst_wasapi_util_hresult_to_string (hr); \
+ GST_ERROR_OBJECT (self, #func " failed (%x): %s", (guint) hr, msg); \
+ g_free (msg); \
+ and; \
+ } \
+ } G_STMT_END
+
+#define HR_FAILED_RET(hr,func,ret) HR_FAILED_AND(hr,func,return ret)
+
+#define HR_FAILED_GOTO(hr,func,where) HR_FAILED_AND(hr,func,res = FALSE; goto where)
+
+#define HR_FAILED_ELEMENT_ERROR_AND(hr,func,el,and) \
+ G_STMT_START { \
+ if (FAILED (hr)) { \
+ gchar *msg = gst_wasapi_util_hresult_to_string (hr); \
+ GST_ERROR_OBJECT (el, #func " failed (%x): %s", (guint) hr, msg); \
+ if (GST_IS_AUDIO_SRC (el)) \
+ GST_ELEMENT_ERROR(el, RESOURCE, READ, \
+ (#func " failed (%x): %s", (guint) hr, msg), (NULL)); \
+ else \
+ GST_ELEMENT_ERROR(el, RESOURCE, WRITE, \
+ (#func " failed (%x): %s", (guint) hr, msg), (NULL)); \
+ g_free (msg); \
+ and; \
+ } \
+ } G_STMT_END
+
+#define HR_FAILED_ELEMENT_ERROR_RET(hr,func,el,ret) \
+ HR_FAILED_ELEMENT_ERROR_AND(hr,func,el,return ret)
+
+
+/* Device role enum property */
+typedef enum
+{
+ GST_WASAPI_DEVICE_ROLE_CONSOLE,
+ GST_WASAPI_DEVICE_ROLE_MULTIMEDIA,
+ GST_WASAPI_DEVICE_ROLE_COMMS
+} GstWasapiDeviceRole;
+#define GST_WASAPI_DEVICE_TYPE_ROLE (gst_wasapi_device_role_get_type())
+GType gst_wasapi_device_role_get_type (void);
+
+/* Utilities */
+
+gboolean gst_wasapi_util_have_audioclient3 (void);
+
+gint gst_wasapi_device_role_to_erole (gint role);
+
+gint gst_wasapi_erole_to_device_role (gint erole);
+
+gchar *gst_wasapi_util_hresult_to_string (HRESULT hr);
+
+gboolean gst_wasapi_util_get_devices (GstMMDeviceEnumerator * enumerator,
+ gboolean active,
+ GList ** devices);
+
+gboolean gst_wasapi_util_get_device (GstMMDeviceEnumerator * enumerator,
+ gint data_flow, gint role, const wchar_t * device_strid,
+ IMMDevice ** ret_device);
+
+gboolean gst_wasapi_util_get_audio_client (GstElement * self,
+ IMMDevice * device, IAudioClient ** ret_client);
+
+gboolean gst_wasapi_util_get_device_format (GstElement * element,
+ gint device_mode, IMMDevice * device, IAudioClient * client,
+ WAVEFORMATEX ** ret_format);
+
+gboolean gst_wasapi_util_get_render_client (GstElement * element,
+ IAudioClient * client, IAudioRenderClient ** ret_render_client);
+
+gboolean gst_wasapi_util_get_capture_client (GstElement * element,
+ IAudioClient * client, IAudioCaptureClient ** ret_capture_client);
+
+gboolean gst_wasapi_util_get_clock (GstElement * element,
+ IAudioClient * client, IAudioClock ** ret_clock);
+
+gboolean gst_wasapi_util_parse_waveformatex (WAVEFORMATEXTENSIBLE * format,
+ GstCaps * template_caps, GstCaps ** out_caps,
+ GstAudioChannelPosition ** out_positions);
+
+void gst_wasapi_util_get_best_buffer_sizes (GstAudioRingBufferSpec * spec,
+ gboolean exclusive, REFERENCE_TIME default_period,
+ REFERENCE_TIME min_period, REFERENCE_TIME * ret_period,
+ REFERENCE_TIME * ret_buffer_duration);
+
+gboolean gst_wasapi_util_initialize_audioclient (GstElement * element,
+ GstAudioRingBufferSpec * spec, IAudioClient * client,
+ WAVEFORMATEX * format, guint sharemode, gboolean low_latency,
+ gboolean loopback, guint * ret_devicep_frames);
+
+gboolean gst_wasapi_util_initialize_audioclient3 (GstElement * element,
+ GstAudioRingBufferSpec * spec, IAudioClient3 * client,
+ WAVEFORMATEX * format, gboolean low_latency, gboolean loopback,
+ guint * ret_devicep_frames);
+
+#endif /* __GST_WASAPI_UTIL_H__ */
diff --git a/sys/wasapi/meson.build b/sys/wasapi/meson.build
new file mode 100644
index 0000000000..3fdd73a2fb
--- /dev/null
+++ b/sys/wasapi/meson.build
@@ -0,0 +1,41 @@
+wasapi_sources = [
+ 'gstmmdeviceenumerator.cpp',
+ 'gstwasapi.c',
+ 'gstwasapisrc.c',
+ 'gstwasapisink.c',
+ 'gstwasapiutil.c',
+ 'gstwasapidevice.c',
+]
+
+if host_system != 'windows'
+ if get_option('wasapi').disabled()
+ subdir_done()
+ elif get_option('wasapi').enabled()
+ error('Cannot build wasapi plugin when not building for Windows')
+ endif
+endif
+
+ole32_dep = cc.find_library('ole32', required : get_option('wasapi'))
+ksuser_dep = cc.find_library('ksuser', required : get_option('wasapi'))
+have_audioclient_h = cc.has_header('audioclient.h')
+if not have_audioclient_h and get_option('wasapi').enabled()
+ error('wasapi plugin enabled but audioclient.h not found')
+endif
+
+if ole32_dep.found() and ksuser_dep.found() and have_audioclient_h
+ wasapi_args = ['-DCOBJMACROS']
+ if cc.has_header_symbol('audioclient.h', 'AUDCLNT_STREAMOPTIONS_NONE')
+ wasapi_args += ['-DHAVE_AUDCLNT_STREAMOPTIONS']
+ endif
+
+ gstwasapi = library('gstwasapi',
+ wasapi_sources,
+ c_args : gst_plugins_bad_args + wasapi_args,
+ cpp_args: gst_plugins_bad_args,
+ include_directories : [configinc],
+ dependencies : [gstaudio_dep, ole32_dep, ksuser_dep],
+ install : true,
+ install_dir : plugins_install_dir)
+ pkgconfig.generate(gstwasapi, install_dir : plugins_pkgconfig_install_dir)
+ plugins += [gstwasapi]
+endif