aboutsummaryrefslogtreecommitdiff
path: root/ext/sndio/sndiosink.c
diff options
context:
space:
mode:
Diffstat (limited to 'ext/sndio/sndiosink.c')
-rw-r--r--ext/sndio/sndiosink.c532
1 files changed, 532 insertions, 0 deletions
diff --git a/ext/sndio/sndiosink.c b/ext/sndio/sndiosink.c
new file mode 100644
index 00000000..e91ecd48
--- /dev/null
+++ b/ext/sndio/sndiosink.c
@@ -0,0 +1,532 @@
+/*
+ * Copyright (C) <2008> Jacob Meuser <jakemsr@sdf.lonestar.org>
+ *
+ * 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
+ *
+ * <refsect2>
+ * <para>
+ * This element outputs sound to a sound card using sndio.
+ * </para>
+ * <para>
+ * Simple example pipeline that plays an Ogg/Vorbis file via sndio:
+ * <programlisting>
+ * gst-launch -v filesrc location=foo.ogg ! decodebin ! audioconvert ! audioresample ! sndiosink
+ * </programlisting>
+ * </para>
+ * </refsect2>
+ */
+
+#ifdef HAVE_CONFIG_H
+#include "config.h"
+#endif
+
+#include "sndiosink.h"
+#include <unistd.h>
+#include <errno.h>
+
+#include <gst/gst-i18n-plugin.h>
+
+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 <jakemsr@sdf.lonestar.org>");
+
+ 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;
+ }
+}