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