mirror of
https://gitlab.freedesktop.org/gstreamer/gstreamer.git
synced 2025-06-07 07:58:51 +00:00
wasapi2: Add support for process loopback capture
Adding loopback capture mode for specified PID. Note that this feature requires Windows 10 build 20348 (Windows 11/Windows Server 2022 or later), and any process loopback related properties will not be exposed if OS does not support it. Example launch lines: * wasapi2src loopback-mode=include-process-tree loopback-target-pid=<PID> Captures audio generated by an application (specified by PID) and its child process * wasapi2src loopback-mode=exclude-process-tree loopback-target-pid=<PID> Captures desktop audio excluding PID and its child process Fixes: https://gitlab.freedesktop.org/gstreamer/gstreamer/-/issues/1278 Part-of: <https://gitlab.freedesktop.org/gstreamer/gstreamer/-/merge_requests/3195>
This commit is contained in:
parent
2c3f9d4587
commit
cb7958e710
10 changed files with 491 additions and 73 deletions
|
@ -230073,6 +230073,32 @@
|
||||||
"type": "gboolean",
|
"type": "gboolean",
|
||||||
"writable": true
|
"writable": true
|
||||||
},
|
},
|
||||||
|
"loopback-mode": {
|
||||||
|
"blurb": "Loopback mode to use",
|
||||||
|
"conditionally-available": true,
|
||||||
|
"construct": false,
|
||||||
|
"construct-only": false,
|
||||||
|
"controllable": false,
|
||||||
|
"default": "default (0)",
|
||||||
|
"mutable": "ready",
|
||||||
|
"readable": true,
|
||||||
|
"type": "GstWasapi2SrcLoopbackMode",
|
||||||
|
"writable": true
|
||||||
|
},
|
||||||
|
"loopback-target-pid": {
|
||||||
|
"blurb": "Process ID to be recorded or excluded for process loopback mode",
|
||||||
|
"conditionally-available": true,
|
||||||
|
"construct": false,
|
||||||
|
"construct-only": false,
|
||||||
|
"controllable": false,
|
||||||
|
"default": "0",
|
||||||
|
"max": "-1",
|
||||||
|
"min": "0",
|
||||||
|
"mutable": "ready",
|
||||||
|
"readable": true,
|
||||||
|
"type": "guint",
|
||||||
|
"writable": true
|
||||||
|
},
|
||||||
"low-latency": {
|
"low-latency": {
|
||||||
"blurb": "Optimize all settings for lowest latency. Always safe to enable.",
|
"blurb": "Optimize all settings for lowest latency. Always safe to enable.",
|
||||||
"conditionally-available": false,
|
"conditionally-available": false,
|
||||||
|
@ -230117,7 +230143,28 @@
|
||||||
},
|
},
|
||||||
"filename": "gstwasapi2",
|
"filename": "gstwasapi2",
|
||||||
"license": "LGPL",
|
"license": "LGPL",
|
||||||
"other-types": {},
|
"other-types": {
|
||||||
|
"GstWasapi2SrcLoopbackMode": {
|
||||||
|
"kind": "enum",
|
||||||
|
"values": [
|
||||||
|
{
|
||||||
|
"desc": "Default",
|
||||||
|
"name": "default",
|
||||||
|
"value": "0"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"desc": "Include process and its child processes",
|
||||||
|
"name": "include-process-tree",
|
||||||
|
"value": "1"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"desc": "Exclude process and its child processes",
|
||||||
|
"name": "exclude-process-tree",
|
||||||
|
"value": "2"
|
||||||
|
}
|
||||||
|
]
|
||||||
|
}
|
||||||
|
},
|
||||||
"package": "GStreamer Bad Plug-ins",
|
"package": "GStreamer Bad Plug-ins",
|
||||||
"source": "gst-plugins-bad",
|
"source": "gst-plugins-bad",
|
||||||
"tracers": {},
|
"tracers": {},
|
||||||
|
|
|
@ -52,6 +52,37 @@ using namespace ABI::Windows::Devices::Enumeration;
|
||||||
using namespace Microsoft::WRL;
|
using namespace Microsoft::WRL;
|
||||||
using namespace Microsoft::WRL::Wrappers;
|
using namespace Microsoft::WRL::Wrappers;
|
||||||
|
|
||||||
|
/* Copy of audioclientactivationparams.h since those types are defined only for
|
||||||
|
* NTDDI_VERSION >= NTDDI_WIN10_FE */
|
||||||
|
#define GST_VIRTUAL_AUDIO_DEVICE_PROCESS_LOOPBACK L"VAD\\Process_Loopback"
|
||||||
|
typedef enum
|
||||||
|
{
|
||||||
|
GST_PROCESS_LOOPBACK_MODE_INCLUDE_TARGET_PROCESS_TREE = 0,
|
||||||
|
GST_PROCESS_LOOPBACK_MODE_EXCLUDE_TARGET_PROCESS_TREE = 1
|
||||||
|
} GST_PROCESS_LOOPBACK_MODE;
|
||||||
|
|
||||||
|
typedef struct
|
||||||
|
{
|
||||||
|
DWORD TargetProcessId;
|
||||||
|
GST_PROCESS_LOOPBACK_MODE ProcessLoopbackMode;
|
||||||
|
} GST_AUDIOCLIENT_PROCESS_LOOPBACK_PARAMS;
|
||||||
|
|
||||||
|
typedef enum
|
||||||
|
{
|
||||||
|
GST_AUDIOCLIENT_ACTIVATION_TYPE_DEFAULT = 0,
|
||||||
|
GST_AUDIOCLIENT_ACTIVATION_TYPE_PROCESS_LOOPBACK = 1
|
||||||
|
} GST_AUDIOCLIENT_ACTIVATION_TYPE;
|
||||||
|
|
||||||
|
typedef struct
|
||||||
|
{
|
||||||
|
GST_AUDIOCLIENT_ACTIVATION_TYPE ActivationType;
|
||||||
|
union
|
||||||
|
{
|
||||||
|
GST_AUDIOCLIENT_PROCESS_LOOPBACK_PARAMS ProcessLoopbackParams;
|
||||||
|
} DUMMYUNIONNAME;
|
||||||
|
} GST_AUDIOCLIENT_ACTIVATION_PARAMS;
|
||||||
|
/* End of audioclientactivationparams.h */
|
||||||
|
|
||||||
G_BEGIN_DECLS
|
G_BEGIN_DECLS
|
||||||
|
|
||||||
GST_DEBUG_CATEGORY_EXTERN (gst_wasapi2_client_debug);
|
GST_DEBUG_CATEGORY_EXTERN (gst_wasapi2_client_debug);
|
||||||
|
@ -152,19 +183,29 @@ public:
|
||||||
}
|
}
|
||||||
|
|
||||||
HRESULT
|
HRESULT
|
||||||
ActivateDeviceAsync(const std::wstring &device_id)
|
ActivateDeviceAsync(const std::wstring &device_id,
|
||||||
|
GST_AUDIOCLIENT_ACTIVATION_PARAMS * params)
|
||||||
{
|
{
|
||||||
ComPtr<IAsyncAction> async_action;
|
ComPtr<IAsyncAction> async_action;
|
||||||
bool run_async = false;
|
bool run_async = false;
|
||||||
HRESULT hr;
|
HRESULT hr;
|
||||||
|
|
||||||
auto work_item = Callback<Implements<RuntimeClassFlags<ClassicCom>,
|
auto work_item = Callback<Implements<RuntimeClassFlags<ClassicCom>,
|
||||||
IDispatchedHandler, FtmBase>>([this, device_id]{
|
IDispatchedHandler, FtmBase>>([this, device_id, params]{
|
||||||
ComPtr<IActivateAudioInterfaceAsyncOperation> async_op;
|
ComPtr<IActivateAudioInterfaceAsyncOperation> async_op;
|
||||||
HRESULT async_hr = S_OK;
|
HRESULT async_hr = S_OK;
|
||||||
|
PROPVARIANT activate_params = {};
|
||||||
|
if (params) {
|
||||||
|
activate_params.vt = VT_BLOB;
|
||||||
|
activate_params.blob.cbSize = sizeof(GST_AUDIOCLIENT_ACTIVATION_PARAMS);
|
||||||
|
activate_params.blob.pBlobData = (BYTE *) params;
|
||||||
|
|
||||||
|
async_hr = ActivateAudioInterfaceAsync (device_id.c_str (),
|
||||||
|
__uuidof(IAudioClient), &activate_params, this, &async_op);
|
||||||
|
} else {
|
||||||
async_hr = ActivateAudioInterfaceAsync (device_id.c_str (),
|
async_hr = ActivateAudioInterfaceAsync (device_id.c_str (),
|
||||||
__uuidof(IAudioClient), nullptr, this, &async_op);
|
__uuidof(IAudioClient), nullptr, this, &async_op);
|
||||||
|
}
|
||||||
|
|
||||||
/* for debugging */
|
/* for debugging */
|
||||||
gst_wasapi2_result (async_hr);
|
gst_wasapi2_result (async_hr);
|
||||||
|
@ -227,6 +268,7 @@ enum
|
||||||
PROP_DEVICE_CLASS,
|
PROP_DEVICE_CLASS,
|
||||||
PROP_DISPATCHER,
|
PROP_DISPATCHER,
|
||||||
PROP_CAN_AUTO_ROUTING,
|
PROP_CAN_AUTO_ROUTING,
|
||||||
|
PROP_LOOPBACK_TARGET_PID,
|
||||||
};
|
};
|
||||||
|
|
||||||
#define DEFAULT_DEVICE_INDEX -1
|
#define DEFAULT_DEVICE_INDEX -1
|
||||||
|
@ -242,6 +284,7 @@ struct _GstWasapi2Client
|
||||||
gint device_index;
|
gint device_index;
|
||||||
gpointer dispatcher;
|
gpointer dispatcher;
|
||||||
gboolean can_auto_routing;
|
gboolean can_auto_routing;
|
||||||
|
guint target_pid;
|
||||||
|
|
||||||
IAudioClient *audio_client;
|
IAudioClient *audio_client;
|
||||||
GstWasapiDeviceActivator *activator;
|
GstWasapiDeviceActivator *activator;
|
||||||
|
@ -269,6 +312,12 @@ gst_wasapi2_client_device_class_get_type (void)
|
||||||
{GST_WASAPI2_CLIENT_DEVICE_CLASS_RENDER, "Render", "render"},
|
{GST_WASAPI2_CLIENT_DEVICE_CLASS_RENDER, "Render", "render"},
|
||||||
{GST_WASAPI2_CLIENT_DEVICE_CLASS_LOOPBACK_CAPTURE, "Loopback-Capture",
|
{GST_WASAPI2_CLIENT_DEVICE_CLASS_LOOPBACK_CAPTURE, "Loopback-Capture",
|
||||||
"loopback-capture"},
|
"loopback-capture"},
|
||||||
|
{GST_WASAPI2_CLIENT_DEVICE_CLASS_INCLUDE_PROCESS_LOOPBACK_CAPTURE,
|
||||||
|
"Include-Process-Loopback-Capture",
|
||||||
|
"include-process-loopback-capture"},
|
||||||
|
{GST_WASAPI2_CLIENT_DEVICE_CLASS_EXCLUDE_PROCESS_LOOPBACK_CAPTURE,
|
||||||
|
"Exclude-Process-Loopback-Capture",
|
||||||
|
"exclude-process-loopback-capture"},
|
||||||
{0, nullptr, nullptr}
|
{0, nullptr, nullptr}
|
||||||
};
|
};
|
||||||
|
|
||||||
|
@ -328,6 +377,9 @@ gst_wasapi2_client_class_init (GstWasapi2ClientClass * klass)
|
||||||
g_param_spec_boolean ("auto-routing", "Auto Routing",
|
g_param_spec_boolean ("auto-routing", "Auto Routing",
|
||||||
"Whether client can support automatic stream routing", FALSE,
|
"Whether client can support automatic stream routing", FALSE,
|
||||||
(GParamFlags) (G_PARAM_READWRITE | G_PARAM_STATIC_STRINGS)));
|
(GParamFlags) (G_PARAM_READWRITE | G_PARAM_STATIC_STRINGS)));
|
||||||
|
g_object_class_install_property (gobject_class, PROP_LOOPBACK_TARGET_PID,
|
||||||
|
g_param_spec_uint ("loopback-target-pid", "Loopback Target PID",
|
||||||
|
"Target process id to record", 0, G_MAXUINT32, 0, param_flags));
|
||||||
}
|
}
|
||||||
|
|
||||||
static void
|
static void
|
||||||
|
@ -425,6 +477,9 @@ gst_wasapi2_client_get_property (GObject * object, guint prop_id,
|
||||||
case PROP_CAN_AUTO_ROUTING:
|
case PROP_CAN_AUTO_ROUTING:
|
||||||
g_value_set_boolean (value, self->can_auto_routing);
|
g_value_set_boolean (value, self->can_auto_routing);
|
||||||
break;
|
break;
|
||||||
|
case PROP_LOOPBACK_TARGET_PID:
|
||||||
|
g_value_set_uint (value, self->target_pid);
|
||||||
|
break;
|
||||||
default:
|
default:
|
||||||
G_OBJECT_WARN_INVALID_PROPERTY_ID (object, prop_id, pspec);
|
G_OBJECT_WARN_INVALID_PROPERTY_ID (object, prop_id, pspec);
|
||||||
break;
|
break;
|
||||||
|
@ -456,6 +511,9 @@ gst_wasapi2_client_set_property (GObject * object, guint prop_id,
|
||||||
case PROP_DISPATCHER:
|
case PROP_DISPATCHER:
|
||||||
self->dispatcher = g_value_get_pointer (value);
|
self->dispatcher = g_value_get_pointer (value);
|
||||||
break;
|
break;
|
||||||
|
case PROP_LOOPBACK_TARGET_PID:
|
||||||
|
self->target_pid = g_value_get_uint (value);
|
||||||
|
break;
|
||||||
default:
|
default:
|
||||||
G_OBJECT_WARN_INVALID_PROPERTY_ID (object, prop_id, pspec);
|
G_OBJECT_WARN_INVALID_PROPERTY_ID (object, prop_id, pspec);
|
||||||
break;
|
break;
|
||||||
|
@ -559,6 +617,46 @@ gst_wasapi2_client_activate_async (GstWasapi2Client * self,
|
||||||
std::string target_device_id;
|
std::string target_device_id;
|
||||||
std::string target_device_name;
|
std::string target_device_name;
|
||||||
gboolean use_default_device = FALSE;
|
gboolean use_default_device = FALSE;
|
||||||
|
GST_AUDIOCLIENT_ACTIVATION_PARAMS activation_params;
|
||||||
|
gboolean process_loopback = FALSE;
|
||||||
|
|
||||||
|
memset (&activation_params, 0, sizeof (GST_AUDIOCLIENT_ACTIVATION_PARAMS));
|
||||||
|
activation_params.ActivationType = GST_AUDIOCLIENT_ACTIVATION_TYPE_DEFAULT;
|
||||||
|
|
||||||
|
if (self->device_class ==
|
||||||
|
GST_WASAPI2_CLIENT_DEVICE_CLASS_INCLUDE_PROCESS_LOOPBACK_CAPTURE ||
|
||||||
|
self->device_class ==
|
||||||
|
GST_WASAPI2_CLIENT_DEVICE_CLASS_EXCLUDE_PROCESS_LOOPBACK_CAPTURE) {
|
||||||
|
if (self->target_pid == 0) {
|
||||||
|
GST_ERROR_OBJECT (self, "Process loopback mode without PID");
|
||||||
|
goto failed;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (!gst_wasapi2_can_process_loopback ()) {
|
||||||
|
GST_ERROR_OBJECT (self, "Process loopback is not supported");
|
||||||
|
goto failed;
|
||||||
|
}
|
||||||
|
|
||||||
|
process_loopback = TRUE;
|
||||||
|
activation_params.ActivationType =
|
||||||
|
GST_AUDIOCLIENT_ACTIVATION_TYPE_PROCESS_LOOPBACK;
|
||||||
|
activation_params.ProcessLoopbackParams.TargetProcessId =
|
||||||
|
(DWORD) self->target_pid;
|
||||||
|
target_device_id_wstring = GST_VIRTUAL_AUDIO_DEVICE_PROCESS_LOOPBACK;
|
||||||
|
target_device_id = convert_wstring_to_string (target_device_id_wstring);
|
||||||
|
|
||||||
|
if (self->device_class ==
|
||||||
|
GST_WASAPI2_CLIENT_DEVICE_CLASS_INCLUDE_PROCESS_LOOPBACK_CAPTURE) {
|
||||||
|
activation_params.ProcessLoopbackParams.ProcessLoopbackMode =
|
||||||
|
GST_PROCESS_LOOPBACK_MODE_INCLUDE_TARGET_PROCESS_TREE;
|
||||||
|
} else {
|
||||||
|
activation_params.ProcessLoopbackParams.ProcessLoopbackMode =
|
||||||
|
GST_PROCESS_LOOPBACK_MODE_EXCLUDE_TARGET_PROCESS_TREE;
|
||||||
|
}
|
||||||
|
|
||||||
|
target_device_name = "Process-loopback";
|
||||||
|
goto activate;
|
||||||
|
}
|
||||||
|
|
||||||
GST_INFO_OBJECT (self,
|
GST_INFO_OBJECT (self,
|
||||||
"requested device info, device-class: %s, device: %s, device-index: %d",
|
"requested device info, device-class: %s, device: %s, device-index: %d",
|
||||||
|
@ -773,7 +871,13 @@ activate:
|
||||||
/* default device supports automatic stream routing */
|
/* default device supports automatic stream routing */
|
||||||
self->can_auto_routing = use_default_device;
|
self->can_auto_routing = use_default_device;
|
||||||
|
|
||||||
hr = activator->ActivateDeviceAsync (target_device_id_wstring);
|
if (process_loopback) {
|
||||||
|
hr = activator->ActivateDeviceAsync (target_device_id_wstring,
|
||||||
|
&activation_params);
|
||||||
|
} else {
|
||||||
|
hr = activator->ActivateDeviceAsync (target_device_id_wstring, nullptr);
|
||||||
|
}
|
||||||
|
|
||||||
if (!gst_wasapi2_result (hr)) {
|
if (!gst_wasapi2_result (hr)) {
|
||||||
GST_WARNING_OBJECT (self, "Failed to activate device");
|
GST_WARNING_OBJECT (self, "Failed to activate device");
|
||||||
goto failed;
|
goto failed;
|
||||||
|
@ -886,9 +990,13 @@ gst_wasapi2_client_get_caps (GstWasapi2Client * client)
|
||||||
|
|
||||||
hr = client->audio_client->GetMixFormat (&mix_format);
|
hr = client->audio_client->GetMixFormat (&mix_format);
|
||||||
if (!gst_wasapi2_result (hr)) {
|
if (!gst_wasapi2_result (hr)) {
|
||||||
|
if (gst_wasapi2_device_class_is_process_loopback (client->device_class)) {
|
||||||
|
mix_format = gst_wasapi2_get_default_mix_format ();
|
||||||
|
} else {
|
||||||
GST_WARNING_OBJECT (client, "Failed to get mix format");
|
GST_WARNING_OBJECT (client, "Failed to get mix format");
|
||||||
return nullptr;
|
return nullptr;
|
||||||
}
|
}
|
||||||
|
}
|
||||||
|
|
||||||
scaps = gst_static_caps_get (&static_caps);
|
scaps = gst_static_caps_get (&static_caps);
|
||||||
gst_wasapi2_util_parse_waveformatex (mix_format,
|
gst_wasapi2_util_parse_waveformatex (mix_format,
|
||||||
|
@ -950,7 +1058,8 @@ find_dispatcher (ICoreDispatcher ** dispatcher)
|
||||||
|
|
||||||
GstWasapi2Client *
|
GstWasapi2Client *
|
||||||
gst_wasapi2_client_new (GstWasapi2ClientDeviceClass device_class,
|
gst_wasapi2_client_new (GstWasapi2ClientDeviceClass device_class,
|
||||||
gint device_index, const gchar * device_id, gpointer dispatcher)
|
gint device_index, const gchar * device_id, guint32 target_pid,
|
||||||
|
gpointer dispatcher)
|
||||||
{
|
{
|
||||||
GstWasapi2Client *self;
|
GstWasapi2Client *self;
|
||||||
/* *INDENT-OFF* */
|
/* *INDENT-OFF* */
|
||||||
|
@ -977,7 +1086,8 @@ gst_wasapi2_client_new (GstWasapi2ClientDeviceClass device_class,
|
||||||
|
|
||||||
self = (GstWasapi2Client *) g_object_new (GST_TYPE_WASAPI2_CLIENT,
|
self = (GstWasapi2Client *) g_object_new (GST_TYPE_WASAPI2_CLIENT,
|
||||||
"device-class", device_class, "device-index", device_index,
|
"device-class", device_class, "device-index", device_index,
|
||||||
"device", device_id, "dispatcher", dispatcher, nullptr);
|
"device", device_id, "loopback-target-pid", target_pid,
|
||||||
|
"dispatcher", dispatcher, nullptr);
|
||||||
|
|
||||||
/* Reset explicitly to ensure that it happens before
|
/* Reset explicitly to ensure that it happens before
|
||||||
* RoInitializeWrapper dtor is called */
|
* RoInitializeWrapper dtor is called */
|
||||||
|
|
|
@ -31,8 +31,37 @@ typedef enum
|
||||||
GST_WASAPI2_CLIENT_DEVICE_CLASS_CAPTURE = 0,
|
GST_WASAPI2_CLIENT_DEVICE_CLASS_CAPTURE = 0,
|
||||||
GST_WASAPI2_CLIENT_DEVICE_CLASS_RENDER,
|
GST_WASAPI2_CLIENT_DEVICE_CLASS_RENDER,
|
||||||
GST_WASAPI2_CLIENT_DEVICE_CLASS_LOOPBACK_CAPTURE,
|
GST_WASAPI2_CLIENT_DEVICE_CLASS_LOOPBACK_CAPTURE,
|
||||||
|
GST_WASAPI2_CLIENT_DEVICE_CLASS_INCLUDE_PROCESS_LOOPBACK_CAPTURE,
|
||||||
|
GST_WASAPI2_CLIENT_DEVICE_CLASS_EXCLUDE_PROCESS_LOOPBACK_CAPTURE,
|
||||||
} GstWasapi2ClientDeviceClass;
|
} GstWasapi2ClientDeviceClass;
|
||||||
|
|
||||||
|
static inline gboolean
|
||||||
|
gst_wasapi2_device_class_is_loopback (GstWasapi2ClientDeviceClass device_class)
|
||||||
|
{
|
||||||
|
switch (device_class) {
|
||||||
|
case GST_WASAPI2_CLIENT_DEVICE_CLASS_LOOPBACK_CAPTURE:
|
||||||
|
return TRUE;
|
||||||
|
default:
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
|
||||||
|
return FALSE;
|
||||||
|
}
|
||||||
|
|
||||||
|
static inline gboolean
|
||||||
|
gst_wasapi2_device_class_is_process_loopback (GstWasapi2ClientDeviceClass device_class)
|
||||||
|
{
|
||||||
|
switch (device_class) {
|
||||||
|
case GST_WASAPI2_CLIENT_DEVICE_CLASS_INCLUDE_PROCESS_LOOPBACK_CAPTURE:
|
||||||
|
case GST_WASAPI2_CLIENT_DEVICE_CLASS_EXCLUDE_PROCESS_LOOPBACK_CAPTURE:
|
||||||
|
return TRUE;
|
||||||
|
default:
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
|
||||||
|
return FALSE;
|
||||||
|
}
|
||||||
|
|
||||||
#define GST_TYPE_WASAPI2_CLIENT_DEVICE_CLASS (gst_wasapi2_client_device_class_get_type())
|
#define GST_TYPE_WASAPI2_CLIENT_DEVICE_CLASS (gst_wasapi2_client_device_class_get_type())
|
||||||
GType gst_wasapi2_client_device_class_get_type (void);
|
GType gst_wasapi2_client_device_class_get_type (void);
|
||||||
|
|
||||||
|
@ -43,6 +72,7 @@ G_DECLARE_FINAL_TYPE (GstWasapi2Client,
|
||||||
GstWasapi2Client * gst_wasapi2_client_new (GstWasapi2ClientDeviceClass device_class,
|
GstWasapi2Client * gst_wasapi2_client_new (GstWasapi2ClientDeviceClass device_class,
|
||||||
gint device_index,
|
gint device_index,
|
||||||
const gchar * device_id,
|
const gchar * device_id,
|
||||||
|
guint target_pid,
|
||||||
gpointer dispatcher);
|
gpointer dispatcher);
|
||||||
|
|
||||||
gboolean gst_wasapi2_client_ensure_activation (GstWasapi2Client * client);
|
gboolean gst_wasapi2_client_ensure_activation (GstWasapi2Client * client);
|
||||||
|
|
|
@ -283,7 +283,7 @@ gst_wasapi2_device_provider_probe_internal (GstWasapi2DeviceProvider * self,
|
||||||
gchar *device_id = NULL;
|
gchar *device_id = NULL;
|
||||||
gchar *device_name = NULL;
|
gchar *device_name = NULL;
|
||||||
|
|
||||||
client = gst_wasapi2_client_new (client_class, i, NULL, NULL);
|
client = gst_wasapi2_client_new (client_class, i, NULL, 0, NULL);
|
||||||
|
|
||||||
if (!client)
|
if (!client)
|
||||||
return;
|
return;
|
||||||
|
|
|
@ -145,6 +145,7 @@ struct _GstWasapi2RingBuffer
|
||||||
gdouble volume;
|
gdouble volume;
|
||||||
gpointer dispatcher;
|
gpointer dispatcher;
|
||||||
gboolean can_auto_routing;
|
gboolean can_auto_routing;
|
||||||
|
guint loopback_target_pid;
|
||||||
|
|
||||||
GstWasapi2Client *client;
|
GstWasapi2Client *client;
|
||||||
GstWasapi2Client *loopback_client;
|
GstWasapi2Client *loopback_client;
|
||||||
|
@ -380,7 +381,7 @@ gst_wasapi2_ring_buffer_open_device (GstAudioRingBuffer * buf)
|
||||||
}
|
}
|
||||||
|
|
||||||
self->client = gst_wasapi2_client_new (self->device_class,
|
self->client = gst_wasapi2_client_new (self->device_class,
|
||||||
-1, self->device_id, self->dispatcher);
|
-1, self->device_id, self->loopback_target_pid, self->dispatcher);
|
||||||
if (!self->client) {
|
if (!self->client) {
|
||||||
gst_wasapi2_ring_buffer_post_open_error (self);
|
gst_wasapi2_ring_buffer_post_open_error (self);
|
||||||
return FALSE;
|
return FALSE;
|
||||||
|
@ -389,10 +390,10 @@ gst_wasapi2_ring_buffer_open_device (GstAudioRingBuffer * buf)
|
||||||
g_object_get (self->client, "auto-routing", &self->can_auto_routing, nullptr);
|
g_object_get (self->client, "auto-routing", &self->can_auto_routing, nullptr);
|
||||||
|
|
||||||
/* Open another render client to feed silence */
|
/* Open another render client to feed silence */
|
||||||
if (self->device_class == GST_WASAPI2_CLIENT_DEVICE_CLASS_LOOPBACK_CAPTURE) {
|
if (gst_wasapi2_device_class_is_loopback (self->device_class)) {
|
||||||
self->loopback_client =
|
self->loopback_client =
|
||||||
gst_wasapi2_client_new (GST_WASAPI2_CLIENT_DEVICE_CLASS_RENDER,
|
gst_wasapi2_client_new (GST_WASAPI2_CLIENT_DEVICE_CLASS_RENDER,
|
||||||
-1, self->device_id, self->dispatcher);
|
-1, self->device_id, 0, self->dispatcher);
|
||||||
|
|
||||||
if (!self->loopback_client) {
|
if (!self->loopback_client) {
|
||||||
gst_wasapi2_ring_buffer_post_open_error (self);
|
gst_wasapi2_ring_buffer_post_open_error (self);
|
||||||
|
@ -480,6 +481,8 @@ gst_wasapi2_ring_buffer_read (GstWasapi2RingBuffer * self)
|
||||||
", expected position %" G_GUINT64_FORMAT, to_read, position,
|
", expected position %" G_GUINT64_FORMAT, to_read, position,
|
||||||
self->expected_position);
|
self->expected_position);
|
||||||
|
|
||||||
|
/* XXX: position might not be increased in case of process loopback */
|
||||||
|
if (!gst_wasapi2_device_class_is_process_loopback (self->device_class)) {
|
||||||
if (self->is_first) {
|
if (self->is_first) {
|
||||||
self->expected_position = position + to_read;
|
self->expected_position = position + to_read;
|
||||||
self->is_first = FALSE;
|
self->is_first = FALSE;
|
||||||
|
@ -494,6 +497,10 @@ gst_wasapi2_ring_buffer_read (GstWasapi2RingBuffer * self)
|
||||||
|
|
||||||
self->expected_position = position + to_read;
|
self->expected_position = position + to_read;
|
||||||
}
|
}
|
||||||
|
} else if (self->mute) {
|
||||||
|
/* volume clinet might not be available in case of process loopback */
|
||||||
|
flags |= AUDCLNT_BUFFERFLAGS_SILENT;
|
||||||
|
}
|
||||||
|
|
||||||
/* Fill gap data if any */
|
/* Fill gap data if any */
|
||||||
while (gap_size > 0) {
|
while (gap_size > 0) {
|
||||||
|
@ -679,6 +686,8 @@ gst_wasapi2_ring_buffer_io_callback (GstWasapi2RingBuffer * self)
|
||||||
switch (self->device_class) {
|
switch (self->device_class) {
|
||||||
case GST_WASAPI2_CLIENT_DEVICE_CLASS_CAPTURE:
|
case GST_WASAPI2_CLIENT_DEVICE_CLASS_CAPTURE:
|
||||||
case GST_WASAPI2_CLIENT_DEVICE_CLASS_LOOPBACK_CAPTURE:
|
case GST_WASAPI2_CLIENT_DEVICE_CLASS_LOOPBACK_CAPTURE:
|
||||||
|
case GST_WASAPI2_CLIENT_DEVICE_CLASS_INCLUDE_PROCESS_LOOPBACK_CAPTURE:
|
||||||
|
case GST_WASAPI2_CLIENT_DEVICE_CLASS_EXCLUDE_PROCESS_LOOPBACK_CAPTURE:
|
||||||
hr = gst_wasapi2_ring_buffer_read (self);
|
hr = gst_wasapi2_ring_buffer_read (self);
|
||||||
break;
|
break;
|
||||||
case GST_WASAPI2_CLIENT_DEVICE_CLASS_RENDER:
|
case GST_WASAPI2_CLIENT_DEVICE_CLASS_RENDER:
|
||||||
|
@ -694,7 +703,8 @@ gst_wasapi2_ring_buffer_io_callback (GstWasapi2RingBuffer * self)
|
||||||
* loopback capture client doesn't seem to be able to recover status from this
|
* loopback capture client doesn't seem to be able to recover status from this
|
||||||
* situation */
|
* situation */
|
||||||
if (self->can_auto_routing &&
|
if (self->can_auto_routing &&
|
||||||
self->device_class != GST_WASAPI2_CLIENT_DEVICE_CLASS_LOOPBACK_CAPTURE &&
|
!gst_wasapi2_device_class_is_loopback (self->device_class) &&
|
||||||
|
!gst_wasapi2_device_class_is_process_loopback (self->device_class) &&
|
||||||
(hr == AUDCLNT_E_ENDPOINT_CREATE_FAILED
|
(hr == AUDCLNT_E_ENDPOINT_CREATE_FAILED
|
||||||
|| hr == AUDCLNT_E_DEVICE_INVALIDATED)) {
|
|| hr == AUDCLNT_E_DEVICE_INVALIDATED)) {
|
||||||
GST_WARNING_OBJECT (self,
|
GST_WARNING_OBJECT (self,
|
||||||
|
@ -777,8 +787,8 @@ gst_wasapi2_ring_buffer_loopback_callback (GstWasapi2RingBuffer * self)
|
||||||
HRESULT hr = E_FAIL;
|
HRESULT hr = E_FAIL;
|
||||||
|
|
||||||
g_return_val_if_fail (GST_IS_WASAPI2_RING_BUFFER (self), E_FAIL);
|
g_return_val_if_fail (GST_IS_WASAPI2_RING_BUFFER (self), E_FAIL);
|
||||||
g_return_val_if_fail (self->device_class ==
|
g_return_val_if_fail (gst_wasapi2_device_class_is_loopback
|
||||||
GST_WASAPI2_CLIENT_DEVICE_CLASS_LOOPBACK_CAPTURE, E_FAIL);
|
(self->device_class), E_FAIL);
|
||||||
|
|
||||||
if (!self->running) {
|
if (!self->running) {
|
||||||
GST_INFO_OBJECT (self, "We are not running now");
|
GST_INFO_OBJECT (self, "We are not running now");
|
||||||
|
@ -852,7 +862,7 @@ gst_wasapi2_ring_buffer_initialize_audio_client3 (GstWasapi2RingBuffer * self,
|
||||||
static HRESULT
|
static HRESULT
|
||||||
gst_wasapi2_ring_buffer_initialize_audio_client (GstWasapi2RingBuffer * self,
|
gst_wasapi2_ring_buffer_initialize_audio_client (GstWasapi2RingBuffer * self,
|
||||||
IAudioClient * client_handle, WAVEFORMATEX * mix_format, guint * period,
|
IAudioClient * client_handle, WAVEFORMATEX * mix_format, guint * period,
|
||||||
DWORD extra_flags)
|
DWORD extra_flags, GstWasapi2ClientDeviceClass device_class)
|
||||||
{
|
{
|
||||||
GstAudioRingBuffer *ringbuffer = GST_AUDIO_RING_BUFFER_CAST (self);
|
GstAudioRingBuffer *ringbuffer = GST_AUDIO_RING_BUFFER_CAST (self);
|
||||||
REFERENCE_TIME default_period, min_period;
|
REFERENCE_TIME default_period, min_period;
|
||||||
|
@ -862,6 +872,7 @@ gst_wasapi2_ring_buffer_initialize_audio_client (GstWasapi2RingBuffer * self,
|
||||||
|
|
||||||
stream_flags |= extra_flags;
|
stream_flags |= extra_flags;
|
||||||
|
|
||||||
|
if (!gst_wasapi2_device_class_is_process_loopback (device_class)) {
|
||||||
hr = client_handle->GetDevicePeriod (&default_period, &min_period);
|
hr = client_handle->GetDevicePeriod (&default_period, &min_period);
|
||||||
if (!gst_wasapi2_result (hr)) {
|
if (!gst_wasapi2_result (hr)) {
|
||||||
GST_WARNING_OBJECT (self, "Couldn't get device period info");
|
GST_WARNING_OBJECT (self, "Couldn't get device period info");
|
||||||
|
@ -879,6 +890,17 @@ gst_wasapi2_ring_buffer_initialize_audio_client (GstWasapi2RingBuffer * self,
|
||||||
0,
|
0,
|
||||||
/* This must always be 0 in shared mode */
|
/* This must always be 0 in shared mode */
|
||||||
0, mix_format, nullptr);
|
0, mix_format, nullptr);
|
||||||
|
} else {
|
||||||
|
/* XXX: virtual device will not report device period.
|
||||||
|
* Use hardcoded period 20ms, same as Microsoft sample code
|
||||||
|
* https://github.com/microsoft/windows-classic-samples/tree/main/Samples/ApplicationLoopback
|
||||||
|
*/
|
||||||
|
default_period = (20 * GST_MSECOND) / 100;
|
||||||
|
hr = client_handle->Initialize (AUDCLNT_SHAREMODE_SHARED,
|
||||||
|
AUDCLNT_STREAMFLAGS_LOOPBACK | AUDCLNT_STREAMFLAGS_EVENTCALLBACK,
|
||||||
|
default_period,
|
||||||
|
AUDCLNT_STREAMFLAGS_AUTOCONVERTPCM, mix_format, nullptr);
|
||||||
|
}
|
||||||
|
|
||||||
if (!gst_wasapi2_result (hr)) {
|
if (!gst_wasapi2_result (hr)) {
|
||||||
GST_WARNING_OBJECT (self, "Couldn't initialize audioclient");
|
GST_WARNING_OBJECT (self, "Couldn't initialize audioclient");
|
||||||
|
@ -923,7 +945,7 @@ gst_wasapi2_ring_buffer_prepare_loopback_client (GstWasapi2RingBuffer * self)
|
||||||
}
|
}
|
||||||
|
|
||||||
hr = gst_wasapi2_ring_buffer_initialize_audio_client (self, client_handle,
|
hr = gst_wasapi2_ring_buffer_initialize_audio_client (self, client_handle,
|
||||||
mix_format, &period, 0);
|
mix_format, &period, 0, GST_WASAPI2_CLIENT_DEVICE_CLASS_RENDER);
|
||||||
|
|
||||||
if (!gst_wasapi2_result (hr)) {
|
if (!gst_wasapi2_result (hr)) {
|
||||||
GST_ERROR_OBJECT (self, "Failed to initialize audio client");
|
GST_ERROR_OBJECT (self, "Failed to initialize audio client");
|
||||||
|
@ -970,7 +992,7 @@ gst_wasapi2_ring_buffer_acquire (GstAudioRingBuffer * buf,
|
||||||
if (!self->client && !gst_wasapi2_ring_buffer_open_device (buf))
|
if (!self->client && !gst_wasapi2_ring_buffer_open_device (buf))
|
||||||
return FALSE;
|
return FALSE;
|
||||||
|
|
||||||
if (self->device_class == GST_WASAPI2_CLIENT_DEVICE_CLASS_LOOPBACK_CAPTURE) {
|
if (gst_wasapi2_device_class_is_loopback (self->device_class)) {
|
||||||
if (!gst_wasapi2_ring_buffer_prepare_loopback_client (self)) {
|
if (!gst_wasapi2_ring_buffer_prepare_loopback_client (self)) {
|
||||||
GST_ERROR_OBJECT (self, "Failed to prepare loopback client");
|
GST_ERROR_OBJECT (self, "Failed to prepare loopback client");
|
||||||
goto error;
|
goto error;
|
||||||
|
@ -991,9 +1013,13 @@ gst_wasapi2_ring_buffer_acquire (GstAudioRingBuffer * buf,
|
||||||
/* TODO: convert given caps to mix format */
|
/* TODO: convert given caps to mix format */
|
||||||
hr = client_handle->GetMixFormat (&mix_format);
|
hr = client_handle->GetMixFormat (&mix_format);
|
||||||
if (!gst_wasapi2_result (hr)) {
|
if (!gst_wasapi2_result (hr)) {
|
||||||
|
if (gst_wasapi2_device_class_is_process_loopback (self->device_class)) {
|
||||||
|
mix_format = gst_wasapi2_get_default_mix_format ();
|
||||||
|
} else {
|
||||||
GST_ERROR_OBJECT (self, "Failed to get mix format");
|
GST_ERROR_OBJECT (self, "Failed to get mix format");
|
||||||
goto error;
|
goto error;
|
||||||
}
|
}
|
||||||
|
}
|
||||||
|
|
||||||
/* Only use audioclient3 when low-latency is requested because otherwise
|
/* Only use audioclient3 when low-latency is requested because otherwise
|
||||||
* very slow machines and VMs with 1 CPU allocated will get glitches:
|
* very slow machines and VMs with 1 CPU allocated will get glitches:
|
||||||
|
@ -1002,7 +1028,8 @@ gst_wasapi2_ring_buffer_acquire (GstAudioRingBuffer * buf,
|
||||||
if (self->low_latency &&
|
if (self->low_latency &&
|
||||||
/* AUDCLNT_STREAMFLAGS_LOOPBACK is not allowed for
|
/* AUDCLNT_STREAMFLAGS_LOOPBACK is not allowed for
|
||||||
* InitializeSharedAudioStream */
|
* InitializeSharedAudioStream */
|
||||||
self->device_class != GST_WASAPI2_CLIENT_DEVICE_CLASS_LOOPBACK_CAPTURE) {
|
!gst_wasapi2_device_class_is_loopback (self->device_class) &&
|
||||||
|
!gst_wasapi2_device_class_is_process_loopback (self->device_class)) {
|
||||||
hr = gst_wasapi2_ring_buffer_initialize_audio_client3 (self, client_handle,
|
hr = gst_wasapi2_ring_buffer_initialize_audio_client3 (self, client_handle,
|
||||||
mix_format, &period);
|
mix_format, &period);
|
||||||
}
|
}
|
||||||
|
@ -1015,11 +1042,11 @@ gst_wasapi2_ring_buffer_acquire (GstAudioRingBuffer * buf,
|
||||||
*/
|
*/
|
||||||
if (FAILED (hr)) {
|
if (FAILED (hr)) {
|
||||||
DWORD extra_flags = 0;
|
DWORD extra_flags = 0;
|
||||||
if (self->device_class == GST_WASAPI2_CLIENT_DEVICE_CLASS_LOOPBACK_CAPTURE)
|
if (gst_wasapi2_device_class_is_loopback (self->device_class))
|
||||||
extra_flags = AUDCLNT_STREAMFLAGS_LOOPBACK;
|
extra_flags = AUDCLNT_STREAMFLAGS_LOOPBACK;
|
||||||
|
|
||||||
hr = gst_wasapi2_ring_buffer_initialize_audio_client (self, client_handle,
|
hr = gst_wasapi2_ring_buffer_initialize_audio_client (self, client_handle,
|
||||||
mix_format, &period, extra_flags);
|
mix_format, &period, extra_flags, self->device_class);
|
||||||
}
|
}
|
||||||
|
|
||||||
if (!gst_wasapi2_result (hr)) {
|
if (!gst_wasapi2_result (hr)) {
|
||||||
|
@ -1090,10 +1117,8 @@ gst_wasapi2_ring_buffer_acquire (GstAudioRingBuffer * buf,
|
||||||
|
|
||||||
hr = client_handle->GetService (IID_PPV_ARGS (&audio_volume));
|
hr = client_handle->GetService (IID_PPV_ARGS (&audio_volume));
|
||||||
if (!gst_wasapi2_result (hr)) {
|
if (!gst_wasapi2_result (hr)) {
|
||||||
GST_ERROR_OBJECT (self, "ISimpleAudioVolume is unavailable");
|
GST_WARNING_OBJECT (self, "ISimpleAudioVolume is unavailable");
|
||||||
goto error;
|
} else {
|
||||||
}
|
|
||||||
|
|
||||||
g_mutex_lock (&self->volume_lock);
|
g_mutex_lock (&self->volume_lock);
|
||||||
self->volume_object = audio_volume.Detach ();
|
self->volume_object = audio_volume.Detach ();
|
||||||
|
|
||||||
|
@ -1109,6 +1134,7 @@ gst_wasapi2_ring_buffer_acquire (GstAudioRingBuffer * buf,
|
||||||
self->volume_changed = FALSE;
|
self->volume_changed = FALSE;
|
||||||
}
|
}
|
||||||
g_mutex_unlock (&self->volume_lock);
|
g_mutex_unlock (&self->volume_lock);
|
||||||
|
}
|
||||||
|
|
||||||
buf->size = spec->segtotal * spec->segsize;
|
buf->size = spec->segtotal * spec->segsize;
|
||||||
buf->memory = (guint8 *) g_malloc (buf->size);
|
buf->memory = (guint8 *) g_malloc (buf->size);
|
||||||
|
@ -1327,7 +1353,7 @@ gst_wasapi2_ring_buffer_delay (GstAudioRingBuffer * buf)
|
||||||
GstAudioRingBuffer *
|
GstAudioRingBuffer *
|
||||||
gst_wasapi2_ring_buffer_new (GstWasapi2ClientDeviceClass device_class,
|
gst_wasapi2_ring_buffer_new (GstWasapi2ClientDeviceClass device_class,
|
||||||
gboolean low_latency, const gchar * device_id, gpointer dispatcher,
|
gboolean low_latency, const gchar * device_id, gpointer dispatcher,
|
||||||
const gchar * name)
|
const gchar * name, guint loopback_target_pid)
|
||||||
{
|
{
|
||||||
GstWasapi2RingBuffer *self;
|
GstWasapi2RingBuffer *self;
|
||||||
|
|
||||||
|
@ -1343,6 +1369,7 @@ gst_wasapi2_ring_buffer_new (GstWasapi2ClientDeviceClass device_class,
|
||||||
self->low_latency = low_latency;
|
self->low_latency = low_latency;
|
||||||
self->device_id = g_strdup (device_id);
|
self->device_id = g_strdup (device_id);
|
||||||
self->dispatcher = dispatcher;
|
self->dispatcher = dispatcher;
|
||||||
|
self->loopback_target_pid = loopback_target_pid;
|
||||||
|
|
||||||
return GST_AUDIO_RING_BUFFER_CAST (self);
|
return GST_AUDIO_RING_BUFFER_CAST (self);
|
||||||
}
|
}
|
||||||
|
|
|
@ -34,7 +34,8 @@ GstAudioRingBuffer * gst_wasapi2_ring_buffer_new (GstWasapi2ClientDeviceClass
|
||||||
gboolean low_latency,
|
gboolean low_latency,
|
||||||
const gchar *device_id,
|
const gchar *device_id,
|
||||||
gpointer dispatcher,
|
gpointer dispatcher,
|
||||||
const gchar * name);
|
const gchar * name,
|
||||||
|
guint loopback_target_pid);
|
||||||
|
|
||||||
GstCaps * gst_wasapi2_ring_buffer_get_caps (GstWasapi2RingBuffer * buf);
|
GstCaps * gst_wasapi2_ring_buffer_get_caps (GstWasapi2RingBuffer * buf);
|
||||||
|
|
||||||
|
|
|
@ -333,7 +333,7 @@ gst_wasapi2_sink_create_ringbuffer (GstAudioBaseSink * sink)
|
||||||
|
|
||||||
ringbuffer =
|
ringbuffer =
|
||||||
gst_wasapi2_ring_buffer_new (GST_WASAPI2_CLIENT_DEVICE_CLASS_RENDER,
|
gst_wasapi2_ring_buffer_new (GST_WASAPI2_CLIENT_DEVICE_CLASS_RENDER,
|
||||||
self->low_latency, self->device_id, self->dispatcher, name);
|
self->low_latency, self->device_id, self->dispatcher, name, 0);
|
||||||
|
|
||||||
g_free (name);
|
g_free (name);
|
||||||
|
|
||||||
|
|
|
@ -53,10 +53,72 @@ static GstStaticPadTemplate src_template = GST_STATIC_PAD_TEMPLATE ("src",
|
||||||
GST_PAD_ALWAYS,
|
GST_PAD_ALWAYS,
|
||||||
GST_STATIC_CAPS (GST_WASAPI2_STATIC_CAPS));
|
GST_STATIC_CAPS (GST_WASAPI2_STATIC_CAPS));
|
||||||
|
|
||||||
|
/**
|
||||||
|
* GstWasapi2SrcLoopbackMode:
|
||||||
|
*
|
||||||
|
* Loopback capture mode
|
||||||
|
*
|
||||||
|
* Since: 1.22
|
||||||
|
*/
|
||||||
|
typedef enum
|
||||||
|
{
|
||||||
|
/**
|
||||||
|
* GstWasapi2SrcLoopbackMode::default:
|
||||||
|
*
|
||||||
|
* Default loopback mode
|
||||||
|
*
|
||||||
|
* Since: 1.22
|
||||||
|
*/
|
||||||
|
GST_WASAPI2_SRC_LOOPBACK_DEFAULT,
|
||||||
|
|
||||||
|
/**
|
||||||
|
* GstWasapi2SrcLoopbackMode::include-process-tree:
|
||||||
|
*
|
||||||
|
* Captures only specified process and its child process
|
||||||
|
*
|
||||||
|
* Since: 1.22
|
||||||
|
*/
|
||||||
|
GST_WASAPI2_SRC_LOOPBACK_INCLUDE_PROCESS_TREE,
|
||||||
|
|
||||||
|
/**
|
||||||
|
* GstWasapi2SrcLoopbackMode::exclude-process-tree:
|
||||||
|
*
|
||||||
|
* Excludes specified process and its child process
|
||||||
|
*
|
||||||
|
* Since: 1.22
|
||||||
|
*/
|
||||||
|
GST_WASAPI2_SRC_LOOPBACK_EXCLUDE_PROCESS_TREE,
|
||||||
|
} GstWasapi2SrcLoopbackMode;
|
||||||
|
|
||||||
|
#define GST_TYPE_WASAPI2_SRC_LOOPBACK_MODE (gst_wasapi2_src_loopback_mode_get_type ())
|
||||||
|
static GType
|
||||||
|
gst_wasapi2_src_loopback_mode_get_type (void)
|
||||||
|
{
|
||||||
|
static GType loopback_type = 0;
|
||||||
|
static const GEnumValue types[] = {
|
||||||
|
{GST_WASAPI2_SRC_LOOPBACK_DEFAULT, "Default", "default"},
|
||||||
|
{GST_WASAPI2_SRC_LOOPBACK_INCLUDE_PROCESS_TREE,
|
||||||
|
"Include process and its child processes",
|
||||||
|
"include-process-tree"},
|
||||||
|
{GST_WASAPI2_SRC_LOOPBACK_EXCLUDE_PROCESS_TREE,
|
||||||
|
"Exclude process and its child processes",
|
||||||
|
"exclude-process-tree"},
|
||||||
|
{0, NULL, NULL}
|
||||||
|
};
|
||||||
|
|
||||||
|
if (g_once_init_enter (&loopback_type)) {
|
||||||
|
GType gtype = g_enum_register_static ("GstWasapi2SrcLoopbackMode", types);
|
||||||
|
g_once_init_leave (&loopback_type, gtype);
|
||||||
|
}
|
||||||
|
|
||||||
|
return loopback_type;
|
||||||
|
}
|
||||||
|
|
||||||
#define DEFAULT_LOW_LATENCY FALSE
|
#define DEFAULT_LOW_LATENCY FALSE
|
||||||
#define DEFAULT_MUTE FALSE
|
#define DEFAULT_MUTE FALSE
|
||||||
#define DEFAULT_VOLUME 1.0
|
#define DEFAULT_VOLUME 1.0
|
||||||
#define DEFAULT_LOOPBACK FALSE
|
#define DEFAULT_LOOPBACK FALSE
|
||||||
|
#define DEFAULT_LOOPBACK_MODE GST_WASAPI2_SRC_LOOPBACK_DEFAULT
|
||||||
|
|
||||||
enum
|
enum
|
||||||
{
|
{
|
||||||
|
@ -67,6 +129,8 @@ enum
|
||||||
PROP_VOLUME,
|
PROP_VOLUME,
|
||||||
PROP_DISPATCHER,
|
PROP_DISPATCHER,
|
||||||
PROP_LOOPBACK,
|
PROP_LOOPBACK,
|
||||||
|
PROP_LOOPBACK_MODE,
|
||||||
|
PROP_LOOPBACK_TARGET_PID,
|
||||||
};
|
};
|
||||||
|
|
||||||
struct _GstWasapi2Src
|
struct _GstWasapi2Src
|
||||||
|
@ -80,6 +144,8 @@ struct _GstWasapi2Src
|
||||||
gdouble volume;
|
gdouble volume;
|
||||||
gpointer dispatcher;
|
gpointer dispatcher;
|
||||||
gboolean loopback;
|
gboolean loopback;
|
||||||
|
GstWasapi2SrcLoopbackMode loopback_mode;
|
||||||
|
guint loopback_pid;
|
||||||
|
|
||||||
gboolean mute_changed;
|
gboolean mute_changed;
|
||||||
gboolean volume_changed;
|
gboolean volume_changed;
|
||||||
|
@ -173,6 +239,41 @@ gst_wasapi2_src_class_init (GstWasapi2SrcClass * klass)
|
||||||
GST_PARAM_MUTABLE_READY | G_PARAM_READWRITE |
|
GST_PARAM_MUTABLE_READY | G_PARAM_READWRITE |
|
||||||
G_PARAM_STATIC_STRINGS));
|
G_PARAM_STATIC_STRINGS));
|
||||||
|
|
||||||
|
if (gst_wasapi2_can_process_loopback ()) {
|
||||||
|
/**
|
||||||
|
* GstWasapi2Src:loopback-mode:
|
||||||
|
*
|
||||||
|
* Loopback mode. "target-process-id" must be specified in case of
|
||||||
|
* process loopback modes.
|
||||||
|
*
|
||||||
|
* This feature requires "Windows 10 build 20348"
|
||||||
|
*
|
||||||
|
* Since: 1.22
|
||||||
|
*/
|
||||||
|
g_object_class_install_property (gobject_class, PROP_LOOPBACK_MODE,
|
||||||
|
g_param_spec_enum ("loopback-mode", "Loopback Mode",
|
||||||
|
"Loopback mode to use", GST_TYPE_WASAPI2_SRC_LOOPBACK_MODE,
|
||||||
|
DEFAULT_LOOPBACK_MODE,
|
||||||
|
GST_PARAM_CONDITIONALLY_AVAILABLE | GST_PARAM_MUTABLE_READY |
|
||||||
|
G_PARAM_READWRITE | G_PARAM_STATIC_STRINGS));
|
||||||
|
|
||||||
|
/**
|
||||||
|
* GstWasapi2Src:loopback-target-pid:
|
||||||
|
*
|
||||||
|
* Target process id to be recorded or excluded depending on loopback mode
|
||||||
|
*
|
||||||
|
* This feature requires "Windows 10 build 20348"
|
||||||
|
*
|
||||||
|
* Since: 1.22
|
||||||
|
*/
|
||||||
|
g_object_class_install_property (gobject_class, PROP_LOOPBACK_TARGET_PID,
|
||||||
|
g_param_spec_uint ("loopback-target-pid", "Loopback Target PID",
|
||||||
|
"Process ID to be recorded or excluded for process loopback mode",
|
||||||
|
0, G_MAXUINT32, 0,
|
||||||
|
GST_PARAM_CONDITIONALLY_AVAILABLE | GST_PARAM_MUTABLE_READY |
|
||||||
|
G_PARAM_READWRITE | G_PARAM_STATIC_STRINGS));
|
||||||
|
}
|
||||||
|
|
||||||
gst_element_class_add_static_pad_template (element_class, &src_template);
|
gst_element_class_add_static_pad_template (element_class, &src_template);
|
||||||
gst_element_class_set_static_metadata (element_class, "Wasapi2Src",
|
gst_element_class_set_static_metadata (element_class, "Wasapi2Src",
|
||||||
"Source/Audio/Hardware",
|
"Source/Audio/Hardware",
|
||||||
|
@ -191,6 +292,9 @@ gst_wasapi2_src_class_init (GstWasapi2SrcClass * klass)
|
||||||
|
|
||||||
GST_DEBUG_CATEGORY_INIT (gst_wasapi2_src_debug, "wasapi2src",
|
GST_DEBUG_CATEGORY_INIT (gst_wasapi2_src_debug, "wasapi2src",
|
||||||
0, "Windows audio session API source");
|
0, "Windows audio session API source");
|
||||||
|
|
||||||
|
if (gst_wasapi2_can_process_loopback ())
|
||||||
|
gst_type_mark_as_plugin_api (GST_TYPE_WASAPI2_SRC_LOOPBACK_MODE, 0);
|
||||||
}
|
}
|
||||||
|
|
||||||
static void
|
static void
|
||||||
|
@ -238,6 +342,12 @@ gst_wasapi2_src_set_property (GObject * object, guint prop_id,
|
||||||
case PROP_LOOPBACK:
|
case PROP_LOOPBACK:
|
||||||
self->loopback = g_value_get_boolean (value);
|
self->loopback = g_value_get_boolean (value);
|
||||||
break;
|
break;
|
||||||
|
case PROP_LOOPBACK_MODE:
|
||||||
|
self->loopback_mode = g_value_get_enum (value);
|
||||||
|
break;
|
||||||
|
case PROP_LOOPBACK_TARGET_PID:
|
||||||
|
self->loopback_pid = g_value_get_uint (value);
|
||||||
|
break;
|
||||||
default:
|
default:
|
||||||
G_OBJECT_WARN_INVALID_PROPERTY_ID (object, prop_id, pspec);
|
G_OBJECT_WARN_INVALID_PROPERTY_ID (object, prop_id, pspec);
|
||||||
break;
|
break;
|
||||||
|
@ -266,6 +376,12 @@ gst_wasapi2_src_get_property (GObject * object, guint prop_id,
|
||||||
case PROP_LOOPBACK:
|
case PROP_LOOPBACK:
|
||||||
g_value_set_boolean (value, self->loopback);
|
g_value_set_boolean (value, self->loopback);
|
||||||
break;
|
break;
|
||||||
|
case PROP_LOOPBACK_MODE:
|
||||||
|
g_value_set_enum (value, self->loopback_mode);
|
||||||
|
break;
|
||||||
|
case PROP_LOOPBACK_TARGET_PID:
|
||||||
|
g_value_set_uint (value, self->loopback_pid);
|
||||||
|
break;
|
||||||
default:
|
default:
|
||||||
G_OBJECT_WARN_INVALID_PROPERTY_ID (object, prop_id, pspec);
|
G_OBJECT_WARN_INVALID_PROPERTY_ID (object, prop_id, pspec);
|
||||||
break;
|
break;
|
||||||
|
@ -350,14 +466,27 @@ gst_wasapi2_src_create_ringbuffer (GstAudioBaseSrc * src)
|
||||||
GstWasapi2ClientDeviceClass device_class =
|
GstWasapi2ClientDeviceClass device_class =
|
||||||
GST_WASAPI2_CLIENT_DEVICE_CLASS_CAPTURE;
|
GST_WASAPI2_CLIENT_DEVICE_CLASS_CAPTURE;
|
||||||
|
|
||||||
if (self->loopback)
|
if (self->loopback_pid) {
|
||||||
|
if (self->loopback_mode == GST_WASAPI2_SRC_LOOPBACK_INCLUDE_PROCESS_TREE) {
|
||||||
|
device_class =
|
||||||
|
GST_WASAPI2_CLIENT_DEVICE_CLASS_INCLUDE_PROCESS_LOOPBACK_CAPTURE;
|
||||||
|
} else if (self->loopback_mode ==
|
||||||
|
GST_WASAPI2_SRC_LOOPBACK_EXCLUDE_PROCESS_TREE) {
|
||||||
|
device_class =
|
||||||
|
GST_WASAPI2_CLIENT_DEVICE_CLASS_EXCLUDE_PROCESS_LOOPBACK_CAPTURE;
|
||||||
|
}
|
||||||
|
} else if (self->loopback) {
|
||||||
device_class = GST_WASAPI2_CLIENT_DEVICE_CLASS_LOOPBACK_CAPTURE;
|
device_class = GST_WASAPI2_CLIENT_DEVICE_CLASS_LOOPBACK_CAPTURE;
|
||||||
|
}
|
||||||
|
|
||||||
|
GST_DEBUG_OBJECT (self, "Device class %d", device_class);
|
||||||
|
|
||||||
name = g_strdup_printf ("%s-ringbuffer", GST_OBJECT_NAME (src));
|
name = g_strdup_printf ("%s-ringbuffer", GST_OBJECT_NAME (src));
|
||||||
|
|
||||||
ringbuffer =
|
ringbuffer =
|
||||||
gst_wasapi2_ring_buffer_new (device_class,
|
gst_wasapi2_ring_buffer_new (device_class,
|
||||||
self->low_latency, self->device_id, self->dispatcher, name);
|
self->low_latency, self->device_id, self->dispatcher, name,
|
||||||
|
self->loopback_pid);
|
||||||
g_free (name);
|
g_free (name);
|
||||||
|
|
||||||
return ringbuffer;
|
return ringbuffer;
|
||||||
|
|
|
@ -490,3 +490,73 @@ gst_wasapi2_can_automatic_stream_routing (void)
|
||||||
return ret;
|
return ret;
|
||||||
#endif
|
#endif
|
||||||
}
|
}
|
||||||
|
|
||||||
|
gboolean
|
||||||
|
gst_wasapi2_can_process_loopback (void)
|
||||||
|
{
|
||||||
|
#ifdef GST_WASAPI2_WINAPI_ONLY_APP
|
||||||
|
/* FIXME: Needs WinRT (Windows.System.Profile) API call
|
||||||
|
* for OS version check */
|
||||||
|
return FALSE;
|
||||||
|
#else
|
||||||
|
static gboolean ret = FALSE;
|
||||||
|
static gsize version_once = 0;
|
||||||
|
|
||||||
|
if (g_once_init_enter (&version_once)) {
|
||||||
|
OSVERSIONINFOEXW osverinfo;
|
||||||
|
typedef NTSTATUS (WINAPI fRtlGetVersion) (PRTL_OSVERSIONINFOEXW);
|
||||||
|
fRtlGetVersion *RtlGetVersion = NULL;
|
||||||
|
HMODULE hmodule = NULL;
|
||||||
|
|
||||||
|
memset (&osverinfo, 0, sizeof (OSVERSIONINFOEXW));
|
||||||
|
osverinfo.dwOSVersionInfoSize = sizeof (OSVERSIONINFOEXW);
|
||||||
|
|
||||||
|
hmodule = LoadLibraryW (L"ntdll.dll");
|
||||||
|
if (hmodule)
|
||||||
|
RtlGetVersion =
|
||||||
|
(fRtlGetVersion *) GetProcAddress (hmodule, "RtlGetVersion");
|
||||||
|
|
||||||
|
if (RtlGetVersion) {
|
||||||
|
RtlGetVersion (&osverinfo);
|
||||||
|
|
||||||
|
/* Process loopback requires Windows 10 build 20348
|
||||||
|
* https://learn.microsoft.com/en-us/windows/win32/api/audioclientactivationparams/ns-audioclientactivationparams-audioclient_process_loopback_params
|
||||||
|
*
|
||||||
|
* Note: "Windows 10 build 20348" would mean "Windows server 2022" or
|
||||||
|
* "Windows 11", since build number of "Windows 10 version 21H2" is
|
||||||
|
* still 19044.XXX
|
||||||
|
*/
|
||||||
|
if (osverinfo.dwMajorVersion > 10 ||
|
||||||
|
(osverinfo.dwMajorVersion == 10 && osverinfo.dwBuildNumber >= 20348))
|
||||||
|
ret = TRUE;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (hmodule)
|
||||||
|
FreeLibrary (hmodule);
|
||||||
|
|
||||||
|
g_once_init_leave (&version_once, 1);
|
||||||
|
}
|
||||||
|
|
||||||
|
GST_INFO ("Process loopback support: %d", ret);
|
||||||
|
|
||||||
|
return ret;
|
||||||
|
#endif
|
||||||
|
}
|
||||||
|
|
||||||
|
WAVEFORMATEX *
|
||||||
|
gst_wasapi2_get_default_mix_format (void)
|
||||||
|
{
|
||||||
|
WAVEFORMATEX *format;
|
||||||
|
|
||||||
|
/* virtual loopback device might not provide mix format. Create our default
|
||||||
|
* mix format */
|
||||||
|
format = CoTaskMemAlloc (sizeof (WAVEFORMATEX));
|
||||||
|
format->wFormatTag = WAVE_FORMAT_PCM;
|
||||||
|
format->nChannels = 2;
|
||||||
|
format->nSamplesPerSec = 44100;
|
||||||
|
format->wBitsPerSample = 16;
|
||||||
|
format->nBlockAlign = format->nChannels * format->wBitsPerSample / 8;
|
||||||
|
format->nAvgBytesPerSec = format->nSamplesPerSec * format->nBlockAlign;
|
||||||
|
|
||||||
|
return format;
|
||||||
|
}
|
||||||
|
|
|
@ -65,6 +65,10 @@ gchar * gst_wasapi2_util_get_error_message (HRESULT hr);
|
||||||
|
|
||||||
gboolean gst_wasapi2_can_automatic_stream_routing (void);
|
gboolean gst_wasapi2_can_automatic_stream_routing (void);
|
||||||
|
|
||||||
|
gboolean gst_wasapi2_can_process_loopback (void);
|
||||||
|
|
||||||
|
WAVEFORMATEX * gst_wasapi2_get_default_mix_format (void);
|
||||||
|
|
||||||
G_END_DECLS
|
G_END_DECLS
|
||||||
|
|
||||||
#endif /* __GST_WASAPI_UTIL_H__ */
|
#endif /* __GST_WASAPI_UTIL_H__ */
|
||||||
|
|
Loading…
Reference in a new issue