2 * empathy-gst-audio-src.c - Source for EmpathyGstAudioSrc
3 * Copyright (C) 2008 Collabora Ltd.
4 * @author Sjoerd Simons <sjoerd.simons@collabora.co.uk>
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.
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.
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
22 #include "empathy-audio-src.h"
25 #include <gst/audio/streamvolume.h>
27 #include <gst/interfaces/streamvolume.h>
30 #include "empathy-audio-utils.h"
31 #include "empathy-mic-monitor.h"
32 #include "empathy-utils.h"
34 #define DEBUG_FLAG EMPATHY_DEBUG_VOIP
35 #include "empathy-debug.h"
37 G_DEFINE_TYPE(EmpathyGstAudioSrc, empathy_audio_src, GST_TYPE_BIN)
45 /* private structure */
46 struct _EmpathyGstAudioSrcPrivate
48 gboolean dispose_has_run;
50 GstElement *volume_element;
52 EmpathyMicMonitor *mic_monitor;
54 /* 0 if not known yet */
55 guint source_output_idx;
56 /* G_MAXUINT if not known yet */
61 gboolean have_stream_volume;
67 #define EMPATHY_GST_AUDIO_SRC_GET_PRIVATE(o) \
68 (G_TYPE_INSTANCE_GET_PRIVATE ((o), EMPATHY_TYPE_GST_AUDIO_SRC, \
69 EmpathyGstAudioSrcPrivate))
73 empathy_audio_src_volume_changed (GObject *object,
78 empathy_audio_set_hw_mute (EmpathyGstAudioSrc *self, gboolean mute)
80 if (mute == self->priv->mute)
83 if (self->priv->have_stream_volume)
84 g_object_set (self->priv->src, "mute", mute, NULL);
86 /* Belt and braces: If for some reason the underlying src doesn't mute
87 * correctly or doesn't update us when it unmutes correctly enforce it using
88 * our own volume element. Our UI can in no circumstances be made to think
89 * the input is muted while it's not */
90 g_object_set (self->priv->volume_element, "mute", mute, NULL);
92 self->priv->mute = mute;
96 empathy_audio_src_get_hw_mute (EmpathyGstAudioSrc *self)
99 g_object_get (self->priv->src, "mute", &result, NULL);
105 empathy_audio_src_set_hw_volume (EmpathyGstAudioSrc *self,
108 if (volume == self->priv->volume)
111 if (self->priv->have_stream_volume)
112 g_object_set (self->priv->src, "volume", volume, NULL);
113 self->priv->volume = volume;
117 empathy_audio_src_get_hw_volume (EmpathyGstAudioSrc *self)
120 g_object_get (self->priv->src, "volume", &result, NULL);
127 empathy_audio_src_supports_changing_mic (EmpathyGstAudioSrc *self)
129 EmpathyGstAudioSrcPrivate *priv = EMPATHY_GST_AUDIO_SRC_GET_PRIVATE (self);
130 GObjectClass *object_class;
132 object_class = G_OBJECT_GET_CLASS (priv->src);
134 return (g_object_class_find_property (object_class,
135 "source-output-index") != NULL);
139 empathy_audio_src_get_mic_index (EmpathyGstAudioSrc *self)
141 EmpathyGstAudioSrcPrivate *priv = EMPATHY_GST_AUDIO_SRC_GET_PRIVATE (self);
142 guint audio_src_idx = PA_INVALID_INDEX;
144 if (empathy_audio_src_supports_changing_mic (self))
145 g_object_get (priv->src,
146 "source-output-index", &audio_src_idx,
149 return audio_src_idx;
153 empathy_audio_src_microphone_changed_cb (EmpathyMicMonitor *monitor,
154 guint source_output_idx,
158 EmpathyGstAudioSrc *self = user_data;
159 EmpathyGstAudioSrcPrivate *priv = EMPATHY_GST_AUDIO_SRC_GET_PRIVATE (self);
162 audio_src_idx = empathy_audio_src_get_mic_index (self);
164 if (source_output_idx == PA_INVALID_INDEX
165 || source_output_idx != audio_src_idx)
168 if (priv->source_idx == source_idx)
171 priv->source_idx = source_idx;
172 g_object_notify (G_OBJECT (self), "microphone");
176 empathy_audio_src_get_current_mic_cb (GObject *source_object,
177 GAsyncResult *result,
180 EmpathyMicMonitor *monitor = EMPATHY_MIC_MONITOR (source_object);
181 EmpathyGstAudioSrc *self = user_data;
182 EmpathyGstAudioSrcPrivate *priv = EMPATHY_GST_AUDIO_SRC_GET_PRIVATE (self);
184 GError *error = NULL;
186 source_idx = empathy_mic_monitor_get_current_mic_finish (monitor, result, &error);
190 DEBUG ("Failed to get current mic: %s", error->message);
191 g_clear_error (&error);
195 if (priv->source_idx == source_idx)
198 priv->source_idx = source_idx;
199 g_object_notify (G_OBJECT (self), "microphone");
203 empathy_audio_src_source_output_index_notify (GObject *object,
205 EmpathyGstAudioSrc *self)
207 EmpathyGstAudioSrcPrivate *priv = EMPATHY_GST_AUDIO_SRC_GET_PRIVATE (self);
208 guint source_output_idx;
210 source_output_idx = empathy_audio_src_get_mic_index (self);
212 if (source_output_idx == PA_INVALID_INDEX)
215 if (priv->source_output_idx == source_output_idx)
218 /* It's actually changed. */
219 priv->source_output_idx = source_output_idx;
221 empathy_mic_monitor_get_current_mic_async (priv->mic_monitor,
222 source_output_idx, empathy_audio_src_get_current_mic_cb, self);
229 const gchar *description;
231 description = g_getenv ("EMPATHY_AUDIO_SRC");
233 if (description != NULL)
235 GError *error = NULL;
237 src = gst_parse_bin_from_description (description, TRUE, &error);
240 DEBUG ("Failed to create bin %s: %s", description, error->message);
241 g_error_free (error);
247 /* Use pulsesrc as default */
248 src = gst_element_factory_make ("pulsesrc", NULL);
251 g_warning ("Missing 'pulsesrc' element");
255 empathy_audio_set_stream_properties (src, TRUE);
257 /* Set latency (buffering on the PulseAudio side) of 20ms */
258 g_object_set (src, "buffer-time", (gint64) 20000, NULL);
264 empathy_audio_src_init (EmpathyGstAudioSrc *obj)
266 EmpathyGstAudioSrcPrivate *priv = EMPATHY_GST_AUDIO_SRC_GET_PRIVATE (obj);
270 g_mutex_init (&priv->lock);
274 priv->src = create_src ();
275 if (priv->src == NULL)
278 if (GST_IS_STREAM_VOLUME (priv->src))
283 priv->have_stream_volume = TRUE;
284 /* We can't do a bidirection bind as the ::notify comes from another
285 * thread, for other bits of empathy it's most simpler if it comes from
287 g_object_bind_property (obj, "volume", priv->src, "volume",
289 g_object_bind_property (obj, "mute", priv->src, "mute",
292 /* sync and callback for bouncing */
293 g_object_get (priv->src, "volume", &volume, NULL);
294 g_object_set (obj, "volume", volume, NULL);
296 g_object_get (priv->src, "mute", &mute, NULL);
297 g_object_set (obj, "mute", mute, NULL);
299 g_signal_connect (priv->src, "notify::volume",
300 G_CALLBACK (empathy_audio_src_volume_changed), obj);
301 g_signal_connect (priv->src, "notify::mute",
302 G_CALLBACK (empathy_audio_src_volume_changed), obj);
306 g_message ("No stream volume available :(, mute will work though");
307 priv->have_stream_volume = FALSE;
310 gst_bin_add (GST_BIN (obj), priv->src);
312 priv->volume_element = gst_element_factory_make ("volume", NULL);
313 gst_bin_add (GST_BIN (obj), priv->volume_element);
317 GstElement *capsfilter;
320 /* Explicitly state what format we want from pulsesrc. This pushes resampling
321 * and format conversion as early as possible, lowering the amount of data
322 * transferred and thus improving performance. When moving to GStreamer
323 * 0.11/1.0, this should change so that we actually request what the encoder
324 * wants downstream. */
325 caps = gst_caps_new_simple ("audio/x-raw-int",
326 "channels", G_TYPE_INT, 1,
327 "width", G_TYPE_INT, 16,
328 "depth", G_TYPE_INT, 16,
329 "rate", G_TYPE_INT, 32000,
331 capsfilter = gst_element_factory_make ("capsfilter", NULL);
332 g_object_set (G_OBJECT (capsfilter), "caps", caps, NULL);
333 gst_bin_add (GST_BIN (obj), capsfilter);
334 gst_element_link (priv->src, capsfilter);
335 gst_element_link (capsfilter, priv->volume_element);
338 gst_element_link (priv->src, priv->volume_element);
341 src = gst_element_get_static_pad (priv->volume_element, "src");
343 ghost = gst_ghost_pad_new ("src", src);
344 gst_element_add_pad (GST_ELEMENT (obj), ghost);
346 gst_object_unref (G_OBJECT (src));
348 /* Listen to changes to GstPulseSrc:source-output-index so we know when
349 * it's no longer PA_INVALID_INDEX (starting for the first time) or if it
350 * changes (READY->NULL->READY...) */
351 g_signal_connect (priv->src, "notify::source-output-index",
352 G_CALLBACK (empathy_audio_src_source_output_index_notify),
355 priv->mic_monitor = empathy_mic_monitor_new ();
356 g_signal_connect (priv->mic_monitor, "microphone-changed",
357 G_CALLBACK (empathy_audio_src_microphone_changed_cb), obj);
359 priv->source_idx = PA_INVALID_INDEX;
362 static void empathy_audio_src_dispose (GObject *object);
363 static void empathy_audio_src_finalize (GObject *object);
366 empathy_audio_src_set_property (GObject *object,
367 guint property_id, const GValue *value, GParamSpec *pspec)
372 empathy_audio_src_set_hw_volume (EMPATHY_GST_AUDIO_SRC (object),
373 g_value_get_double (value));
376 empathy_audio_set_hw_mute (EMPATHY_GST_AUDIO_SRC (object),
377 g_value_get_boolean (value));
380 G_OBJECT_WARN_INVALID_PROPERTY_ID (object, property_id, pspec);
385 empathy_audio_src_get_property (GObject *object,
386 guint property_id, GValue *value, GParamSpec *pspec)
388 EmpathyGstAudioSrc *self = EMPATHY_GST_AUDIO_SRC (object);
389 EmpathyGstAudioSrcPrivate *priv = EMPATHY_GST_AUDIO_SRC_GET_PRIVATE (self);
394 g_value_set_double (value, priv->volume);
397 g_value_set_boolean (value, priv->mute);
399 case PROP_MICROPHONE:
400 g_value_set_uint (value, priv->source_idx);
403 G_OBJECT_WARN_INVALID_PROPERTY_ID (object, property_id, pspec);
408 empathy_audio_src_class_init (EmpathyGstAudioSrcClass
409 *empathy_audio_src_class)
411 GObjectClass *object_class = G_OBJECT_CLASS (empathy_audio_src_class);
412 GParamSpec *param_spec;
414 g_type_class_add_private (empathy_audio_src_class,
415 sizeof (EmpathyGstAudioSrcPrivate));
417 object_class->dispose = empathy_audio_src_dispose;
418 object_class->finalize = empathy_audio_src_finalize;
420 object_class->set_property = empathy_audio_src_set_property;
421 object_class->get_property = empathy_audio_src_get_property;
423 param_spec = g_param_spec_double ("volume", "Volume", "volume contol",
425 G_PARAM_READWRITE | G_PARAM_STATIC_STRINGS);
426 g_object_class_install_property (object_class, PROP_VOLUME, param_spec);
428 param_spec = g_param_spec_boolean ("mute", "Mute", "mute contol",
430 G_PARAM_READWRITE | G_PARAM_STATIC_STRINGS);
431 g_object_class_install_property (object_class, PROP_MUTE, param_spec);
433 param_spec = g_param_spec_uint ("microphone", "microphone", "microphone",
434 0, G_MAXUINT, G_MAXUINT,
435 G_PARAM_READABLE | G_PARAM_STATIC_STRINGS);
436 g_object_class_install_property (object_class, PROP_MICROPHONE, param_spec);
440 empathy_audio_src_dispose (GObject *object)
442 EmpathyGstAudioSrc *self = EMPATHY_GST_AUDIO_SRC (object);
443 EmpathyGstAudioSrcPrivate *priv = EMPATHY_GST_AUDIO_SRC_GET_PRIVATE (self);
445 if (priv->dispose_has_run)
448 priv->dispose_has_run = TRUE;
450 if (priv->volume_idle_id != 0)
451 g_source_remove (priv->volume_idle_id);
452 priv->volume_idle_id = 0;
454 tp_clear_object (&priv->mic_monitor);
456 /* release any references held by the object here */
458 if (G_OBJECT_CLASS (empathy_audio_src_parent_class)->dispose)
459 G_OBJECT_CLASS (empathy_audio_src_parent_class)->dispose (object);
463 empathy_audio_src_finalize (GObject *object)
465 EmpathyGstAudioSrc *self = EMPATHY_GST_AUDIO_SRC (object);
466 EmpathyGstAudioSrcPrivate *priv = EMPATHY_GST_AUDIO_SRC_GET_PRIVATE (self);
468 /* free any data held directly by the object here */
469 g_mutex_clear (&priv->lock);
471 G_OBJECT_CLASS (empathy_audio_src_parent_class)->finalize (object);
475 empathy_audio_src_volume_changed_idle (gpointer user_data)
477 EmpathyGstAudioSrc *self = EMPATHY_GST_AUDIO_SRC (user_data);
478 EmpathyGstAudioSrcPrivate *priv = EMPATHY_GST_AUDIO_SRC_GET_PRIVATE (self);
482 g_mutex_lock (&priv->lock);
483 priv->volume_idle_id = 0;
484 g_mutex_unlock (&priv->lock);
486 volume = empathy_audio_src_get_hw_volume (self);
488 if (volume != priv->volume)
490 priv->volume = volume;
491 g_object_notify (G_OBJECT (self), "volume");
494 mute = empathy_audio_src_get_hw_mute (self);
495 if (mute != priv->mute)
498 /* hw mute changed, follow with own volume */
499 g_object_set (self->priv->volume_element, "mute", mute, NULL);
500 g_object_notify (G_OBJECT (self), "mute");
507 empathy_audio_src_volume_changed (GObject *object,
511 EmpathyGstAudioSrc *self = EMPATHY_GST_AUDIO_SRC (user_data);
513 g_mutex_lock (&self->priv->lock);
514 if (self->priv->volume_idle_id == 0)
515 self->priv->volume_idle_id = g_idle_add (
516 empathy_audio_src_volume_changed_idle, self);
517 g_mutex_unlock (&self->priv->lock);
523 empathy_audio_src_new (void)
525 static gboolean registered = FALSE;
528 if (!gst_element_register (NULL, "empathyaudiosrc",
529 GST_RANK_NONE, EMPATHY_TYPE_GST_AUDIO_SRC))
533 return gst_element_factory_make ("empathyaudiosrc", NULL);
537 empathy_audio_src_set_echo_cancel (EmpathyGstAudioSrc *src,
540 DEBUG ("Src echo cancellation setting: %s", enable ? "on" : "off");
541 empathy_audio_set_stream_properties (src->priv->src, enable);
545 empathy_audio_src_set_volume (EmpathyGstAudioSrc *src, gdouble volume)
547 g_object_set (src, "volume", volume, NULL);
551 empathy_audio_src_get_volume (EmpathyGstAudioSrc *src)
553 return src->priv->volume;
557 empathy_audio_src_get_microphone (EmpathyGstAudioSrc *src)
559 EmpathyGstAudioSrcPrivate *priv = EMPATHY_GST_AUDIO_SRC_GET_PRIVATE (src);
561 return priv->source_idx;
565 empathy_audio_src_change_microphone_cb (GObject *source_object,
566 GAsyncResult *result,
569 EmpathyMicMonitor *monitor = EMPATHY_MIC_MONITOR (source_object);
570 GSimpleAsyncResult *simple = user_data;
571 GError *error = NULL;
573 if (!empathy_mic_monitor_change_microphone_finish (monitor,
576 g_simple_async_result_take_error (simple, error);
579 g_simple_async_result_complete (simple);
580 g_object_unref (simple);
584 empathy_audio_src_change_microphone_async (EmpathyGstAudioSrc *src,
586 GAsyncReadyCallback callback,
589 EmpathyGstAudioSrcPrivate *priv = EMPATHY_GST_AUDIO_SRC_GET_PRIVATE (src);
590 guint source_output_idx;
591 GSimpleAsyncResult *simple;
593 simple = g_simple_async_result_new (G_OBJECT (src), callback, user_data,
594 empathy_audio_src_change_microphone_async);
596 if (!empathy_audio_src_supports_changing_mic (src))
598 g_simple_async_result_set_error (simple, G_IO_ERROR, G_IO_ERROR_FAILED,
599 "pulsesrc is not new enough to support changing microphone");
600 g_simple_async_result_complete_in_idle (simple);
601 g_object_unref (simple);
605 source_output_idx = empathy_audio_src_get_mic_index (src);
607 if (source_output_idx == PA_INVALID_INDEX)
609 g_simple_async_result_set_error (simple, G_IO_ERROR, G_IO_ERROR_FAILED,
610 "pulsesrc is not yet PLAYING");
611 g_simple_async_result_complete_in_idle (simple);
612 g_object_unref (simple);
616 empathy_mic_monitor_change_microphone_async (priv->mic_monitor,
617 source_output_idx, microphone, empathy_audio_src_change_microphone_cb,
622 empathy_audio_src_change_microphone_finish (EmpathyGstAudioSrc *src,
623 GAsyncResult *result,
626 empathy_implement_finish_void (src,
627 empathy_audio_src_change_microphone_async);
631 empathy_audio_src_set_mute (EmpathyGstAudioSrc *self,
634 empathy_audio_set_hw_mute (self, mute);
636 g_object_notify (G_OBJECT (self), "mute");