mirror of
https://gitlab.freedesktop.org/gstreamer/gstreamer.git
synced 2024-11-30 13:41:48 +00:00
wasapisrc: Fix capturing from some buggy audio drivers
Some audio drivers return varying amounts of data per ::GetBuffer call, instead of following the device period that they've told us about in `src_prepare()`. Previously, we would just drop those extra buffers hoping that the extra buffers were temporary (f.ex., a startup 'burst' of audio data). However, it seems that some audio drivers, particularly on older Windows versions (such as Windows 10 1703 and older) consistently return varying amounts of data. Use GstAdapter to smooth that out, and hope that the audio driver is locally varying but globally periodic. Initially reported in https://gitlab.freedesktop.org/gstreamer/gst-plugins-bad/issues/808
This commit is contained in:
parent
5d9c060ca7
commit
6cbff552fe
2 changed files with 44 additions and 22 deletions
|
@ -190,6 +190,7 @@ gst_wasapi_src_init (GstWasapiSrc * self)
|
||||||
self->try_audioclient3 = DEFAULT_AUDIOCLIENT3;
|
self->try_audioclient3 = DEFAULT_AUDIOCLIENT3;
|
||||||
self->event_handle = CreateEvent (NULL, FALSE, FALSE, NULL);
|
self->event_handle = CreateEvent (NULL, FALSE, FALSE, NULL);
|
||||||
self->client_needs_restart = FALSE;
|
self->client_needs_restart = FALSE;
|
||||||
|
self->adapter = gst_adapter_new ();
|
||||||
|
|
||||||
CoInitializeEx (NULL, COINIT_MULTITHREADED);
|
CoInitializeEx (NULL, COINIT_MULTITHREADED);
|
||||||
}
|
}
|
||||||
|
@ -236,6 +237,9 @@ gst_wasapi_src_finalize (GObject * object)
|
||||||
g_clear_pointer (&self->positions, g_free);
|
g_clear_pointer (&self->positions, g_free);
|
||||||
g_clear_pointer (&self->device_strid, g_free);
|
g_clear_pointer (&self->device_strid, g_free);
|
||||||
|
|
||||||
|
g_object_unref (self->adapter);
|
||||||
|
self->adapter = NULL;
|
||||||
|
|
||||||
G_OBJECT_CLASS (parent_class)->finalize (object);
|
G_OBJECT_CLASS (parent_class)->finalize (object);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -564,14 +568,23 @@ gst_wasapi_src_read (GstAudioSrc * asrc, gpointer data, guint length,
|
||||||
HR_FAILED_ELEMENT_ERROR_AND (hr, IAudioClient::Start, self,
|
HR_FAILED_ELEMENT_ERROR_AND (hr, IAudioClient::Start, self,
|
||||||
GST_OBJECT_UNLOCK (self); goto err);
|
GST_OBJECT_UNLOCK (self); goto err);
|
||||||
self->client_needs_restart = FALSE;
|
self->client_needs_restart = FALSE;
|
||||||
|
gst_adapter_clear (self->adapter);
|
||||||
}
|
}
|
||||||
|
|
||||||
bpf = self->mix_format->nBlockAlign;
|
bpf = self->mix_format->nBlockAlign;
|
||||||
GST_OBJECT_UNLOCK (self);
|
GST_OBJECT_UNLOCK (self);
|
||||||
|
|
||||||
|
/* If we've accumulated enough data, return it immediately */
|
||||||
|
if (gst_adapter_available (self->adapter) >= wanted) {
|
||||||
|
memcpy (data, gst_adapter_map (self->adapter, wanted), wanted);
|
||||||
|
gst_adapter_flush (self->adapter, wanted);
|
||||||
|
GST_DEBUG_OBJECT (self, "Adapter has enough data, returning %i", wanted);
|
||||||
|
goto out;
|
||||||
|
}
|
||||||
|
|
||||||
while (wanted > 0) {
|
while (wanted > 0) {
|
||||||
DWORD dwWaitResult;
|
DWORD dwWaitResult;
|
||||||
guint have_frames, n_frames, want_frames, read_len;
|
guint got_frames, avail_frames, n_frames, want_frames, read_len;
|
||||||
|
|
||||||
/* Wait for data to become available */
|
/* Wait for data to become available */
|
||||||
dwWaitResult = WaitForSingleObject (self->event_handle, INFINITE);
|
dwWaitResult = WaitForSingleObject (self->event_handle, INFINITE);
|
||||||
|
@ -582,7 +595,7 @@ gst_wasapi_src_read (GstAudioSrc * asrc, gpointer data, guint length,
|
||||||
}
|
}
|
||||||
|
|
||||||
hr = IAudioCaptureClient_GetBuffer (self->capture_client,
|
hr = IAudioCaptureClient_GetBuffer (self->capture_client,
|
||||||
(BYTE **) & from, &have_frames, &flags, NULL, NULL);
|
(BYTE **) & from, &got_frames, &flags, NULL, NULL);
|
||||||
if (hr != S_OK) {
|
if (hr != S_OK) {
|
||||||
if (hr == AUDCLNT_S_BUFFER_EMPTY) {
|
if (hr == AUDCLNT_S_BUFFER_EMPTY) {
|
||||||
gchar *msg = gst_wasapi_util_hresult_to_string (hr);
|
gchar *msg = gst_wasapi_util_hresult_to_string (hr);
|
||||||
|
@ -606,29 +619,36 @@ gst_wasapi_src_read (GstAudioSrc * asrc, gpointer data, guint length,
|
||||||
if (flags & AUDCLNT_BUFFERFLAGS_DATA_DISCONTINUITY)
|
if (flags & AUDCLNT_BUFFERFLAGS_DATA_DISCONTINUITY)
|
||||||
GST_WARNING_OBJECT (self, "WASAPI reported glitch in buffer");
|
GST_WARNING_OBJECT (self, "WASAPI reported glitch in buffer");
|
||||||
|
|
||||||
want_frames = wanted / bpf;
|
/* Copy all the frames we got into the adapter, and then extract at most
|
||||||
|
* @wanted size of frames from it. This helps when ::GetBuffer returns more
|
||||||
|
* data than we can handle right now */
|
||||||
|
{
|
||||||
|
GstBuffer *tmp = gst_buffer_new_allocate (NULL, got_frames * bpf, NULL);
|
||||||
|
gst_buffer_fill (tmp, 0, from, got_frames * bpf);
|
||||||
|
gst_adapter_push (self->adapter, tmp);
|
||||||
|
}
|
||||||
|
|
||||||
/* If GetBuffer is returning more frames than we can handle, all we can do is
|
/* Release all captured buffers; we copied them above */
|
||||||
* hope that this is temporary and that things will settle down later. */
|
hr = IAudioCaptureClient_ReleaseBuffer (self->capture_client, got_frames);
|
||||||
if (G_UNLIKELY (have_frames > want_frames))
|
from = NULL;
|
||||||
GST_WARNING_OBJECT (self, "captured too many frames: have %i, want %i",
|
|
||||||
have_frames, want_frames);
|
|
||||||
|
|
||||||
/* Only copy data that will fit into the allocated buffer of size @length */
|
|
||||||
n_frames = MIN (have_frames, want_frames);
|
|
||||||
read_len = n_frames * bpf;
|
|
||||||
|
|
||||||
GST_DEBUG_OBJECT (self, "have: %i (%i bytes), can read: %i (%i bytes), "
|
|
||||||
"will read: %i (%i bytes)", have_frames, have_frames * bpf,
|
|
||||||
want_frames, wanted, n_frames, read_len);
|
|
||||||
|
|
||||||
memcpy (data, from, read_len);
|
|
||||||
wanted -= read_len;
|
|
||||||
|
|
||||||
/* Always release all captured buffers if we've captured any at all */
|
|
||||||
hr = IAudioCaptureClient_ReleaseBuffer (self->capture_client, have_frames);
|
|
||||||
HR_FAILED_ELEMENT_ERROR_AND (hr, IAudioCaptureClient::ReleaseBuffer, self,
|
HR_FAILED_ELEMENT_ERROR_AND (hr, IAudioCaptureClient::ReleaseBuffer, self,
|
||||||
goto err);
|
goto err);
|
||||||
|
|
||||||
|
want_frames = wanted / bpf;
|
||||||
|
avail_frames = gst_adapter_available (self->adapter) / bpf;
|
||||||
|
|
||||||
|
/* Only copy data that will fit into the allocated buffer of size @length */
|
||||||
|
n_frames = MIN (avail_frames, want_frames);
|
||||||
|
read_len = n_frames * bpf;
|
||||||
|
|
||||||
|
GST_DEBUG_OBJECT (self, "frames captured: %i (%i bytes), "
|
||||||
|
"can read: %i (%i bytes), will read: %i (%i bytes), "
|
||||||
|
"adapter has: %i (%i bytes)", got_frames, got_frames * bpf, want_frames,
|
||||||
|
wanted, n_frames, read_len, avail_frames, avail_frames * bpf);
|
||||||
|
|
||||||
|
memcpy (data, gst_adapter_map (self->adapter, read_len), read_len);
|
||||||
|
gst_adapter_flush (self->adapter, read_len);
|
||||||
|
wanted -= read_len;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
|
|
|
@ -46,6 +46,8 @@ struct _GstWasapiSrc
|
||||||
guint64 client_clock_freq;
|
guint64 client_clock_freq;
|
||||||
IAudioCaptureClient *capture_client;
|
IAudioCaptureClient *capture_client;
|
||||||
HANDLE event_handle;
|
HANDLE event_handle;
|
||||||
|
/* Smooth frames captured from WASAPI, which can be irregular sometimes */
|
||||||
|
GstAdapter *adapter;
|
||||||
/* Client was reset, so it needs to be started again */
|
/* Client was reset, so it needs to be started again */
|
||||||
gboolean client_needs_restart;
|
gboolean client_needs_restart;
|
||||||
|
|
||||||
|
|
Loading…
Reference in a new issue