# HG changeset patch # User jakub.adam@ktknet.cz # Date 1301011303 0 # Node ID f4a1df87ce91bcabbefd34343e80074e5ecc4837 # Parent 585afe54508c180b469ec86adf07ac2ae886f2ec This patch adds to Pidgin's vvconfig plugin a simple dialog, where user can test if the audio input and output that he selected are actually working. Two parameters can be also adjusted here: * Output volume (also changeable during call) * Threshold of silence for silence suppression (see ticket #13180) I developed this feature in conjunction with #13180, but the dialog can also be useful on its own, as there is now no way to try the voice configuration prior the call. If requested, I can split the path. Fixes #13182. committer: John Bailey diff -r 585afe54508c -r f4a1df87ce91 pidgin/plugins/vvconfig.c --- a/pidgin/plugins/vvconfig.c Thu Mar 24 23:46:21 2011 +0000 +++ b/pidgin/plugins/vvconfig.c Fri Mar 25 00:01:43 2011 +0000 @@ -505,19 +505,22 @@ gtk_widget_destroy(GTK_WIDGET(window)); } +typedef GtkWidget *(*FrameCreateCb)(PurplePlugin *plugin); + static void show_config(PurplePluginAction *action) { if (!window) { + FrameCreateCb create_frame = action->user_data; GtkWidget *vbox = gtk_vbox_new(FALSE, PIDGIN_HIG_BORDER); GtkWidget *hbox = gtk_hbox_new(FALSE, PIDGIN_HIG_BORDER); - GtkWidget *config_frame = get_plugin_config_frame(NULL); + GtkWidget *config_frame = create_frame(NULL); GtkWidget *close = gtk_button_new_from_stock(GTK_STOCK_CLOSE); gtk_container_add(GTK_CONTAINER(vbox), config_frame); gtk_container_add(GTK_CONTAINER(vbox), hbox); - window = pidgin_create_window(_("Voice/Video Settings"), - PIDGIN_HIG_BORDER, NULL, TRUE); + window = pidgin_create_window(action->label, + PIDGIN_HIG_BORDER, NULL, FALSE); g_signal_connect(G_OBJECT(window), "destroy", G_CALLBACK(config_destroy), NULL); g_signal_connect(G_OBJECT(close), "clicked", @@ -531,6 +534,180 @@ gtk_window_present(GTK_WINDOW(window)); } +static GstElement * +create_pipeline() +{ + GstElement *pipeline = gst_pipeline_new("voicetest"); + GstElement *src = create_audio_src(NULL, NULL, NULL); + GstElement *sink = create_audio_sink(NULL, NULL, NULL); + GstElement *volume = gst_element_factory_make("volume", "volume"); + GstElement *level = gst_element_factory_make("level", "level"); + GstElement *valve = gst_element_factory_make("valve", "valve"); + + gst_bin_add_many(GST_BIN(pipeline), src, volume, level, valve, sink, NULL); + gst_element_link_many(src, volume, level, valve, sink, NULL); + + gst_element_set_state(GST_ELEMENT(pipeline), GST_STATE_PLAYING); + + return pipeline; +} + +static void +on_volume_change_cb(GtkRange *range, GstBin *pipeline) +{ + GstElement *volume; + + g_return_if_fail(pipeline != NULL); + + volume = gst_bin_get_by_name(pipeline, "volume"); + g_object_set(volume, "volume", gtk_range_get_value(range) / 10.0, NULL); +} + +static gdouble +gst_msg_db_to_percent(GstMessage *msg, gchar *value_name) +{ + const GValue *list; + const GValue *value; + gdouble value_db; + gdouble percent; + + list = gst_structure_get_value( + gst_message_get_structure(msg), value_name); + value = gst_value_list_get_value(list, 0); + value_db = g_value_get_double(value); + percent = pow(10, value_db / 20); + return (percent > 1.0) ? 1.0 : percent; +} + +typedef struct +{ + GtkProgressBar *level; + GtkRange *threshold; +} BusCbCtx; + +static gboolean +gst_bus_cb(GstBus *bus, GstMessage *msg, BusCbCtx *ctx) +{ + if (GST_MESSAGE_TYPE(msg) == GST_MESSAGE_ELEMENT && + gst_structure_has_name(msg->structure, "level")) { + + GstElement *src = GST_ELEMENT(GST_MESSAGE_SRC(msg)); + gchar *name = gst_element_get_name(src); + + if (!strcmp(name, "level")) { + gdouble percent; + gdouble threshold; + GstElement *valve; + + percent = gst_msg_db_to_percent(msg, "rms"); + gtk_progress_bar_set_fraction(ctx->level, percent * 5); + + percent = gst_msg_db_to_percent(msg, "decay"); + threshold = gtk_range_get_value(ctx->threshold) / 100.0; + valve = gst_bin_get_by_name(GST_BIN(GST_ELEMENT_PARENT(src)), "valve"); + g_object_set(valve, "drop", (percent < threshold), NULL); + g_object_set(ctx->level, + "text", (percent < threshold) ? _("DROP") : " ", NULL); + } + + g_free(name); + } + + return TRUE; +} + +static void +voice_test_frame_destroy_cb(GtkObject *w, GstElement *pipeline) +{ + g_return_if_fail(GST_IS_ELEMENT(pipeline)); + + gst_element_set_state(pipeline, GST_STATE_NULL); + gst_object_unref(pipeline); +} + +static void +volume_scale_destroy_cb(GtkRange *volume, gpointer nul) +{ + purple_prefs_set_int("/purple/media/audio/volume/input", + gtk_range_get_value(volume)); +} + +static gchar* +threshold_value_format_cb(GtkScale *scale, gdouble value) +{ + return g_strdup_printf ("%.*f%%", gtk_scale_get_digits(scale), value); +} + +static void +threshold_scale_destroy_cb(GtkRange *threshold, gpointer nul) +{ + purple_prefs_set_int("/purple/media/audio/silence_threshold", + gtk_range_get_value(threshold)); +} + +static GtkWidget * +get_voice_test_frame(PurplePlugin *plugin) +{ + GtkWidget *vbox = gtk_vbox_new(FALSE, PIDGIN_HIG_BORDER); + GtkWidget *level = gtk_progress_bar_new(); + GtkWidget *volume = gtk_hscale_new_with_range(0, 100, 1); + GtkWidget *threshold = gtk_hscale_new_with_range(0, 100, 1); + GtkWidget *label; + GtkTable *table = GTK_TABLE(gtk_table_new(2, 2, FALSE)); + + GstElement *pipeline; + GstBus *bus; + BusCbCtx *ctx; + + g_object_set(vbox, "width-request", 500, NULL); + + gtk_table_set_row_spacings(table, PIDGIN_HIG_BOX_SPACE); + gtk_table_set_col_spacings(table, PIDGIN_HIG_BOX_SPACE); + + label = gtk_label_new(_("Volume:")); + g_object_set(label, "xalign", 0.0, NULL); + gtk_table_attach(table, label, 0, 1, 0, 1, GTK_FILL, 0, 0, 0); + gtk_table_attach_defaults(table, volume, 1, 2, 0, 1); + label = gtk_label_new(_("Silence threshold:")); + g_object_set(label, "xalign", 0.0, "yalign", 1.0, NULL); + gtk_table_attach(table, label, 0, 1, 1, 2, GTK_FILL, GTK_FILL, 0, 0); + gtk_table_attach_defaults(table, threshold, 1, 2, 1, 2); + + gtk_container_add(GTK_CONTAINER(vbox), level); + gtk_container_add(GTK_CONTAINER(vbox), GTK_WIDGET(table)); + gtk_widget_show_all(vbox); + + pipeline = create_pipeline(); + bus = gst_pipeline_get_bus(GST_PIPELINE(pipeline)); + gst_bus_add_signal_watch(bus); + ctx = g_new(BusCbCtx, 1); + ctx->level = GTK_PROGRESS_BAR(level); + ctx->threshold = GTK_RANGE(threshold); + g_signal_connect_data(bus, "message", G_CALLBACK(gst_bus_cb), + ctx, (GClosureNotify)g_free, 0); + gst_object_unref(bus); + + g_signal_connect(volume, "value-changed", + (GCallback)on_volume_change_cb, pipeline); + + gtk_range_set_value(GTK_RANGE(volume), + purple_prefs_get_int("/purple/media/audio/volume/input")); + gtk_widget_set(volume, "draw-value", FALSE, NULL); + + gtk_range_set_value(GTK_RANGE(threshold), + purple_prefs_get_int("/purple/media/audio/silence_threshold")); + + g_signal_connect(vbox, "destroy", + G_CALLBACK(voice_test_frame_destroy_cb), pipeline); + g_signal_connect(volume, "destroy", + G_CALLBACK(volume_scale_destroy_cb), NULL); + g_signal_connect(threshold, "format-value", + G_CALLBACK(threshold_value_format_cb), NULL); + g_signal_connect(threshold, "destroy", + G_CALLBACK(threshold_scale_destroy_cb), NULL); + + return vbox; +} static GList * actions(PurplePlugin *plugin, gpointer context) @@ -538,8 +715,14 @@ GList *l = NULL; PurplePluginAction *act = NULL; - act = purple_plugin_action_new(_("Voice and Video Settings"), + act = purple_plugin_action_new(_("Input and Output Settings"), show_config); + act->user_data = get_plugin_config_frame; + l = g_list_append(l, act); + + act = purple_plugin_action_new(_("Microphone Test"), + show_config); + act->user_data = get_voice_test_frame; l = g_list_append(l, act); return l;