]> git.0d.be Git - empathy.git/blob - src/empathy-audio-sink.c
Merge branch 'change-audio'
[empathy.git] / src / empathy-audio-sink.c
1 /*
2  * empathy-gst-audio-sink.c - Source for EmpathyGstAudioSink
3  * Copyright (C) 2008 Collabora Ltd.
4  * @author Sjoerd Simons <sjoerd.simons@collabora.co.uk>
5  *
6  * This library is free software; you can redistribute it and/or
7  * modify it under the terms of the GNU Lesser General Public
8  * License as published by the Free Software Foundation; either
9  * version 2.1 of the License, or (at your option) any later version.
10  *
11  * This library is distributed in the hope that it will be useful,
12  * but WITHOUT ANY WARRANTY; without even the implied warranty of
13  * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
14  * Lesser General Public License for more details.
15  *
16  * You should have received a copy of the GNU Lesser General Public
17  * License along with this library; if not, write to the Free Software
18  * Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA  02110-1301  USA
19  */
20
21
22 #include <stdio.h>
23 #include <stdlib.h>
24
25 #include <gst/audio/audio.h>
26 #include <gst/farsight/fs-element-added-notifier.h>
27
28 #include "empathy-audio-sink.h"
29
30
31 G_DEFINE_TYPE(EmpathyGstAudioSink, empathy_audio_sink, GST_TYPE_BIN)
32
33 /* signal enum */
34 #if 0
35 enum
36 {
37     LAST_SIGNAL
38 };
39
40 static guint signals[LAST_SIGNAL] = {0};
41 #endif
42 typedef struct {
43   GstPad *pad;
44   GstElement *bin;
45   GstElement *volume;
46   GstElement *sink;
47 } AudioBin;
48
49 static AudioBin *
50 audio_bin_new (GstPad *pad,
51     GstElement *bin,
52     GstElement *volume,
53     GstElement *sink)
54 {
55   AudioBin *result = g_slice_new0 (AudioBin);
56
57   result->pad = pad;
58   result->bin = bin;
59   result->volume = gst_object_ref (volume);
60   result->sink = sink;
61
62   return result;
63 }
64
65 static void
66 audio_bin_free (AudioBin *bin)
67 {
68   gst_object_unref (bin->volume);
69   g_slice_free (AudioBin, bin);
70 }
71
72
73 static GstStaticPadTemplate sink_template = GST_STATIC_PAD_TEMPLATE(
74     "sink%d",
75     GST_PAD_SINK,
76     GST_PAD_REQUEST,
77     GST_STATIC_CAPS ( GST_AUDIO_INT_PAD_TEMPLATE_CAPS " ; "
78         GST_AUDIO_FLOAT_PAD_TEMPLATE_CAPS)
79 );
80
81 enum {
82   PROP_VOLUME = 1,
83 };
84
85 struct _EmpathyGstAudioSinkPrivate
86 {
87   gboolean dispose_has_run;
88   FsElementAddedNotifier *notifier;
89
90   gdouble volume;
91
92   /* Pad -> *owned* subbin hash */
93   GHashTable *audio_bins;
94
95   /* Mutex to hold while change the hash table */
96   GMutex *audio_bins_lock;
97 };
98
99 #define EMPATHY_GST_AUDIO_SINK_GET_PRIVATE(o) \
100   (G_TYPE_INSTANCE_GET_PRIVATE ((o), EMPATHY_TYPE_GST_AUDIO_SINK, \
101   EmpathyGstAudioSinkPrivate))
102
103 static void
104 empathy_audio_sink_element_added_cb (FsElementAddedNotifier *notifier,
105   GstBin *bin, GstElement *element, EmpathyGstAudioSink *self)
106 {
107   EmpathyGstAudioSinkPrivate *priv = EMPATHY_GST_AUDIO_SINK_GET_PRIVATE (self);
108
109   if (g_object_class_find_property (G_OBJECT_GET_CLASS (element), "volume"))
110     {
111       /* An element was added with a volume property, lets find its subbin and
112        * update the volume in it */
113       GHashTableIter iter;
114       AudioBin *audio_bin = NULL;
115       gpointer value;
116
117       g_mutex_lock (self->priv->audio_bins_lock);
118       g_hash_table_iter_init (&iter, priv->audio_bins);
119
120       while (g_hash_table_iter_next (&iter, NULL, &value))
121         {
122           AudioBin *b = value;
123
124           if (gst_object_has_ancestor (GST_OBJECT (element),
125               GST_OBJECT (b->bin)))
126             {
127               audio_bin = b;
128               break;
129             }
130         }
131
132       if (audio_bin == NULL)
133         {
134           g_warning ("Element added that doesn't belong to us ?");
135           return;
136         }
137
138       /* Set the old volume to 1 and the new volume to the volume */
139       g_object_set (audio_bin->volume, "volume", 1.0, NULL);
140       gst_object_unref (audio_bin->volume);
141
142       audio_bin->volume = gst_object_ref (element);
143       g_object_set (audio_bin->volume, "volume", self->priv->volume, NULL);
144       g_mutex_unlock (self->priv->audio_bins_lock);
145     }
146 }
147
148 static void
149 empathy_audio_sink_init (EmpathyGstAudioSink *self)
150 {
151   EmpathyGstAudioSinkPrivate *priv;
152
153   priv = self->priv = EMPATHY_GST_AUDIO_SINK_GET_PRIVATE (self);
154
155   priv->volume = 1.0;
156
157   priv->audio_bins = g_hash_table_new_full (g_direct_hash, g_direct_equal,
158     NULL, (GDestroyNotify) audio_bin_free);
159
160   priv->audio_bins_lock = g_mutex_new ();
161
162   priv->notifier = fs_element_added_notifier_new ();
163   g_signal_connect (priv->notifier, "element-added",
164     G_CALLBACK (empathy_audio_sink_element_added_cb), self);
165 }
166
167 static void empathy_audio_sink_dispose (GObject *object);
168 static void empathy_audio_sink_finalize (GObject *object);
169
170 static GstPad * empathy_audio_sink_request_new_pad (GstElement *self,
171   GstPadTemplate *templ,
172   const gchar* name);
173
174 static void empathy_audio_sink_release_pad (GstElement *self,
175   GstPad *pad);
176
177 static void
178 empathy_audio_sink_set_property (GObject *object,
179   guint property_id, const GValue *value, GParamSpec *pspec)
180 {
181   switch (property_id)
182     {
183       case PROP_VOLUME:
184         empathy_audio_sink_set_volume (EMPATHY_GST_AUDIO_SINK (object),
185           g_value_get_double (value));
186         break;
187       default:
188         G_OBJECT_WARN_INVALID_PROPERTY_ID (object, property_id, pspec);
189     }
190 }
191
192 static void
193 empathy_audio_sink_get_property (GObject *object,
194   guint property_id, GValue *value, GParamSpec *pspec)
195 {
196   switch (property_id)
197     {
198       case PROP_VOLUME:
199         g_value_set_double (value,
200           empathy_audio_sink_get_volume (EMPATHY_GST_AUDIO_SINK (object)));
201         break;
202       default:
203         G_OBJECT_WARN_INVALID_PROPERTY_ID (object, property_id, pspec);
204     }
205 }
206
207 static void
208 empathy_audio_sink_class_init (EmpathyGstAudioSinkClass
209   *empathy_audio_sink_class)
210 {
211   GObjectClass *object_class = G_OBJECT_CLASS (empathy_audio_sink_class);
212   GstElementClass *element_class =
213     GST_ELEMENT_CLASS (empathy_audio_sink_class);
214   GParamSpec *param_spec;
215
216   gst_element_class_add_pad_template (element_class,
217     gst_static_pad_template_get (&sink_template));
218
219   g_type_class_add_private (empathy_audio_sink_class,
220     sizeof (EmpathyGstAudioSinkPrivate));
221
222   object_class->dispose = empathy_audio_sink_dispose;
223   object_class->finalize = empathy_audio_sink_finalize;
224
225   object_class->set_property = empathy_audio_sink_set_property;
226   object_class->get_property = empathy_audio_sink_get_property;
227
228   element_class->request_new_pad = empathy_audio_sink_request_new_pad;
229   element_class->release_pad = empathy_audio_sink_release_pad;
230
231   param_spec = g_param_spec_double ("volume", "Volume", "volume control",
232     0.0, 5.0, 1.0,
233     G_PARAM_READWRITE | G_PARAM_STATIC_STRINGS);
234   g_object_class_install_property (object_class, PROP_VOLUME, param_spec);
235 }
236
237 void
238 empathy_audio_sink_dispose (GObject *object)
239 {
240   EmpathyGstAudioSink *self = EMPATHY_GST_AUDIO_SINK (object);
241   EmpathyGstAudioSinkPrivate *priv = EMPATHY_GST_AUDIO_SINK_GET_PRIVATE (self);
242
243   if (priv->dispose_has_run)
244     return;
245
246   priv->dispose_has_run = TRUE;
247
248   if (priv->notifier != NULL)
249     g_object_unref (priv->notifier);
250   priv->notifier = NULL;
251
252   if (priv->audio_bins != NULL)
253     g_hash_table_unref (priv->audio_bins);
254   priv->audio_bins = NULL;
255
256   if (priv->audio_bins_lock != NULL)
257     g_mutex_free (priv->audio_bins_lock);
258   priv->audio_bins_lock = NULL;
259
260   if (G_OBJECT_CLASS (empathy_audio_sink_parent_class)->dispose)
261     G_OBJECT_CLASS (empathy_audio_sink_parent_class)->dispose (object);
262 }
263
264 void
265 empathy_audio_sink_finalize (GObject *object)
266 {
267   //EmpathyGstAudioSink *self = EMPATHY_GST_AUDIO_SINK (object);
268   //EmpathyGstAudioSinkPrivate *priv =
269   //  EMPATHY_GST_AUDIO_SINK_GET_PRIVATE (self);
270
271   /* free any data held directly by the object here */
272
273   G_OBJECT_CLASS (empathy_audio_sink_parent_class)->finalize (object);
274 }
275
276 GstElement *
277 empathy_audio_sink_new (void)
278 {
279   static gboolean registered = FALSE;
280
281   if (!registered) {
282     if (!gst_element_register (NULL, "empathyaudiosink",
283             GST_RANK_NONE, EMPATHY_TYPE_GST_AUDIO_SINK))
284       return NULL;
285     registered = TRUE;
286   }
287   return gst_element_factory_make ("empathyaudiosink", NULL);
288 }
289
290 void
291 empathy_audio_sink_set_volume (EmpathyGstAudioSink *sink, gdouble volume)
292 {
293   EmpathyGstAudioSinkPrivate *priv = EMPATHY_GST_AUDIO_SINK_GET_PRIVATE (sink);
294   GHashTableIter iter;
295   gpointer value;
296
297   priv->volume = volume;
298
299   g_mutex_lock (priv->audio_bins_lock);
300
301   g_hash_table_iter_init (&iter, priv->audio_bins);
302   while (g_hash_table_iter_next (&iter, NULL, &value))
303     {
304       AudioBin *b = value;
305       g_object_set (b->volume, "volume", volume, NULL);
306     }
307
308   g_mutex_unlock (priv->audio_bins_lock);
309 }
310
311 gdouble
312 empathy_audio_sink_get_volume (EmpathyGstAudioSink *sink)
313 {
314   EmpathyGstAudioSinkPrivate *priv = EMPATHY_GST_AUDIO_SINK_GET_PRIVATE (sink);
315   return priv->volume;
316 }
317
318 static GstPad *
319 empathy_audio_sink_request_new_pad (GstElement *element,
320   GstPadTemplate *templ,
321   const gchar* name)
322 {
323   EmpathyGstAudioSink *self = EMPATHY_GST_AUDIO_SINK (element);
324   GstElement *bin, *sink, *volume, *resample, *audioconvert0, *audioconvert1;
325   GstPad *pad = NULL;
326   GstPad *subpad, *filterpad;
327   AudioBin *audiobin;
328
329   bin = gst_bin_new (NULL);
330
331   audioconvert0 = gst_element_factory_make ("audioconvert", NULL);
332   if (audioconvert0 == NULL)
333     goto error;
334
335   gst_bin_add (GST_BIN (bin), audioconvert0);
336
337   resample = gst_element_factory_make ("audioresample", NULL);
338   if (resample == NULL)
339     goto error;
340
341   gst_bin_add (GST_BIN (bin), resample);
342
343   audioconvert1 = gst_element_factory_make ("audioconvert", NULL);
344   if (audioconvert1 == NULL)
345     goto error;
346
347   gst_bin_add (GST_BIN (bin), audioconvert1);
348
349   volume = gst_element_factory_make ("volume", NULL);
350   if (volume == NULL)
351     goto error;
352
353   gst_bin_add (GST_BIN (bin), volume);
354
355   sink = gst_element_factory_make ("gconfaudiosink", NULL);
356   if (sink == NULL)
357     goto error;
358
359   gst_bin_add (GST_BIN (bin), sink);
360   fs_element_added_notifier_add (self->priv->notifier, GST_BIN (sink));
361
362   if (!gst_element_link_many (audioconvert0, resample, audioconvert1,
363       volume, sink, NULL))
364     goto error;
365
366   filterpad = gst_element_get_static_pad (audioconvert0, "sink");
367
368   if (filterpad == NULL)
369     goto error;
370
371   subpad = gst_ghost_pad_new ("sink", filterpad);
372   if (!gst_element_add_pad (GST_ELEMENT (bin), subpad))
373     goto error;
374
375
376   /* Ensure that state changes only happen _after_ the element has been added
377    * to the hash table. But add it to the bin first so we can create our
378    * ghostpad (if we create the ghostpad before adding it to the bin it will
379    * get unlinked) */
380   gst_element_set_locked_state (GST_ELEMENT (bin), TRUE);
381   gst_bin_add (GST_BIN (self), bin);
382
383   pad = gst_ghost_pad_new (name, subpad);
384   g_assert (pad != NULL);
385
386   audiobin = audio_bin_new (pad, bin, volume, sink);
387
388   g_mutex_lock (self->priv->audio_bins_lock);
389   g_hash_table_insert (self->priv->audio_bins, pad, audiobin);
390   g_mutex_unlock (self->priv->audio_bins_lock);
391
392   gst_element_set_locked_state (GST_ELEMENT (bin), FALSE);
393
394   if (!gst_element_sync_state_with_parent (bin))
395     goto error;
396
397   if (!gst_pad_set_active (pad, TRUE))
398     goto error;
399
400   if (!gst_element_add_pad (GST_ELEMENT (self), pad))
401     goto error;
402
403
404   return pad;
405
406 error:
407   if (pad != NULL)
408     {
409       g_mutex_lock (self->priv->audio_bins_lock);
410       g_hash_table_remove (self->priv->audio_bins, pad);
411       g_mutex_unlock (self->priv->audio_bins_lock);
412
413       gst_object_unref (pad);
414     }
415
416   gst_object_unref (bin);
417   g_warning ("Failed to create output subpipeline");
418   return NULL;
419 }
420
421 static void
422 empathy_audio_sink_release_pad (GstElement *element,
423   GstPad *pad)
424 {
425   EmpathyGstAudioSink *self = EMPATHY_GST_AUDIO_SINK (element);
426   AudioBin *abin;
427
428   g_mutex_lock (self->priv->audio_bins_lock);
429   abin = g_hash_table_lookup (self->priv->audio_bins, pad);
430   g_hash_table_steal (self->priv->audio_bins, pad);
431   g_mutex_unlock (self->priv->audio_bins_lock);
432
433   if (abin == NULL)
434     {
435       g_warning ("Releasing a pad that doesn't belong to us ?");
436       return;
437     }
438
439   gst_pad_set_active (pad, FALSE);
440   gst_element_remove_pad (element, pad);
441
442   gst_element_set_locked_state (abin->bin, TRUE);
443   gst_element_set_state (abin->bin, GST_STATE_NULL);
444   gst_bin_remove (GST_BIN (self), abin->bin);
445
446   audio_bin_free (abin);
447 }