From 18b0bfc1f107a4ad82a3c07c3ef333f161bee11f Mon Sep 17 00:00:00 2001 From: Jacob Meuser Date: Wed, 11 Apr 2012 21:05:26 -0400 Subject: [PATCH] sndio: add sndio plugin for OpenBSD --- configure.ac | 15 ++ ext/Makefile.am | 8 + ext/sndio/Makefile.am | 11 + ext/sndio/gstsndio.c | 51 ++++ ext/sndio/sndiosink.c | 532 ++++++++++++++++++++++++++++++++++++++++++ ext/sndio/sndiosink.h | 67 ++++++ ext/sndio/sndiosrc.c | 531 +++++++++++++++++++++++++++++++++++++++++ ext/sndio/sndiosrc.h | 67 ++++++ 8 files changed, 1282 insertions(+) create mode 100644 ext/sndio/Makefile.am create mode 100644 ext/sndio/gstsndio.c create mode 100644 ext/sndio/sndiosink.c create mode 100644 ext/sndio/sndiosink.h create mode 100644 ext/sndio/sndiosrc.c create mode 100644 ext/sndio/sndiosrc.h diff --git a/configure.ac b/configure.ac index e6e73687f5..4da2865963 100644 --- a/configure.ac +++ b/configure.ac @@ -1839,6 +1839,19 @@ AG_GST_CHECK_FEATURE(GSETTINGS, [GSettings plugin], gsettings, [ fi ]) +dnl *** sndio *** +translit(dnm, m, l) AM_CONDITIONAL(USE_SNDIO, true) +AG_GST_CHECK_FEATURE(SNDIO, [sndio audio], sndio, [ + AC_CHECK_HEADER(sndio.h, HAVE_SNDIO="yes", HAVE_SNDIO="no") + if test "x$HAVE_SNDIO" = "xyes"; then + AC_CHECK_LIB(sndio, sio_open, HAVE_SNDIO="yes", HAVE_SNDIO="no") + if test "x$HAVE_SNDIO" = "xyes"; then + SNDIO_LIBS=-lsndio + AC_SUBST(SNDIO_LIBS) + fi + fi +]) + else dnl not building plugins with external dependencies, @@ -1910,6 +1923,7 @@ AM_CONDITIONAL(USE_ZBAR, false) AM_CONDITIONAL(USE_VP8, false) AM_CONDITIONAL(USE_RTMP, false) AM_CONDITIONAL(USE_TELETEXTDEC, false) +AM_CONDITIONAL(USE_SNDIO, false) fi dnl of EXT plugins @@ -2147,6 +2161,7 @@ ext/sdl/Makefile ext/sndfile/Makefile ext/soundtouch/Makefile ext/spandsp/Makefile +ext/sndio/Makefile ext/teletextdec/Makefile ext/gme/Makefile ext/gsettings/Makefile diff --git a/ext/Makefile.am b/ext/Makefile.am index 0d57dc3bba..6bd70443a8 100644 --- a/ext/Makefile.am +++ b/ext/Makefile.am @@ -330,6 +330,12 @@ else SNDFILE_DIR= endif +if USE_SNDIO +SNDIO_DIR = sndio +else +SNDIO_DIR = +endif + if USE_SOUNDTOUCH SOUNDTOUCH_DIR=soundtouch else @@ -452,6 +458,7 @@ SUBDIRS=\ $(SHOUT_DIR) \ $(SMOOTHWAVE_DIR) \ $(SNDFILE_DIR) \ + $(SNDIO_DIR) \ $(SOUNDTOUCH_DIR) \ $(SPANDSP_DIR) \ $(GME_DIR) \ @@ -507,6 +514,7 @@ DIST_SUBDIRS = \ schroedinger \ sdl \ sndfile \ + sndio \ soundtouch \ spandsp \ spc \ diff --git a/ext/sndio/Makefile.am b/ext/sndio/Makefile.am new file mode 100644 index 0000000000..03a42b1a53 --- /dev/null +++ b/ext/sndio/Makefile.am @@ -0,0 +1,11 @@ +plugin_LTLIBRARIES = libgstsndio.la + +libgstsndio_la_SOURCES = gstsndio.c sndiosink.c sndiosrc.c +libgstsndio_la_CFLAGS = $(GST_PLUGINS_BAD_CFLAGS) $(GST_PLUGINS_BASE_CFLAGS) $(GST_CFLAGS) +libgstsndio_la_LIBADD = \ + $(GST_PLUGINS_BASE_LIBS) -lgstaudio-$(GST_MAJORMINOR) \ + $(SNDIO_LIBS) +libgstsndio_la_LDFLAGS = $(GST_PLUGIN_LDFLAGS) + +noinst_HEADERS = sndiosink.h sndiosrc.h +EXTRA_DIST = diff --git a/ext/sndio/gstsndio.c b/ext/sndio/gstsndio.c new file mode 100644 index 0000000000..d52a4242dd --- /dev/null +++ b/ext/sndio/gstsndio.c @@ -0,0 +1,51 @@ +/* + * Copyright (C) <2008> Jacob Meuser + * + * Permission to use, copy, modify, and distribute this software for any + * purpose with or without fee is hereby granted, provided that the above + * copyright notice and this permission notice appear in all copies. + * + * THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES + * WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF + * MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR + * ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES + * WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN + * ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF + * OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. + */ + +#ifdef HAVE_CONFIG_H +#include "config.h" +#endif +#include "sndiosink.h" +#include "sndiosrc.h" + +#include "gst/gst-i18n-plugin.h" + +GST_DEBUG_CATEGORY (gst_sndio_debug); + +static gboolean +plugin_init (GstPlugin * plugin) +{ + if (!gst_element_register (plugin, "sndiosrc", GST_RANK_PRIMARY, + GST_TYPE_SNDIOSRC) || + !gst_element_register (plugin, "sndiosink", GST_RANK_PRIMARY, + GST_TYPE_SNDIOSINK)) { + return FALSE; + } + + GST_DEBUG_CATEGORY_INIT (gst_sndio_debug, "sndio", 0, "sndio elements"); + +#ifdef ENABLE_NLS + setlocale (LC_ALL, ""); + bindtextdomain (GETTEXT_PACKAGE, LOCALEDIR); +#endif /* ENABLE_NLS */ + + return TRUE; +} + +GST_PLUGIN_DEFINE (GST_VERSION_MAJOR, + GST_VERSION_MINOR, + "sndio", + "sndio support for GStreamer", + plugin_init, VERSION, GST_LICENSE, GST_PACKAGE_NAME, GST_PACKAGE_ORIGIN) diff --git a/ext/sndio/sndiosink.c b/ext/sndio/sndiosink.c new file mode 100644 index 0000000000..e91ecd4850 --- /dev/null +++ b/ext/sndio/sndiosink.c @@ -0,0 +1,532 @@ +/* + * Copyright (C) <2008> Jacob Meuser + * + * Permission to use, copy, modify, and distribute this software for any + * purpose with or without fee is hereby granted, provided that the above + * copyright notice and this permission notice appear in all copies. + * + * THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES + * WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF + * MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR + * ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES + * WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN + * ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF + * OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. + */ + +/** + * SECTION:element-sndiosink + * @see_also: #GstAutoAudioSink + * + * + * + * This element outputs sound to a sound card using sndio. + * + * + * Simple example pipeline that plays an Ogg/Vorbis file via sndio: + * + * gst-launch -v filesrc location=foo.ogg ! decodebin ! audioconvert ! audioresample ! sndiosink + * + * + * + */ + +#ifdef HAVE_CONFIG_H +#include "config.h" +#endif + +#include "sndiosink.h" +#include +#include + +#include + +GST_DEBUG_CATEGORY_EXTERN (gst_sndio_debug); +#define GST_CAT_DEFAULT gst_sndio_debug + +enum +{ + PROP_0, + PROP_HOST +}; + +static GstStaticPadTemplate sndio_sink_factory = + GST_STATIC_PAD_TEMPLATE ("sink", + GST_PAD_SINK, + GST_PAD_ALWAYS, + GST_STATIC_CAPS ("audio/x-raw-int, " + "endianness = (int) { 1234, 4321 }, " + "signed = (boolean) { TRUE, FALSE }, " + "width = (int) { 8, 16, 24, 32 }, " + "depth = (int) { 8, 16, 24, 32 }, " + "rate = (int) [ 8000, 192000 ], " + "channels = (int) [ 1, 16 ] ") + ); + +static void gst_sndiosink_finalize (GObject * object); + +static GstCaps *gst_sndiosink_getcaps (GstBaseSink * bsink); + +static gboolean gst_sndiosink_open (GstAudioSink * asink); +static gboolean gst_sndiosink_close (GstAudioSink * asink); +static gboolean gst_sndiosink_prepare (GstAudioSink * asink, + GstRingBufferSpec * spec); +static gboolean gst_sndiosink_unprepare (GstAudioSink * asink); +static guint gst_sndiosink_write (GstAudioSink * asink, gpointer data, + guint length); +static guint gst_sndiosink_delay (GstAudioSink * asink); +static void gst_sndiosink_reset (GstAudioSink * asink); + +static void gst_sndiosink_set_property (GObject * object, guint prop_id, + const GValue * value, GParamSpec * pspec); +static void gst_sndiosink_get_property (GObject * object, guint prop_id, + GValue * value, GParamSpec * pspec); +static void gst_sndiosink_cb (void *addr, int delta); + +GST_BOILERPLATE (GstSndioSink, gst_sndiosink, GstAudioSink, + GST_TYPE_AUDIO_SINK); + +static void +gst_sndiosink_base_init (gpointer g_class) +{ + GstElementClass *element_class = GST_ELEMENT_CLASS (g_class); + + gst_element_class_set_details_simple (element_class, + "Sndio audio sink", + "Sink/Audio", + "Plays audio through sndio", + "Jacob Meuser "); + + gst_element_class_add_pad_template (element_class, + gst_static_pad_template_get (&sndio_sink_factory)); +} + +static void +gst_sndiosink_class_init (GstSndioSinkClass * klass) +{ + GObjectClass *gobject_class; + GstBaseSinkClass *gstbasesink_class; + GstBaseAudioSinkClass *gstbaseaudiosink_class; + GstAudioSinkClass *gstaudiosink_class; + + gobject_class = (GObjectClass *) klass; + gstbasesink_class = (GstBaseSinkClass *) klass; + gstbaseaudiosink_class = (GstBaseAudioSinkClass *) klass; + gstaudiosink_class = (GstAudioSinkClass *) klass; + + parent_class = g_type_class_peek_parent (klass); + + gobject_class->finalize = gst_sndiosink_finalize; + + gstbasesink_class->get_caps = GST_DEBUG_FUNCPTR (gst_sndiosink_getcaps); + + gstaudiosink_class->open = GST_DEBUG_FUNCPTR (gst_sndiosink_open); + gstaudiosink_class->close = GST_DEBUG_FUNCPTR (gst_sndiosink_close); + gstaudiosink_class->prepare = GST_DEBUG_FUNCPTR (gst_sndiosink_prepare); + gstaudiosink_class->unprepare = + GST_DEBUG_FUNCPTR (gst_sndiosink_unprepare); + gstaudiosink_class->write = GST_DEBUG_FUNCPTR (gst_sndiosink_write); + gstaudiosink_class->delay = GST_DEBUG_FUNCPTR (gst_sndiosink_delay); + gstaudiosink_class->reset = GST_DEBUG_FUNCPTR (gst_sndiosink_reset); + + gobject_class->set_property = gst_sndiosink_set_property; + gobject_class->get_property = gst_sndiosink_get_property; + + /* default value is filled in the _init method */ + g_object_class_install_property (gobject_class, PROP_HOST, + g_param_spec_string ("host", "Host", + "Device or socket sndio will access", NULL, G_PARAM_READWRITE)); +} + +static void +gst_sndiosink_init (GstSndioSink * sndiosink, + GstSndioSinkClass * klass) +{ + sndiosink->hdl = NULL; + sndiosink->host = g_strdup (g_getenv ("AUDIODEVICE")); +} + +static void +gst_sndiosink_finalize (GObject * object) +{ + GstSndioSink *sndiosink = GST_SNDIOSINK (object); + + gst_caps_replace (&sndiosink->cur_caps, NULL); + g_free (sndiosink->host); + + G_OBJECT_CLASS (parent_class)->finalize (object); +} + +static GstCaps * +gst_sndiosink_getcaps (GstBaseSink * bsink) +{ + GstSndioSink *sndiosink; + + sndiosink = GST_SNDIOSINK (bsink); + + /* no hdl, we're done with the template caps */ + if (sndiosink->cur_caps == NULL) { + GST_LOG_OBJECT (sndiosink, "getcaps called, returning template caps"); + return NULL; + } + + GST_LOG_OBJECT (sndiosink, "returning %" GST_PTR_FORMAT, + sndiosink->cur_caps); + + return gst_caps_ref (sndiosink->cur_caps); +} + +static gboolean +gst_sndiosink_open (GstAudioSink * asink) +{ + GstPadTemplate *pad_template; + GstSndioSink *sndiosink; + struct sio_par par; + struct sio_cap cap; + GArray *rates, *chans; + GValue rates_v = { 0 }; + GValue chans_v = { 0 }; + GValue value = { 0 }; + struct sio_enc enc; + struct sio_conf conf; + int confs[SIO_NCONF]; + int rate, chan; + int i, j, k; + int nconfs; + + sndiosink = GST_SNDIOSINK (asink); + + GST_DEBUG_OBJECT (sndiosink, "open"); + + /* conect */ + sndiosink->hdl = sio_open (sndiosink->host, SIO_PLAY, 0); + + if (sndiosink->hdl == NULL) + goto couldnt_connect; + + /* Use sndio defaults as the only encodings, but get the supported + * sample rates and number of channels. + */ + + if (!sio_getpar (sndiosink->hdl, &par)) + goto no_server_info; + + if (!sio_getcap (sndiosink->hdl, &cap)) + goto no_server_info; + + rates = g_array_new (FALSE, FALSE, sizeof (int)); + chans = g_array_new (FALSE, FALSE, sizeof (int)); + + /* find confs that have the default encoding */ + nconfs = 0; + for (i = 0; i < cap.nconf; i++) { + for (j = 0; j < SIO_NENC; j++) { + if (cap.confs[i].enc & (1 << j)) { + enc = cap.enc[j]; + if (enc.bits == par.bits && enc.sig == par.sig && enc.le == par.le) { + confs[nconfs] = i; + nconfs++; + break; + } + } + } + } + + /* find the rates and channels of the confs that have the default encoding */ + for (i = 0; i < nconfs; i++) { + conf = cap.confs[confs[i]]; + /* rates */ + for (j = 0; j < SIO_NRATE; j++) { + if (conf.rate & (1 << j)) { + rate = cap.rate[j]; + for (k = 0; k < rates->len && rate; k++) { + if (rate == g_array_index (rates, int, k)) + rate = 0; + } + /* add in ascending order */ + if (rate) { + for (k = 0; k < rates->len; k++) { + if (rate < g_array_index (rates, int, k)) + { + g_array_insert_val (rates, k, rate); + break; + } + } + if (k == rates->len) + g_array_append_val (rates, rate); + } + } + } + /* channels */ + for (j = 0; j < SIO_NCHAN; j++) { + if (conf.pchan & (1 << j)) { + chan = cap.pchan[j]; + for (k = 0; k < chans->len && chan; k++) { + if (chan == g_array_index (chans, int, k)) + chan = 0; + } + /* add in ascending order */ + if (chan) { + for (k = 0; k < chans->len; k++) { + if (chan < g_array_index (chans, int, k)) + { + g_array_insert_val (chans, k, chan); + break; + } + } + if (k == chans->len) + g_array_append_val (chans, chan); + } + } + } + } + /* not sure how this can happen, but it might */ + if (cap.nconf == 0) { + g_array_append_val (rates, par.rate); + g_array_append_val (chans, par.pchan); + } + + g_value_init (&rates_v, GST_TYPE_LIST); + g_value_init (&chans_v, GST_TYPE_LIST); + g_value_init (&value, G_TYPE_INT); + + for (i = 0; i < rates->len; i++) { + g_value_set_int (&value, g_array_index (rates, int, i)); + gst_value_list_append_value (&rates_v, &value); + } + for (i = 0; i < chans->len; i++) { + g_value_set_int (&value, g_array_index (chans, int, i)); + gst_value_list_append_value (&chans_v, &value); + } + + g_array_free (rates, TRUE); + g_array_free (chans, TRUE); + + pad_template = gst_static_pad_template_get (&sndio_sink_factory); + sndiosink->cur_caps = + gst_caps_copy (gst_pad_template_get_caps (pad_template)); + gst_object_unref (pad_template); + + for (i = 0; i < sndiosink->cur_caps->structs->len; i++) { + GstStructure *s; + + s = gst_caps_get_structure (sndiosink->cur_caps, i); + gst_structure_set (s, "endianness", G_TYPE_INT, par.le ? 1234 : 4321, NULL); + gst_structure_set (s, "signed", G_TYPE_BOOLEAN, par.sig ? TRUE : FALSE, + NULL); + gst_structure_set (s, "width", G_TYPE_INT, par.bits, NULL); + // gst_structure_set (s, "depth", G_TYPE_INT, par.bps * 8, NULL); /* XXX */ + gst_structure_set_value (s, "rate", &rates_v); + gst_structure_set_value (s, "channels", &chans_v); + } + + return TRUE; + + /* ERRORS */ +couldnt_connect: + { + GST_ELEMENT_ERROR (sndiosink, RESOURCE, OPEN_WRITE, + (_("Could not establish connection to sndio")), + ("can't open connection to sndio")); + return FALSE; + } +no_server_info: + { + GST_ELEMENT_ERROR (sndiosink, RESOURCE, OPEN_WRITE, + (_("Failed to query sndio capabilities")), + ("couldn't get sndio info!")); + return FALSE; + } +} + +static gboolean +gst_sndiosink_close (GstAudioSink * asink) +{ + GstSndioSink *sndiosink = GST_SNDIOSINK (asink); + + GST_DEBUG_OBJECT (sndiosink, "close"); + + gst_caps_replace (&sndiosink->cur_caps, NULL); + sio_close (sndiosink->hdl); + sndiosink->hdl = NULL; + + return TRUE; +} + +static void +gst_sndiosink_cb (void *addr, int delta) +{ + GstSndioSink *sndiosink = GST_SNDIOSINK ((GstAudioSink *) addr); + + sndiosink->realpos += delta; + + if (sndiosink->realpos >= sndiosink->playpos) + sndiosink->latency = 0; + else + sndiosink->latency = sndiosink->playpos - sndiosink->realpos; +} + +static gboolean +gst_sndiosink_prepare (GstAudioSink * asink, GstRingBufferSpec * spec) +{ + GstSndioSink *sndiosink = GST_SNDIOSINK (asink); + struct sio_par par; + int spec_bpf; + + GST_DEBUG_OBJECT (sndiosink, "prepare"); + + sndiosink->playpos = sndiosink->realpos = sndiosink->latency = 0; + + sio_initpar (&par); + par.sig = spec->sign; + par.le = !spec->bigend; + par.bits = spec->width; + // par.bps = spec->depth / 8; /* XXX */ + par.rate = spec->rate; + par.pchan = spec->channels; + + spec_bpf = ((spec->width / 8) * spec->channels); + + par.appbufsz = (spec->segsize * spec->segtotal) / spec_bpf; + + if (!sio_setpar (sndiosink->hdl, &par)) + goto cannot_configure; + + sio_getpar (sndiosink->hdl, &par); + + spec->sign = par.sig; + spec->bigend = !par.le; + spec->width = par.bits; + // spec->depth = par.bps * 8; /* XXX */ + spec->rate = par.rate; + spec->channels = par.pchan; + + sndiosink->bpf = par.bps * par.pchan; + + spec->segsize = par.round * par.pchan * par.bps; + spec->segtotal = par.bufsz / par.round; + + /* FIXME: this is wrong for signed ints (and the + * audioringbuffers should do it for us anyway) */ + spec->silence_sample[0] = 0; + spec->silence_sample[1] = 0; + spec->silence_sample[2] = 0; + spec->silence_sample[3] = 0; + + sio_onmove (sndiosink->hdl, gst_sndiosink_cb, sndiosink); + + if (!sio_start (sndiosink->hdl)) + goto cannot_start; + + GST_INFO_OBJECT (sndiosink, "successfully opened connection to sndio"); + + return TRUE; + + /* ERRORS */ +cannot_configure: + { + GST_ELEMENT_ERROR (sndiosink, RESOURCE, OPEN_WRITE, + (_("Could not configure sndio")), ("can't configure sndio")); + return FALSE; + } +cannot_start: + { + GST_ELEMENT_ERROR (sndiosink, RESOURCE, OPEN_WRITE, + (_("Could not start sndio")), ("can't start sndio")); + return FALSE; + } +} + +static gboolean +gst_sndiosink_unprepare (GstAudioSink * asink) +{ + GstSndioSink *sndiosink = GST_SNDIOSINK (asink); + + if (sndiosink->hdl == NULL) + return TRUE; + + sio_stop (sndiosink->hdl); + + return TRUE; +} + +static guint +gst_sndiosink_write (GstAudioSink * asink, gpointer data, guint length) +{ + GstSndioSink *sndiosink = GST_SNDIOSINK (asink); + guint done; + + done = sio_write (sndiosink->hdl, data, length); + + if (done == 0) + goto write_error; + + sndiosink->playpos += (done / sndiosink->bpf); + + data = (char *) data + done; + + return done; + + /* ERRORS */ +write_error: + { + GST_ELEMENT_ERROR (sndiosink, RESOURCE, WRITE, + ("Failed to write data to sndio"), GST_ERROR_SYSTEM); + return 0; + } +} + +static guint +gst_sndiosink_delay (GstAudioSink * asink) +{ + GstSndioSink *sndiosink = GST_SNDIOSINK (asink); + + if (sndiosink->latency == (guint) - 1) { + GST_WARNING_OBJECT (asink, "couldn't get latency"); + return 0; + } + + GST_DEBUG_OBJECT (asink, "got latency: %u", sndiosink->latency); + + return sndiosink->latency; +} + +static void +gst_sndiosink_reset (GstAudioSink * asink) +{ + /* no way to flush the buffers with sndio ? */ + + GST_DEBUG_OBJECT (asink, "reset called"); +} + +static void +gst_sndiosink_set_property (GObject * object, guint prop_id, + const GValue * value, GParamSpec * pspec) +{ + GstSndioSink *sndiosink = GST_SNDIOSINK (object); + + switch (prop_id) { + case PROP_HOST: + g_free (sndiosink->host); + sndiosink->host = g_value_dup_string (value); + break; + default: + break; + } +} + +static void +gst_sndiosink_get_property (GObject * object, guint prop_id, GValue * value, + GParamSpec * pspec) +{ + GstSndioSink *sndiosink = GST_SNDIOSINK (object); + + switch (prop_id) { + case PROP_HOST: + g_value_set_string (value, sndiosink->host); + break; + default: + G_OBJECT_WARN_INVALID_PROPERTY_ID (object, prop_id, pspec); + break; + } +} diff --git a/ext/sndio/sndiosink.h b/ext/sndio/sndiosink.h new file mode 100644 index 0000000000..25bb8799bd --- /dev/null +++ b/ext/sndio/sndiosink.h @@ -0,0 +1,67 @@ +/* + * Copyright (C) <2008> Jacob Meuser + * + * Permission to use, copy, modify, and distribute this software for any + * purpose with or without fee is hereby granted, provided that the above + * copyright notice and this permission notice appear in all copies. + * + * THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES + * WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF + * MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR + * ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES + * WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN + * ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF + * OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. + */ + + +#ifndef __GST_SNDIOSINK_H__ +#define __GST_SNDIOSINK_H__ + +#include + +#include +#include + +G_BEGIN_DECLS + +#define GST_TYPE_SNDIOSINK \ + (gst_sndiosink_get_type()) +#define GST_SNDIOSINK(obj) \ + (G_TYPE_CHECK_INSTANCE_CAST((obj),GST_TYPE_SNDIOSINK,GstSndioSink)) +#define GST_SNDIOSINK_CLASS(klass) \ + (G_TYPE_CHECK_CLASS_CAST((klass),GST_TYPE_SNDIOSINK,GstSndioSinkClass)) +#define GST_IS_SNDIOSINK(obj) \ + (G_TYPE_CHECK_INSTANCE_TYPE((obj),GST_TYPE_SNDIOSINK)) +#define GST_IS_SNDIOSINK_CLASS(klass) \ + (G_TYPE_CHECK_CLASS_TYPE((klass),GST_TYPE_SNDIOSINK)) + +typedef struct _GstSndioSink GstSndioSink; +typedef struct _GstSndioSinkClass GstSndioSinkClass; + +struct _GstSndioSink { + GstAudioSink sink; + + struct sio_hdl *hdl; + gchar *host; + + /* bytes per frame */ + int bpf; + + /* frames counts */ + volatile long long realpos; + volatile long long playpos; + volatile guint latency; + + GstCaps *cur_caps; +}; + +struct _GstSndioSinkClass { + GstAudioSinkClass parent_class; +}; + +GType gst_sndiosink_get_type (void); + +G_END_DECLS + +#endif /* __GST_SNDIOSINK_H__ */ diff --git a/ext/sndio/sndiosrc.c b/ext/sndio/sndiosrc.c new file mode 100644 index 0000000000..43cf35accc --- /dev/null +++ b/ext/sndio/sndiosrc.c @@ -0,0 +1,531 @@ +/* + * Copyright (C) <2008> Jacob Meuser + * + * Permission to use, copy, modify, and distribute this software for any + * purpose with or without fee is hereby granted, provided that the above + * copyright notice and this permission notice appear in all copies. + * + * THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES + * WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF + * MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR + * ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES + * WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN + * ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF + * OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. + */ + +/** + * SECTION:element-sndiosrc + * @see_also: #GstAutoAudioSrc + * + * + * + * This element retrieves samples from a sound card using sndio. + * + * + * Simple example pipeline that records an Ogg/Vorbis file via sndio: + * + * gst-launch -v sndiosrc ! audioconvert ! vorbisenc ! oggmux ! filesink location=foo.ogg + * + * + * + */ + +#ifdef HAVE_CONFIG_H +#include "config.h" +#endif + +#include "sndiosrc.h" +#include +#include + +#include + +GST_DEBUG_CATEGORY_EXTERN (gst_sndio_debug); +#define GST_CAT_DEFAULT gst_sndio_debug + +enum +{ + PROP_0, + PROP_HOST +}; + +static GstStaticPadTemplate sndio_src_factory = + GST_STATIC_PAD_TEMPLATE ("src", + GST_PAD_SRC, + GST_PAD_ALWAYS, + GST_STATIC_CAPS ("audio/x-raw-int, " + "endianness = (int) { 1234, 4321 }, " + "signed = (boolean) { TRUE, FALSE }, " + "width = (int) { 8, 16, 24, 32 }, " + "depth = (int) { 8, 16, 24, 32 }, " + "rate = (int) [ 8000, 192000 ], " + "channels = (int) [ 1, 16 ] ") + ); + +static void gst_sndiosrc_finalize (GObject * object); + +static GstCaps *gst_sndiosrc_getcaps (GstBaseSrc * bsrc); + +static gboolean gst_sndiosrc_open (GstAudioSrc * asrc); +static gboolean gst_sndiosrc_close (GstAudioSrc * asrc); +static gboolean gst_sndiosrc_prepare (GstAudioSrc * asrc, + GstRingBufferSpec * spec); +static gboolean gst_sndiosrc_unprepare (GstAudioSrc * asrc); +static guint gst_sndiosrc_read (GstAudioSrc * asrc, gpointer data, + guint length); +static guint gst_sndiosrc_delay (GstAudioSrc * asrc); +static void gst_sndiosrc_reset (GstAudioSrc * asrc); + +static void gst_sndiosrc_set_property (GObject * object, guint prop_id, + const GValue * value, GParamSpec * pspec); +static void gst_sndiosrc_get_property (GObject * object, guint prop_id, + GValue * value, GParamSpec * pspec); +static void gst_sndiosrc_cb (void *addr, int delta); + +GST_BOILERPLATE (GstSndioSrc, gst_sndiosrc, GstAudioSrc, + GST_TYPE_AUDIO_SRC); + +static void +gst_sndiosrc_base_init (gpointer g_class) +{ + GstElementClass *element_class = GST_ELEMENT_CLASS (g_class); + + gst_element_class_set_details_simple (element_class, + "Sndio audio source", + "Source/Audio", + "Records audio through sndio", + "Jacob Meuser "); + + gst_element_class_add_pad_template (element_class, + gst_static_pad_template_get (&sndio_src_factory)); +} + +static void +gst_sndiosrc_class_init (GstSndioSrcClass * klass) +{ + GObjectClass *gobject_class; + GstBaseSrcClass *gstbasesrc_class; + GstBaseAudioSrcClass *gstbaseaudiosrc_class; + GstAudioSrcClass *gstaudiosrc_class; + + gobject_class = (GObjectClass *) klass; + gstbasesrc_class = (GstBaseSrcClass *) klass; + gstbaseaudiosrc_class = (GstBaseAudioSrcClass *) klass; + gstaudiosrc_class = (GstAudioSrcClass *) klass; + + parent_class = g_type_class_peek_parent (klass); + + gobject_class->finalize = gst_sndiosrc_finalize; + + gstbasesrc_class->get_caps = GST_DEBUG_FUNCPTR (gst_sndiosrc_getcaps); + + gstaudiosrc_class->open = GST_DEBUG_FUNCPTR (gst_sndiosrc_open); + gstaudiosrc_class->close = GST_DEBUG_FUNCPTR (gst_sndiosrc_close); + gstaudiosrc_class->prepare = GST_DEBUG_FUNCPTR (gst_sndiosrc_prepare); + gstaudiosrc_class->unprepare = GST_DEBUG_FUNCPTR (gst_sndiosrc_unprepare); + gstaudiosrc_class->read = GST_DEBUG_FUNCPTR (gst_sndiosrc_read); + gstaudiosrc_class->delay = GST_DEBUG_FUNCPTR (gst_sndiosrc_delay); + gstaudiosrc_class->reset = GST_DEBUG_FUNCPTR (gst_sndiosrc_reset); + + gobject_class->set_property = gst_sndiosrc_set_property; + gobject_class->get_property = gst_sndiosrc_get_property; + + /* default value is filled in the _init method */ + g_object_class_install_property (gobject_class, PROP_HOST, + g_param_spec_string ("host", "Host", + "Device or socket sndio will access", NULL, G_PARAM_READWRITE)); +} + +static void +gst_sndiosrc_init (GstSndioSrc * sndiosrc, GstSndioSrcClass * klass) +{ + sndiosrc->hdl = NULL; + sndiosrc->host = g_strdup (g_getenv ("AUDIODEVICE")); +} + +static void +gst_sndiosrc_finalize (GObject * object) +{ + GstSndioSrc *sndiosrc = GST_SNDIOSRC (object); + + gst_caps_replace (&sndiosrc->cur_caps, NULL); + g_free (sndiosrc->host); + + G_OBJECT_CLASS (parent_class)->finalize (object); +} + +static GstCaps * +gst_sndiosrc_getcaps (GstBaseSrc * bsrc) +{ + GstSndioSrc *sndiosrc; + + sndiosrc = GST_SNDIOSRC (bsrc); + + /* no hdl, we're done with the template caps */ + if (sndiosrc->cur_caps == NULL) { + GST_LOG_OBJECT (sndiosrc, "getcaps called, returning template caps"); + return NULL; + } + + GST_LOG_OBJECT (sndiosrc, "returning %" GST_PTR_FORMAT, + sndiosrc->cur_caps); + + return gst_caps_ref (sndiosrc->cur_caps); +} + +static gboolean +gst_sndiosrc_open (GstAudioSrc * asrc) +{ + GstPadTemplate *pad_template; + GstSndioSrc *sndiosrc; + struct sio_par par; + struct sio_cap cap; + GArray *rates, *chans; + GValue rates_v = { 0 }; + GValue chans_v = { 0 }; + GValue value = { 0 }; + struct sio_enc enc; + struct sio_conf conf; + int confs[SIO_NCONF]; + int rate, chan; + int i, j, k; + int nconfs; + + sndiosrc = GST_SNDIOSRC (asrc); + + GST_DEBUG_OBJECT (sndiosrc, "open"); + + /* connect */ + sndiosrc->hdl = sio_open (sndiosrc->host, SIO_REC, 0); + + if (sndiosrc->hdl == NULL) + goto couldnt_connect; + + /* Use sndio defaults as the only encodings, but get the supported + * sample rates and number of channels. + */ + + if (!sio_getpar (sndiosrc->hdl, &par)) + goto no_server_info; + + if (!sio_getcap (sndiosrc->hdl, &cap)) + goto no_server_info; + + rates = g_array_new (FALSE, FALSE, sizeof (int)); + chans = g_array_new (FALSE, FALSE, sizeof (int)); + + /* find confs that have the default encoding */ + nconfs = 0; + for (i = 0; i < cap.nconf; i++) { + for (j = 0; j < SIO_NENC; j++) { + if (cap.confs[i].enc & (1 << j)) { + enc = cap.enc[j]; + if (enc.bits == par.bits && enc.sig == par.sig && enc.le == par.le) { + confs[nconfs] = i; + nconfs++; + break; + } + } + } + } + + /* find the rates and channels of the confs that have the default encoding */ + for (i = 0; i < nconfs; i++) { + conf = cap.confs[confs[i]]; + /* rates */ + for (j = 0; j < SIO_NRATE; j++) { + if (conf.rate & (1 << j)) { + rate = cap.rate[j]; + for (k = 0; k < rates->len && rate; k++) { + if (rate == g_array_index (rates, int, k)) + rate = 0; + } + /* add in ascending order */ + if (rate) { + for (k = 0; k < rates->len; k++) { + if (rate < g_array_index (rates, int, k)) + { + g_array_insert_val (rates, k, rate); + break; + } + } + if (k == rates->len) + g_array_append_val (rates, rate); + } + } + } + /* channels */ + for (j = 0; j < SIO_NCHAN; j++) { + if (conf.rchan & (1 << j)) { + chan = cap.rchan[j]; + for (k = 0; k < chans->len && chan; k++) { + if (chan == g_array_index (chans, int, k)) + chan = 0; + } + /* add in ascending order */ + if (chan) { + for (k = 0; k < chans->len; k++) { + if (chan < g_array_index (chans, int, k)) + { + g_array_insert_val (chans, k, chan); + break; + } + } + if (k == chans->len) + g_array_append_val (chans, chan); + } + } + } + } + /* not sure how this can happen, but it might */ + if (cap.nconf == 0) { + g_array_append_val (rates, par.rate); + g_array_append_val (chans, par.rchan); + } + + g_value_init (&rates_v, GST_TYPE_LIST); + g_value_init (&chans_v, GST_TYPE_LIST); + g_value_init (&value, G_TYPE_INT); + + for (i = 0; i < rates->len; i++) { + g_value_set_int (&value, g_array_index (rates, int, i)); + gst_value_list_append_value (&rates_v, &value); + } + for (i = 0; i < chans->len; i++) { + g_value_set_int (&value, g_array_index (chans, int, i)); + gst_value_list_append_value (&chans_v, &value); + } + + g_array_free (rates, TRUE); + g_array_free (chans, TRUE); + + pad_template = gst_static_pad_template_get (&sndio_src_factory); + sndiosrc->cur_caps = + gst_caps_copy (gst_pad_template_get_caps (pad_template)); + gst_object_unref (pad_template); + + for (i = 0; i < sndiosrc->cur_caps->structs->len; i++) { + GstStructure *s; + + s = gst_caps_get_structure (sndiosrc->cur_caps, i); + gst_structure_set (s, "endianness", G_TYPE_INT, par.le ? 1234 : 4321, NULL); + gst_structure_set (s, "signed", G_TYPE_BOOLEAN, par.sig ? TRUE : FALSE, + NULL); + gst_structure_set (s, "width", G_TYPE_INT, par.bits, NULL); + // gst_structure_set (s, "depth", G_TYPE_INT, par.bps * 8, NULL); /* XXX */ + gst_structure_set_value (s, "rate", &rates_v); + gst_structure_set_value (s, "channels", &chans_v); + } + + return TRUE; + + /* ERRORS */ +couldnt_connect: + { + GST_ELEMENT_ERROR (sndiosrc, RESOURCE, OPEN_READ, + (_("Could not establish connection to sndio")), + ("can't open connection to sndio")); + return FALSE; + } +no_server_info: + { + GST_ELEMENT_ERROR (sndiosrc, RESOURCE, OPEN_READ, + (_("Failed to query sndio capabilities")), + ("couldn't get sndio info!")); + return FALSE; + } +} + +static gboolean +gst_sndiosrc_close (GstAudioSrc * asrc) +{ + GstSndioSrc *sndiosrc = GST_SNDIOSRC (asrc); + + GST_DEBUG_OBJECT (sndiosrc, "close"); + + gst_caps_replace (&sndiosrc->cur_caps, NULL); + sio_close (sndiosrc->hdl); + sndiosrc->hdl = NULL; + + return TRUE; +} + +static void +gst_sndiosrc_cb (void *addr, int delta) +{ + GstSndioSrc *sndiosrc = GST_SNDIOSRC ((GstAudioSrc *) addr); + + sndiosrc->realpos += delta; + + if (sndiosrc->readpos >= sndiosrc->realpos) + sndiosrc->latency = 0; + else + sndiosrc->latency = sndiosrc->realpos - sndiosrc->readpos; +} + +static gboolean +gst_sndiosrc_prepare (GstAudioSrc * asrc, GstRingBufferSpec * spec) +{ + GstSndioSrc *sndiosrc = GST_SNDIOSRC (asrc); + struct sio_par par; + int spec_bpf; + + GST_DEBUG_OBJECT (sndiosrc, "prepare"); + + sndiosrc->readpos = sndiosrc->realpos = sndiosrc->latency = 0; + + sio_initpar (&par); + par.sig = spec->sign; + par.le = !spec->bigend; + par.bits = spec->width; + // par.bps = spec->depth / 8; /* XXX */ + par.rate = spec->rate; + par.rchan = spec->channels; + + spec_bpf = ((spec->width / 8) * spec->channels); + + par.round = spec->segsize / spec_bpf; + par.appbufsz = (spec->segsize * spec->segtotal) / spec_bpf; + + if (!sio_setpar (sndiosrc->hdl, &par)) + goto cannot_configure; + + sio_getpar (sndiosrc->hdl, &par); + + spec->sign = par.sig; + spec->bigend = !par.le; + spec->width = par.bits; + // spec->depth = par.bps * 8; /* XXX */ + spec->rate = par.rate; + spec->channels = par.rchan; + + sndiosrc->bpf = par.bps * par.rchan; + + spec->segsize = par.round * par.rchan * par.bps; + spec->segtotal = par.bufsz / par.round; + + /* FIXME: this is wrong for signed ints (and the + * audioringbuffers should do it for us anyway) */ + spec->silence_sample[0] = 0; + spec->silence_sample[1] = 0; + spec->silence_sample[2] = 0; + spec->silence_sample[3] = 0; + + sio_onmove (sndiosrc->hdl, gst_sndiosrc_cb, sndiosrc); + + if (!sio_start (sndiosrc->hdl)) + goto cannot_start; + + GST_INFO_OBJECT (sndiosrc, "successfully opened connection to sndio"); + + return TRUE; + + /* ERRORS */ +cannot_configure: + { + GST_ELEMENT_ERROR (sndiosrc, RESOURCE, OPEN_READ, + (_("Could not configure sndio")), ("can't configure sndio")); + return FALSE; + } +cannot_start: + { + GST_ELEMENT_ERROR (sndiosrc, RESOURCE, OPEN_READ, + (_("Could not start sndio")), ("can't start sndio")); + return FALSE; + } +} + +static gboolean +gst_sndiosrc_unprepare (GstAudioSrc * asrc) +{ + GstSndioSrc *sndiosrc = GST_SNDIOSRC (asrc); + + if (sndiosrc->hdl == NULL) + return TRUE; + + sio_stop (sndiosrc->hdl); + + return TRUE; +} + +static guint +gst_sndiosrc_read (GstAudioSrc * asrc, gpointer data, guint length) +{ + GstSndioSrc *sndiosrc = GST_SNDIOSRC (asrc); + guint done; + + done = sio_read (sndiosrc->hdl, data, length); + + if (done == 0) + goto read_error; + + sndiosrc->readpos += (done / sndiosrc->bpf); + + data = (char *) data + done; + + return done; + + /* ERRORS */ +read_error: + { + GST_ELEMENT_ERROR (sndiosrc, RESOURCE, READ, + ("Failed to read data from sndio"), GST_ERROR_SYSTEM); + return 0; + } +} + +static guint +gst_sndiosrc_delay (GstAudioSrc * asrc) +{ + GstSndioSrc *sndiosrc = GST_SNDIOSRC (asrc); + + if (sndiosrc->latency == (guint) - 1) { + GST_WARNING_OBJECT (asrc, "couldn't get latency"); + return 0; + } + + GST_DEBUG_OBJECT (asrc, "got latency: %u", sndiosrc->latency); + + return sndiosrc->latency; +} + +static void +gst_sndiosrc_reset (GstAudioSrc * asrc) +{ + /* no way to flush the buffers with sndio ? */ + + GST_DEBUG_OBJECT (asrc, "reset called"); +} + +static void +gst_sndiosrc_set_property (GObject * object, guint prop_id, + const GValue * value, GParamSpec * pspec) +{ + GstSndioSrc *sndiosrc = GST_SNDIOSRC (object); + + switch (prop_id) { + case PROP_HOST: + g_free (sndiosrc->host); + sndiosrc->host = g_value_dup_string (value); + break; + default: + break; + } +} + +static void +gst_sndiosrc_get_property (GObject * object, guint prop_id, GValue * value, + GParamSpec * pspec) +{ + GstSndioSrc *sndiosrc = GST_SNDIOSRC (object); + + switch (prop_id) { + case PROP_HOST: + g_value_set_string (value, sndiosrc->host); + break; + default: + G_OBJECT_WARN_INVALID_PROPERTY_ID (object, prop_id, pspec); + break; + } +} diff --git a/ext/sndio/sndiosrc.h b/ext/sndio/sndiosrc.h new file mode 100644 index 0000000000..48e7a26060 --- /dev/null +++ b/ext/sndio/sndiosrc.h @@ -0,0 +1,67 @@ +/* + * Copyright (C) <2008> Jacob Meuser + * + * Permission to use, copy, modify, and distribute this software for any + * purpose with or without fee is hereby granted, provided that the above + * copyright notice and this permission notice appear in all copies. + * + * THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES + * WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF + * MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR + * ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES + * WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN + * ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF + * OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. + */ + + +#ifndef __GST_SNDIOSRC_H__ +#define __GST_SNDIOSRC_H__ + +#include + +#include +#include + +G_BEGIN_DECLS + +#define GST_TYPE_SNDIOSRC \ + (gst_sndiosrc_get_type()) +#define GST_SNDIOSRC(obj) \ + (G_TYPE_CHECK_INSTANCE_CAST((obj),GST_TYPE_SNDIOSRC,GstSndioSrc)) +#define GST_SNDIOSRC_CLASS(klass) \ + (G_TYPE_CHECK_CLASS_CAST((klass),GST_TYPE_SNDIOSRC,GstSndioSrcClass)) +#define GST_IS_SNDIOSRC(obj) \ + (G_TYPE_CHECK_INSTANCE_TYPE((obj),GST_TYPE_SNDIOSRC)) +#define GST_IS_SNDIOSRC_CLASS(klass) \ + (G_TYPE_CHECK_CLASS_TYPE((klass),GST_TYPE_SNDIOSRC)) + +typedef struct _GstSndioSrc GstSndioSrc; +typedef struct _GstSndioSrcClass GstSndioSrcClass; + +struct _GstSndioSrc { + GstAudioSrc src; + + struct sio_hdl *hdl; + gchar *host; + + /* bytes per frame */ + int bpf; + + /* frames counts */ + volatile long long realpos; + volatile long long readpos; + volatile guint latency; + + GstCaps *cur_caps; +}; + +struct _GstSndioSrcClass { + GstAudioSrcClass parent_class; +}; + +GType gst_sndiosrc_get_type (void); + +G_END_DECLS + +#endif /* __GST_SNDIOSRC_H__ */