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