]> git.0d.be Git - empathy.git/blobdiff - src/empathy-audio-sink.c
Merge branch 'gnome-3-8'
[empathy.git] / src / empathy-audio-sink.c
index 1d2169593f73739dad371b861164a48239a6f575..985a1fa36d83d8f825277b1676dc543601e40cd6 100644 (file)
  * Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA  02110-1301  USA
  */
 
+#include "config.h"
+#include "empathy-audio-sink.h"
 
-#include <stdio.h>
-#include <stdlib.h>
+#ifdef HAVE_GST1
+#include <gst/audio/streamvolume.h>
+#else
+#include <gst/interfaces/streamvolume.h>
+#endif
 
-#include <gst/farsight/fs-element-added-notifier.h>
-
-#include "empathy-audio-sink.h"
+#include "empathy-audio-utils.h"
 
+#define DEBUG_FLAG EMPATHY_DEBUG_VOIP
+#include "empathy-debug.h"
 
 G_DEFINE_TYPE(EmpathyGstAudioSink, empathy_audio_sink, GST_TYPE_BIN)
 
@@ -39,19 +44,29 @@ enum
 static guint signals[LAST_SIGNAL] = {0};
 #endif
 
+static GstStaticPadTemplate sink_template = GST_STATIC_PAD_TEMPLATE(
+    "sink%d",
+    GST_PAD_SINK,
+    GST_PAD_REQUEST,
+#ifdef HAVE_GST1
+    GST_STATIC_CAPS ( "audio/x-raw" )
+#else
+    GST_STATIC_CAPS ( GST_AUDIO_INT_PAD_TEMPLATE_CAPS " ; "
+        GST_AUDIO_FLOAT_PAD_TEMPLATE_CAPS)
+#endif
+);
+
 enum {
   PROP_VOLUME = 1,
 };
 
-/* private structure */
-typedef struct _EmpathyGstAudioSinkPrivate EmpathyGstAudioSinkPrivate;
-
 struct _EmpathyGstAudioSinkPrivate
 {
-  gboolean dispose_has_run;
   GstElement *sink;
-  GstElement *volume;
-  FsElementAddedNotifier *notifier;
+  gboolean echo_cancel;
+  gdouble volume;
+  gint volume_idle_id;
+  GMutex volume_mutex;
 };
 
 #define EMPATHY_GST_AUDIO_SINK_GET_PRIVATE(o) \
@@ -59,70 +74,38 @@ struct _EmpathyGstAudioSinkPrivate
   EmpathyGstAudioSinkPrivate))
 
 static void
-empathy_audio_sink_element_added_cb (FsElementAddedNotifier *notifier,
-  GstBin *bin, GstElement *element, EmpathyGstAudioSink *self)
+empathy_audio_sink_init (EmpathyGstAudioSink *self)
 {
-  EmpathyGstAudioSinkPrivate *priv = EMPATHY_GST_AUDIO_SINK_GET_PRIVATE (self);
-
-  if (g_object_class_find_property (G_OBJECT_GET_CLASS (element), "volume"))
-    {
-      gdouble volume;
-
-      volume = empathy_audio_sink_get_volume (self);
-      empathy_audio_sink_set_volume (self, 1.0);
-
-      if (priv->volume != NULL)
-        g_object_unref (priv->volume);
-      priv->volume = g_object_ref (element);
-
-      if (volume != 1.0)
-        empathy_audio_sink_set_volume (self, volume);
-    }
+  self->priv = EMPATHY_GST_AUDIO_SINK_GET_PRIVATE (self);
+  self->priv->echo_cancel = TRUE;
+  g_mutex_init (&self->priv->volume_mutex);
 }
 
-static void
-empathy_audio_sink_init (EmpathyGstAudioSink *obj)
-{
-  EmpathyGstAudioSinkPrivate *priv = EMPATHY_GST_AUDIO_SINK_GET_PRIVATE (obj);
-  GstElement *resample;
-  GstPad *ghost, *sink;
-
-  priv->notifier = fs_element_added_notifier_new ();
-  g_signal_connect (priv->notifier, "element-added",
-    G_CALLBACK (empathy_audio_sink_element_added_cb), obj);
-
-  resample = gst_element_factory_make ("audioresample", NULL);
-
-  priv->volume = gst_element_factory_make ("volume", NULL);
-  g_object_ref (priv->volume);
-
-  priv->sink = gst_element_factory_make ("gconfaudiosink", NULL);
-
-  fs_element_added_notifier_add (priv->notifier, GST_BIN (priv->sink));
-
-  gst_bin_add_many (GST_BIN (obj), resample, priv->volume, priv->sink, NULL);
-  gst_element_link_many (resample, priv->volume, priv->sink, NULL);
-
-  sink = gst_element_get_static_pad (resample, "sink");
-
-  ghost = gst_ghost_pad_new ("sink", sink);
-  gst_element_add_pad (GST_ELEMENT (obj), ghost);
-
-  gst_object_unref (G_OBJECT (sink));
-}
+#ifdef HAVE_GST1
+static GstPad * empathy_audio_sink_request_new_pad (GstElement *self,
+  GstPadTemplate *templ,
+  const gchar* name,
+  const GstCaps *caps);
+#else
+static GstPad * empathy_audio_sink_request_new_pad (GstElement *self,
+  GstPadTemplate *templ,
+  const gchar* name);
+#endif
 
-static void empathy_audio_sink_dispose (GObject *object);
-static void empathy_audio_sink_finalize (GObject *object);
+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)
 {
+  EmpathyGstAudioSink *self = EMPATHY_GST_AUDIO_SINK (object);
   switch (property_id)
     {
       case PROP_VOLUME:
-        empathy_audio_sink_set_volume (EMPATHY_GST_AUDIO_SINK (object),
-          g_value_get_double (value));
+        g_mutex_lock (&self->priv->volume_mutex);
+        self->priv->volume = g_value_get_double (value);
+        g_mutex_unlock (&self->priv->volume_mutex);
         break;
       default:
         G_OBJECT_WARN_INVALID_PROPERTY_ID (object, property_id, pspec);
@@ -133,32 +116,55 @@ static void
 empathy_audio_sink_get_property (GObject *object,
   guint property_id, GValue *value, GParamSpec *pspec)
 {
+  EmpathyGstAudioSink *self = EMPATHY_GST_AUDIO_SINK (object);
   switch (property_id)
     {
       case PROP_VOLUME:
-        g_value_set_double (value,
-          empathy_audio_sink_get_volume (EMPATHY_GST_AUDIO_SINK (object)));
+        g_value_set_double (value, self->priv->volume);
         break;
       default:
         G_OBJECT_WARN_INVALID_PROPERTY_ID (object, property_id, pspec);
     }
 }
 
+static void
+empathy_audio_sink_dispose (GObject *object)
+{
+  EmpathyGstAudioSink *self = EMPATHY_GST_AUDIO_SINK (object);
+  EmpathyGstAudioSinkPrivate *priv = self->priv;
+
+  if (priv->volume_idle_id != 0)
+    g_source_remove (priv->volume_idle_id);
+  priv->volume_idle_id = 0;
+
+  g_mutex_clear (&self->priv->volume_mutex);
+
+  /* release any references held by the object here */
+  if (G_OBJECT_CLASS (empathy_audio_sink_parent_class)->dispose)
+    G_OBJECT_CLASS (empathy_audio_sink_parent_class)->dispose (object);
+}
+
 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;
+  object_class->dispose = empathy_audio_sink_dispose;
+
+  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,
@@ -166,41 +172,6 @@ empathy_audio_sink_class_init (EmpathyGstAudioSinkClass
   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->volume != NULL)
-    g_object_unref (priv->volume);
-  priv->volume = 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)
 {
@@ -218,18 +189,235 @@ empathy_audio_sink_new (void)
 void
 empathy_audio_sink_set_volume (EmpathyGstAudioSink *sink, gdouble volume)
 {
-  EmpathyGstAudioSinkPrivate *priv = EMPATHY_GST_AUDIO_SINK_GET_PRIVATE (sink);
-
-  g_object_set (G_OBJECT (priv->volume), "volume", volume, NULL);
+  g_object_set (sink, "volume", volume, NULL);
 }
 
 gdouble
 empathy_audio_sink_get_volume (EmpathyGstAudioSink *sink)
 {
   EmpathyGstAudioSinkPrivate *priv = EMPATHY_GST_AUDIO_SINK_GET_PRIVATE (sink);
+
+  return priv->volume;
+}
+
+static GstElement *
+create_sink (EmpathyGstAudioSink *self)
+{
+  GstElement *sink;
+  const gchar *description;
+
+  description = g_getenv ("EMPATHY_AUDIO_SINK");
+
+  if (description != NULL)
+    {
+      GError *error = NULL;
+
+      sink = gst_parse_bin_from_description (description, TRUE, &error);
+      if (sink == NULL)
+        {
+          DEBUG ("Failed to create bin %s: %s", description, error->message);
+          g_error_free (error);
+        }
+
+      return sink;
+    }
+
+  /* Use pulsesink as default */
+  sink = gst_element_factory_make ("pulsesink", NULL);
+  if (sink == NULL)
+    return NULL;
+
+  empathy_audio_set_stream_properties (sink, self->priv->echo_cancel);
+
+  /* Set latency (buffering on the PulseAudio side) of 40ms and transfer data
+   * in 10ms chunks */
+  g_object_set (sink,
+      "buffer-time", (gint64) 40000,
+      "latency-time", (gint64) 10000,
+      NULL);
+
+  return sink;
+}
+
+static gboolean
+empathy_audio_sink_volume_idle_updated (gpointer user_data)
+{
+  EmpathyGstAudioSink *self = EMPATHY_GST_AUDIO_SINK (user_data);
+
+  g_mutex_lock (&self->priv->volume_mutex);
+  self->priv->volume_idle_id = 0;
+  g_mutex_unlock (&self->priv->volume_mutex);
+
+  g_object_notify (G_OBJECT (self), "volume");
+
+  return FALSE;
+}
+
+static void
+empathy_audio_sink_volume_updated (GObject *object,
+  GParamSpec *pspec,
+  gpointer user_data)
+{
+  EmpathyGstAudioSink *self = EMPATHY_GST_AUDIO_SINK (user_data);
+  gdouble volume;
+
+  g_mutex_lock (&self->priv->volume_mutex);
+
+  g_object_get (object, "volume", &volume, NULL);
+  if (self->priv->volume == volume)
+    goto out;
+
+  self->priv->volume = volume;
+  if (self->priv->volume_idle_id == 0)
+    self->priv->volume_idle_id = g_idle_add (
+      empathy_audio_sink_volume_idle_updated, self);
+
+out:
+  g_mutex_unlock (&self->priv->volume_mutex);
+}
+
+static gboolean
+empathy_audio_sink_volume_idle_setup (gpointer user_data)
+{
+  EmpathyGstAudioSink *self = EMPATHY_GST_AUDIO_SINK (user_data);
   gdouble volume;
 
-  g_object_get (G_OBJECT (priv->volume), "volume", &volume, NULL);
+  g_mutex_lock (&self->priv->volume_mutex);
+  self->priv->volume_idle_id = 0;
+  g_mutex_unlock (&self->priv->volume_mutex);
+
+  /* We can't do a bidirection bind as the ::notify comes from another
+   * thread, for other bits of empathy it's most simpler if it comes from
+   * the main thread */
+  g_object_bind_property (self, "volume", self->priv->sink, "volume",
+    G_BINDING_DEFAULT);
+
+  /* sync and callback for bouncing */
+  g_object_get (self->priv->sink, "volume", &volume, NULL);
+  g_object_set (self, "volume", volume, NULL);
+  g_signal_connect (self->priv->sink, "notify::volume",
+    G_CALLBACK (empathy_audio_sink_volume_updated), self);
+
+  return FALSE;
+}
+
+#ifdef HAVE_GST1
+static GstPad *
+empathy_audio_sink_request_new_pad (GstElement *element,
+  GstPadTemplate *templ,
+  const gchar* name,
+  const GstCaps *caps)
+#else
+static GstPad *
+empathy_audio_sink_request_new_pad (GstElement *element,
+  GstPadTemplate *templ,
+  const gchar* name)
+#endif
+{
+  EmpathyGstAudioSink *self = EMPATHY_GST_AUDIO_SINK (element);
+  GstElement *bin, *resample, *audioconvert0, *audioconvert1;
+  GstPad *pad = NULL;
+  GstPad *subpad, *filterpad;
+
+  bin = gst_bin_new (NULL);
+
+  audioconvert0 = gst_element_factory_make ("audioconvert", NULL);
+  if (audioconvert0 == NULL)
+    goto error;
 
-  return volume;
+  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);
+
+  self->priv->sink = create_sink (self);
+  if (self->priv->sink == NULL)
+    goto error;
+
+  if (GST_IS_STREAM_VOLUME (self->priv->sink))
+    {
+      g_mutex_lock (&self->priv->volume_mutex);
+      if (self->priv->volume_idle_id == 0)
+        self->priv->volume_idle_id = g_idle_add (
+          empathy_audio_sink_volume_idle_setup, self);
+      g_mutex_unlock (&self->priv->volume_mutex);
+    }
+  else
+    {
+      gchar *n = gst_element_get_name (self->priv->sink);
+
+      DEBUG ("Element %s doesn't support volume", n);
+      g_free (n);
+    }
+
+  gst_bin_add (GST_BIN (bin), self->priv->sink);
+
+  if (!gst_element_link_many (audioconvert0, resample, audioconvert1,
+      self->priv->sink, NULL))
+    goto error;
+
+  filterpad = gst_element_get_static_pad (audioconvert0, "sink");
+
+  if (filterpad == NULL)
+    goto error;
+
+  subpad = gst_ghost_pad_new ("sink", filterpad);
+  gst_object_unref (filterpad);
+
+  if (!gst_element_add_pad (GST_ELEMENT (bin), subpad))
+    goto error;
+
+  gst_bin_add (GST_BIN (self), bin);
+
+  pad = gst_ghost_pad_new (name, subpad);
+  g_assert (pad != NULL);
+
+  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)
+    {
+      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)
+{
+  gst_pad_set_active (pad, FALSE);
+  gst_element_remove_pad (element, pad);
+}
+
+void
+empathy_audio_sink_set_echo_cancel (EmpathyGstAudioSink *sink,
+  gboolean echo_cancel)
+{
+  DEBUG ("Sink echo cancellation setting: %s", echo_cancel ? "on" : "off");
+  sink->priv->echo_cancel = echo_cancel;
+  if (sink->priv->sink != NULL)
+    empathy_audio_set_stream_properties (sink->priv->sink,
+      sink->priv->echo_cancel);
 }