aboutsummaryrefslogblamecommitdiffstats
path: root/src/empathy-audio-sink.c
blob: c410d7a306d34a874d578916008ba03058348a1d (plain) (tree)























                                                                             
                            

                                                   

                               
 










                                                                    





































                                                                    
 



                  


                                  
                                   




                                  


                                                 






                                                                   




                                                                               
                                                                            
     





                                                                              
                                                 

























                                                                           
                                                   



           
                                                   
 
                                   
 
                                                                
 
                     
 

                                                                          
 

                                         


                                                            




                                                          






                                                                     
           




























                                                                           
           



                                                                         

                                                 
                         
 


                                                    




                                                       



                                                               


                                                                      
                                                                         


                                                                          












                                                                               



                                    


                                          
 



                                         


















                                                                       








                                                             
 




                                                                               



                        
 


                                                   




                                                       

                                         





                                                                               



























































                                                                             





                                                                             

                                    
                                         

                         

                                                    
                                             
                                                              
                                               
 

                                                          














                                                     
                                                 
                                                        

                                                   














                                                               
                                             

                                                           
                                               








                                                                
 


                                                    
 
                        
 
/*
 * empathy-gst-audio-sink.c - Source for EmpathyGstAudioSink
 * Copyright (C) 2008 Collabora Ltd.
 * @author Sjoerd Simons <sjoerd.simons@collabora.co.uk>
 *
 * 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 <stdio.h>
#include <stdlib.h>

#include <gst/audio/audio.h>
#include <gst/farsight/fs-element-added-notifier.h>

#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);
}