]> git.0d.be Git - empathy.git/blob - src/empathy-audio-src.c
129e75319817eaf925452588bf37bdaf916c564d
[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 <pulse/pulseaudio.h>
26 #include <pulse/glib-mainloop.h>
27
28 #include <libempathy/empathy-utils.h>
29
30 #include "empathy-audio-src.h"
31
32 #include "src-marshal.h"
33
34 #define DEBUG_FLAG EMPATHY_DEBUG_VOIP
35 #include <libempathy/empathy-debug.h>
36
37 G_DEFINE_TYPE(EmpathyGstAudioSrc, empathy_audio_src, GST_TYPE_BIN)
38
39 /* signal enum */
40 enum
41 {
42     PEAK_LEVEL_CHANGED,
43     RMS_LEVEL_CHANGED,
44     MICROPHONE_ADDED,
45     MICROPHONE_REMOVED,
46     LAST_SIGNAL
47 };
48
49 static guint signals[LAST_SIGNAL] = {0};
50
51 enum {
52     PROP_VOLUME = 1,
53     PROP_RMS_LEVEL,
54     PROP_PEAK_LEVEL,
55     PROP_MICROPHONE,
56 };
57
58 /* private structure */
59 typedef struct _EmpathyGstAudioSrcPrivate EmpathyGstAudioSrcPrivate;
60
61 struct _EmpathyGstAudioSrcPrivate
62 {
63   gboolean dispose_has_run;
64   GstElement *src;
65   GstElement *volume;
66   GstElement *level;
67
68   pa_glib_mainloop *loop;
69   pa_context *context;
70   GQueue *operations;
71
72   /* 0 if not known yet */
73   guint source_output_idx;
74   /* G_MAXUINT if not known yet */
75   guint source_idx;
76
77   gdouble peak_level;
78   gdouble rms_level;
79
80   GMutex *lock;
81   guint idle_id;
82 };
83
84 #define EMPATHY_GST_AUDIO_SRC_GET_PRIVATE(o) \
85   (G_TYPE_INSTANCE_GET_PRIVATE ((o), EMPATHY_TYPE_GST_AUDIO_SRC, \
86   EmpathyGstAudioSrcPrivate))
87
88 typedef void (*OperationFunc) (EmpathyGstAudioSrc *, GSimpleAsyncResult *);
89
90 typedef struct
91 {
92   OperationFunc func;
93   GSimpleAsyncResult *result;
94 } Operation;
95
96 static Operation *
97 operation_new (OperationFunc func,
98     GSimpleAsyncResult *result)
99 {
100   Operation *o = g_slice_new0 (Operation);
101
102   o->func = func;
103   o->result = result;
104
105   return o;
106 }
107
108 static void
109 operation_free (Operation *o,
110     gboolean cancelled)
111 {
112   if (cancelled)
113     {
114       g_simple_async_result_set_error (o->result,
115           G_IO_ERROR, G_IO_ERROR_CANCELLED,
116           "The audio source was disposed");
117       g_simple_async_result_complete (o->result);
118       g_object_unref (o->result);
119     }
120
121   g_slice_free (Operation, o);
122 }
123
124 static void
125 operation_get_microphones_free (gpointer data)
126 {
127   GQueue *queue = data;
128   GList *l;
129
130   for (l = queue->head; l != NULL; l = l->next)
131     {
132       EmpathyAudioSrcMicrophone *mic = l->data;
133
134       g_free (mic->name);
135       g_free (mic->description);
136       g_slice_free (EmpathyAudioSrcMicrophone, mic);
137     }
138
139   g_queue_free (queue);
140 }
141
142 static void
143 operation_get_microphones_cb (pa_context *context,
144     const pa_source_info *info,
145     int eol,
146     void *userdata)
147 {
148   GSimpleAsyncResult *result = userdata;
149   EmpathyAudioSrcMicrophone *mic;
150   GQueue *queue;
151
152   if (eol)
153     {
154       g_simple_async_result_complete (result);
155       g_object_unref (result);
156       return;
157     }
158
159   mic = g_slice_new0 (EmpathyAudioSrcMicrophone);
160   mic->index = info->index;
161   mic->name = g_strdup (info->name);
162   mic->description = g_strdup (info->description);
163   mic->is_monitor = (info->monitor_of_sink != PA_INVALID_INDEX);
164
165   /* add it to the queue */
166   queue = g_simple_async_result_get_op_res_gpointer (result);
167   g_queue_push_tail (queue, mic);
168 }
169
170 static void
171 operation_get_microphones (EmpathyGstAudioSrc *self,
172     GSimpleAsyncResult *result)
173 {
174   EmpathyGstAudioSrcPrivate *priv = EMPATHY_GST_AUDIO_SRC_GET_PRIVATE (self);
175
176   g_assert_cmpuint (pa_context_get_state (priv->context), ==, PA_CONTEXT_READY);
177
178   g_simple_async_result_set_op_res_gpointer (result, g_queue_new (),
179       operation_get_microphones_free);
180
181   pa_context_get_source_info_list (priv->context,
182       operation_get_microphones_cb, result);
183 }
184
185 static void
186 operation_change_microphone_cb (pa_context *context,
187     int success,
188     void *userdata)
189 {
190   GSimpleAsyncResult *result = userdata;
191
192   if (!success)
193     {
194       g_simple_async_result_set_error (result, G_IO_ERROR, G_IO_ERROR_FAILED,
195           "Failed to change microphone. Reason unknown.");
196     }
197
198   g_simple_async_result_complete (result);
199   g_object_unref (result);
200 }
201
202 static void
203 operation_change_microphone (EmpathyGstAudioSrc *self,
204     GSimpleAsyncResult *result)
205 {
206   EmpathyGstAudioSrcPrivate *priv = EMPATHY_GST_AUDIO_SRC_GET_PRIVATE (self);
207   guint source_output_idx, microphone;
208
209   g_object_get (priv->src, "source-output-index", &source_output_idx, NULL);
210
211   g_assert_cmpuint (pa_context_get_state (priv->context), ==, PA_CONTEXT_READY);
212   g_assert_cmpuint (source_output_idx, !=, PA_INVALID_INDEX);
213
214   microphone = GPOINTER_TO_UINT (
215       g_simple_async_result_get_op_res_gpointer (result));
216
217   pa_context_move_source_output_by_index (priv->context, source_output_idx, microphone,
218       operation_change_microphone_cb, result);
219 }
220
221 static void
222 operations_run (EmpathyGstAudioSrc *self)
223 {
224   EmpathyGstAudioSrcPrivate *priv = EMPATHY_GST_AUDIO_SRC_GET_PRIVATE (self);
225   pa_context_state_t state = pa_context_get_state (priv->context);
226   GList *l;
227
228   if (state != PA_CONTEXT_READY)
229     return;
230
231   for (l = priv->operations->head; l != NULL; l = l->next)
232     {
233       Operation *o = l->data;
234
235       o->func (self, o->result);
236
237       operation_free (o, FALSE);
238     }
239
240   g_queue_clear (priv->operations);
241 }
242
243 static void
244 empathy_audio_src_source_output_info_cb (pa_context *context,
245     const pa_source_output_info *info,
246     int eol,
247     void *userdata)
248 {
249   EmpathyGstAudioSrc *self = userdata;
250   EmpathyGstAudioSrcPrivate *priv = EMPATHY_GST_AUDIO_SRC_GET_PRIVATE (self);
251
252   if (eol)
253     return;
254
255   /* There should only be one call here. */
256
257   if (priv->source_idx == info->source)
258     return;
259
260   priv->source_idx = info->source;
261   g_object_notify (G_OBJECT (self), "microphone");
262 }
263
264 static void
265 empathy_audio_src_source_info_cb (pa_context *context,
266     const pa_source_info *info,
267     int eol,
268     void *userdata)
269 {
270   EmpathyGstAudioSrc *self = userdata;
271   gboolean is_monitor;
272
273   if (eol)
274     return;
275
276   is_monitor = (info->monitor_of_sink != PA_INVALID_INDEX);
277
278   g_signal_emit (self, signals[MICROPHONE_ADDED], 0,
279       info->index, info->name, info->description, is_monitor);
280 }
281
282 static void
283 empathy_audio_src_pa_event_cb (pa_context *context,
284     pa_subscription_event_type_t type,
285     uint32_t idx,
286     void *userdata)
287 {
288   EmpathyGstAudioSrc *self = userdata;
289   EmpathyGstAudioSrcPrivate *priv = EMPATHY_GST_AUDIO_SRC_GET_PRIVATE (self);
290
291   if ((type & PA_SUBSCRIPTION_EVENT_FACILITY_MASK) == PA_SUBSCRIPTION_EVENT_SOURCE_OUTPUT
292       && (type & PA_SUBSCRIPTION_EVENT_TYPE_MASK) == PA_SUBSCRIPTION_EVENT_CHANGE
293       && idx == priv->source_output_idx)
294     {
295       /* Microphone in the source output has changed */
296       pa_context_get_source_output_info (context, idx,
297           empathy_audio_src_source_output_info_cb, self);
298     }
299   else if ((type & PA_SUBSCRIPTION_EVENT_FACILITY_MASK) == PA_SUBSCRIPTION_EVENT_SOURCE
300       && (type & PA_SUBSCRIPTION_EVENT_TYPE_MASK) == PA_SUBSCRIPTION_EVENT_REMOVE)
301     {
302       /* A mic has been removed */
303       g_signal_emit (self, signals[MICROPHONE_REMOVED], 0, idx);
304     }
305   else if ((type & PA_SUBSCRIPTION_EVENT_FACILITY_MASK) == PA_SUBSCRIPTION_EVENT_SOURCE
306       && (type & PA_SUBSCRIPTION_EVENT_TYPE_MASK) == PA_SUBSCRIPTION_EVENT_NEW)
307     {
308       /* A mic has been plugged in */
309       pa_context_get_source_info_by_index (context, idx,
310           empathy_audio_src_source_info_cb, self);
311     }
312 }
313
314 static void
315 empathy_audio_src_pa_subscribe_cb (pa_context *context,
316     int success,
317     void *userdata)
318 {
319   if (!success)
320     DEBUG ("Failed to subscribe to PulseAudio events");
321 }
322
323 static void
324 empathy_audio_src_pa_state_change_cb (pa_context *context,
325     void *userdata)
326 {
327   EmpathyGstAudioSrc *self = userdata;
328   EmpathyGstAudioSrcPrivate *priv = EMPATHY_GST_AUDIO_SRC_GET_PRIVATE (self);
329   pa_context_state_t state = pa_context_get_state (priv->context);
330
331   if (state == PA_CONTEXT_READY)
332     {
333       /* Listen to pulseaudio events so we know when sources are
334        * added and when the microphone is changed. */
335       pa_context_set_subscribe_callback (priv->context,
336           empathy_audio_src_pa_event_cb, self);
337       pa_context_subscribe (priv->context,
338           PA_SUBSCRIPTION_MASK_SOURCE | PA_SUBSCRIPTION_MASK_SOURCE_OUTPUT,
339           empathy_audio_src_pa_subscribe_cb, NULL);
340
341       operations_run (self);
342     }
343 }
344
345 static void
346 empathy_audio_src_source_output_index_notify (GObject *object,
347     GParamSpec *pspec,
348     EmpathyGstAudioSrc *self)
349 {
350   EmpathyGstAudioSrcPrivate *priv = EMPATHY_GST_AUDIO_SRC_GET_PRIVATE (self);
351   guint source_output_idx = PA_INVALID_INDEX;
352
353   g_object_get (priv->src, "source-output-index", &source_output_idx, NULL);
354
355   if (source_output_idx == PA_INVALID_INDEX)
356     return;
357
358   if (priv->source_output_idx == source_output_idx)
359     return;
360
361   /* It's actually changed. */
362   priv->source_output_idx = source_output_idx;
363
364   pa_context_get_source_output_info (priv->context, source_output_idx,
365       empathy_audio_src_source_output_info_cb, self);
366 }
367
368 static void
369 empathy_audio_src_init (EmpathyGstAudioSrc *obj)
370 {
371   EmpathyGstAudioSrcPrivate *priv = EMPATHY_GST_AUDIO_SRC_GET_PRIVATE (obj);
372   GstPad *ghost, *src;
373   const gchar *src_element;
374
375   priv->peak_level = -G_MAXDOUBLE;
376   priv->lock = g_mutex_new ();
377
378   src_element = g_getenv ("EMPATHY_AUDIO_SRC");
379   if (src_element == NULL)
380     src_element = "pulsesrc";
381
382   priv->src = gst_element_factory_make (src_element, NULL);
383   gst_bin_add (GST_BIN (obj), priv->src);
384
385   priv->volume = gst_element_factory_make ("volume", NULL);
386   g_object_ref (priv->volume);
387
388   gst_bin_add (GST_BIN (obj), priv->volume);
389   gst_element_link (priv->src, priv->volume);
390
391   priv->level = gst_element_factory_make ("level", NULL);
392   gst_bin_add (GST_BIN (obj), priv->level);
393   gst_element_link (priv->volume, priv->level);
394
395   src = gst_element_get_static_pad (priv->level, "src");
396
397   ghost = gst_ghost_pad_new ("src", src);
398   gst_element_add_pad (GST_ELEMENT (obj), ghost);
399
400   gst_object_unref (G_OBJECT (src));
401
402   /* PulseAudio stuff: We need to create a dummy pa_glib_mainloop* so
403    * Pulse can use the mainloop that GTK has created for us. */
404   priv->loop = pa_glib_mainloop_new (NULL);
405   priv->context = pa_context_new (pa_glib_mainloop_get_api (priv->loop),
406       "EmpathyAudioSrc");
407
408   /* Listen to changes to GstPulseSrc:source-output-index so we know when
409    * it's no longer PA_INVALID_INDEX (starting for the first time) or if it
410    * changes (READY->NULL->READY...) */
411   g_signal_connect (priv->src, "notify::source-output-index",
412       G_CALLBACK (empathy_audio_src_source_output_index_notify),
413       obj);
414
415   /* Finally listen for state changes so we know when we've
416    * connected. */
417   pa_context_set_state_callback (priv->context,
418       empathy_audio_src_pa_state_change_cb, obj);
419   pa_context_connect (priv->context, NULL, 0, NULL);
420
421   priv->operations = g_queue_new ();
422 }
423
424 static void empathy_audio_src_dispose (GObject *object);
425 static void empathy_audio_src_finalize (GObject *object);
426 static void empathy_audio_src_handle_message (GstBin *bin,
427   GstMessage *message);
428
429 static gboolean empathy_audio_src_levels_updated (gpointer user_data);
430
431 static void
432 empathy_audio_src_set_property (GObject *object,
433   guint property_id, const GValue *value, GParamSpec *pspec)
434 {
435   switch (property_id)
436     {
437       case PROP_VOLUME:
438         empathy_audio_src_set_volume (EMPATHY_GST_AUDIO_SRC (object),
439           g_value_get_double (value));
440         break;
441       default:
442         G_OBJECT_WARN_INVALID_PROPERTY_ID (object, property_id, pspec);
443     }
444 }
445
446 static void
447 empathy_audio_src_get_property (GObject *object,
448   guint property_id, GValue *value, GParamSpec *pspec)
449 {
450   EmpathyGstAudioSrc *self = EMPATHY_GST_AUDIO_SRC (object);
451   EmpathyGstAudioSrcPrivate *priv = EMPATHY_GST_AUDIO_SRC_GET_PRIVATE (self);
452
453   switch (property_id)
454     {
455       case PROP_VOLUME:
456         g_value_set_double (value,
457           empathy_audio_src_get_volume (self));
458         break;
459       case PROP_PEAK_LEVEL:
460         g_mutex_lock (priv->lock);
461         g_value_set_double (value, priv->peak_level);
462         g_mutex_unlock (priv->lock);
463         break;
464       case PROP_RMS_LEVEL:
465         g_mutex_lock (priv->lock);
466         g_value_set_double (value, priv->rms_level);
467         g_mutex_unlock (priv->lock);
468         break;
469       case PROP_MICROPHONE:
470         g_value_set_uint (value, priv->source_idx);
471         break;
472       default:
473         G_OBJECT_WARN_INVALID_PROPERTY_ID (object, property_id, pspec);
474     }
475 }
476
477 static void
478 empathy_audio_src_class_init (EmpathyGstAudioSrcClass
479   *empathy_audio_src_class)
480 {
481   GObjectClass *object_class = G_OBJECT_CLASS (empathy_audio_src_class);
482   GstBinClass *gstbin_class = GST_BIN_CLASS (empathy_audio_src_class);
483   GParamSpec *param_spec;
484
485   g_type_class_add_private (empathy_audio_src_class,
486     sizeof (EmpathyGstAudioSrcPrivate));
487
488   object_class->dispose = empathy_audio_src_dispose;
489   object_class->finalize = empathy_audio_src_finalize;
490
491   object_class->set_property = empathy_audio_src_set_property;
492   object_class->get_property = empathy_audio_src_get_property;
493
494   gstbin_class->handle_message =
495     GST_DEBUG_FUNCPTR (empathy_audio_src_handle_message);
496
497   param_spec = g_param_spec_double ("volume", "Volume", "volume contol",
498     0.0, 5.0, 1.0,
499     G_PARAM_READWRITE | G_PARAM_STATIC_STRINGS);
500   g_object_class_install_property (object_class, PROP_VOLUME, param_spec);
501
502   param_spec = g_param_spec_double ("peak-level", "peak level", "peak level",
503     -G_MAXDOUBLE, G_MAXDOUBLE, 0,
504     G_PARAM_READABLE | G_PARAM_STATIC_STRINGS);
505   g_object_class_install_property (object_class, PROP_PEAK_LEVEL, param_spec);
506
507   param_spec = g_param_spec_uint ("microphone", "microphone", "microphone",
508     0, G_MAXUINT, G_MAXUINT,
509     G_PARAM_READABLE | G_PARAM_STATIC_STRINGS);
510   g_object_class_install_property (object_class, PROP_MICROPHONE, param_spec);
511
512   signals[PEAK_LEVEL_CHANGED] = g_signal_new ("peak-level-changed",
513     G_TYPE_FROM_CLASS (empathy_audio_src_class),
514     G_SIGNAL_RUN_LAST,
515     0,
516     NULL, NULL,
517     g_cclosure_marshal_VOID__DOUBLE,
518     G_TYPE_NONE, 1, G_TYPE_DOUBLE);
519
520   param_spec = g_param_spec_double ("rms-level", "RMS level", "RMS level",
521     -G_MAXDOUBLE, G_MAXDOUBLE, 0,
522     G_PARAM_READABLE | G_PARAM_STATIC_STRINGS);
523   g_object_class_install_property (object_class, PROP_RMS_LEVEL, param_spec);
524
525
526   signals[RMS_LEVEL_CHANGED] = g_signal_new ("rms-level-changed",
527     G_TYPE_FROM_CLASS (empathy_audio_src_class),
528     G_SIGNAL_RUN_LAST,
529     0,
530     NULL, NULL,
531     g_cclosure_marshal_VOID__DOUBLE,
532     G_TYPE_NONE, 1, G_TYPE_DOUBLE);
533
534   signals[MICROPHONE_ADDED] = g_signal_new ("microphone-added",
535     G_TYPE_FROM_CLASS (empathy_audio_src_class),
536     G_SIGNAL_RUN_LAST,
537     0,
538     NULL, NULL,
539     _src_marshal_VOID__UINT_STRING_STRING_BOOLEAN,
540     G_TYPE_NONE, 4, G_TYPE_UINT, G_TYPE_STRING, G_TYPE_STRING, G_TYPE_BOOLEAN);
541
542   signals[MICROPHONE_REMOVED] = g_signal_new ("microphone-removed",
543     G_TYPE_FROM_CLASS (empathy_audio_src_class),
544     G_SIGNAL_RUN_LAST,
545     0,
546     NULL, NULL,
547     g_cclosure_marshal_VOID__UINT,
548     G_TYPE_NONE, 1, G_TYPE_UINT);
549 }
550
551 void
552 empathy_audio_src_dispose (GObject *object)
553 {
554   EmpathyGstAudioSrc *self = EMPATHY_GST_AUDIO_SRC (object);
555   EmpathyGstAudioSrcPrivate *priv = EMPATHY_GST_AUDIO_SRC_GET_PRIVATE (self);
556
557   if (priv->dispose_has_run)
558     return;
559
560   priv->dispose_has_run = TRUE;
561
562   if (priv->idle_id != 0)
563     g_source_remove (priv->idle_id);
564
565   priv->idle_id = 0;
566
567   if (priv->context != NULL)
568     pa_context_unref (priv->context);
569   priv->context = NULL;
570
571   if (priv->loop != NULL)
572     pa_glib_mainloop_free (priv->loop);
573   priv->loop = NULL;
574
575   /* release any references held by the object here */
576
577   if (G_OBJECT_CLASS (empathy_audio_src_parent_class)->dispose)
578     G_OBJECT_CLASS (empathy_audio_src_parent_class)->dispose (object);
579 }
580
581 void
582 empathy_audio_src_finalize (GObject *object)
583 {
584   EmpathyGstAudioSrc *self = EMPATHY_GST_AUDIO_SRC (object);
585   EmpathyGstAudioSrcPrivate *priv = EMPATHY_GST_AUDIO_SRC_GET_PRIVATE (self);
586
587   /* free any data held directly by the object here */
588   g_mutex_free (priv->lock);
589
590   g_queue_foreach (priv->operations, (GFunc) operation_free,
591       GUINT_TO_POINTER (TRUE));
592   g_queue_free (priv->operations);
593
594   G_OBJECT_CLASS (empathy_audio_src_parent_class)->finalize (object);
595 }
596
597 static gboolean
598 empathy_audio_src_levels_updated (gpointer user_data)
599 {
600   EmpathyGstAudioSrc *self = EMPATHY_GST_AUDIO_SRC (user_data);
601   EmpathyGstAudioSrcPrivate *priv = EMPATHY_GST_AUDIO_SRC_GET_PRIVATE (self);
602
603   g_mutex_lock (priv->lock);
604
605   g_signal_emit (self, signals[PEAK_LEVEL_CHANGED], 0, priv->peak_level);
606   g_signal_emit (self, signals[RMS_LEVEL_CHANGED], 0, priv->rms_level);
607   priv->idle_id = 0;
608
609   g_mutex_unlock (priv->lock);
610
611   return FALSE;
612 }
613
614 static void
615 empathy_audio_src_handle_message (GstBin *bin, GstMessage *message)
616 {
617   EmpathyGstAudioSrc *self = EMPATHY_GST_AUDIO_SRC (bin);
618   EmpathyGstAudioSrcPrivate *priv = EMPATHY_GST_AUDIO_SRC_GET_PRIVATE (self);
619
620   if  (GST_MESSAGE_TYPE (message) == GST_MESSAGE_ELEMENT &&
621         GST_MESSAGE_SRC (message) == GST_OBJECT (priv->level))
622     {
623       const GstStructure *s;
624       const gchar *name;
625       const GValue *list;
626       guint i, len;
627       gdouble peak = -G_MAXDOUBLE;
628       gdouble rms = -G_MAXDOUBLE;
629
630       s = gst_message_get_structure (message);
631       name = gst_structure_get_name (s);
632
633       if (g_strcmp0 ("level", name) != 0)
634         goto out;
635
636       list = gst_structure_get_value (s, "peak");
637       len = gst_value_list_get_size (list);
638
639       for (i =0 ; i < len; i++)
640         {
641           const GValue *value;
642           gdouble db;
643
644           value = gst_value_list_get_value (list, i);
645           db = g_value_get_double (value);
646           peak = MAX (db, peak);
647         }
648
649       list = gst_structure_get_value (s, "rms");
650       len = gst_value_list_get_size (list);
651
652       for (i =0 ; i < len; i++)
653         {
654           const GValue *value;
655           gdouble db;
656
657           value = gst_value_list_get_value (list, i);
658           db = g_value_get_double (value);
659           rms = MAX (db, rms);
660         }
661
662       g_mutex_lock (priv->lock);
663
664       priv->peak_level = peak;
665       priv->rms_level = rms;
666       if (priv->idle_id == 0)
667         priv->idle_id = g_idle_add (empathy_audio_src_levels_updated, self);
668
669       g_mutex_unlock (priv->lock);
670     }
671
672 out:
673    GST_BIN_CLASS (empathy_audio_src_parent_class)->handle_message (bin,
674     message);
675 }
676
677 GstElement *
678 empathy_audio_src_new (void)
679 {
680   static gboolean registered = FALSE;
681
682   if (!registered) {
683     if (!gst_element_register (NULL, "empathyaudiosrc",
684             GST_RANK_NONE, EMPATHY_TYPE_GST_AUDIO_SRC))
685       return NULL;
686     registered = TRUE;
687   }
688   return gst_element_factory_make ("empathyaudiosrc", NULL);
689 }
690
691 void
692 empathy_audio_src_set_volume (EmpathyGstAudioSrc *src, gdouble volume)
693 {
694   EmpathyGstAudioSrcPrivate *priv = EMPATHY_GST_AUDIO_SRC_GET_PRIVATE (src);
695   GParamSpec *pspec;
696   GParamSpecDouble *pspec_double;
697
698   pspec = g_object_class_find_property (G_OBJECT_GET_CLASS (priv->volume),
699     "volume");
700
701   g_assert (pspec != NULL);
702
703   pspec_double = G_PARAM_SPEC_DOUBLE (pspec);
704
705   volume = CLAMP (volume, pspec_double->minimum, pspec_double->maximum);
706
707   g_object_set (G_OBJECT (priv->volume), "volume", volume, NULL);
708 }
709
710 gdouble
711 empathy_audio_src_get_volume (EmpathyGstAudioSrc *src)
712 {
713   EmpathyGstAudioSrcPrivate *priv = EMPATHY_GST_AUDIO_SRC_GET_PRIVATE (src);
714   gdouble volume;
715
716   g_object_get (G_OBJECT (priv->volume), "volume", &volume, NULL);
717
718   return volume;
719 }
720
721 void
722 empathy_audio_src_get_microphones_async (EmpathyGstAudioSrc *src,
723     GAsyncReadyCallback callback,
724     gpointer user_data)
725 {
726   EmpathyGstAudioSrcPrivate *priv = EMPATHY_GST_AUDIO_SRC_GET_PRIVATE (src);
727   Operation *operation;
728   GSimpleAsyncResult *simple;
729
730   simple = g_simple_async_result_new (G_OBJECT (src), callback, user_data,
731       empathy_audio_src_get_microphones_async);
732
733   operation = operation_new (operation_get_microphones, simple);
734   g_queue_push_tail (priv->operations, operation);
735
736   /* gogogogo */
737   operations_run (src);
738 }
739
740 const GList *
741 empathy_audio_src_get_microphones_finish (EmpathyGstAudioSrc *src,
742     GAsyncResult *result,
743     GError **error)
744 {
745   GSimpleAsyncResult *simple = G_SIMPLE_ASYNC_RESULT (result);
746   GQueue *queue;
747
748   if (g_simple_async_result_propagate_error (simple, error))
749       return NULL;
750
751   g_return_val_if_fail (g_simple_async_result_is_valid (result,
752           G_OBJECT (src), empathy_audio_src_get_microphones_async),
753       NULL);
754
755   queue = g_simple_async_result_get_op_res_gpointer (simple);
756   return queue->head;
757 }
758
759 guint
760 empathy_audio_src_get_microphone (EmpathyGstAudioSrc *src)
761 {
762   EmpathyGstAudioSrcPrivate *priv = EMPATHY_GST_AUDIO_SRC_GET_PRIVATE (src);
763
764   return priv->source_idx;
765 }
766
767 void
768 empathy_audio_src_change_microphone_async (EmpathyGstAudioSrc *src,
769     guint microphone,
770     GAsyncReadyCallback callback,
771     gpointer user_data)
772 {
773   EmpathyGstAudioSrcPrivate *priv = EMPATHY_GST_AUDIO_SRC_GET_PRIVATE (src);
774   guint source_output_idx;
775   GSimpleAsyncResult *simple;
776   Operation *operation;
777
778   simple = g_simple_async_result_new (G_OBJECT (src), callback, user_data,
779       empathy_audio_src_change_microphone_async);
780
781   g_object_get (priv->src, "source-output-index", &source_output_idx, NULL);
782
783   if (source_output_idx == PA_INVALID_INDEX)
784     {
785       g_simple_async_result_set_error (simple, G_IO_ERROR, G_IO_ERROR_FAILED,
786           "pulsesrc is not yet PLAYING");
787       g_simple_async_result_complete_in_idle (simple);
788       g_object_unref (simple);
789       return;
790     }
791
792   g_simple_async_result_set_op_res_gpointer (simple,
793       GUINT_TO_POINTER (microphone), NULL);
794
795   operation = operation_new (operation_change_microphone, simple);
796   g_queue_push_tail (priv->operations, operation);
797
798   /* gogogogo */
799   operations_run (src);
800 }
801
802 gboolean
803 empathy_audio_src_change_microphone_finish (EmpathyGstAudioSrc *src,
804     GAsyncResult *result,
805     GError **error)
806 {
807   empathy_implement_finish_void (src,
808       empathy_audio_src_change_microphone_async);
809 }