diff --git a/subprojects/gst-plugins-bad/sys/wasapi/gstwasapisink.c b/subprojects/gst-plugins-bad/sys/wasapi/gstwasapisink.c index b10d391104dc20c3e3029398874bf1139738142b..39fcbe5a220f17ecfaf20277fff199c6b8e75944 100644 --- a/subprojects/gst-plugins-bad/sys/wasapi/gstwasapisink.c +++ b/subprojects/gst-plugins-bad/sys/wasapi/gstwasapisink.c @@ -59,6 +59,7 @@ static GstStaticPadTemplate sink_template = GST_STATIC_PAD_TEMPLATE ("sink", #define DEFAULT_EXCLUSIVE FALSE #define DEFAULT_LOW_LATENCY FALSE #define DEFAULT_AUDIOCLIENT3 TRUE +#define DEFAULT_MIN_PERIOD 2500 enum { @@ -68,7 +69,8 @@ enum PROP_DEVICE, PROP_EXCLUSIVE, PROP_LOW_LATENCY, - PROP_AUDIOCLIENT3 + PROP_AUDIOCLIENT3, + PROP_MIN_PERIOD }; static void gst_wasapi_sink_dispose (GObject * object); @@ -145,6 +147,16 @@ gst_wasapi_sink_class_init (GstWasapiSinkClass * klass) "low-latency property is set to TRUE", DEFAULT_AUDIOCLIENT3, G_PARAM_READWRITE | G_PARAM_STATIC_STRINGS)); + g_object_class_install_property (gobject_class, + PROP_MIN_PERIOD, + g_param_spec_uint ("min-period", "Minimum WASAPI device period", + "The smallest WASAPI device period in microseconds that will " + "be negotiated when use-audioclient3 and low-latency are TRUE. " + "Lower values might be supported on specialized hardware but " + "could also lead to increased CPU usage or playback glitches.", + 0, G_MAXUINT32, DEFAULT_MIN_PERIOD, + G_PARAM_READWRITE | G_PARAM_STATIC_STRINGS)); + gst_element_class_add_static_pad_template (gstelement_class, &sink_template); gst_element_class_set_static_metadata (gstelement_class, "WasapiSrc", "Sink/Audio/Hardware", @@ -176,6 +188,7 @@ gst_wasapi_sink_init (GstWasapiSink * self) self->sharemode = AUDCLNT_SHAREMODE_SHARED; self->low_latency = DEFAULT_LOW_LATENCY; self->try_audioclient3 = DEFAULT_AUDIOCLIENT3; + self->min_period = DEFAULT_MIN_PERIOD; self->event_handle = CreateEvent (NULL, FALSE, FALSE, NULL); self->cancellable = CreateEvent (NULL, TRUE, FALSE, NULL); self->client_needs_restart = FALSE; @@ -264,6 +277,9 @@ gst_wasapi_sink_set_property (GObject * object, guint prop_id, case PROP_AUDIOCLIENT3: self->try_audioclient3 = g_value_get_boolean (value); break; + case PROP_MIN_PERIOD: + self->min_period = g_value_get_uint (value); + break; default: G_OBJECT_WARN_INVALID_PROPERTY_ID (object, prop_id, pspec); break; @@ -297,6 +313,9 @@ gst_wasapi_sink_get_property (GObject * object, guint prop_id, case PROP_AUDIOCLIENT3: g_value_set_boolean (value, self->try_audioclient3); break; + case PROP_MIN_PERIOD: + g_value_set_uint (value, self->min_period); + break; default: G_OBJECT_WARN_INVALID_PROPERTY_ID (object, prop_id, pspec); break; @@ -473,6 +492,9 @@ gst_wasapi_sink_get_can_frames (GstWasapiSink * self) GST_DEBUG_OBJECT (self, "%i unread frames (padding)", n_frames_padding); /* We can write out these many frames */ + if (n_frames_padding > self->buffer_frame_count) { + return 0; + } return self->buffer_frame_count - n_frames_padding; } @@ -494,8 +516,8 @@ 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, - FALSE, &devicep_frames)) + (IAudioClient3 *)self->client, self->mix_format, self->low_latency, + FALSE, self->min_period, &devicep_frames)) goto beach; } else { if (!gst_wasapi_util_initialize_audioclient (GST_ELEMENT (self), spec, @@ -515,6 +537,13 @@ gst_wasapi_sink_prepare (GstAudioSink * asink, GstAudioRingBufferSpec * spec) "frames, bpf is %i bytes, rate is %i Hz", self->buffer_frame_count, devicep_frames, bpf, rate); + /* In low-latency shared mode, restrict buffer to one device period */ + if (self->low_latency && self->sharemode == AUDCLNT_SHAREMODE_SHARED) { + self->buffer_frame_count = MIN (self->buffer_frame_count, devicep_frames); + GST_INFO_OBJECT (self, "low-latency buffer size capped at %i frames", + self->buffer_frame_count); + } + /* Actual latency-time/buffer-time will be different now */ spec->segsize = devicep_frames * bpf; @@ -672,7 +701,7 @@ gst_wasapi_sink_write (GstAudioSink * asink, gpointer data, guint length) goto err; } - if (can_frames == 0) { + while (can_frames == 0) { dwWaitResult = WaitForMultipleObjects (2, event_handle, FALSE, INFINITE); if (dwWaitResult != WAIT_OBJECT_0 && dwWaitResult != WAIT_OBJECT_0 + 1) { GST_ERROR_OBJECT (self, "Error waiting for event handle: %x", diff --git a/subprojects/gst-plugins-bad/sys/wasapi/gstwasapisink.h b/subprojects/gst-plugins-bad/sys/wasapi/gstwasapisink.h index 4aac69efc7fd52d3c851e51626a7b62ec325a93a..fdda3afef3f82c10e4d60fe8ebbd57fc2c587fc4 100644 --- a/subprojects/gst-plugins-bad/sys/wasapi/gstwasapisink.h +++ b/subprojects/gst-plugins-bad/sys/wasapi/gstwasapisink.h @@ -67,6 +67,7 @@ struct _GstWasapiSink gboolean mute; gboolean low_latency; gboolean try_audioclient3; + guint min_period; wchar_t *device_strid; }; diff --git a/subprojects/gst-plugins-bad/sys/wasapi/gstwasapisrc.c b/subprojects/gst-plugins-bad/sys/wasapi/gstwasapisrc.c index 8bdff3be085ad8b65d2da4f6371d3541fe8bb651..51cb37da801ef3f40ec8c396dad3f57188bd65cf 100644 --- a/subprojects/gst-plugins-bad/sys/wasapi/gstwasapisrc.c +++ b/subprojects/gst-plugins-bad/sys/wasapi/gstwasapisrc.c @@ -607,7 +607,7 @@ 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, - self->loopback, &devicep_frames)) + self->loopback, 0, &devicep_frames)) goto beach; } else { if (!gst_wasapi_util_initialize_audioclient (GST_ELEMENT (self), spec, diff --git a/subprojects/gst-plugins-bad/sys/wasapi/gstwasapiutil.c b/subprojects/gst-plugins-bad/sys/wasapi/gstwasapiutil.c index b59f5f19363741cc1e3f8c8871ca96b7a45e65ee..4408505034df54eb92c21f5703bbb0f6349f275b 100644 --- a/subprojects/gst-plugins-bad/sys/wasapi/gstwasapiutil.c +++ b/subprojects/gst-plugins-bad/sys/wasapi/gstwasapiutil.c @@ -923,13 +923,14 @@ gboolean gst_wasapi_util_initialize_audioclient3 (GstElement * self, GstAudioRingBufferSpec * spec, IAudioClient3 * client, WAVEFORMATEX * format, gboolean low_latency, gboolean loopback, - guint * ret_devicep_frames) + guint min_period, guint * ret_devicep_frames) { HRESULT hr; gint stream_flags; guint devicep_frames; guint defaultp_frames, fundp_frames, minp_frames, maxp_frames; WAVEFORMATEX *tmpf; + AudioClientProperties audio_props = {0}; hr = IAudioClient3_GetSharedModeEnginePeriod (client, format, &defaultp_frames, &fundp_frames, &minp_frames, &maxp_frames); @@ -939,12 +940,23 @@ gst_wasapi_util_initialize_audioclient3 (GstElement * self, "fundamental period %i frames, minimum period %i frames, maximum period " "%i frames", defaultp_frames, fundp_frames, minp_frames, maxp_frames); - if (low_latency) - devicep_frames = minp_frames; - else + if (low_latency) { + /* Convert minimum usec to frames as a multiple of fundp_frames */ + devicep_frames = (guint)gst_util_uint64_scale_ceil(min_period, + format->nSamplesPerSec, GST_SECOND / GST_USECOND); + devicep_frames = ((devicep_frames + fundp_frames - 1) / + fundp_frames) * fundp_frames; + devicep_frames = CLAMP (devicep_frames, minp_frames, maxp_frames); + } else { /* Just pick the max period, because lower values can cause glitches * https://bugzilla.gnome.org/show_bug.cgi?id=794497 */ devicep_frames = maxp_frames; + } + + audio_props.cbSize = sizeof( AudioClientProperties ); + audio_props.eCategory = AudioCategory_Other; + hr = IAudioClient3_SetClientProperties (client, &audio_props); + HR_FAILED_RET (hr, IAudioClient3::SetClientProperties, FALSE); stream_flags = AUDCLNT_STREAMFLAGS_EVENTCALLBACK; if (loopback) @@ -952,13 +964,35 @@ gst_wasapi_util_initialize_audioclient3 (GstElement * self, hr = IAudioClient3_InitializeSharedAudioStream (client, stream_flags, devicep_frames, format, NULL); + if (hr == AUDCLNT_E_ENGINE_PERIODICITY_LOCKED) { + /* Another WASAPI client has set "exact match" client properties and + * initialized a shared period other than what we requested. We must + * match the same period, but we leave the devicep_frames variable + * unchanged in order to honor the implied minimum WASAPI buffer size. */ + guint locked_period; + hr = IAudioClient3_GetCurrentSharedModeEnginePeriod (client, &tmpf, + &locked_period); + CoTaskMemFree (tmpf); + HR_FAILED_RET (hr, IAudioClient3::GetCurrentSharedModeEnginePeriod, FALSE); + GST_INFO_OBJECT (self, "Received E_ENGINE_PERIODICITY_LOCKED, " + "retrying with period %i frames", locked_period); + hr = IAudioClient3_InitializeSharedAudioStream (client, stream_flags, + locked_period, format, NULL); + } HR_FAILED_RET (hr, IAudioClient3::InitializeSharedAudioStream, FALSE); + /* Query the device to find the actual period, which may be different */ hr = IAudioClient3_GetCurrentSharedModeEnginePeriod (client, &tmpf, - &devicep_frames); + ret_devicep_frames); CoTaskMemFree (tmpf); HR_FAILED_RET (hr, IAudioClient3::GetCurrentSharedModeEnginePeriod, FALSE); - *ret_devicep_frames = devicep_frames; + /* The actual period may be lower than devicep_frames if another app has + * also requested a lower shared period, or higher if locked by another + * application. We return the maximum of actual and requested periods, so + * our target buffer size (based on period) never shrinks below min_period + * microseconds. It is okay to return a larger-than-actual period because + * writes are scheduled using the WASAPI event handle, not clock time. */ + *ret_devicep_frames = MAX (*ret_devicep_frames, devicep_frames); return TRUE; } diff --git a/subprojects/gst-plugins-bad/sys/wasapi/gstwasapiutil.h b/subprojects/gst-plugins-bad/sys/wasapi/gstwasapiutil.h index 70f241980cf9bb90c782ae52ba8c249f58f443f4..11480566477e97218d761b6a13b675d8a78cb03f 100644 --- a/subprojects/gst-plugins-bad/sys/wasapi/gstwasapiutil.h +++ b/subprojects/gst-plugins-bad/sys/wasapi/gstwasapiutil.h @@ -134,6 +134,6 @@ gboolean gst_wasapi_util_initialize_audioclient (GstElement * element, gboolean gst_wasapi_util_initialize_audioclient3 (GstElement * element, GstAudioRingBufferSpec * spec, IAudioClient3 * client, WAVEFORMATEX * format, gboolean low_latency, gboolean loopback, - guint * ret_devicep_frames); + guint min_period, guint * ret_devicep_frames); #endif /* __GST_WASAPI_UTIL_H__ */