diff options
Diffstat (limited to 'ext/sndio/sndiosink.c')
-rw-r--r-- | ext/sndio/sndiosink.c | 532 |
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; + } +} |