wasapi: Correctly set ringbuffer segsize/segtotal
authorNirbheek Chauhan <nirbheek@centricular.com>
Tue, 30 Jan 2018 22:21:47 +0000 (03:51 +0530)
committerNirbheek Chauhan <nirbheek@centricular.com>
Wed, 31 Jan 2018 09:28:21 +0000 (14:58 +0530)
This will set the actual-latency-time and actual-buffer-time of the sink
and source.

We completely ignore the latency-time/buffer-time values set
on the element because WASAPI is happiest when it is reading/writing at
the default period. Improving this will likely require the use of the
IAudioClient3 interfaces which are not available in MinGW yet.

https://bugzilla.gnome.org/show_bug.cgi?id=792897

sys/wasapi/gstwasapisink.c
sys/wasapi/gstwasapisrc.c

index 7a89cca..3dbb249 100644 (file)
@@ -346,13 +346,29 @@ gst_wasapi_sink_prepare (GstAudioSink * asink, GstAudioRingBufferSpec * spec)
 {
   GstWasapiSink *self = GST_WASAPI_SINK (asink);
   gboolean res = FALSE;
-  HRESULT hr;
   REFERENCE_TIME latency_rt;
   IAudioRenderClient *render_client = NULL;
+  gint64 default_period, min_period;
+  guint bpf, rate;
+  HRESULT hr;
 
+  hr = IAudioClient_GetDevicePeriod (self->client, &default_period, &min_period);
+  if (hr != S_OK) {
+    GST_ERROR_OBJECT (self, "IAudioClient::GetDevicePeriod failed");
+    goto beach;
+  }
+  GST_INFO_OBJECT (self, "wasapi default period: %" G_GINT64_FORMAT
+      ", min period: %" G_GINT64_FORMAT, default_period, min_period);
+
+  /* Set hnsBufferDuration to 0, which should, in theory, tell the device to
+   * create a buffer with the smallest latency possible. In practice, this is
+   * usually 2 * default_period. See:
+   * https://msdn.microsoft.com/en-us/library/windows/desktop/dd370871(v=vs.85).aspx
+   *
+   * NOTE: min_period is a lie, and I have never seen WASAPI use it as the
+   * current period */
   hr = IAudioClient_Initialize (self->client, AUDCLNT_SHAREMODE_SHARED,
-      AUDCLNT_STREAMFLAGS_EVENTCALLBACK,
-      spec->buffer_time * 10, 0, self->mix_format, NULL);
+      AUDCLNT_STREAMFLAGS_EVENTCALLBACK, 0, 0, self->mix_format, NULL);
   if (hr != S_OK) {
     GST_ELEMENT_ERROR (self, RESOURCE, OPEN_READ, (NULL),
         ("IAudioClient::Initialize () failed: %s",
@@ -360,6 +376,26 @@ gst_wasapi_sink_prepare (GstAudioSink * asink, GstAudioRingBufferSpec * spec)
     goto beach;
   }
 
+  /* Total size of the allocated buffer that we will write to */
+  hr = IAudioClient_GetBufferSize (self->client, &self->buffer_frame_count);
+  if (hr != S_OK) {
+    GST_ERROR_OBJECT (self, "IAudioClient::GetBufferSize failed");
+    goto beach;
+  }
+
+  bpf = GST_AUDIO_INFO_BPF (&spec->info);
+  rate = GST_AUDIO_INFO_RATE (&spec->info);
+  GST_INFO_OBJECT (self, "buffer size is %i frames, bpf is %i bytes, "
+      "rate is %i Hz", self->buffer_frame_count, bpf, rate);
+
+  /* Actual latency-time/buffer-time are different now */
+  spec->segsize = gst_util_uint64_scale_int_round (rate * bpf,
+      default_period * 100, GST_SECOND);
+  spec->segtotal = (self->buffer_frame_count * bpf) / spec->segsize;
+
+  GST_INFO_OBJECT (self, "segsize is %i, segtotal is %i", spec->segsize,
+      spec->segtotal);
+
   /* Get latency for logging */
   hr = IAudioClient_GetStreamLatency (self->client, &latency_rt);
   if (hr != S_OK) {
@@ -376,17 +412,6 @@ gst_wasapi_sink_prepare (GstAudioSink * asink, GstAudioRingBufferSpec * spec)
     goto beach;
   }
 
-  /* Total size of the allocated buffer that we will write to
-   * XXX: Will this ever change while playing? */
-  hr = IAudioClient_GetBufferSize (self->client, &self->buffer_frame_count);
-  if (hr != S_OK) {
-    GST_ERROR_OBJECT (self, "IAudioClient::GetBufferSize failed");
-    goto beach;
-  }
-  GST_INFO_OBJECT (self, "frame count is %i, blockAlign is %i, "
-      "buffer_time is %" G_GINT64_FORMAT, self->buffer_frame_count,
-      self->mix_format->nBlockAlign, spec->buffer_time);
-
   /* Get render sink client and start it up */
   if (!gst_wasapi_util_get_render_client (GST_ELEMENT (self), self->client,
           &render_client)) {
index 3825cf8..20d4df6 100644 (file)
@@ -346,11 +346,27 @@ gst_wasapi_src_prepare (GstAudioSrc * asrc, GstAudioRingBufferSpec * spec)
   guint64 client_clock_freq = 0;
   IAudioCaptureClient *capture_client = NULL;
   REFERENCE_TIME latency_rt;
+  gint64 default_period, min_period;
+  guint bpf, rate, buffer_frames;
   HRESULT hr;
 
+  hr = IAudioClient_GetDevicePeriod (self->client, &default_period, &min_period);
+  if (hr != S_OK) {
+    GST_ERROR_OBJECT (self, "IAudioClient::GetDevicePeriod failed");
+    goto beach;
+  }
+  GST_INFO_OBJECT (self, "wasapi default period: %" G_GINT64_FORMAT
+      ", min period: %" G_GINT64_FORMAT, default_period, min_period);
+
+  /* Set hnsBufferDuration to 0, which should, in theory, tell the device to
+   * create a buffer with the smallest latency possible. In practice, this is
+   * usually 2 * default_period. See:
+   * https://msdn.microsoft.com/en-us/library/windows/desktop/dd370871(v=vs.85).aspx
+   *
+   * NOTE: min_period is a lie, and I have never seen WASAPI use it as the
+   * current period */
   hr = IAudioClient_Initialize (self->client, AUDCLNT_SHAREMODE_SHARED,
-      AUDCLNT_STREAMFLAGS_EVENTCALLBACK, spec->buffer_time * 10, 0,
-      self->mix_format, NULL);
+      AUDCLNT_STREAMFLAGS_EVENTCALLBACK, 0, 0, self->mix_format, NULL);
   if (hr != S_OK) {
     GST_ELEMENT_ERROR (self, RESOURCE, OPEN_READ, (NULL),
         ("IAudioClient::Initialize failed: %s",
@@ -358,7 +374,26 @@ gst_wasapi_src_prepare (GstAudioSrc * asrc, GstAudioRingBufferSpec * spec)
     goto beach;
   }
 
-  /* Get latency for logging */
+  /* Total size in frames of the allocated buffer that we will read from */
+  hr = IAudioClient_GetBufferSize (self->client, &buffer_frames);
+  if (hr != S_OK) {
+    GST_ERROR_OBJECT (self, "IAudioClient::GetBufferSize failed");
+    goto beach;
+  }
+
+  bpf = GST_AUDIO_INFO_BPF (&spec->info);
+  rate = GST_AUDIO_INFO_RATE (&spec->info);
+  GST_INFO_OBJECT (self, "buffer size is %i frames, bpf is %i bytes, "
+      "rate is %i Hz", buffer_frames, bpf, rate);
+
+  spec->segsize = gst_util_uint64_scale_int_round (rate * bpf,
+      default_period * 100, GST_SECOND);
+  spec->segtotal = (buffer_frames * bpf) / spec->segsize;
+
+  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);
   if (hr != S_OK) {
     GST_ERROR_OBJECT (self, "IAudioClient::GetStreamLatency failed");
@@ -386,17 +421,6 @@ gst_wasapi_src_prepare (GstAudioSrc * asrc, GstAudioRingBufferSpec * spec)
     goto beach;
   }
 
-  /* Total size of the allocated buffer that we will read from
-   * XXX: Will this ever change while playing? */
-  hr = IAudioClient_GetBufferSize (self->client, &self->buffer_frame_count);
-  if (hr != S_OK) {
-    GST_ERROR_OBJECT (self, "IAudioClient::GetBufferSize failed");
-    goto beach;
-  }
-  GST_INFO_OBJECT (self, "frame count is %i, blockAlign is %i, "
-      "buffer_time is %" G_GINT64_FORMAT, self->buffer_frame_count,
-      self->mix_format->nBlockAlign, spec->buffer_time);
-
   /* Get capture source client and start it up */
   if (!gst_wasapi_util_get_capture_client (GST_ELEMENT (self), self->client,
           &capture_client)) {