summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorSeungha Yang <seungha@centricular.com>2021-08-24 03:54:27 +0900
committerSeungha Yang <seungha@centricular.com>2021-09-21 17:34:35 +0000
commit589ff8ca6dcb16057082fbbc0878413728fd1a9e (patch)
tree261f247b387c165cb10cc2fb022c4135228a4cbc
parent3c937c8023bc6158f26baebfcce4063e1fbc50f5 (diff)
downloadgstreamer-plugins-bad-589ff8ca6dcb16057082fbbc0878413728fd1a9e.tar.gz
wasapideviceprovider: Add support for dynamic device add/remove
Adding IMMDeviceEnumerator::RegisterEndpointNotificationCallback in order to support device monitoring. On OnDeviceAdded(), OnDeviceRemoved(), and OnDefaultDeviceChanged() callback, wasapi device provider implementation will enumerate devices again and will notify newly added and removed device via GstDeviceProvider API. As a bonus point, this IMMDeviceEnumerator abstraction object will spawn a dedicated internal COM thread, so various COM thread related issues of WASAPI plugin can be resolved by this commit. Fixes: https://gitlab.freedesktop.org/gstreamer/gst-plugins-bad/-/issues/1649 Fixes: https://gitlab.freedesktop.org/gstreamer/gst-plugins-bad/-/issues/1110 Part-of: <https://gitlab.freedesktop.org/gstreamer/gst-plugins-bad/-/merge_requests/2484>
-rw-r--r--sys/wasapi/gstmmdeviceenumerator.cpp472
-rw-r--r--sys/wasapi/gstmmdeviceenumerator.h69
-rw-r--r--sys/wasapi/gstwasapidevice.c205
-rw-r--r--sys/wasapi/gstwasapidevice.h2
-rw-r--r--sys/wasapi/gstwasapisink.c12
-rw-r--r--sys/wasapi/gstwasapisink.h2
-rw-r--r--sys/wasapi/gstwasapisrc.c14
-rw-r--r--sys/wasapi/gstwasapisrc.h2
-rw-r--r--sys/wasapi/gstwasapiutil.c49
-rw-r--r--sys/wasapi/gstwasapiutil.h8
-rw-r--r--sys/wasapi/meson.build2
-rw-r--r--tests/check/elements/wasapi.c205
12 files changed, 967 insertions, 75 deletions
diff --git a/sys/wasapi/gstmmdeviceenumerator.cpp b/sys/wasapi/gstmmdeviceenumerator.cpp
new file mode 100644
index 000000000..1826b7fe1
--- /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 000000000..d3f7b07a9
--- /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/gstwasapidevice.c b/sys/wasapi/gstwasapidevice.c
index 4e6379c3e..c13fc6a8a 100644
--- a/sys/wasapi/gstwasapidevice.c
+++ b/sys/wasapi/gstwasapidevice.c
@@ -1,5 +1,6 @@
/* 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
@@ -23,11 +24,27 @@
#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)
@@ -38,6 +55,8 @@ gst_wasapi_device_provider_class_init (GstWasapiDeviceProviderClass * 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",
@@ -46,15 +65,68 @@ gst_wasapi_device_provider_class_init (GstWasapiDeviceProviderClass * klass)
}
static void
-gst_wasapi_device_provider_init (GstWasapiDeviceProvider * provider)
+gst_wasapi_device_provider_init (GstWasapiDeviceProvider * self)
{
- CoInitializeEx (NULL, COINIT_MULTITHREADED);
+ 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)
{
- CoUninitialize ();
+ 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 *
@@ -63,12 +135,137 @@ gst_wasapi_device_provider_probe (GstDeviceProvider * provider)
GstWasapiDeviceProvider *self = GST_WASAPI_DEVICE_PROVIDER (provider);
GList *devices = NULL;
- if (!gst_wasapi_util_get_devices (GST_OBJECT (self), TRUE, &devices))
+ 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
diff --git a/sys/wasapi/gstwasapidevice.h b/sys/wasapi/gstwasapidevice.h
index 60d454b81..55eddd06b 100644
--- a/sys/wasapi/gstwasapidevice.h
+++ b/sys/wasapi/gstwasapidevice.h
@@ -38,6 +38,8 @@ typedef struct _GstWasapiDeviceProviderClass GstWasapiDeviceProviderClass;
struct _GstWasapiDeviceProvider
{
GstDeviceProvider parent;
+
+ GstMMDeviceEnumerator *enumerator;
};
struct _GstWasapiDeviceProviderClass
diff --git a/sys/wasapi/gstwasapisink.c b/sys/wasapi/gstwasapisink.c
index 711f6f68a..b10d39110 100644
--- a/sys/wasapi/gstwasapisink.c
+++ b/sys/wasapi/gstwasapisink.c
@@ -180,7 +180,7 @@ gst_wasapi_sink_init (GstWasapiSink * self)
self->cancellable = CreateEvent (NULL, TRUE, FALSE, NULL);
self->client_needs_restart = FALSE;
- CoInitializeEx (NULL, COINIT_MULTITHREADED);
+ self->enumerator = gst_mm_device_enumerator_new ();
}
static void
@@ -208,6 +208,8 @@ gst_wasapi_sink_dispose (GObject * object)
self->render_client = NULL;
}
+ gst_clear_object (&self->enumerator);
+
G_OBJECT_CLASS (gst_wasapi_sink_parent_class)->dispose (object);
}
@@ -219,8 +221,6 @@ gst_wasapi_sink_finalize (GObject * object)
CoTaskMemFree (self->mix_format);
self->mix_format = NULL;
- CoUninitialize ();
-
if (self->cached_caps != NULL) {
gst_caps_unref (self->cached_caps);
self->cached_caps = NULL;
@@ -412,7 +412,7 @@ gst_wasapi_sink_open (GstAudioSink * asink)
* 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 (GST_ELEMENT (self), eRender,
+ 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)) {
@@ -485,8 +485,6 @@ gst_wasapi_sink_prepare (GstAudioSink * asink, GstAudioRingBufferSpec * spec)
guint bpf, rate, devicep_frames;
HRESULT hr;
- CoInitializeEx (NULL, COINIT_MULTITHREADED);
-
if (!self->client) {
GST_DEBUG_OBJECT (self, "no IAudioClient, creating a new one");
if (!gst_wasapi_util_get_audio_client (GST_ELEMENT (self),
@@ -608,8 +606,6 @@ gst_wasapi_sink_unprepare (GstAudioSink * asink)
self->render_client = NULL;
}
- CoUninitialize ();
-
return TRUE;
}
diff --git a/sys/wasapi/gstwasapisink.h b/sys/wasapi/gstwasapisink.h
index 76ec38fb1..4aac69efc 100644
--- a/sys/wasapi/gstwasapisink.h
+++ b/sys/wasapi/gstwasapisink.h
@@ -40,6 +40,8 @@ struct _GstWasapiSink
{
GstAudioSink parent;
+ GstMMDeviceEnumerator *enumerator;
+
IMMDevice *device;
IAudioClient *client;
IAudioRenderClient *render_client;
diff --git a/sys/wasapi/gstwasapisrc.c b/sys/wasapi/gstwasapisrc.c
index 016f862a5..8bdff3be0 100644
--- a/sys/wasapi/gstwasapisrc.c
+++ b/sys/wasapi/gstwasapisrc.c
@@ -199,7 +199,7 @@ gst_wasapi_src_init (GstWasapiSrc * self)
self->loopback_event_handle = CreateEvent (NULL, FALSE, FALSE, NULL);
self->loopback_cancellable = CreateEvent (NULL, TRUE, FALSE, NULL);
- CoInitializeEx (NULL, COINIT_MULTITHREADED);
+ self->enumerator = gst_mm_device_enumerator_new ();
}
static void
@@ -247,6 +247,8 @@ gst_wasapi_src_dispose (GObject * object)
self->loopback_cancellable = NULL;
}
+ gst_clear_object (&self->enumerator);
+
G_OBJECT_CLASS (parent_class)->dispose (object);
}
@@ -258,8 +260,6 @@ gst_wasapi_src_finalize (GObject * object)
CoTaskMemFree (self->mix_format);
self->mix_format = NULL;
- CoUninitialize ();
-
g_clear_pointer (&self->cached_caps, gst_caps_unref);
g_clear_pointer (&self->positions, g_free);
g_clear_pointer (&self->device_strid, g_free);
@@ -426,7 +426,7 @@ gst_wasapi_src_open (GstAudioSrc * asrc)
* 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 (GST_ELEMENT (self),
+ 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),
@@ -447,7 +447,7 @@ gst_wasapi_src_open (GstAudioSrc * asrc)
* 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 (GST_ELEMENT (self),
+ 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)) {
@@ -604,8 +604,6 @@ gst_wasapi_src_prepare (GstAudioSrc * asrc, GstAudioRingBufferSpec * spec)
guint bpf, rate, devicep_frames, buffer_frames;
HRESULT hr;
- CoInitializeEx (NULL, COINIT_MULTITHREADED);
-
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,
@@ -744,8 +742,6 @@ gst_wasapi_src_unprepare (GstAudioSrc * asrc)
self->client_clock_freq = 0;
- CoUninitialize ();
-
return TRUE;
}
diff --git a/sys/wasapi/gstwasapisrc.h b/sys/wasapi/gstwasapisrc.h
index 53dacd91f..8b78b7f17 100644
--- a/sys/wasapi/gstwasapisrc.h
+++ b/sys/wasapi/gstwasapisrc.h
@@ -40,6 +40,8 @@ struct _GstWasapiSrc
{
GstAudioSrc parent;
+ GstMMDeviceEnumerator *enumerator;
+
IMMDevice *device;
IAudioClient *client;
IAudioClock *client_clock;
diff --git a/sys/wasapi/gstwasapiutil.c b/sys/wasapi/gstwasapiutil.c
index 73442f7f8..b59f5f193 100644
--- a/sys/wasapi/gstwasapiutil.c
+++ b/sys/wasapi/gstwasapiutil.c
@@ -305,39 +305,29 @@ gst_wasapi_util_hresult_to_string (HRESULT hr)
return error_text;
}
-static IMMDeviceEnumerator *
-gst_wasapi_util_get_device_enumerator (GstObject * self)
-{
- HRESULT hr;
- IMMDeviceEnumerator *enumerator = NULL;
-
- hr = CoCreateInstance (&CLSID_MMDeviceEnumerator, NULL, CLSCTX_ALL,
- &IID_IMMDeviceEnumerator, (void **) &enumerator);
- HR_FAILED_RET (hr, CoCreateInstance (MMDeviceEnumerator), NULL);
-
- return enumerator;
-}
-
gboolean
-gst_wasapi_util_get_devices (GstObject * self, gboolean active,
- GList ** devices)
+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 *enumerator = NULL;
+ IMMDeviceEnumerator *enum_handle = NULL;
const gchar *device_class, *element_name;
guint ii, count;
HRESULT hr;
*devices = NULL;
- enumerator = gst_wasapi_util_get_device_enumerator (self);
- if (!enumerator)
+ if (!self)
return FALSE;
- hr = IMMDeviceEnumerator_EnumAudioEndpoints (enumerator, eAll, dwStateMask,
+ 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);
@@ -459,8 +449,6 @@ gst_wasapi_util_get_devices (GstObject * self, gboolean active,
res = TRUE;
err:
- if (enumerator)
- IUnknown_Release (enumerator);
if (device_collection)
IUnknown_Release (device_collection);
return res;
@@ -533,24 +521,28 @@ out:
}
gboolean
-gst_wasapi_util_get_device (GstElement * self,
+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 *enumerator = NULL;
+ IMMDeviceEnumerator *enum_handle = NULL;
IMMDevice *device = NULL;
- if (!(enumerator = gst_wasapi_util_get_device_enumerator (GST_OBJECT (self))))
- goto beach;
+ if (!self)
+ return FALSE;
+
+ enum_handle = gst_mm_device_enumerator_get_handle (self);
+ if (!enum_handle)
+ return FALSE;
if (!device_strid) {
- hr = IMMDeviceEnumerator_GetDefaultAudioEndpoint (enumerator, data_flow,
+ hr = IMMDeviceEnumerator_GetDefaultAudioEndpoint (enum_handle, data_flow,
role, &device);
HR_FAILED_GOTO (hr, IMMDeviceEnumerator::GetDefaultAudioEndpoint, beach);
} else {
- hr = IMMDeviceEnumerator_GetDevice (enumerator, device_strid, &device);
+ 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"
@@ -569,9 +561,6 @@ beach:
if (device != NULL)
IUnknown_Release (device);
- if (enumerator != NULL)
- IUnknown_Release (enumerator);
-
return res;
}
diff --git a/sys/wasapi/gstwasapiutil.h b/sys/wasapi/gstwasapiutil.h
index 8cd5d3240..70f241980 100644
--- a/sys/wasapi/gstwasapiutil.h
+++ b/sys/wasapi/gstwasapiutil.h
@@ -29,6 +29,7 @@
#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, " \
@@ -92,10 +93,11 @@ gint gst_wasapi_erole_to_device_role (gint erole);
gchar *gst_wasapi_util_hresult_to_string (HRESULT hr);
-gboolean gst_wasapi_util_get_devices (GstObject * element, gboolean active,
- GList ** devices);
+gboolean gst_wasapi_util_get_devices (GstMMDeviceEnumerator * enumerator,
+ gboolean active,
+ GList ** devices);
-gboolean gst_wasapi_util_get_device (GstElement * self,
+gboolean gst_wasapi_util_get_device (GstMMDeviceEnumerator * enumerator,
gint data_flow, gint role, const wchar_t * device_strid,
IMMDevice ** ret_device);
diff --git a/sys/wasapi/meson.build b/sys/wasapi/meson.build
index 9adc3d690..3fdd73a2f 100644
--- a/sys/wasapi/meson.build
+++ b/sys/wasapi/meson.build
@@ -1,4 +1,5 @@
wasapi_sources = [
+ 'gstmmdeviceenumerator.cpp',
'gstwasapi.c',
'gstwasapisrc.c',
'gstwasapisink.c',
@@ -30,6 +31,7 @@ if ole32_dep.found() and ksuser_dep.found() and have_audioclient_h
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,
diff --git a/tests/check/elements/wasapi.c b/tests/check/elements/wasapi.c
index 9132b19ad..881bb682c 100644
--- a/tests/check/elements/wasapi.c
+++ b/tests/check/elements/wasapi.c
@@ -28,14 +28,143 @@
typedef struct
{
GMainLoop *loop;
+ GstElement *pipeline;
+ guint n_buffers;
+ guint restart_count;
+ GstState reuse_state;
+} SrcReuseTestData;
+
+static gboolean
+src_reuse_bus_handler (GstBus * bus, GstMessage * message,
+ SrcReuseTestData * data)
+{
+ if (GST_MESSAGE_TYPE (message) == GST_MESSAGE_ERROR) {
+ GST_ERROR ("Got error message from pipeline");
+ g_main_loop_quit (data->loop);
+ }
+
+ return TRUE;
+}
+
+static void
+start_pipeline (SrcReuseTestData * data)
+{
+ GstStateChangeReturn ret;
+
+ GST_INFO ("Start pipeline");
+ ret = gst_element_set_state (data->pipeline, GST_STATE_PLAYING);
+ fail_unless (ret != GST_STATE_CHANGE_FAILURE);
+}
+
+static gboolean
+restart_pipeline (SrcReuseTestData * data)
+{
+ data->restart_count++;
+ start_pipeline (data);
+
+ return G_SOURCE_REMOVE;
+}
+
+static gboolean
+handle_handoff (SrcReuseTestData * data)
+{
+ data->n_buffers++;
+
+ /* Restart every 10 packets */
+ if (data->n_buffers > 10) {
+ GstStateChangeReturn ret;
+ data->n_buffers = 0;
+
+ ret = gst_element_set_state (data->pipeline, data->reuse_state);
+ fail_unless (ret != GST_STATE_CHANGE_FAILURE);
+
+ if (data->restart_count < 2) {
+ GST_INFO ("Restart pipeline, current restart count %d",
+ data->restart_count);
+ g_timeout_add_seconds (1, (GSourceFunc) restart_pipeline, data);
+ } else {
+ GST_INFO ("Finish test");
+ g_main_loop_quit (data->loop);
+ }
+ }
+
+ return G_SOURCE_REMOVE;
+}
+
+static void
+on_sink_handoff (GstElement * element, GstBuffer * buffer, GstPad * pad,
+ SrcReuseTestData * data)
+{
+ g_idle_add ((GSourceFunc) handle_handoff, data);
+}
+
+static void
+wasapisrc_reuse (GstState reuse_state)
+{
+ GstBus *bus;
+ GstElement *sink;
+ SrcReuseTestData data;
+
+ memset (&data, 0, sizeof (SrcReuseTestData));
+
+ data.loop = g_main_loop_new (NULL, FALSE);
+
+ data.pipeline = gst_parse_launch ("wasapisrc provide-clock=false ! queue ! "
+ "fakesink name=sink async=false", NULL);
+ fail_unless (data.pipeline != NULL);
+ data.reuse_state = reuse_state;
+
+ sink = gst_bin_get_by_name (GST_BIN (data.pipeline), "sink");
+ fail_unless (sink);
+
+ g_object_set (G_OBJECT (sink), "signal-handoffs", TRUE, NULL);
+ g_signal_connect (sink, "handoff", G_CALLBACK (on_sink_handoff), &data);
+
+ bus = gst_element_get_bus (GST_ELEMENT (data.pipeline));
+ fail_unless (bus != NULL);
+
+ gst_bus_add_watch (bus, (GstBusFunc) src_reuse_bus_handler, &data);
+ start_pipeline (&data);
+ g_main_loop_run (data.loop);
+
+ fail_unless (data.restart_count == 2);
+
+ gst_element_set_start_time (data.pipeline, GST_STATE_NULL);
+ gst_bus_remove_watch (bus);
+ gst_object_unref (bus);
+
+ gst_object_unref (data.pipeline);
+ g_main_loop_unref (data.loop);
+}
+
+/* https://gitlab.freedesktop.org/gstreamer/gst-plugins-bad/-/issues/1110 */
+GST_START_TEST (test_wasapisrc_reuse_null)
+{
+ wasapisrc_reuse (GST_STATE_NULL);
+}
+
+GST_END_TEST;
+
+GST_START_TEST (test_wasapisrc_reuse_ready)
+{
+ wasapisrc_reuse (GST_STATE_READY);
+}
+
+GST_END_TEST;
+
+typedef struct
+{
+ GMainLoop *loop;
GstElement *pipe;
guint rem_st_changes;
+ GstState reuse_state;
} SinkPlayReadyTData;
static gboolean
-bus_watch_cb (GstBus * bus, GstMessage * message, gpointer user_data)
+sink_reuse_bus_watch_cb (GstBus * bus, GstMessage * message, gpointer user_data)
{
fail_unless (message->type != GST_MESSAGE_ERROR);
+
return G_SOURCE_CONTINUE;
}
@@ -44,7 +173,7 @@ state_timer_cb (gpointer user_data)
{
SinkPlayReadyTData *tdata = user_data;
GstState nxt_st = tdata->rem_st_changes % 2 == 1 ?
- GST_STATE_READY : GST_STATE_PLAYING;
+ tdata->reuse_state : GST_STATE_PLAYING;
ASSERT_SET_STATE (tdata->pipe, nxt_st, GST_STATE_CHANGE_SUCCESS);
tdata->rem_st_changes--;
@@ -58,7 +187,8 @@ state_timer_cb (gpointer user_data)
/* Test that the wasapisink can survive the state change
* from PLAYING to READY and then back to PLAYING */
-GST_START_TEST (test_sink_play_ready)
+static void
+wasapisink_reuse (GstState reuse_state)
{
SinkPlayReadyTData tdata;
GstBus *bus;
@@ -67,7 +197,9 @@ GST_START_TEST (test_sink_play_ready)
fail_unless (tdata.pipe != NULL);
bus = gst_element_get_bus (tdata.pipe);
fail_unless (bus != NULL);
- gst_bus_add_watch (bus, bus_watch_cb, NULL);
+ gst_bus_add_watch (bus, sink_reuse_bus_watch_cb, NULL);
+
+ tdata.reuse_state = reuse_state;
ASSERT_SET_STATE (tdata.pipe, GST_STATE_PLAYING, GST_STATE_CHANGE_SUCCESS);
tdata.rem_st_changes = 3; /* -> READY -> PLAYING -> QUIT */
@@ -82,47 +214,78 @@ GST_START_TEST (test_sink_play_ready)
gst_object_unref (tdata.pipe);
}
+GST_START_TEST (test_wasapisink_reuse_null)
+{
+ wasapisink_reuse (GST_STATE_NULL);
+}
+
+GST_END_TEST;
+
+GST_START_TEST (test_wasapisink_reuse_ready)
+{
+ wasapisink_reuse (GST_STATE_READY);
+}
+
GST_END_TEST;
static gboolean
-device_is_available (const gchar * factory_name)
+check_wasapi_element (gboolean is_src)
{
+ gboolean ret = TRUE;
GstElement *elem;
- gboolean avail;
+ const gchar *elem_name;
+
+ if (is_src)
+ elem_name = "wasapisrc";
+ else
+ elem_name = "wasapisink";
- elem = gst_element_factory_make (factory_name, NULL);
- if (elem == NULL) {
- GST_INFO ("%s: not available", factory_name);
+ elem = gst_element_factory_make (elem_name, NULL);
+ if (!elem) {
+ GST_INFO ("%s is not available", elem_name);
return FALSE;
}
- avail = gst_element_set_state (elem, GST_STATE_READY)
- == GST_STATE_CHANGE_SUCCESS;
- if (!avail) {
- GST_INFO ("%s: cannot change state to ready", factory_name);
+ /* GST_STATE_READY is meaning that camera is available */
+ if (gst_element_set_state (elem, GST_STATE_READY) != GST_STATE_CHANGE_SUCCESS) {
+ GST_INFO ("cannot open device");
+ ret = FALSE;
}
gst_element_set_state (elem, GST_STATE_NULL);
gst_object_unref (elem);
- return avail;
+ return ret;
}
static Suite *
wasapi_suite (void)
{
- Suite *suite = suite_create ("wasapi");
- TCase *tc_sink = tcase_create ("sink");
+ Suite *s = suite_create ("wasapi");
+ TCase *tc_basic = tcase_create ("general");
+ gboolean have_src = FALSE;
+ gboolean have_sink = FALSE;
- suite_add_tcase (suite, tc_sink);
+ suite_add_tcase (s, tc_basic);
- if (device_is_available ("wasapisink")) {
- tcase_add_test (tc_sink, test_sink_play_ready);
+ have_src = check_wasapi_element (TRUE);
+ have_sink = check_wasapi_element (FALSE);
+
+ if (!have_src && !have_sink) {
+ GST_INFO ("Skipping tests, wasapisrc/wasapisink are unavailable");
} else {
- GST_INFO ("Sink not available, skipping sink tests");
+ if (have_src) {
+ tcase_add_test (tc_basic, test_wasapisrc_reuse_null);
+ tcase_add_test (tc_basic, test_wasapisrc_reuse_ready);
+ }
+
+ if (have_sink) {
+ tcase_add_test (tc_basic, test_wasapisink_reuse_null);
+ tcase_add_test (tc_basic, test_wasapisink_reuse_ready);
+ }
}
- return suite;
+ return s;
}
GST_CHECK_MAIN (wasapi)