//roarsink.c: /* GStreamer * Copyright (C) <2005> Arwed v. Merkatz * Copyright (C) <2008-2011> Philipp 'ph3-der-loewe' Schafft * * * Roughly based on the gstreamer 0.8 esdsink plugin: * Copyright (C) <2001> Richard Boulton * * roarsink.c: an RoarAudio audio sink * * 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, 51 Franklin Street, Fifth Floor, * Boston, MA 02111-1307, USA. */ #ifdef HAVE_CONFIG_H #include "config.h" #endif #include "roarsink.h" #include #include #define _(x) (x) //#include #ifndef ROAR_MAX_WRITE_SIZE #define ROAR_MAX_WRITE_SIZE (21 * 4096) #endif #define ROAR_BUF_SIZE 1764 /* this is dynamic for RoarAudio, use the most common value here for the moment */ //GST_DEBUG_CATEGORY_EXTERN (roar_debug); //#define GST_CAT_DEFAULT roar_debug /* elementfactory information */ static const GstElementDetails roarsink_details = GST_ELEMENT_DETAILS("RoarAudio audio sink", "Sink/Audio", "Plays audio to an RoarAudio server", "Philipp 'ph3-der-loewe' Schafft "); enum { PROP_0, PROP_HOST, PROP_ROLE, PROP_VOLUME, PROP_MUTE }; #define _QM(x) #x #define QM(x) _QM(x) static GstStaticPadTemplate sink_factory = GST_STATIC_PAD_TEMPLATE("sink", GST_PAD_SINK, GST_PAD_ALWAYS, GST_STATIC_CAPS( "audio/x-alaw, " "rate = (int) [ 1, MAX ], " "channels = (int) [ 1, " QM(ROAR_MAX_CHANNELS) " ]; " "audio/x-mulaw, " "rate = (int) [ 1, MAX ], " "channels = (int) [ 1, " QM(ROAR_MAX_CHANNELS) " ]; " // "audio/x-flac; " "audio/x-gsm; " // "application/ogg; " // "audio/x-wav; " /* "audio/x-raw-int, " "endianness = (int) { BIG_ENDIAN, LITTLE_ENDIAN }, " "signed = (boolean) { true, false }, " "width = (int) 32, " "depth = (int) 32, " "rate = (int) [ 1, MAX ], " "channels = (int) [ 1, " QM(ROAR_MAX_CHANNELS) " ]; " */ /* "audio/x-raw-int, " "endianness = (int) { BIG_ENDIAN, LITTLE_ENDIAN }, " "signed = (boolean) { true, false }, " "width = (int) 24, " "depth = (int) 24, " "rate = (int) [ 1, MAX ], " "channels = (int) [ 1, " QM(ROAR_MAX_CHANNELS) " ]; " */ "audio/x-raw-int, " "endianness = (int) { BIG_ENDIAN, LITTLE_ENDIAN }, " "signed = (boolean) { true, false }, " "width = (int) 16, " "depth = (int) 16, " "rate = (int) [ 1, MAX ], " "channels = (int) [ 1, " QM(ROAR_MAX_CHANNELS) " ]; " "audio/x-raw-int, " "signed = (boolean) { true, false }, " "width = (int) 8, " "depth = (int) 8, " "rate = (int) [ 1, MAX ], " "channels = (int) [ 1, " QM(ROAR_MAX_CHANNELS) " ]" ) ); #undef _QM #undef QM static void gst_roarsink_finalize (GObject * object); static GstCaps *gst_roarsink_getcaps (GstBaseSink * bsink); static gboolean gst_roarsink_open (GstAudioSink * asink); static gboolean gst_roarsink_close (GstAudioSink * asink); static gboolean gst_roarsink_prepare (GstAudioSink * asink, GstRingBufferSpec * spec); static gboolean gst_roarsink_unprepare (GstAudioSink * asink); static guint gst_roarsink_write (GstAudioSink * asink, gpointer data, guint length); static guint gst_roarsink_delay (GstAudioSink * asink); static void gst_roarsink_reset (GstAudioSink * asink); static void gst_roarsink_set_property (GObject * object, guint prop_id, const GValue * value, GParamSpec * pspec); static void gst_roarsink_get_property (GObject * object, guint prop_id, GValue * value, GParamSpec * pspec); GST_BOILERPLATE (GstRoarSink, gst_roarsink, GstAudioSink, GST_TYPE_AUDIO_SINK); static void gst_roarsink_base_init (gpointer g_class) { GstElementClass *element_class = GST_ELEMENT_CLASS(g_class); gst_element_class_add_pad_template(element_class, gst_static_pad_template_get(&sink_factory)); gst_element_class_set_details(element_class, &roarsink_details); } static void gst_roarsink_class_init (GstRoarSinkClass * 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_roarsink_finalize; gstbasesink_class->get_caps = GST_DEBUG_FUNCPTR(gst_roarsink_getcaps); gstaudiosink_class->open = GST_DEBUG_FUNCPTR(gst_roarsink_open); gstaudiosink_class->close = GST_DEBUG_FUNCPTR(gst_roarsink_close); gstaudiosink_class->prepare = GST_DEBUG_FUNCPTR(gst_roarsink_prepare); gstaudiosink_class->unprepare = GST_DEBUG_FUNCPTR(gst_roarsink_unprepare); gstaudiosink_class->write = GST_DEBUG_FUNCPTR(gst_roarsink_write); gstaudiosink_class->delay = GST_DEBUG_FUNCPTR(gst_roarsink_delay); gstaudiosink_class->reset = GST_DEBUG_FUNCPTR(gst_roarsink_reset); gobject_class->set_property = gst_roarsink_set_property; gobject_class->get_property = gst_roarsink_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", "The server/host running the RoarAudio daemon", NULL, G_PARAM_READWRITE)); g_object_class_install_property(gobject_class, PROP_HOST, g_param_spec_string("server", "Server", "The server/host running the RoarAudio daemon", NULL, G_PARAM_READWRITE)); g_object_class_install_property(gobject_class, PROP_ROLE, g_param_spec_string("role", "Role", "The stream role", NULL, G_PARAM_READWRITE)); g_object_class_install_property(gobject_class, PROP_VOLUME, g_param_spec_float("volume", "Volume", "The stream volume (0.0 to 1.0)", 0.0, 1.0, 1.0, G_PARAM_READWRITE)); g_object_class_install_property(gobject_class, PROP_MUTE, g_param_spec_boolean("mute", "Mute", "The mute state of the stream", FALSE, G_PARAM_READWRITE)); } static void gst_roarsink_init (GstRoarSink * roarsink, GstRoarSinkClass * klass) { roarsink->vss = NULL; roarsink->host = NULL; roarsink->role = NULL; roarsink->volume = -1; roarsink->mute = ROAR_VS_ASK; } static void gst_roarsink_finalize (GObject * object) { GstRoarSink *roarsink = GST_ROARSINK(object); gst_caps_replace(&roarsink->cur_caps, NULL); if ( roarsink->host != NULL ) g_free(roarsink->host); if ( roarsink->role != NULL ) g_free(roarsink->role); G_OBJECT_CLASS(parent_class)->finalize(object); } static GstCaps * gst_roarsink_getcaps (GstBaseSink * bsink) { GstRoarSink *roarsink; roarsink = GST_ROARSINK(bsink); /* no fd, we're done with the template caps */ if (roarsink->vss == NULL || roarsink->cur_caps == NULL) { GST_LOG_OBJECT(roarsink, "getcaps called, returning template caps"); return NULL; } GST_LOG_OBJECT(roarsink, "returning %" GST_PTR_FORMAT, roarsink->cur_caps); return gst_caps_ref(roarsink->cur_caps); } static gboolean gst_roarsink_open(GstAudioSink * asink) { GstPadTemplate *pad_template; GstRoarSink *roarsink; gint i; struct roar_stream oinfo; roarsink = GST_ROARSINK(asink); GST_DEBUG_OBJECT(roarsink, "open"); /* now try to connect to any existing/running sound daemons */ if ( (roarsink->vss = roar_vs_new(roarsink->host, "gstreamer client", NULL)) == NULL ) goto couldnt_connect; if ( roar_server_oinfo(roar_vs_connection_obj(roarsink->vss, NULL), &oinfo) == -1 ) goto no_server_info; GST_INFO_OBJECT(roarsink, "got server info rate: %i", oinfo.info.rate); pad_template = gst_static_pad_template_get(&sink_factory); roarsink->cur_caps = gst_caps_copy(gst_pad_template_get_caps(pad_template)); for (i = 0; i < roarsink->cur_caps->structs->len; i++) { GstStructure *s; s = gst_caps_get_structure(roarsink->cur_caps, i); gst_structure_set(s, "rate", G_TYPE_INT, oinfo.info.rate, NULL); } GST_INFO_OBJECT(roarsink, "server caps: %" GST_PTR_FORMAT, roarsink->cur_caps); return TRUE; /* ERRORS */ couldnt_connect: { GST_ELEMENT_ERROR(roarsink, RESOURCE, OPEN_WRITE, (_("Could not establish connection to sound server")), ("can't open connection to RoarAudio server")); return FALSE; } no_server_info: { GST_ELEMENT_ERROR(roarsink, RESOURCE, OPEN_WRITE, (_("Failed to query sound server capabilities")), ("couldn't get server info!")); return FALSE; } } static gboolean gst_roarsink_close (GstAudioSink * asink) { GstRoarSink *roarsink = GST_ROARSINK(asink); GST_DEBUG_OBJECT(roarsink, "close"); gst_caps_replace(&roarsink->cur_caps, NULL); roar_vs_close(roarsink->vss, ROAR_VS_FALSE, NULL); roarsink->vss = NULL; return TRUE; } static gboolean gst_roarsink_prepare (GstAudioSink * asink, GstRingBufferSpec * spec) { GstRoarSink *roarsink = GST_ROARSINK(asink); struct roar_audio_info info; info.codec = ROAR_CODEC_DEFAULT; info.bits = spec->depth; info.rate = spec->rate; info.channels = spec->channels; GST_DEBUG_OBJECT(roarsink, "prepare"); GST_INFO_OBJECT(roarsink, "attempting to open data connection to RoarAudio server"); switch (spec->type) { case GST_BUFTYPE_LINEAR: switch (spec->sign) { case TRUE: switch (spec->bigend) { case TRUE: info.codec = ROAR_CODEC_PCM_S_BE; break; case FALSE: info.codec = ROAR_CODEC_PCM_S_LE; break; default: return FALSE; break; } break; case FALSE: switch (spec->bigend) { case TRUE: info.codec = ROAR_CODEC_PCM_U_BE; break; case FALSE: info.codec = ROAR_CODEC_PCM_U_LE; break; default: return FALSE; break; } break; default: return FALSE; break; } break; case GST_BUFTYPE_A_LAW: info.codec = ROAR_CODEC_ALAW; info.bits = 8; break; case GST_BUFTYPE_MU_LAW: info.codec = ROAR_CODEC_MULAW; info.bits = 8; break; case GST_BUFTYPE_GSM: info.codec = ROAR_CODEC_GSM; info.bits = 0; break; default: return FALSE; } if ( roar_vs_stream(roarsink->vss, &info, ROAR_DIR_PLAY, NULL) == -1 ) goto cannot_open; // apply parameters: if ( roarsink->mute != ROAR_VS_ASK && roar_vs_mute(roarsink->vss, roarsink->mute, NULL) == -1 ) { goto cannot_open; } if ( roarsink->volume >= 0.0 && roar_vs_volume_mono(roarsink->vss, roarsink->volume, NULL) == -1 ) { goto cannot_open; } /* get server info */ spec->segsize = ROAR_BUF_SIZE; spec->segtotal = (ROAR_MAX_WRITE_SIZE / spec->segsize); /* FIXME: this is wrong for signed ints (and the * audioringbuffers should do it for us anyway) */ spec->bytes_per_sample = roar_info2framesize(&info) / 8; memset(&(spec->silence_sample), 0, sizeof(spec->silence_sample)); GST_INFO_OBJECT(roarsink, "successfully opened connection to RoarAudio server"); return TRUE; /* ERRORS */ cannot_open: { GST_ELEMENT_ERROR(roarsink, RESOURCE, OPEN_WRITE, (_("Could not establish connection to sound server")), ("can't open connection to RoarAudio server")); return FALSE; } } static gboolean gst_roarsink_unprepare (GstAudioSink * asink) { GstRoarSink *roarsink = GST_ROARSINK(asink); if (roarsink->vss == NULL) return TRUE; roar_vs_close(roarsink->vss, ROAR_VS_FALSE, NULL); roarsink->vss = NULL; GST_INFO_OBJECT(roarsink, "closed sound device"); return TRUE; } static guint gst_roarsink_write (GstAudioSink * asink, gpointer data, guint length) { GstRoarSink *roarsink = GST_ROARSINK(asink); gint to_write = 0; to_write = length; while (to_write > 0) { ssize_t done; done = roar_vs_write(roarsink->vss, data, to_write, NULL); if (done < 0) goto write_error; to_write -= done; data += done; } return length; /* ERRORS */ write_error: { GST_ELEMENT_ERROR(roarsink, RESOURCE, WRITE, ("Failed to write data to the RoarAudio daemon"), GST_ERROR_SYSTEM); return 0; } } static guint gst_roarsink_delay (GstAudioSink * asink) { // GstRoarSink *roarsink = GST_ROARSINK (asink); guint latency; latency = 441; // compile type depending and link level deppendent, // use default value for local operations here for the moment GST_DEBUG_OBJECT(asink, "got latency: %u", latency); return latency; } static void gst_roarsink_reset (GstAudioSink * asink) { GST_DEBUG_OBJECT(asink, "reset called"); } static void gst_roarsink_set_property (GObject * object, guint prop_id, const GValue * value, GParamSpec * pspec) { GstRoarSink *roarsink = GST_ROARSINK(object); switch (prop_id) { case PROP_HOST: if ( roarsink->host != NULL ) g_free(roarsink->host); roarsink->host = g_value_dup_string(value); break; case PROP_ROLE: if ( roarsink->role != NULL ) g_free(roarsink->role); roarsink->role = g_value_dup_string(value); break; case PROP_VOLUME: roarsink->volume = (float)g_value_get_float(value); if ( roarsink->vss == NULL ) return; roar_vs_volume_mono(roarsink->vss, roarsink->volume, NULL); break; case PROP_MUTE: if ( g_value_get_boolean(value) ) { roarsink->mute = ROAR_VS_TRUE; } else { roarsink->mute = ROAR_VS_FALSE; } if ( roarsink->vss == NULL ) return; roar_vs_mute(roarsink->vss, roarsink->mute, NULL); break; default: break; } } static void gst_roarsink_get_property (GObject * object, guint prop_id, GValue * value, GParamSpec * pspec) { GstRoarSink *roarsink = GST_ROARSINK(object); float l, r; int ret; switch (prop_id) { case PROP_HOST: g_value_set_string(value, roarsink->host); break; case PROP_ROLE: g_value_set_string(value, roarsink->role); break; case PROP_VOLUME: if ( roarsink->vss == NULL || roar_vs_volume_get(roarsink->vss, &l, &r, NULL) == -1 ) { l = roarsink->volume >= 0.0 ? roarsink->volume : 1.0; } else { l = (l + r)/2; roarsink->volume = l; } g_value_set_float(value, l); break; case PROP_MUTE: if ( roarsink->vss == NULL || (ret = roar_vs_mute(roarsink->vss, ROAR_VS_ASK, NULL)) == -1 ) { ret = roarsink->mute != ROAR_VS_ASK ? roarsink->mute : ROAR_VS_FALSE; } else { roarsink->mute = ret; } g_value_set_boolean(value, ret == ROAR_VS_TRUE ? TRUE : FALSE); break; default: G_OBJECT_WARN_INVALID_PROPERTY_ID(object, prop_id, pspec); break; } } gboolean gst_roarsink_factory_init (GstPlugin * plugin) { if (!gst_element_register(plugin, "roarsink", GST_RANK_MARGINAL, GST_TYPE_ROARSINK)) return FALSE; return TRUE; }