summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorSjoerd Simons <sjoerd.simons@collabora.co.uk>2012-08-28 10:32:59 +0200
committerSjoerd Simons <sjoerd.simons@collabora.co.uk>2012-08-28 10:32:59 +0200
commit9fe582bb3f49fe84498211de5dc2961e194b358e (patch)
tree97e814b1825a0e8b74553a41063d6ce4a1dc8a9e
parent858781b7a0ff7359d27ae5c5ac204ff5e8d96f62 (diff)
Add call responder
-rw-r--r--data/phoenix/uninstalled/Makefile.am3
-rw-r--r--data/phoenix/uninstalled/dbus-1/services/Phoenix.EchoCall.service.in3
-rw-r--r--data/phoenix/uninstalled/telepathy/clients/Phoenix.EchoCall.client.in24
-rw-r--r--src/Makefile.am8
-rw-r--r--src/echo-call.c643
5 files changed, 680 insertions, 1 deletions
diff --git a/data/phoenix/uninstalled/Makefile.am b/data/phoenix/uninstalled/Makefile.am
index ce1989a..baeb7ef 100644
--- a/data/phoenix/uninstalled/Makefile.am
+++ b/data/phoenix/uninstalled/Makefile.am
@@ -1,8 +1,11 @@
service_files = \
+ dbus-1/services/org.freedesktop.Telepathy.Client.Phoenix.EchoCall.service \
dbus-1/services/org.freedesktop.Telepathy.Client.Phoenix.EchoText.service \
dbus-1/services/org.freedesktop.Telepathy.Client.Phoenix.Approver.service \
dbus-1/services/org.freedesktop.Telepathy.Client.Phoenix.Authenticator.service
+
client_files = \
+ telepathy/clients/Phoenix.EchoCall.client \
telepathy/clients/Phoenix.EchoText.client \
telepathy/clients/Phoenix.Approver.client \
telepathy/clients/Phoenix.Authenticator.client
diff --git a/data/phoenix/uninstalled/dbus-1/services/Phoenix.EchoCall.service.in b/data/phoenix/uninstalled/dbus-1/services/Phoenix.EchoCall.service.in
new file mode 100644
index 0000000..b8ee37c
--- /dev/null
+++ b/data/phoenix/uninstalled/dbus-1/services/Phoenix.EchoCall.service.in
@@ -0,0 +1,3 @@
+[D-BUS Service]
+Name=org.freedesktop.Telepathy.Client.Phoenix.EchoText
+Exec=@abs_top_builddir@/src/phoenix-echo-call
diff --git a/data/phoenix/uninstalled/telepathy/clients/Phoenix.EchoCall.client.in b/data/phoenix/uninstalled/telepathy/clients/Phoenix.EchoCall.client.in
new file mode 100644
index 0000000..661b89f
--- /dev/null
+++ b/data/phoenix/uninstalled/telepathy/clients/Phoenix.EchoCall.client.in
@@ -0,0 +1,24 @@
+[org.freedesktop.Telepathy.Client]
+Interfaces=org.freedesktop.Telepathy.Client.Handler
+
+[org.freedesktop.Telepathy.Client.Handler.HandlerChannelFilter 0]
+org.freedesktop.Telepathy.Channel.ChannelType s=org.freedesktop.Telepathy.Channel.Type.Call1
+org.freedesktop.Telepathy.Channel.TargetHandleType u=1
+
+[org.freedesktop.Telepathy.Client.Handler.HandlerChannelFilter 1]
+org.freedesktop.Telepathy.Channel.ChannelType s=org.freedesktop.Telepathy.Channel.Type.Call1
+org.freedesktop.Telepathy.Channel.TargetHandleType u=1
+org.freedesktop.Telepathy.Channel.Type.Call1.InitialAudio b=true
+
+[org.freedesktop.Telepathy.Client.Handler.HandlerChannelFilter 2]
+org.freedesktop.Telepathy.Channel.ChannelType s=org.freedesktop.Telepathy.Channel.Type.Call1
+org.freedesktop.Telepathy.Channel.TargetHandleType u=1
+org.freedesktop.Telepathy.Channel.Type.Call1.InitialVideo b=true
+
+[org.freedesktop.Telepathy.Client.Handler.Capabilities]
+org.freedesktop.Telepathy.Channel.Type.Call1/audio=true
+org.freedesktop.Telepathy.Channel.Type.Call1/video=true
+org.freedesktop.Telepathy.Channel.Type.Call1/ice=true
+org.freedesktop.Telepathy.Channel.Type.Call1/gtalk-p2p=true
+org.freedesktop.Telepathy.Channel.Type.Call1/video/h264=true
+
diff --git a/src/Makefile.am b/src/Makefile.am
index 0ac1926..5ca11d6 100644
--- a/src/Makefile.am
+++ b/src/Makefile.am
@@ -4,8 +4,14 @@ EXTRA_DIST = \
phoenix.py \
util.py
-libexec_PROGRAMS = phoenix-authenticator
+libexec_PROGRAMS = phoenix-authenticator phoenix-echo-call
phoenix_authenticator_SOURCES = authenticator.c
phoenix_authenticator_CFLAGS = @GLIB_CFLAGS@ @TP_GLIB_CFLAGS@
phoenix_authenticator_LDFLAGS = @GLIB_LIBS@ @TP_GLIB_LIBS@
+
+phoenix_echo_call_SOURCES = echo-call.c
+phoenix_echo_call_CFLAGS = @GLIB_CFLAGS@ @TP_GLIB_CFLAGS@ \
+ @TP_FARSTREAM_CFLAGS@
+phoenix_echo_call_LDFLAGS = @GLIB_LIBS@ @TP_GLIB_LIBS@ \
+ @TP_FARSTREAM_LIBS@
diff --git a/src/echo-call.c b/src/echo-call.c
new file mode 100644
index 0000000..fc24b42
--- /dev/null
+++ b/src/echo-call.c
@@ -0,0 +1,643 @@
+/*
+ * call-handler.c
+ * 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 <gst/gst.h>
+#include <telepathy-glib/telepathy-glib.h>
+#include <farstream/fs-element-added-notifier.h>
+#include <farstream/fs-utils.h>
+#include <telepathy-farstream/telepathy-farstream.h>
+
+typedef struct {
+ GstElement *pipeline;
+ guint buswatch;
+ TpChannel *proxy;
+ TfChannel *channel;
+ GList *notifiers;
+
+ guint input_volume;
+ guint output_volume;
+
+ gboolean has_audio_src;
+ gboolean has_video_src;
+
+ GstElement *video_input;
+ GstElement *video_capsfilter;
+
+ guint width;
+ guint height;
+ guint framerate;
+} ChannelContext;
+
+GMainLoop *loop;
+
+static gboolean
+bus_watch_cb (GstBus *bus,
+ GstMessage *message,
+ gpointer user_data)
+{
+ ChannelContext *context = user_data;
+
+ if (context->channel != NULL)
+ tf_channel_bus_message (context->channel, message);
+
+ if (GST_MESSAGE_TYPE (message) == GST_MESSAGE_ERROR)
+ {
+ GError *error = NULL;
+ gchar *debug = NULL;
+ gst_message_parse_error (message, &error, &debug);
+ g_printerr ("ERROR from element %s: %s\n",
+ GST_OBJECT_NAME (message->src), error->message);
+ g_printerr ("Debugging info: %s\n", (debug) ? debug : "none");
+ g_error_free (error);
+ g_free (debug);
+ }
+
+ return TRUE;
+}
+
+static void
+on_audio_output_volume_changed (TfContent *content,
+ GParamSpec *spec,
+ GstElement *volume)
+{
+ guint output_volume = 0;
+
+ g_object_get (content, "output-volume", &output_volume, NULL);
+
+ if (output_volume == 0)
+ return;
+
+ g_object_set (volume, "volume", (double)output_volume / 255.0, NULL);
+}
+
+static void
+src_pad_added_cb (TfContent *content,
+ TpHandle handle,
+ FsStream *stream,
+ GstPad *pad,
+ FsCodec *codec,
+ gpointer user_data)
+{
+ ChannelContext *context = user_data;
+ gchar *cstr = fs_codec_to_string (codec);
+ FsMediaType mtype;
+ GstPad *sinkpad;
+ GstElement *element;
+ GstStateChangeReturn ret;
+
+ g_debug ("New src pad: %s", cstr);
+ g_object_get (content, "media-type", &mtype, NULL);
+
+ switch (mtype)
+ {
+ case FS_MEDIA_TYPE_AUDIO:
+ {
+ GstElement *volume = NULL;
+ gchar *tmp_str = g_strdup_printf ("audioconvert ! audioresample "
+ "! volume name=\"output_volume%s\" "
+ "! audioconvert ! fakesink", cstr);
+ element = gst_parse_bin_from_description (tmp_str,
+ TRUE, NULL);
+ g_free (tmp_str);
+
+ tmp_str = g_strdup_printf ("output_volume%s", cstr);
+ volume = gst_bin_get_by_name (GST_BIN (element), tmp_str);
+ g_free (tmp_str);
+
+ tp_g_signal_connect_object (content, "notify::output-volume",
+ G_CALLBACK (on_audio_output_volume_changed),
+ volume, 0);
+
+ gst_object_unref (volume);
+
+ break;
+ }
+ case FS_MEDIA_TYPE_VIDEO:
+ element = gst_parse_bin_from_description (
+ "ffmpegcolorspace ! videoscale ! fakesink",
+ TRUE, NULL);
+ break;
+ default:
+ g_warning ("Unknown media type");
+ return;
+ }
+
+ gst_bin_add (GST_BIN (context->pipeline), element);
+ sinkpad = gst_element_get_pad (element, "sink");
+ ret = gst_element_set_state (element, GST_STATE_PLAYING);
+ if (ret == GST_STATE_CHANGE_FAILURE)
+ {
+ tp_channel_close_async (TP_CHANNEL (context->proxy), NULL, NULL);
+ g_warning ("Failed to start sink pipeline !?");
+ return;
+ }
+
+ if (GST_PAD_LINK_FAILED (gst_pad_link (pad, sinkpad)))
+ {
+ tp_channel_close_async (TP_CHANNEL (context->proxy), NULL, NULL);
+ g_warning ("Couldn't link sink pipeline !?");
+ return;
+ }
+
+ g_object_unref (sinkpad);
+}
+
+static void
+update_video_parameters (ChannelContext *context, gboolean restart)
+{
+ GstCaps *caps;
+ GstClock *clock;
+
+ if (restart)
+ {
+ /* Assuming the pipeline is in playing state */
+ gst_element_set_locked_state (context->video_input, TRUE);
+ gst_element_set_state (context->video_input, GST_STATE_NULL);
+ }
+
+ g_object_get (context->video_capsfilter, "caps", &caps, NULL);
+ caps = gst_caps_make_writable (caps);
+
+ gst_caps_set_simple (caps,
+ "framerate", GST_TYPE_FRACTION, context->framerate, 1,
+ "width", G_TYPE_INT, context->width,
+ "height", G_TYPE_INT, context->height,
+ NULL);
+
+ g_object_set (context->video_capsfilter, "caps", caps, NULL);
+
+ if (restart)
+ {
+ clock = gst_pipeline_get_clock (GST_PIPELINE (context->pipeline));
+ /* Need to reset the clock if we set the pipeline back to ready by hand */
+ if (clock != NULL)
+ {
+ gst_element_set_clock (context->video_input, clock);
+ g_object_unref (clock);
+ }
+
+ gst_element_set_locked_state (context->video_input, FALSE);
+ gst_element_sync_state_with_parent (context->video_input);
+ }
+}
+
+static void
+on_video_framerate_changed (TfContent *content,
+ GParamSpec *spec,
+ ChannelContext *context)
+{
+ guint framerate;
+
+ g_object_get (content, "framerate", &framerate, NULL);
+
+ if (framerate != 0)
+ context->framerate = framerate;
+
+ update_video_parameters (context, FALSE);
+}
+
+static void
+on_video_resolution_changed (TfContent *content,
+ guint width,
+ guint height,
+ ChannelContext *context)
+{
+ g_assert (width > 0 && height > 0);
+
+ context->width = width;
+ context->height = height;
+
+ update_video_parameters (context, TRUE);
+}
+
+static void
+on_audio_input_volume_changed (TfContent *content,
+ GParamSpec *spec,
+ ChannelContext *context)
+{
+ GstElement *volume;
+ guint input_volume = 0;
+
+ g_object_get (content, "request-input-volume", &input_volume, NULL);
+
+ if (input_volume == 0)
+ return;
+
+ volume = gst_bin_get_by_name (GST_BIN (context->pipeline), "input_volume");
+ g_object_set (volume, "volume", (double)input_volume / 255.0, NULL);
+ gst_object_unref (volume);
+}
+
+static GstElement *
+setup_audio_source (ChannelContext *context, TfContent *content)
+{
+ GstElement *result;
+ GstElement *volume;
+ gint input_volume = 0;
+
+ result = gst_parse_bin_from_description (
+ "audiotestsrc is-live=true ! audio/x-raw-int,rate=8000 ! queue"
+ " ! audioconvert ! audioresample"
+ " ! volume name=input_volume ! audioconvert ",
+ TRUE, NULL);
+
+ /* FIXME Need to handle both requested/reported */
+ /* TODO Volume control should be handled in FsIo */
+ g_object_get (content,
+ "requested-input-volume", &input_volume,
+ NULL);
+
+ if (input_volume >= 0)
+ {
+ volume = gst_bin_get_by_name (GST_BIN (result), "input_volume");
+ g_debug ("Requested volume is: %i", input_volume);
+ g_object_set (volume, "volume", (double)input_volume / 255.0, NULL);
+ gst_object_unref (volume);
+ }
+
+ g_signal_connect (content, "notify::request-input-volume",
+ G_CALLBACK (on_audio_input_volume_changed),
+ context);
+
+ return result;
+}
+
+static GstElement *
+setup_video_source (ChannelContext *context, TfContent *content)
+{
+ GstElement *result, *capsfilter;
+ GstCaps *caps;
+ guint framerate = 0, width = 0, height = 0;
+
+ result = gst_parse_bin_from_description_full (
+ "videotestsrc is-live=1 ! videomaxrate ! videoscale ! colorspace ! capsfilter name=c",
+ TRUE, NULL, GST_PARSE_FLAG_FATAL_ERRORS, NULL);
+
+ g_assert (result);
+ capsfilter = gst_bin_get_by_name (GST_BIN (result), "c");
+
+ g_object_get (content,
+ "framerate", &framerate,
+ "width", &width,
+ "height", &height,
+ NULL);
+
+ if (framerate == 0)
+ framerate = 15;
+
+ if (width == 0 || height == 0)
+ {
+ width = 320;
+ height = 240;
+ }
+
+ context->framerate = framerate;
+ context->width = width;
+ context->height = height;
+
+ caps = gst_caps_new_simple ("video/x-raw-yuv",
+ "width", G_TYPE_INT, width,
+ "height", G_TYPE_INT, height,
+ "framerate", GST_TYPE_FRACTION, framerate, 1,
+ NULL);
+
+ g_object_set (G_OBJECT (capsfilter), "caps", caps, NULL);
+
+ gst_caps_unref (caps);
+
+ context->video_input = result;
+ context->video_capsfilter = capsfilter;
+
+ g_signal_connect (content, "notify::framerate",
+ G_CALLBACK (on_video_framerate_changed),
+ context);
+
+ g_signal_connect (content, "resolution-changed",
+ G_CALLBACK (on_video_resolution_changed),
+ context);
+
+ return result;
+}
+
+static gboolean
+start_sending_cb (TfContent *content, gpointer user_data)
+{
+ ChannelContext *context = user_data;
+ GstPad *srcpad, *sinkpad;
+ FsMediaType mtype;
+ GstElement *element;
+ GstStateChangeReturn ret;
+ gboolean res = FALSE;
+
+ g_debug ("Start sending");
+
+ g_object_get (content,
+ "sink-pad", &sinkpad,
+ "media-type", &mtype,
+ NULL);
+
+ switch (mtype)
+ {
+ case FS_MEDIA_TYPE_AUDIO:
+ if (context->has_audio_src)
+ goto out;
+
+ element = setup_audio_source (context, content);
+ break;
+ case FS_MEDIA_TYPE_VIDEO:
+ if (context->has_video_src)
+ goto out;
+
+ element = setup_video_source (context, content);
+ break;
+ default:
+ g_warning ("Unknown media type");
+ goto out;
+ }
+
+
+ gst_bin_add (GST_BIN (context->pipeline), element);
+ srcpad = gst_element_get_pad (element, "src");
+
+ if (GST_PAD_LINK_FAILED (gst_pad_link (srcpad, sinkpad)))
+ {
+ tp_channel_close_async (TP_CHANNEL (context->proxy), NULL, NULL);
+ g_warning ("Couldn't link source pipeline !?");
+ goto out2;
+ }
+
+ ret = gst_element_set_state (element, GST_STATE_PLAYING);
+ if (ret == GST_STATE_CHANGE_FAILURE)
+ {
+ tp_channel_close_async (TP_CHANNEL (context->proxy), NULL, NULL);
+ g_warning ("source pipeline failed to start!?");
+ goto out2;
+ }
+
+ res = TRUE;
+
+out2:
+ g_object_unref (srcpad);
+out:
+ g_object_unref (sinkpad);
+
+ return res;
+}
+
+static void
+content_added_cb (TfChannel *channel,
+ TfContent *content,
+ gpointer user_data)
+{
+ ChannelContext *context = user_data;
+
+ g_debug ("Content added");
+
+ g_signal_connect (content, "src-pad-added",
+ G_CALLBACK (src_pad_added_cb), context);
+ g_signal_connect (content, "start-sending",
+ G_CALLBACK (start_sending_cb), context);
+}
+
+static void
+conference_added_cb (TfChannel *channel,
+ GstElement *conference,
+ gpointer user_data)
+{
+ ChannelContext *context = user_data;
+ GKeyFile *keyfile;
+
+ g_debug ("Conference added");
+
+ /* Add notifier to set the various element properties as needed */
+ keyfile = fs_utils_get_default_element_properties (conference);
+ if (keyfile != NULL)
+ {
+ FsElementAddedNotifier *notifier;
+ g_debug ("Loaded default codecs for %s", GST_ELEMENT_NAME (conference));
+
+ notifier = fs_element_added_notifier_new ();
+ fs_element_added_notifier_set_properties_from_keyfile (notifier, keyfile);
+ fs_element_added_notifier_add (notifier, GST_BIN (context->pipeline));
+
+ context->notifiers = g_list_prepend (context->notifiers, notifier);
+ }
+
+
+ gst_bin_add (GST_BIN (context->pipeline), conference);
+ gst_element_set_state (conference, GST_STATE_PLAYING);
+}
+
+
+static void
+conference_removed_cb (TfChannel *channel,
+ GstElement *conference,
+ gpointer user_data)
+{
+ ChannelContext *context = user_data;
+
+ gst_element_set_locked_state (conference, TRUE);
+ gst_element_set_state (conference, GST_STATE_NULL);
+ gst_bin_remove (GST_BIN (context->pipeline), conference);
+}
+
+static gboolean
+dump_pipeline_cb (gpointer data)
+{
+ ChannelContext *context = data;
+
+ GST_DEBUG_BIN_TO_DOT_FILE_WITH_TS (GST_BIN (context->pipeline),
+ GST_DEBUG_GRAPH_SHOW_ALL,
+ "call-handler");
+
+ return TRUE;
+}
+
+static void
+new_tf_channel_cb (GObject *source,
+ GAsyncResult *result,
+ gpointer user_data)
+{
+ ChannelContext *context = user_data;
+ GError *error = NULL;
+
+ g_debug ("New TfChannel");
+
+ context->channel = tf_channel_new_finish (source, result, &error);
+
+ if (context->channel == NULL)
+ {
+ g_error ("Failed to create channel: %s", error->message);
+ g_clear_error (&error);
+ }
+
+ g_debug ("Adding timeout");
+ g_timeout_add_seconds (5, dump_pipeline_cb, context);
+
+ g_signal_connect (context->channel, "fs-conference-added",
+ G_CALLBACK (conference_added_cb), context);
+
+
+ g_signal_connect (context->channel, "fs-conference-removed",
+ G_CALLBACK (conference_removed_cb), context);
+
+ g_signal_connect (context->channel, "content-added",
+ G_CALLBACK (content_added_cb), context);
+}
+
+static void
+proxy_invalidated_cb (TpProxy *proxy,
+ guint domain,
+ gint code,
+ gchar *message,
+ gpointer user_data)
+{
+ ChannelContext *context = user_data;
+
+ g_debug ("Channel closed");
+ if (context->pipeline != NULL)
+ {
+ gst_element_set_state (context->pipeline, GST_STATE_NULL);
+ g_object_unref (context->pipeline);
+ }
+
+ if (context->channel != NULL)
+ g_object_unref (context->channel);
+
+ g_list_foreach (context->notifiers, (GFunc) g_object_unref, NULL);
+ g_list_free (context->notifiers);
+
+ g_object_unref (context->proxy);
+
+ g_slice_free (ChannelContext, context);
+
+ g_main_loop_quit (loop);
+}
+
+static void
+new_call_channel_cb (TpSimpleHandler *handler,
+ TpAccount *account,
+ TpConnection *connection,
+ GList *channels,
+ GList *requests_satisfied,
+ gint64 user_action_time,
+ TpHandleChannelsContext *handler_context,
+ gpointer user_data)
+{
+ ChannelContext *context;
+ TpChannel *proxy;
+ GstBus *bus;
+ GstElement *pipeline;
+ GstStateChangeReturn ret;
+
+ g_debug ("New channel");
+
+ proxy = channels->data;
+
+ pipeline = gst_pipeline_new (NULL);
+
+ ret = gst_element_set_state (pipeline, GST_STATE_PLAYING);
+
+ if (ret == GST_STATE_CHANGE_FAILURE)
+ {
+ tp_channel_close_async (TP_CHANNEL (proxy), NULL, NULL);
+ g_object_unref (pipeline);
+ g_warning ("Failed to start an empty pipeline !?");
+ return;
+ }
+
+ context = g_slice_new0 (ChannelContext);
+ context->pipeline = pipeline;
+
+ bus = gst_pipeline_get_bus (GST_PIPELINE (pipeline));
+ context->buswatch = gst_bus_add_watch (bus, bus_watch_cb, context);
+ g_object_unref (bus);
+
+ tf_channel_new_async (proxy, new_tf_channel_cb, context);
+
+ tp_handle_channels_context_accept (handler_context);
+
+ tp_cli_channel_type_call_call_accept (proxy, -1,
+ NULL, NULL, NULL, NULL);
+
+ context->proxy = g_object_ref (proxy);
+ g_signal_connect (proxy, "invalidated",
+ G_CALLBACK (proxy_invalidated_cb),
+ context);
+}
+
+int
+main (int argc, char **argv)
+{
+ TpBaseClient *client;
+ TpAccountManager *am;
+
+ g_type_init ();
+ gst_init (&argc, &argv);
+
+ loop = g_main_loop_new (NULL, FALSE);
+
+ am = tp_account_manager_dup ();
+
+ client = tp_simple_handler_new_with_am (am,
+ FALSE,
+ FALSE,
+ "Phoenix.EchoCall",
+ TRUE,
+ new_call_channel_cb,
+ NULL,
+ NULL);
+
+ tp_base_client_take_handler_filter (client,
+ tp_asv_new (
+ TP_PROP_CHANNEL_CHANNEL_TYPE, G_TYPE_STRING,
+ TP_IFACE_CHANNEL_TYPE_CALL,
+ TP_PROP_CHANNEL_TARGET_HANDLE_TYPE, G_TYPE_UINT,
+ TP_HANDLE_TYPE_CONTACT,
+ TP_PROP_CHANNEL_TYPE_CALL_INITIAL_AUDIO, G_TYPE_BOOLEAN,
+ TRUE,
+ NULL));
+
+ tp_base_client_take_handler_filter (client,
+ tp_asv_new (
+ TP_PROP_CHANNEL_CHANNEL_TYPE, G_TYPE_STRING,
+ TP_IFACE_CHANNEL_TYPE_CALL,
+ TP_PROP_CHANNEL_TARGET_HANDLE_TYPE, G_TYPE_UINT,
+ TP_HANDLE_TYPE_CONTACT,
+ TP_PROP_CHANNEL_TYPE_CALL_INITIAL_VIDEO, G_TYPE_BOOLEAN,
+ TRUE,
+ NULL));
+
+ tp_base_client_add_handler_capabilities_varargs (client,
+ TP_IFACE_CHANNEL_TYPE_CALL "/video/h264",
+ TP_IFACE_CHANNEL_TYPE_CALL "/shm",
+ TP_IFACE_CHANNEL_TYPE_CALL "/ice",
+ TP_IFACE_CHANNEL_TYPE_CALL "/gtalk-p2p",
+ NULL);
+
+ tp_base_client_register (client, NULL);
+
+ g_main_loop_run (loop);
+
+ g_object_unref (am);
+ g_object_unref (client);
+ g_main_loop_unref (loop);
+
+ return 0;
+}