]> git.0d.be Git - empathy.git/blob - src/empathy-audio-sink.c
Ensure our bin doesn't change state before being in the hash table
[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
96 #define EMPATHY_GST_AUDIO_SINK_GET_PRIVATE(o) \
97   (G_TYPE_INSTANCE_GET_PRIVATE ((o), EMPATHY_TYPE_GST_AUDIO_SINK, \
98   EmpathyGstAudioSinkPrivate))
99
100 static void
101 empathy_audio_sink_element_added_cb (FsElementAddedNotifier *notifier,
102   GstBin *bin, GstElement *element, EmpathyGstAudioSink *self)
103 {
104   EmpathyGstAudioSinkPrivate *priv = EMPATHY_GST_AUDIO_SINK_GET_PRIVATE (self);
105
106   if (g_object_class_find_property (G_OBJECT_GET_CLASS (element), "volume"))
107     {
108       /* An element was added with a volume property, lets find its subbin and
109        * update the volume in it */
110       GHashTableIter iter;
111       AudioBin *audio_bin = NULL;
112       gpointer value;
113
114       g_hash_table_iter_init (&iter, priv->audio_bins);
115
116       while (g_hash_table_iter_next (&iter, NULL, &value))
117         {
118           AudioBin *b = value;
119
120           if (gst_object_has_ancestor (GST_OBJECT (element),
121               GST_OBJECT (b->bin)))
122             {
123               audio_bin = b;
124               break;
125             }
126         }
127
128       if (audio_bin == NULL)
129         {
130           g_warning ("Element added that doesn't belong to us ?");
131           return;
132         }
133
134       /* Set the old volume to 1 and the new volume to the volume */
135       g_object_set (audio_bin->volume, "volume", 1.0, NULL);
136       gst_object_unref (audio_bin->volume);
137
138       audio_bin->volume = gst_object_ref (element);
139       g_object_set (audio_bin->volume, "volume", self->priv->volume, NULL);
140     }
141 }
142
143 static void
144 empathy_audio_sink_init (EmpathyGstAudioSink *self)
145 {
146   EmpathyGstAudioSinkPrivate *priv;
147
148   priv = self->priv = EMPATHY_GST_AUDIO_SINK_GET_PRIVATE (self);
149
150   priv->volume = 1.0;
151
152   priv->audio_bins = g_hash_table_new_full (g_direct_hash, g_direct_equal,
153     NULL, (GDestroyNotify) audio_bin_free);
154
155   priv->notifier = fs_element_added_notifier_new ();
156   g_signal_connect (priv->notifier, "element-added",
157     G_CALLBACK (empathy_audio_sink_element_added_cb), self);
158 }
159
160 static void empathy_audio_sink_dispose (GObject *object);
161 static void empathy_audio_sink_finalize (GObject *object);
162
163 static GstPad * empathy_audio_sink_request_new_pad (GstElement *self,
164   GstPadTemplate *templ,
165   const gchar* name);
166
167 static void empathy_audio_sink_release_pad (GstElement *self,
168   GstPad *pad);
169
170 static void
171 empathy_audio_sink_set_property (GObject *object,
172   guint property_id, const GValue *value, GParamSpec *pspec)
173 {
174   switch (property_id)
175     {
176       case PROP_VOLUME:
177         empathy_audio_sink_set_volume (EMPATHY_GST_AUDIO_SINK (object),
178           g_value_get_double (value));
179         break;
180       default:
181         G_OBJECT_WARN_INVALID_PROPERTY_ID (object, property_id, pspec);
182     }
183 }
184
185 static void
186 empathy_audio_sink_get_property (GObject *object,
187   guint property_id, GValue *value, GParamSpec *pspec)
188 {
189   switch (property_id)
190     {
191       case PROP_VOLUME:
192         g_value_set_double (value,
193           empathy_audio_sink_get_volume (EMPATHY_GST_AUDIO_SINK (object)));
194         break;
195       default:
196         G_OBJECT_WARN_INVALID_PROPERTY_ID (object, property_id, pspec);
197     }
198 }
199
200 static void
201 empathy_audio_sink_class_init (EmpathyGstAudioSinkClass
202   *empathy_audio_sink_class)
203 {
204   GObjectClass *object_class = G_OBJECT_CLASS (empathy_audio_sink_class);
205   GstElementClass *element_class =
206     GST_ELEMENT_CLASS (empathy_audio_sink_class);
207   GParamSpec *param_spec;
208
209   gst_element_class_add_pad_template (element_class,
210     gst_static_pad_template_get (&sink_template));
211
212   g_type_class_add_private (empathy_audio_sink_class,
213     sizeof (EmpathyGstAudioSinkPrivate));
214
215   object_class->dispose = empathy_audio_sink_dispose;
216   object_class->finalize = empathy_audio_sink_finalize;
217
218   object_class->set_property = empathy_audio_sink_set_property;
219   object_class->get_property = empathy_audio_sink_get_property;
220
221   element_class->request_new_pad = empathy_audio_sink_request_new_pad;
222   element_class->release_pad = empathy_audio_sink_release_pad;
223
224   param_spec = g_param_spec_double ("volume", "Volume", "volume control",
225     0.0, 5.0, 1.0,
226     G_PARAM_READWRITE | G_PARAM_STATIC_STRINGS);
227   g_object_class_install_property (object_class, PROP_VOLUME, param_spec);
228 }
229
230 void
231 empathy_audio_sink_dispose (GObject *object)
232 {
233   EmpathyGstAudioSink *self = EMPATHY_GST_AUDIO_SINK (object);
234   EmpathyGstAudioSinkPrivate *priv = EMPATHY_GST_AUDIO_SINK_GET_PRIVATE (self);
235
236   if (priv->dispose_has_run)
237     return;
238
239   priv->dispose_has_run = TRUE;
240
241   if (priv->notifier != NULL)
242     g_object_unref (priv->notifier);
243   priv->notifier = NULL;
244
245   if (priv->audio_bins != NULL)
246     g_hash_table_unref (priv->audio_bins);
247   priv->audio_bins = NULL;
248
249   if (G_OBJECT_CLASS (empathy_audio_sink_parent_class)->dispose)
250     G_OBJECT_CLASS (empathy_audio_sink_parent_class)->dispose (object);
251 }
252
253 void
254 empathy_audio_sink_finalize (GObject *object)
255 {
256   //EmpathyGstAudioSink *self = EMPATHY_GST_AUDIO_SINK (object);
257   //EmpathyGstAudioSinkPrivate *priv =
258   //  EMPATHY_GST_AUDIO_SINK_GET_PRIVATE (self);
259
260   /* free any data held directly by the object here */
261
262   G_OBJECT_CLASS (empathy_audio_sink_parent_class)->finalize (object);
263 }
264
265 GstElement *
266 empathy_audio_sink_new (void)
267 {
268   static gboolean registered = FALSE;
269
270   if (!registered) {
271     if (!gst_element_register (NULL, "empathyaudiosink",
272             GST_RANK_NONE, EMPATHY_TYPE_GST_AUDIO_SINK))
273       return NULL;
274     registered = TRUE;
275   }
276   return gst_element_factory_make ("empathyaudiosink", NULL);
277 }
278
279 void
280 empathy_audio_sink_set_volume (EmpathyGstAudioSink *sink, gdouble volume)
281 {
282   EmpathyGstAudioSinkPrivate *priv = EMPATHY_GST_AUDIO_SINK_GET_PRIVATE (sink);
283   GHashTableIter iter;
284   gpointer value;
285
286   priv->volume = volume;
287   g_hash_table_iter_init (&iter, priv->audio_bins);
288
289   while (g_hash_table_iter_next (&iter, NULL, &value))
290     {
291       AudioBin *b = value;
292       g_object_set (b->volume, "volume", volume, NULL);
293     }
294 }
295
296 gdouble
297 empathy_audio_sink_get_volume (EmpathyGstAudioSink *sink)
298 {
299   EmpathyGstAudioSinkPrivate *priv = EMPATHY_GST_AUDIO_SINK_GET_PRIVATE (sink);
300   return priv->volume;
301 }
302
303 static GstPad *
304 empathy_audio_sink_request_new_pad (GstElement *element,
305   GstPadTemplate *templ,
306   const gchar* name)
307 {
308   EmpathyGstAudioSink *self = EMPATHY_GST_AUDIO_SINK (element);
309   GstElement *bin, *sink, *volume, *resample, *audioconvert0, *audioconvert1;
310   GstPad *pad = NULL;
311   GstPad *subpad, *filterpad;
312   AudioBin *audiobin;
313
314   bin = gst_bin_new (NULL);
315
316   audioconvert0 = gst_element_factory_make ("audioconvert", NULL);
317   if (audioconvert0 == NULL)
318     goto error;
319
320   gst_bin_add (GST_BIN (bin), audioconvert0);
321
322   resample = gst_element_factory_make ("audioresample", NULL);
323   if (resample == NULL)
324     goto error;
325
326   gst_bin_add (GST_BIN (bin), resample);
327
328   audioconvert1 = gst_element_factory_make ("audioconvert", NULL);
329   if (audioconvert1 == NULL)
330     goto error;
331
332   gst_bin_add (GST_BIN (bin), audioconvert1);
333
334   volume = gst_element_factory_make ("volume", NULL);
335   if (volume == NULL)
336     goto error;
337
338   gst_bin_add (GST_BIN (bin), volume);
339
340   sink = gst_element_factory_make ("gconfaudiosink", NULL);
341   if (sink == NULL)
342     goto error;
343
344   gst_bin_add (GST_BIN (bin), sink);
345   fs_element_added_notifier_add (self->priv->notifier, GST_BIN (sink));
346
347   if (!gst_element_link_many (audioconvert0, resample, audioconvert1,
348       volume, sink, NULL))
349     goto error;
350
351   filterpad = gst_element_get_static_pad (audioconvert0, "sink");
352
353   if (filterpad == NULL)
354     goto error;
355
356   subpad = gst_ghost_pad_new ("sink", filterpad);
357   if (!gst_element_add_pad (GST_ELEMENT (bin), subpad))
358     goto error;
359
360
361   /* Ensure that state changes only happen _after_ the element has been added
362    * to the hash table. But add it to the bin first so we can create our
363    * ghostpad (if we create the ghostpad before adding it to the bin it will
364    * get unlinked) */
365   gst_element_set_locked_state (GST_ELEMENT (bin), TRUE);
366   gst_bin_add (GST_BIN (self), bin);
367
368   pad = gst_ghost_pad_new (name, subpad);
369   g_assert (pad != NULL);
370
371   audiobin = audio_bin_new (pad, bin, volume, sink);
372
373   g_hash_table_insert (self->priv->audio_bins, pad, audiobin);
374
375   gst_element_set_locked_state (GST_ELEMENT (bin), FALSE);
376
377   if (!gst_element_sync_state_with_parent (bin))
378     goto error;
379
380   if (!gst_pad_set_active (pad, TRUE))
381     goto error;
382
383   if (!gst_element_add_pad (GST_ELEMENT (self), pad))
384     goto error;
385
386
387   return pad;
388
389 error:
390   if (pad != NULL)
391     {
392       g_hash_table_remove (self->priv->audio_bins, pad);
393       gst_object_unref (pad);
394     }
395
396   gst_object_unref (bin);
397   g_warning ("Failed to create output subpipeline");
398   return NULL;
399 }
400
401 static void
402 empathy_audio_sink_release_pad (GstElement *element,
403   GstPad *pad)
404 {
405   EmpathyGstAudioSink *self = EMPATHY_GST_AUDIO_SINK (element);
406   AudioBin *abin;
407
408   abin = g_hash_table_lookup (self->priv->audio_bins, pad);
409   g_hash_table_steal (self->priv->audio_bins, pad);
410
411   if (abin == NULL)
412     {
413       g_warning ("Releasing a pad that doesn't belong to us ?");
414       return;
415     }
416
417   gst_pad_set_active (pad, FALSE);
418   gst_element_remove_pad (element, pad);
419
420   gst_element_set_locked_state (abin->bin, TRUE);
421   gst_element_set_state (abin->bin, GST_STATE_NULL);
422   gst_bin_remove (GST_BIN (self), abin->bin);
423
424   audio_bin_free (abin);
425 }