From cd40005a5e9608fa8e1295b8606788af38100dba Mon Sep 17 00:00:00 2001 From: Jonny Lamb Date: Wed, 17 Aug 2011 15:55:32 +0100 Subject: mic-monitor: move the PA-specific stuff from audio-src to here Sorry for another big commit. Signed-off-by: Jonny Lamb --- src/Makefile.am | 6 +- src/empathy-audio-src.c | 396 +++++--------------------------- src/empathy-audio-src.h | 13 -- src/empathy-mic-menu.c | 27 ++- src/empathy-mic-monitor.c | 566 ++++++++++++++++++++++++++++++++++++++++++++++ src/empathy-mic-monitor.h | 80 +++++++ 6 files changed, 724 insertions(+), 364 deletions(-) create mode 100644 src/empathy-mic-monitor.c create mode 100644 src/empathy-mic-monitor.h (limited to 'src') diff --git a/src/Makefile.am b/src/Makefile.am index 0232ffe0a..1f80fe9a3 100644 --- a/src/Makefile.am +++ b/src/Makefile.am @@ -123,6 +123,8 @@ empathy_av_SOURCES = \ empathy-video-widget.h \ ev-sidebar.c \ ev-sidebar.h \ + empathy-mic-monitor.c \ + empathy-mic-monitor.h $(NULL) nodist_empathy_av_SOURCES = $(BUILT_SOURCES) @@ -176,7 +178,9 @@ empathy_call_SOURCES = \ empathy-mic-menu.c \ empathy-mic-menu.h \ empathy-rounded-actor.c \ - empathy-rounded-actor.h + empathy-rounded-actor.h \ + empathy-mic-monitor.c \ + empathy-mic-monitor.h nodist_empathy_call_SOURCES = $(BUILT_SOURCES) diff --git a/src/empathy-audio-src.c b/src/empathy-audio-src.c index 8f7a0599f..0b19b868a 100644 --- a/src/empathy-audio-src.c +++ b/src/empathy-audio-src.c @@ -22,15 +22,13 @@ #include #include -#include -#include - #include #include #include "empathy-audio-src.h" #include "src-marshal.h" +#include "empathy-mic-monitor.h" #define DEBUG_FLAG EMPATHY_DEBUG_VOIP #include @@ -42,8 +40,6 @@ enum { PEAK_LEVEL_CHANGED, RMS_LEVEL_CHANGED, - MICROPHONE_ADDED, - MICROPHONE_REMOVED, LAST_SIGNAL }; @@ -66,9 +62,7 @@ struct _EmpathyGstAudioSrcPrivate GstElement *volume; GstElement *level; - pa_glib_mainloop *loop; - pa_context *context; - GQueue *operations; + EmpathyMicMonitor *mic_monitor; /* 0 if not known yet */ guint source_output_idx; @@ -98,261 +92,54 @@ empathy_audio_src_supports_changing_mic (EmpathyGstAudioSrc *self) "source-output-index") != NULL); } -typedef void (*OperationFunc) (EmpathyGstAudioSrc *, GSimpleAsyncResult *); - -typedef struct -{ - OperationFunc func; - GSimpleAsyncResult *result; -} Operation; - -static Operation * -operation_new (OperationFunc func, - GSimpleAsyncResult *result) -{ - Operation *o = g_slice_new0 (Operation); - - o->func = func; - o->result = result; - - return o; -} - -static void -operation_free (Operation *o, - gboolean cancelled) -{ - if (cancelled) - { - g_simple_async_result_set_error (o->result, - G_IO_ERROR, G_IO_ERROR_CANCELLED, - "The audio source was disposed"); - g_simple_async_result_complete (o->result); - g_object_unref (o->result); - } - - g_slice_free (Operation, o); -} - static void -operation_get_microphones_free (gpointer data) -{ - GQueue *queue = data; - GList *l; - - for (l = queue->head; l != NULL; l = l->next) - { - EmpathyAudioSrcMicrophone *mic = l->data; - - g_free (mic->name); - g_free (mic->description); - g_slice_free (EmpathyAudioSrcMicrophone, mic); - } - - g_queue_free (queue); -} - -static void -operation_get_microphones_cb (pa_context *context, - const pa_source_info *info, - int eol, - void *userdata) -{ - GSimpleAsyncResult *result = userdata; - EmpathyAudioSrcMicrophone *mic; - GQueue *queue; - - if (eol) - { - g_simple_async_result_complete (result); - g_object_unref (result); - return; - } - - mic = g_slice_new0 (EmpathyAudioSrcMicrophone); - mic->index = info->index; - mic->name = g_strdup (info->name); - mic->description = g_strdup (info->description); - mic->is_monitor = (info->monitor_of_sink != PA_INVALID_INDEX); - - /* add it to the queue */ - queue = g_simple_async_result_get_op_res_gpointer (result); - g_queue_push_tail (queue, mic); -} - -static void -operation_get_microphones (EmpathyGstAudioSrc *self, - GSimpleAsyncResult *result) -{ - EmpathyGstAudioSrcPrivate *priv = EMPATHY_GST_AUDIO_SRC_GET_PRIVATE (self); - - g_assert_cmpuint (pa_context_get_state (priv->context), ==, PA_CONTEXT_READY); - - g_simple_async_result_set_op_res_gpointer (result, g_queue_new (), - operation_get_microphones_free); - - pa_context_get_source_info_list (priv->context, - operation_get_microphones_cb, result); -} - -static void -operation_change_microphone_cb (pa_context *context, - int success, - void *userdata) -{ - GSimpleAsyncResult *result = userdata; - - if (!success) - { - g_simple_async_result_set_error (result, G_IO_ERROR, G_IO_ERROR_FAILED, - "Failed to change microphone. Reason unknown."); - } - - g_simple_async_result_complete (result); - g_object_unref (result); -} - -static void -operation_change_microphone (EmpathyGstAudioSrc *self, - GSimpleAsyncResult *result) -{ - EmpathyGstAudioSrcPrivate *priv = EMPATHY_GST_AUDIO_SRC_GET_PRIVATE (self); - guint source_output_idx, microphone; - - g_object_get (priv->src, "source-output-index", &source_output_idx, NULL); - - g_assert_cmpuint (pa_context_get_state (priv->context), ==, PA_CONTEXT_READY); - g_assert_cmpuint (source_output_idx, !=, PA_INVALID_INDEX); - - microphone = GPOINTER_TO_UINT ( - g_simple_async_result_get_op_res_gpointer (result)); - - pa_context_move_source_output_by_index (priv->context, source_output_idx, microphone, - operation_change_microphone_cb, result); -} - -static void -operations_run (EmpathyGstAudioSrc *self) +empathy_audio_src_microphone_changed_cb (EmpathyMicMonitor *monitor, + guint source_output_idx, + guint source_idx, + gpointer user_data) { + EmpathyGstAudioSrc *self = user_data; EmpathyGstAudioSrcPrivate *priv = EMPATHY_GST_AUDIO_SRC_GET_PRIVATE (self); - pa_context_state_t state = pa_context_get_state (priv->context); - GList *l; - - if (state != PA_CONTEXT_READY) - return; - - for (l = priv->operations->head; l != NULL; l = l->next) - { - Operation *o = l->data; + guint audio_src_idx = PA_INVALID_INDEX; - o->func (self, o->result); - - operation_free (o, FALSE); - } + g_object_get (priv->src, "source-output-index", &audio_src_idx, NULL); - g_queue_clear (priv->operations); -} - -static void -empathy_audio_src_source_output_info_cb (pa_context *context, - const pa_source_output_info *info, - int eol, - void *userdata) -{ - EmpathyGstAudioSrc *self = userdata; - EmpathyGstAudioSrcPrivate *priv = EMPATHY_GST_AUDIO_SRC_GET_PRIVATE (self); - - if (eol) + if (source_output_idx == PA_INVALID_INDEX + || source_output_idx != audio_src_idx) return; - /* There should only be one call here. */ - - if (priv->source_idx == info->source) + if (priv->source_idx == source_idx) return; - priv->source_idx = info->source; + priv->source_idx = source_idx; g_object_notify (G_OBJECT (self), "microphone"); } static void -empathy_audio_src_source_info_cb (pa_context *context, - const pa_source_info *info, - int eol, - void *userdata) -{ - EmpathyGstAudioSrc *self = userdata; - gboolean is_monitor; - - if (eol) - return; - - is_monitor = (info->monitor_of_sink != PA_INVALID_INDEX); - - g_signal_emit (self, signals[MICROPHONE_ADDED], 0, - info->index, info->name, info->description, is_monitor); -} - -static void -empathy_audio_src_pa_event_cb (pa_context *context, - pa_subscription_event_type_t type, - uint32_t idx, - void *userdata) +empathy_audio_src_get_current_mic_cb (GObject *source_object, + GAsyncResult *result, + gpointer user_data) { - EmpathyGstAudioSrc *self = userdata; + EmpathyMicMonitor *monitor = EMPATHY_MIC_MONITOR (source_object); + EmpathyGstAudioSrc *self = user_data; EmpathyGstAudioSrcPrivate *priv = EMPATHY_GST_AUDIO_SRC_GET_PRIVATE (self); + guint source_idx; + GError *error = NULL; - if ((type & PA_SUBSCRIPTION_EVENT_FACILITY_MASK) == PA_SUBSCRIPTION_EVENT_SOURCE_OUTPUT - && (type & PA_SUBSCRIPTION_EVENT_TYPE_MASK) == PA_SUBSCRIPTION_EVENT_CHANGE - && idx == priv->source_output_idx) - { - /* Microphone in the source output has changed */ - pa_context_get_source_output_info (context, idx, - empathy_audio_src_source_output_info_cb, self); - } - else if ((type & PA_SUBSCRIPTION_EVENT_FACILITY_MASK) == PA_SUBSCRIPTION_EVENT_SOURCE - && (type & PA_SUBSCRIPTION_EVENT_TYPE_MASK) == PA_SUBSCRIPTION_EVENT_REMOVE) - { - /* A mic has been removed */ - g_signal_emit (self, signals[MICROPHONE_REMOVED], 0, idx); - } - else if ((type & PA_SUBSCRIPTION_EVENT_FACILITY_MASK) == PA_SUBSCRIPTION_EVENT_SOURCE - && (type & PA_SUBSCRIPTION_EVENT_TYPE_MASK) == PA_SUBSCRIPTION_EVENT_NEW) + source_idx = empathy_mic_monitor_get_current_mic_finish (monitor, result, &error); + + if (error != NULL) { - /* A mic has been plugged in */ - pa_context_get_source_info_by_index (context, idx, - empathy_audio_src_source_info_cb, self); + DEBUG ("Failed to get current mic: %s", error->message); + g_clear_error (&error); + return; } -} - -static void -empathy_audio_src_pa_subscribe_cb (pa_context *context, - int success, - void *userdata) -{ - if (!success) - DEBUG ("Failed to subscribe to PulseAudio events"); -} -static void -empathy_audio_src_pa_state_change_cb (pa_context *context, - void *userdata) -{ - EmpathyGstAudioSrc *self = userdata; - EmpathyGstAudioSrcPrivate *priv = EMPATHY_GST_AUDIO_SRC_GET_PRIVATE (self); - pa_context_state_t state = pa_context_get_state (priv->context); + if (priv->source_idx == source_idx) + return; - if (state == PA_CONTEXT_READY) - { - /* Listen to pulseaudio events so we know when sources are - * added and when the microphone is changed. */ - pa_context_set_subscribe_callback (priv->context, - empathy_audio_src_pa_event_cb, self); - pa_context_subscribe (priv->context, - PA_SUBSCRIPTION_MASK_SOURCE | PA_SUBSCRIPTION_MASK_SOURCE_OUTPUT, - empathy_audio_src_pa_subscribe_cb, NULL); - - operations_run (self); - } + priv->source_idx = source_idx; + g_object_notify (G_OBJECT (self), "microphone"); } static void @@ -374,8 +161,8 @@ empathy_audio_src_source_output_index_notify (GObject *object, /* It's actually changed. */ priv->source_output_idx = source_output_idx; - pa_context_get_source_output_info (priv->context, source_output_idx, - empathy_audio_src_source_output_info_cb, self); + empathy_mic_monitor_get_current_mic_async (priv->mic_monitor, + source_output_idx, empathy_audio_src_get_current_mic_cb, self); } static GstElement * @@ -442,12 +229,6 @@ empathy_audio_src_init (EmpathyGstAudioSrc *obj) gst_object_unref (G_OBJECT (src)); - /* PulseAudio stuff: We need to create a dummy pa_glib_mainloop* so - * Pulse can use the mainloop that GTK has created for us. */ - priv->loop = pa_glib_mainloop_new (NULL); - priv->context = pa_context_new (pa_glib_mainloop_get_api (priv->loop), - "EmpathyAudioSrc"); - /* Listen to changes to GstPulseSrc:source-output-index so we know when * it's no longer PA_INVALID_INDEX (starting for the first time) or if it * changes (READY->NULL->READY...) */ @@ -455,13 +236,11 @@ empathy_audio_src_init (EmpathyGstAudioSrc *obj) G_CALLBACK (empathy_audio_src_source_output_index_notify), obj); - /* Finally listen for state changes so we know when we've - * connected. */ - pa_context_set_state_callback (priv->context, - empathy_audio_src_pa_state_change_cb, obj); - pa_context_connect (priv->context, NULL, 0, NULL); + priv->mic_monitor = empathy_mic_monitor_new (); + g_signal_connect (priv->mic_monitor, "microphone-changed", + G_CALLBACK (empathy_audio_src_microphone_changed_cb), obj); - priv->operations = g_queue_new (); + priv->source_idx = PA_INVALID_INDEX; } static void empathy_audio_src_dispose (GObject *object); @@ -565,7 +344,6 @@ empathy_audio_src_class_init (EmpathyGstAudioSrcClass G_PARAM_READABLE | G_PARAM_STATIC_STRINGS); g_object_class_install_property (object_class, PROP_RMS_LEVEL, param_spec); - signals[RMS_LEVEL_CHANGED] = g_signal_new ("rms-level-changed", G_TYPE_FROM_CLASS (empathy_audio_src_class), G_SIGNAL_RUN_LAST, @@ -573,22 +351,6 @@ empathy_audio_src_class_init (EmpathyGstAudioSrcClass NULL, NULL, g_cclosure_marshal_VOID__DOUBLE, G_TYPE_NONE, 1, G_TYPE_DOUBLE); - - signals[MICROPHONE_ADDED] = g_signal_new ("microphone-added", - G_TYPE_FROM_CLASS (empathy_audio_src_class), - G_SIGNAL_RUN_LAST, - 0, - NULL, NULL, - _src_marshal_VOID__UINT_STRING_STRING_BOOLEAN, - G_TYPE_NONE, 4, G_TYPE_UINT, G_TYPE_STRING, G_TYPE_STRING, G_TYPE_BOOLEAN); - - signals[MICROPHONE_REMOVED] = g_signal_new ("microphone-removed", - G_TYPE_FROM_CLASS (empathy_audio_src_class), - G_SIGNAL_RUN_LAST, - 0, - NULL, NULL, - g_cclosure_marshal_VOID__UINT, - G_TYPE_NONE, 1, G_TYPE_UINT); } void @@ -607,13 +369,7 @@ empathy_audio_src_dispose (GObject *object) priv->idle_id = 0; - if (priv->context != NULL) - pa_context_unref (priv->context); - priv->context = NULL; - - if (priv->loop != NULL) - pa_glib_mainloop_free (priv->loop); - priv->loop = NULL; + tp_clear_object (&priv->mic_monitor); /* release any references held by the object here */ @@ -630,10 +386,6 @@ empathy_audio_src_finalize (GObject *object) /* free any data held directly by the object here */ g_mutex_free (priv->lock); - g_queue_foreach (priv->operations, (GFunc) operation_free, - GUINT_TO_POINTER (TRUE)); - g_queue_free (priv->operations); - G_OBJECT_CLASS (empathy_audio_src_parent_class)->finalize (object); } @@ -761,61 +513,31 @@ empathy_audio_src_get_volume (EmpathyGstAudioSrc *src) return volume; } -void -empathy_audio_src_get_microphones_async (EmpathyGstAudioSrc *src, - GAsyncReadyCallback callback, - gpointer user_data) +guint +empathy_audio_src_get_microphone (EmpathyGstAudioSrc *src) { EmpathyGstAudioSrcPrivate *priv = EMPATHY_GST_AUDIO_SRC_GET_PRIVATE (src); - Operation *operation; - GSimpleAsyncResult *simple; - - simple = g_simple_async_result_new (G_OBJECT (src), callback, user_data, - empathy_audio_src_get_microphones_async); - - /* If we can't change mic let's not pretend we can by returning the - * list of available mics. */ - if (!empathy_audio_src_supports_changing_mic (src)) - { - g_simple_async_result_set_error (simple, G_IO_ERROR, G_IO_ERROR_FAILED, - "pulsesrc is not new enough to support changing microphone"); - g_simple_async_result_complete_in_idle (simple); - g_object_unref (simple); - return; - } - operation = operation_new (operation_get_microphones, simple); - g_queue_push_tail (priv->operations, operation); - - /* gogogogo */ - operations_run (src); + return priv->source_idx; } -const GList * -empathy_audio_src_get_microphones_finish (EmpathyGstAudioSrc *src, +static void +empathy_audio_src_change_microphone_cb (GObject *source_object, GAsyncResult *result, - GError **error) + gpointer user_data) { - GSimpleAsyncResult *simple = G_SIMPLE_ASYNC_RESULT (result); - GQueue *queue; - - if (g_simple_async_result_propagate_error (simple, error)) - return NULL; - - g_return_val_if_fail (g_simple_async_result_is_valid (result, - G_OBJECT (src), empathy_audio_src_get_microphones_async), - NULL); - - queue = g_simple_async_result_get_op_res_gpointer (simple); - return queue->head; -} + EmpathyMicMonitor *monitor = EMPATHY_MIC_MONITOR (source_object); + GSimpleAsyncResult *simple = user_data; + GError *error = NULL; -guint -empathy_audio_src_get_microphone (EmpathyGstAudioSrc *src) -{ - EmpathyGstAudioSrcPrivate *priv = EMPATHY_GST_AUDIO_SRC_GET_PRIVATE (src); + if (!empathy_mic_monitor_change_microphone_finish (monitor, + result, &error)) + { + g_simple_async_result_take_error (simple, error); + } - return priv->source_idx; + g_simple_async_result_complete (simple); + g_object_unref (simple); } void @@ -827,7 +549,6 @@ empathy_audio_src_change_microphone_async (EmpathyGstAudioSrc *src, EmpathyGstAudioSrcPrivate *priv = EMPATHY_GST_AUDIO_SRC_GET_PRIVATE (src); guint source_output_idx; GSimpleAsyncResult *simple; - Operation *operation; simple = g_simple_async_result_new (G_OBJECT (src), callback, user_data, empathy_audio_src_change_microphone_async); @@ -852,14 +573,9 @@ empathy_audio_src_change_microphone_async (EmpathyGstAudioSrc *src, return; } - g_simple_async_result_set_op_res_gpointer (simple, - GUINT_TO_POINTER (microphone), NULL); - - operation = operation_new (operation_change_microphone, simple); - g_queue_push_tail (priv->operations, operation); - - /* gogogogo */ - operations_run (src); + empathy_mic_monitor_change_microphone_async (priv->mic_monitor, + source_output_idx, microphone, empathy_audio_src_change_microphone_cb, + simple); } gboolean diff --git a/src/empathy-audio-src.h b/src/empathy-audio-src.h index 05e3c46cc..5eb0d1534 100644 --- a/src/empathy-audio-src.h +++ b/src/empathy-audio-src.h @@ -62,19 +62,6 @@ GstElement *empathy_audio_src_new (void); void empathy_audio_src_set_volume (EmpathyGstAudioSrc *src, gdouble volume); gdouble empathy_audio_src_get_volume (EmpathyGstAudioSrc *src); -typedef struct -{ - guint index; - gchar *name; - gchar *description; - gboolean is_monitor; -} EmpathyAudioSrcMicrophone; - -void empathy_audio_src_get_microphones_async (EmpathyGstAudioSrc *src, - GAsyncReadyCallback callback, gpointer user_data); -const GList * empathy_audio_src_get_microphones_finish (EmpathyGstAudioSrc *src, - GAsyncResult *result, GError **error); - guint empathy_audio_src_get_microphone (EmpathyGstAudioSrc *src); void empathy_audio_src_change_microphone_async (EmpathyGstAudioSrc *src, diff --git a/src/empathy-mic-menu.c b/src/empathy-mic-menu.c index dc3d0f006..7f5cd2380 100644 --- a/src/empathy-mic-menu.c +++ b/src/empathy-mic-menu.c @@ -24,6 +24,7 @@ #include #include "empathy-mic-menu.h" +#include "empathy-mic-monitor.h" #define DEBUG_FLAG EMPATHY_DEBUG_VOIP #include @@ -50,6 +51,8 @@ struct _EmpathyMicMenuPrivate /* Queue of GtkRadioActions. */ GQueue *microphones; + + EmpathyMicMonitor *mic_monitor; }; G_DEFINE_TYPE (EmpathyMicMenu, empathy_mic_menu, G_TYPE_OBJECT); @@ -263,7 +266,7 @@ empathy_mic_menu_notify_microphone_cb (EmpathyGstAudioSrc *audio, } static void -empathy_mic_menu_microphone_added_cb (EmpathyGstAudioSrc *audio, +empathy_mic_menu_microphone_added_cb (EmpathyMicMonitor *monitor, guint source_idx, const gchar *name, const gchar *description, @@ -277,7 +280,7 @@ empathy_mic_menu_microphone_added_cb (EmpathyGstAudioSrc *audio, } static void -empathy_mic_menu_microphone_removed_cb (EmpathyGstAudioSrc *audio, +empathy_mic_menu_microphone_removed_cb (EmpathyMicMonitor *monitor, guint source_idx, EmpathyMicMenu *self) { @@ -309,16 +312,16 @@ empathy_mic_menu_microphone_removed_cb (EmpathyGstAudioSrc *audio, } static void -empathy_mic_menu_get_microphones_cb (GObject *source_object, +empathy_mic_menu_list_microphones_cb (GObject *source_object, GAsyncResult *result, gpointer user_data) { - EmpathyGstAudioSrc *audio = EMPATHY_GST_AUDIO_SRC (source_object); + EmpathyMicMonitor *monitor = EMPATHY_MIC_MONITOR (source_object); EmpathyMicMenu *self = user_data; GError *error = NULL; const GList *mics = NULL; - mics = empathy_audio_src_get_microphones_finish (audio, result, &error); + mics = empathy_mic_monitor_list_microphones_finish (monitor, result, &error); if (error != NULL) { @@ -329,7 +332,7 @@ empathy_mic_menu_get_microphones_cb (GObject *source_object, for (; mics != NULL; mics = mics->next) { - EmpathyAudioSrcMicrophone *mic = mics->data; + EmpathyMicrophone *mic = mics->data; empathy_mic_menu_add_microphone (self, mic->name, mic->description, mic->index, mic->is_monitor); @@ -356,6 +359,8 @@ empathy_mic_menu_constructed (GObject *obj) /* Okay let's go go go. */ + priv->mic_monitor = empathy_mic_monitor_new (); + priv->action_group = gtk_action_group_new ("EmpathyMicMenu"); gtk_ui_manager_insert_action_group (ui_manager, priv->action_group, -1); /* the UI manager now owns this */ @@ -369,15 +374,15 @@ empathy_mic_menu_constructed (GObject *obj) tp_g_signal_connect_object (audio, "notify::microphone", G_CALLBACK (empathy_mic_menu_notify_microphone_cb), self, 0); - tp_g_signal_connect_object (audio, "microphone-added", + tp_g_signal_connect_object (priv->mic_monitor, "microphone-added", G_CALLBACK (empathy_mic_menu_microphone_added_cb), self, 0); - tp_g_signal_connect_object (audio, "microphone-removed", + tp_g_signal_connect_object (priv->mic_monitor, "microphone-removed", G_CALLBACK (empathy_mic_menu_microphone_removed_cb), self, 0); priv->microphones = g_queue_new (); - empathy_audio_src_get_microphones_async (audio, - empathy_mic_menu_get_microphones_cb, self); + empathy_mic_monitor_list_microphones_async (priv->mic_monitor, + empathy_mic_menu_list_microphones_cb, self); } static void @@ -390,6 +395,8 @@ empathy_mic_menu_dispose (GObject *obj) g_queue_free (priv->microphones); priv->microphones = NULL; + tp_clear_object (&priv->mic_monitor); + G_OBJECT_CLASS (empathy_mic_menu_parent_class)->dispose (obj); } diff --git a/src/empathy-mic-monitor.c b/src/empathy-mic-monitor.c new file mode 100644 index 000000000..5a90e1718 --- /dev/null +++ b/src/empathy-mic-monitor.c @@ -0,0 +1,566 @@ +/* + * Copyright (C) 2011 Collabora Ltd. + * + * This library is free software; you can redistribute it and/or + * modify it under the terms of the GNU Lesser General Public + * License as published by the Free Software Foundation; either + * version 2.1 of the License, or (at your option) any later version. + * + * This library is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public + * License along with this library; if not, write to the Free Software + * Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA + * + */ + +#include + +#include + +#include +#include + +#include "empathy-mic-monitor.h" + +#include "src-marshal.h" + +#include + +#define DEBUG_FLAG EMPATHY_DEBUG_VOIP +#include + +enum +{ + MICROPHONE_ADDED, + MICROPHONE_REMOVED, + MICROPHONE_CHANGED, + LAST_SIGNAL +}; + +static guint signals[LAST_SIGNAL] = {0}; + +struct _EmpathyMicMonitorPrivate +{ + pa_glib_mainloop *loop; + pa_context *context; + GQueue *operations; +}; + +G_DEFINE_TYPE (EmpathyMicMonitor, empathy_mic_monitor, G_TYPE_OBJECT); + +typedef void (*OperationFunc) (EmpathyMicMonitor *, GSimpleAsyncResult *); + +typedef struct +{ + OperationFunc func; + GSimpleAsyncResult *result; +} Operation; + +static Operation * +operation_new (OperationFunc func, + GSimpleAsyncResult *result) +{ + Operation *o = g_slice_new0 (Operation); + + o->func = func; + o->result = result; + + return o; +} + +static void +operation_free (Operation *o, + gboolean cancelled) +{ + if (cancelled) + { + g_simple_async_result_set_error (o->result, + G_IO_ERROR, G_IO_ERROR_CANCELLED, + "The microphone monitor was disposed"); + g_simple_async_result_complete (o->result); + g_object_unref (o->result); + } + + g_slice_free (Operation, o); +} + +static void +operations_run (EmpathyMicMonitor *self) +{ + EmpathyMicMonitorPrivate *priv = self->priv; + pa_context_state_t state = pa_context_get_state (priv->context); + GList *l; + + if (state != PA_CONTEXT_READY) + return; + + for (l = priv->operations->head; l != NULL; l = l->next) + { + Operation *o = l->data; + + o->func (self, o->result); + + operation_free (o, FALSE); + } + + g_queue_clear (priv->operations); +} + +static void +empathy_mic_monitor_source_output_info_cb (pa_context *context, + const pa_source_output_info *info, + int eol, + void *userdata) +{ + EmpathyMicMonitor *self = userdata; + + if (eol) + return; + + g_signal_emit (self, signals[MICROPHONE_CHANGED], 0, + info->index, info->source); +} + +static void +empathy_mic_monitor_source_info_cb (pa_context *context, + const pa_source_info *info, + int eol, + void *userdata) +{ + EmpathyMicMonitor *self = userdata; + gboolean is_monitor; + + if (eol) + return; + + is_monitor = (info->monitor_of_sink != PA_INVALID_INDEX); + + g_signal_emit (self, signals[MICROPHONE_ADDED], 0, + info->index, info->name, info->description, is_monitor); +} + +static void +empathy_mic_monitor_pa_event_cb (pa_context *context, + pa_subscription_event_type_t type, + uint32_t idx, + void *userdata) +{ + EmpathyMicMonitor *self = userdata; + + if ((type & PA_SUBSCRIPTION_EVENT_FACILITY_MASK) == PA_SUBSCRIPTION_EVENT_SOURCE_OUTPUT + && (type & PA_SUBSCRIPTION_EVENT_TYPE_MASK) == PA_SUBSCRIPTION_EVENT_CHANGE) + { + /* Microphone in the source output has changed */ + pa_context_get_source_output_info (context, idx, + empathy_mic_monitor_source_output_info_cb, self); + } + else if ((type & PA_SUBSCRIPTION_EVENT_FACILITY_MASK) == PA_SUBSCRIPTION_EVENT_SOURCE + && (type & PA_SUBSCRIPTION_EVENT_TYPE_MASK) == PA_SUBSCRIPTION_EVENT_REMOVE) + { + /* A mic has been removed */ + g_signal_emit (self, signals[MICROPHONE_REMOVED], 0, idx); + } + else if ((type & PA_SUBSCRIPTION_EVENT_FACILITY_MASK) == PA_SUBSCRIPTION_EVENT_SOURCE + && (type & PA_SUBSCRIPTION_EVENT_TYPE_MASK) == PA_SUBSCRIPTION_EVENT_NEW) + { + /* A mic has been plugged in */ + pa_context_get_source_info_by_index (context, idx, + empathy_mic_monitor_source_info_cb, self); + } +} + +static void +empathy_mic_monitor_pa_subscribe_cb (pa_context *context, + int success, + void *userdata) +{ + if (!success) + DEBUG ("Failed to subscribe to PulseAudio events"); +} + +static void +empathy_mic_monitor_pa_state_change_cb (pa_context *context, + void *userdata) +{ + EmpathyMicMonitor *self = userdata; + EmpathyMicMonitorPrivate *priv = self->priv; + pa_context_state_t state = pa_context_get_state (priv->context); + + if (state == PA_CONTEXT_READY) + { + /* Listen to pulseaudio events so we know when sources are + * added and when the microphone is changed. */ + pa_context_set_subscribe_callback (priv->context, + empathy_mic_monitor_pa_event_cb, self); + pa_context_subscribe (priv->context, + PA_SUBSCRIPTION_MASK_SOURCE | PA_SUBSCRIPTION_MASK_SOURCE_OUTPUT, + empathy_mic_monitor_pa_subscribe_cb, NULL); + + operations_run (self); + } +} + +static void +empathy_mic_monitor_init (EmpathyMicMonitor *self) +{ + EmpathyMicMonitorPrivate *priv = G_TYPE_INSTANCE_GET_PRIVATE (self, + EMPATHY_TYPE_MIC_MONITOR, EmpathyMicMonitorPrivate); + + self->priv = priv; +} + +static void +empathy_mic_monitor_constructed (GObject *obj) +{ + EmpathyMicMonitor *self = EMPATHY_MIC_MONITOR (obj); + EmpathyMicMonitorPrivate *priv = self->priv; + + /* PulseAudio stuff: We need to create a dummy pa_glib_mainloop* so + * Pulse can use the mainloop that GTK has created for us. */ + priv->loop = pa_glib_mainloop_new (NULL); + priv->context = pa_context_new (pa_glib_mainloop_get_api (priv->loop), + "EmpathyMicMonitor"); + + /* Finally listen for state changes so we know when we've + * connected. */ + pa_context_set_state_callback (priv->context, + empathy_mic_monitor_pa_state_change_cb, obj); + pa_context_connect (priv->context, NULL, 0, NULL); + + priv->operations = g_queue_new (); +} + +static void +empathy_mic_monitor_dispose (GObject *obj) +{ + EmpathyMicMonitor *self = EMPATHY_MIC_MONITOR (obj); + EmpathyMicMonitorPrivate *priv = self->priv; + + g_queue_foreach (priv->operations, (GFunc) operation_free, + GUINT_TO_POINTER (TRUE)); + g_queue_free (priv->operations); + + if (priv->context != NULL) + pa_context_unref (priv->context); + priv->context = NULL; + + if (priv->loop != NULL) + pa_glib_mainloop_free (priv->loop); + priv->loop = NULL; + + G_OBJECT_CLASS (empathy_mic_monitor_parent_class)->dispose (obj); +} + +static void +empathy_mic_monitor_class_init (EmpathyMicMonitorClass *klass) +{ + GObjectClass *object_class = G_OBJECT_CLASS (klass); + + object_class->constructed = empathy_mic_monitor_constructed; + object_class->dispose = empathy_mic_monitor_dispose; + + signals[MICROPHONE_ADDED] = g_signal_new ("microphone-added", + G_TYPE_FROM_CLASS (klass), + G_SIGNAL_RUN_LAST, + 0, + NULL, NULL, + _src_marshal_VOID__UINT_STRING_STRING_BOOLEAN, + G_TYPE_NONE, 4, G_TYPE_UINT, G_TYPE_STRING, G_TYPE_STRING, G_TYPE_BOOLEAN); + + signals[MICROPHONE_REMOVED] = g_signal_new ("microphone-removed", + G_TYPE_FROM_CLASS (klass), + G_SIGNAL_RUN_LAST, + 0, + NULL, NULL, + g_cclosure_marshal_VOID__UINT, + G_TYPE_NONE, 1, G_TYPE_UINT); + + signals[MICROPHONE_CHANGED] = g_signal_new ("microphone-changed", + G_TYPE_FROM_CLASS (klass), + G_SIGNAL_RUN_LAST, + 0, + NULL, NULL, + _src_marshal_VOID__UINT_UINT, + G_TYPE_NONE, 2, G_TYPE_UINT, G_TYPE_UINT); + + g_type_class_add_private (object_class, sizeof (EmpathyMicMonitorPrivate)); +} + +EmpathyMicMonitor * +empathy_mic_monitor_new (void) +{ + return g_object_new (EMPATHY_TYPE_MIC_MONITOR, + NULL); +} + +/* operation: list microphones */ +static void +operation_list_microphones_free (gpointer data) +{ + GQueue *queue = data; + GList *l; + + for (l = queue->head; l != NULL; l = l->next) + { + EmpathyMicrophone *mic = l->data; + + g_free (mic->name); + g_free (mic->description); + g_slice_free (EmpathyMicrophone, mic); + } + + g_queue_free (queue); +} + +static void +operation_list_microphones_cb (pa_context *context, + const pa_source_info *info, + int eol, + void *userdata) +{ + GSimpleAsyncResult *result = userdata; + EmpathyMicrophone *mic; + GQueue *queue; + + if (eol) + { + g_simple_async_result_complete (result); + g_object_unref (result); + return; + } + + mic = g_slice_new0 (EmpathyMicrophone); + mic->index = info->index; + mic->name = g_strdup (info->name); + mic->description = g_strdup (info->description); + mic->is_monitor = (info->monitor_of_sink != PA_INVALID_INDEX); + + /* add it to the queue */ + queue = g_simple_async_result_get_op_res_gpointer (result); + g_queue_push_tail (queue, mic); +} + +static void +operation_list_microphones (EmpathyMicMonitor *self, + GSimpleAsyncResult *result) +{ + EmpathyMicMonitorPrivate *priv = self->priv; + + g_assert_cmpuint (pa_context_get_state (priv->context), ==, PA_CONTEXT_READY); + + g_simple_async_result_set_op_res_gpointer (result, g_queue_new (), + operation_list_microphones_free); + + pa_context_get_source_info_list (priv->context, + operation_list_microphones_cb, result); +} + +void +empathy_mic_monitor_list_microphones_async (EmpathyMicMonitor *self, + GAsyncReadyCallback callback, + gpointer user_data) +{ +EmpathyMicMonitorPrivate *priv = self->priv; + Operation *operation; + GSimpleAsyncResult *simple; + + simple = g_simple_async_result_new (G_OBJECT (self), callback, user_data, + empathy_mic_monitor_list_microphones_async); + + operation = operation_new (operation_list_microphones, simple); + g_queue_push_tail (priv->operations, operation); + + /* gogogogo */ + operations_run (self); +} + +const GList * +empathy_mic_monitor_list_microphones_finish (EmpathyMicMonitor *src, + GAsyncResult *result, + GError **error) +{ + GSimpleAsyncResult *simple = G_SIMPLE_ASYNC_RESULT (result); + GQueue *queue; + + if (g_simple_async_result_propagate_error (simple, error)) + return NULL; + + g_return_val_if_fail (g_simple_async_result_is_valid (result, + G_OBJECT (src), empathy_mic_monitor_list_microphones_async), + NULL); + + queue = g_simple_async_result_get_op_res_gpointer (simple); + return queue->head; +} + +/* operation: change microphone */ +typedef struct +{ + guint source_output_idx; + guint source_idx; +} ChangeMicrophoneData; + +static void +operation_change_microphone_cb (pa_context *context, + int success, + void *userdata) +{ + GSimpleAsyncResult *result = userdata; + + if (!success) + { + g_simple_async_result_set_error (result, G_IO_ERROR, G_IO_ERROR_FAILED, + "Failed to change microphone. Reason unknown."); + } + + g_simple_async_result_complete (result); + g_object_unref (result); +} + +static void +operation_change_microphone (EmpathyMicMonitor *self, + GSimpleAsyncResult *result) +{ + EmpathyMicMonitorPrivate *priv = self->priv; + ChangeMicrophoneData *data; + + g_assert_cmpuint (pa_context_get_state (priv->context), ==, PA_CONTEXT_READY); + + data = g_simple_async_result_get_op_res_gpointer (result); + + pa_context_move_source_output_by_index (priv->context, + data->source_output_idx, data->source_idx, + operation_change_microphone_cb, result); + + g_simple_async_result_set_op_res_gpointer (result, NULL, NULL); + g_slice_free (ChangeMicrophoneData, data); +} + +void +empathy_mic_monitor_change_microphone_async (EmpathyMicMonitor *self, + guint source_output_idx, + guint source_idx, + GAsyncReadyCallback callback, + gpointer user_data) +{ + EmpathyMicMonitorPrivate *priv = self->priv; + GSimpleAsyncResult *simple; + Operation *operation; + ChangeMicrophoneData *data; + + simple = g_simple_async_result_new (G_OBJECT (self), callback, user_data, + empathy_mic_monitor_change_microphone_async); + + if (source_output_idx == PA_INVALID_INDEX) + { + g_simple_async_result_set_error (simple, G_IO_ERROR, G_IO_ERROR_FAILED, + "Invalid source output index"); + g_simple_async_result_complete_in_idle (simple); + g_object_unref (simple); + return; + } + + data = g_slice_new0 (ChangeMicrophoneData); + data->source_idx = source_idx; + data->source_output_idx = source_output_idx; + g_simple_async_result_set_op_res_gpointer (simple, data, NULL); + + operation = operation_new (operation_change_microphone, simple); + g_queue_push_tail (priv->operations, operation); + + /* gogogogo */ + operations_run (self); +} + +gboolean +empathy_mic_monitor_change_microphone_finish (EmpathyMicMonitor *self, + GAsyncResult *result, + GError **error) +{ + empathy_implement_finish_void (self, + empathy_mic_monitor_change_microphone_async); +} + +/* operation: get current mic */ +static void +empathy_mic_monitor_get_current_mic_cb (pa_context *context, + const pa_source_output_info *info, + int eol, + void *userdata) +{ + GSimpleAsyncResult *result = userdata; + + if (eol) + return; + + if (g_simple_async_result_get_op_res_gpointer (result) != NULL) + return; + + g_simple_async_result_set_op_res_gpointer (result, + GUINT_TO_POINTER (info->source), NULL); + g_simple_async_result_complete (result); + g_object_unref (result); +} + +static void +operation_get_current_mic (EmpathyMicMonitor *self, + GSimpleAsyncResult *result) +{ + EmpathyMicMonitorPrivate *priv = self->priv; + guint source_output_idx; + + g_assert_cmpuint (pa_context_get_state (priv->context), ==, PA_CONTEXT_READY); + + source_output_idx = GPOINTER_TO_UINT ( + g_simple_async_result_get_op_res_gpointer (result)); + + /* unset this so we can use it in the cb */ + g_simple_async_result_set_op_res_gpointer (result, NULL, NULL); + + pa_context_get_source_output_info (priv->context, source_output_idx, + empathy_mic_monitor_get_current_mic_cb, result); +} + +void +empathy_mic_monitor_get_current_mic_async (EmpathyMicMonitor *self, + guint source_output_idx, + GAsyncReadyCallback callback, + gpointer user_data) +{ + EmpathyMicMonitorPrivate *priv = self->priv; + Operation *operation; + GSimpleAsyncResult *simple; + + simple = g_simple_async_result_new (G_OBJECT (self), callback, user_data, + empathy_mic_monitor_get_current_mic_async); + + g_simple_async_result_set_op_res_gpointer (simple, + GUINT_TO_POINTER (source_output_idx), NULL); + + operation = operation_new (operation_get_current_mic, simple); + g_queue_push_tail (priv->operations, operation); + + operations_run (self); +} + +guint +empathy_mic_monitor_get_current_mic_finish (EmpathyMicMonitor *self, + GAsyncResult *result, + GError **error) +{ + GSimpleAsyncResult *simple = G_SIMPLE_ASYNC_RESULT (result); + + if (g_simple_async_result_propagate_error (simple, error)) + return PA_INVALID_INDEX; + + g_return_val_if_fail (g_simple_async_result_is_valid (result, + G_OBJECT (self), empathy_mic_monitor_get_current_mic_async), + PA_INVALID_INDEX); + + return GPOINTER_TO_UINT ( + g_simple_async_result_get_op_res_gpointer (simple)); +} diff --git a/src/empathy-mic-monitor.h b/src/empathy-mic-monitor.h new file mode 100644 index 000000000..69106c55d --- /dev/null +++ b/src/empathy-mic-monitor.h @@ -0,0 +1,80 @@ +/* + * Copyright (C) 2011 Collabora Ltd. + * + * This library is free software; you can redistribute it and/or + * modify it under the terms of the GNU Lesser General Public + * License as published by the Free Software Foundation; either + * version 2.1 of the License, or (at your option) any later version. + * + * This library is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public + * License along with this library; if not, write to the Free Software + * Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA + */ + +#ifndef __EMPATHY_MIC_MONITOR_H__ +#define __EMPATHY_MIC_MONITOR_H__ + +#include + +#include + +G_BEGIN_DECLS + +#define EMPATHY_TYPE_MIC_MONITOR (empathy_mic_monitor_get_type ()) +#define EMPATHY_MIC_MONITOR(o) (G_TYPE_CHECK_INSTANCE_CAST ((o), EMPATHY_TYPE_MIC_MONITOR, EmpathyMicMonitor)) +#define EMPATHY_MIC_MONITOR_CLASS(k) (G_TYPE_CHECK_CLASS_CAST((k), EMPATHY_TYPE_MIC_MONITOR, EmpathyMicMonitorClass)) +#define EMPATHY_IS_MIC_MONITOR(o) (G_TYPE_CHECK_INSTANCE_TYPE ((o), EMPATHY_TYPE_MIC_MONITOR)) +#define EMPATHY_IS_MIC_MONITOR_CLASS(k) (G_TYPE_CHECK_CLASS_TYPE ((k), EMPATHY_TYPE_MIC_MONITOR)) +#define EMPATHY_MIC_MONITOR_GET_CLASS(o) (G_TYPE_INSTANCE_GET_CLASS ((o), EMPATHY_TYPE_MIC_MONITOR, EmpathyMicMonitorClass)) + +typedef struct _EmpathyMicMonitor EmpathyMicMonitor; +typedef struct _EmpathyMicMonitorPrivate EmpathyMicMonitorPrivate; +typedef struct _EmpathyMicMonitorClass EmpathyMicMonitorClass; + +struct _EmpathyMicMonitor +{ + GObject parent; + EmpathyMicMonitorPrivate *priv; +}; + +struct _EmpathyMicMonitorClass +{ + GObjectClass parent_class; +}; + +GType empathy_mic_monitor_get_type (void) G_GNUC_CONST; + +EmpathyMicMonitor * empathy_mic_monitor_new (void); + + +typedef struct +{ + guint index; + gchar *name; + gchar *description; + gboolean is_monitor; +} EmpathyMicrophone; + +void empathy_mic_monitor_list_microphones_async (EmpathyMicMonitor *monitor, + GAsyncReadyCallback callback, gpointer user_data); +const GList * empathy_mic_monitor_list_microphones_finish (EmpathyMicMonitor *monitor, + GAsyncResult *result, GError **error); + +void empathy_mic_monitor_change_microphone_async (EmpathyMicMonitor *monitor, + guint source_output_idx, guint source_idx, GAsyncReadyCallback callback, gpointer user_data); +gboolean empathy_mic_monitor_change_microphone_finish (EmpathyMicMonitor *monitor, + GAsyncResult *result, GError **error); + +void empathy_mic_monitor_get_current_mic_async (EmpathyMicMonitor *self, + guint source_output_idx, GAsyncReadyCallback callback, gpointer user_data); +guint empathy_mic_monitor_get_current_mic_finish (EmpathyMicMonitor *self, + GAsyncResult *result, GError **error); + +G_END_DECLS + +#endif /* __EMPATHY_MIC_MONITOR_H__ */ -- cgit v1.2.3