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