/*
* 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
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_pad_template (element_class,
- gst_static_pad_template_get (&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)
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
{
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);
{
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);
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)
{
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);
return result;
}
+#endif