mirror of
https://gitlab.freedesktop.org/gstreamer/gstreamer.git
synced 2024-12-18 14:26:43 +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",
|
||||
"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": {
|
||||
"blurb": "Optimize all settings for lowest latency. Always safe to enable.",
|
||||
"conditionally-available": false,
|
||||
|
@ -230117,7 +230143,28 @@
|
|||
},
|
||||
"filename": "gstwasapi2",
|
||||
"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",
|
||||
"source": "gst-plugins-bad",
|
||||
"tracers": {},
|
||||
|
|
|
@ -52,6 +52,37 @@ using namespace ABI::Windows::Devices::Enumeration;
|
|||
using namespace Microsoft::WRL;
|
||||
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
|
||||
|
||||
GST_DEBUG_CATEGORY_EXTERN (gst_wasapi2_client_debug);
|
||||
|
@ -152,19 +183,29 @@ public:
|
|||
}
|
||||
|
||||
HRESULT
|
||||
ActivateDeviceAsync(const std::wstring &device_id)
|
||||
ActivateDeviceAsync(const std::wstring &device_id,
|
||||
GST_AUDIOCLIENT_ACTIVATION_PARAMS * params)
|
||||
{
|
||||
ComPtr<IAsyncAction> async_action;
|
||||
bool run_async = false;
|
||||
HRESULT hr;
|
||||
|
||||
auto work_item = Callback<Implements<RuntimeClassFlags<ClassicCom>,
|
||||
IDispatchedHandler, FtmBase>>([this, device_id]{
|
||||
IDispatchedHandler, FtmBase>>([this, device_id, params]{
|
||||
ComPtr<IActivateAudioInterfaceAsyncOperation> async_op;
|
||||
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), nullptr, this, &async_op);
|
||||
async_hr = ActivateAudioInterfaceAsync (device_id.c_str (),
|
||||
__uuidof(IAudioClient), &activate_params, this, &async_op);
|
||||
} else {
|
||||
async_hr = ActivateAudioInterfaceAsync (device_id.c_str (),
|
||||
__uuidof(IAudioClient), nullptr, this, &async_op);
|
||||
}
|
||||
|
||||
/* for debugging */
|
||||
gst_wasapi2_result (async_hr);
|
||||
|
@ -227,6 +268,7 @@ enum
|
|||
PROP_DEVICE_CLASS,
|
||||
PROP_DISPATCHER,
|
||||
PROP_CAN_AUTO_ROUTING,
|
||||
PROP_LOOPBACK_TARGET_PID,
|
||||
};
|
||||
|
||||
#define DEFAULT_DEVICE_INDEX -1
|
||||
|
@ -242,6 +284,7 @@ struct _GstWasapi2Client
|
|||
gint device_index;
|
||||
gpointer dispatcher;
|
||||
gboolean can_auto_routing;
|
||||
guint target_pid;
|
||||
|
||||
IAudioClient *audio_client;
|
||||
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_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}
|
||||
};
|
||||
|
||||
|
@ -328,6 +377,9 @@ gst_wasapi2_client_class_init (GstWasapi2ClientClass * klass)
|
|||
g_param_spec_boolean ("auto-routing", "Auto Routing",
|
||||
"Whether client can support automatic stream routing", FALSE,
|
||||
(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
|
||||
|
@ -425,6 +477,9 @@ gst_wasapi2_client_get_property (GObject * object, guint prop_id,
|
|||
case PROP_CAN_AUTO_ROUTING:
|
||||
g_value_set_boolean (value, self->can_auto_routing);
|
||||
break;
|
||||
case PROP_LOOPBACK_TARGET_PID:
|
||||
g_value_set_uint (value, self->target_pid);
|
||||
break;
|
||||
default:
|
||||
G_OBJECT_WARN_INVALID_PROPERTY_ID (object, prop_id, pspec);
|
||||
break;
|
||||
|
@ -456,6 +511,9 @@ gst_wasapi2_client_set_property (GObject * object, guint prop_id,
|
|||
case PROP_DISPATCHER:
|
||||
self->dispatcher = g_value_get_pointer (value);
|
||||
break;
|
||||
case PROP_LOOPBACK_TARGET_PID:
|
||||
self->target_pid = g_value_get_uint (value);
|
||||
break;
|
||||
default:
|
||||
G_OBJECT_WARN_INVALID_PROPERTY_ID (object, prop_id, pspec);
|
||||
break;
|
||||
|
@ -559,6 +617,46 @@ gst_wasapi2_client_activate_async (GstWasapi2Client * self,
|
|||
std::string target_device_id;
|
||||
std::string target_device_name;
|
||||
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,
|
||||
"requested device info, device-class: %s, device: %s, device-index: %d",
|
||||
|
@ -773,7 +871,13 @@ activate:
|
|||
/* default device supports automatic stream routing */
|
||||
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)) {
|
||||
GST_WARNING_OBJECT (self, "Failed to activate device");
|
||||
goto failed;
|
||||
|
@ -886,8 +990,12 @@ gst_wasapi2_client_get_caps (GstWasapi2Client * client)
|
|||
|
||||
hr = client->audio_client->GetMixFormat (&mix_format);
|
||||
if (!gst_wasapi2_result (hr)) {
|
||||
GST_WARNING_OBJECT (client, "Failed to get mix format");
|
||||
return nullptr;
|
||||
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");
|
||||
return nullptr;
|
||||
}
|
||||
}
|
||||
|
||||
scaps = gst_static_caps_get (&static_caps);
|
||||
|
@ -950,7 +1058,8 @@ find_dispatcher (ICoreDispatcher ** dispatcher)
|
|||
|
||||
GstWasapi2Client *
|
||||
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;
|
||||
/* *INDENT-OFF* */
|
||||
|
@ -977,7 +1086,8 @@ gst_wasapi2_client_new (GstWasapi2ClientDeviceClass device_class,
|
|||
|
||||
self = (GstWasapi2Client *) g_object_new (GST_TYPE_WASAPI2_CLIENT,
|
||||
"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
|
||||
* RoInitializeWrapper dtor is called */
|
||||
|
|
|
@ -31,8 +31,37 @@ typedef enum
|
|||
GST_WASAPI2_CLIENT_DEVICE_CLASS_CAPTURE = 0,
|
||||
GST_WASAPI2_CLIENT_DEVICE_CLASS_RENDER,
|
||||
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;
|
||||
|
||||
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())
|
||||
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,
|
||||
gint device_index,
|
||||
const gchar * device_id,
|
||||
guint target_pid,
|
||||
gpointer dispatcher);
|
||||
|
||||
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_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)
|
||||
return;
|
||||
|
|
|
@ -145,6 +145,7 @@ struct _GstWasapi2RingBuffer
|
|||
gdouble volume;
|
||||
gpointer dispatcher;
|
||||
gboolean can_auto_routing;
|
||||
guint loopback_target_pid;
|
||||
|
||||
GstWasapi2Client *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,
|
||||
-1, self->device_id, self->dispatcher);
|
||||
-1, self->device_id, self->loopback_target_pid, self->dispatcher);
|
||||
if (!self->client) {
|
||||
gst_wasapi2_ring_buffer_post_open_error (self);
|
||||
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);
|
||||
|
||||
/* 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 =
|
||||
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) {
|
||||
gst_wasapi2_ring_buffer_post_open_error (self);
|
||||
|
@ -480,19 +481,25 @@ gst_wasapi2_ring_buffer_read (GstWasapi2RingBuffer * self)
|
|||
", expected position %" G_GUINT64_FORMAT, to_read, position,
|
||||
self->expected_position);
|
||||
|
||||
if (self->is_first) {
|
||||
self->expected_position = position + to_read;
|
||||
self->is_first = FALSE;
|
||||
} else {
|
||||
if (position > self->expected_position) {
|
||||
guint gap_frames;
|
||||
/* 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) {
|
||||
self->expected_position = position + to_read;
|
||||
self->is_first = FALSE;
|
||||
} else {
|
||||
if (position > self->expected_position) {
|
||||
guint gap_frames;
|
||||
|
||||
gap_frames = (guint) (position - self->expected_position);
|
||||
GST_WARNING_OBJECT (self, "Found %u frames gap", gap_frames);
|
||||
gap_size = gap_frames * GST_AUDIO_INFO_BPF (info);
|
||||
gap_frames = (guint) (position - self->expected_position);
|
||||
GST_WARNING_OBJECT (self, "Found %u frames gap", gap_frames);
|
||||
gap_size = gap_frames * GST_AUDIO_INFO_BPF (info);
|
||||
}
|
||||
|
||||
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 */
|
||||
|
@ -679,6 +686,8 @@ gst_wasapi2_ring_buffer_io_callback (GstWasapi2RingBuffer * self)
|
|||
switch (self->device_class) {
|
||||
case GST_WASAPI2_CLIENT_DEVICE_CLASS_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);
|
||||
break;
|
||||
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
|
||||
* situation */
|
||||
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_DEVICE_INVALIDATED)) {
|
||||
GST_WARNING_OBJECT (self,
|
||||
|
@ -777,8 +787,8 @@ gst_wasapi2_ring_buffer_loopback_callback (GstWasapi2RingBuffer * self)
|
|||
HRESULT hr = E_FAIL;
|
||||
|
||||
g_return_val_if_fail (GST_IS_WASAPI2_RING_BUFFER (self), E_FAIL);
|
||||
g_return_val_if_fail (self->device_class ==
|
||||
GST_WASAPI2_CLIENT_DEVICE_CLASS_LOOPBACK_CAPTURE, E_FAIL);
|
||||
g_return_val_if_fail (gst_wasapi2_device_class_is_loopback
|
||||
(self->device_class), E_FAIL);
|
||||
|
||||
if (!self->running) {
|
||||
GST_INFO_OBJECT (self, "We are not running now");
|
||||
|
@ -852,7 +862,7 @@ gst_wasapi2_ring_buffer_initialize_audio_client3 (GstWasapi2RingBuffer * self,
|
|||
static HRESULT
|
||||
gst_wasapi2_ring_buffer_initialize_audio_client (GstWasapi2RingBuffer * self,
|
||||
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);
|
||||
REFERENCE_TIME default_period, min_period;
|
||||
|
@ -862,24 +872,36 @@ gst_wasapi2_ring_buffer_initialize_audio_client (GstWasapi2RingBuffer * self,
|
|||
|
||||
stream_flags |= extra_flags;
|
||||
|
||||
hr = client_handle->GetDevicePeriod (&default_period, &min_period);
|
||||
if (!gst_wasapi2_result (hr)) {
|
||||
GST_WARNING_OBJECT (self, "Couldn't get device period info");
|
||||
return hr;
|
||||
if (!gst_wasapi2_device_class_is_process_loopback (device_class)) {
|
||||
hr = client_handle->GetDevicePeriod (&default_period, &min_period);
|
||||
if (!gst_wasapi2_result (hr)) {
|
||||
GST_WARNING_OBJECT (self, "Couldn't get device period info");
|
||||
return hr;
|
||||
}
|
||||
|
||||
GST_INFO_OBJECT (self, "wasapi2 default period: %" G_GINT64_FORMAT
|
||||
", min period: %" G_GINT64_FORMAT, default_period, min_period);
|
||||
|
||||
hr = client_handle->Initialize (AUDCLNT_SHAREMODE_SHARED, stream_flags,
|
||||
/* hnsBufferDuration should be same as hnsPeriodicity
|
||||
* when AUDCLNT_STREAMFLAGS_EVENTCALLBACK is used.
|
||||
* And in case of shared mode, hnsPeriodicity should be zero, so
|
||||
* this value should be zero as well */
|
||||
0,
|
||||
/* This must always be 0 in shared mode */
|
||||
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);
|
||||
}
|
||||
|
||||
GST_INFO_OBJECT (self, "wasapi2 default period: %" G_GINT64_FORMAT
|
||||
", min period: %" G_GINT64_FORMAT, default_period, min_period);
|
||||
|
||||
hr = client_handle->Initialize (AUDCLNT_SHAREMODE_SHARED, stream_flags,
|
||||
/* hnsBufferDuration should be same as hnsPeriodicity
|
||||
* when AUDCLNT_STREAMFLAGS_EVENTCALLBACK is used.
|
||||
* And in case of shared mode, hnsPeriodicity should be zero, so
|
||||
* this value should be zero as well */
|
||||
0,
|
||||
/* This must always be 0 in shared mode */
|
||||
0, mix_format, nullptr);
|
||||
|
||||
if (!gst_wasapi2_result (hr)) {
|
||||
GST_WARNING_OBJECT (self, "Couldn't initialize audioclient");
|
||||
return hr;
|
||||
|
@ -923,7 +945,7 @@ gst_wasapi2_ring_buffer_prepare_loopback_client (GstWasapi2RingBuffer * self)
|
|||
}
|
||||
|
||||
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)) {
|
||||
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))
|
||||
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)) {
|
||||
GST_ERROR_OBJECT (self, "Failed to prepare loopback client");
|
||||
goto error;
|
||||
|
@ -991,8 +1013,12 @@ gst_wasapi2_ring_buffer_acquire (GstAudioRingBuffer * buf,
|
|||
/* TODO: convert given caps to mix format */
|
||||
hr = client_handle->GetMixFormat (&mix_format);
|
||||
if (!gst_wasapi2_result (hr)) {
|
||||
GST_ERROR_OBJECT (self, "Failed to get mix format");
|
||||
goto error;
|
||||
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");
|
||||
goto error;
|
||||
}
|
||||
}
|
||||
|
||||
/* Only use audioclient3 when low-latency is requested because otherwise
|
||||
|
@ -1002,7 +1028,8 @@ gst_wasapi2_ring_buffer_acquire (GstAudioRingBuffer * buf,
|
|||
if (self->low_latency &&
|
||||
/* AUDCLNT_STREAMFLAGS_LOOPBACK is not allowed for
|
||||
* 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,
|
||||
mix_format, &period);
|
||||
}
|
||||
|
@ -1015,11 +1042,11 @@ gst_wasapi2_ring_buffer_acquire (GstAudioRingBuffer * buf,
|
|||
*/
|
||||
if (FAILED (hr)) {
|
||||
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;
|
||||
|
||||
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)) {
|
||||
|
@ -1090,25 +1117,24 @@ gst_wasapi2_ring_buffer_acquire (GstAudioRingBuffer * buf,
|
|||
|
||||
hr = client_handle->GetService (IID_PPV_ARGS (&audio_volume));
|
||||
if (!gst_wasapi2_result (hr)) {
|
||||
GST_ERROR_OBJECT (self, "ISimpleAudioVolume is unavailable");
|
||||
goto error;
|
||||
}
|
||||
|
||||
g_mutex_lock (&self->volume_lock);
|
||||
self->volume_object = audio_volume.Detach ();
|
||||
|
||||
if (self->mute_changed) {
|
||||
self->volume_object->SetMute (self->mute, nullptr);
|
||||
self->mute_changed = FALSE;
|
||||
GST_WARNING_OBJECT (self, "ISimpleAudioVolume is unavailable");
|
||||
} else {
|
||||
self->volume_object->SetMute (FALSE, nullptr);
|
||||
}
|
||||
g_mutex_lock (&self->volume_lock);
|
||||
self->volume_object = audio_volume.Detach ();
|
||||
|
||||
if (self->volume_changed) {
|
||||
self->volume_object->SetMasterVolume (self->volume, nullptr);
|
||||
self->volume_changed = FALSE;
|
||||
if (self->mute_changed) {
|
||||
self->volume_object->SetMute (self->mute, nullptr);
|
||||
self->mute_changed = FALSE;
|
||||
} else {
|
||||
self->volume_object->SetMute (FALSE, nullptr);
|
||||
}
|
||||
|
||||
if (self->volume_changed) {
|
||||
self->volume_object->SetMasterVolume (self->volume, nullptr);
|
||||
self->volume_changed = FALSE;
|
||||
}
|
||||
g_mutex_unlock (&self->volume_lock);
|
||||
}
|
||||
g_mutex_unlock (&self->volume_lock);
|
||||
|
||||
buf->size = spec->segtotal * spec->segsize;
|
||||
buf->memory = (guint8 *) g_malloc (buf->size);
|
||||
|
@ -1327,7 +1353,7 @@ gst_wasapi2_ring_buffer_delay (GstAudioRingBuffer * buf)
|
|||
GstAudioRingBuffer *
|
||||
gst_wasapi2_ring_buffer_new (GstWasapi2ClientDeviceClass device_class,
|
||||
gboolean low_latency, const gchar * device_id, gpointer dispatcher,
|
||||
const gchar * name)
|
||||
const gchar * name, guint loopback_target_pid)
|
||||
{
|
||||
GstWasapi2RingBuffer *self;
|
||||
|
||||
|
@ -1343,6 +1369,7 @@ gst_wasapi2_ring_buffer_new (GstWasapi2ClientDeviceClass device_class,
|
|||
self->low_latency = low_latency;
|
||||
self->device_id = g_strdup (device_id);
|
||||
self->dispatcher = dispatcher;
|
||||
self->loopback_target_pid = loopback_target_pid;
|
||||
|
||||
return GST_AUDIO_RING_BUFFER_CAST (self);
|
||||
}
|
||||
|
|
|
@ -34,7 +34,8 @@ GstAudioRingBuffer * gst_wasapi2_ring_buffer_new (GstWasapi2ClientDeviceClass
|
|||
gboolean low_latency,
|
||||
const gchar *device_id,
|
||||
gpointer dispatcher,
|
||||
const gchar * name);
|
||||
const gchar * name,
|
||||
guint loopback_target_pid);
|
||||
|
||||
GstCaps * gst_wasapi2_ring_buffer_get_caps (GstWasapi2RingBuffer * buf);
|
||||
|
||||
|
|
|
@ -333,7 +333,7 @@ gst_wasapi2_sink_create_ringbuffer (GstAudioBaseSink * sink)
|
|||
|
||||
ringbuffer =
|
||||
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);
|
||||
|
||||
|
|
|
@ -53,10 +53,72 @@ static GstStaticPadTemplate src_template = GST_STATIC_PAD_TEMPLATE ("src",
|
|||
GST_PAD_ALWAYS,
|
||||
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_MUTE FALSE
|
||||
#define DEFAULT_VOLUME 1.0
|
||||
#define DEFAULT_LOOPBACK FALSE
|
||||
#define DEFAULT_LOOPBACK_MODE GST_WASAPI2_SRC_LOOPBACK_DEFAULT
|
||||
|
||||
enum
|
||||
{
|
||||
|
@ -67,6 +129,8 @@ enum
|
|||
PROP_VOLUME,
|
||||
PROP_DISPATCHER,
|
||||
PROP_LOOPBACK,
|
||||
PROP_LOOPBACK_MODE,
|
||||
PROP_LOOPBACK_TARGET_PID,
|
||||
};
|
||||
|
||||
struct _GstWasapi2Src
|
||||
|
@ -80,6 +144,8 @@ struct _GstWasapi2Src
|
|||
gdouble volume;
|
||||
gpointer dispatcher;
|
||||
gboolean loopback;
|
||||
GstWasapi2SrcLoopbackMode loopback_mode;
|
||||
guint loopback_pid;
|
||||
|
||||
gboolean mute_changed;
|
||||
gboolean volume_changed;
|
||||
|
@ -173,6 +239,41 @@ gst_wasapi2_src_class_init (GstWasapi2SrcClass * klass)
|
|||
GST_PARAM_MUTABLE_READY | G_PARAM_READWRITE |
|
||||
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_set_static_metadata (element_class, "Wasapi2Src",
|
||||
"Source/Audio/Hardware",
|
||||
|
@ -191,6 +292,9 @@ gst_wasapi2_src_class_init (GstWasapi2SrcClass * klass)
|
|||
|
||||
GST_DEBUG_CATEGORY_INIT (gst_wasapi2_src_debug, "wasapi2src",
|
||||
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
|
||||
|
@ -238,6 +342,12 @@ gst_wasapi2_src_set_property (GObject * object, guint prop_id,
|
|||
case PROP_LOOPBACK:
|
||||
self->loopback = g_value_get_boolean (value);
|
||||
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:
|
||||
G_OBJECT_WARN_INVALID_PROPERTY_ID (object, prop_id, pspec);
|
||||
break;
|
||||
|
@ -266,6 +376,12 @@ gst_wasapi2_src_get_property (GObject * object, guint prop_id,
|
|||
case PROP_LOOPBACK:
|
||||
g_value_set_boolean (value, self->loopback);
|
||||
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:
|
||||
G_OBJECT_WARN_INVALID_PROPERTY_ID (object, prop_id, pspec);
|
||||
break;
|
||||
|
@ -350,14 +466,27 @@ gst_wasapi2_src_create_ringbuffer (GstAudioBaseSrc * src)
|
|||
GstWasapi2ClientDeviceClass device_class =
|
||||
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;
|
||||
}
|
||||
|
||||
GST_DEBUG_OBJECT (self, "Device class %d", device_class);
|
||||
|
||||
name = g_strdup_printf ("%s-ringbuffer", GST_OBJECT_NAME (src));
|
||||
|
||||
ringbuffer =
|
||||
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);
|
||||
|
||||
return ringbuffer;
|
||||
|
|
|
@ -490,3 +490,73 @@ gst_wasapi2_can_automatic_stream_routing (void)
|
|||
return ret;
|
||||
#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_process_loopback (void);
|
||||
|
||||
WAVEFORMATEX * gst_wasapi2_get_default_mix_format (void);
|
||||
|
||||
G_END_DECLS
|
||||
|
||||
#endif /* __GST_WASAPI_UTIL_H__ */
|
||||
|
|
Loading…
Reference in a new issue