wasapi: Allow opening devices in exclusive mode
[platform/upstream/gstreamer.git] / sys / wasapi / gstwasapisrc.c
1 /*
2  * Copyright (C) 2008 Ole André Vadla Ravnås <ole.andre.ravnas@tandberg.com>
3  * Copyright (C) 2018 Centricular Ltd.
4  *   Author: Nirbheek Chauhan <nirbheek@centricular.com>
5  *
6  * This library is free software; you can redistribute it and/or
7  * modify it under the terms of the GNU Library General Public
8  * License as published by the Free Software Foundation; either
9  * version 2 of the License, or (at your option) any later version.
10  *
11  * This library is distributed in the hope that it will be useful,
12  * but WITHOUT ANY WARRANTY; without even the implied warranty of
13  * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
14  * Library General Public License for more details.
15  *
16  * You should have received a copy of the GNU Library General Public
17  * License along with this library; if not, write to the
18  * Free Software Foundation, Inc., 51 Franklin St, Fifth Floor,
19  * Boston, MA 02110-1301, USA.
20  */
21
22 /**
23  * SECTION:element-wasapisrc
24  * @title: wasapisrc
25  *
26  * Provides audio capture from the Windows Audio Session API available with
27  * Vista and newer.
28  *
29  * ## Example pipelines
30  * |[
31  * gst-launch-1.0 -v wasapisrc ! fakesink
32  * ]| Capture from the default audio device and render to fakesink.
33  *
34  */
35 #ifdef HAVE_CONFIG_H
36 #  include <config.h>
37 #endif
38
39 #include "gstwasapisrc.h"
40
41 GST_DEBUG_CATEGORY_STATIC (gst_wasapi_src_debug);
42 #define GST_CAT_DEFAULT gst_wasapi_src_debug
43
44 static GstStaticPadTemplate src_template = GST_STATIC_PAD_TEMPLATE ("src",
45     GST_PAD_SRC,
46     GST_PAD_ALWAYS,
47     GST_STATIC_CAPS (GST_WASAPI_STATIC_CAPS));
48
49 #define DEFAULT_ROLE      GST_WASAPI_DEVICE_ROLE_CONSOLE
50 #define DEFAULT_EXCLUSIVE FALSE
51
52 enum
53 {
54   PROP_0,
55   PROP_ROLE,
56   PROP_DEVICE,
57   PROP_EXCLUSIVE
58 };
59
60 static void gst_wasapi_src_dispose (GObject * object);
61 static void gst_wasapi_src_finalize (GObject * object);
62 static void gst_wasapi_src_set_property (GObject * object, guint prop_id,
63     const GValue * value, GParamSpec * pspec);
64 static void gst_wasapi_src_get_property (GObject * object, guint prop_id,
65     GValue * value, GParamSpec * pspec);
66
67 static GstCaps *gst_wasapi_src_get_caps (GstBaseSrc * bsrc, GstCaps * filter);
68
69 static gboolean gst_wasapi_src_open (GstAudioSrc * asrc);
70 static gboolean gst_wasapi_src_close (GstAudioSrc * asrc);
71 static gboolean gst_wasapi_src_prepare (GstAudioSrc * asrc,
72     GstAudioRingBufferSpec * spec);
73 static gboolean gst_wasapi_src_unprepare (GstAudioSrc * asrc);
74 static guint gst_wasapi_src_read (GstAudioSrc * asrc, gpointer data,
75     guint length, GstClockTime * timestamp);
76 static guint gst_wasapi_src_delay (GstAudioSrc * asrc);
77 static void gst_wasapi_src_reset (GstAudioSrc * asrc);
78
79 static GstClockTime gst_wasapi_src_get_time (GstClock * clock,
80     gpointer user_data);
81
82 #define gst_wasapi_src_parent_class parent_class
83 G_DEFINE_TYPE (GstWasapiSrc, gst_wasapi_src, GST_TYPE_AUDIO_SRC);
84
85 static void
86 gst_wasapi_src_class_init (GstWasapiSrcClass * klass)
87 {
88   GObjectClass *gobject_class = G_OBJECT_CLASS (klass);
89   GstElementClass *gstelement_class = GST_ELEMENT_CLASS (klass);
90   GstBaseSrcClass *gstbasesrc_class = GST_BASE_SRC_CLASS (klass);
91   GstAudioSrcClass *gstaudiosrc_class = GST_AUDIO_SRC_CLASS (klass);
92
93   gobject_class->dispose = gst_wasapi_src_dispose;
94   gobject_class->finalize = gst_wasapi_src_finalize;
95   gobject_class->set_property = gst_wasapi_src_set_property;
96   gobject_class->get_property = gst_wasapi_src_get_property;
97
98   g_object_class_install_property (gobject_class,
99       PROP_ROLE,
100       g_param_spec_enum ("role", "Role",
101           "Role of the device: communications, multimedia, etc",
102           GST_WASAPI_DEVICE_TYPE_ROLE, DEFAULT_ROLE, G_PARAM_READWRITE |
103           G_PARAM_STATIC_STRINGS | GST_PARAM_MUTABLE_READY));
104
105   g_object_class_install_property (gobject_class,
106       PROP_DEVICE,
107       g_param_spec_string ("device", "Device",
108           "WASAPI playback device as a GUID string",
109           NULL, G_PARAM_READWRITE | G_PARAM_STATIC_STRINGS));
110
111   g_object_class_install_property (gobject_class,
112       PROP_EXCLUSIVE,
113       g_param_spec_boolean ("exclusive", "Exclusive mode",
114           "Open the device in exclusive mode",
115           DEFAULT_EXCLUSIVE, G_PARAM_READWRITE | G_PARAM_STATIC_STRINGS));
116
117   gst_element_class_add_static_pad_template (gstelement_class, &src_template);
118   gst_element_class_set_static_metadata (gstelement_class, "WasapiSrc",
119       "Source/Audio",
120       "Stream audio from an audio capture device through WASAPI",
121       "Ole André Vadla Ravnås <ole.andre.ravnas@tandberg.com>");
122
123   gstbasesrc_class->get_caps = GST_DEBUG_FUNCPTR (gst_wasapi_src_get_caps);
124
125   gstaudiosrc_class->open = GST_DEBUG_FUNCPTR (gst_wasapi_src_open);
126   gstaudiosrc_class->close = GST_DEBUG_FUNCPTR (gst_wasapi_src_close);
127   gstaudiosrc_class->read = GST_DEBUG_FUNCPTR (gst_wasapi_src_read);
128   gstaudiosrc_class->prepare = GST_DEBUG_FUNCPTR (gst_wasapi_src_prepare);
129   gstaudiosrc_class->unprepare = GST_DEBUG_FUNCPTR (gst_wasapi_src_unprepare);
130   gstaudiosrc_class->delay = GST_DEBUG_FUNCPTR (gst_wasapi_src_delay);
131   gstaudiosrc_class->reset = GST_DEBUG_FUNCPTR (gst_wasapi_src_reset);
132
133   GST_DEBUG_CATEGORY_INIT (gst_wasapi_src_debug, "wasapisrc",
134       0, "Windows audio session API source");
135 }
136
137 static void
138 gst_wasapi_src_init (GstWasapiSrc * self)
139 {
140   /* override with a custom clock */
141   if (GST_AUDIO_BASE_SRC (self)->clock)
142     gst_object_unref (GST_AUDIO_BASE_SRC (self)->clock);
143
144   GST_AUDIO_BASE_SRC (self)->clock = gst_audio_clock_new ("GstWasapiSrcClock",
145       gst_wasapi_src_get_time, gst_object_ref (self),
146       (GDestroyNotify) gst_object_unref);
147
148   self->event_handle = CreateEvent (NULL, FALSE, FALSE, NULL);
149
150   CoInitialize (NULL);
151 }
152
153 static void
154 gst_wasapi_src_dispose (GObject * object)
155 {
156   GstWasapiSrc *self = GST_WASAPI_SRC (object);
157
158   if (self->event_handle != NULL) {
159     CloseHandle (self->event_handle);
160     self->event_handle = NULL;
161   }
162
163   if (self->client_clock != NULL) {
164     IUnknown_Release (self->client_clock);
165     self->client_clock = NULL;
166   }
167
168   if (self->client != NULL) {
169     IUnknown_Release (self->client);
170     self->client = NULL;
171   }
172
173   if (self->capture_client != NULL) {
174     IUnknown_Release (self->capture_client);
175     self->capture_client = NULL;
176   }
177
178   G_OBJECT_CLASS (parent_class)->dispose (object);
179 }
180
181 static void
182 gst_wasapi_src_finalize (GObject * object)
183 {
184   GstWasapiSrc *self = GST_WASAPI_SRC (object);
185
186   g_clear_pointer (&self->mix_format, CoTaskMemFree);
187
188   CoUninitialize ();
189
190   g_clear_pointer (&self->cached_caps, gst_caps_unref);
191   g_clear_pointer (&self->positions, g_free);
192   g_clear_pointer (&self->device_strid, g_free);
193
194   G_OBJECT_CLASS (parent_class)->finalize (object);
195 }
196
197 static void
198 gst_wasapi_src_set_property (GObject * object, guint prop_id,
199     const GValue * value, GParamSpec * pspec)
200 {
201   GstWasapiSrc *self = GST_WASAPI_SRC (object);
202
203   switch (prop_id) {
204     case PROP_ROLE:
205       self->role = gst_wasapi_device_role_to_erole (g_value_get_enum (value));
206       break;
207     case PROP_DEVICE:
208     {
209       const gchar *device = g_value_get_string (value);
210       g_free (self->device_strid);
211       self->device_strid =
212           device ? g_utf8_to_utf16 (device, -1, NULL, NULL, NULL) : NULL;
213       break;
214     }
215     case PROP_EXCLUSIVE:
216       self->sharemode = g_value_get_boolean (value)
217           ? AUDCLNT_SHAREMODE_EXCLUSIVE : AUDCLNT_SHAREMODE_SHARED;
218       break;
219     default:
220       G_OBJECT_WARN_INVALID_PROPERTY_ID (object, prop_id, pspec);
221       break;
222   }
223 }
224
225 static void
226 gst_wasapi_src_get_property (GObject * object, guint prop_id,
227     GValue * value, GParamSpec * pspec)
228 {
229   GstWasapiSrc *self = GST_WASAPI_SRC (object);
230
231   switch (prop_id) {
232     case PROP_ROLE:
233       g_value_set_enum (value, gst_wasapi_erole_to_device_role (self->role));
234       break;
235     case PROP_DEVICE:
236       g_value_take_string (value, self->device_strid ?
237           g_utf16_to_utf8 (self->device_strid, -1, NULL, NULL, NULL) : NULL);
238       break;
239     case PROP_EXCLUSIVE:
240       g_value_set_boolean (value,
241           self->sharemode == AUDCLNT_SHAREMODE_EXCLUSIVE);
242       break;
243     default:
244       G_OBJECT_WARN_INVALID_PROPERTY_ID (object, prop_id, pspec);
245       break;
246   }
247 }
248
249 static GstCaps *
250 gst_wasapi_src_get_caps (GstBaseSrc * bsrc, GstCaps * filter)
251 {
252   GstWasapiSrc *self = GST_WASAPI_SRC (bsrc);
253   WAVEFORMATEX *format = NULL;
254   GstCaps *caps = NULL;
255
256   GST_DEBUG_OBJECT (self, "entering get caps");
257
258   if (self->cached_caps) {
259     caps = gst_caps_ref (self->cached_caps);
260   } else {
261     GstCaps *template_caps;
262     gboolean ret;
263
264     template_caps = gst_pad_get_pad_template_caps (bsrc->srcpad);
265
266     if (!self->client)
267       gst_wasapi_src_open (GST_AUDIO_SRC (bsrc));
268
269     ret = gst_wasapi_util_get_device_format (GST_ELEMENT (self),
270         self->sharemode, self->device, self->client, &format);
271     if (!ret) {
272       GST_ELEMENT_ERROR (self, STREAM, FORMAT, (NULL),
273           ("failed to detect format"));
274       goto out;
275     }
276
277     gst_wasapi_util_parse_waveformatex ((WAVEFORMATEXTENSIBLE *) format,
278         template_caps, &caps, &self->positions);
279     if (caps == NULL) {
280       GST_ELEMENT_ERROR (self, STREAM, FORMAT, (NULL), ("unknown format"));
281       goto out;
282     }
283
284     {
285       gchar *pos_str = gst_audio_channel_positions_to_string (self->positions,
286           format->nChannels);
287       GST_INFO_OBJECT (self, "positions are: %s", pos_str);
288       g_free (pos_str);
289     }
290
291     self->mix_format = format;
292     gst_caps_replace (&self->cached_caps, caps);
293     gst_caps_unref (template_caps);
294   }
295
296   if (filter) {
297     GstCaps *filtered =
298         gst_caps_intersect_full (filter, caps, GST_CAPS_INTERSECT_FIRST);
299     gst_caps_unref (caps);
300     caps = filtered;
301   }
302
303   GST_DEBUG_OBJECT (self, "returning caps %" GST_PTR_FORMAT, caps);
304
305 out:
306   return caps;
307 }
308
309 static gboolean
310 gst_wasapi_src_open (GstAudioSrc * asrc)
311 {
312   GstWasapiSrc *self = GST_WASAPI_SRC (asrc);
313   gboolean res = FALSE;
314   IAudioClient *client = NULL;
315   IMMDevice *device = NULL;
316
317   if (self->client)
318     return TRUE;
319
320   /* FIXME: Switching the default device does not switch the stream to it,
321    * even if the old device was unplugged. We need to handle this somehow.
322    * For example, perhaps we should automatically switch to the new device if
323    * the default device is changed and a device isn't explicitly selected. */
324   if (!gst_wasapi_util_get_device_client (GST_ELEMENT (self), TRUE,
325           self->role, self->device_strid, &device, &client)) {
326     if (!self->device_strid)
327       GST_ELEMENT_ERROR (self, RESOURCE, OPEN_READ, (NULL),
328           ("Failed to get default device"));
329     else
330       GST_ELEMENT_ERROR (self, RESOURCE, OPEN_READ, (NULL),
331           ("Failed to open device %S", self->device_strid));
332     goto beach;
333   }
334
335   self->client = client;
336   self->device = device;
337   res = TRUE;
338
339 beach:
340
341   return res;
342 }
343
344 static gboolean
345 gst_wasapi_src_close (GstAudioSrc * asrc)
346 {
347   GstWasapiSrc *self = GST_WASAPI_SRC (asrc);
348
349   if (self->device != NULL) {
350     IUnknown_Release (self->device);
351     self->device = NULL;
352   }
353
354   if (self->client != NULL) {
355     IUnknown_Release (self->client);
356     self->client = NULL;
357   }
358
359   return TRUE;
360 }
361
362 static gboolean
363 gst_wasapi_src_prepare (GstAudioSrc * asrc, GstAudioRingBufferSpec * spec)
364 {
365   GstWasapiSrc *self = GST_WASAPI_SRC (asrc);
366   gboolean res = FALSE;
367   IAudioClock *client_clock = NULL;
368   guint64 client_clock_freq = 0;
369   IAudioCaptureClient *capture_client = NULL;
370   REFERENCE_TIME latency_rt;
371   gint64 default_period, min_period, use_period;
372   guint bpf, rate, buffer_frames;
373   HRESULT hr;
374
375   hr = IAudioClient_GetDevicePeriod (self->client, &default_period,
376       &min_period);
377   if (hr != S_OK) {
378     GST_ERROR_OBJECT (self, "IAudioClient::GetDevicePeriod failed");
379     goto beach;
380   }
381   GST_INFO_OBJECT (self, "wasapi default period: %" G_GINT64_FORMAT
382       ", min period: %" G_GINT64_FORMAT, default_period, min_period);
383
384   if (self->sharemode == AUDCLNT_SHAREMODE_SHARED) {
385     use_period = default_period;
386     /* Set hnsBufferDuration to 0, which should, in theory, tell the device to
387      * create a buffer with the smallest latency possible. In practice, this is
388      * usually 2 * default_period. See:
389      * https://msdn.microsoft.com/en-us/library/windows/desktop/dd370871(v=vs.85).aspx
390      *
391      * NOTE: min_period is a lie, and I have never seen WASAPI use it as the
392      * current period */
393     hr = IAudioClient_Initialize (self->client, AUDCLNT_SHAREMODE_SHARED,
394         AUDCLNT_STREAMFLAGS_EVENTCALLBACK, 0, 0, self->mix_format, NULL);
395   } else {
396     use_period = default_period;
397     /* For some reason, we need to call this another time for exclusive mode */
398     CoInitialize (NULL);
399     /* FIXME: We should be able to use min_period as the device buffer size,
400      * but I'm hitting a problem in GStreamer. */
401     hr = IAudioClient_Initialize (self->client, AUDCLNT_SHAREMODE_EXCLUSIVE,
402         AUDCLNT_STREAMFLAGS_EVENTCALLBACK, use_period, use_period,
403         self->mix_format, NULL);
404   }
405   if (hr != S_OK) {
406     GST_ELEMENT_ERROR (self, RESOURCE, OPEN_READ, (NULL),
407         ("IAudioClient::Initialize () failed: %s",
408             gst_wasapi_util_hresult_to_string (hr)));
409     goto beach;
410   }
411
412   /* Total size in frames of the allocated buffer that we will read from */
413   hr = IAudioClient_GetBufferSize (self->client, &buffer_frames);
414   if (hr != S_OK) {
415     GST_ERROR_OBJECT (self, "IAudioClient::GetBufferSize failed");
416     goto beach;
417   }
418
419   bpf = GST_AUDIO_INFO_BPF (&spec->info);
420   rate = GST_AUDIO_INFO_RATE (&spec->info);
421   GST_INFO_OBJECT (self, "buffer size is %i frames, bpf is %i bytes, "
422       "rate is %i Hz", buffer_frames, bpf, rate);
423
424   spec->segsize = gst_util_uint64_scale_int_round (rate * bpf,
425       use_period * 100, GST_SECOND);
426
427   /* We need a minimum of 2 segments to ensure glitch-free playback */
428   spec->segtotal = MAX (self->buffer_frame_count * bpf / spec->segsize, 2);
429
430   GST_INFO_OBJECT (self, "segsize is %i, segtotal is %i", spec->segsize,
431       spec->segtotal);
432
433   /* Get WASAPI latency for logging */
434   hr = IAudioClient_GetStreamLatency (self->client, &latency_rt);
435   if (hr != S_OK) {
436     GST_ERROR_OBJECT (self, "IAudioClient::GetStreamLatency failed");
437     goto beach;
438   }
439   GST_INFO_OBJECT (self, "wasapi stream latency: %" G_GINT64_FORMAT " (%"
440       G_GINT64_FORMAT " ms)", latency_rt, latency_rt / 10000);
441
442   /* Set the event handler which will trigger reads */
443   hr = IAudioClient_SetEventHandle (self->client, self->event_handle);
444   if (hr != S_OK) {
445     GST_ERROR_OBJECT (self, "IAudioClient::SetEventHandle failed");
446     goto beach;
447   }
448
449   /* Get the clock and the clock freq */
450   if (!gst_wasapi_util_get_clock (GST_ELEMENT (self), self->client,
451           &client_clock)) {
452     goto beach;
453   }
454
455   hr = IAudioClock_GetFrequency (client_clock, &client_clock_freq);
456   if (hr != S_OK) {
457     GST_ERROR_OBJECT (self, "IAudioClock::GetFrequency failed");
458     goto beach;
459   }
460
461   /* Get capture source client and start it up */
462   if (!gst_wasapi_util_get_capture_client (GST_ELEMENT (self), self->client,
463           &capture_client)) {
464     goto beach;
465   }
466
467   hr = IAudioClient_Start (self->client);
468   if (hr != S_OK) {
469     GST_ERROR_OBJECT (self, "IAudioClient::Start failed");
470     goto beach;
471   }
472
473   self->client_clock = client_clock;
474   self->client_clock_freq = client_clock_freq;
475   self->capture_client = capture_client;
476
477   gst_audio_ring_buffer_set_channel_positions (GST_AUDIO_BASE_SRC
478       (self)->ringbuffer, self->positions);
479
480   res = TRUE;
481
482 beach:
483   if (!res) {
484     if (capture_client != NULL)
485       IUnknown_Release (capture_client);
486
487     if (client_clock != NULL)
488       IUnknown_Release (client_clock);
489   }
490
491   return res;
492 }
493
494 static gboolean
495 gst_wasapi_src_unprepare (GstAudioSrc * asrc)
496 {
497   GstWasapiSrc *self = GST_WASAPI_SRC (asrc);
498
499   if (self->sharemode == AUDCLNT_SHAREMODE_EXCLUSIVE)
500     CoUninitialize ();
501
502   if (self->client != NULL) {
503     IAudioClient_Stop (self->client);
504   }
505
506   if (self->capture_client != NULL) {
507     IUnknown_Release (self->capture_client);
508     self->capture_client = NULL;
509   }
510
511   if (self->client_clock != NULL) {
512     IUnknown_Release (self->client_clock);
513     self->client_clock = NULL;
514   }
515
516   return TRUE;
517 }
518
519 static guint
520 gst_wasapi_src_read (GstAudioSrc * asrc, gpointer data, guint length,
521     GstClockTime * timestamp)
522 {
523   GstWasapiSrc *self = GST_WASAPI_SRC (asrc);
524   HRESULT hr;
525   gint16 *from = NULL;
526   guint wanted = length;
527   DWORD flags;
528
529   while (wanted > 0) {
530     guint have_frames, n_frames, want_frames, read_len;
531
532     /* Wait for data to become available */
533     WaitForSingleObject (self->event_handle, INFINITE);
534
535     hr = IAudioCaptureClient_GetBuffer (self->capture_client,
536         (BYTE **) & from, &have_frames, &flags, NULL, NULL);
537     if (hr != S_OK) {
538       if (hr == AUDCLNT_S_BUFFER_EMPTY)
539         GST_WARNING_OBJECT (self, "IAudioCaptureClient::GetBuffer failed: %s"
540             ", retrying", gst_wasapi_util_hresult_to_string (hr));
541       else
542         GST_ERROR_OBJECT (self, "IAudioCaptureClient::GetBuffer failed: %s",
543             gst_wasapi_util_hresult_to_string (hr));
544       length = 0;
545       goto beach;
546     }
547
548     if (flags != 0)
549       GST_INFO_OBJECT (self, "buffer flags=%#08x", (guint) flags);
550
551     /* XXX: How do we handle AUDCLNT_BUFFERFLAGS_SILENT? We're supposed to write
552      * out silence when that flag is set? See:
553      * https://msdn.microsoft.com/en-us/library/windows/desktop/dd370800(v=vs.85).aspx */
554
555     if (flags & AUDCLNT_BUFFERFLAGS_DATA_DISCONTINUITY)
556       GST_WARNING_OBJECT (self, "WASAPI reported glitch in buffer");
557
558     want_frames = wanted / self->mix_format->nBlockAlign;
559
560     /* If GetBuffer is returning more frames than we can handle, all we can do is
561      * hope that this is temporary and that things will settle down later. */
562     if (G_UNLIKELY (have_frames > want_frames))
563       GST_WARNING_OBJECT (self, "captured too many frames: have %i, want %i",
564           have_frames, want_frames);
565
566     /* Only copy data that will fit into the allocated buffer of size @length */
567     n_frames = MIN (have_frames, want_frames);
568     read_len = n_frames * self->mix_format->nBlockAlign;
569
570     {
571       guint bpf = self->mix_format->nBlockAlign;
572       GST_TRACE_OBJECT (self, "have: %i (%i bytes), can read: %i (%i bytes), "
573           "will read: %i (%i bytes)", have_frames, have_frames * bpf,
574           want_frames, wanted, n_frames, read_len);
575     }
576
577     memcpy (data, from, read_len);
578     wanted -= read_len;
579
580     /* Always release all captured buffers if we've captured any at all */
581     hr = IAudioCaptureClient_ReleaseBuffer (self->capture_client, have_frames);
582     if (hr != S_OK) {
583       GST_ERROR_OBJECT (self,
584           "IAudioCaptureClient::ReleaseBuffer () failed: %s",
585           gst_wasapi_util_hresult_to_string (hr));
586       goto beach;
587     }
588   }
589
590
591 beach:
592
593   return length;
594 }
595
596 static guint
597 gst_wasapi_src_delay (GstAudioSrc * asrc)
598 {
599   GstWasapiSrc *self = GST_WASAPI_SRC (asrc);
600   guint delay = 0;
601   HRESULT hr;
602
603   hr = IAudioClient_GetCurrentPadding (self->client, &delay);
604   if (hr != S_OK) {
605     GST_ELEMENT_ERROR (self, RESOURCE, READ, (NULL),
606         ("IAudioClient::GetCurrentPadding failed %s",
607             gst_wasapi_util_hresult_to_string (hr)));
608   }
609
610   return delay;
611 }
612
613 static void
614 gst_wasapi_src_reset (GstAudioSrc * asrc)
615 {
616   GstWasapiSrc *self = GST_WASAPI_SRC (asrc);
617   HRESULT hr;
618
619   if (self->client) {
620     hr = IAudioClient_Stop (self->client);
621     if (hr != S_OK) {
622       GST_ERROR_OBJECT (self, "IAudioClient::Stop () failed: %s",
623           gst_wasapi_util_hresult_to_string (hr));
624       return;
625     }
626
627     hr = IAudioClient_Reset (self->client);
628     if (hr != S_OK) {
629       GST_ERROR_OBJECT (self, "IAudioClient::Reset () failed: %s",
630           gst_wasapi_util_hresult_to_string (hr));
631       return;
632     }
633   }
634 }
635
636 static GstClockTime
637 gst_wasapi_src_get_time (GstClock * clock, gpointer user_data)
638 {
639   GstWasapiSrc *self = GST_WASAPI_SRC (user_data);
640   HRESULT hr;
641   guint64 devpos;
642   GstClockTime result;
643
644   if (G_UNLIKELY (self->client_clock == NULL))
645     return GST_CLOCK_TIME_NONE;
646
647   hr = IAudioClock_GetPosition (self->client_clock, &devpos, NULL);
648   if (G_UNLIKELY (hr != S_OK))
649     return GST_CLOCK_TIME_NONE;
650
651   result = gst_util_uint64_scale_int (devpos, GST_SECOND,
652       self->client_clock_freq);
653
654   /*
655      GST_DEBUG_OBJECT (self, "devpos = %" G_GUINT64_FORMAT
656      " frequency = %" G_GUINT64_FORMAT
657      " result = %" G_GUINT64_FORMAT " ms",
658      devpos, self->client_clock_freq, GST_TIME_AS_MSECONDS (result));
659    */
660
661   return result;
662 }