wasapi2: Implement IMMDeviceEnumerator based enumerator
... and merge wasapi2{capture,render}deviceprovider into single wasapi2deviceprovider since we can enumerate input/output audio devices at once using IMMDeviceEnumerator This is a preparation for complete porting to Win32 API Part-of: <https://gitlab.freedesktop.org/gstreamer/gstreamer/-/merge_requests/9307>
This commit is contained in:
parent
ea08b70946
commit
ba41200c96
151
subprojects/gst-plugins-bad/sys/wasapi2/gstwasapi2activator.cpp
Normal file
151
subprojects/gst-plugins-bad/sys/wasapi2/gstwasapi2activator.cpp
Normal file
@ -0,0 +1,151 @@
|
||||
/* GStreamer
|
||||
* Copyright (C) 2025 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.
|
||||
*/
|
||||
|
||||
#include "gstwasapi2activator.h"
|
||||
#include <objidl.h>
|
||||
|
||||
GST_DEBUG_CATEGORY_EXTERN (gst_wasapi2_debug);
|
||||
#define GST_CAT_DEFAULT gst_wasapi2_debug
|
||||
|
||||
/* *INDENT-OFF* */
|
||||
using namespace Microsoft::WRL;
|
||||
|
||||
void
|
||||
Wasapi2ActivationHandler::CreateInstance (Wasapi2ActivationHandler ** handler,
|
||||
const wchar_t * device_id,
|
||||
const AUDIOCLIENT_ACTIVATION_PARAMS * params)
|
||||
{
|
||||
auto self = new Wasapi2ActivationHandler ();
|
||||
self->device_id_ = device_id;
|
||||
|
||||
if (params) {
|
||||
self->params_ = *params;
|
||||
self->prop_.vt = VT_BLOB;
|
||||
self->prop_.blob.cbSize = sizeof (AUDIOCLIENT_ACTIVATION_PARAMS);
|
||||
self->prop_.blob.pBlobData = (BYTE *) &self->params_;
|
||||
self->have_params_ = true;
|
||||
}
|
||||
|
||||
*handler = self;
|
||||
}
|
||||
|
||||
STDMETHODIMP_ (ULONG)
|
||||
Wasapi2ActivationHandler::AddRef (void)
|
||||
{
|
||||
return InterlockedIncrement (&refcount_);
|
||||
}
|
||||
|
||||
STDMETHODIMP_ (ULONG)
|
||||
Wasapi2ActivationHandler::Release (void)
|
||||
{
|
||||
ULONG ref_count;
|
||||
|
||||
ref_count = InterlockedDecrement (&refcount_);
|
||||
|
||||
if (ref_count == 0)
|
||||
delete this;
|
||||
|
||||
return ref_count;
|
||||
}
|
||||
|
||||
STDMETHODIMP
|
||||
Wasapi2ActivationHandler::QueryInterface (REFIID riid, void ** object)
|
||||
{
|
||||
if (riid == __uuidof(IUnknown) || riid == __uuidof(IAgileObject)) {
|
||||
*object = static_cast<IUnknown *>(static_cast<Wasapi2ActivationHandler*>(this));
|
||||
} else if (riid == __uuidof(IActivateAudioInterfaceCompletionHandler)) {
|
||||
*object = static_cast<IActivateAudioInterfaceCompletionHandler *>(
|
||||
static_cast<Wasapi2ActivationHandler*>(this));
|
||||
} else if (riid == IID_Wasapi2ActivationHandler) {
|
||||
*object = this;
|
||||
} else {
|
||||
*object = nullptr;
|
||||
return E_NOINTERFACE;
|
||||
}
|
||||
|
||||
AddRef ();
|
||||
|
||||
return S_OK;
|
||||
}
|
||||
|
||||
STDMETHODIMP
|
||||
Wasapi2ActivationHandler::ActivateCompleted (IActivateAudioInterfaceAsyncOperation * op)
|
||||
{
|
||||
ComPtr<IUnknown> iface;
|
||||
HRESULT hr = S_OK;
|
||||
hr = op->GetActivateResult (&hr, &iface);
|
||||
if (FAILED (hr)) {
|
||||
GST_ERROR ("Couldn't get activate result, hr: 0x%x", (guint) hr);
|
||||
activate_hr_ = hr;
|
||||
SetEvent (event_);
|
||||
return hr;
|
||||
}
|
||||
|
||||
hr = iface.As (&client_);
|
||||
activate_hr_ = hr;
|
||||
|
||||
GST_LOG ("Activation result 0x%x", (guint) hr);
|
||||
|
||||
SetEvent (event_);
|
||||
|
||||
return hr;
|
||||
}
|
||||
|
||||
HRESULT
|
||||
Wasapi2ActivationHandler::ActivateAsync ()
|
||||
{
|
||||
ComPtr<IActivateAudioInterfaceAsyncOperation> async_op;
|
||||
auto hr = ActivateAudioInterfaceAsync (device_id_.c_str (),
|
||||
__uuidof (IAudioClient), have_params_ ? &prop_ : nullptr,
|
||||
this, &async_op);
|
||||
if (!gst_wasapi2_result (hr)) {
|
||||
activate_hr_ = hr;
|
||||
SetEvent (event_);
|
||||
}
|
||||
|
||||
return hr;
|
||||
}
|
||||
|
||||
HRESULT
|
||||
Wasapi2ActivationHandler::GetClient (IAudioClient ** client, DWORD timeout)
|
||||
{
|
||||
WaitForSingleObject (event_, timeout);
|
||||
auto hr = activate_hr_.load ();
|
||||
if (!gst_wasapi2_result (hr))
|
||||
return hr;
|
||||
|
||||
if (!client)
|
||||
return S_OK;
|
||||
|
||||
*client = client_.Get ();
|
||||
(*client)->AddRef ();
|
||||
|
||||
return S_OK;
|
||||
}
|
||||
|
||||
Wasapi2ActivationHandler::Wasapi2ActivationHandler ()
|
||||
{
|
||||
event_ = CreateEvent (nullptr, TRUE, FALSE, nullptr);
|
||||
}
|
||||
|
||||
Wasapi2ActivationHandler::~Wasapi2ActivationHandler ()
|
||||
{
|
||||
CloseHandle (event_);
|
||||
}
|
||||
/* *INDENT-ON* */
|
@ -0,0 +1,94 @@
|
||||
/* GStreamer
|
||||
* Copyright (C) 2025 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.
|
||||
*/
|
||||
|
||||
#pragma once
|
||||
|
||||
#include <gst/gst.h>
|
||||
#include "gstwasapi2util.h"
|
||||
#include <wrl.h>
|
||||
#include <atomic>
|
||||
#include <string>
|
||||
|
||||
/* Copy of audioclientactivationparams.h since those types are defined only for
|
||||
* NTDDI_VERSION >= NTDDI_WIN10_FE */
|
||||
#define VIRTUAL_AUDIO_DEVICE_PROCESS_LOOPBACK L"VAD\\Process_Loopback"
|
||||
typedef enum
|
||||
{
|
||||
PROCESS_LOOPBACK_MODE_INCLUDE_TARGET_PROCESS_TREE = 0,
|
||||
PROCESS_LOOPBACK_MODE_EXCLUDE_TARGET_PROCESS_TREE = 1
|
||||
} PROCESS_LOOPBACK_MODE;
|
||||
|
||||
typedef struct
|
||||
{
|
||||
DWORD TargetProcessId;
|
||||
PROCESS_LOOPBACK_MODE ProcessLoopbackMode;
|
||||
} AUDIOCLIENT_PROCESS_LOOPBACK_PARAMS;
|
||||
|
||||
typedef enum
|
||||
{
|
||||
AUDIOCLIENT_ACTIVATION_TYPE_DEFAULT = 0,
|
||||
AUDIOCLIENT_ACTIVATION_TYPE_PROCESS_LOOPBACK = 1
|
||||
} AUDIOCLIENT_ACTIVATION_TYPE;
|
||||
|
||||
typedef struct
|
||||
{
|
||||
AUDIOCLIENT_ACTIVATION_TYPE ActivationType;
|
||||
union
|
||||
{
|
||||
AUDIOCLIENT_PROCESS_LOOPBACK_PARAMS ProcessLoopbackParams;
|
||||
} DUMMYUNIONNAME;
|
||||
} AUDIOCLIENT_ACTIVATION_PARAMS;
|
||||
/* End of audioclientactivationparams.h */
|
||||
|
||||
DEFINE_GUID (IID_Wasapi2ActivationHandler, 0xaa7e8f85, 0x211e,
|
||||
0x42cc, 0x8c, 0x86, 0x99, 0x83, 0x5b, 0xef, 0x54, 0x86);
|
||||
class Wasapi2ActivationHandler :
|
||||
public IActivateAudioInterfaceCompletionHandler
|
||||
{
|
||||
public:
|
||||
static void CreateInstance (Wasapi2ActivationHandler ** handler,
|
||||
const wchar_t * device_id,
|
||||
const AUDIOCLIENT_ACTIVATION_PARAMS * params);
|
||||
|
||||
STDMETHODIMP_ (ULONG) AddRef (void);
|
||||
|
||||
STDMETHODIMP_ (ULONG) Release (void);
|
||||
|
||||
STDMETHODIMP QueryInterface (REFIID riid, void ** object);
|
||||
|
||||
STDMETHODIMP ActivateCompleted (IActivateAudioInterfaceAsyncOperation * op);
|
||||
|
||||
HRESULT ActivateAsync (void);
|
||||
|
||||
HRESULT GetClient (IAudioClient ** client, DWORD timeout);
|
||||
|
||||
private:
|
||||
Wasapi2ActivationHandler ();
|
||||
virtual ~Wasapi2ActivationHandler ();
|
||||
|
||||
private:
|
||||
Microsoft::WRL::ComPtr<IAudioClient> client_;
|
||||
std::atomic<HRESULT> activate_hr_ = { E_FAIL };
|
||||
HANDLE event_;
|
||||
PROPVARIANT prop_ = { };
|
||||
AUDIOCLIENT_ACTIVATION_PARAMS params_ = { };
|
||||
bool have_params_ = false;
|
||||
std::wstring device_id_;
|
||||
ULONG refcount_ = 1;
|
||||
};
|
@ -84,12 +84,9 @@ typedef struct
|
||||
} GST_AUDIOCLIENT_ACTIVATION_PARAMS;
|
||||
/* End of audioclientactivationparams.h */
|
||||
|
||||
G_BEGIN_DECLS
|
||||
|
||||
GST_DEBUG_CATEGORY_EXTERN (gst_wasapi2_client_debug);
|
||||
#define GST_CAT_DEFAULT gst_wasapi2_client_debug
|
||||
|
||||
G_END_DECLS
|
||||
/* *INDENT-ON* */
|
||||
|
||||
static void
|
||||
|
@ -1,601 +0,0 @@
|
||||
/* GStreamer
|
||||
* Copyright (C) 2018 Nirbheek Chauhan <nirbheek@centricular.com>
|
||||
* Copyright (C) 2020 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 "gstwasapi2device.h"
|
||||
#include "gstwasapi2client.h"
|
||||
#include "gstwasapi2util.h"
|
||||
#include <gst/winrt/gstwinrt.h>
|
||||
|
||||
GST_DEBUG_CATEGORY_EXTERN (gst_wasapi2_debug);
|
||||
#define GST_CAT_DEFAULT gst_wasapi2_debug
|
||||
|
||||
enum
|
||||
{
|
||||
PROP_0,
|
||||
PROP_DEVICE,
|
||||
};
|
||||
|
||||
struct _GstWasapi2Device
|
||||
{
|
||||
GstDevice parent;
|
||||
|
||||
gchar *device_id;
|
||||
const gchar *factory_name;
|
||||
GstWasapi2ClientDeviceClass device_class;
|
||||
};
|
||||
|
||||
G_DEFINE_TYPE (GstWasapi2Device, gst_wasapi2_device, GST_TYPE_DEVICE);
|
||||
|
||||
static void gst_wasapi2_device_get_property (GObject * object,
|
||||
guint prop_id, GValue * value, GParamSpec * pspec);
|
||||
static void gst_wasapi2_device_set_property (GObject * object,
|
||||
guint prop_id, const GValue * value, GParamSpec * pspec);
|
||||
static void gst_wasapi2_device_finalize (GObject * object);
|
||||
static GstElement *gst_wasapi2_device_create_element (GstDevice * device,
|
||||
const gchar * name);
|
||||
|
||||
static void
|
||||
gst_wasapi2_device_class_init (GstWasapi2DeviceClass * klass)
|
||||
{
|
||||
GObjectClass *gobject_class = G_OBJECT_CLASS (klass);
|
||||
GstDeviceClass *dev_class = GST_DEVICE_CLASS (klass);
|
||||
|
||||
dev_class->create_element = gst_wasapi2_device_create_element;
|
||||
|
||||
gobject_class->get_property = gst_wasapi2_device_get_property;
|
||||
gobject_class->set_property = gst_wasapi2_device_set_property;
|
||||
gobject_class->finalize = gst_wasapi2_device_finalize;
|
||||
|
||||
g_object_class_install_property (gobject_class, PROP_DEVICE,
|
||||
g_param_spec_string ("device", "Device",
|
||||
"Audio device ID as provided by "
|
||||
"Windows.Devices.Enumeration.DeviceInformation.Id", NULL,
|
||||
G_PARAM_READWRITE | G_PARAM_CONSTRUCT_ONLY | G_PARAM_STATIC_STRINGS));
|
||||
}
|
||||
|
||||
static void
|
||||
gst_wasapi2_device_init (GstWasapi2Device * self)
|
||||
{
|
||||
}
|
||||
|
||||
static void
|
||||
gst_wasapi2_device_finalize (GObject * object)
|
||||
{
|
||||
GstWasapi2Device *self = GST_WASAPI2_DEVICE (object);
|
||||
|
||||
g_free (self->device_id);
|
||||
|
||||
G_OBJECT_CLASS (gst_wasapi2_device_parent_class)->finalize (object);
|
||||
}
|
||||
|
||||
static GstElement *
|
||||
gst_wasapi2_device_create_element (GstDevice * device, const gchar * name)
|
||||
{
|
||||
GstWasapi2Device *self = GST_WASAPI2_DEVICE (device);
|
||||
GstElement *elem;
|
||||
|
||||
elem = gst_element_factory_make (self->factory_name, name);
|
||||
|
||||
g_object_set (elem, "device", self->device_id, NULL);
|
||||
|
||||
if (self->device_class == GST_WASAPI2_CLIENT_DEVICE_CLASS_LOOPBACK_CAPTURE)
|
||||
g_object_set (elem, "loopback", TRUE, NULL);
|
||||
|
||||
return elem;
|
||||
}
|
||||
|
||||
static void
|
||||
gst_wasapi2_device_get_property (GObject * object, guint prop_id,
|
||||
GValue * value, GParamSpec * pspec)
|
||||
{
|
||||
GstWasapi2Device *self = GST_WASAPI2_DEVICE (object);
|
||||
|
||||
switch (prop_id) {
|
||||
case PROP_DEVICE:
|
||||
g_value_set_string (value, self->device_id);
|
||||
break;
|
||||
default:
|
||||
G_OBJECT_WARN_INVALID_PROPERTY_ID (object, prop_id, pspec);
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
static void
|
||||
gst_wasapi2_device_set_property (GObject * object, guint prop_id,
|
||||
const GValue * value, GParamSpec * pspec)
|
||||
{
|
||||
GstWasapi2Device *self = GST_WASAPI2_DEVICE (object);
|
||||
|
||||
switch (prop_id) {
|
||||
case PROP_DEVICE:
|
||||
/* G_PARAM_CONSTRUCT_ONLY */
|
||||
self->device_id = g_value_dup_string (value);
|
||||
break;
|
||||
default:
|
||||
G_OBJECT_WARN_INVALID_PROPERTY_ID (object, prop_id, pspec);
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
typedef struct _GstWasapi2DeviceProvider
|
||||
{
|
||||
GstDeviceProvider parent;
|
||||
|
||||
GstWinRTDeviceWatcher *watcher;
|
||||
|
||||
GMutex lock;
|
||||
GCond cond;
|
||||
|
||||
gboolean enum_completed;
|
||||
} GstWasapi2DeviceProvider;
|
||||
|
||||
typedef struct _GstWasapi2DeviceProviderClass
|
||||
{
|
||||
GstDeviceProviderClass parent_class;
|
||||
|
||||
GstWinRTDeviceClass winrt_device_class;
|
||||
} GstWasapi2DeviceProviderClass;
|
||||
|
||||
static GstDeviceProviderClass *parent_class = NULL;
|
||||
|
||||
#define GST_WASAPI2_DEVICE_PROVIDER(object) \
|
||||
((GstWasapi2DeviceProvider *) (object))
|
||||
#define GST_WASAPI2_DEVICE_PROVIDER_GET_CLASS(object) \
|
||||
(G_TYPE_INSTANCE_GET_CLASS ((object),G_TYPE_FROM_INSTANCE (object),GstWasapi2DeviceProviderClass))
|
||||
|
||||
static void gst_wasapi2_device_provider_dispose (GObject * object);
|
||||
static void gst_wasapi2_device_provider_finalize (GObject * object);
|
||||
|
||||
static GList *gst_wasapi2_device_provider_probe (GstDeviceProvider * provider);
|
||||
static gboolean
|
||||
gst_wasapi2_device_provider_start (GstDeviceProvider * provider);
|
||||
static void gst_wasapi2_device_provider_stop (GstDeviceProvider * provider);
|
||||
|
||||
static void
|
||||
gst_wasapi2_device_provider_device_added (GstWinRTDeviceWatcher * watcher,
|
||||
__x_ABI_CWindows_CDevices_CEnumeration_CIDeviceInformation * info,
|
||||
gpointer user_data);
|
||||
static void
|
||||
gst_wasapi2_device_provider_device_updated (GstWinRTDeviceWatcher * watcher,
|
||||
__x_ABI_CWindows_CDevices_CEnumeration_CIDeviceInformationUpdate *
|
||||
info_update, gpointer user_data);
|
||||
static void gst_wasapi2_device_provider_device_removed (GstWinRTDeviceWatcher *
|
||||
watcher,
|
||||
__x_ABI_CWindows_CDevices_CEnumeration_CIDeviceInformationUpdate *
|
||||
info_update, gpointer user_data);
|
||||
static void
|
||||
gst_wasapi2_device_provider_device_enum_completed (GstWinRTDeviceWatcher *
|
||||
watcher, gpointer user_data);
|
||||
|
||||
static void
|
||||
gst_wasapi2_device_provider_class_init (GstWasapi2DeviceProviderClass * klass,
|
||||
gpointer data)
|
||||
{
|
||||
GObjectClass *gobject_class = G_OBJECT_CLASS (klass);
|
||||
GstDeviceProviderClass *provider_class = GST_DEVICE_PROVIDER_CLASS (klass);
|
||||
|
||||
gobject_class->dispose = gst_wasapi2_device_provider_dispose;
|
||||
gobject_class->finalize = gst_wasapi2_device_provider_finalize;
|
||||
|
||||
provider_class->probe = GST_DEBUG_FUNCPTR (gst_wasapi2_device_provider_probe);
|
||||
provider_class->start = GST_DEBUG_FUNCPTR (gst_wasapi2_device_provider_start);
|
||||
provider_class->stop = GST_DEBUG_FUNCPTR (gst_wasapi2_device_provider_stop);
|
||||
|
||||
parent_class = (GstDeviceProviderClass *) g_type_class_peek_parent (klass);
|
||||
|
||||
klass->winrt_device_class = (GstWinRTDeviceClass) GPOINTER_TO_INT (data);
|
||||
if (klass->winrt_device_class == GST_WINRT_DEVICE_CLASS_AUDIO_CAPTURE) {
|
||||
gst_device_provider_class_set_static_metadata (provider_class,
|
||||
"WASAPI (Windows Audio Session API) Capture Device Provider",
|
||||
"Source/Audio", "List WASAPI source devices",
|
||||
"Nirbheek Chauhan <nirbheek@centricular.com>, "
|
||||
"Seungha Yang <seungha@centricular.com>");
|
||||
} else {
|
||||
gst_device_provider_class_set_static_metadata (provider_class,
|
||||
"WASAPI (Windows Audio Session API) Render and Loopback Capture Device Provider",
|
||||
"Source/Sink/Audio", "List WASAPI loop back source and sink devices",
|
||||
"Nirbheek Chauhan <nirbheek@centricular.com>, "
|
||||
"Seungha Yang <seungha@centricular.com>");
|
||||
}
|
||||
}
|
||||
|
||||
static void
|
||||
gst_wasapi2_device_provider_init (GstWasapi2DeviceProvider * self)
|
||||
{
|
||||
GstWasapi2DeviceProviderClass *klass =
|
||||
GST_WASAPI2_DEVICE_PROVIDER_GET_CLASS (self);
|
||||
GstWinRTDeviceWatcherCallbacks callbacks;
|
||||
|
||||
g_mutex_init (&self->lock);
|
||||
g_cond_init (&self->cond);
|
||||
|
||||
callbacks.added = gst_wasapi2_device_provider_device_added;
|
||||
callbacks.updated = gst_wasapi2_device_provider_device_updated;
|
||||
callbacks.removed = gst_wasapi2_device_provider_device_removed;
|
||||
callbacks.enumeration_completed =
|
||||
gst_wasapi2_device_provider_device_enum_completed;
|
||||
|
||||
self->watcher =
|
||||
gst_winrt_device_watcher_new (klass->winrt_device_class,
|
||||
&callbacks, self);
|
||||
}
|
||||
|
||||
static void
|
||||
gst_wasapi2_device_provider_dispose (GObject * object)
|
||||
{
|
||||
GstWasapi2DeviceProvider *self = GST_WASAPI2_DEVICE_PROVIDER (object);
|
||||
|
||||
gst_clear_object (&self->watcher);
|
||||
|
||||
G_OBJECT_CLASS (parent_class)->dispose (object);
|
||||
}
|
||||
|
||||
static void
|
||||
gst_wasapi2_device_provider_finalize (GObject * object)
|
||||
{
|
||||
GstWasapi2DeviceProvider *self = GST_WASAPI2_DEVICE_PROVIDER (object);
|
||||
|
||||
g_mutex_clear (&self->lock);
|
||||
g_cond_clear (&self->cond);
|
||||
|
||||
G_OBJECT_CLASS (parent_class)->finalize (object);
|
||||
}
|
||||
|
||||
static void
|
||||
gst_wasapi2_device_provider_probe_internal (GstWasapi2DeviceProvider * self,
|
||||
GstWasapi2ClientDeviceClass client_class, GList ** devices)
|
||||
{
|
||||
gint i;
|
||||
const gchar *device_class, *factory_name;
|
||||
|
||||
if (client_class == GST_WASAPI2_CLIENT_DEVICE_CLASS_RENDER) {
|
||||
device_class = "Audio/Sink";
|
||||
factory_name = "wasapi2sink";
|
||||
} else {
|
||||
device_class = "Audio/Source";
|
||||
factory_name = "wasapi2src";
|
||||
}
|
||||
|
||||
for (i = 0;; i++) {
|
||||
GstWasapi2Client *client = NULL;
|
||||
GstDevice *device;
|
||||
GstStructure *props = NULL;
|
||||
GstCaps *caps = NULL;
|
||||
gchar *device_id = NULL;
|
||||
gchar *device_name = NULL;
|
||||
GstWasapi2Result result;
|
||||
|
||||
result = gst_wasapi2_client_enumerate (client_class, i, &client);
|
||||
if (result == GST_WASAPI2_DEVICE_NOT_FOUND)
|
||||
return;
|
||||
else if (result == GST_WASAPI2_ACTIVATION_FAILED)
|
||||
continue;
|
||||
|
||||
g_assert (client);
|
||||
|
||||
caps = gst_wasapi2_client_get_caps (client);
|
||||
if (!caps) {
|
||||
GST_WARNING_OBJECT (self, "Couldn't get caps from client %d", i);
|
||||
/* this might be a case where device activation is not finished yet */
|
||||
caps = gst_caps_from_string (GST_WASAPI2_STATIC_CAPS);
|
||||
}
|
||||
|
||||
g_object_get (client,
|
||||
"device", &device_id, "device-name", &device_name, NULL);
|
||||
if (!device_id) {
|
||||
GST_WARNING_OBJECT (self, "Couldn't get device name from client %d", i);
|
||||
goto next;
|
||||
}
|
||||
|
||||
if (!device_name) {
|
||||
GST_WARNING_OBJECT (self, "Couldn't get device name from client %d", i);
|
||||
goto next;
|
||||
}
|
||||
|
||||
props = gst_structure_new ("wasapi2-proplist",
|
||||
"device.api", G_TYPE_STRING, "wasapi2",
|
||||
"device.id", G_TYPE_STRING, device_id,
|
||||
"device.default", G_TYPE_BOOLEAN, i == 0,
|
||||
"wasapi2.device.description", G_TYPE_STRING, device_name, NULL);
|
||||
switch (client_class) {
|
||||
case GST_WASAPI2_CLIENT_DEVICE_CLASS_CAPTURE:
|
||||
gst_structure_set (props,
|
||||
"wasapi2.device.loopback", G_TYPE_BOOLEAN, FALSE, NULL);
|
||||
break;
|
||||
case GST_WASAPI2_CLIENT_DEVICE_CLASS_LOOPBACK_CAPTURE:
|
||||
gst_structure_set (props,
|
||||
"wasapi2.device.loopback", G_TYPE_BOOLEAN, TRUE, NULL);
|
||||
break;
|
||||
default:
|
||||
break;
|
||||
}
|
||||
|
||||
device = g_object_new (GST_TYPE_WASAPI2_DEVICE, "device", device_id,
|
||||
"display-name", device_name, "caps", caps,
|
||||
"device-class", device_class, "properties", props, NULL);
|
||||
GST_WASAPI2_DEVICE (device)->factory_name = factory_name;
|
||||
GST_WASAPI2_DEVICE (device)->device_class = client_class;
|
||||
|
||||
*devices = g_list_append (*devices, device);
|
||||
|
||||
next:
|
||||
gst_clear_object (&client);
|
||||
gst_clear_caps (&caps);
|
||||
g_free (device_id);
|
||||
g_free (device_name);
|
||||
if (props)
|
||||
gst_structure_free (props);
|
||||
}
|
||||
|
||||
return;
|
||||
}
|
||||
|
||||
static GList *
|
||||
gst_wasapi2_device_provider_probe (GstDeviceProvider * provider)
|
||||
{
|
||||
GstWasapi2DeviceProvider *self = GST_WASAPI2_DEVICE_PROVIDER (provider);
|
||||
GstWasapi2DeviceProviderClass *klass =
|
||||
GST_WASAPI2_DEVICE_PROVIDER_GET_CLASS (self);
|
||||
GList *devices = NULL;
|
||||
|
||||
if (klass->winrt_device_class == GST_WINRT_DEVICE_CLASS_AUDIO_CAPTURE) {
|
||||
gst_wasapi2_device_provider_probe_internal (self,
|
||||
GST_WASAPI2_CLIENT_DEVICE_CLASS_CAPTURE, &devices);
|
||||
} else {
|
||||
gst_wasapi2_device_provider_probe_internal (self,
|
||||
GST_WASAPI2_CLIENT_DEVICE_CLASS_LOOPBACK_CAPTURE, &devices);
|
||||
gst_wasapi2_device_provider_probe_internal (self,
|
||||
GST_WASAPI2_CLIENT_DEVICE_CLASS_RENDER, &devices);
|
||||
}
|
||||
|
||||
return devices;
|
||||
}
|
||||
|
||||
static gboolean
|
||||
gst_wasapi2_device_provider_start (GstDeviceProvider * provider)
|
||||
{
|
||||
GstWasapi2DeviceProvider *self = GST_WASAPI2_DEVICE_PROVIDER (provider);
|
||||
GList *devices = NULL;
|
||||
GList *iter;
|
||||
|
||||
if (!self->watcher) {
|
||||
GST_ERROR_OBJECT (self, "DeviceWatcher object wasn't configured");
|
||||
return FALSE;
|
||||
}
|
||||
|
||||
self->enum_completed = FALSE;
|
||||
|
||||
if (!gst_winrt_device_watcher_start (self->watcher))
|
||||
return FALSE;
|
||||
|
||||
/* Wait for initial enumeration to be completed */
|
||||
g_mutex_lock (&self->lock);
|
||||
while (!self->enum_completed)
|
||||
g_cond_wait (&self->cond, &self->lock);
|
||||
|
||||
devices = gst_wasapi2_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);
|
||||
}
|
||||
g_mutex_unlock (&self->lock);
|
||||
|
||||
return TRUE;
|
||||
}
|
||||
|
||||
static void
|
||||
gst_wasapi2_device_provider_stop (GstDeviceProvider * provider)
|
||||
{
|
||||
GstWasapi2DeviceProvider *self = GST_WASAPI2_DEVICE_PROVIDER (provider);
|
||||
|
||||
if (self->watcher)
|
||||
gst_winrt_device_watcher_stop (self->watcher);
|
||||
}
|
||||
|
||||
static gboolean
|
||||
gst_wasapi2_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.id");
|
||||
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.id");
|
||||
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_wasapi2_device_provider_update_devices (GstWasapi2DeviceProvider * 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_wasapi2_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_wasapi2_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_wasapi2_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 void
|
||||
gst_wasapi2_device_provider_device_added (GstWinRTDeviceWatcher * watcher,
|
||||
__x_ABI_CWindows_CDevices_CEnumeration_CIDeviceInformation * info,
|
||||
gpointer user_data)
|
||||
{
|
||||
GstWasapi2DeviceProvider *self = GST_WASAPI2_DEVICE_PROVIDER (user_data);
|
||||
|
||||
if (self->enum_completed)
|
||||
gst_wasapi2_device_provider_update_devices (self);
|
||||
}
|
||||
|
||||
static void
|
||||
gst_wasapi2_device_provider_device_removed (GstWinRTDeviceWatcher * watcher,
|
||||
__x_ABI_CWindows_CDevices_CEnumeration_CIDeviceInformationUpdate *
|
||||
info_update, gpointer user_data)
|
||||
{
|
||||
GstWasapi2DeviceProvider *self = GST_WASAPI2_DEVICE_PROVIDER (user_data);
|
||||
|
||||
if (self->enum_completed)
|
||||
gst_wasapi2_device_provider_update_devices (self);
|
||||
}
|
||||
|
||||
static void
|
||||
gst_wasapi2_device_provider_device_updated (GstWinRTDeviceWatcher * watcher,
|
||||
__x_ABI_CWindows_CDevices_CEnumeration_CIDeviceInformationUpdate * info,
|
||||
gpointer user_data)
|
||||
{
|
||||
GstWasapi2DeviceProvider *self = GST_WASAPI2_DEVICE_PROVIDER (user_data);
|
||||
|
||||
gst_wasapi2_device_provider_update_devices (self);
|
||||
}
|
||||
|
||||
static void
|
||||
gst_wasapi2_device_provider_device_enum_completed (GstWinRTDeviceWatcher *
|
||||
watcher, gpointer user_data)
|
||||
{
|
||||
GstWasapi2DeviceProvider *self = GST_WASAPI2_DEVICE_PROVIDER (user_data);
|
||||
|
||||
g_mutex_lock (&self->lock);
|
||||
GST_DEBUG_OBJECT (self, "Enumeration completed");
|
||||
self->enum_completed = TRUE;
|
||||
g_cond_signal (&self->cond);
|
||||
g_mutex_unlock (&self->lock);
|
||||
}
|
||||
|
||||
static void
|
||||
gst_wasapi2_device_provider_register_internal (GstPlugin * plugin,
|
||||
guint rank, GstWinRTDeviceClass winrt_device_class)
|
||||
{
|
||||
GType type;
|
||||
const gchar *type_name = NULL;
|
||||
const gchar *feature_name = NULL;
|
||||
GTypeInfo type_info = {
|
||||
sizeof (GstWasapi2DeviceProviderClass),
|
||||
NULL,
|
||||
NULL,
|
||||
(GClassInitFunc) gst_wasapi2_device_provider_class_init,
|
||||
NULL,
|
||||
NULL,
|
||||
sizeof (GstWasapi2DeviceProvider),
|
||||
0,
|
||||
(GInstanceInitFunc) gst_wasapi2_device_provider_init,
|
||||
};
|
||||
|
||||
type_info.class_data = GINT_TO_POINTER (winrt_device_class);
|
||||
|
||||
if (winrt_device_class == GST_WINRT_DEVICE_CLASS_AUDIO_CAPTURE) {
|
||||
type_name = "GstWasapi2CaputreDeviceProvider";
|
||||
feature_name = "wasapi2capturedeviceprovider";
|
||||
} else if (winrt_device_class == GST_WINRT_DEVICE_CLASS_AUDIO_RENDER) {
|
||||
type_name = "GstWasapi2RenderDeviceProvider";
|
||||
feature_name = "wasapi2renderdeviceprovider";
|
||||
} else {
|
||||
g_assert_not_reached ();
|
||||
return;
|
||||
}
|
||||
|
||||
type = g_type_register_static (GST_TYPE_DEVICE_PROVIDER,
|
||||
type_name, &type_info, (GTypeFlags) 0);
|
||||
|
||||
if (!gst_device_provider_register (plugin, feature_name, rank, type))
|
||||
GST_WARNING ("Failed to register provider '%s'", type_name);
|
||||
}
|
||||
|
||||
void
|
||||
gst_wasapi2_device_provider_register (GstPlugin * plugin, guint rank)
|
||||
{
|
||||
gst_wasapi2_device_provider_register_internal (plugin, rank,
|
||||
GST_WINRT_DEVICE_CLASS_AUDIO_CAPTURE);
|
||||
gst_wasapi2_device_provider_register_internal (plugin, rank,
|
||||
GST_WINRT_DEVICE_CLASS_AUDIO_RENDER);
|
||||
}
|
395
subprojects/gst-plugins-bad/sys/wasapi2/gstwasapi2device.cpp
Normal file
395
subprojects/gst-plugins-bad/sys/wasapi2/gstwasapi2device.cpp
Normal file
@ -0,0 +1,395 @@
|
||||
/* GStreamer
|
||||
* Copyright (C) 2018 Nirbheek Chauhan <nirbheek@centricular.com>
|
||||
* Copyright (C) 2020 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 "gstwasapi2device.h"
|
||||
#include "gstwasapi2util.h"
|
||||
#include "gstwasapi2enumerator.h"
|
||||
|
||||
GST_DEBUG_CATEGORY_EXTERN (gst_wasapi2_debug);
|
||||
#define GST_CAT_DEFAULT gst_wasapi2_debug
|
||||
|
||||
enum
|
||||
{
|
||||
PROP_0,
|
||||
PROP_DEVICE,
|
||||
};
|
||||
|
||||
struct _GstWasapi2Device
|
||||
{
|
||||
GstDevice parent;
|
||||
|
||||
gchar *device_id;
|
||||
const gchar *factory_name;
|
||||
GstWasapi2EndpointClass device_class;
|
||||
};
|
||||
|
||||
G_DEFINE_TYPE (GstWasapi2Device, gst_wasapi2_device, GST_TYPE_DEVICE);
|
||||
|
||||
static void gst_wasapi2_device_get_property (GObject * object,
|
||||
guint prop_id, GValue * value, GParamSpec * pspec);
|
||||
static void gst_wasapi2_device_set_property (GObject * object,
|
||||
guint prop_id, const GValue * value, GParamSpec * pspec);
|
||||
static void gst_wasapi2_device_finalize (GObject * object);
|
||||
static GstElement *gst_wasapi2_device_create_element (GstDevice * device,
|
||||
const gchar * name);
|
||||
|
||||
static void
|
||||
gst_wasapi2_device_class_init (GstWasapi2DeviceClass * klass)
|
||||
{
|
||||
GObjectClass *gobject_class = G_OBJECT_CLASS (klass);
|
||||
GstDeviceClass *dev_class = GST_DEVICE_CLASS (klass);
|
||||
|
||||
dev_class->create_element = gst_wasapi2_device_create_element;
|
||||
|
||||
gobject_class->get_property = gst_wasapi2_device_get_property;
|
||||
gobject_class->set_property = gst_wasapi2_device_set_property;
|
||||
gobject_class->finalize = gst_wasapi2_device_finalize;
|
||||
|
||||
g_object_class_install_property (gobject_class, PROP_DEVICE,
|
||||
g_param_spec_string ("device", "Device",
|
||||
"Audio device ID as provided by "
|
||||
"Windows.Devices.Enumeration.DeviceInformation.Id", nullptr,
|
||||
(GParamFlags) (G_PARAM_READWRITE | G_PARAM_CONSTRUCT_ONLY |
|
||||
G_PARAM_STATIC_STRINGS)));
|
||||
}
|
||||
|
||||
static void
|
||||
gst_wasapi2_device_init (GstWasapi2Device * self)
|
||||
{
|
||||
}
|
||||
|
||||
static void
|
||||
gst_wasapi2_device_finalize (GObject * object)
|
||||
{
|
||||
GstWasapi2Device *self = GST_WASAPI2_DEVICE (object);
|
||||
|
||||
g_free (self->device_id);
|
||||
|
||||
G_OBJECT_CLASS (gst_wasapi2_device_parent_class)->finalize (object);
|
||||
}
|
||||
|
||||
static GstElement *
|
||||
gst_wasapi2_device_create_element (GstDevice * device, const gchar * name)
|
||||
{
|
||||
GstWasapi2Device *self = GST_WASAPI2_DEVICE (device);
|
||||
GstElement *elem;
|
||||
|
||||
elem = gst_element_factory_make (self->factory_name, name);
|
||||
|
||||
g_object_set (elem, "device", self->device_id, nullptr);
|
||||
|
||||
if (self->device_class == GST_WASAPI2_ENDPOINT_CLASS_LOOPBACK_CAPTURE)
|
||||
g_object_set (elem, "loopback", TRUE, nullptr);
|
||||
|
||||
return elem;
|
||||
}
|
||||
|
||||
static void
|
||||
gst_wasapi2_device_get_property (GObject * object, guint prop_id,
|
||||
GValue * value, GParamSpec * pspec)
|
||||
{
|
||||
GstWasapi2Device *self = GST_WASAPI2_DEVICE (object);
|
||||
|
||||
switch (prop_id) {
|
||||
case PROP_DEVICE:
|
||||
g_value_set_string (value, self->device_id);
|
||||
break;
|
||||
default:
|
||||
G_OBJECT_WARN_INVALID_PROPERTY_ID (object, prop_id, pspec);
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
static void
|
||||
gst_wasapi2_device_set_property (GObject * object, guint prop_id,
|
||||
const GValue * value, GParamSpec * pspec)
|
||||
{
|
||||
GstWasapi2Device *self = GST_WASAPI2_DEVICE (object);
|
||||
|
||||
switch (prop_id) {
|
||||
case PROP_DEVICE:
|
||||
/* G_PARAM_CONSTRUCT_ONLY */
|
||||
self->device_id = g_value_dup_string (value);
|
||||
break;
|
||||
default:
|
||||
G_OBJECT_WARN_INVALID_PROPERTY_ID (object, prop_id, pspec);
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
struct _GstWasapi2DeviceProvider
|
||||
{
|
||||
GstDeviceProvider parent;
|
||||
|
||||
GstWasapi2Enumerator *enumerator;
|
||||
GPtrArray *device_list;
|
||||
};
|
||||
|
||||
G_DEFINE_TYPE (GstWasapi2DeviceProvider, gst_wasapi2_device_provider,
|
||||
GST_TYPE_DEVICE_PROVIDER);
|
||||
|
||||
static void gst_wasapi2_device_provider_dispose (GObject * object);
|
||||
|
||||
static GList *gst_wasapi2_device_provider_probe (GstDeviceProvider * provider);
|
||||
static gboolean
|
||||
gst_wasapi2_device_provider_start (GstDeviceProvider * provider);
|
||||
static void gst_wasapi2_device_provider_stop (GstDeviceProvider * provider);
|
||||
static void gst_wasapi2_device_provider_on_updated (GstWasapi2Enumerator *
|
||||
object, GstWasapi2DeviceProvider * self);
|
||||
|
||||
static void
|
||||
gst_wasapi2_device_provider_class_init (GstWasapi2DeviceProviderClass * klass)
|
||||
{
|
||||
auto gobject_class = G_OBJECT_CLASS (klass);
|
||||
auto provider_class = GST_DEVICE_PROVIDER_CLASS (klass);
|
||||
|
||||
gobject_class->dispose = gst_wasapi2_device_provider_dispose;
|
||||
|
||||
provider_class->probe = GST_DEBUG_FUNCPTR (gst_wasapi2_device_provider_probe);
|
||||
provider_class->start = GST_DEBUG_FUNCPTR (gst_wasapi2_device_provider_start);
|
||||
provider_class->stop = GST_DEBUG_FUNCPTR (gst_wasapi2_device_provider_stop);
|
||||
|
||||
gst_device_provider_class_set_static_metadata (provider_class,
|
||||
"WASAPI (Windows Audio Session API) Device Provider",
|
||||
"Source/Sink/Audio", "List WASAPI source devices",
|
||||
"Nirbheek Chauhan <nirbheek@centricular.com>, "
|
||||
"Seungha Yang <seungha@centricular.com>");
|
||||
}
|
||||
|
||||
static void
|
||||
gst_wasapi2_device_provider_init (GstWasapi2DeviceProvider * self)
|
||||
{
|
||||
self->enumerator = gst_wasapi2_enumerator_new ();
|
||||
g_signal_connect (self->enumerator, "updated",
|
||||
G_CALLBACK (gst_wasapi2_device_provider_on_updated), self);
|
||||
|
||||
self->device_list = g_ptr_array_new_with_free_func ((GDestroyNotify)
|
||||
gst_wasapi2_enumerator_entry_free);
|
||||
}
|
||||
|
||||
static void
|
||||
gst_wasapi2_device_provider_dispose (GObject * object)
|
||||
{
|
||||
auto self = GST_WASAPI2_DEVICE_PROVIDER (object);
|
||||
|
||||
gst_clear_object (&self->enumerator);
|
||||
|
||||
g_clear_pointer (&self->device_list, g_ptr_array_unref);
|
||||
|
||||
G_OBJECT_CLASS (gst_wasapi2_device_provider_parent_class)->dispose (object);
|
||||
}
|
||||
|
||||
static GList *
|
||||
gst_wasapi2_device_provider_probe (GstDeviceProvider * provider)
|
||||
{
|
||||
auto self = GST_WASAPI2_DEVICE_PROVIDER (provider);
|
||||
GList *devices = nullptr;
|
||||
|
||||
g_ptr_array_set_size (self->device_list, 0);
|
||||
gst_wasapi2_enumerator_enumerate_devices (self->enumerator,
|
||||
self->device_list);
|
||||
|
||||
for (guint i = 0; i < self->device_list->len; i++) {
|
||||
auto entry = (GstWasapi2EnumeratorEntry *)
|
||||
g_ptr_array_index (self->device_list, i);
|
||||
|
||||
auto props = gst_structure_new ("wasapi2-proplist",
|
||||
"device.api", G_TYPE_STRING, "wasapi2",
|
||||
"device.id", G_TYPE_STRING, entry->device_id,
|
||||
"device.default", G_TYPE_BOOLEAN, entry->is_default,
|
||||
"wasapi2.device.description", G_TYPE_STRING, entry->device_name,
|
||||
nullptr);
|
||||
|
||||
if (entry->flow == eCapture) {
|
||||
gst_structure_set (props,
|
||||
"wasapi2.device.loopback", G_TYPE_BOOLEAN, FALSE, nullptr);
|
||||
|
||||
auto device = (GstDevice *) g_object_new (GST_TYPE_WASAPI2_DEVICE,
|
||||
"device", entry->device_id,
|
||||
"display-name", entry->device_name, "caps", entry->caps,
|
||||
"device-class", "Audio/Source", "properties", props, nullptr);
|
||||
gst_structure_free (props);
|
||||
|
||||
GST_WASAPI2_DEVICE (device)->factory_name = "wasapi2src";
|
||||
GST_WASAPI2_DEVICE (device)->device_class =
|
||||
GST_WASAPI2_ENDPOINT_CLASS_CAPTURE;
|
||||
|
||||
devices = g_list_append (devices, device);
|
||||
} else {
|
||||
auto prop_copy = gst_structure_copy (props);
|
||||
gst_structure_set (prop_copy,
|
||||
"wasapi2.device.loopback", G_TYPE_BOOLEAN, TRUE, nullptr);
|
||||
|
||||
auto device = (GstDevice *) g_object_new (GST_TYPE_WASAPI2_DEVICE,
|
||||
"device", entry->device_id,
|
||||
"display-name", entry->device_name, "caps", entry->caps,
|
||||
"device-class", "Audio/Sink", "properties", props, nullptr);
|
||||
gst_structure_free (props);
|
||||
|
||||
GST_WASAPI2_DEVICE (device)->factory_name = "wasapi2sink";
|
||||
GST_WASAPI2_DEVICE (device)->device_class =
|
||||
GST_WASAPI2_ENDPOINT_CLASS_RENDER;
|
||||
|
||||
devices = g_list_append (devices, device);
|
||||
|
||||
device = (GstDevice *) g_object_new (GST_TYPE_WASAPI2_DEVICE,
|
||||
"device", entry->device_id,
|
||||
"display-name", entry->device_name, "caps", entry->caps,
|
||||
"device-class", "Audio/Source", "properties", prop_copy, nullptr);
|
||||
gst_structure_free (prop_copy);
|
||||
|
||||
GST_WASAPI2_DEVICE (device)->factory_name = "wasapi2src";
|
||||
GST_WASAPI2_DEVICE (device)->device_class =
|
||||
GST_WASAPI2_ENDPOINT_CLASS_LOOPBACK_CAPTURE;
|
||||
|
||||
devices = g_list_append (devices, device);
|
||||
}
|
||||
}
|
||||
|
||||
g_ptr_array_set_size (self->device_list, 0);
|
||||
|
||||
return devices;
|
||||
}
|
||||
|
||||
static gboolean
|
||||
gst_wasapi2_device_provider_start (GstDeviceProvider * provider)
|
||||
{
|
||||
auto self = GST_WASAPI2_DEVICE_PROVIDER (provider);
|
||||
GList *devices = nullptr;
|
||||
GList *iter;
|
||||
|
||||
if (!self->enumerator) {
|
||||
GST_ERROR_OBJECT (self, "Enumerator object wasn't configured");
|
||||
return FALSE;
|
||||
}
|
||||
|
||||
devices = gst_wasapi2_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);
|
||||
}
|
||||
|
||||
gst_wasapi2_enumerator_activate_notification (self->enumerator, TRUE);
|
||||
|
||||
return TRUE;
|
||||
}
|
||||
|
||||
static void
|
||||
gst_wasapi2_device_provider_stop (GstDeviceProvider * provider)
|
||||
{
|
||||
auto self = GST_WASAPI2_DEVICE_PROVIDER (provider);
|
||||
|
||||
if (self->enumerator)
|
||||
gst_wasapi2_enumerator_activate_notification (self->enumerator, FALSE);
|
||||
}
|
||||
|
||||
static gboolean
|
||||
gst_wasapi2_device_is_in_list (GList * list, GstDevice * device)
|
||||
{
|
||||
GList *iter;
|
||||
GstStructure *s;
|
||||
gboolean found = FALSE;
|
||||
|
||||
s = gst_device_get_properties (device);
|
||||
g_assert (s);
|
||||
|
||||
for (iter = list; iter; iter = g_list_next (iter)) {
|
||||
GstStructure *other_s;
|
||||
|
||||
other_s = gst_device_get_properties (GST_DEVICE (iter->data));
|
||||
g_assert (other_s);
|
||||
|
||||
found = gst_structure_is_equal (s, other_s);
|
||||
|
||||
gst_structure_free (other_s);
|
||||
if (found)
|
||||
break;
|
||||
}
|
||||
|
||||
gst_structure_free (s);
|
||||
|
||||
return found;
|
||||
}
|
||||
|
||||
static void
|
||||
gst_wasapi2_device_provider_update_devices (GstWasapi2DeviceProvider * self)
|
||||
{
|
||||
GstDeviceProvider *provider = GST_DEVICE_PROVIDER_CAST (self);
|
||||
GList *prev_devices = nullptr;
|
||||
GList *new_devices = nullptr;
|
||||
GList *to_add = nullptr;
|
||||
GList *to_remove = nullptr;
|
||||
GList *iter;
|
||||
|
||||
GST_OBJECT_LOCK (self);
|
||||
prev_devices = g_list_copy_deep (provider->devices,
|
||||
(GCopyFunc) gst_object_ref, nullptr);
|
||||
GST_OBJECT_UNLOCK (self);
|
||||
|
||||
new_devices = gst_wasapi2_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_wasapi2_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_wasapi2_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 void
|
||||
gst_wasapi2_device_provider_on_updated (GstWasapi2Enumerator * object,
|
||||
GstWasapi2DeviceProvider * self)
|
||||
{
|
||||
gst_wasapi2_device_provider_update_devices (self);
|
||||
}
|
@ -28,7 +28,9 @@ G_BEGIN_DECLS
|
||||
G_DECLARE_FINAL_TYPE (GstWasapi2Device, gst_wasapi2_device,
|
||||
GST, WASAPI2_DEVICE, GstDevice);
|
||||
|
||||
void gst_wasapi2_device_provider_register (GstPlugin * plugin, guint rank);
|
||||
#define GST_TYPE_WASAPI2_DEVICE_PROVIDER (gst_wasapi2_device_provider_get_type())
|
||||
G_DECLARE_FINAL_TYPE (GstWasapi2DeviceProvider, gst_wasapi2_device_provider,
|
||||
GST, WASAPI2_DEVICE_PROVIDER, GstDeviceProvider);
|
||||
|
||||
G_END_DECLS
|
||||
|
||||
|
620
subprojects/gst-plugins-bad/sys/wasapi2/gstwasapi2enumerator.cpp
Normal file
620
subprojects/gst-plugins-bad/sys/wasapi2/gstwasapi2enumerator.cpp
Normal file
@ -0,0 +1,620 @@
|
||||
/* GStreamer
|
||||
* Copyright (C) 2025 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 "gstwasapi2enumerator.h"
|
||||
#include "gstwasapi2activator.h"
|
||||
#include <mutex>
|
||||
#include <condition_variable>
|
||||
#include <wrl.h>
|
||||
#include <functiondiscoverykeys_devpkey.h>
|
||||
#include <string>
|
||||
|
||||
/* *INDENT-OFF* */
|
||||
using namespace Microsoft::WRL;
|
||||
|
||||
GST_DEBUG_CATEGORY_EXTERN (gst_wasapi2_debug);
|
||||
#define GST_CAT_DEFAULT gst_wasapi2_debug
|
||||
|
||||
static GstStaticCaps template_caps = GST_STATIC_CAPS (GST_WASAPI2_STATIC_CAPS);
|
||||
|
||||
static void gst_wasapi2_on_device_updated (GstWasapi2Enumerator * object);
|
||||
|
||||
/* IMMNotificationClient implementation */
|
||||
class IWasapi2NotificationClient : public IMMNotificationClient
|
||||
{
|
||||
public:
|
||||
static void
|
||||
CreateInstance (GstWasapi2Enumerator * object, IMMNotificationClient ** client)
|
||||
{
|
||||
auto self = new IWasapi2NotificationClient ();
|
||||
|
||||
g_weak_ref_set (&self->obj_, object);
|
||||
|
||||
*client = (IMMNotificationClient *) self;
|
||||
}
|
||||
|
||||
/* 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)
|
||||
{
|
||||
auto object = (GstWasapi2Enumerator *) g_weak_ref_get (&obj_);
|
||||
if (!object)
|
||||
return S_OK;
|
||||
|
||||
gst_wasapi2_on_device_updated (object);
|
||||
gst_object_unref (object);
|
||||
|
||||
return S_OK;
|
||||
}
|
||||
|
||||
STDMETHODIMP
|
||||
OnDeviceAdded (LPCWSTR device_id)
|
||||
{
|
||||
auto object = (GstWasapi2Enumerator *) g_weak_ref_get (&obj_);
|
||||
if (!object)
|
||||
return S_OK;
|
||||
|
||||
gst_wasapi2_on_device_updated (object);
|
||||
gst_object_unref (object);
|
||||
|
||||
return S_OK;
|
||||
}
|
||||
|
||||
STDMETHODIMP
|
||||
OnDeviceRemoved (LPCWSTR device_id)
|
||||
{
|
||||
auto object = (GstWasapi2Enumerator *) g_weak_ref_get (&obj_);
|
||||
if (!object)
|
||||
return S_OK;
|
||||
|
||||
gst_wasapi2_on_device_updated (object);
|
||||
gst_object_unref (object);
|
||||
|
||||
return S_OK;
|
||||
}
|
||||
|
||||
STDMETHODIMP
|
||||
OnDefaultDeviceChanged (EDataFlow flow, ERole role, LPCWSTR default_device_id)
|
||||
{
|
||||
auto object = (GstWasapi2Enumerator *) g_weak_ref_get (&obj_);
|
||||
if (!object)
|
||||
return S_OK;
|
||||
|
||||
gst_wasapi2_on_device_updated (object);
|
||||
gst_object_unref (object);
|
||||
|
||||
return S_OK;
|
||||
}
|
||||
|
||||
STDMETHODIMP
|
||||
OnPropertyValueChanged (LPCWSTR device_id, const PROPERTYKEY key)
|
||||
{
|
||||
return S_OK;
|
||||
}
|
||||
|
||||
private:
|
||||
IWasapi2NotificationClient ()
|
||||
{
|
||||
g_weak_ref_init (&obj_, nullptr);
|
||||
}
|
||||
|
||||
virtual ~IWasapi2NotificationClient ()
|
||||
{
|
||||
g_weak_ref_clear (&obj_);
|
||||
}
|
||||
|
||||
private:
|
||||
ULONG ref_count_ = 1;
|
||||
GWeakRef obj_;
|
||||
};
|
||||
|
||||
enum
|
||||
{
|
||||
PROP_0,
|
||||
PROP_ENUMERATOR,
|
||||
};
|
||||
|
||||
enum
|
||||
{
|
||||
SIGNAL_UPDATED,
|
||||
SIGNAL_LAST,
|
||||
};
|
||||
|
||||
static guint wasapi2_device_signals[SIGNAL_LAST] = { };
|
||||
|
||||
struct GstWasapi2EnumeratorPrivate
|
||||
{
|
||||
ComPtr<IMMDeviceEnumerator> handle;
|
||||
std::mutex lock;
|
||||
std::condition_variable cond;
|
||||
|
||||
ComPtr<IMMNotificationClient> client;
|
||||
Wasapi2ActivationHandler *capture_activator = nullptr;
|
||||
Wasapi2ActivationHandler *render_activator = nullptr;
|
||||
|
||||
void ClearCOM ()
|
||||
{
|
||||
if (capture_activator) {
|
||||
capture_activator->GetClient (nullptr, INFINITE);
|
||||
capture_activator->Release ();
|
||||
}
|
||||
|
||||
if (render_activator) {
|
||||
render_activator->GetClient (nullptr, INFINITE);
|
||||
render_activator->Release ();
|
||||
}
|
||||
|
||||
if (client && handle)
|
||||
handle->UnregisterEndpointNotificationCallback (client.Get ());
|
||||
|
||||
client = nullptr;
|
||||
handle = nullptr;
|
||||
}
|
||||
};
|
||||
/* *INDENT-ON* */
|
||||
|
||||
struct _GstWasapi2Enumerator
|
||||
{
|
||||
GstObject parent;
|
||||
|
||||
GstWasapi2EnumeratorPrivate *priv;
|
||||
|
||||
GThread *thread;
|
||||
GMainContext *context;
|
||||
GMainLoop *loop;
|
||||
};
|
||||
|
||||
static void gst_wasapi2_enumerator_finalize (GObject * object);
|
||||
|
||||
#define gst_wasapi2_enumerator_parent_class parent_class
|
||||
G_DEFINE_TYPE (GstWasapi2Enumerator, gst_wasapi2_enumerator, GST_TYPE_OBJECT);
|
||||
|
||||
static void
|
||||
gst_wasapi2_enumerator_class_init (GstWasapi2EnumeratorClass * klass)
|
||||
{
|
||||
auto object_class = G_OBJECT_CLASS (klass);
|
||||
|
||||
object_class->finalize = gst_wasapi2_enumerator_finalize;
|
||||
|
||||
wasapi2_device_signals[SIGNAL_UPDATED] =
|
||||
g_signal_new_class_handler ("updated", G_TYPE_FROM_CLASS (klass),
|
||||
G_SIGNAL_RUN_LAST, nullptr, nullptr, nullptr, nullptr, G_TYPE_NONE, 0);
|
||||
}
|
||||
|
||||
static void
|
||||
gst_wasapi2_enumerator_init (GstWasapi2Enumerator * self)
|
||||
{
|
||||
self->priv = new GstWasapi2EnumeratorPrivate ();
|
||||
self->context = g_main_context_new ();
|
||||
self->loop = g_main_loop_new (self->context, FALSE);
|
||||
}
|
||||
|
||||
static void
|
||||
gst_wasapi2_enumerator_finalize (GObject * object)
|
||||
{
|
||||
auto self = GST_WASAPI2_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);
|
||||
|
||||
delete self->priv;
|
||||
|
||||
G_OBJECT_CLASS (parent_class)->finalize (object);
|
||||
}
|
||||
|
||||
static void
|
||||
gst_wasapi2_on_device_updated (GstWasapi2Enumerator * object)
|
||||
{
|
||||
/* *INDENT-OFF* */
|
||||
g_main_context_invoke_full (object->context, G_PRIORITY_DEFAULT,
|
||||
[] (gpointer obj) -> gboolean {
|
||||
g_signal_emit (obj, wasapi2_device_signals[SIGNAL_UPDATED], 0);
|
||||
return G_SOURCE_REMOVE;
|
||||
},
|
||||
gst_object_ref (object), (GDestroyNotify) gst_object_unref);
|
||||
/* *INDENT-ON* */
|
||||
}
|
||||
|
||||
static gpointer
|
||||
gst_wasapi2_enumerator_thread_func (GstWasapi2Enumerator * self)
|
||||
{
|
||||
auto priv = self->priv;
|
||||
|
||||
CoInitializeEx (nullptr, COINIT_MULTITHREADED);
|
||||
|
||||
g_main_context_push_thread_default (self->context);
|
||||
|
||||
auto idle_source = g_idle_source_new ();
|
||||
/* *INDENT-OFF* */
|
||||
g_source_set_callback (idle_source,
|
||||
[] (gpointer user_data) -> gboolean {
|
||||
auto self = (GstWasapi2Enumerator *) user_data;
|
||||
auto priv = self->priv;
|
||||
std::lock_guard < std::mutex > lk (priv->lock);
|
||||
priv->cond.notify_all ();
|
||||
return G_SOURCE_REMOVE;
|
||||
},
|
||||
self, nullptr);
|
||||
/* *INDENT-ON* */
|
||||
g_source_attach (idle_source, self->context);
|
||||
g_source_unref (idle_source);
|
||||
|
||||
auto hr = CoCreateInstance (__uuidof (MMDeviceEnumerator),
|
||||
nullptr, CLSCTX_ALL, IID_PPV_ARGS (&priv->handle));
|
||||
if (FAILED (hr)) {
|
||||
GST_ERROR_OBJECT (self, "Failed to create IMMDeviceEnumerator instance");
|
||||
goto run_loop;
|
||||
}
|
||||
|
||||
if (gst_wasapi2_can_automatic_stream_routing ()) {
|
||||
Wasapi2ActivationHandler::CreateInstance (&priv->capture_activator,
|
||||
gst_wasapi2_get_default_device_id_wide (eCapture), nullptr);
|
||||
priv->capture_activator->ActivateAsync ();
|
||||
|
||||
Wasapi2ActivationHandler::CreateInstance (&priv->render_activator,
|
||||
gst_wasapi2_get_default_device_id_wide (eRender), nullptr);
|
||||
priv->render_activator->ActivateAsync ();
|
||||
}
|
||||
|
||||
run_loop:
|
||||
GST_INFO_OBJECT (self, "Starting loop");
|
||||
g_main_loop_run (self->loop);
|
||||
GST_INFO_OBJECT (self, "Stopped loop");
|
||||
|
||||
priv->ClearCOM ();
|
||||
|
||||
g_main_context_pop_thread_default (self->context);
|
||||
|
||||
CoUninitialize ();
|
||||
|
||||
return nullptr;
|
||||
}
|
||||
|
||||
GstWasapi2Enumerator *
|
||||
gst_wasapi2_enumerator_new (void)
|
||||
{
|
||||
auto self = (GstWasapi2Enumerator *)
|
||||
g_object_new (GST_TYPE_WASAPI2_ENUMERATOR, nullptr);
|
||||
gst_object_ref_sink (self);
|
||||
|
||||
auto priv = self->priv;
|
||||
|
||||
{
|
||||
std::unique_lock < std::mutex > lk (priv->lock);
|
||||
self->thread = g_thread_new ("GstWasapi2Enumerator",
|
||||
(GThreadFunc) gst_wasapi2_enumerator_thread_func, self);
|
||||
while (!g_main_loop_is_running (self->loop))
|
||||
priv->cond.wait (lk);
|
||||
}
|
||||
|
||||
if (!priv->handle) {
|
||||
gst_object_unref (self);
|
||||
return nullptr;
|
||||
}
|
||||
|
||||
return self;
|
||||
}
|
||||
|
||||
/* *INDENT-OFF* */
|
||||
struct ActivateNotificationData
|
||||
{
|
||||
ActivateNotificationData ()
|
||||
{
|
||||
event = CreateEvent (nullptr, FALSE, FALSE, nullptr);
|
||||
}
|
||||
|
||||
~ActivateNotificationData ()
|
||||
{
|
||||
CloseHandle (event);
|
||||
}
|
||||
|
||||
GstWasapi2Enumerator *self;
|
||||
gboolean active;
|
||||
HANDLE event;
|
||||
};
|
||||
/* *INDENT-ON* */
|
||||
|
||||
static gboolean
|
||||
set_notification_callback (ActivateNotificationData * data)
|
||||
{
|
||||
auto self = data->self;
|
||||
auto priv = self->priv;
|
||||
|
||||
if (data->active) {
|
||||
if (!priv->client) {
|
||||
ComPtr < IMMNotificationClient > client;
|
||||
IWasapi2NotificationClient::CreateInstance (self, &client);
|
||||
|
||||
auto hr =
|
||||
priv->handle->RegisterEndpointNotificationCallback (client.Get ());
|
||||
if (FAILED (hr)) {
|
||||
GST_ERROR_OBJECT (self, "Couldn't register callback");
|
||||
} else {
|
||||
GST_LOG_OBJECT (self, "Registered notification");
|
||||
priv->client = client;
|
||||
}
|
||||
}
|
||||
} else if (priv->client) {
|
||||
priv->handle->UnregisterEndpointNotificationCallback (priv->client.Get ());
|
||||
priv->client = nullptr;
|
||||
GST_LOG_OBJECT (self, "Unregistered notification");
|
||||
}
|
||||
|
||||
SetEvent (data->event);
|
||||
|
||||
return G_SOURCE_REMOVE;
|
||||
}
|
||||
|
||||
void
|
||||
gst_wasapi2_enumerator_activate_notification (GstWasapi2Enumerator * object,
|
||||
gboolean active)
|
||||
{
|
||||
auto priv = object->priv;
|
||||
|
||||
if (!priv->handle)
|
||||
return;
|
||||
|
||||
ActivateNotificationData data;
|
||||
data.self = object;
|
||||
data.active = active;
|
||||
|
||||
g_main_context_invoke (object->context,
|
||||
(GSourceFunc) set_notification_callback, &data);
|
||||
|
||||
WaitForSingleObject (data.event, INFINITE);
|
||||
}
|
||||
|
||||
void
|
||||
gst_wasapi2_enumerator_entry_free (GstWasapi2EnumeratorEntry * entry)
|
||||
{
|
||||
g_free (entry->device_id);
|
||||
g_free (entry->device_name);
|
||||
gst_clear_caps (&entry->caps);
|
||||
g_free (entry);
|
||||
}
|
||||
|
||||
/* *INDENT-OFF* */
|
||||
struct EnumerateData
|
||||
{
|
||||
EnumerateData ()
|
||||
{
|
||||
event = CreateEvent (nullptr, FALSE, FALSE, nullptr);
|
||||
}
|
||||
|
||||
~EnumerateData ()
|
||||
{
|
||||
CloseHandle (event);
|
||||
}
|
||||
|
||||
GstWasapi2Enumerator *self;
|
||||
GPtrArray *device_list;
|
||||
HANDLE event;
|
||||
};
|
||||
/* *INDENT-ON* */
|
||||
|
||||
static void
|
||||
gst_wasapi2_enumerator_add_entry (GstWasapi2Enumerator * self,
|
||||
IAudioClient * client,
|
||||
GstCaps * static_caps, EDataFlow flow, gboolean is_default,
|
||||
gchar * device_id, gchar * device_name, GPtrArray * device_list)
|
||||
{
|
||||
WAVEFORMATEX *mix_format = nullptr;
|
||||
GstCaps *supported_caps = nullptr;
|
||||
|
||||
client->GetMixFormat (&mix_format);
|
||||
if (!mix_format) {
|
||||
g_free (device_id);
|
||||
g_free (device_name);
|
||||
return;
|
||||
}
|
||||
|
||||
gst_wasapi2_util_parse_waveformatex (mix_format,
|
||||
static_caps, &supported_caps, nullptr);
|
||||
CoTaskMemFree (mix_format);
|
||||
|
||||
if (!supported_caps) {
|
||||
g_free (device_id);
|
||||
g_free (device_name);
|
||||
return;
|
||||
}
|
||||
|
||||
auto entry = g_new0 (GstWasapi2EnumeratorEntry, 1);
|
||||
|
||||
entry->device_id = device_id;
|
||||
entry->device_name = device_name;
|
||||
entry->caps = supported_caps;
|
||||
entry->flow = flow;
|
||||
entry->is_default = is_default;
|
||||
|
||||
GST_LOG_OBJECT (self, "Adding entry %s (%s), flow %d, caps %" GST_PTR_FORMAT,
|
||||
device_id, device_name, flow, supported_caps);
|
||||
|
||||
g_ptr_array_add (device_list, entry);
|
||||
}
|
||||
|
||||
static gboolean
|
||||
gst_wasapi2_enumerator_enumerate_internal (EnumerateData * data)
|
||||
{
|
||||
auto self = data->self;
|
||||
auto priv = self->priv;
|
||||
ComPtr < IMMDeviceCollection > collection;
|
||||
|
||||
auto hr = priv->handle->EnumAudioEndpoints (eAll, DEVICE_STATE_ACTIVE,
|
||||
&collection);
|
||||
if (!gst_wasapi2_result (hr)) {
|
||||
SetEvent (data->event);
|
||||
return G_SOURCE_REMOVE;
|
||||
}
|
||||
|
||||
UINT count = 0;
|
||||
hr = collection->GetCount (&count);
|
||||
if (!gst_wasapi2_result (hr) || count == 0) {
|
||||
SetEvent (data->event);
|
||||
return G_SOURCE_REMOVE;
|
||||
}
|
||||
|
||||
auto scaps = gst_static_caps_get (&template_caps);
|
||||
|
||||
ComPtr < IAudioClient > default_capture_client;
|
||||
ComPtr < IAudioClient > default_render_client;
|
||||
if (priv->capture_activator)
|
||||
priv->capture_activator->GetClient (&default_capture_client, 10000);
|
||||
if (priv->render_activator)
|
||||
priv->render_activator->GetClient (&default_render_client, 10000);
|
||||
|
||||
if (default_capture_client) {
|
||||
gst_wasapi2_enumerator_add_entry (self, default_capture_client.Get (),
|
||||
scaps, eCapture, TRUE,
|
||||
g_strdup (gst_wasapi2_get_default_device_id (eCapture)),
|
||||
g_strdup ("Default Audio Capture Device"), data->device_list);
|
||||
}
|
||||
|
||||
if (default_render_client) {
|
||||
gst_wasapi2_enumerator_add_entry (self, default_render_client.Get (),
|
||||
scaps, eRender, TRUE,
|
||||
g_strdup (gst_wasapi2_get_default_device_id (eRender)),
|
||||
g_strdup ("Default Audio Render Device"), data->device_list);
|
||||
}
|
||||
|
||||
for (UINT i = 0; i < count; i++) {
|
||||
ComPtr < IMMDevice > device;
|
||||
ComPtr < IMMEndpoint > endpoint;
|
||||
EDataFlow flow;
|
||||
|
||||
hr = collection->Item (i, &device);
|
||||
if (!gst_wasapi2_result (hr))
|
||||
continue;
|
||||
|
||||
hr = device.As (&endpoint);
|
||||
if (!gst_wasapi2_result (hr))
|
||||
continue;
|
||||
|
||||
hr = endpoint->GetDataFlow (&flow);
|
||||
if (!gst_wasapi2_result (hr))
|
||||
continue;
|
||||
|
||||
ComPtr < IPropertyStore > prop;
|
||||
hr = device->OpenPropertyStore (STGM_READ, &prop);
|
||||
if (!gst_wasapi2_result (hr))
|
||||
continue;
|
||||
|
||||
PROPVARIANT var;
|
||||
PropVariantInit (&var);
|
||||
hr = prop->GetValue (PKEY_Device_FriendlyName, &var);
|
||||
if (!gst_wasapi2_result (hr))
|
||||
continue;
|
||||
|
||||
auto desc = g_utf16_to_utf8 ((gunichar2 *) var.pwszVal,
|
||||
-1, nullptr, nullptr, nullptr);
|
||||
PropVariantClear (&var);
|
||||
|
||||
LPWSTR wid = nullptr;
|
||||
hr = device->GetId (&wid);
|
||||
if (!gst_wasapi2_result (hr)) {
|
||||
g_free (desc);
|
||||
continue;
|
||||
}
|
||||
|
||||
auto device_id = g_utf16_to_utf8 ((gunichar2 *) wid,
|
||||
-1, nullptr, nullptr, nullptr);
|
||||
CoTaskMemFree (wid);
|
||||
|
||||
ComPtr < IAudioClient > client;
|
||||
hr = device->Activate (__uuidof (IAudioClient), CLSCTX_ALL, nullptr,
|
||||
&client);
|
||||
if (!gst_wasapi2_result (hr)) {
|
||||
g_free (device_id);
|
||||
g_free (desc);
|
||||
continue;
|
||||
}
|
||||
|
||||
gst_wasapi2_enumerator_add_entry (self, client.Get (), scaps, flow, FALSE,
|
||||
device_id, desc, data->device_list);
|
||||
}
|
||||
|
||||
gst_caps_unref (scaps);
|
||||
|
||||
SetEvent (data->event);
|
||||
return G_SOURCE_REMOVE;
|
||||
}
|
||||
|
||||
void
|
||||
gst_wasapi2_enumerator_enumerate_devices (GstWasapi2Enumerator * object,
|
||||
GPtrArray * device_list)
|
||||
{
|
||||
EnumerateData data;
|
||||
|
||||
data.self = object;
|
||||
data.device_list = device_list;
|
||||
|
||||
g_main_context_invoke (object->context,
|
||||
(GSourceFunc) gst_wasapi2_enumerator_enumerate_internal, &data);
|
||||
|
||||
WaitForSingleObject (data.event, INFINITE);
|
||||
}
|
@ -0,0 +1,51 @@
|
||||
/* GStreamer
|
||||
* Copyright (C) 2025 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.
|
||||
*/
|
||||
|
||||
#pragma once
|
||||
|
||||
#include <gst/gst.h>
|
||||
#include "gstwasapi2util.h"
|
||||
|
||||
G_BEGIN_DECLS
|
||||
|
||||
#define GST_TYPE_WASAPI2_ENUMERATOR (gst_wasapi2_enumerator_get_type ())
|
||||
G_DECLARE_FINAL_TYPE (GstWasapi2Enumerator, gst_wasapi2_enumerator,
|
||||
GST, WASAPI2_ENUMERATOR, GstObject);
|
||||
|
||||
typedef struct _GstWasapi2EnumeratorEntry
|
||||
{
|
||||
gchar *device_id;
|
||||
gchar *device_name;
|
||||
gboolean is_default;
|
||||
GstCaps *caps;
|
||||
EDataFlow flow;
|
||||
} GstWasapi2EnumeratorEntry;
|
||||
|
||||
GstWasapi2Enumerator * gst_wasapi2_enumerator_new (void);
|
||||
|
||||
void gst_wasapi2_enumerator_activate_notification (GstWasapi2Enumerator * object,
|
||||
gboolean active);
|
||||
|
||||
void gst_wasapi2_enumerator_entry_free (GstWasapi2EnumeratorEntry * entry);
|
||||
|
||||
void gst_wasapi2_enumerator_enumerate_devices (GstWasapi2Enumerator * object,
|
||||
GPtrArray * entry);
|
||||
|
||||
G_END_DECLS
|
||||
|
@ -27,6 +27,8 @@
|
||||
#include <audioclient.h>
|
||||
#include <mmdeviceapi.h>
|
||||
#include <winternl.h>
|
||||
#include <mutex>
|
||||
#include <string.h>
|
||||
|
||||
GST_DEBUG_CATEGORY_EXTERN (gst_wasapi2_debug);
|
||||
#define GST_CAT_DEFAULT gst_wasapi2_debug
|
||||
@ -320,7 +322,7 @@ gst_wasapi2_util_waveformatex_to_channel_mask (WAVEFORMATEX * format,
|
||||
|
||||
/* 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);
|
||||
GST_LOG ("Got too many (%i) channels, assuming non-positional", nChannels);
|
||||
goto out;
|
||||
}
|
||||
|
||||
@ -344,7 +346,7 @@ gst_wasapi2_util_waveformatex_to_channel_mask (WAVEFORMATEX * format,
|
||||
|
||||
/* XXX: Warn if some channel masks couldn't be mapped? */
|
||||
|
||||
GST_DEBUG ("Converted WASAPI mask 0x%" G_GINT64_MODIFIER "x -> 0x%"
|
||||
GST_TRACE ("Converted WASAPI mask 0x%" G_GINT64_MODIFIER "x -> 0x%"
|
||||
G_GINT64_MODIFIER "x", (guint64) dwChannelMask, (guint64) mask);
|
||||
|
||||
out:
|
||||
@ -377,10 +379,10 @@ gst_wasapi2_util_waveformatex_to_audio_format (WAVEFORMATEX * format)
|
||||
case WAVE_FORMAT_EXTENSIBLE:
|
||||
{
|
||||
WAVEFORMATEXTENSIBLE *ex = (WAVEFORMATEXTENSIBLE *) format;
|
||||
if (IsEqualGUID (&ex->SubFormat, &KSDATAFORMAT_SUBTYPE_PCM)) {
|
||||
if (IsEqualGUID (ex->SubFormat, KSDATAFORMAT_SUBTYPE_PCM)) {
|
||||
fmt = gst_audio_format_build_integer (TRUE, G_LITTLE_ENDIAN,
|
||||
format->wBitsPerSample, ex->Samples.wValidBitsPerSample);
|
||||
} else if (IsEqualGUID (&ex->SubFormat, &KSDATAFORMAT_SUBTYPE_IEEE_FLOAT)) {
|
||||
} else if (IsEqualGUID (ex->SubFormat, KSDATAFORMAT_SUBTYPE_IEEE_FLOAT)) {
|
||||
if (format->wBitsPerSample == 32
|
||||
&& ex->Samples.wValidBitsPerSample == 32)
|
||||
fmt = GST_AUDIO_FORMAT_F32LE;
|
||||
@ -453,9 +455,8 @@ gst_wasapi2_can_automatic_stream_routing (void)
|
||||
return TRUE;
|
||||
#else
|
||||
static gboolean ret = FALSE;
|
||||
static gsize version_once = 0;
|
||||
|
||||
if (g_once_init_enter (&version_once)) {
|
||||
GST_WASAPI2_CALL_ONCE_BEGIN {
|
||||
OSVERSIONINFOEXW osverinfo;
|
||||
typedef NTSTATUS (WINAPI fRtlGetVersion) (PRTL_OSVERSIONINFOEXW);
|
||||
fRtlGetVersion *RtlGetVersion = NULL;
|
||||
@ -481,11 +482,10 @@ gst_wasapi2_can_automatic_stream_routing (void)
|
||||
|
||||
if (hmodule)
|
||||
FreeLibrary (hmodule);
|
||||
|
||||
g_once_init_leave (&version_once, 1);
|
||||
}
|
||||
GST_WASAPI2_CALL_ONCE_END;
|
||||
|
||||
GST_INFO ("Automatic stream routing support: %d", ret);
|
||||
GST_TRACE ("Automatic stream routing support: %d", ret);
|
||||
|
||||
return ret;
|
||||
#endif
|
||||
@ -500,9 +500,8 @@ gst_wasapi2_can_process_loopback (void)
|
||||
return FALSE;
|
||||
#else
|
||||
static gboolean ret = FALSE;
|
||||
static gsize version_once = 0;
|
||||
|
||||
if (g_once_init_enter (&version_once)) {
|
||||
GST_WASAPI2_CALL_ONCE_BEGIN {
|
||||
OSVERSIONINFOEXW osverinfo;
|
||||
typedef NTSTATUS (WINAPI fRtlGetVersion) (PRTL_OSVERSIONINFOEXW);
|
||||
fRtlGetVersion *RtlGetVersion = NULL;
|
||||
@ -535,9 +534,8 @@ gst_wasapi2_can_process_loopback (void)
|
||||
|
||||
if (hmodule)
|
||||
FreeLibrary (hmodule);
|
||||
|
||||
g_once_init_leave (&version_once, 1);
|
||||
}
|
||||
GST_WASAPI2_CALL_ONCE_END;
|
||||
|
||||
GST_INFO ("Process loopback support: %d", ret);
|
||||
|
||||
@ -552,7 +550,7 @@ gst_wasapi2_get_default_mix_format (void)
|
||||
|
||||
/* virtual loopback device might not provide mix format. Create our default
|
||||
* mix format */
|
||||
format = CoTaskMemAlloc (sizeof (WAVEFORMATEX));
|
||||
format = (WAVEFORMATEX *) CoTaskMemAlloc (sizeof (WAVEFORMATEX));
|
||||
format->wFormatTag = WAVE_FORMAT_PCM;
|
||||
format->nChannels = 2;
|
||||
format->nSamplesPerSec = 44100;
|
||||
@ -562,3 +560,46 @@ gst_wasapi2_get_default_mix_format (void)
|
||||
|
||||
return format;
|
||||
}
|
||||
|
||||
const wchar_t *
|
||||
gst_wasapi2_get_default_device_id_wide (EDataFlow flow)
|
||||
{
|
||||
static wchar_t *capture = nullptr;
|
||||
static wchar_t *render = nullptr;
|
||||
|
||||
GST_WASAPI2_CALL_ONCE_BEGIN {
|
||||
StringFromIID (DEVINTERFACE_AUDIO_CAPTURE, &capture);
|
||||
StringFromIID (DEVINTERFACE_AUDIO_RENDER, &render);
|
||||
} GST_WASAPI2_CALL_ONCE_END;
|
||||
|
||||
if (flow == eCapture)
|
||||
return (const wchar_t *) capture;
|
||||
|
||||
return (const wchar_t *) render;
|
||||
}
|
||||
|
||||
const char *
|
||||
gst_wasapi2_get_default_device_id (EDataFlow flow)
|
||||
{
|
||||
static char *capture = nullptr;
|
||||
static char *render = nullptr;
|
||||
|
||||
GST_WASAPI2_CALL_ONCE_BEGIN {
|
||||
auto wstr = gst_wasapi2_get_default_device_id_wide (eCapture);
|
||||
if (wstr) {
|
||||
capture = g_utf16_to_utf8 ((const gunichar2 *) wstr,
|
||||
-1, nullptr, nullptr, nullptr);
|
||||
}
|
||||
|
||||
wstr = gst_wasapi2_get_default_device_id_wide (eRender);
|
||||
if (wstr) {
|
||||
render = g_utf16_to_utf8 ((const gunichar2 *) wstr,
|
||||
-1, nullptr, nullptr, nullptr);
|
||||
}
|
||||
} GST_WASAPI2_CALL_ONCE_END;
|
||||
|
||||
if (flow == eCapture)
|
||||
return (const char *) capture;
|
||||
|
||||
return (const char *) render;
|
||||
}
|
@ -26,6 +26,8 @@
|
||||
#include <initguid.h>
|
||||
#include <audioclient.h>
|
||||
#include <endpointvolume.h>
|
||||
#include <string.h>
|
||||
#include <mmdeviceapi.h>
|
||||
|
||||
G_BEGIN_DECLS
|
||||
|
||||
@ -57,6 +59,42 @@ gboolean _gst_wasapi2_result (HRESULT hr,
|
||||
_gst_wasapi2_result (result, NULL, __FILE__, GST_FUNCTION, __LINE__)
|
||||
#endif
|
||||
|
||||
typedef enum
|
||||
{
|
||||
GST_WASAPI2_ENDPOINT_CLASS_CAPTURE = 0,
|
||||
GST_WASAPI2_ENDPOINT_CLASS_RENDER,
|
||||
GST_WASAPI2_ENDPOINT_CLASS_LOOPBACK_CAPTURE,
|
||||
GST_WASAPI2_ENDPOINT_CLASS_INCLUDE_PROCESS_LOOPBACK_CAPTURE,
|
||||
GST_WASAPI2_ENDPOINT_CLASS_EXCLUDE_PROCESS_LOOPBACK_CAPTURE,
|
||||
} GstWasapi2EndpointClass;
|
||||
|
||||
static inline gboolean
|
||||
gst_wasapi2_is_loopback_class (GstWasapi2EndpointClass device_class)
|
||||
{
|
||||
switch (device_class) {
|
||||
case GST_WASAPI2_ENDPOINT_CLASS_LOOPBACK_CAPTURE:
|
||||
return TRUE;
|
||||
default:
|
||||
break;
|
||||
}
|
||||
|
||||
return FALSE;
|
||||
}
|
||||
|
||||
static inline gboolean
|
||||
gst_wasapi2_is_process_loopback_class (GstWasapi2EndpointClass device_class)
|
||||
{
|
||||
switch (device_class) {
|
||||
case GST_WASAPI2_ENDPOINT_CLASS_INCLUDE_PROCESS_LOOPBACK_CAPTURE:
|
||||
case GST_WASAPI2_ENDPOINT_CLASS_EXCLUDE_PROCESS_LOOPBACK_CAPTURE:
|
||||
return TRUE;
|
||||
default:
|
||||
break;
|
||||
}
|
||||
|
||||
return FALSE;
|
||||
}
|
||||
|
||||
guint64 gst_wasapi2_util_waveformatex_to_channel_mask (WAVEFORMATEX * format,
|
||||
GstAudioChannelPosition ** out_position);
|
||||
|
||||
@ -75,6 +113,20 @@ gboolean gst_wasapi2_can_process_loopback (void);
|
||||
|
||||
WAVEFORMATEX * gst_wasapi2_get_default_mix_format (void);
|
||||
|
||||
const wchar_t * gst_wasapi2_get_default_device_id_wide (EDataFlow flow);
|
||||
|
||||
const char * gst_wasapi2_get_default_device_id (EDataFlow flow);
|
||||
|
||||
G_END_DECLS
|
||||
|
||||
#ifdef __cplusplus
|
||||
#include <mutex>
|
||||
|
||||
#define GST_WASAPI2_CALL_ONCE_BEGIN \
|
||||
static std::once_flag __once_flag; \
|
||||
std::call_once (__once_flag, [&]()
|
||||
|
||||
#define GST_WASAPI2_CALL_ONCE_END )
|
||||
#endif
|
||||
|
||||
#endif /* __GST_WASAPI_UTIL_H__ */
|
||||
|
@ -1,11 +1,13 @@
|
||||
wasapi2_sources = [
|
||||
'gstwasapi2src.c',
|
||||
'gstwasapi2sink.c',
|
||||
'gstwasapi2util.c',
|
||||
'gstwasapi2util.cpp',
|
||||
'gstwasapi2client.cpp',
|
||||
'gstwasapi2device.c',
|
||||
'gstwasapi2device.cpp',
|
||||
'gstwasapi2ringbuffer.cpp',
|
||||
'plugin.c',
|
||||
'gstwasapi2activator.cpp',
|
||||
'gstwasapi2enumerator.cpp',
|
||||
'plugin.cpp',
|
||||
]
|
||||
|
||||
wasapi2_headers = [
|
||||
@ -15,6 +17,7 @@ wasapi2_headers = [
|
||||
'gstwasapi2src.h',
|
||||
'gstwasapi2sink.h',
|
||||
'gstwasapi2client.h',
|
||||
'gstwasapi2object.h',
|
||||
'AsyncOperations.h',
|
||||
]
|
||||
|
||||
@ -163,6 +166,7 @@ gstwasapi2 = library('gstwasapi2',
|
||||
cpp_args : gst_plugins_bad_args + extra_args + extra_cpp_args,
|
||||
include_directories : [configinc],
|
||||
dependencies : [gstaudio_dep, gstwinrt_dep] + wasapi2_dep,
|
||||
override_options : ['cpp_std=c++14'],
|
||||
install : true,
|
||||
install_dir : plugins_install_dir)
|
||||
plugins += [gstwasapi2]
|
||||
|
@ -41,7 +41,7 @@ plugin_deinit (gpointer data)
|
||||
static gboolean
|
||||
plugin_init (GstPlugin * plugin)
|
||||
{
|
||||
GstRank rank = GST_RANK_PRIMARY + 1;
|
||||
guint rank = GST_RANK_PRIMARY + 1;
|
||||
HRESULT hr;
|
||||
|
||||
/**
|
||||
@ -52,7 +52,7 @@ plugin_init (GstPlugin * plugin)
|
||||
|
||||
hr = MFStartup (MF_VERSION, MFSTARTUP_NOSOCKET);
|
||||
if (!gst_wasapi2_result (hr)) {
|
||||
GST_WARNING ("MFStartup failure, hr: 0x%x", hr);
|
||||
GST_WARNING ("MFStartup failure, hr: 0x%x", (guint) hr);
|
||||
return TRUE;
|
||||
}
|
||||
|
||||
@ -63,10 +63,11 @@ plugin_init (GstPlugin * plugin)
|
||||
gst_element_register (plugin, "wasapi2sink", rank, GST_TYPE_WASAPI2_SINK);
|
||||
gst_element_register (plugin, "wasapi2src", rank, GST_TYPE_WASAPI2_SRC);
|
||||
|
||||
gst_wasapi2_device_provider_register (plugin, rank);
|
||||
gst_device_provider_register (plugin, "wasapi2deviceprovider", rank,
|
||||
GST_TYPE_WASAPI2_DEVICE_PROVIDER);
|
||||
|
||||
g_object_set_data_full (G_OBJECT (plugin),
|
||||
"plugin-wasapi2-shutdown", "shutdown-data",
|
||||
"plugin-wasapi2-shutdown", (gpointer) "shutdown-data",
|
||||
(GDestroyNotify) plugin_deinit);
|
||||
|
||||
return TRUE;
|
Loading…
x
Reference in New Issue
Block a user