]> git.0d.be Git - empathy.git/blob - src/empathy-audio-sink.c
Be more compatible with Facebook emoticon codes
[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 #include "config.h"
22 #include "empathy-audio-sink.h"
23
24 #include <gst/audio/streamvolume.h>
25
26 #include "empathy-audio-utils.h"
27
28 #define DEBUG_FLAG EMPATHY_DEBUG_VOIP
29 #include "empathy-debug.h"
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
43 static GstStaticPadTemplate sink_template = GST_STATIC_PAD_TEMPLATE(
44     "sink%d",
45     GST_PAD_SINK,
46     GST_PAD_REQUEST,
47     GST_STATIC_CAPS ( "audio/x-raw" )
48 );
49
50 enum {
51   PROP_VOLUME = 1,
52 };
53
54 struct _EmpathyGstAudioSinkPrivate
55 {
56   GstElement *sink;
57   gboolean echo_cancel;
58   gdouble volume;
59   gint volume_idle_id;
60   GMutex volume_mutex;
61 };
62
63 #define EMPATHY_GST_AUDIO_SINK_GET_PRIVATE(o) \
64   (G_TYPE_INSTANCE_GET_PRIVATE ((o), EMPATHY_TYPE_GST_AUDIO_SINK, \
65   EmpathyGstAudioSinkPrivate))
66
67 static void
68 empathy_audio_sink_init (EmpathyGstAudioSink *self)
69 {
70   self->priv = EMPATHY_GST_AUDIO_SINK_GET_PRIVATE (self);
71   self->priv->echo_cancel = TRUE;
72   g_mutex_init (&self->priv->volume_mutex);
73 }
74
75 static GstPad * empathy_audio_sink_request_new_pad (GstElement *self,
76   GstPadTemplate *templ,
77   const gchar* name,
78   const GstCaps *caps);
79
80 static void empathy_audio_sink_release_pad (GstElement *self,
81   GstPad *pad);
82
83 static void
84 empathy_audio_sink_set_property (GObject *object,
85   guint property_id, const GValue *value, GParamSpec *pspec)
86 {
87   EmpathyGstAudioSink *self = EMPATHY_GST_AUDIO_SINK (object);
88   switch (property_id)
89     {
90       case PROP_VOLUME:
91         g_mutex_lock (&self->priv->volume_mutex);
92         self->priv->volume = g_value_get_double (value);
93         g_mutex_unlock (&self->priv->volume_mutex);
94         break;
95       default:
96         G_OBJECT_WARN_INVALID_PROPERTY_ID (object, property_id, pspec);
97     }
98 }
99
100 static void
101 empathy_audio_sink_get_property (GObject *object,
102   guint property_id, GValue *value, GParamSpec *pspec)
103 {
104   EmpathyGstAudioSink *self = EMPATHY_GST_AUDIO_SINK (object);
105   switch (property_id)
106     {
107       case PROP_VOLUME:
108         g_value_set_double (value, self->priv->volume);
109         break;
110       default:
111         G_OBJECT_WARN_INVALID_PROPERTY_ID (object, property_id, pspec);
112     }
113 }
114
115 static void
116 empathy_audio_sink_dispose (GObject *object)
117 {
118   EmpathyGstAudioSink *self = EMPATHY_GST_AUDIO_SINK (object);
119   EmpathyGstAudioSinkPrivate *priv = self->priv;
120
121   if (priv->volume_idle_id != 0)
122     g_source_remove (priv->volume_idle_id);
123   priv->volume_idle_id = 0;
124
125   g_mutex_clear (&self->priv->volume_mutex);
126
127   /* release any references held by the object here */
128   if (G_OBJECT_CLASS (empathy_audio_sink_parent_class)->dispose)
129     G_OBJECT_CLASS (empathy_audio_sink_parent_class)->dispose (object);
130 }
131
132 static void
133 empathy_audio_sink_class_init (EmpathyGstAudioSinkClass
134   *empathy_audio_sink_class)
135 {
136   GObjectClass *object_class = G_OBJECT_CLASS (empathy_audio_sink_class);
137   GstElementClass *element_class =
138     GST_ELEMENT_CLASS (empathy_audio_sink_class);
139   GParamSpec *param_spec;
140
141   gst_element_class_add_pad_template (element_class,
142     gst_static_pad_template_get (&sink_template));
143
144   g_type_class_add_private (empathy_audio_sink_class,
145     sizeof (EmpathyGstAudioSinkPrivate));
146
147   object_class->set_property = empathy_audio_sink_set_property;
148   object_class->get_property = empathy_audio_sink_get_property;
149   object_class->dispose = empathy_audio_sink_dispose;
150
151   element_class->request_new_pad = empathy_audio_sink_request_new_pad;
152   element_class->release_pad = empathy_audio_sink_release_pad;
153
154   param_spec = g_param_spec_double ("volume", "Volume", "volume control",
155     0.0, 5.0, 1.0,
156     G_PARAM_READWRITE | G_PARAM_STATIC_STRINGS);
157   g_object_class_install_property (object_class, PROP_VOLUME, param_spec);
158 }
159
160 GstElement *
161 empathy_audio_sink_new (void)
162 {
163   static gboolean registered = FALSE;
164
165   if (!registered) {
166     if (!gst_element_register (NULL, "empathyaudiosink",
167             GST_RANK_NONE, EMPATHY_TYPE_GST_AUDIO_SINK))
168       return NULL;
169     registered = TRUE;
170   }
171   return gst_element_factory_make ("empathyaudiosink", NULL);
172 }
173
174 void
175 empathy_audio_sink_set_volume (EmpathyGstAudioSink *sink, gdouble volume)
176 {
177   g_object_set (sink, "volume", volume, NULL);
178 }
179
180 gdouble
181 empathy_audio_sink_get_volume (EmpathyGstAudioSink *sink)
182 {
183   EmpathyGstAudioSinkPrivate *priv = EMPATHY_GST_AUDIO_SINK_GET_PRIVATE (sink);
184
185   return priv->volume;
186 }
187
188 static GstElement *
189 create_sink (EmpathyGstAudioSink *self)
190 {
191   GstElement *sink;
192   const gchar *description;
193
194   description = g_getenv ("EMPATHY_AUDIO_SINK");
195
196   if (description != NULL)
197     {
198       GError *error = NULL;
199
200       sink = gst_parse_bin_from_description (description, TRUE, &error);
201       if (sink == NULL)
202         {
203           DEBUG ("Failed to create bin %s: %s", description, error->message);
204           g_error_free (error);
205         }
206
207       return sink;
208     }
209
210   /* Use pulsesink as default */
211   sink = gst_element_factory_make ("pulsesink", NULL);
212   if (sink == NULL)
213     return NULL;
214
215   empathy_audio_set_stream_properties (sink, self->priv->echo_cancel);
216
217   /* Set latency (buffering on the PulseAudio side) of 40ms and transfer data
218    * in 10ms chunks */
219   g_object_set (sink,
220       "buffer-time", (gint64) 40000,
221       "latency-time", (gint64) 10000,
222       NULL);
223
224   return sink;
225 }
226
227 static gboolean
228 empathy_audio_sink_volume_idle_updated (gpointer user_data)
229 {
230   EmpathyGstAudioSink *self = EMPATHY_GST_AUDIO_SINK (user_data);
231
232   g_mutex_lock (&self->priv->volume_mutex);
233   self->priv->volume_idle_id = 0;
234   g_mutex_unlock (&self->priv->volume_mutex);
235
236   g_object_notify (G_OBJECT (self), "volume");
237
238   return FALSE;
239 }
240
241 static void
242 empathy_audio_sink_volume_updated (GObject *object,
243   GParamSpec *pspec,
244   gpointer user_data)
245 {
246   EmpathyGstAudioSink *self = EMPATHY_GST_AUDIO_SINK (user_data);
247   gdouble volume;
248
249   g_mutex_lock (&self->priv->volume_mutex);
250
251   g_object_get (object, "volume", &volume, NULL);
252   if (self->priv->volume == volume)
253     goto out;
254
255   self->priv->volume = volume;
256   if (self->priv->volume_idle_id == 0)
257     self->priv->volume_idle_id = g_idle_add (
258       empathy_audio_sink_volume_idle_updated, self);
259
260 out:
261   g_mutex_unlock (&self->priv->volume_mutex);
262 }
263
264 static gboolean
265 empathy_audio_sink_volume_idle_setup (gpointer user_data)
266 {
267   EmpathyGstAudioSink *self = EMPATHY_GST_AUDIO_SINK (user_data);
268   gdouble volume;
269
270   g_mutex_lock (&self->priv->volume_mutex);
271   self->priv->volume_idle_id = 0;
272   g_mutex_unlock (&self->priv->volume_mutex);
273
274   /* We can't do a bidirection bind as the ::notify comes from another
275    * thread, for other bits of empathy it's most simpler if it comes from
276    * the main thread */
277   g_object_bind_property (self, "volume", self->priv->sink, "volume",
278     G_BINDING_DEFAULT);
279
280   /* sync and callback for bouncing */
281   g_object_get (self->priv->sink, "volume", &volume, NULL);
282   g_object_set (self, "volume", volume, NULL);
283   g_signal_connect (self->priv->sink, "notify::volume",
284     G_CALLBACK (empathy_audio_sink_volume_updated), self);
285
286   return FALSE;
287 }
288
289 static GstPad *
290 empathy_audio_sink_request_new_pad (GstElement *element,
291   GstPadTemplate *templ,
292   const gchar* name,
293   const GstCaps *caps)
294 {
295   EmpathyGstAudioSink *self = EMPATHY_GST_AUDIO_SINK (element);
296   GstElement *bin, *resample, *audioconvert0, *audioconvert1;
297   GstPad *pad = NULL;
298   GstPad *subpad, *filterpad;
299
300   bin = gst_bin_new (NULL);
301
302   audioconvert0 = gst_element_factory_make ("audioconvert", NULL);
303   if (audioconvert0 == NULL)
304     goto error;
305
306   gst_bin_add (GST_BIN (bin), audioconvert0);
307
308   resample = gst_element_factory_make ("audioresample", NULL);
309   if (resample == NULL)
310     goto error;
311
312   gst_bin_add (GST_BIN (bin), resample);
313
314   audioconvert1 = gst_element_factory_make ("audioconvert", NULL);
315   if (audioconvert1 == NULL)
316     goto error;
317
318   gst_bin_add (GST_BIN (bin), audioconvert1);
319
320   self->priv->sink = create_sink (self);
321   if (self->priv->sink == NULL)
322     goto error;
323
324   if (GST_IS_STREAM_VOLUME (self->priv->sink))
325     {
326       g_mutex_lock (&self->priv->volume_mutex);
327       if (self->priv->volume_idle_id == 0)
328         self->priv->volume_idle_id = g_idle_add (
329           empathy_audio_sink_volume_idle_setup, self);
330       g_mutex_unlock (&self->priv->volume_mutex);
331     }
332   else
333     {
334       gchar *n = gst_element_get_name (self->priv->sink);
335
336       DEBUG ("Element %s doesn't support volume", n);
337       g_free (n);
338     }
339
340   gst_bin_add (GST_BIN (bin), self->priv->sink);
341
342   if (!gst_element_link_many (audioconvert0, resample, audioconvert1,
343       self->priv->sink, NULL))
344     goto error;
345
346   filterpad = gst_element_get_static_pad (audioconvert0, "sink");
347
348   if (filterpad == NULL)
349     goto error;
350
351   subpad = gst_ghost_pad_new ("sink", filterpad);
352   gst_object_unref (filterpad);
353
354   if (!gst_element_add_pad (GST_ELEMENT (bin), subpad))
355     goto error;
356
357   gst_bin_add (GST_BIN (self), bin);
358
359   pad = gst_ghost_pad_new (name, subpad);
360   g_assert (pad != NULL);
361
362   if (!gst_element_sync_state_with_parent (bin))
363     goto error;
364
365   if (!gst_pad_set_active (pad, TRUE))
366     goto error;
367
368   if (!gst_element_add_pad (GST_ELEMENT (self), pad))
369     goto error;
370
371   return pad;
372
373 error:
374   if (pad != NULL)
375     {
376       gst_object_unref (pad);
377     }
378
379   gst_object_unref (bin);
380   g_warning ("Failed to create output subpipeline");
381   return NULL;
382 }
383
384 static void
385 empathy_audio_sink_release_pad (GstElement *element,
386   GstPad *pad)
387 {
388   gst_pad_set_active (pad, FALSE);
389   gst_element_remove_pad (element, pad);
390 }
391
392 void
393 empathy_audio_sink_set_echo_cancel (EmpathyGstAudioSink *sink,
394   gboolean echo_cancel)
395 {
396   DEBUG ("Sink echo cancellation setting: %s", echo_cancel ? "on" : "off");
397   sink->priv->echo_cancel = echo_cancel;
398   if (sink->priv->sink != NULL)
399     empathy_audio_set_stream_properties (sink->priv->sink,
400       sink->priv->echo_cancel);
401 }