*/
#include "config.h"
+#include "empathy-audio-src.h"
-#include <stdio.h>
-#include <stdlib.h>
-
-#include <libempathy/empathy-utils.h>
-#include <libempathy-gtk/empathy-call-utils.h>
+#include <tp-account-widgets/tpaw-utils.h>
-#include "empathy-audio-src.h"
+#include <gst/audio/streamvolume.h>
+#include "empathy-audio-utils.h"
#include "empathy-mic-monitor.h"
+#include "empathy-utils.h"
#define DEBUG_FLAG EMPATHY_DEBUG_VOIP
-#include <libempathy/empathy-debug.h>
+#include "empathy-debug.h"
G_DEFINE_TYPE(EmpathyGstAudioSrc, empathy_audio_src, GST_TYPE_BIN)
-/* signal enum */
-enum
-{
- PEAK_LEVEL_CHANGED,
- RMS_LEVEL_CHANGED,
- LAST_SIGNAL
-};
-
-static guint signals[LAST_SIGNAL] = {0};
-
enum {
PROP_VOLUME = 1,
- PROP_RMS_LEVEL,
- PROP_PEAK_LEVEL,
+ PROP_MUTE,
PROP_MICROPHONE,
};
/* private structure */
-typedef struct _EmpathyGstAudioSrcPrivate EmpathyGstAudioSrcPrivate;
-
struct _EmpathyGstAudioSrcPrivate
{
gboolean dispose_has_run;
GstElement *src;
- GstElement *volume;
- GstElement *level;
+ GstElement *volume_element;
EmpathyMicMonitor *mic_monitor;
/* G_MAXUINT if not known yet */
guint source_idx;
- gdouble peak_level;
- gdouble rms_level;
+ gdouble volume;
+ gboolean mute;
+ gboolean have_stream_volume;
- GMutex *lock;
- guint idle_id;
+ GMutex lock;
+ guint volume_idle_id;
};
#define EMPATHY_GST_AUDIO_SRC_GET_PRIVATE(o) \
(G_TYPE_INSTANCE_GET_PRIVATE ((o), EMPATHY_TYPE_GST_AUDIO_SRC, \
EmpathyGstAudioSrcPrivate))
+
+static gboolean
+empathy_audio_src_volume_changed (GObject *object,
+ GParamSpec *pspec,
+ gpointer user_data);
+
+static void
+empathy_audio_set_hw_mute (EmpathyGstAudioSrc *self, gboolean mute)
+{
+ if (mute == self->priv->mute)
+ return;
+
+ if (self->priv->have_stream_volume)
+ g_object_set (self->priv->src, "mute", mute, NULL);
+
+ /* Belt and braces: If for some reason the underlying src doesn't mute
+ * correctly or doesn't update us when it unmutes correctly enforce it using
+ * our own volume element. Our UI can in no circumstances be made to think
+ * the input is muted while it's not */
+ g_object_set (self->priv->volume_element, "mute", mute, NULL);
+
+ self->priv->mute = mute;
+}
+
+static gboolean
+empathy_audio_src_get_hw_mute (EmpathyGstAudioSrc *self)
+{
+ gboolean result;
+ g_object_get (self->priv->src, "mute", &result, NULL);
+
+ return result;
+}
+
+static void
+empathy_audio_src_set_hw_volume (EmpathyGstAudioSrc *self,
+ gdouble volume)
+{
+ if (volume == self->priv->volume)
+ return;
+
+ if (self->priv->have_stream_volume)
+ g_object_set (self->priv->src, "volume", volume, NULL);
+ self->priv->volume = volume;
+}
+
+static gdouble
+empathy_audio_src_get_hw_volume (EmpathyGstAudioSrc *self)
+{
+ gdouble result;
+ g_object_get (self->priv->src, "volume", &result, NULL);
+
+ return result;
+}
+
+
gboolean
empathy_audio_src_supports_changing_mic (EmpathyGstAudioSrc *self)
{
return audio_src_idx;
}
-
static void
empathy_audio_src_microphone_changed_cb (EmpathyMicMonitor *monitor,
guint source_output_idx,
/* Use pulsesrc as default */
src = gst_element_factory_make ("pulsesrc", NULL);
if (src == NULL)
- return NULL;
+ {
+ g_warning ("Missing 'pulsesrc' element");
+ return NULL;
+ }
+
+ empathy_audio_set_stream_properties (src, TRUE);
- empathy_call_set_stream_properties (src);
+ /* Set latency (buffering on the PulseAudio side) of 20ms */
+ g_object_set (src, "buffer-time", (gint64) 20000, NULL);
return src;
}
{
EmpathyGstAudioSrcPrivate *priv = EMPATHY_GST_AUDIO_SRC_GET_PRIVATE (obj);
GstPad *ghost, *src;
- GstElement *capsfilter;
- GstCaps *caps;
- priv->peak_level = -G_MAXDOUBLE;
- priv->lock = g_mutex_new ();
+ obj->priv = priv;
+ g_mutex_init (&priv->lock);
+
+ priv->volume = 1.0;
priv->src = create_src ();
if (priv->src == NULL)
return;
- gst_bin_add (GST_BIN (obj), priv->src);
-
- /* Explicitly state what format we want from pulsesrc. This pushes resampling
- * and format conversion as early as possible, lowering the amount of data
- * transferred and thus improving performance. When moving to GStreamer
- * 0.11/1.0, this should change so that we actually request what the encoder
- * wants downstream. */
- caps = gst_caps_new_simple ("audio/x-raw-int",
- "channels", G_TYPE_INT, 1,
- "width", G_TYPE_INT, 16,
- "depth", G_TYPE_INT, 16,
- "rate", G_TYPE_INT, 32000,
- NULL);
- capsfilter = gst_element_factory_make ("capsfilter", NULL);
- g_object_set (G_OBJECT (capsfilter), "caps", caps, NULL);
- gst_bin_add (GST_BIN (obj), capsfilter);
- gst_element_link (priv->src, capsfilter);
-
- priv->volume = gst_element_factory_make ("volume", NULL);
- g_object_ref (priv->volume);
+ if (GST_IS_STREAM_VOLUME (priv->src))
+ {
+ gdouble volume;
+ gboolean mute;
+
+ priv->have_stream_volume = TRUE;
+ /* We can't do a bidirection bind as the ::notify comes from another
+ * thread, for other bits of empathy it's most simpler if it comes from
+ * the main thread */
+ g_object_bind_property (obj, "volume", priv->src, "volume",
+ G_BINDING_DEFAULT);
+ g_object_bind_property (obj, "mute", priv->src, "mute",
+ G_BINDING_DEFAULT);
+
+ /* sync and callback for bouncing */
+ g_object_get (priv->src, "volume", &volume, NULL);
+ g_object_set (obj, "volume", volume, NULL);
+
+ g_object_get (priv->src, "mute", &mute, NULL);
+ g_object_set (obj, "mute", mute, NULL);
+
+ g_signal_connect (priv->src, "notify::volume",
+ G_CALLBACK (empathy_audio_src_volume_changed), obj);
+ g_signal_connect (priv->src, "notify::mute",
+ G_CALLBACK (empathy_audio_src_volume_changed), obj);
+ }
+ else
+ {
+ g_message ("No stream volume available :(, mute will work though");
+ priv->have_stream_volume = FALSE;
+ }
- gst_bin_add (GST_BIN (obj), priv->volume);
- gst_element_link (capsfilter, priv->volume);
+ gst_bin_add (GST_BIN (obj), priv->src);
- priv->level = gst_element_factory_make ("level", NULL);
- gst_bin_add (GST_BIN (obj), priv->level);
- gst_element_link (priv->volume, priv->level);
+ priv->volume_element = gst_element_factory_make ("volume", NULL);
+ gst_bin_add (GST_BIN (obj), priv->volume_element);
+
+ {
+ GstElement *capsfilter;
+ GstCaps *caps;
+
+ /* Explicitly state what format we want from pulsesrc. This pushes resampling
+ * and format conversion as early as possible, lowering the amount of data
+ * transferred and thus improving performance. When moving to GStreamer
+ * 0.11/1.0, this should change so that we actually request what the encoder
+ * wants downstream. */
+ caps = gst_caps_new_simple ("audio/x-raw",
+ "channels", G_TYPE_INT, 1,
+ "width", G_TYPE_INT, 16,
+ "depth", G_TYPE_INT, 16,
+ "rate", G_TYPE_INT, 32000,
+ NULL);
+ capsfilter = gst_element_factory_make ("capsfilter", NULL);
+ g_object_set (G_OBJECT (capsfilter), "caps", caps, NULL);
+ gst_bin_add (GST_BIN (obj), capsfilter);
+ gst_element_link (priv->src, capsfilter);
+ gst_element_link (capsfilter, priv->volume_element);
+ }
- src = gst_element_get_static_pad (priv->level, "src");
+ src = gst_element_get_static_pad (priv->volume_element, "src");
ghost = gst_ghost_pad_new ("src", src);
gst_element_add_pad (GST_ELEMENT (obj), ghost);
static void empathy_audio_src_dispose (GObject *object);
static void empathy_audio_src_finalize (GObject *object);
-static void empathy_audio_src_handle_message (GstBin *bin,
- GstMessage *message);
-
-static gboolean empathy_audio_src_levels_updated (gpointer user_data);
static void
empathy_audio_src_set_property (GObject *object,
switch (property_id)
{
case PROP_VOLUME:
- empathy_audio_src_set_volume (EMPATHY_GST_AUDIO_SRC (object),
+ empathy_audio_src_set_hw_volume (EMPATHY_GST_AUDIO_SRC (object),
g_value_get_double (value));
break;
+ case PROP_MUTE:
+ empathy_audio_set_hw_mute (EMPATHY_GST_AUDIO_SRC (object),
+ g_value_get_boolean (value));
+ break;
default:
G_OBJECT_WARN_INVALID_PROPERTY_ID (object, property_id, pspec);
}
switch (property_id)
{
case PROP_VOLUME:
- g_value_set_double (value,
- empathy_audio_src_get_volume (self));
- break;
- case PROP_PEAK_LEVEL:
- g_mutex_lock (priv->lock);
- g_value_set_double (value, priv->peak_level);
- g_mutex_unlock (priv->lock);
+ g_value_set_double (value, priv->volume);
break;
- case PROP_RMS_LEVEL:
- g_mutex_lock (priv->lock);
- g_value_set_double (value, priv->rms_level);
- g_mutex_unlock (priv->lock);
+ case PROP_MUTE:
+ g_value_set_boolean (value, priv->mute);
break;
case PROP_MICROPHONE:
g_value_set_uint (value, priv->source_idx);
*empathy_audio_src_class)
{
GObjectClass *object_class = G_OBJECT_CLASS (empathy_audio_src_class);
- GstBinClass *gstbin_class = GST_BIN_CLASS (empathy_audio_src_class);
GParamSpec *param_spec;
g_type_class_add_private (empathy_audio_src_class,
object_class->set_property = empathy_audio_src_set_property;
object_class->get_property = empathy_audio_src_get_property;
- gstbin_class->handle_message =
- GST_DEBUG_FUNCPTR (empathy_audio_src_handle_message);
-
param_spec = g_param_spec_double ("volume", "Volume", "volume contol",
0.0, 5.0, 1.0,
G_PARAM_READWRITE | G_PARAM_STATIC_STRINGS);
g_object_class_install_property (object_class, PROP_VOLUME, param_spec);
- param_spec = g_param_spec_double ("peak-level", "peak level", "peak level",
- -G_MAXDOUBLE, G_MAXDOUBLE, 0,
- G_PARAM_READABLE | G_PARAM_STATIC_STRINGS);
- g_object_class_install_property (object_class, PROP_PEAK_LEVEL, param_spec);
+ param_spec = g_param_spec_boolean ("mute", "Mute", "mute contol",
+ FALSE,
+ G_PARAM_READWRITE | G_PARAM_STATIC_STRINGS);
+ g_object_class_install_property (object_class, PROP_MUTE, param_spec);
param_spec = g_param_spec_uint ("microphone", "microphone", "microphone",
0, G_MAXUINT, G_MAXUINT,
G_PARAM_READABLE | G_PARAM_STATIC_STRINGS);
g_object_class_install_property (object_class, PROP_MICROPHONE, param_spec);
-
- signals[PEAK_LEVEL_CHANGED] = g_signal_new ("peak-level-changed",
- G_TYPE_FROM_CLASS (empathy_audio_src_class),
- G_SIGNAL_RUN_LAST,
- 0,
- NULL, NULL,
- g_cclosure_marshal_generic,
- G_TYPE_NONE, 1, G_TYPE_DOUBLE);
-
- param_spec = g_param_spec_double ("rms-level", "RMS level", "RMS level",
- -G_MAXDOUBLE, G_MAXDOUBLE, 0,
- 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,
- 0,
- NULL, NULL,
- g_cclosure_marshal_generic,
- G_TYPE_NONE, 1, G_TYPE_DOUBLE);
}
void
priv->dispose_has_run = TRUE;
- if (priv->idle_id != 0)
- g_source_remove (priv->idle_id);
-
- priv->idle_id = 0;
+ if (priv->volume_idle_id != 0)
+ g_source_remove (priv->volume_idle_id);
+ priv->volume_idle_id = 0;
tp_clear_object (&priv->mic_monitor);
EmpathyGstAudioSrcPrivate *priv = EMPATHY_GST_AUDIO_SRC_GET_PRIVATE (self);
/* free any data held directly by the object here */
- g_mutex_free (priv->lock);
+ g_mutex_clear (&priv->lock);
G_OBJECT_CLASS (empathy_audio_src_parent_class)->finalize (object);
}
static gboolean
-empathy_audio_src_levels_updated (gpointer user_data)
+empathy_audio_src_volume_changed_idle (gpointer user_data)
{
EmpathyGstAudioSrc *self = EMPATHY_GST_AUDIO_SRC (user_data);
EmpathyGstAudioSrcPrivate *priv = EMPATHY_GST_AUDIO_SRC_GET_PRIVATE (self);
+ gdouble volume;
+ gboolean mute;
- g_mutex_lock (priv->lock);
+ g_mutex_lock (&priv->lock);
+ priv->volume_idle_id = 0;
+ g_mutex_unlock (&priv->lock);
- g_signal_emit (self, signals[PEAK_LEVEL_CHANGED], 0, priv->peak_level);
- g_signal_emit (self, signals[RMS_LEVEL_CHANGED], 0, priv->rms_level);
- priv->idle_id = 0;
+ volume = empathy_audio_src_get_hw_volume (self);
- g_mutex_unlock (priv->lock);
+ if (volume != priv->volume)
+ {
+ priv->volume = volume;
+ g_object_notify (G_OBJECT (self), "volume");
+ }
+
+ mute = empathy_audio_src_get_hw_mute (self);
+ if (mute != priv->mute)
+ {
+ priv->mute = mute;
+ /* hw mute changed, follow with own volume */
+ g_object_set (self->priv->volume_element, "mute", mute, NULL);
+ g_object_notify (G_OBJECT (self), "mute");
+ }
return FALSE;
}
-static void
-empathy_audio_src_handle_message (GstBin *bin, GstMessage *message)
+static gboolean
+empathy_audio_src_volume_changed (GObject *object,
+ GParamSpec *pspec,
+ gpointer user_data)
{
- EmpathyGstAudioSrc *self = EMPATHY_GST_AUDIO_SRC (bin);
- EmpathyGstAudioSrcPrivate *priv = EMPATHY_GST_AUDIO_SRC_GET_PRIVATE (self);
-
- if (GST_MESSAGE_TYPE (message) == GST_MESSAGE_ELEMENT &&
- GST_MESSAGE_SRC (message) == GST_OBJECT (priv->level))
- {
- const GstStructure *s;
- const gchar *name;
- const GValue *list;
- guint i, len;
- gdouble peak = -G_MAXDOUBLE;
- gdouble rms = -G_MAXDOUBLE;
-
- s = gst_message_get_structure (message);
- name = gst_structure_get_name (s);
-
- if (g_strcmp0 ("level", name) != 0)
- goto out;
-
- list = gst_structure_get_value (s, "peak");
- len = gst_value_list_get_size (list);
-
- for (i =0 ; i < len; i++)
- {
- const GValue *value;
- gdouble db;
-
- value = gst_value_list_get_value (list, i);
- db = g_value_get_double (value);
- peak = MAX (db, peak);
- }
-
- list = gst_structure_get_value (s, "rms");
- len = gst_value_list_get_size (list);
-
- for (i =0 ; i < len; i++)
- {
- const GValue *value;
- gdouble db;
-
- value = gst_value_list_get_value (list, i);
- db = g_value_get_double (value);
- rms = MAX (db, rms);
- }
-
- g_mutex_lock (priv->lock);
-
- priv->peak_level = peak;
- priv->rms_level = rms;
- if (priv->idle_id == 0)
- priv->idle_id = g_idle_add (empathy_audio_src_levels_updated, self);
+ EmpathyGstAudioSrc *self = EMPATHY_GST_AUDIO_SRC (user_data);
- g_mutex_unlock (priv->lock);
- }
+ g_mutex_lock (&self->priv->lock);
+ if (self->priv->volume_idle_id == 0)
+ self->priv->volume_idle_id = g_idle_add (
+ empathy_audio_src_volume_changed_idle, self);
+ g_mutex_unlock (&self->priv->lock);
-out:
- GST_BIN_CLASS (empathy_audio_src_parent_class)->handle_message (bin,
- message);
+ return FALSE;
}
GstElement *
}
void
-empathy_audio_src_set_volume (EmpathyGstAudioSrc *src, gdouble volume)
+empathy_audio_src_set_echo_cancel (EmpathyGstAudioSrc *src,
+ gboolean enable)
{
- EmpathyGstAudioSrcPrivate *priv = EMPATHY_GST_AUDIO_SRC_GET_PRIVATE (src);
- GParamSpec *pspec;
- GParamSpecDouble *pspec_double;
-
- pspec = g_object_class_find_property (G_OBJECT_GET_CLASS (priv->volume),
- "volume");
-
- g_assert (pspec != NULL);
-
- pspec_double = G_PARAM_SPEC_DOUBLE (pspec);
-
- volume = CLAMP (volume, pspec_double->minimum, pspec_double->maximum);
+ DEBUG ("Src echo cancellation setting: %s", enable ? "on" : "off");
+ empathy_audio_set_stream_properties (src->priv->src, enable);
+}
- g_object_set (G_OBJECT (priv->volume), "volume", volume, NULL);
+void
+empathy_audio_src_set_volume (EmpathyGstAudioSrc *src, gdouble volume)
+{
+ g_object_set (src, "volume", volume, NULL);
}
gdouble
empathy_audio_src_get_volume (EmpathyGstAudioSrc *src)
{
- EmpathyGstAudioSrcPrivate *priv = EMPATHY_GST_AUDIO_SRC_GET_PRIVATE (src);
- gdouble volume;
-
- g_object_get (G_OBJECT (priv->volume), "volume", &volume, NULL);
-
- return volume;
+ return src->priv->volume;
}
guint
GAsyncResult *result,
GError **error)
{
- empathy_implement_finish_void (src,
+ tpaw_implement_finish_void (src,
empathy_audio_src_change_microphone_async);
}
+
+void
+empathy_audio_src_set_mute (EmpathyGstAudioSrc *self,
+ gboolean mute)
+{
+ empathy_audio_set_hw_mute (self, mute);
+
+ g_object_notify (G_OBJECT (self), "mute");
+}