/* * empathy-gst-audio-sink.c - Source for EmpathyGstAudioSink * Copyright (C) 2008 Collabora Ltd. * @author Sjoerd Simons * * This library is free software; you can redistribute it and/or * modify it under the terms of the GNU Lesser General Public * License as published by the Free Software Foundation; either * version 2.1 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 * Lesser General Public License for more details. * * You should have received a copy of the GNU Lesser General Public * License along with this library; if not, write to the Free Software * Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA */ #include #include #include #include #include "empathy-audio-sink.h" G_DEFINE_TYPE(EmpathyGstAudioSink, empathy_audio_sink, GST_TYPE_BIN) /* signal enum */ #if 0 enum { LAST_SIGNAL }; static guint signals[LAST_SIGNAL] = {0}; #endif typedef struct { GstPad *pad; GstElement *bin; GstElement *volume; GstElement *sink; } AudioBin; static AudioBin * audio_bin_new (GstPad *pad, GstElement *bin, GstElement *volume, GstElement *sink) { AudioBin *result = g_slice_new0 (AudioBin); result->pad = pad; result->bin = bin; result->volume = gst_object_ref (volume); result->sink = sink; return result; } static void audio_bin_free (AudioBin *bin) { gst_object_unref (bin->volume); g_slice_free (AudioBin, bin); } static GstStaticPadTemplate sink_template = GST_STATIC_PAD_TEMPLATE( "sink%d", GST_PAD_SINK, GST_PAD_REQUEST, GST_STATIC_CAPS ( GST_AUDIO_INT_PAD_TEMPLATE_CAPS " ; " GST_AUDIO_FLOAT_PAD_TEMPLATE_CAPS) ); enum { PROP_VOLUME = 1, }; struct _EmpathyGstAudioSinkPrivate { gboolean dispose_has_run; FsElementAddedNotifier *notifier; gdouble volume; /* Pad -> *owned* subbin hash */ GHashTable *audio_bins; /* Mutex to hold while change the hash table */ GMutex *audio_bins_lock; }; #define EMPATHY_GST_AUDIO_SINK_GET_PRIVATE(o) \ (G_TYPE_INSTANCE_GET_PRIVATE ((o), EMPATHY_TYPE_GST_AUDIO_SINK, \ EmpathyGstAudioSinkPrivate)) static void empathy_audio_sink_element_added_cb (FsElementAddedNotifier *notifier, GstBin *bin, GstElement *element, EmpathyGstAudioSink *self) { EmpathyGstAudioSinkPrivate *priv = EMPATHY_GST_AUDIO_SINK_GET_PRIVATE (self); if (g_object_class_find_property (G_OBJECT_GET_CLASS (element), "volume")) { /* An element was added with a volume property, lets find its subbin and * update the volume in it */ GHashTableIter iter; AudioBin *audio_bin = NULL; gpointer value; g_mutex_lock (self->priv->audio_bins_lock); g_hash_table_iter_init (&iter, priv->audio_bins); while (g_hash_table_iter_next (&iter, NULL, &value)) { AudioBin *b = value; if (gst_object_has_ancestor (GST_OBJECT (element), GST_OBJECT (b->bin))) { audio_bin = b; break; } } if (audio_bin == NULL) { g_warning ("Element added that doesn't belong to us ?"); return; } /* Set the old volume to 1 and the new volume to the volume */ g_object_set (audio_bin->volume, "volume", 1.0, NULL); gst_object_unref (audio_bin->volume); audio_bin->volume = gst_object_ref (element); g_object_set (audio_bin->volume, "volume", self->priv->volume, NULL); g_mutex_unlock (self->priv->audio_bins_lock); } } static void empathy_audio_sink_init (EmpathyGstAudioSink *self) { EmpathyGstAudioSinkPrivate *priv; priv = self->priv = EMPATHY_GST_AUDIO_SINK_GET_PRIVATE (self); priv->volume = 1.0; priv->audio_bins = g_hash_table_new_full (g_direct_hash, g_direct_equal, NULL, (GDestroyNotify) audio_bin_free); priv->audio_bins_lock = g_mutex_new (); priv->notifier = fs_element_added_notifier_new (); g_signal_connect (priv->notifier, "element-added", G_CALLBACK (empathy_audio_sink_element_added_cb), self); } static void empathy_audio_sink_dispose (GObject *object); static void empathy_audio_sink_finalize (GObject *object); static GstPad * empathy_audio_sink_request_new_pad (GstElement *self, GstPadTemplate *templ, const gchar* name); static void empathy_audio_sink_release_pad (GstElement *self, GstPad *pad); static void empathy_audio_sink_set_property (GObject *object, guint property_id, const GValue *value, GParamSpec *pspec) { switch (property_id) { case PROP_VOLUME: empathy_audio_sink_set_volume (EMPATHY_GST_AUDIO_SINK (object), g_value_get_double (value)); break; default: G_OBJECT_WARN_INVALID_PROPERTY_ID (object, property_id, pspec); } } static void empathy_audio_sink_get_property (GObject *object, guint property_id, GValue *value, GParamSpec *pspec) { switch (property_id) { case PROP_VOLUME: g_value_set_double (value, empathy_audio_sink_get_volume (EMPATHY_GST_AUDIO_SINK (object))); break; default: G_OBJECT_WARN_INVALID_PROPERTY_ID (object, property_id, pspec); } } static void empathy_audio_sink_class_init (EmpathyGstAudioSinkClass *empathy_audio_sink_class) { GObjectClass *object_class = G_OBJECT_CLASS (empathy_audio_sink_class); GstElementClass *element_class = GST_ELEMENT_CLASS (empathy_audio_sink_class); GParamSpec *param_spec; gst_element_class_add_pad_template (element_class, gst_static_pad_template_get (&sink_template)); g_type_class_add_private (empathy_audio_sink_class, sizeof (EmpathyGstAudioSinkPrivate)); object_class->dispose = empathy_audio_sink_dispose; object_class->finalize = empathy_audio_sink_finalize; object_class->set_property = empathy_audio_sink_set_property; object_class->get_property = empathy_audio_sink_get_property; element_class->request_new_pad = empathy_audio_sink_request_new_pad; element_class->release_pad = empathy_audio_sink_release_pad; param_spec = g_param_spec_double ("volume", "Volume", "volume control", 0.0, 5.0, 1.0, G_PARAM_READWRITE | G_PARAM_STATIC_STRINGS); g_object_class_install_property (object_class, PROP_VOLUME, param_spec); } void empathy_audio_sink_dispose (GObject *object) { EmpathyGstAudioSink *self = EMPATHY_GST_AUDIO_SINK (object); EmpathyGstAudioSinkPrivate *priv = EMPATHY_GST_AUDIO_SINK_GET_PRIVATE (self); if (priv->dispose_has_run) return; priv->dispose_has_run = TRUE; if (priv->notifier != NULL) g_object_unref (priv->notifier); priv->notifier = NULL; if (priv->audio_bins != NULL) g_hash_table_unref (priv->audio_bins); priv->audio_bins = NULL; if (priv->audio_bins_lock != NULL) g_mutex_free (priv->audio_bins_lock); priv->audio_bins_lock = NULL; if (G_OBJECT_CLASS (empathy_audio_sink_parent_class)->dispose) G_OBJECT_CLASS (empathy_audio_sink_parent_class)->dispose (object); } void empathy_audio_sink_finalize (GObject *object) { //EmpathyGstAudioSink *self = EMPATHY_GST_AUDIO_SINK (object); //EmpathyGstAudioSinkPrivate *priv = // EMPATHY_GST_AUDIO_SINK_GET_PRIVATE (self); /* free any data held directly by the object here */ G_OBJECT_CLASS (empathy_audio_sink_parent_class)->finalize (object); } GstElement * empathy_audio_sink_new (void) { static gboolean registered = FALSE; if (!registered) { if (!gst_element_register (NULL, "empathyaudiosink", GST_RANK_NONE, EMPATHY_TYPE_GST_AUDIO_SINK)) return NULL; registered = TRUE; } return gst_element_factory_make ("empathyaudiosink", NULL); } void empathy_audio_sink_set_volume (EmpathyGstAudioSink *sink, gdouble volume) { EmpathyGstAudioSinkPrivate *priv = EMPATHY_GST_AUDIO_SINK_GET_PRIVATE (sink); GHashTableIter iter; gpointer value; priv->volume = volume; g_mutex_lock (priv->audio_bins_lock); g_hash_table_iter_init (&iter, priv->audio_bins); while (g_hash_table_iter_next (&iter, NULL, &value)) { AudioBin *b = value; g_object_set (b->volume, "volume", volume, NULL); } g_mutex_unlock (priv->audio_bins_lock); } gdouble empathy_audio_sink_get_volume (EmpathyGstAudioSink *sink) { EmpathyGstAudioSinkPrivate *priv = EMPATHY_GST_AUDIO_SINK_GET_PRIVATE (sink); return priv->volume; } static GstPad * empathy_audio_sink_request_new_pad (GstElement *element, GstPadTemplate *templ, const gchar* name) { EmpathyGstAudioSink *self = EMPATHY_GST_AUDIO_SINK (element); GstElement *bin, *sink, *volume, *resample, *audioconvert0, *audioconvert1; GstPad *pad = NULL; GstPad *subpad, *filterpad; AudioBin *audiobin; bin = gst_bin_new (NULL); audioconvert0 = gst_element_factory_make ("audioconvert", NULL); if (audioconvert0 == NULL) goto error; gst_bin_add (GST_BIN (bin), audioconvert0); resample = gst_element_factory_make ("audioresample", NULL); if (resample == NULL) goto error; gst_bin_add (GST_BIN (bin), resample); audioconvert1 = gst_element_factory_make ("audioconvert", NULL); if (audioconvert1 == NULL) goto error; gst_bin_add (GST_BIN (bin), audioconvert1); volume = gst_element_factory_make ("volume", NULL); if (volume == NULL) goto error; gst_bin_add (GST_BIN (bin), volume); sink = gst_element_factory_make ("gconfaudiosink", NULL); if (sink == NULL) goto error; gst_bin_add (GST_BIN (bin), sink); fs_element_added_notifier_add (self->priv->notifier, GST_BIN (sink)); if (!gst_element_link_many (audioconvert0, resample, audioconvert1, volume, sink, NULL)) goto error; filterpad = gst_element_get_static_pad (audioconvert0, "sink"); if (filterpad == NULL) goto error; subpad = gst_ghost_pad_new ("sink", filterpad); if (!gst_element_add_pad (GST_ELEMENT (bin), subpad)) goto error; /* Ensure that state changes only happen _after_ the element has been added * to the hash table. But add it to the bin first so we can create our * ghostpad (if we create the ghostpad before adding it to the bin it will * get unlinked) */ gst_element_set_locked_state (GST_ELEMENT (bin), TRUE); gst_bin_add (GST_BIN (self), bin); pad = gst_ghost_pad_new (name, subpad); g_assert (pad != NULL); audiobin = audio_bin_new (pad, bin, volume, sink); g_mutex_lock (self->priv->audio_bins_lock); g_hash_table_insert (self->priv->audio_bins, pad, audiobin); g_mutex_unlock (self->priv->audio_bins_lock); gst_element_set_locked_state (GST_ELEMENT (bin), FALSE); if (!gst_element_sync_state_with_parent (bin)) goto error; if (!gst_pad_set_active (pad, TRUE)) goto error; if (!gst_element_add_pad (GST_ELEMENT (self), pad)) goto error; return pad; error: if (pad != NULL) { g_mutex_lock (self->priv->audio_bins_lock); g_hash_table_remove (self->priv->audio_bins, pad); g_mutex_unlock (self->priv->audio_bins_lock); gst_object_unref (pad); } gst_object_unref (bin); g_warning ("Failed to create output subpipeline"); return NULL; } static void empathy_audio_sink_release_pad (GstElement *element, GstPad *pad) { EmpathyGstAudioSink *self = EMPATHY_GST_AUDIO_SINK (element); AudioBin *abin; g_mutex_lock (self->priv->audio_bins_lock); abin = g_hash_table_lookup (self->priv->audio_bins, pad); g_hash_table_steal (self->priv->audio_bins, pad); g_mutex_unlock (self->priv->audio_bins_lock); if (abin == NULL) { g_warning ("Releasing a pad that doesn't belong to us ?"); return; } gst_pad_set_active (pad, FALSE); gst_element_remove_pad (element, pad); gst_element_set_locked_state (abin->bin, TRUE); gst_element_set_state (abin->bin, GST_STATE_NULL); gst_bin_remove (GST_BIN (self), abin->bin); audio_bin_free (abin); }