wasapisrc: Correctly handle BUFFERFLAGS_SILENT
[platform/upstream/gstreamer.git] / sys / wasapi / gstwasapisrc.c
index 0ef642b..2286b0d 100644 (file)
@@ -1,5 +1,7 @@
 /*
  * Copyright (C) 2008 Ole André Vadla Ravnås <ole.andre.ravnas@tandberg.com>
+ * Copyright (C) 2018 Centricular Ltd.
+ *   Author: Nirbheek Chauhan <nirbheek@centricular.com>
  *
  * This library is free software; you can redistribute it and/or
  * modify it under the terms of the GNU Library General Public
  *
  * You should have received a copy of the GNU Library General Public
  * License along with this library; if not, write to the
- * Free Software Foundation, Inc., 59 Temple Place - Suite 330,
- * Boston, MA 02111-1307, USA.
+ * Free Software Foundation, Inc., 51 Franklin St, Fifth Floor,
+ * Boston, MA 02110-1301, USA.
  */
 
 /**
  * SECTION:element-wasapisrc
+ * @title: wasapisrc
  *
  * Provides audio capture from the Windows Audio Session API available with
  * Vista and newer.
  *
- * <refsect2>
- * <title>Example pipelines</title>
+ * ## Example pipelines
  * |[
- * gst-launch-0.10 -v wasapisrc ! fakesink
+ * gst-launch-1.0 -v wasapisrc ! fakesink
  * ]| Capture from the default audio device and render to fakesink.
- * </refsect2>
+ *
+ * |[
+ * gst-launch-1.0 -v wasapisrc low-latency=true ! fakesink
+ * ]| Capture from the default audio device with the minimum possible latency and render to fakesink.
+ *
  */
+#ifdef HAVE_CONFIG_H
+#  include <config.h>
+#endif
 
 #include "gstwasapisrc.h"
-#include <gst/audio/gstaudioclock.h>
+
+#include <avrt.h>
 
 GST_DEBUG_CATEGORY_STATIC (gst_wasapi_src_debug);
 #define GST_CAT_DEFAULT gst_wasapi_src_debug
@@ -40,41 +50,54 @@ GST_DEBUG_CATEGORY_STATIC (gst_wasapi_src_debug);
 static GstStaticPadTemplate src_template = GST_STATIC_PAD_TEMPLATE ("src",
     GST_PAD_SRC,
     GST_PAD_ALWAYS,
-    GST_STATIC_CAPS ("audio/x-raw-int, "
-        "width = (int) 16, "
-        "depth = (int) 16, "
-        "rate = (int) 8000, "
-        "channels = (int) 1, "
-        "signed = (boolean) TRUE, "
-        "endianness = (int) " G_STRINGIFY (G_BYTE_ORDER)));
+    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
+/* The clock provided by WASAPI is always off and causes buffers to be late
+ * very quickly on the sink. Disable pending further investigation. */
+#define DEFAULT_PROVIDE_CLOCK FALSE
+
+enum
+{
+  PROP_0,
+  PROP_ROLE,
+  PROP_DEVICE,
+  PROP_LOOPBACK,
+  PROP_EXCLUSIVE,
+  PROP_LOW_LATENCY,
+  PROP_AUDIOCLIENT3
+};
 
 static void gst_wasapi_src_dispose (GObject * object);
 static void gst_wasapi_src_finalize (GObject * object);
-
-static GstClock *gst_wasapi_src_provide_clock (GstElement * element);
-
-static gboolean gst_wasapi_src_start (GstBaseSrc * src);
-static gboolean gst_wasapi_src_stop (GstBaseSrc * src);
-static gboolean gst_wasapi_src_query (GstBaseSrc * src, GstQuery * query);
-
-static GstFlowReturn gst_wasapi_src_create (GstPushSrc * src, GstBuffer ** buf);
-
+static void gst_wasapi_src_set_property (GObject * object, guint prop_id,
+    const GValue * value, GParamSpec * pspec);
+static void gst_wasapi_src_get_property (GObject * object, guint prop_id,
+    GValue * value, GParamSpec * pspec);
+
+static GstCaps *gst_wasapi_src_get_caps (GstBaseSrc * bsrc, GstCaps * filter);
+
+static gboolean gst_wasapi_src_open (GstAudioSrc * asrc);
+static gboolean gst_wasapi_src_close (GstAudioSrc * asrc);
+static gboolean gst_wasapi_src_prepare (GstAudioSrc * asrc,
+    GstAudioRingBufferSpec * spec);
+static gboolean gst_wasapi_src_unprepare (GstAudioSrc * asrc);
+static guint gst_wasapi_src_read (GstAudioSrc * asrc, gpointer data,
+    guint length, GstClockTime * timestamp);
+static guint gst_wasapi_src_delay (GstAudioSrc * asrc);
+static void gst_wasapi_src_reset (GstAudioSrc * asrc);
+
+#if DEFAULT_PROVIDE_CLOCK
 static GstClockTime gst_wasapi_src_get_time (GstClock * clock,
     gpointer user_data);
+#endif
 
-GST_BOILERPLATE (GstWasapiSrc, gst_wasapi_src, GstPushSrc, GST_TYPE_PUSH_SRC);
-
-static void
-gst_wasapi_src_base_init (gpointer gclass)
-{
-  GstElementClass *element_class = GST_ELEMENT_CLASS (gclass);
-
-  gst_element_class_add_static_pad_template (element_class, &src_template);
-  gst_element_class_set_details_simple (element_class, "WasapiSrc",
-      "Source/Audio",
-      "Stream audio from an audio capture device through WASAPI",
-      "Ole André Vadla Ravnås <ole.andre.ravnas@tandberg.com>");
-}
+#define gst_wasapi_src_parent_class parent_class
+G_DEFINE_TYPE (GstWasapiSrc, gst_wasapi_src, GST_TYPE_AUDIO_SRC);
 
 static void
 gst_wasapi_src_class_init (GstWasapiSrcClass * klass)
@@ -82,50 +105,94 @@ gst_wasapi_src_class_init (GstWasapiSrcClass * klass)
   GObjectClass *gobject_class = G_OBJECT_CLASS (klass);
   GstElementClass *gstelement_class = GST_ELEMENT_CLASS (klass);
   GstBaseSrcClass *gstbasesrc_class = GST_BASE_SRC_CLASS (klass);
-  GstPushSrcClass *gstpushsrc_class = GST_PUSH_SRC_CLASS (klass);
+  GstAudioSrcClass *gstaudiosrc_class = GST_AUDIO_SRC_CLASS (klass);
 
   gobject_class->dispose = gst_wasapi_src_dispose;
   gobject_class->finalize = gst_wasapi_src_finalize;
+  gobject_class->set_property = gst_wasapi_src_set_property;
+  gobject_class->get_property = gst_wasapi_src_get_property;
+
+  g_object_class_install_property (gobject_class,
+      PROP_ROLE,
+      g_param_spec_enum ("role", "Role",
+          "Role of the device: communications, multimedia, etc",
+          GST_WASAPI_DEVICE_TYPE_ROLE, DEFAULT_ROLE, G_PARAM_READWRITE |
+          G_PARAM_STATIC_STRINGS | GST_PARAM_MUTABLE_READY));
+
+  g_object_class_install_property (gobject_class,
+      PROP_DEVICE,
+      g_param_spec_string ("device", "Device",
+          "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",
+          "Open the device in exclusive mode",
+          DEFAULT_EXCLUSIVE, G_PARAM_READWRITE | G_PARAM_STATIC_STRINGS));
+
+  g_object_class_install_property (gobject_class,
+      PROP_LOW_LATENCY,
+      g_param_spec_boolean ("low-latency", "Low latency",
+          "Optimize all settings for lowest latency. Always safe to enable.",
+          DEFAULT_LOW_LATENCY, G_PARAM_READWRITE | G_PARAM_STATIC_STRINGS));
+
+  g_object_class_install_property (gobject_class,
+      PROP_AUDIOCLIENT3,
+      g_param_spec_boolean ("use-audioclient3", "Use the AudioClient3 API",
+          "Whether to use the Windows 10 AudioClient3 API when available",
+          DEFAULT_AUDIOCLIENT3, G_PARAM_READWRITE | G_PARAM_STATIC_STRINGS));
+
+  gst_element_class_add_static_pad_template (gstelement_class, &src_template);
+  gst_element_class_set_static_metadata (gstelement_class, "WasapiSrc",
+      "Source/Audio/Hardware",
+      "Stream audio from an audio capture device through WASAPI",
+      "Nirbheek Chauhan <nirbheek@centricular.com>, "
+      "Ole André Vadla Ravnås <ole.andre.ravnas@tandberg.com>");
 
-  gstelement_class->provide_clock = gst_wasapi_src_provide_clock;
-
-  gstbasesrc_class->start = gst_wasapi_src_start;
-  gstbasesrc_class->stop = gst_wasapi_src_stop;
-  gstbasesrc_class->query = gst_wasapi_src_query;
+  gstbasesrc_class->get_caps = GST_DEBUG_FUNCPTR (gst_wasapi_src_get_caps);
 
-  gstpushsrc_class->create = gst_wasapi_src_create;
+  gstaudiosrc_class->open = GST_DEBUG_FUNCPTR (gst_wasapi_src_open);
+  gstaudiosrc_class->close = GST_DEBUG_FUNCPTR (gst_wasapi_src_close);
+  gstaudiosrc_class->read = GST_DEBUG_FUNCPTR (gst_wasapi_src_read);
+  gstaudiosrc_class->prepare = GST_DEBUG_FUNCPTR (gst_wasapi_src_prepare);
+  gstaudiosrc_class->unprepare = GST_DEBUG_FUNCPTR (gst_wasapi_src_unprepare);
+  gstaudiosrc_class->delay = GST_DEBUG_FUNCPTR (gst_wasapi_src_delay);
+  gstaudiosrc_class->reset = GST_DEBUG_FUNCPTR (gst_wasapi_src_reset);
 
   GST_DEBUG_CATEGORY_INIT (gst_wasapi_src_debug, "wasapisrc",
       0, "Windows audio session API source");
 }
 
 static void
-gst_wasapi_src_init (GstWasapiSrc * self, GstWasapiSrcClass * gclass)
+gst_wasapi_src_init (GstWasapiSrc * self)
 {
-  GstBaseSrc *basesrc = GST_BASE_SRC (self);
-
-  gst_base_src_set_format (basesrc, GST_FORMAT_TIME);
-  gst_base_src_set_live (basesrc, TRUE);
+#if DEFAULT_PROVIDE_CLOCK
+  /* override with a custom clock */
+  if (GST_AUDIO_BASE_SRC (self)->clock)
+    gst_object_unref (GST_AUDIO_BASE_SRC (self)->clock);
 
-  self->rate = 8000;
-  self->buffer_time = 20 * GST_MSECOND;
-  self->period_time = 20 * GST_MSECOND;
-  self->latency = GST_CLOCK_TIME_NONE;
-  self->samples_per_buffer = self->rate / (GST_SECOND / self->period_time);
-
-  self->start_time = GST_CLOCK_TIME_NONE;
-  self->next_time = GST_CLOCK_TIME_NONE;
-
-#if GST_CHECK_VERSION(0, 10, 31) || (GST_CHECK_VERSION(0, 10, 30) && GST_VERSION_NANO > 0)
-  self->clock = gst_audio_clock_new_full ("GstWasapiSrcClock",
+  GST_AUDIO_BASE_SRC (self)->clock = gst_audio_clock_new ("GstWasapiSrcClock",
       gst_wasapi_src_get_time, gst_object_ref (self),
       (GDestroyNotify) gst_object_unref);
-#else
-  self->clock = gst_audio_clock_new ("GstWasapiSrcClock",
-      gst_wasapi_src_get_time, self);
 #endif
 
-  CoInitialize (NULL);
+  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);
+  self->client_needs_restart = FALSE;
+  self->adapter = gst_adapter_new ();
+
+  CoInitializeEx (NULL, COINIT_MULTITHREADED);
 }
 
 static void
@@ -133,9 +200,24 @@ gst_wasapi_src_dispose (GObject * object)
 {
   GstWasapiSrc *self = GST_WASAPI_SRC (object);
 
-  if (self->clock != NULL) {
-    gst_object_unref (self->clock);
-    self->clock = NULL;
+  if (self->event_handle != NULL) {
+    CloseHandle (self->event_handle);
+    self->event_handle = NULL;
+  }
+
+  if (self->client_clock != NULL) {
+    IUnknown_Release (self->client_clock);
+    self->client_clock = NULL;
+  }
+
+  if (self->client != NULL) {
+    IUnknown_Release (self->client);
+    self->client = NULL;
+  }
+
+  if (self->capture_client != NULL) {
+    IUnknown_Release (self->capture_client);
+    self->capture_client = NULL;
   }
 
   G_OBJECT_CLASS (parent_class)->dispose (object);
@@ -146,105 +228,309 @@ gst_wasapi_src_finalize (GObject * object)
 {
   GstWasapiSrc *self = GST_WASAPI_SRC (object);
 
+  CoTaskMemFree (self->mix_format);
+  self->mix_format = NULL;
+
   CoUninitialize ();
 
+  g_clear_pointer (&self->cached_caps, gst_caps_unref);
+  g_clear_pointer (&self->positions, 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);
 }
 
-static GstClock *
-gst_wasapi_src_provide_clock (GstElement * element)
+static void
+gst_wasapi_src_set_property (GObject * object, guint prop_id,
+    const GValue * value, GParamSpec * pspec)
 {
-  GstWasapiSrc *self = GST_WASAPI_SRC (element);
-  GstClock *clock;
-
-  GST_OBJECT_LOCK (self);
+  GstWasapiSrc *self = GST_WASAPI_SRC (object);
 
-  if (self->client_clock == NULL)
-    goto wrong_state;
+  switch (prop_id) {
+    case PROP_ROLE:
+      self->role = gst_wasapi_device_role_to_erole (g_value_get_enum (value));
+      break;
+    case PROP_DEVICE:
+    {
+      const gchar *device = g_value_get_string (value);
+      g_free (self->device_strid);
+      self->device_strid =
+          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;
+      break;
+    case PROP_LOW_LATENCY:
+      self->low_latency = g_value_get_boolean (value);
+      break;
+    case PROP_AUDIOCLIENT3:
+      self->try_audioclient3 = g_value_get_boolean (value);
+      break;
+    default:
+      G_OBJECT_WARN_INVALID_PROPERTY_ID (object, prop_id, pspec);
+      break;
+  }
+}
 
-  clock = GST_CLOCK (gst_object_ref (self->clock));
+static void
+gst_wasapi_src_get_property (GObject * object, guint prop_id,
+    GValue * value, GParamSpec * pspec)
+{
+  GstWasapiSrc *self = GST_WASAPI_SRC (object);
 
-  GST_OBJECT_UNLOCK (self);
-  return clock;
-
-  /* ERRORS */
-wrong_state:
-  {
-    GST_OBJECT_UNLOCK (self);
-    GST_DEBUG_OBJECT (self, "IAudioClock not acquired");
-    return NULL;
+  switch (prop_id) {
+    case PROP_ROLE:
+      g_value_set_enum (value, gst_wasapi_erole_to_device_role (self->role));
+      break;
+    case PROP_DEVICE:
+      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);
+      break;
+    case PROP_LOW_LATENCY:
+      g_value_set_boolean (value, self->low_latency);
+      break;
+    case PROP_AUDIOCLIENT3:
+      g_value_set_boolean (value, self->try_audioclient3);
+      break;
+    default:
+      G_OBJECT_WARN_INVALID_PROPERTY_ID (object, prop_id, pspec);
+      break;
   }
 }
 
 static gboolean
-gst_wasapi_src_start (GstBaseSrc * src)
+gst_wasapi_src_can_audioclient3 (GstWasapiSrc * self)
 {
-  GstWasapiSrc *self = GST_WASAPI_SRC (src);
-  gboolean res = FALSE;
-  IAudioClient *client = NULL;
-  IAudioClock *client_clock = NULL;
-  guint64 client_clock_freq = 0;
-  IAudioCaptureClient *capture_client = NULL;
-  HRESULT hr;
+  if (self->sharemode == AUDCLNT_SHAREMODE_SHARED &&
+      self->try_audioclient3 && gst_wasapi_util_have_audioclient3 ())
+    return TRUE;
+  return FALSE;
+}
 
-  if (!gst_wasapi_util_get_default_device_client (GST_ELEMENT (self),
-          TRUE, self->rate, self->buffer_time, self->period_time, 0, &client,
-          &self->latency))
-    goto beach;
+static GstCaps *
+gst_wasapi_src_get_caps (GstBaseSrc * bsrc, GstCaps * filter)
+{
+  GstWasapiSrc *self = GST_WASAPI_SRC (bsrc);
+  WAVEFORMATEX *format = NULL;
+  GstCaps *caps = NULL;
 
-  hr = IAudioClient_GetService (client, &IID_IAudioClock, &client_clock);
-  if (hr != S_OK) {
-    GST_ERROR_OBJECT (self, "IAudioClient::GetService (IID_IAudioClock) "
-        "failed");
-    goto beach;
-  }
+  GST_DEBUG_OBJECT (self, "entering get caps");
 
-  hr = IAudioClock_GetFrequency (client_clock, &client_clock_freq);
-  if (hr != S_OK) {
-    GST_ERROR_OBJECT (self, "IAudioClock::GetFrequency () failed");
-    goto beach;
+  if (self->cached_caps) {
+    caps = gst_caps_ref (self->cached_caps);
+  } else {
+    GstCaps *template_caps;
+    gboolean ret;
+
+    template_caps = gst_pad_get_pad_template_caps (bsrc->srcpad);
+
+    if (!self->client) {
+      caps = template_caps;
+      goto out;
+    }
+
+    ret = gst_wasapi_util_get_device_format (GST_ELEMENT (self),
+        self->sharemode, self->device, self->client, &format);
+    if (!ret) {
+      GST_ELEMENT_ERROR (self, STREAM, FORMAT, (NULL),
+          ("failed to detect format"));
+      gst_caps_unref (template_caps);
+      return NULL;
+    }
+
+    gst_wasapi_util_parse_waveformatex ((WAVEFORMATEXTENSIBLE *) format,
+        template_caps, &caps, &self->positions);
+    if (caps == NULL) {
+      GST_ELEMENT_ERROR (self, STREAM, FORMAT, (NULL), ("unknown format"));
+      gst_caps_unref (template_caps);
+      return NULL;
+    }
+
+    {
+      gchar *pos_str = gst_audio_channel_positions_to_string (self->positions,
+          format->nChannels);
+      GST_INFO_OBJECT (self, "positions are: %s", pos_str);
+      g_free (pos_str);
+    }
+
+    self->mix_format = format;
+    gst_caps_replace (&self->cached_caps, caps);
+    gst_caps_unref (template_caps);
   }
 
-  hr = IAudioClient_GetService (client, &IID_IAudioCaptureClient,
-      &capture_client);
-  if (hr != S_OK) {
-    GST_ERROR_OBJECT (self, "IAudioClient::GetService "
-        "(IID_IAudioCaptureClient) failed");
-    goto beach;
+  if (filter) {
+    GstCaps *filtered =
+        gst_caps_intersect_full (filter, caps, GST_CAPS_INTERSECT_FIRST);
+    gst_caps_unref (caps);
+    caps = filtered;
   }
 
-  hr = IAudioClient_Start (client);
-  if (hr != S_OK) {
-    GST_ERROR_OBJECT (self, "IAudioClient::Start failed");
+out:
+  GST_DEBUG_OBJECT (self, "returning caps %" GST_PTR_FORMAT, caps);
+  return caps;
+}
+
+static gboolean
+gst_wasapi_src_open (GstAudioSrc * asrc)
+{
+  GstWasapiSrc *self = GST_WASAPI_SRC (asrc);
+  gboolean res = FALSE;
+  IAudioClient *client = NULL;
+  IMMDevice *device = NULL;
+
+  if (self->client)
+    return TRUE;
+
+  /* FIXME: Switching the default device does not switch the stream to it,
+   * 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),
+          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"));
+    else
+      GST_ELEMENT_ERROR (self, RESOURCE, OPEN_READ, (NULL),
+          ("Failed to open device %S", self->device_strid));
     goto beach;
   }
 
   self->client = client;
-  self->client_clock = client_clock;
-  self->client_clock_freq = client_clock_freq;
-  self->capture_client = capture_client;
-
+  self->device = device;
   res = TRUE;
 
 beach:
-  if (!res) {
-    if (capture_client != NULL)
-      IUnknown_Release (capture_client);
 
-    if (client_clock != NULL)
-      IUnknown_Release (client_clock);
+  return res;
+}
+
+static gboolean
+gst_wasapi_src_close (GstAudioSrc * asrc)
+{
+  GstWasapiSrc *self = GST_WASAPI_SRC (asrc);
+
+  if (self->device != NULL) {
+    IUnknown_Release (self->device);
+    self->device = NULL;
+  }
+
+  if (self->client != NULL) {
+    IUnknown_Release (self->client);
+    self->client = NULL;
+  }
+
+  return TRUE;
+}
+
+static gboolean
+gst_wasapi_src_prepare (GstAudioSrc * asrc, GstAudioRingBufferSpec * spec)
+{
+  GstWasapiSrc *self = GST_WASAPI_SRC (asrc);
+  gboolean res = FALSE;
+  REFERENCE_TIME latency_rt;
+  guint bpf, rate, devicep_frames, buffer_frames;
+  HRESULT hr;
+
+  CoInitializeEx (NULL, COINIT_MULTITHREADED);
+
+  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))
+      goto beach;
+  } else {
+    if (!gst_wasapi_util_initialize_audioclient (GST_ELEMENT (self), spec,
+            self->client, self->mix_format, self->sharemode, self->low_latency,
+            self->loopback, &devicep_frames))
+      goto beach;
+  }
+
+  bpf = GST_AUDIO_INFO_BPF (&spec->info);
+  rate = GST_AUDIO_INFO_RATE (&spec->info);
+
+  /* Total size in frames of the allocated buffer that we will read from */
+  hr = IAudioClient_GetBufferSize (self->client, &buffer_frames);
+  HR_FAILED_GOTO (hr, IAudioClient::GetBufferSize, beach);
 
-    if (client != NULL)
-      IUnknown_Release (client);
+  GST_INFO_OBJECT (self, "buffer size is %i frames, device period is %i "
+      "frames, bpf is %i bytes, rate is %i Hz", buffer_frames,
+      devicep_frames, bpf, rate);
+
+  /* Actual latency-time/buffer-time will be different now */
+  spec->segsize = devicep_frames * bpf;
+
+  /* We need a minimum of 2 segments to ensure glitch-free playback */
+  spec->segtotal = MAX (buffer_frames * bpf / spec->segsize, 2);
+
+  GST_INFO_OBJECT (self, "segsize is %i, segtotal is %i", spec->segsize,
+      spec->segtotal);
+
+  /* Get WASAPI latency for logging */
+  hr = IAudioClient_GetStreamLatency (self->client, &latency_rt);
+  HR_FAILED_GOTO (hr, IAudioClient::GetStreamLatency, beach);
+
+  GST_INFO_OBJECT (self, "wasapi stream latency: %" G_GINT64_FORMAT " (%"
+      G_GINT64_FORMAT " ms)", latency_rt, latency_rt / 10000);
+
+  /* Set the event handler which will trigger reads */
+  hr = IAudioClient_SetEventHandle (self->client, self->event_handle);
+  HR_FAILED_GOTO (hr, IAudioClient::SetEventHandle, beach);
+
+  /* Get the clock and the clock freq */
+  if (!gst_wasapi_util_get_clock (GST_ELEMENT (self), self->client,
+          &self->client_clock))
+    goto beach;
+
+  hr = IAudioClock_GetFrequency (self->client_clock, &self->client_clock_freq);
+  HR_FAILED_GOTO (hr, IAudioClock::GetFrequency, beach);
+
+  GST_INFO_OBJECT (self, "wasapi clock freq is %" G_GUINT64_FORMAT,
+      self->client_clock_freq);
+
+  /* Get capture source client and start it up */
+  if (!gst_wasapi_util_get_capture_client (GST_ELEMENT (self), self->client,
+          &self->capture_client)) {
+    goto beach;
   }
 
+  hr = IAudioClient_Start (self->client);
+  HR_FAILED_GOTO (hr, IAudioClock::Start, beach);
+  self->client_needs_restart = FALSE;
+
+  gst_audio_ring_buffer_set_channel_positions (GST_AUDIO_BASE_SRC
+      (self)->ringbuffer, self->positions);
+
+  res = TRUE;
+beach:
+  /* unprepare() is not called if prepare() fails, but we want it to be, so call
+   * it manually when needed */
+  if (!res)
+    gst_wasapi_src_unprepare (asrc);
+
   return res;
 }
 
 static gboolean
-gst_wasapi_src_stop (GstBaseSrc * src)
+gst_wasapi_src_unprepare (GstAudioSrc * asrc)
 {
-  GstWasapiSrc *self = GST_WASAPI_SRC (src);
+  GstWasapiSrc *self = GST_WASAPI_SRC (asrc);
 
   if (self->client != NULL) {
     IAudioClient_Stop (self->client);
@@ -260,162 +546,159 @@ gst_wasapi_src_stop (GstBaseSrc * src)
     self->client_clock = NULL;
   }
 
-  if (self->client != NULL) {
-    IUnknown_Release (self->client);
-    self->client = NULL;
-  }
+  self->client_clock_freq = 0;
+
+  CoUninitialize ();
 
   return TRUE;
 }
 
-static gboolean
-gst_wasapi_src_query (GstBaseSrc * src, GstQuery * query)
+static guint
+gst_wasapi_src_read (GstAudioSrc * asrc, gpointer data, guint length,
+    GstClockTime * timestamp)
 {
-  GstWasapiSrc *self = GST_WASAPI_SRC (src);
-  gboolean ret = FALSE;
+  GstWasapiSrc *self = GST_WASAPI_SRC (asrc);
+  HRESULT hr;
+  gint16 *from = NULL;
+  guint wanted = length;
+  guint bpf;
+  DWORD flags;
 
-  GST_DEBUG_OBJECT (self, "query for %s",
-      gst_query_type_get_name (GST_QUERY_TYPE (query)));
+  GST_OBJECT_LOCK (self);
+  if (self->client_needs_restart) {
+    hr = IAudioClient_Start (self->client);
+    HR_FAILED_ELEMENT_ERROR_AND (hr, IAudioClient::Start, self,
+        GST_OBJECT_UNLOCK (self); goto err);
+    self->client_needs_restart = FALSE;
+    gst_adapter_clear (self->adapter);
+  }
 
-  switch (GST_QUERY_TYPE (query)) {
-    case GST_QUERY_LATENCY:{
-      GstClockTime min_latency, max_latency;
+  bpf = self->mix_format->nBlockAlign;
+  GST_OBJECT_UNLOCK (self);
 
-      min_latency = self->latency + self->period_time;
-      max_latency = min_latency;
+  /* 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;
+  }
 
-      GST_DEBUG_OBJECT (self, "reporting latency of min %" GST_TIME_FORMAT
-          " max %" GST_TIME_FORMAT,
-          GST_TIME_ARGS (min_latency), GST_TIME_ARGS (max_latency));
+  while (wanted > 0) {
+    DWORD dwWaitResult;
+    guint got_frames, avail_frames, n_frames, want_frames, read_len;
 
-      gst_query_set_latency (query, TRUE, min_latency, max_latency);
-      ret = TRUE;
-      break;
+    /* Wait for data to become available */
+    dwWaitResult = WaitForSingleObject (self->event_handle, INFINITE);
+    if (dwWaitResult != WAIT_OBJECT_0) {
+      GST_ERROR_OBJECT (self, "Error waiting for event handle: %x",
+          (guint) dwWaitResult);
+      goto err;
     }
 
-    default:
-      ret = GST_BASE_SRC_CLASS (parent_class)->query (src, query);
-      break;
-  }
+    hr = IAudioCaptureClient_GetBuffer (self->capture_client,
+        (BYTE **) & from, &got_frames, &flags, NULL, NULL);
+    if (hr != S_OK) {
+      if (hr == AUDCLNT_S_BUFFER_EMPTY) {
+        gchar *msg = gst_wasapi_util_hresult_to_string (hr);
+        GST_WARNING_OBJECT (self, "IAudioCaptureClient::GetBuffer failed: %s"
+            ", retrying", msg);
+        g_free (msg);
+        length = 0;
+        goto out;
+      }
+      HR_FAILED_ELEMENT_ERROR_AND (hr, IAudioCaptureClient::GetBuffer, self,
+          goto err);
+    }
 
-  return ret;
-}
+    if (G_UNLIKELY (flags != 0)) {
+      /* https://docs.microsoft.com/en-us/windows/win32/api/audioclient/ne-audioclient-_audclnt_bufferflags */
+      if (flags & AUDCLNT_BUFFERFLAGS_DATA_DISCONTINUITY)
+        GST_DEBUG_OBJECT (self, "WASAPI reported discontinuity (glitch?)");
+      if (flags & AUDCLNT_BUFFERFLAGS_TIMESTAMP_ERROR)
+        GST_DEBUG_OBJECT (self, "WASAPI reported a timestamp error");
+    }
 
-static GstFlowReturn
-gst_wasapi_src_create (GstPushSrc * src, GstBuffer ** buf)
-{
-  GstWasapiSrc *self = GST_WASAPI_SRC (src);
-  GstFlowReturn ret = GST_FLOW_OK;
-  GstClock *clock;
-  GstClockTime timestamp, duration = self->period_time;
-  HRESULT hr;
-  gint16 *samples = NULL;
-  guint32 nsamples_read = 0, nsamples;
-  DWORD flags = 0;
-  guint64 devpos;
+    /* 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);
+      /* If flags has AUDCLNT_BUFFERFLAGS_SILENT, we will ignore the actual
+       * data and write out silence, see:
+       * https://docs.microsoft.com/en-us/windows/win32/api/audioclient/ne-audioclient-_audclnt_bufferflags */
+      if (flags & AUDCLNT_BUFFERFLAGS_SILENT)
+        memset (from, 0, got_frames * bpf);
+      gst_buffer_fill (tmp, 0, from, got_frames * bpf);
+      gst_adapter_push (self->adapter, tmp);
+    }
 
-  GST_OBJECT_LOCK (self);
-  clock = GST_ELEMENT_CLOCK (self);
-  if (clock != NULL)
-    gst_object_ref (clock);
-  GST_OBJECT_UNLOCK (self);
+    /* Release all captured buffers; we copied them above */
+    hr = IAudioCaptureClient_ReleaseBuffer (self->capture_client, got_frames);
+    from = NULL;
+    HR_FAILED_ELEMENT_ERROR_AND (hr, IAudioCaptureClient::ReleaseBuffer, self,
+        goto err);
 
-  if (clock != NULL && GST_CLOCK_TIME_IS_VALID (self->next_time)) {
-    GstClockID id;
+    want_frames = wanted / bpf;
+    avail_frames = gst_adapter_available (self->adapter) / bpf;
 
-    id = gst_clock_new_single_shot_id (clock, self->next_time);
-    gst_clock_id_wait (id, NULL);
-    gst_clock_id_unref (id);
-  }
+    /* 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;
 
-  do {
-    hr = IAudioCaptureClient_GetBuffer (self->capture_client,
-        (BYTE **) & samples, &nsamples_read, &flags, &devpos, NULL);
-  }
-  while (hr == AUDCLNT_S_BUFFER_EMPTY);
+    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);
 
-  if (hr != S_OK) {
-    GST_ERROR_OBJECT (self, "IAudioCaptureClient::GetBuffer () failed: %s",
-        gst_wasapi_util_hresult_to_string (hr));
-    ret = GST_FLOW_ERROR;
-    goto beach;
+    memcpy (data, gst_adapter_map (self->adapter, read_len), read_len);
+    gst_adapter_flush (self->adapter, read_len);
+    wanted -= read_len;
   }
 
-  if (flags != 0) {
-    GST_WARNING_OBJECT (self, "devpos %" G_GUINT64_FORMAT ": flags=0x%08x",
-        devpos, flags);
-  }
 
-  /* FIXME: Why do we get 1024 sometimes and not a multiple of
-   *        samples_per_buffer? Shouldn't WASAPI provide a DISCONT
-   *        flag if we read too slow?
-   */
-  nsamples = nsamples_read;
-  g_assert (nsamples >= self->samples_per_buffer);
-  if (nsamples > self->samples_per_buffer) {
-    GST_WARNING_OBJECT (self,
-        "devpos %" G_GUINT64_FORMAT ": got %d samples, expected %d, clipping!",
-        devpos, nsamples, self->samples_per_buffer);
-
-    nsamples = self->samples_per_buffer;
-  }
-
-  if (clock == NULL || clock == self->clock) {
-    timestamp =
-        gst_util_uint64_scale (devpos, GST_SECOND, self->client_clock_freq);
-  } else {
-    GstClockTime base_time;
-
-    timestamp = gst_clock_get_time (clock);
-
-    base_time = GST_ELEMENT_CAST (self)->base_time;
-    if (timestamp > base_time)
-      timestamp -= base_time;
-    else
-      timestamp = 0;
+out:
+  return length;
 
-    if (timestamp > duration)
-      timestamp -= duration;
-    else
-      timestamp = 0;
-  }
+err:
+  length = -1;
+  goto out;
+}
 
-  ret = gst_pad_alloc_buffer_and_set_caps (GST_BASE_SRC_PAD (self),
-      devpos,
-      nsamples * sizeof (gint16), GST_PAD_CAPS (GST_BASE_SRC_PAD (self)), buf);
+static guint
+gst_wasapi_src_delay (GstAudioSrc * asrc)
+{
+  GstWasapiSrc *self = GST_WASAPI_SRC (asrc);
+  guint delay = 0;
+  HRESULT hr;
 
-  if (ret == GST_FLOW_OK) {
-    guint i;
-    gint16 *dst;
+  hr = IAudioClient_GetCurrentPadding (self->client, &delay);
+  HR_FAILED_RET (hr, IAudioClock::GetCurrentPadding, 0);
 
-    GST_BUFFER_OFFSET_END (*buf) = devpos + self->samples_per_buffer;
-    GST_BUFFER_TIMESTAMP (*buf) = timestamp;
-    GST_BUFFER_DURATION (*buf) = duration;
+  return delay;
+}
 
-    dst = (gint16 *) GST_BUFFER_DATA (*buf);
-    for (i = 0; i < nsamples; i++) {
-      *dst = *samples;
+static void
+gst_wasapi_src_reset (GstAudioSrc * asrc)
+{
+  GstWasapiSrc *self = GST_WASAPI_SRC (asrc);
+  HRESULT hr;
 
-      samples += 2;
-      dst++;
-    }
-  }
+  if (!self->client)
+    return;
 
-  hr = IAudioCaptureClient_ReleaseBuffer (self->capture_client, nsamples_read);
-  if (hr != S_OK) {
-    GST_ERROR_OBJECT (self, "IAudioCaptureClient::ReleaseBuffer () failed: %s",
-        gst_wasapi_util_hresult_to_string (hr));
-    ret = GST_FLOW_ERROR;
-    goto beach;
-  }
+  GST_OBJECT_LOCK (self);
+  hr = IAudioClient_Stop (self->client);
+  HR_FAILED_RET (hr, IAudioClock::Stop,);
 
-beach:
-  if (clock != NULL)
-    gst_object_unref (clock);
+  hr = IAudioClient_Reset (self->client);
+  HR_FAILED_RET (hr, IAudioClock::Reset,);
 
-  return ret;
+  self->client_needs_restart = TRUE;
+  GST_OBJECT_UNLOCK (self);
 }
 
+#if DEFAULT_PROVIDE_CLOCK
 static GstClockTime
 gst_wasapi_src_get_time (GstClock * clock, gpointer user_data)
 {
@@ -428,8 +711,7 @@ gst_wasapi_src_get_time (GstClock * clock, gpointer user_data)
     return GST_CLOCK_TIME_NONE;
 
   hr = IAudioClock_GetPosition (self->client_clock, &devpos, NULL);
-  if (G_UNLIKELY (hr != S_OK))
-    return GST_CLOCK_TIME_NONE;
+  HR_FAILED_RET (hr, IAudioClock::GetPosition, GST_CLOCK_TIME_NONE);
 
   result = gst_util_uint64_scale_int (devpos, GST_SECOND,
       self->client_clock_freq);
@@ -443,3 +725,4 @@ gst_wasapi_src_get_time (GstClock * clock, gpointer user_data)
 
   return result;
 }
+#endif