From 69fad589acd5653e95a5cee8986548861b7eb1b3 Mon Sep 17 00:00:00 2001 From: =?utf8?q?Ole=20Andr=C3=A9=20Vadla=20Ravn=C3=A5s?= Date: Tue, 30 Sep 2008 11:19:10 +0000 Subject: [PATCH] sys/: New plugin for audio capture and playback using Windows Audio Session Original commit message from CVS: * sys/Makefile.am: * sys/wasapi/Makefile.am: * sys/wasapi/gstwasapi.c: * sys/wasapi/gstwasapisink.c: * sys/wasapi/gstwasapisink.h: * sys/wasapi/gstwasapisrc.c: * sys/wasapi/gstwasapisrc.h: * sys/wasapi/gstwasapiutil.c: * sys/wasapi/gstwasapiutil.h: New plugin for audio capture and playback using Windows Audio Session API (WASAPI) available with Vista and newer (#520901). Comes with hardcoded caps and obviously needs lots of love. Haven't had time to work on this code since it was written, was initially just a quick experiment to play around with this new API. --- ChangeLog | 18 ++ sys/Makefile.am | 2 +- sys/wasapi/Makefile.am | 5 + sys/wasapi/gstwasapi.c | 45 +++++ sys/wasapi/gstwasapisink.c | 267 +++++++++++++++++++++++++++ sys/wasapi/gstwasapisink.h | 67 +++++++ sys/wasapi/gstwasapisrc.c | 443 +++++++++++++++++++++++++++++++++++++++++++++ sys/wasapi/gstwasapisrc.h | 74 ++++++++ sys/wasapi/gstwasapiutil.c | 261 ++++++++++++++++++++++++++ sys/wasapi/gstwasapiutil.h | 41 +++++ 10 files changed, 1222 insertions(+), 1 deletion(-) create mode 100644 sys/wasapi/Makefile.am create mode 100644 sys/wasapi/gstwasapi.c create mode 100644 sys/wasapi/gstwasapisink.c create mode 100644 sys/wasapi/gstwasapisink.h create mode 100644 sys/wasapi/gstwasapisrc.c create mode 100644 sys/wasapi/gstwasapisrc.h create mode 100644 sys/wasapi/gstwasapiutil.c create mode 100644 sys/wasapi/gstwasapiutil.h diff --git a/ChangeLog b/ChangeLog index 912096f..f3e6b8d 100644 --- a/ChangeLog +++ b/ChangeLog @@ -1,5 +1,23 @@ 2008-09-30 Ole André Vadla Ravnås + * sys/Makefile.am: + * sys/wasapi/Makefile.am: + * sys/wasapi/gstwasapi.c: + * sys/wasapi/gstwasapisink.c: + * sys/wasapi/gstwasapisink.h: + * sys/wasapi/gstwasapisrc.c: + * sys/wasapi/gstwasapisrc.h: + * sys/wasapi/gstwasapiutil.c: + * sys/wasapi/gstwasapiutil.h: + New plugin for audio capture and playback using Windows Audio Session + API (WASAPI) available with Vista and newer (#520901). + + Comes with hardcoded caps and obviously needs lots of love. Haven't + had time to work on this code since it was written, was initially just + a quick experiment to play around with this new API. + +2008-09-30 Ole André Vadla Ravnås + * sys/dshowdecwrapper/gstdshowaudiodec.cpp (AudioFakeSink.DoRenderSample): Fix a couple of signed/unsigned comparison warnings. diff --git a/sys/Makefile.am b/sys/Makefile.am index 2a8f04d..88d7b26 100644 --- a/sys/Makefile.am +++ b/sys/Makefile.am @@ -61,5 +61,5 @@ endif SUBDIRS = $(ACM_DIR) $(DVB_DIR) $(FBDEV_DIR) $(OSS4_DIR) $(QT_DIR) $(VCD_DIR) $(WININET_DIR) DIST_SUBDIRS = acmenc dvb fbdev dshowdecwrapper dshowsrcwrapper dshowvideosink \ - oss4 qtwrapper vcd wininet winks winscreencap + oss4 qtwrapper vcd wasapi wininet winks winscreencap diff --git a/sys/wasapi/Makefile.am b/sys/wasapi/Makefile.am new file mode 100644 index 0000000..9058178 --- /dev/null +++ b/sys/wasapi/Makefile.am @@ -0,0 +1,5 @@ +EXTRA_DIST = \ + gstwasapi.c \ + gstwasapisrc.c gstwasapisrc.h \ + gstwasapisink.c gstwasapisink.h \ + gstwasapiutil.c gstwasapiutil.h diff --git a/sys/wasapi/gstwasapi.c b/sys/wasapi/gstwasapi.c new file mode 100644 index 0000000..9fef21f --- /dev/null +++ b/sys/wasapi/gstwasapi.c @@ -0,0 +1,45 @@ +/* + * Copyright (C) 2008 Ole André Vadla Ravnås + * + * This library is free software; you can redistribute it and/or + * modify it under the terms of the GNU Library General Public + * License as published by the Free Software Foundation; either + * version 2 of the License, or (at your option) any later version. + * + * This library is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * Library General Public License for more details. + * + * 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. + */ + +#include "gstwasapisrc.h" +#include "gstwasapisink.h" + +#ifdef HAVE_CONFIG_H +# include +#endif + +static gboolean +plugin_init (GstPlugin * plugin) +{ + gboolean ret; + + ret = gst_element_register (plugin, "wasapisrc", + GST_RANK_NONE, GST_TYPE_WASAPI_SRC); + if (!ret) + return ret; + + return gst_element_register (plugin, "wasapisink", + GST_RANK_NONE, GST_TYPE_WASAPI_SINK); +} + +GST_PLUGIN_DEFINE (GST_VERSION_MAJOR, + GST_VERSION_MINOR, + "wasapi", + "Windows audio session API plugin", + plugin_init, VERSION, "LGPL", "GStreamer", "http://gstreamer.net/") diff --git a/sys/wasapi/gstwasapisink.c b/sys/wasapi/gstwasapisink.c new file mode 100644 index 0000000..f26e56d --- /dev/null +++ b/sys/wasapi/gstwasapisink.c @@ -0,0 +1,267 @@ +/* + * Copyright (C) 2008 Ole André Vadla Ravnås + * + * This library is free software; you can redistribute it and/or + * modify it under the terms of the GNU Library General Public + * License as published by the Free Software Foundation; either + * version 2 of the License, or (at your option) any later version. + * + * This library is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * Library General Public License for more details. + * + * 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. + */ + +/** + * SECTION:element-wasapisink + * + * Provides audio playback using the Windows Audio Session API available with + * Vista and newer. + * + * + * Example pipelines + * |[ + * gst-launch-0.10 -v audiotestsrc samplesperbuffer=160 ! wasapisink + * ]| Generate 20 ms buffers and render to the default audio device. + * + */ + +#include "gstwasapisink.h" + +GST_DEBUG_CATEGORY_STATIC (gst_wasapi_sink_debug); +#define GST_CAT_DEFAULT gst_wasapi_sink_debug + +static GstStaticPadTemplate sink_template = GST_STATIC_PAD_TEMPLATE ("sink", + GST_PAD_SINK, + 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))); + +static void gst_wasapi_sink_dispose (GObject * object); +static void gst_wasapi_sink_finalize (GObject * object); + +static void gst_wasapi_sink_get_times (GstBaseSink * sink, GstBuffer * buffer, + GstClockTime * start, GstClockTime * end); +static gboolean gst_wasapi_sink_start (GstBaseSink * sink); +static gboolean gst_wasapi_sink_stop (GstBaseSink * sink); +static GstFlowReturn gst_wasapi_sink_render (GstBaseSink * sink, + GstBuffer * buffer); + +GST_BOILERPLATE (GstWasapiSink, gst_wasapi_sink, GstBaseSink, + GST_TYPE_BASE_SINK); + +static void +gst_wasapi_sink_base_init (gpointer gclass) +{ + GstElementClass *element_class = GST_ELEMENT_CLASS (gclass); + static GstElementDetails element_details = { + "WasapiSrc", + "Sink/Audio", + "Stream audio to an audio capture device through WASAPI", + "Ole André Vadla Ravnås " + }; + + gst_element_class_add_pad_template (element_class, + gst_static_pad_template_get (&sink_template)); + gst_element_class_set_details (element_class, &element_details); +} + +static void +gst_wasapi_sink_class_init (GstWasapiSinkClass * klass) +{ + GObjectClass *gobject_class = G_OBJECT_CLASS (klass); + GstBaseSinkClass *gstbasesink_class = GST_BASE_SINK_CLASS (klass); + + gobject_class->dispose = gst_wasapi_sink_dispose; + gobject_class->finalize = gst_wasapi_sink_finalize; + + gstbasesink_class->get_times = gst_wasapi_sink_get_times; + gstbasesink_class->start = gst_wasapi_sink_start; + gstbasesink_class->stop = gst_wasapi_sink_stop; + gstbasesink_class->render = gst_wasapi_sink_render; + + GST_DEBUG_CATEGORY_INIT (gst_wasapi_sink_debug, "wasapisink", + 0, "Windows audio session API sink"); +} + +static void +gst_wasapi_sink_init (GstWasapiSink * self, GstWasapiSinkClass * gclass) +{ + self->rate = 8000; + self->buffer_time = 20 * GST_MSECOND; + self->period_time = 20 * GST_MSECOND; + self->latency = GST_CLOCK_TIME_NONE; + + self->event_handle = CreateEvent (NULL, FALSE, FALSE, NULL); + + CoInitialize (NULL); +} + +static void +gst_wasapi_sink_dispose (GObject * object) +{ + GstWasapiSink *self = GST_WASAPI_SINK (object); + + if (self->event_handle != NULL) { + CloseHandle (self->event_handle); + self->event_handle = NULL; + } + + G_OBJECT_CLASS (parent_class)->dispose (object); +} + +static void +gst_wasapi_sink_finalize (GObject * object) +{ + GstWasapiSink *self = GST_WASAPI_SINK (object); + + CoUninitialize (); + + G_OBJECT_CLASS (parent_class)->finalize (object); +} + +static void +gst_wasapi_sink_get_times (GstBaseSink * sink, + GstBuffer * buffer, GstClockTime * start, GstClockTime * end) +{ + GstWasapiSink *self = GST_WASAPI_SINK (sink); + + if (GST_BUFFER_TIMESTAMP_IS_VALID (buffer)) { + *start = GST_BUFFER_TIMESTAMP (buffer); + + if (GST_BUFFER_DURATION_IS_VALID (buffer)) { + *end = *start + GST_BUFFER_DURATION (buffer); + } else { + *end = *start + self->buffer_time; + } + + *start += self->latency; + *end += self->latency; + } +} + +static gboolean +gst_wasapi_sink_start (GstBaseSink * sink) +{ + GstWasapiSink *self = GST_WASAPI_SINK (sink); + gboolean res = FALSE; + IAudioClient *client = NULL; + HRESULT hr; + IAudioRenderClient *render_client = NULL; + + if (!gst_wasapi_util_get_default_device_client (GST_ELEMENT (self), + FALSE, self->rate, self->buffer_time, self->period_time, + AUDCLNT_STREAMFLAGS_EVENTCALLBACK, &client, &self->latency)) + goto beach; + + hr = IAudioClient_SetEventHandle (client, self->event_handle); + if (hr != S_OK) { + GST_ERROR_OBJECT (self, "IAudioClient::SetEventHandle () failed"); + goto beach; + } + + hr = IAudioClient_GetService (client, &IID_IAudioRenderClient, + &render_client); + if (hr != S_OK) { + GST_ERROR_OBJECT (self, "IAudioClient::GetService " + "(IID_IAudioRenderClient) failed"); + goto beach; + } + + hr = IAudioClient_Start (client); + if (hr != S_OK) { + GST_ERROR_OBJECT (self, "IAudioClient::Start failed"); + goto beach; + } + + self->client = client; + self->render_client = render_client; + + res = TRUE; + +beach: + if (!res) { + if (render_client != NULL) + IUnknown_Release (render_client); + + if (client != NULL) + IUnknown_Release (client); + } + + return res; +} + +static gboolean +gst_wasapi_sink_stop (GstBaseSink * sink) +{ + GstWasapiSink *self = GST_WASAPI_SINK (sink); + + if (self->client != NULL) { + IAudioClient_Stop (self->client); + } + + if (self->render_client != NULL) { + IUnknown_Release (self->render_client); + self->render_client = NULL; + } + + if (self->client != NULL) { + IUnknown_Release (self->client); + self->client = NULL; + } + + return TRUE; +} + +static GstFlowReturn +gst_wasapi_sink_render (GstBaseSink * sink, GstBuffer * buffer) +{ + GstWasapiSink *self = GST_WASAPI_SINK (sink); + GstFlowReturn ret = GST_FLOW_OK; + HRESULT hr; + gint16 *src = (gint16 *) GST_BUFFER_DATA (buffer); + gint16 *dst = NULL; + guint nsamples = GST_BUFFER_SIZE (buffer) / sizeof (gint16); + guint i; + + WaitForSingleObject (self->event_handle, INFINITE); + + hr = IAudioRenderClient_GetBuffer (self->render_client, nsamples, + (BYTE **) & dst); + if (hr != S_OK) { + GST_ELEMENT_ERROR (self, RESOURCE, WRITE, (NULL), + ("IAudioRenderClient::GetBuffer () failed: %s", + gst_wasapi_util_hresult_to_string (hr))); + ret = GST_FLOW_ERROR; + goto beach; + } + + for (i = 0; i < nsamples; i++) { + dst[0] = *src; + dst[1] = *src; + + src++; + dst += 2; + } + + hr = IAudioRenderClient_ReleaseBuffer (self->render_client, nsamples, 0); + if (hr != S_OK) { + GST_ERROR_OBJECT (self, "IAudioRenderClient::ReleaseBuffer () failed: %s", + gst_wasapi_util_hresult_to_string (hr)); + ret = GST_FLOW_ERROR; + goto beach; + } + +beach: + return ret; +} diff --git a/sys/wasapi/gstwasapisink.h b/sys/wasapi/gstwasapisink.h new file mode 100644 index 0000000..16cc22f --- /dev/null +++ b/sys/wasapi/gstwasapisink.h @@ -0,0 +1,67 @@ +/* + * Copyright (C) 2008 Ole André Vadla Ravnås + * + * This library is free software; you can redistribute it and/or + * modify it under the terms of the GNU Library General Public + * License as published by the Free Software Foundation; either + * version 2 of the License, or (at your option) any later version. + * + * This library is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * Library General Public License for more details. + * + * 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. + */ + +#ifndef __GST_WASAPI_SINK_H__ +#define __GST_WASAPI_SINK_H__ + +#include "gstwasapiutil.h" + +#include + +G_BEGIN_DECLS + +#define GST_TYPE_WASAPI_SINK \ + (gst_wasapi_sink_get_type ()) +#define GST_WASAPI_SINK(obj) \ + (G_TYPE_CHECK_INSTANCE_CAST ((obj), GST_TYPE_WASAPI_SINK, GstWasapiSink)) +#define GST_WASAPI_SINK_CLASS(klass) \ + (G_TYPE_CHECK_CLASS_CAST ((klass), GST_TYPE_WASAPI_SINK, GstWasapiSinkClass)) +#define GST_IS_WASAPI_SINK(obj) \ + (G_TYPE_CHECK_INSTANCE_TYPE ((obj), GST_TYPE_WASAPI_SINK)) +#define GST_IS_WASAPI_SINK_CLASS(klass) \ + (G_TYPE_CHECK_CLASS_TYPE ((klass), GST_TYPE_WASAPI_SINK)) + +typedef struct _GstWasapiSink GstWasapiSink; +typedef struct _GstWasapiSinkClass GstWasapiSinkClass; + +struct _GstWasapiSink +{ + GstBaseSink base_sink; + + guint rate; + GstClockTime buffer_time; + GstClockTime period_time; + GstClockTime latency; + + IAudioClient * client; + IAudioRenderClient * render_client; + HANDLE event_handle; +}; + +struct _GstWasapiSinkClass +{ + GstBaseSinkClass parent_class; +}; + +GType gst_wasapi_sink_get_type (void); + +G_END_DECLS + +#endif /* __GST_WASAPI_SINK_H__ */ + diff --git a/sys/wasapi/gstwasapisrc.c b/sys/wasapi/gstwasapisrc.c new file mode 100644 index 0000000..0a0edd2 --- /dev/null +++ b/sys/wasapi/gstwasapisrc.c @@ -0,0 +1,443 @@ +/* + * Copyright (C) 2008 Ole André Vadla Ravnås + * + * This library is free software; you can redistribute it and/or + * modify it under the terms of the GNU Library General Public + * License as published by the Free Software Foundation; either + * version 2 of the License, or (at your option) any later version. + * + * This library is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * Library General Public License for more details. + * + * 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. + */ + +/** + * SECTION:element-wasapisrc + * + * Provides audio capture from the Windows Audio Session API available with + * Vista and newer. + * + * + * Example pipelines + * |[ + * gst-launch-0.10 -v wasapisrc ! fakesink + * ]| Capture from the default audio device and render to fakesink. + * + */ + +#include "gstwasapisrc.h" +#include + +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))); + +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 GstClockTime gst_wasapi_src_get_time (GstClock * clock, + gpointer user_data); + +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); + static GstElementDetails element_details = { + "WasapiSrc", + "Source/Audio", + "Stream audio from an audio capture device through WASAPI", + "Ole André Vadla Ravnås " + }; + + gst_element_class_add_pad_template (element_class, + gst_static_pad_template_get (&src_template)); + gst_element_class_set_details (element_class, &element_details); +} + +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); + + gobject_class->dispose = gst_wasapi_src_dispose; + gobject_class->finalize = gst_wasapi_src_finalize; + + 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; + + gstpushsrc_class->create = gst_wasapi_src_create; + + 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) +{ + GstBaseSrc *basesrc = GST_BASE_SRC (self); + + gst_base_src_set_format (basesrc, GST_FORMAT_TIME); + gst_base_src_set_live (basesrc, TRUE); + + 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; + + self->clock = gst_audio_clock_new ("GstWasapiSrcClock", + gst_wasapi_src_get_time, self); + + CoInitialize (NULL); +} + +static void +gst_wasapi_src_dispose (GObject * object) +{ + GstWasapiSrc *self = GST_WASAPI_SRC (object); + + if (self->clock != NULL) { + gst_object_unref (self->clock); + self->clock = NULL; + } + + G_OBJECT_CLASS (parent_class)->dispose (object); +} + +static void +gst_wasapi_src_finalize (GObject * object) +{ + GstWasapiSrc *self = GST_WASAPI_SRC (object); + + CoUninitialize (); + + G_OBJECT_CLASS (parent_class)->finalize (object); +} + +static GstClock * +gst_wasapi_src_provide_clock (GstElement * element) +{ + GstWasapiSrc *self = GST_WASAPI_SRC (element); + GstClock *clock; + + GST_OBJECT_LOCK (self); + + if (self->client_clock == NULL) + goto wrong_state; + + clock = GST_CLOCK (gst_object_ref (self->clock)); + + GST_OBJECT_UNLOCK (self); + return clock; + + /* ERRORS */ +wrong_state: + { + GST_OBJECT_UNLOCK (self); + GST_DEBUG_OBJECT (self, "IAudioClock not acquired"); + return NULL; + } +} + +static gboolean +gst_wasapi_src_start (GstBaseSrc * src) +{ + 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 (!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; + + hr = IAudioClient_GetService (client, &IID_IAudioClock, &client_clock); + if (hr != S_OK) { + GST_ERROR_OBJECT (self, "IAudioClient::GetService (IID_IAudioClock) " + "failed"); + goto beach; + } + + hr = IAudioClock_GetFrequency (client_clock, &client_clock_freq); + if (hr != S_OK) { + GST_ERROR_OBJECT (self, "IAudioClock::GetFrequency () failed"); + goto beach; + } + + hr = IAudioClient_GetService (client, &IID_IAudioCaptureClient, + &capture_client); + if (hr != S_OK) { + GST_ERROR_OBJECT (self, "IAudioClient::GetService " + "(IID_IAudioCaptureClient) failed"); + goto beach; + } + + hr = IAudioClient_Start (client); + if (hr != S_OK) { + GST_ERROR_OBJECT (self, "IAudioClient::Start failed"); + goto beach; + } + + self->client = client; + self->client_clock = client_clock; + self->client_clock_freq = client_clock_freq; + self->capture_client = capture_client; + + res = TRUE; + +beach: + if (!res) { + if (capture_client != NULL) + IUnknown_Release (capture_client); + + if (client_clock != NULL) + IUnknown_Release (client_clock); + + if (client != NULL) + IUnknown_Release (client); + } + + return res; +} + +static gboolean +gst_wasapi_src_stop (GstBaseSrc * src) +{ + GstWasapiSrc *self = GST_WASAPI_SRC (src); + + if (self->client != NULL) { + IAudioClient_Stop (self->client); + } + + if (self->capture_client != NULL) { + IUnknown_Release (self->capture_client); + self->capture_client = 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; + } + + return TRUE; +} + +static gboolean +gst_wasapi_src_query (GstBaseSrc * src, GstQuery * query) +{ + GstWasapiSrc *self = GST_WASAPI_SRC (src); + gboolean ret = FALSE; + + GST_DEBUG_OBJECT (self, "query for %s", + gst_query_type_get_name (GST_QUERY_TYPE (query))); + + switch (GST_QUERY_TYPE (query)) { + case GST_QUERY_LATENCY:{ + GstClockTime min_latency, max_latency; + + min_latency = self->latency + self->period_time; + max_latency = min_latency; + + 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)); + + gst_query_set_latency (query, TRUE, min_latency, max_latency); + ret = TRUE; + break; + } + + default: + ret = GST_BASE_SRC_CLASS (parent_class)->query (src, query); + break; + } + + return ret; +} + +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; + + GST_OBJECT_LOCK (self); + clock = GST_ELEMENT_CLOCK (self); + if (clock != NULL) + gst_object_ref (clock); + GST_OBJECT_UNLOCK (self); + + if (clock != NULL && GST_CLOCK_TIME_IS_VALID (self->next_time)) { + GstClockID id; + + id = gst_clock_new_single_shot_id (clock, self->next_time); + gst_clock_id_wait (id, NULL); + gst_clock_id_unref (id); + } + + do { + hr = IAudioCaptureClient_GetBuffer (self->capture_client, + (BYTE **) & samples, &nsamples_read, &flags, &devpos, NULL); + } + while (hr == AUDCLNT_S_BUFFER_EMPTY); + + 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; + } + + 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; + + if (timestamp > duration) + timestamp -= duration; + else + timestamp = 0; + } + + 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); + + if (ret == GST_FLOW_OK) { + guint i; + gint16 *dst; + + GST_BUFFER_OFFSET_END (*buf) = devpos + self->samples_per_buffer; + GST_BUFFER_TIMESTAMP (*buf) = timestamp; + GST_BUFFER_DURATION (*buf) = duration; + + dst = (gint16 *) GST_BUFFER_DATA (*buf); + for (i = 0; i < nsamples; i++) { + *dst = *samples; + + samples += 2; + dst++; + } + } + + 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; + } + +beach: + if (clock != NULL) + gst_object_unref (clock); + + return ret; +} + +static GstClockTime +gst_wasapi_src_get_time (GstClock * clock, gpointer user_data) +{ + GstWasapiSrc *self = GST_WASAPI_SRC (user_data); + HRESULT hr; + guint64 devpos; + GstClockTime result; + + if (G_UNLIKELY (self->client_clock == NULL)) + return GST_CLOCK_TIME_NONE; + + hr = IAudioClock_GetPosition (self->client_clock, &devpos, NULL); + if (G_UNLIKELY (hr != S_OK)) + return GST_CLOCK_TIME_NONE; + + result = gst_util_uint64_scale_int (devpos, GST_SECOND, + self->client_clock_freq); + + /* + GST_DEBUG_OBJECT (self, "devpos = %" G_GUINT64_FORMAT + " frequency = %" G_GUINT64_FORMAT + " result = %" G_GUINT64_FORMAT " ms", + devpos, self->client_clock_freq, GST_TIME_AS_MSECONDS (result)); + */ + + return result; +} diff --git a/sys/wasapi/gstwasapisrc.h b/sys/wasapi/gstwasapisrc.h new file mode 100644 index 0000000..26d14fb --- /dev/null +++ b/sys/wasapi/gstwasapisrc.h @@ -0,0 +1,74 @@ +/* + * Copyright (C) 2008 Ole André Vadla Ravnås + * + * This library is free software; you can redistribute it and/or + * modify it under the terms of the GNU Library General Public + * License as published by the Free Software Foundation; either + * version 2 of the License, or (at your option) any later version. + * + * This library is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * Library General Public License for more details. + * + * 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. + */ + +#ifndef __GST_WASAPI_SRC_H__ +#define __GST_WASAPI_SRC_H__ + +#include "gstwasapiutil.h" + +#include + +G_BEGIN_DECLS + +#define GST_TYPE_WASAPI_SRC \ + (gst_wasapi_src_get_type ()) +#define GST_WASAPI_SRC(obj) \ + (G_TYPE_CHECK_INSTANCE_CAST ((obj), GST_TYPE_WASAPI_SRC, GstWasapiSrc)) +#define GST_WASAPI_SRC_CLASS(klass) \ + (G_TYPE_CHECK_CLASS_CAST ((klass), GST_TYPE_WASAPI_SRC, GstWasapiSrcClass)) +#define GST_IS_WASAPI_SRC(obj) \ + (G_TYPE_CHECK_INSTANCE_TYPE ((obj), GST_TYPE_WASAPI_SRC)) +#define GST_IS_WASAPI_SRC_CLASS(klass) \ + (G_TYPE_CHECK_CLASS_TYPE ((klass), GST_TYPE_WASAPI_SRC)) + +typedef struct _GstWasapiSrc GstWasapiSrc; +typedef struct _GstWasapiSrcClass GstWasapiSrcClass; + +struct _GstWasapiSrc +{ + GstPushSrc audio_src; + + GstClock * clock; + + guint rate; + GstClockTime buffer_time; + GstClockTime period_time; + GstClockTime latency; + guint samples_per_buffer; + + IAudioClient * client; + IAudioClock * client_clock; + guint64 client_clock_freq; + IAudioCaptureClient * capture_client; + + GstClockTime start_time; + GstClockTime next_time; +}; + +struct _GstWasapiSrcClass +{ + GstPushSrcClass parent_class; +}; + +GType gst_wasapi_src_get_type (void); + +G_END_DECLS + +#endif /* __GST_WASAPI_SRC_H__ */ + diff --git a/sys/wasapi/gstwasapiutil.c b/sys/wasapi/gstwasapiutil.c new file mode 100644 index 0000000..79c9249 --- /dev/null +++ b/sys/wasapi/gstwasapiutil.c @@ -0,0 +1,261 @@ +/* + * Copyright (C) 2008 Ole André Vadla Ravnås + * + * This library is free software; you can redistribute it and/or + * modify it under the terms of the GNU Library General Public + * License as published by the Free Software Foundation; either + * version 2 of the License, or (at your option) any later version. + * + * This library is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * Library General Public License for more details. + * + * 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. + */ + +#include "gstwasapiutil.h" + +#include + +/* These seem to be missing in the Windows SDK... */ +const CLSID CLSID_MMDeviceEnumerator = { 0xbcde0395, 0xe52f, 0x467c, + {0x8e, 0x3d, 0xc4, 0x57, 0x92, 0x91, 0x69, 0x2e} +}; +const IID IID_IMMDeviceEnumerator = { 0xa95664d2, 0x9614, 0x4f35, + {0xa7, 0x46, 0xde, 0x8d, 0xb6, 0x36, 0x17, 0xe6} +}; +const IID IID_IAudioClient = { 0x1cb9ad4c, 0xdbfa, 0x4c32, + {0xb1, 0x78, 0xc2, 0xf5, 0x68, 0xa7, 0x03, 0xb2} +}; +const IID IID_IAudioClock = { 0xcd63314f, 0x3fba, 0x4a1b, + {0x81, 0x2c, 0xef, 0x96, 0x35, 0x87, 0x28, 0xe7} +}; +const IID IID_IAudioCaptureClient = { 0xc8adbd64, 0xe71e, 0x48a0, + {0xa4, 0xde, 0x18, 0x5c, 0x39, 0x5c, 0xd3, 0x17} +}; +const IID IID_IAudioRenderClient = { 0xf294acfc, 0x3146, 0x4483, + {0xa7, 0xbf, 0xad, 0xdc, 0xa7, 0xc2, 0x60, 0xe2} +}; + +const gchar * +gst_wasapi_util_hresult_to_string (HRESULT hr) +{ + const gchar *s = "AUDCLNT_E_UNKNOWN"; + + switch (hr) { + case AUDCLNT_E_NOT_INITIALIZED: + s = "AUDCLNT_E_NOT_INITIALIZED"; + break; + case AUDCLNT_E_ALREADY_INITIALIZED: + s = "AUDCLNT_E_ALREADY_INITIALIZED"; + break; + case AUDCLNT_E_WRONG_ENDPOINT_TYPE: + s = "AUDCLNT_E_WRONG_ENDPOINT_TYPE"; + break; + case AUDCLNT_E_DEVICE_INVALIDATED: + s = "AUDCLNT_E_DEVICE_INVALIDATED"; + break; + case AUDCLNT_E_NOT_STOPPED: + s = "AUDCLNT_E_NOT_STOPPED"; + break; + case AUDCLNT_E_BUFFER_TOO_LARGE: + s = "AUDCLNT_E_BUFFER_TOO_LARGE"; + break; + case AUDCLNT_E_OUT_OF_ORDER: + s = "AUDCLNT_E_OUT_OF_ORDER"; + break; + case AUDCLNT_E_UNSUPPORTED_FORMAT: + s = "AUDCLNT_E_UNSUPPORTED_FORMAT"; + break; + case AUDCLNT_E_INVALID_SIZE: + s = "AUDCLNT_E_INVALID_SIZE"; + break; + case AUDCLNT_E_DEVICE_IN_USE: + s = "AUDCLNT_E_DEVICE_IN_USE"; + break; + case AUDCLNT_E_BUFFER_OPERATION_PENDING: + s = "AUDCLNT_E_BUFFER_OPERATION_PENDING"; + break; + case AUDCLNT_E_THREAD_NOT_REGISTERED: + s = "AUDCLNT_E_THREAD_NOT_REGISTERED"; + break; + case AUDCLNT_E_EXCLUSIVE_MODE_NOT_ALLOWED: + s = "AUDCLNT_E_EXCLUSIVE_MODE_NOT_ALLOWED"; + break; + case AUDCLNT_E_ENDPOINT_CREATE_FAILED: + s = "AUDCLNT_E_ENDPOINT_CREATE_FAILED"; + break; + case AUDCLNT_E_SERVICE_NOT_RUNNING: + s = "AUDCLNT_E_SERVICE_NOT_RUNNING"; + break; + case AUDCLNT_E_EVENTHANDLE_NOT_EXPECTED: + s = "AUDCLNT_E_EVENTHANDLE_NOT_EXPECTED"; + break; + case AUDCLNT_E_EXCLUSIVE_MODE_ONLY: + s = "AUDCLNT_E_EXCLUSIVE_MODE_ONLY"; + break; + case AUDCLNT_E_BUFDURATION_PERIOD_NOT_EQUAL: + s = "AUDCLNT_E_BUFDURATION_PERIOD_NOT_EQUAL"; + break; + case AUDCLNT_E_EVENTHANDLE_NOT_SET: + s = "AUDCLNT_E_EVENTHANDLE_NOT_SET"; + break; + case AUDCLNT_E_INCORRECT_BUFFER_SIZE: + s = "AUDCLNT_E_INCORRECT_BUFFER_SIZE"; + break; + case AUDCLNT_E_BUFFER_SIZE_ERROR: + s = "AUDCLNT_E_BUFFER_SIZE_ERROR"; + break; + case AUDCLNT_E_CPUUSAGE_EXCEEDED: + s = "AUDCLNT_E_CPUUSAGE_EXCEEDED"; + break; + case AUDCLNT_S_BUFFER_EMPTY: + s = "AUDCLNT_S_BUFFER_EMPTY"; + break; + case AUDCLNT_S_THREAD_ALREADY_REGISTERED: + s = "AUDCLNT_S_THREAD_ALREADY_REGISTERED"; + break; + case AUDCLNT_S_POSITION_STALLED: + s = "AUDCLNT_S_POSITION_STALLED"; + break; + } + + return s; +} + +gboolean +gst_wasapi_util_get_default_device_client (GstElement * element, + gboolean capture, + guint rate, + GstClockTime buffer_time, + GstClockTime period_time, + DWORD flags, IAudioClient ** ret_client, GstClockTime * ret_latency) +{ + gboolean res = FALSE; + HRESULT hr; + IMMDeviceEnumerator *enumerator = NULL; + IMMDevice *device = NULL; + IAudioClient *client = NULL; + REFERENCE_TIME latency_rt, def_period, min_period; + WAVEFORMATEXTENSIBLE format; + + hr = CoCreateInstance (&CLSID_MMDeviceEnumerator, NULL, CLSCTX_ALL, + &IID_IMMDeviceEnumerator, &enumerator); + if (hr != S_OK) { + GST_ERROR_OBJECT (element, "CoCreateInstance (MMDeviceEnumerator) failed"); + goto beach; + } + + hr = IMMDeviceEnumerator_GetDefaultAudioEndpoint (enumerator, + (capture) ? eCapture : eRender, eCommunications, &device); + if (hr != S_OK) { + GST_ERROR_OBJECT (element, + "IMMDeviceEnumerator::GetDefaultAudioEndpoint () failed"); + goto beach; + } + + hr = IMMDevice_Activate (device, &IID_IAudioClient, CLSCTX_ALL, NULL, + &client); + if (hr != S_OK) { + GST_ERROR_OBJECT (element, "IMMDevice::Activate (IID_IAudioClient) failed"); + goto beach; + } + + hr = IAudioClient_GetDevicePeriod (client, &def_period, &min_period); + if (hr != S_OK) { + GST_ERROR_OBJECT (element, "IAudioClient::GetDevicePeriod () failed"); + goto beach; + } + + ZeroMemory (&format, sizeof (format)); + format.Format.cbSize = sizeof (format) - sizeof (format.Format); + format.Format.wFormatTag = WAVE_FORMAT_EXTENSIBLE; + format.Format.nChannels = 2; + format.Format.nSamplesPerSec = rate; + format.Format.wBitsPerSample = 16; + format.Format.nBlockAlign = format.Format.nChannels + * (format.Format.wBitsPerSample / 8); + format.Format.nAvgBytesPerSec = format.Format.nSamplesPerSec + * format.Format.nBlockAlign; + format.Samples.wValidBitsPerSample = 16; + format.dwChannelMask = SPEAKER_FRONT_LEFT | SPEAKER_FRONT_RIGHT; + format.SubFormat = KSDATAFORMAT_SUBTYPE_PCM; + + hr = IAudioClient_Initialize (client, AUDCLNT_SHAREMODE_EXCLUSIVE, /* or AUDCLNT_SHAREMODE_SHARED */ + flags, buffer_time / 100, /* buffer duration in 100s of ns */ + period_time / 100, /* periodicity in 100s of ns */ + (WAVEFORMATEX *) & format, NULL); + if (hr != S_OK) { + GST_ELEMENT_ERROR (element, RESOURCE, OPEN_READ, (NULL), + ("IAudioClient::Initialize () failed: %s", + gst_wasapi_util_hresult_to_string (hr))); + goto beach; + } + + hr = IAudioClient_GetStreamLatency (client, &latency_rt); + if (hr != S_OK) { + GST_ERROR_OBJECT (element, "IAudioClient::GetStreamLatency () failed"); + goto beach; + } + + GST_INFO_OBJECT (element, "default period: %d (%d ms), " + "minimum period: %d (%d ms), " + "latency: %d (%d ms)", + (guint32) def_period, (guint32) def_period / 10000, + (guint32) min_period, (guint32) min_period / 10000, + (guint32) latency_rt, (guint32) latency_rt / 10000); + + IUnknown_AddRef (client); + *ret_client = client; + + *ret_latency = latency_rt * 100; + + res = TRUE; + +beach: + if (client != NULL) + IUnknown_Release (client); + + if (device != NULL) + IUnknown_Release (device); + + if (enumerator != NULL) + IUnknown_Release (enumerator); + + return res; +} + +#if 0 +static WAVEFORMATEXTENSIBLE * +gst_wasapi_src_probe_device_format (GstWasapiSrc * self, IMMDevice * device) +{ + HRESULT hr; + IPropertyStore *props = NULL; + PROPVARIANT format_prop; + WAVEFORMATEXTENSIBLE *format = NULL; + + hr = IMMDevice_OpenPropertyStore (device, STGM_READ, &props); + if (hr != S_OK) + goto beach; + + PropVariantInit (&format_prop); + hr = IPropertyStore_GetValue (props, &PKEY_AudioEngine_DeviceFormat, + &format_prop); + if (hr != S_OK) + goto beach; + + format = (WAVEFORMATEXTENSIBLE *) format_prop.blob.pBlobData; + + /* hmm: HKEY_LOCAL_MACHINE\SOFTWARE\Microsoft\Windows\CurrentVersion\MMDevices\Audio\Capture\{64adb8b7-9716-4c02-8929-96e53f5642da}\Properties */ + +beach: + if (props != NULL) + IUnknown_Release (props); + + return format; +} +#endif diff --git a/sys/wasapi/gstwasapiutil.h b/sys/wasapi/gstwasapiutil.h new file mode 100644 index 0000000..fc060ab --- /dev/null +++ b/sys/wasapi/gstwasapiutil.h @@ -0,0 +1,41 @@ +/* + * Copyright (C) 2008 Ole André Vadla Ravnås + * + * This library is free software; you can redistribute it and/or + * modify it under the terms of the GNU Library General Public + * License as published by the Free Software Foundation; either + * version 2 of the License, or (at your option) any later version. + * + * This library is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * Library General Public License for more details. + * + * 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. + */ + +#ifndef __GST_WASAPI_UTIL_H__ +#define __GST_WASAPI_UTIL_H__ + +#include + +#include + +const gchar * +gst_wasapi_util_hresult_to_string (HRESULT hr); + +gboolean +gst_wasapi_util_get_default_device_client (GstElement * element, + gboolean capture, + guint rate, + GstClockTime buffer_time, + GstClockTime period_time, + DWORD flags, + IAudioClient ** ret_client, + GstClockTime * ret_latency); + +#endif /* __GST_WASAPI_UTIL_H__ */ + -- 2.7.4