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