]> git.0d.be Git - empathy.git/blob - src/empathy-audio-src.c
audio-src: make supports_changing_mic public
[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
22 #include <stdio.h>
23 #include <stdlib.h>
24
25 #include <libempathy/empathy-utils.h>
26 #include <libempathy-gtk/empathy-call-utils.h>
27
28 #include "empathy-audio-src.h"
29
30 #include "src-marshal.h"
31 #include "empathy-mic-monitor.h"
32
33 #define DEBUG_FLAG EMPATHY_DEBUG_VOIP
34 #include <libempathy/empathy-debug.h>
35
36 G_DEFINE_TYPE(EmpathyGstAudioSrc, empathy_audio_src, GST_TYPE_BIN)
37
38 /* signal enum */
39 enum
40 {
41     PEAK_LEVEL_CHANGED,
42     RMS_LEVEL_CHANGED,
43     LAST_SIGNAL
44 };
45
46 static guint signals[LAST_SIGNAL] = {0};
47
48 enum {
49     PROP_VOLUME = 1,
50     PROP_RMS_LEVEL,
51     PROP_PEAK_LEVEL,
52     PROP_MICROPHONE,
53 };
54
55 /* private structure */
56 typedef struct _EmpathyGstAudioSrcPrivate EmpathyGstAudioSrcPrivate;
57
58 struct _EmpathyGstAudioSrcPrivate
59 {
60   gboolean dispose_has_run;
61   GstElement *src;
62   GstElement *volume;
63   GstElement *level;
64
65   EmpathyMicMonitor *mic_monitor;
66
67   /* 0 if not known yet */
68   guint source_output_idx;
69   /* G_MAXUINT if not known yet */
70   guint source_idx;
71
72   gdouble peak_level;
73   gdouble rms_level;
74
75   GMutex *lock;
76   guint idle_id;
77 };
78
79 #define EMPATHY_GST_AUDIO_SRC_GET_PRIVATE(o) \
80   (G_TYPE_INSTANCE_GET_PRIVATE ((o), EMPATHY_TYPE_GST_AUDIO_SRC, \
81   EmpathyGstAudioSrcPrivate))
82
83 gboolean
84 empathy_audio_src_supports_changing_mic (EmpathyGstAudioSrc *self)
85 {
86   EmpathyGstAudioSrcPrivate *priv = EMPATHY_GST_AUDIO_SRC_GET_PRIVATE (self);
87   GObjectClass *object_class;
88
89   object_class = G_OBJECT_GET_CLASS (priv->src);
90
91   return (g_object_class_find_property (object_class,
92           "source-output-index") != NULL);
93 }
94
95 static void
96 empathy_audio_src_microphone_changed_cb (EmpathyMicMonitor *monitor,
97     guint source_output_idx,
98     guint source_idx,
99     gpointer user_data)
100 {
101   EmpathyGstAudioSrc *self = user_data;
102   EmpathyGstAudioSrcPrivate *priv = EMPATHY_GST_AUDIO_SRC_GET_PRIVATE (self);
103   guint audio_src_idx = PA_INVALID_INDEX;
104
105   g_object_get (priv->src, "source-output-index", &audio_src_idx, NULL);
106
107   if (source_output_idx == PA_INVALID_INDEX
108       || source_output_idx != audio_src_idx)
109     return;
110
111   if (priv->source_idx == source_idx)
112     return;
113
114   priv->source_idx = source_idx;
115   g_object_notify (G_OBJECT (self), "microphone");
116 }
117
118 static void
119 empathy_audio_src_get_current_mic_cb (GObject *source_object,
120     GAsyncResult *result,
121     gpointer user_data)
122 {
123   EmpathyMicMonitor *monitor = EMPATHY_MIC_MONITOR (source_object);
124   EmpathyGstAudioSrc *self = user_data;
125   EmpathyGstAudioSrcPrivate *priv = EMPATHY_GST_AUDIO_SRC_GET_PRIVATE (self);
126   guint source_idx;
127   GError *error = NULL;
128
129   source_idx = empathy_mic_monitor_get_current_mic_finish (monitor, result, &error);
130
131   if (error != NULL)
132     {
133       DEBUG ("Failed to get current mic: %s", error->message);
134       g_clear_error (&error);
135       return;
136     }
137
138   if (priv->source_idx == source_idx)
139     return;
140
141   priv->source_idx = source_idx;
142   g_object_notify (G_OBJECT (self), "microphone");
143 }
144
145 static void
146 empathy_audio_src_source_output_index_notify (GObject *object,
147     GParamSpec *pspec,
148     EmpathyGstAudioSrc *self)
149 {
150   EmpathyGstAudioSrcPrivate *priv = EMPATHY_GST_AUDIO_SRC_GET_PRIVATE (self);
151   guint source_output_idx = PA_INVALID_INDEX;
152
153   g_object_get (priv->src, "source-output-index", &source_output_idx, NULL);
154
155   if (source_output_idx == PA_INVALID_INDEX)
156     return;
157
158   if (priv->source_output_idx == source_output_idx)
159     return;
160
161   /* It's actually changed. */
162   priv->source_output_idx = source_output_idx;
163
164   empathy_mic_monitor_get_current_mic_async (priv->mic_monitor,
165       source_output_idx, empathy_audio_src_get_current_mic_cb, self);
166 }
167
168 static GstElement *
169 create_src (void)
170 {
171   GstElement *src;
172   const gchar *description;
173
174   description = g_getenv ("EMPATHY_AUDIO_SRC");
175
176   if (description != NULL)
177     {
178       GError *error = NULL;
179
180       src = gst_parse_bin_from_description (description, TRUE, &error);
181       if (src == NULL)
182         {
183           DEBUG ("Failed to create bin %s: %s", description, error->message);
184           g_error_free (error);
185         }
186
187       return src;
188     }
189
190   /* Use pulsesrc as default */
191   src = gst_element_factory_make ("pulsesrc", NULL);
192   if (src == NULL)
193     return NULL;
194
195   empathy_call_set_stream_properties (src);
196
197   return src;
198 }
199
200 static void
201 empathy_audio_src_init (EmpathyGstAudioSrc *obj)
202 {
203   EmpathyGstAudioSrcPrivate *priv = EMPATHY_GST_AUDIO_SRC_GET_PRIVATE (obj);
204   GstPad *ghost, *src;
205
206   priv->peak_level = -G_MAXDOUBLE;
207   priv->lock = g_mutex_new ();
208
209   priv->src = create_src ();
210   if (priv->src == NULL)
211     return;
212
213   gst_bin_add (GST_BIN (obj), priv->src);
214
215   priv->volume = gst_element_factory_make ("volume", NULL);
216   g_object_ref (priv->volume);
217
218   gst_bin_add (GST_BIN (obj), priv->volume);
219   gst_element_link (priv->src, priv->volume);
220
221   priv->level = gst_element_factory_make ("level", NULL);
222   gst_bin_add (GST_BIN (obj), priv->level);
223   gst_element_link (priv->volume, priv->level);
224
225   src = gst_element_get_static_pad (priv->level, "src");
226
227   ghost = gst_ghost_pad_new ("src", src);
228   gst_element_add_pad (GST_ELEMENT (obj), ghost);
229
230   gst_object_unref (G_OBJECT (src));
231
232   /* Listen to changes to GstPulseSrc:source-output-index so we know when
233    * it's no longer PA_INVALID_INDEX (starting for the first time) or if it
234    * changes (READY->NULL->READY...) */
235   g_signal_connect (priv->src, "notify::source-output-index",
236       G_CALLBACK (empathy_audio_src_source_output_index_notify),
237       obj);
238
239   priv->mic_monitor = empathy_mic_monitor_new ();
240   g_signal_connect (priv->mic_monitor, "microphone-changed",
241       G_CALLBACK (empathy_audio_src_microphone_changed_cb), obj);
242
243   priv->source_idx = PA_INVALID_INDEX;
244 }
245
246 static void empathy_audio_src_dispose (GObject *object);
247 static void empathy_audio_src_finalize (GObject *object);
248 static void empathy_audio_src_handle_message (GstBin *bin,
249   GstMessage *message);
250
251 static gboolean empathy_audio_src_levels_updated (gpointer user_data);
252
253 static void
254 empathy_audio_src_set_property (GObject *object,
255   guint property_id, const GValue *value, GParamSpec *pspec)
256 {
257   switch (property_id)
258     {
259       case PROP_VOLUME:
260         empathy_audio_src_set_volume (EMPATHY_GST_AUDIO_SRC (object),
261           g_value_get_double (value));
262         break;
263       default:
264         G_OBJECT_WARN_INVALID_PROPERTY_ID (object, property_id, pspec);
265     }
266 }
267
268 static void
269 empathy_audio_src_get_property (GObject *object,
270   guint property_id, GValue *value, GParamSpec *pspec)
271 {
272   EmpathyGstAudioSrc *self = EMPATHY_GST_AUDIO_SRC (object);
273   EmpathyGstAudioSrcPrivate *priv = EMPATHY_GST_AUDIO_SRC_GET_PRIVATE (self);
274
275   switch (property_id)
276     {
277       case PROP_VOLUME:
278         g_value_set_double (value,
279           empathy_audio_src_get_volume (self));
280         break;
281       case PROP_PEAK_LEVEL:
282         g_mutex_lock (priv->lock);
283         g_value_set_double (value, priv->peak_level);
284         g_mutex_unlock (priv->lock);
285         break;
286       case PROP_RMS_LEVEL:
287         g_mutex_lock (priv->lock);
288         g_value_set_double (value, priv->rms_level);
289         g_mutex_unlock (priv->lock);
290         break;
291       case PROP_MICROPHONE:
292         g_value_set_uint (value, priv->source_idx);
293         break;
294       default:
295         G_OBJECT_WARN_INVALID_PROPERTY_ID (object, property_id, pspec);
296     }
297 }
298
299 static void
300 empathy_audio_src_class_init (EmpathyGstAudioSrcClass
301   *empathy_audio_src_class)
302 {
303   GObjectClass *object_class = G_OBJECT_CLASS (empathy_audio_src_class);
304   GstBinClass *gstbin_class = GST_BIN_CLASS (empathy_audio_src_class);
305   GParamSpec *param_spec;
306
307   g_type_class_add_private (empathy_audio_src_class,
308     sizeof (EmpathyGstAudioSrcPrivate));
309
310   object_class->dispose = empathy_audio_src_dispose;
311   object_class->finalize = empathy_audio_src_finalize;
312
313   object_class->set_property = empathy_audio_src_set_property;
314   object_class->get_property = empathy_audio_src_get_property;
315
316   gstbin_class->handle_message =
317     GST_DEBUG_FUNCPTR (empathy_audio_src_handle_message);
318
319   param_spec = g_param_spec_double ("volume", "Volume", "volume contol",
320     0.0, 5.0, 1.0,
321     G_PARAM_READWRITE | G_PARAM_STATIC_STRINGS);
322   g_object_class_install_property (object_class, PROP_VOLUME, param_spec);
323
324   param_spec = g_param_spec_double ("peak-level", "peak level", "peak level",
325     -G_MAXDOUBLE, G_MAXDOUBLE, 0,
326     G_PARAM_READABLE | G_PARAM_STATIC_STRINGS);
327   g_object_class_install_property (object_class, PROP_PEAK_LEVEL, param_spec);
328
329   param_spec = g_param_spec_uint ("microphone", "microphone", "microphone",
330     0, G_MAXUINT, G_MAXUINT,
331     G_PARAM_READABLE | G_PARAM_STATIC_STRINGS);
332   g_object_class_install_property (object_class, PROP_MICROPHONE, param_spec);
333
334   signals[PEAK_LEVEL_CHANGED] = g_signal_new ("peak-level-changed",
335     G_TYPE_FROM_CLASS (empathy_audio_src_class),
336     G_SIGNAL_RUN_LAST,
337     0,
338     NULL, NULL,
339     g_cclosure_marshal_VOID__DOUBLE,
340     G_TYPE_NONE, 1, G_TYPE_DOUBLE);
341
342   param_spec = g_param_spec_double ("rms-level", "RMS level", "RMS level",
343     -G_MAXDOUBLE, G_MAXDOUBLE, 0,
344     G_PARAM_READABLE | G_PARAM_STATIC_STRINGS);
345   g_object_class_install_property (object_class, PROP_RMS_LEVEL, param_spec);
346
347   signals[RMS_LEVEL_CHANGED] = g_signal_new ("rms-level-changed",
348     G_TYPE_FROM_CLASS (empathy_audio_src_class),
349     G_SIGNAL_RUN_LAST,
350     0,
351     NULL, NULL,
352     g_cclosure_marshal_VOID__DOUBLE,
353     G_TYPE_NONE, 1, G_TYPE_DOUBLE);
354 }
355
356 void
357 empathy_audio_src_dispose (GObject *object)
358 {
359   EmpathyGstAudioSrc *self = EMPATHY_GST_AUDIO_SRC (object);
360   EmpathyGstAudioSrcPrivate *priv = EMPATHY_GST_AUDIO_SRC_GET_PRIVATE (self);
361
362   if (priv->dispose_has_run)
363     return;
364
365   priv->dispose_has_run = TRUE;
366
367   if (priv->idle_id != 0)
368     g_source_remove (priv->idle_id);
369
370   priv->idle_id = 0;
371
372   tp_clear_object (&priv->mic_monitor);
373
374   /* release any references held by the object here */
375
376   if (G_OBJECT_CLASS (empathy_audio_src_parent_class)->dispose)
377     G_OBJECT_CLASS (empathy_audio_src_parent_class)->dispose (object);
378 }
379
380 void
381 empathy_audio_src_finalize (GObject *object)
382 {
383   EmpathyGstAudioSrc *self = EMPATHY_GST_AUDIO_SRC (object);
384   EmpathyGstAudioSrcPrivate *priv = EMPATHY_GST_AUDIO_SRC_GET_PRIVATE (self);
385
386   /* free any data held directly by the object here */
387   g_mutex_free (priv->lock);
388
389   G_OBJECT_CLASS (empathy_audio_src_parent_class)->finalize (object);
390 }
391
392 static gboolean
393 empathy_audio_src_levels_updated (gpointer user_data)
394 {
395   EmpathyGstAudioSrc *self = EMPATHY_GST_AUDIO_SRC (user_data);
396   EmpathyGstAudioSrcPrivate *priv = EMPATHY_GST_AUDIO_SRC_GET_PRIVATE (self);
397
398   g_mutex_lock (priv->lock);
399
400   g_signal_emit (self, signals[PEAK_LEVEL_CHANGED], 0, priv->peak_level);
401   g_signal_emit (self, signals[RMS_LEVEL_CHANGED], 0, priv->rms_level);
402   priv->idle_id = 0;
403
404   g_mutex_unlock (priv->lock);
405
406   return FALSE;
407 }
408
409 static void
410 empathy_audio_src_handle_message (GstBin *bin, GstMessage *message)
411 {
412   EmpathyGstAudioSrc *self = EMPATHY_GST_AUDIO_SRC (bin);
413   EmpathyGstAudioSrcPrivate *priv = EMPATHY_GST_AUDIO_SRC_GET_PRIVATE (self);
414
415   if  (GST_MESSAGE_TYPE (message) == GST_MESSAGE_ELEMENT &&
416         GST_MESSAGE_SRC (message) == GST_OBJECT (priv->level))
417     {
418       const GstStructure *s;
419       const gchar *name;
420       const GValue *list;
421       guint i, len;
422       gdouble peak = -G_MAXDOUBLE;
423       gdouble rms = -G_MAXDOUBLE;
424
425       s = gst_message_get_structure (message);
426       name = gst_structure_get_name (s);
427
428       if (g_strcmp0 ("level", name) != 0)
429         goto out;
430
431       list = gst_structure_get_value (s, "peak");
432       len = gst_value_list_get_size (list);
433
434       for (i =0 ; i < len; i++)
435         {
436           const GValue *value;
437           gdouble db;
438
439           value = gst_value_list_get_value (list, i);
440           db = g_value_get_double (value);
441           peak = MAX (db, peak);
442         }
443
444       list = gst_structure_get_value (s, "rms");
445       len = gst_value_list_get_size (list);
446
447       for (i =0 ; i < len; i++)
448         {
449           const GValue *value;
450           gdouble db;
451
452           value = gst_value_list_get_value (list, i);
453           db = g_value_get_double (value);
454           rms = MAX (db, rms);
455         }
456
457       g_mutex_lock (priv->lock);
458
459       priv->peak_level = peak;
460       priv->rms_level = rms;
461       if (priv->idle_id == 0)
462         priv->idle_id = g_idle_add (empathy_audio_src_levels_updated, self);
463
464       g_mutex_unlock (priv->lock);
465     }
466
467 out:
468    GST_BIN_CLASS (empathy_audio_src_parent_class)->handle_message (bin,
469     message);
470 }
471
472 GstElement *
473 empathy_audio_src_new (void)
474 {
475   static gboolean registered = FALSE;
476
477   if (!registered) {
478     if (!gst_element_register (NULL, "empathyaudiosrc",
479             GST_RANK_NONE, EMPATHY_TYPE_GST_AUDIO_SRC))
480       return NULL;
481     registered = TRUE;
482   }
483   return gst_element_factory_make ("empathyaudiosrc", NULL);
484 }
485
486 void
487 empathy_audio_src_set_volume (EmpathyGstAudioSrc *src, gdouble volume)
488 {
489   EmpathyGstAudioSrcPrivate *priv = EMPATHY_GST_AUDIO_SRC_GET_PRIVATE (src);
490   GParamSpec *pspec;
491   GParamSpecDouble *pspec_double;
492
493   pspec = g_object_class_find_property (G_OBJECT_GET_CLASS (priv->volume),
494     "volume");
495
496   g_assert (pspec != NULL);
497
498   pspec_double = G_PARAM_SPEC_DOUBLE (pspec);
499
500   volume = CLAMP (volume, pspec_double->minimum, pspec_double->maximum);
501
502   g_object_set (G_OBJECT (priv->volume), "volume", volume, NULL);
503 }
504
505 gdouble
506 empathy_audio_src_get_volume (EmpathyGstAudioSrc *src)
507 {
508   EmpathyGstAudioSrcPrivate *priv = EMPATHY_GST_AUDIO_SRC_GET_PRIVATE (src);
509   gdouble volume;
510
511   g_object_get (G_OBJECT (priv->volume), "volume", &volume, NULL);
512
513   return volume;
514 }
515
516 guint
517 empathy_audio_src_get_microphone (EmpathyGstAudioSrc *src)
518 {
519   EmpathyGstAudioSrcPrivate *priv = EMPATHY_GST_AUDIO_SRC_GET_PRIVATE (src);
520
521   return priv->source_idx;
522 }
523
524 static void
525 empathy_audio_src_change_microphone_cb (GObject *source_object,
526     GAsyncResult *result,
527     gpointer user_data)
528 {
529   EmpathyMicMonitor *monitor = EMPATHY_MIC_MONITOR (source_object);
530   GSimpleAsyncResult *simple = user_data;
531   GError *error = NULL;
532
533   if (!empathy_mic_monitor_change_microphone_finish (monitor,
534           result, &error))
535     {
536       g_simple_async_result_take_error (simple, error);
537     }
538
539   g_simple_async_result_complete (simple);
540   g_object_unref (simple);
541 }
542
543 void
544 empathy_audio_src_change_microphone_async (EmpathyGstAudioSrc *src,
545     guint microphone,
546     GAsyncReadyCallback callback,
547     gpointer user_data)
548 {
549   EmpathyGstAudioSrcPrivate *priv = EMPATHY_GST_AUDIO_SRC_GET_PRIVATE (src);
550   guint source_output_idx;
551   GSimpleAsyncResult *simple;
552
553   simple = g_simple_async_result_new (G_OBJECT (src), callback, user_data,
554       empathy_audio_src_change_microphone_async);
555
556   if (!empathy_audio_src_supports_changing_mic (src))
557     {
558       g_simple_async_result_set_error (simple, G_IO_ERROR, G_IO_ERROR_FAILED,
559           "pulsesrc is not new enough to support changing microphone");
560       g_simple_async_result_complete_in_idle (simple);
561       g_object_unref (simple);
562       return;
563     }
564
565   g_object_get (priv->src, "source-output-index", &source_output_idx, NULL);
566
567   if (source_output_idx == PA_INVALID_INDEX)
568     {
569       g_simple_async_result_set_error (simple, G_IO_ERROR, G_IO_ERROR_FAILED,
570           "pulsesrc is not yet PLAYING");
571       g_simple_async_result_complete_in_idle (simple);
572       g_object_unref (simple);
573       return;
574     }
575
576   empathy_mic_monitor_change_microphone_async (priv->mic_monitor,
577       source_output_idx, microphone, empathy_audio_src_change_microphone_cb,
578       simple);
579 }
580
581 gboolean
582 empathy_audio_src_change_microphone_finish (EmpathyGstAudioSrc *src,
583     GAsyncResult *result,
584     GError **error)
585 {
586   empathy_implement_finish_void (src,
587       empathy_audio_src_change_microphone_async);
588 }