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