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