]> git.0d.be Git - empathy.git/blob - src/empathy-audio-src.c
deff297d07d1b7dfb75df7362ee9ff2ea400b312
[empathy.git] / src / empathy-audio-src.c
1 /*
2  * empathy-gst-audio-src.c - Source for EmpathyGstAudioSrc
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/interfaces/streamvolume.h>
27
28 #include <libempathy/empathy-utils.h>
29 #include "empathy-audio-utils.h"
30
31 #include "empathy-audio-src.h"
32
33 #include "empathy-mic-monitor.h"
34
35 #define DEBUG_FLAG EMPATHY_DEBUG_VOIP
36 #include <libempathy/empathy-debug.h>
37
38 G_DEFINE_TYPE(EmpathyGstAudioSrc, empathy_audio_src, GST_TYPE_BIN)
39
40 enum {
41     PROP_VOLUME = 1,
42     PROP_MUTE,
43     PROP_MICROPHONE,
44 };
45
46 /* private structure */
47 struct _EmpathyGstAudioSrcPrivate
48 {
49   gboolean dispose_has_run;
50   GstElement *src;
51   GstElement *volume_element;
52
53   EmpathyMicMonitor *mic_monitor;
54
55   /* 0 if not known yet */
56   guint source_output_idx;
57   /* G_MAXUINT if not known yet */
58   guint source_idx;
59
60   gdouble volume;
61   gboolean mute;
62   gboolean have_stream_volume;
63
64   GMutex lock;
65   guint volume_idle_id;
66 };
67
68 #define EMPATHY_GST_AUDIO_SRC_GET_PRIVATE(o) \
69   (G_TYPE_INSTANCE_GET_PRIVATE ((o), EMPATHY_TYPE_GST_AUDIO_SRC, \
70   EmpathyGstAudioSrcPrivate))
71
72
73 static gboolean
74 empathy_audio_src_volume_changed (GObject *object,
75   GParamSpec *pspec,
76   gpointer user_data);
77
78 static void
79 empathy_audio_set_hw_mute (EmpathyGstAudioSrc *self, gboolean mute)
80 {
81   if (mute == self->priv->mute)
82     return;
83
84   if (self->priv->have_stream_volume)
85     g_object_set (self->priv->src, "mute", mute, NULL);
86
87   /* Belt and braces: If for some reason the underlying src doesn't mute
88    * correctly or doesn't update us when it unmutes correctly enforce it using
89    * our own volume element. Our UI can in no circumstances be made to think
90    * the input is muted while it's not */
91   g_object_set (self->priv->volume_element, "mute", mute, NULL);
92
93   self->priv->mute = mute;
94 }
95
96 static gboolean
97 empathy_audio_src_get_hw_mute (EmpathyGstAudioSrc *self)
98 {
99   gboolean result;
100   g_object_get (self->priv->src, "mute", &result, NULL);
101
102   return result;
103 }
104
105 static void
106 empathy_audio_src_set_hw_volume (EmpathyGstAudioSrc *self,
107     gdouble volume)
108 {
109   if (volume == self->priv->volume)
110     return;
111
112   if (self->priv->have_stream_volume)
113     g_object_set (self->priv->src, "volume", volume, NULL);
114   self->priv->volume = volume;
115 }
116
117 static gdouble
118 empathy_audio_src_get_hw_volume (EmpathyGstAudioSrc *self)
119 {
120   gdouble result;
121   g_object_get (self->priv->src, "volume", &result, NULL);
122
123   return result;
124 }
125
126
127 gboolean
128 empathy_audio_src_supports_changing_mic (EmpathyGstAudioSrc *self)
129 {
130   EmpathyGstAudioSrcPrivate *priv = EMPATHY_GST_AUDIO_SRC_GET_PRIVATE (self);
131   GObjectClass *object_class;
132
133   object_class = G_OBJECT_GET_CLASS (priv->src);
134
135   return (g_object_class_find_property (object_class,
136           "source-output-index") != NULL);
137 }
138
139 static guint
140 empathy_audio_src_get_mic_index (EmpathyGstAudioSrc *self)
141 {
142   EmpathyGstAudioSrcPrivate *priv = EMPATHY_GST_AUDIO_SRC_GET_PRIVATE (self);
143   guint audio_src_idx = PA_INVALID_INDEX;
144
145   if (empathy_audio_src_supports_changing_mic (self))
146     g_object_get (priv->src,
147       "source-output-index", &audio_src_idx,
148       NULL);
149
150   return audio_src_idx;
151 }
152
153 static void
154 empathy_audio_src_microphone_changed_cb (EmpathyMicMonitor *monitor,
155     guint source_output_idx,
156     guint source_idx,
157     gpointer user_data)
158 {
159   EmpathyGstAudioSrc *self = user_data;
160   EmpathyGstAudioSrcPrivate *priv = EMPATHY_GST_AUDIO_SRC_GET_PRIVATE (self);
161   guint audio_src_idx;
162
163   audio_src_idx = empathy_audio_src_get_mic_index (self);
164
165   if (source_output_idx == PA_INVALID_INDEX
166       || source_output_idx != audio_src_idx)
167     return;
168
169   if (priv->source_idx == source_idx)
170     return;
171
172   priv->source_idx = source_idx;
173   g_object_notify (G_OBJECT (self), "microphone");
174 }
175
176 static void
177 empathy_audio_src_get_current_mic_cb (GObject *source_object,
178     GAsyncResult *result,
179     gpointer user_data)
180 {
181   EmpathyMicMonitor *monitor = EMPATHY_MIC_MONITOR (source_object);
182   EmpathyGstAudioSrc *self = user_data;
183   EmpathyGstAudioSrcPrivate *priv = EMPATHY_GST_AUDIO_SRC_GET_PRIVATE (self);
184   guint source_idx;
185   GError *error = NULL;
186
187   source_idx = empathy_mic_monitor_get_current_mic_finish (monitor, result, &error);
188
189   if (error != NULL)
190     {
191       DEBUG ("Failed to get current mic: %s", error->message);
192       g_clear_error (&error);
193       return;
194     }
195
196   if (priv->source_idx == source_idx)
197     return;
198
199   priv->source_idx = source_idx;
200   g_object_notify (G_OBJECT (self), "microphone");
201 }
202
203 static void
204 empathy_audio_src_source_output_index_notify (GObject *object,
205     GParamSpec *pspec,
206     EmpathyGstAudioSrc *self)
207 {
208   EmpathyGstAudioSrcPrivate *priv = EMPATHY_GST_AUDIO_SRC_GET_PRIVATE (self);
209   guint source_output_idx;
210
211   source_output_idx = empathy_audio_src_get_mic_index (self);
212
213   if (source_output_idx == PA_INVALID_INDEX)
214     return;
215
216   if (priv->source_output_idx == source_output_idx)
217     return;
218
219   /* It's actually changed. */
220   priv->source_output_idx = source_output_idx;
221
222   empathy_mic_monitor_get_current_mic_async (priv->mic_monitor,
223       source_output_idx, empathy_audio_src_get_current_mic_cb, self);
224 }
225
226 static GstElement *
227 create_src (void)
228 {
229   GstElement *src;
230   const gchar *description;
231
232   description = g_getenv ("EMPATHY_AUDIO_SRC");
233
234   if (description != NULL)
235     {
236       GError *error = NULL;
237
238       src = gst_parse_bin_from_description (description, TRUE, &error);
239       if (src == NULL)
240         {
241           DEBUG ("Failed to create bin %s: %s", description, error->message);
242           g_error_free (error);
243         }
244
245       return src;
246     }
247
248   /* Use pulsesrc as default */
249   src = gst_element_factory_make ("pulsesrc", NULL);
250   if (src == NULL)
251     return NULL;
252
253   empathy_audio_set_stream_properties (src, TRUE);
254
255   /* Set latency (buffering on the PulseAudio side) of 20ms */
256   g_object_set (src, "buffer-time", (gint64) 20000, NULL);
257
258   return src;
259 }
260
261 static void
262 empathy_audio_src_init (EmpathyGstAudioSrc *obj)
263 {
264   EmpathyGstAudioSrcPrivate *priv = EMPATHY_GST_AUDIO_SRC_GET_PRIVATE (obj);
265   GstPad *ghost, *src;
266   GstElement *capsfilter;
267   GstCaps *caps;
268
269   obj->priv = priv;
270   g_mutex_init (&priv->lock);
271
272   priv->volume = 1.0;
273
274   priv->src = create_src ();
275   if (priv->src == NULL)
276     return;
277
278   if (GST_IS_STREAM_VOLUME (priv->src))
279     {
280       gdouble volume;
281       gboolean mute;
282
283       priv->have_stream_volume = TRUE;
284       /* We can't do a bidirection bind as the ::notify comes from another
285        * thread, for other bits of empathy it's most simpler if it comes from
286        * the main thread */
287       g_object_bind_property (obj, "volume", priv->src, "volume",
288         G_BINDING_DEFAULT);
289       g_object_bind_property (obj, "mute", priv->src, "mute",
290         G_BINDING_DEFAULT);
291
292       /* sync and callback for bouncing */
293       g_object_get (priv->src, "volume", &volume, NULL);
294       g_object_set (obj, "volume", volume, NULL);
295
296       g_object_get (priv->src, "mute", &mute, NULL);
297       g_object_set (obj, "mute", mute, NULL);
298
299       g_signal_connect (priv->src, "notify::volume",
300         G_CALLBACK (empathy_audio_src_volume_changed), obj);
301       g_signal_connect (priv->src, "notify::mute",
302         G_CALLBACK (empathy_audio_src_volume_changed), obj);
303     }
304   else
305     {
306       g_message ("No stream volume available :(, mute will work though");
307       priv->have_stream_volume = FALSE;
308     }
309
310   gst_bin_add (GST_BIN (obj), priv->src);
311
312   /* Explicitly state what format we want from pulsesrc. This pushes resampling
313    * and format conversion as early as possible, lowering the amount of data
314    * transferred and thus improving performance. When moving to GStreamer
315    * 0.11/1.0, this should change so that we actually request what the encoder
316    * wants downstream. */
317   caps = gst_caps_new_simple ("audio/x-raw-int",
318       "channels", G_TYPE_INT, 1,
319       "width", G_TYPE_INT, 16,
320       "depth", G_TYPE_INT, 16,
321       "rate", G_TYPE_INT, 32000,
322       NULL);
323   capsfilter = gst_element_factory_make ("capsfilter", NULL);
324   g_object_set (G_OBJECT (capsfilter), "caps", caps, NULL);
325   gst_bin_add (GST_BIN (obj), capsfilter);
326   gst_element_link (priv->src, capsfilter);
327
328   priv->volume_element = gst_element_factory_make ("volume", NULL);
329   gst_bin_add (GST_BIN (obj), priv->volume_element);
330   gst_element_link (priv->src, priv->volume_element);
331
332   src = gst_element_get_static_pad (priv->volume_element, "src");
333
334   ghost = gst_ghost_pad_new ("src", src);
335   gst_element_add_pad (GST_ELEMENT (obj), ghost);
336
337   gst_object_unref (G_OBJECT (src));
338
339   /* Listen to changes to GstPulseSrc:source-output-index so we know when
340    * it's no longer PA_INVALID_INDEX (starting for the first time) or if it
341    * changes (READY->NULL->READY...) */
342   g_signal_connect (priv->src, "notify::source-output-index",
343       G_CALLBACK (empathy_audio_src_source_output_index_notify),
344       obj);
345
346   priv->mic_monitor = empathy_mic_monitor_new ();
347   g_signal_connect (priv->mic_monitor, "microphone-changed",
348       G_CALLBACK (empathy_audio_src_microphone_changed_cb), obj);
349
350   priv->source_idx = PA_INVALID_INDEX;
351 }
352
353 static void empathy_audio_src_dispose (GObject *object);
354 static void empathy_audio_src_finalize (GObject *object);
355
356 static void
357 empathy_audio_src_set_property (GObject *object,
358   guint property_id, const GValue *value, GParamSpec *pspec)
359 {
360   switch (property_id)
361     {
362       case PROP_VOLUME:
363         empathy_audio_src_set_hw_volume (EMPATHY_GST_AUDIO_SRC (object),
364           g_value_get_double (value));
365         break;
366       case PROP_MUTE:
367         empathy_audio_set_hw_mute (EMPATHY_GST_AUDIO_SRC (object),
368           g_value_get_boolean (value));
369         break;
370       default:
371         G_OBJECT_WARN_INVALID_PROPERTY_ID (object, property_id, pspec);
372     }
373 }
374
375 static void
376 empathy_audio_src_get_property (GObject *object,
377   guint property_id, GValue *value, GParamSpec *pspec)
378 {
379   EmpathyGstAudioSrc *self = EMPATHY_GST_AUDIO_SRC (object);
380   EmpathyGstAudioSrcPrivate *priv = EMPATHY_GST_AUDIO_SRC_GET_PRIVATE (self);
381
382   switch (property_id)
383     {
384       case PROP_VOLUME:
385         g_value_set_double (value, priv->volume);
386         break;
387       case PROP_MUTE:
388         g_value_set_boolean (value, priv->mute);
389         break;
390       case PROP_MICROPHONE:
391         g_value_set_uint (value, priv->source_idx);
392         break;
393       default:
394         G_OBJECT_WARN_INVALID_PROPERTY_ID (object, property_id, pspec);
395     }
396 }
397
398 static void
399 empathy_audio_src_class_init (EmpathyGstAudioSrcClass
400   *empathy_audio_src_class)
401 {
402   GObjectClass *object_class = G_OBJECT_CLASS (empathy_audio_src_class);
403   GParamSpec *param_spec;
404
405   g_type_class_add_private (empathy_audio_src_class,
406     sizeof (EmpathyGstAudioSrcPrivate));
407
408   object_class->dispose = empathy_audio_src_dispose;
409   object_class->finalize = empathy_audio_src_finalize;
410
411   object_class->set_property = empathy_audio_src_set_property;
412   object_class->get_property = empathy_audio_src_get_property;
413
414   param_spec = g_param_spec_double ("volume", "Volume", "volume contol",
415     0.0, 5.0, 1.0,
416     G_PARAM_READWRITE | G_PARAM_STATIC_STRINGS);
417   g_object_class_install_property (object_class, PROP_VOLUME, param_spec);
418
419   param_spec = g_param_spec_boolean ("mute", "Mute", "mute contol",
420     FALSE,
421     G_PARAM_READWRITE | G_PARAM_STATIC_STRINGS);
422   g_object_class_install_property (object_class, PROP_MUTE, param_spec);
423
424   param_spec = g_param_spec_uint ("microphone", "microphone", "microphone",
425     0, G_MAXUINT, G_MAXUINT,
426     G_PARAM_READABLE | G_PARAM_STATIC_STRINGS);
427   g_object_class_install_property (object_class, PROP_MICROPHONE, param_spec);
428 }
429
430 void
431 empathy_audio_src_dispose (GObject *object)
432 {
433   EmpathyGstAudioSrc *self = EMPATHY_GST_AUDIO_SRC (object);
434   EmpathyGstAudioSrcPrivate *priv = EMPATHY_GST_AUDIO_SRC_GET_PRIVATE (self);
435
436   if (priv->dispose_has_run)
437     return;
438
439   priv->dispose_has_run = TRUE;
440
441   if (priv->volume_idle_id != 0)
442     g_source_remove (priv->volume_idle_id);
443   priv->volume_idle_id = 0;
444
445   tp_clear_object (&priv->mic_monitor);
446
447   /* release any references held by the object here */
448
449   if (G_OBJECT_CLASS (empathy_audio_src_parent_class)->dispose)
450     G_OBJECT_CLASS (empathy_audio_src_parent_class)->dispose (object);
451 }
452
453 void
454 empathy_audio_src_finalize (GObject *object)
455 {
456   EmpathyGstAudioSrc *self = EMPATHY_GST_AUDIO_SRC (object);
457   EmpathyGstAudioSrcPrivate *priv = EMPATHY_GST_AUDIO_SRC_GET_PRIVATE (self);
458
459   /* free any data held directly by the object here */
460   g_mutex_clear (&priv->lock);
461
462   G_OBJECT_CLASS (empathy_audio_src_parent_class)->finalize (object);
463 }
464
465 static gboolean
466 empathy_audio_src_volume_changed_idle (gpointer user_data)
467 {
468   EmpathyGstAudioSrc *self = EMPATHY_GST_AUDIO_SRC (user_data);
469   EmpathyGstAudioSrcPrivate *priv = EMPATHY_GST_AUDIO_SRC_GET_PRIVATE (self);
470   gdouble volume;
471   gboolean mute;
472
473   g_mutex_lock (&priv->lock);
474   priv->volume_idle_id = 0;
475   g_mutex_unlock (&priv->lock);
476
477   volume = empathy_audio_src_get_hw_volume (self);
478
479   if (volume != priv->volume)
480     {
481       priv->volume = volume;
482       g_object_notify (G_OBJECT (self), "volume");
483     }
484
485   mute = empathy_audio_src_get_hw_mute (self);
486   if (mute != priv->mute)
487     {
488       priv->mute = mute;
489       /* hw mute changed, follow with own volume */
490       g_object_set (self->priv->volume_element, "mute", mute, NULL);
491       g_object_notify (G_OBJECT (self), "mute");
492     }
493
494   return FALSE;
495 }
496
497 static gboolean
498 empathy_audio_src_volume_changed (GObject *object,
499   GParamSpec *pspec,
500   gpointer user_data)
501 {
502   EmpathyGstAudioSrc *self = EMPATHY_GST_AUDIO_SRC (user_data);
503
504   g_mutex_lock (&self->priv->lock);
505   if (self->priv->volume_idle_id == 0)
506     self->priv->volume_idle_id = g_idle_add (
507       empathy_audio_src_volume_changed_idle, self);
508   g_mutex_unlock (&self->priv->lock);
509
510   return FALSE;
511 }
512
513 GstElement *
514 empathy_audio_src_new (void)
515 {
516   static gboolean registered = FALSE;
517
518   if (!registered) {
519     if (!gst_element_register (NULL, "empathyaudiosrc",
520             GST_RANK_NONE, EMPATHY_TYPE_GST_AUDIO_SRC))
521       return NULL;
522     registered = TRUE;
523   }
524   return gst_element_factory_make ("empathyaudiosrc", NULL);
525 }
526
527 void
528 empathy_audio_src_set_echo_cancel (EmpathyGstAudioSrc *src,
529   gboolean enable)
530 {
531   DEBUG ("Src echo cancellation setting: %s", enable ? "on" : "off");
532   empathy_audio_set_stream_properties (src->priv->src, enable);
533 }
534
535 void
536 empathy_audio_src_set_volume (EmpathyGstAudioSrc *src, gdouble volume)
537 {
538   g_object_set (src, "volume", volume, NULL);
539 }
540
541 gdouble
542 empathy_audio_src_get_volume (EmpathyGstAudioSrc *src)
543 {
544   return src->priv->volume;
545 }
546
547 guint
548 empathy_audio_src_get_microphone (EmpathyGstAudioSrc *src)
549 {
550   EmpathyGstAudioSrcPrivate *priv = EMPATHY_GST_AUDIO_SRC_GET_PRIVATE (src);
551
552   return priv->source_idx;
553 }
554
555 static void
556 empathy_audio_src_change_microphone_cb (GObject *source_object,
557     GAsyncResult *result,
558     gpointer user_data)
559 {
560   EmpathyMicMonitor *monitor = EMPATHY_MIC_MONITOR (source_object);
561   GSimpleAsyncResult *simple = user_data;
562   GError *error = NULL;
563
564   if (!empathy_mic_monitor_change_microphone_finish (monitor,
565           result, &error))
566     {
567       g_simple_async_result_take_error (simple, error);
568     }
569
570   g_simple_async_result_complete (simple);
571   g_object_unref (simple);
572 }
573
574 void
575 empathy_audio_src_change_microphone_async (EmpathyGstAudioSrc *src,
576     guint microphone,
577     GAsyncReadyCallback callback,
578     gpointer user_data)
579 {
580   EmpathyGstAudioSrcPrivate *priv = EMPATHY_GST_AUDIO_SRC_GET_PRIVATE (src);
581   guint source_output_idx;
582   GSimpleAsyncResult *simple;
583
584   simple = g_simple_async_result_new (G_OBJECT (src), callback, user_data,
585       empathy_audio_src_change_microphone_async);
586
587   if (!empathy_audio_src_supports_changing_mic (src))
588     {
589       g_simple_async_result_set_error (simple, G_IO_ERROR, G_IO_ERROR_FAILED,
590           "pulsesrc is not new enough to support changing microphone");
591       g_simple_async_result_complete_in_idle (simple);
592       g_object_unref (simple);
593       return;
594     }
595
596   source_output_idx = empathy_audio_src_get_mic_index (src);
597
598   if (source_output_idx == PA_INVALID_INDEX)
599     {
600       g_simple_async_result_set_error (simple, G_IO_ERROR, G_IO_ERROR_FAILED,
601           "pulsesrc is not yet PLAYING");
602       g_simple_async_result_complete_in_idle (simple);
603       g_object_unref (simple);
604       return;
605     }
606
607   empathy_mic_monitor_change_microphone_async (priv->mic_monitor,
608       source_output_idx, microphone, empathy_audio_src_change_microphone_cb,
609       simple);
610 }
611
612 gboolean
613 empathy_audio_src_change_microphone_finish (EmpathyGstAudioSrc *src,
614     GAsyncResult *result,
615     GError **error)
616 {
617   empathy_implement_finish_void (src,
618       empathy_audio_src_change_microphone_async);
619 }
620
621 void
622 empathy_audio_src_set_mute (EmpathyGstAudioSrc *self,
623     gboolean mute)
624 {
625   empathy_audio_set_hw_mute (self, mute);
626
627   g_object_notify (G_OBJECT (self), "mute");
628 }