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