mirror of
https://gitlab.freedesktop.org/gstreamer/gstreamer.git
synced 2024-12-23 16:50:47 +00:00
589ff8ca6d
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>
472 lines
11 KiB
C++
472 lines
11 KiB
C++
/* 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;
|
|
}
|