wasapisrc: Implement loopback recording

Now, when you set loopback=true on wasapisrc, the `device` property
should refer to a sink (render) device for loopback recording.

If the `device` property is not set, the default sink device is used.
This commit is contained in:
Nirbheek Chauhan 2018-04-04 01:07:14 +05:30
parent 8649cef462
commit affb0182c6
5 changed files with 50 additions and 23 deletions

View file

@ -378,7 +378,7 @@ gst_wasapi_sink_open (GstAudioSink * asink)
* even if the old device was unplugged. We need to handle this somehow.
* For example, perhaps we should automatically switch to the new device if
* the default device is changed and a device isn't explicitly selected. */
if (!gst_wasapi_util_get_device_client (GST_ELEMENT (self), FALSE,
if (!gst_wasapi_util_get_device_client (GST_ELEMENT (self), eRender,
self->role, self->device_strid, &device, &client)) {
if (!self->device_strid)
GST_ELEMENT_ERROR (self, RESOURCE, OPEN_WRITE, (NULL),
@ -452,12 +452,12 @@ gst_wasapi_sink_prepare (GstAudioSink * asink, GstAudioRingBufferSpec * spec)
if (gst_wasapi_sink_can_audioclient3 (self)) {
if (!gst_wasapi_util_initialize_audioclient3 (GST_ELEMENT (self), spec,
(IAudioClient3 *) self->client, self->mix_format, self->low_latency,
&devicep_frames))
FALSE, &devicep_frames))
goto beach;
} else {
if (!gst_wasapi_util_initialize_audioclient (GST_ELEMENT (self), spec,
self->client, self->mix_format, self->sharemode, self->low_latency,
&devicep_frames))
FALSE, &devicep_frames))
goto beach;
}

View file

@ -53,6 +53,7 @@ static GstStaticPadTemplate src_template = GST_STATIC_PAD_TEMPLATE ("src",
GST_STATIC_CAPS (GST_WASAPI_STATIC_CAPS));
#define DEFAULT_ROLE GST_WASAPI_DEVICE_ROLE_CONSOLE
#define DEFAULT_LOOPBACK FALSE
#define DEFAULT_EXCLUSIVE FALSE
#define DEFAULT_LOW_LATENCY FALSE
#define DEFAULT_AUDIOCLIENT3 FALSE
@ -62,6 +63,7 @@ enum
PROP_0,
PROP_ROLE,
PROP_DEVICE,
PROP_LOOPBACK,
PROP_EXCLUSIVE,
PROP_LOW_LATENCY,
PROP_AUDIOCLIENT3
@ -118,6 +120,12 @@ gst_wasapi_src_class_init (GstWasapiSrcClass * klass)
"WASAPI playback device as a GUID string",
NULL, G_PARAM_READWRITE | G_PARAM_STATIC_STRINGS));
g_object_class_install_property (gobject_class,
PROP_LOOPBACK,
g_param_spec_boolean ("loopback", "Loopback recording",
"Open the sink device for loopback recording",
DEFAULT_LOOPBACK, G_PARAM_READWRITE | G_PARAM_STATIC_STRINGS));
g_object_class_install_property (gobject_class,
PROP_EXCLUSIVE,
g_param_spec_boolean ("exclusive", "Exclusive mode",
@ -170,6 +178,7 @@ gst_wasapi_src_init (GstWasapiSrc * self)
self->role = DEFAULT_ROLE;
self->sharemode = AUDCLNT_SHAREMODE_SHARED;
self->loopback = DEFAULT_LOOPBACK;
self->low_latency = DEFAULT_LOW_LATENCY;
self->try_audioclient3 = DEFAULT_AUDIOCLIENT3;
self->event_handle = CreateEvent (NULL, FALSE, FALSE, NULL);
@ -239,6 +248,9 @@ gst_wasapi_src_set_property (GObject * object, guint prop_id,
device ? g_utf8_to_utf16 (device, -1, NULL, NULL, NULL) : NULL;
break;
}
case PROP_LOOPBACK:
self->loopback = g_value_get_boolean (value);
break;
case PROP_EXCLUSIVE:
self->sharemode = g_value_get_boolean (value)
? AUDCLNT_SHAREMODE_EXCLUSIVE : AUDCLNT_SHAREMODE_SHARED;
@ -269,6 +281,9 @@ gst_wasapi_src_get_property (GObject * object, guint prop_id,
g_value_take_string (value, self->device_strid ?
g_utf16_to_utf8 (self->device_strid, -1, NULL, NULL, NULL) : NULL);
break;
case PROP_LOOPBACK:
g_value_set_boolean (value, self->loopback);
break;
case PROP_EXCLUSIVE:
g_value_set_boolean (value,
self->sharemode == AUDCLNT_SHAREMODE_EXCLUSIVE);
@ -369,8 +384,9 @@ gst_wasapi_src_open (GstAudioSrc * asrc)
* even if the old device was unplugged. We need to handle this somehow.
* For example, perhaps we should automatically switch to the new device if
* the default device is changed and a device isn't explicitly selected. */
if (!gst_wasapi_util_get_device_client (GST_ELEMENT (self), TRUE,
self->role, self->device_strid, &device, &client)) {
if (!gst_wasapi_util_get_device_client (GST_ELEMENT (self),
self->loopback ? eRender : eCapture, self->role, self->device_strid,
&device, &client)) {
if (!self->device_strid)
GST_ELEMENT_ERROR (self, RESOURCE, OPEN_READ, (NULL),
("Failed to get default device"));
@ -419,12 +435,12 @@ gst_wasapi_src_prepare (GstAudioSrc * asrc, GstAudioRingBufferSpec * spec)
if (gst_wasapi_src_can_audioclient3 (self)) {
if (!gst_wasapi_util_initialize_audioclient3 (GST_ELEMENT (self), spec,
(IAudioClient3 *) self->client, self->mix_format, self->low_latency,
&devicep_frames))
self->loopback, &devicep_frames))
goto beach;
} else {
if (!gst_wasapi_util_initialize_audioclient (GST_ELEMENT (self), spec,
self->client, self->mix_format, self->sharemode, self->low_latency,
&devicep_frames))
self->loopback, &devicep_frames))
goto beach;
}

View file

@ -62,6 +62,7 @@ struct _GstWasapiSrc
/* properties */
gint role;
gint sharemode;
gboolean loopback;
gboolean low_latency;
gboolean try_audioclient3;
wchar_t *device_strid;

View file

@ -543,7 +543,7 @@ out:
gboolean
gst_wasapi_util_get_device_client (GstElement * self,
gboolean capture, gint role, const wchar_t * device_strid,
gint data_flow, gint role, const wchar_t * device_strid,
IMMDevice ** ret_device, IAudioClient ** ret_client)
{
gboolean res = FALSE;
@ -556,8 +556,8 @@ gst_wasapi_util_get_device_client (GstElement * self,
goto beach;
if (!device_strid) {
hr = IMMDeviceEnumerator_GetDefaultAudioEndpoint (enumerator,
capture ? eCapture : eRender, role, &device);
hr = IMMDeviceEnumerator_GetDefaultAudioEndpoint (enumerator, data_flow,
role, &device);
HR_FAILED_GOTO (hr, IMMDeviceEnumerator::GetDefaultAudioEndpoint, beach);
} else {
hr = IMMDeviceEnumerator_GetDevice (enumerator, device_strid, &device);
@ -840,11 +840,11 @@ gboolean
gst_wasapi_util_initialize_audioclient (GstElement * self,
GstAudioRingBufferSpec * spec, IAudioClient * client,
WAVEFORMATEX * format, guint sharemode, gboolean low_latency,
guint * ret_devicep_frames)
gboolean loopback, guint * ret_devicep_frames)
{
REFERENCE_TIME default_period, min_period;
REFERENCE_TIME device_period, device_buffer_duration;
guint rate;
guint rate, stream_flags;
HRESULT hr;
hr = IAudioClient_GetDevicePeriod (client, &default_period, &min_period);
@ -874,8 +874,12 @@ gst_wasapi_util_initialize_audioclient (GstElement * self,
if (sharemode == AUDCLNT_SHAREMODE_EXCLUSIVE)
CoInitialize (NULL);
hr = IAudioClient_Initialize (client, sharemode,
AUDCLNT_STREAMFLAGS_EVENTCALLBACK, device_buffer_duration,
stream_flags = AUDCLNT_STREAMFLAGS_EVENTCALLBACK;
if (loopback)
stream_flags |= AUDCLNT_STREAMFLAGS_LOOPBACK;
hr = IAudioClient_Initialize (client, sharemode, stream_flags,
device_buffer_duration,
/* This must always be 0 in shared mode */
sharemode == AUDCLNT_SHAREMODE_SHARED ? 0 : device_period, format, NULL);
@ -895,9 +899,8 @@ gst_wasapi_util_initialize_audioclient (GstElement * self,
GST_WARNING_OBJECT (self, "trying to re-initialize with period %i "
"(%i frames, %i rate)", (int) device_period, n_frames, rate);
hr = IAudioClient_Initialize (client, sharemode,
AUDCLNT_STREAMFLAGS_EVENTCALLBACK, device_period,
device_period, format, NULL);
hr = IAudioClient_Initialize (client, sharemode, stream_flags,
device_period, device_period, format, NULL);
}
HR_FAILED_RET (hr, IAudioClient::Initialize, FALSE);
@ -909,9 +912,11 @@ gst_wasapi_util_initialize_audioclient (GstElement * self,
gboolean
gst_wasapi_util_initialize_audioclient3 (GstElement * self,
GstAudioRingBufferSpec * spec, IAudioClient3 * client,
WAVEFORMATEX * format, gboolean low_latency, guint * ret_devicep_frames)
WAVEFORMATEX * format, gboolean low_latency, gboolean loopback,
guint * ret_devicep_frames)
{
HRESULT hr;
gint stream_flags;
guint rate, devicep_frames;
guint defaultp_frames, fundp_frames, minp_frames, maxp_frames;
WAVEFORMATEX *tmpf;
@ -937,8 +942,12 @@ gst_wasapi_util_initialize_audioclient3 (GstElement * self,
devicep_frames = tmp * fundp_frames;
}
hr = IAudioClient3_InitializeSharedAudioStream (client,
AUDCLNT_STREAMFLAGS_EVENTCALLBACK, devicep_frames, format, NULL);
stream_flags = AUDCLNT_STREAMFLAGS_EVENTCALLBACK;
if (loopback)
stream_flags |= AUDCLNT_STREAMFLAGS_LOOPBACK;
hr = IAudioClient3_InitializeSharedAudioStream (client, stream_flags,
devicep_frames, format, NULL);
HR_FAILED_RET (hr, IAudioClient3::InitializeSharedAudioStream, FALSE);
hr = IAudioClient3_GetCurrentSharedModeEnginePeriod (client, &tmpf,

View file

@ -79,7 +79,7 @@ gboolean gst_wasapi_util_get_devices (GstElement * element, gboolean active,
GList ** devices);
gboolean gst_wasapi_util_get_device_client (GstElement * element,
gboolean capture, gint role, const wchar_t * device_strid,
gint data_flow, gint role, const wchar_t * device_strid,
IMMDevice ** ret_device, IAudioClient ** ret_client);
gboolean gst_wasapi_util_get_device_format (GstElement * element,
@ -107,11 +107,12 @@ void gst_wasapi_util_get_best_buffer_sizes (GstAudioRingBufferSpec * spec,
gboolean gst_wasapi_util_initialize_audioclient (GstElement * element,
GstAudioRingBufferSpec * spec, IAudioClient * client,
WAVEFORMATEX * format, guint sharemode, gboolean low_latency,
guint * ret_devicep_frames);
gboolean loopback, guint * ret_devicep_frames);
gboolean gst_wasapi_util_initialize_audioclient3 (GstElement * element,
GstAudioRingBufferSpec * spec, IAudioClient3 * client,
WAVEFORMATEX * format, gboolean low_latency, guint * ret_devicep_frames);
WAVEFORMATEX * format, gboolean low_latency, gboolean loopback,
guint * ret_devicep_frames);
HANDLE gst_wasapi_util_set_thread_characteristics (void);