| /* |
| * 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_static_metadata (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; |
| } |
| } |