*/
#include "config.h"
+#include "empathy-audio-src.h"
-#include <stdio.h>
-#include <stdlib.h>
-
-#include <gst/interfaces/mixer.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_MUTE,
- PROP_RMS_LEVEL,
- PROP_PEAK_LEVEL,
PROP_MICROPHONE,
};
{
gboolean dispose_has_run;
GstElement *src;
- 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;
- /* the mixer track on src we follow and adjust */
- GstMixerTrack *track;
+ gboolean have_stream_volume;
GMutex lock;
- guint level_idle_id;
guint volume_idle_id;
};
(G_TYPE_INSTANCE_GET_PRIVATE ((o), EMPATHY_TYPE_GST_AUDIO_SRC, \
EmpathyGstAudioSrcPrivate))
-/* There is no predefined maximum channels by gstreamer, just pick 32, which is
- * the same as the pulseaudio maximum */
-#define MAX_MIC_CHANNELS 32
+
+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)
{
- g_mutex_lock (&self->priv->lock);
- /* If there is no mixer available ignore the setting */
- if (self->priv->track == NULL)
- goto out;
+ if (mute == self->priv->mute)
+ return;
- gst_mixer_set_mute (GST_MIXER (self->priv->src), self->priv->track, mute);
+ 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);
-out:
- g_mutex_unlock (&self->priv->lock);
self->priv->mute = mute;
}
static gboolean
empathy_audio_src_get_hw_mute (EmpathyGstAudioSrc *self)
{
- gboolean result = self->priv->mute;
-
- g_mutex_lock (&self->priv->lock);
- if (self->priv->track == NULL)
- goto out;
-
- result = GST_MIXER_TRACK_HAS_FLAG (self->priv->track, GST_MIXER_TRACK_MUTE);
-out:
- g_mutex_unlock (&self->priv->lock);
+ gboolean result;
+ g_object_get (self->priv->src, "mute", &result, NULL);
return result;
}
empathy_audio_src_set_hw_volume (EmpathyGstAudioSrc *self,
gdouble volume)
{
- gint volumes[MAX_MIC_CHANNELS];
- int i;
-
- g_mutex_lock (&self->priv->lock);
- /* If there is no mixer available ignore the setting */
- if (self->priv->track == NULL)
- goto out;
-
- for (i = 0; i < MAX_MIC_CHANNELS; i++)
- volumes[i] = self->priv->track->max_volume * volume;
-
- gst_mixer_set_volume (GST_MIXER (self->priv->src),
- self->priv->track, volumes);
-
-out:
- g_mutex_unlock (&self->priv->lock);
+ 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)
{
- gint volumes[MAX_MIC_CHANNELS];
- gdouble result = self->priv->volume;
-
- g_mutex_lock (&self->priv->lock);
- if (self->priv->track == NULL)
- goto out;
-
- gst_mixer_get_volume (GST_MIXER (self->priv->src),
- self->priv->track, volumes);
- result = volumes[0]/(gdouble)self->priv->track->max_volume;
-
-out:
- g_mutex_unlock (&self->priv->lock);
+ gdouble result;
+ g_object_get (self->priv->src, "volume", &result, NULL);
return result;
}
source_output_idx, empathy_audio_src_get_current_mic_cb, self);
}
-static GstMixerTrack *
-empathy_audio_src_get_track (GstElement *src)
-{
- const GList *t;
- GstMixerTrack *track = NULL;
-
- if (!gst_element_implements_interface (src, GST_TYPE_MIXER))
- {
- g_warning ("No mixer interface implementation, can't control volume");
- return NULL;
- }
-
- for (t = gst_mixer_list_tracks (GST_MIXER (src));
- t != NULL; t = g_list_next (t))
- {
- GstMixerTrack *tr = t->data;
- if (!tp_strdiff (tr->label, "Master"))
- {
- track = tr;
- break;
- }
- }
-
- if (track == NULL)
- {
- g_warning ("No suitable track found");
- }
- else if (track->num_channels > MAX_MIC_CHANNELS)
- {
- g_warning ("Microphones with more then %d channels not supported ",
- MAX_MIC_CHANNELS);
- track = NULL;
- }
-
- return track;
-}
-
static GstElement *
create_src (void)
{
/* Use pulsesrc as default */
src = gst_element_factory_make ("pulsesrc", NULL);
if (src == NULL)
- return NULL;
+ {
+ g_warning ("Missing 'pulsesrc' element");
+ return NULL;
+ }
- empathy_call_set_stream_properties (src, TRUE);
+ empathy_audio_set_stream_properties (src, TRUE);
/* Set latency (buffering on the PulseAudio side) of 20ms */
g_object_set (src, "buffer-time", (gint64) 20000, NULL);
{
EmpathyGstAudioSrcPrivate *priv = EMPATHY_GST_AUDIO_SRC_GET_PRIVATE (obj);
GstPad *ghost, *src;
- GstElement *capsfilter;
- GstCaps *caps;
obj->priv = priv;
- priv->peak_level = -G_MAXDOUBLE;
g_mutex_init (&priv->lock);
priv->volume = 1.0;
if (priv->src == NULL)
return;
- gst_bin_add (GST_BIN (obj), priv->src);
+ 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;
+ }
- /* 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);
+ 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 (capsfilter, 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,
case PROP_MUTE:
g_value_set_boolean (value, priv->mute);
break;
- case PROP_PEAK_LEVEL:
- g_mutex_lock (&priv->lock);
- g_value_set_double (value, priv->peak_level);
- g_mutex_unlock (&priv->lock);
- break;
- case PROP_RMS_LEVEL:
- g_mutex_lock (&priv->lock);
- g_value_set_double (value, priv->rms_level);
- g_mutex_unlock (&priv->lock);
- break;
case PROP_MICROPHONE:
g_value_set_uint (value, priv->source_idx);
break;
*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_PARAM_READWRITE | G_PARAM_STATIC_STRINGS);
g_object_class_install_property (object_class, PROP_MUTE, 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_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->level_idle_id != 0)
- g_source_remove (priv->level_idle_id);
- priv->level_idle_id = 0;
-
if (priv->volume_idle_id != 0)
g_source_remove (priv->volume_idle_id);
priv->volume_idle_id = 0;
}
static gboolean
-empathy_audio_src_levels_updated (gpointer user_data)
-{
- EmpathyGstAudioSrc *self = EMPATHY_GST_AUDIO_SRC (user_data);
- EmpathyGstAudioSrcPrivate *priv = EMPATHY_GST_AUDIO_SRC_GET_PRIVATE (self);
-
- g_mutex_lock (&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->level_idle_id = 0;
-
- g_mutex_unlock (&priv->lock);
-
- return FALSE;
-}
-
-static gboolean
-empathy_audio_src_volume_changed (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);
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->level_idle_id == 0)
- priv->level_idle_id = g_idle_add (
- empathy_audio_src_levels_updated, self);
-
- g_mutex_unlock (&priv->lock);
- }
- else if (GST_MESSAGE_TYPE (message) == GST_MESSAGE_ELEMENT &&
- GST_MESSAGE_SRC (message) == GST_OBJECT (priv->src))
- {
- GstMixerTrack *track = NULL;
-
- /* Listen for mute or volume changes on the src element */
- if (gst_mixer_message_get_type (message) ==
- GST_MIXER_MESSAGE_VOLUME_CHANGED)
- gst_mixer_message_parse_volume_changed (message, &track,
- NULL, NULL);
-
- if (gst_mixer_message_get_type (message) ==
- GST_MIXER_MESSAGE_MUTE_TOGGLED)
- gst_mixer_message_parse_mute_toggled (message, &track, NULL);
-
- g_mutex_lock (&priv->lock);
-
- if (track != NULL && track == priv->track && priv->volume_idle_id == 0)
- priv->volume_idle_id = g_idle_add (
- empathy_audio_src_volume_changed, self);
-
- g_mutex_unlock (&priv->lock);
- }
- else if (GST_MESSAGE_TYPE (message) == GST_MESSAGE_STATE_CHANGED &&
- GST_MESSAGE_SRC (message) == GST_OBJECT (priv->src))
- {
- GstState old, new;
-
- gst_message_parse_state_changed (message, &old, &new, NULL);
+ EmpathyGstAudioSrc *self = EMPATHY_GST_AUDIO_SRC (user_data);
- /* GstMixer is only available in state >= READY, so only start
- * controlling the source element when going to ready state and stop
- * doing so when going below ready. Furthermore once we have mixer read
- * the current volume level from it and remove the settings done by
- * Empathy. We want to pick up the level pulseaudio saved */
- if (old == GST_STATE_NULL && new == GST_STATE_READY)
- {
- g_mutex_lock (&priv->lock);
- priv->track = empathy_audio_src_get_track (priv->src);
- if (priv->track != NULL)
- priv->volume_idle_id = g_idle_add (
- empathy_audio_src_volume_changed, self);
- g_mutex_unlock (&priv->lock);
- }
- else if (old == GST_STATE_READY && new == GST_STATE_NULL)
- {
- g_mutex_lock (&priv->lock);
- priv->track = NULL;
- 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 *
gboolean enable)
{
DEBUG ("Src echo cancellation setting: %s", enable ? "on" : "off");
- empathy_call_set_stream_properties (src->priv->src, enable);
+ empathy_audio_set_stream_properties (src->priv->src, enable);
}
void
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");
+}