diff options
Diffstat (limited to 'telepathy-farstream')
29 files changed, 8520 insertions, 0 deletions
diff --git a/telepathy-farstream/Makefile.am b/telepathy-farstream/Makefile.am new file mode 100644 index 000000000..06d3392aa --- /dev/null +++ b/telepathy-farstream/Makefile.am @@ -0,0 +1,106 @@ +tfincludedir = $(includedir)/telepathy-farstream-1/telepathy-farstream +tfinclude_HEADERS = \ + telepathy-farstream.h \ + channel.h \ + content.h + +apisources = \ + content.c \ + channel.c + +libtelepathy_farstream_1_la_SOURCES = \ + $(tfinclude_HEADERS) \ + $(apisources) \ + content-priv.h \ + channel-priv.h \ + call-channel.c \ + call-channel.h \ + call-content.h \ + call-content.c \ + call-stream.h \ + call-stream.c \ + call-priv.h \ + utils.h + +nodist_libtelepathy_farstream_1_la_SOURCES = $(BUILT_SOURCES) + +lib_LTLIBRARIES = libtelepathy-farstream-1.la + +pkgconfigdir = ${libdir}/pkgconfig +pkgconfig_DATA = telepathy-farstream-1.pc + +AM_CPPFLAGS = \ + -I$(top_srcdir) \ + -I$(top_builddir) \ + $(GLIB_CFLAGS) \ + $(DBUS_CFLAGS) \ + $(GST_CFLAGS) \ + $(FARSTREAM_CFLAGS) \ + $(NULL) + +AM_CFLAGS = \ + -DG_LOG_DOMAIN=\"tp-fs\" \ + $(ERROR_CFLAGS) + +libtelepathy_farstream_1_la_LIBADD = \ + $(top_builddir)/telepathy-glib/libtelepathy-glib-1.la \ + $(top_builddir)/telepathy-glib/libtelepathy-glib-1-dbus.la \ + $(top_builddir)/telepathy-glib/libtelepathy-glib-1-core.la \ + $(GLIB_LIBS) \ + $(DBUS_LIBS) \ + $(GST_LIBS) \ + $(FARSTREAM_LIBS) \ + $(NULL) + +libtelepathy_farstream_1_la_LDFLAGS = -no-undefined \ + -export-symbols-regex "^tf_(init|content_|channel_).*" \ + -version-info "$(TPFS_LT_CURRENT)":"$(TPFS_LT_REVISION)":"$(TPFS_LT_AGE)" + +-include $(INTROSPECTION_MAKEFILE) +INTROSPECTION_GIRS = +INTROSPECTION_SCANNER_ARGS = \ + --add-include-path=$(srcdir) \ + --add-include-path=$(top_builddir)/telepathy-glib \ + $(NULL) +INTROSPECTION_COMPILER_ARGS = \ + --includedir=$(srcdir) \ + --includedir=$(top_builddir)/telepathy-glib \ + $(NULL) + +if HAVE_INTROSPECTION +introspection_sources = $(tfinclude_HEADERS) $(apisources) + +TelepathyFarstream-1.gir: libtelepathy-farstream-1.la +TelepathyFarstream_1_gir_INCLUDES = \ + GObject-2.0 \ + Gst-1.0 \ + Farstream-0.2 \ + TelepathyGLib-1 \ + $(NULL) +TelepathyFarstream_1_gir_PACKAGES = \ + glib-2.0 \ + gobject-2.0 \ + gio-2.0 \ + telepathy-glib-1 \ + $(NULL) +TelepathyFarstream_1_gir_EXPORT_PACKAGES = \ + telepathy-farstream-1 \ + $(NULL) +TelepathyFarstream_1_gir_CFLAGS = $(AM_CPPFLAGS) +TelepathyFarstream_1_gir_LIBS = libtelepathy-farstream-1.la +TelepathyFarstream_1_gir_FILES = $(introspection_sources) +TelepathyFarstream_1_gir_SCANNERFLAGS = --identifier-prefix=tf_ --identifier-prefix=Tf +INTROSPECTION_GIRS += TelepathyFarstream-1.gir +INTROSPECTION_SCANNER_ENV = \ + PKG_CONFIG_PATH=$(builddir):$(top_builddir)/telepathy-glib:$${PKG_CONFIG_PATH} + +girdir = $(datadir)/gir-1.0 +gir_DATA = $(INTROSPECTION_GIRS) + +typelibdir = $(libdir)/girepository-1.0 +typelib_DATA = $(INTROSPECTION_GIRS:.gir=.typelib) + +CLEANFILES = $(gir_DATA) $(typelib_DATA) +endif + +SUBDIRS = . examples diff --git a/telepathy-farstream/NEWS b/telepathy-farstream/NEWS new file mode 100644 index 000000000..7363226e7 --- /dev/null +++ b/telepathy-farstream/NEWS @@ -0,0 +1,157 @@ +See the top-level NEWS file for changes that took place after +telepathy-farstream was merged into telepathy-glib. + +telepathy-farstream 0.6.0 (25 Sep 2012) +======================================= + +- Port to the final Farstream 0.2 API + +telepathy-farstream 0.5.0 (13 Sep 2012) +======================================= + +- Port to GStreamer 1.0 and Farstream 0.2 +- Set RemoteContact when accepting & updating media descriptions + +telepathy-farstream 0.4.0 (4 Apr 2012) +====================================== + +This is the start of a new stable branch. + +There have (basically) been no changes since 0.2.3. + +There are too many changes since the last stable release. For more +details, look at the git log. + +telepathy-farstream 0.2.3 (20 Mar 2012) +======================================= +- Fix various bugs +- Improve debug messages +- Improve GI annotations +- Use the generic marshallers + +telepathy-farstream 0.2.2 (08 Mar 2012) +======================================= + +- Allow an Endpoint to be removed so as it work with Rakia call transfers +- Ignore port 2.26 deprecations +- Added a tf_channel_new_finish() function +- Misc bug fixes + +telepathy-farstream 0.2.1 (20 Feb 2012) +======================================= + +- Now use Call1 as well as Streamed Media +- Now requires Farstream and telepathy-glib 0.17.5 + +telepathy-farstream 0.1.2 (18 Nov 2011) +======================================= + +- Fix linking with ld --as-needed (bigon) +- Add AudioControl support (mike, sjoerd) +- respond to farsight-negotiate events (olivier) + +telepathy-farstream 0.1.1 (14 Jul 2011) +======================================= +- Fix the python bindings +- Make the VideoControl interface actually work + +telepathy-farstream 0.1.0 (27 Jun 2011) +======================================= +- Rename from telepathy-farsight to telepathy-farstream +- Implement Call API +- Also implement Streamed Media API under the same C api + +telepathy-farsight 0.0.16 (22 Dec 2010) +======================================= +- Emit the NewActiveTransportPair signal +- Emit CodecsUpdated more often +- Various bug fixes + +telepathy-farsight 0.0.15 (30 Sep 2010) +======================================= +- Release sending resource when SetStreamSending(False) is called + +telepathy-farsight 0.0.14 (26 May 2010) +======================================= +- Add properties to get the Farsight2 session and stream +- Recognize the shm transmitter +- Ignore invalidly empty strings in tp properties +- Fix -Wshadow warnings +- Use silent rules if automake >= 1.11 + +telepathy-farsight 0.0.13 (5 Jan 2010) +====================================== +- Export held resource in a property +- Transfer the ptime/maxptime + +telepathy-farsight 0.0.12 (15 Oct 2009) +======================================= +- Fix mixup between GSlice and malloc +- Fix potential race between src-pad-added and dispose +- The connected state in farsight is a lie, ignore it + +telepathy-farsight 0.0.11 (10 Sep 2009) +======================================= +- Fix double free +- Fix more leaks + +telepathy-farsight 0.0.10 (08 Sep 2009) +====================================== +- Fix some leaks +- Fix possible deadlocks +- Emit different error codes depending on the error +- Emit stream state changes when the stream state changes according to ICE + +telepathy-farsight 0.0.9 (03 Aug 2009) +====================================== +- Emit session-invalidated on channel dispose (prevents leak) +- Fix ICE priority mangling (so not all candidates get 0) +- Use new error numbers from the the 0.17.27 spec + +telepathy-farsight 0.0.8 (03 Aug 2009) +====================================== +- Set ToS property on streams +- Set ICE controlling according to the CreatedLocally property +- Work around bug in the dbus-glib 0.80 of dbus properties +- Fix bugs + +telepathy-farsight 0.0.7 (06 May 2009) +====================================== +- Remove pygtk requirement +- Print errors in its own domain +- Update tp-glib dependency to 0.7.26 and fs2 dependency to 0.0.7 +- Make it more resilient in case of errors from other layers + +telepathy-farsight 0.0.6 (17 March 2009) +======================================== +- Add support of the RelayInfo property + +telepathy-farsight 0.0.5 (16 March 2009) +======================================== +- Recognize ice-udp +- Improve error handling +- Support the new CodecsUpdated method + +telepathy-farsight 0.0.4 (14 January 2009) +========================================== +- Add python bindings for tpfarsight +- Fix hold +- Make the "request-resource" signal optional, assumes + the resource is always there if there is no handler. + +telepathy-farsight 0.0.3 (21 November 2008) +=========================================== +- Fix small brown-paper bug in last release +- Rename tf_channel_new_from_proxy to tf_channel_new, + and leave the proxy creation to the client + +telepathy-farsight 0.0.2 (21 November 2008) +=========================================== + +- Added various makefile niceties from telepathy-glib + +telepathy-farsight 0.0.1 (21 November 2008) +=========================================== + +- Initial version +- Split from stream-engine after 0.5.3 diff --git a/telepathy-farstream/call-channel.c b/telepathy-farstream/call-channel.c new file mode 100644 index 000000000..4b9e46c2d --- /dev/null +++ b/telepathy-farstream/call-channel.c @@ -0,0 +1,668 @@ +/* + * call-channel.c - Source for TfCallChannel + * Copyright (C) 2010 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 + */ + +/** + * SECTION:call-channel + + * @short_description: Handle the Call interface on a Channel + * + * This class handles the + * org.freedesktop.Telepathy.Channel.Interface.Call on a + * channel using Farstream. + */ + +#include "config.h" + +#include "call-channel.h" + +#include <telepathy-glib/telepathy-glib.h> +#include <farstream/fs-conference.h> + +#include "call-content.h" +#include "call-priv.h" + + +static void call_channel_async_initable_init (GAsyncInitableIface *asynciface); + +G_DEFINE_TYPE_WITH_CODE (TfCallChannel, tf_call_channel, G_TYPE_OBJECT, + G_IMPLEMENT_INTERFACE (G_TYPE_ASYNC_INITABLE, + call_channel_async_initable_init)) + +enum +{ + PROP_FS_CONFERENCES = 1 +}; + + +enum +{ + SIGNAL_FS_CONFERENCE_ADDED, + SIGNAL_FS_CONFERENCE_REMOVED, + SIGNAL_CONTENT_ADDED, + SIGNAL_CONTENT_REMOVED, + SIGNAL_COUNT +}; + +static guint signals[SIGNAL_COUNT] = {0}; + +struct CallConference { + gint use_count; + gchar *conference_type; + FsConference *fsconference; +}; + +struct CallParticipant { + gint use_count; + guint handle; + FsConference *fsconference; + FsParticipant *fsparticipant; +}; + +static void +tf_call_channel_get_property (GObject *object, + guint property_id, + GValue *value, + GParamSpec *pspec); + +static void tf_call_channel_dispose (GObject *object); + + +static void tf_call_channel_init_async (GAsyncInitable *initable, + int io_priority, + GCancellable *cancellable, + GAsyncReadyCallback callback, + gpointer user_data); +static gboolean tf_call_channel_init_finish (GAsyncInitable *initable, + GAsyncResult *res, + GError **error); + +static void content_added (TpCallChannel *proxy, + TpCallContent *context_proxy, TfCallChannel *self); +static void content_removed (TpCallChannel *proxy, + TpCallContent *content_proxy, TpCallStateReason *reason, + TfCallChannel *self); +static void channel_prepared (GObject *proxy, GAsyncResult *prepare_res, + gpointer user_data); + + + +static void +tf_call_channel_class_init (TfCallChannelClass *klass) +{ + GObjectClass *object_class = G_OBJECT_CLASS (klass); + + object_class->dispose = tf_call_channel_dispose; + object_class->get_property = tf_call_channel_get_property; + + g_object_class_install_property (object_class, PROP_FS_CONFERENCES, + g_param_spec_boxed ("fs-conferences", + "Farstream FsConference object", + "GPtrArray of Farstream FsConferences for this channel", + G_TYPE_PTR_ARRAY, + G_PARAM_READABLE | G_PARAM_STATIC_STRINGS)); + + signals[SIGNAL_FS_CONFERENCE_ADDED] = g_signal_new ("fs-conference-added", + G_OBJECT_CLASS_TYPE (klass), + G_SIGNAL_RUN_LAST, + 0, NULL, NULL, NULL, + G_TYPE_NONE, 1, FS_TYPE_CONFERENCE); + + signals[SIGNAL_FS_CONFERENCE_REMOVED] = g_signal_new ("fs-conference-removed", + G_OBJECT_CLASS_TYPE (klass), + G_SIGNAL_RUN_LAST, + 0, NULL, NULL, NULL, + G_TYPE_NONE, 1, FS_TYPE_CONFERENCE); + + signals[SIGNAL_CONTENT_ADDED] = g_signal_new ("content-added", + G_OBJECT_CLASS_TYPE (klass), + G_SIGNAL_RUN_LAST, + 0, NULL, NULL, NULL, + G_TYPE_NONE, 1, TF_TYPE_CALL_CONTENT); + + signals[SIGNAL_CONTENT_REMOVED] = g_signal_new ("content-removed", + G_OBJECT_CLASS_TYPE (klass), + G_SIGNAL_RUN_LAST, + 0, NULL, NULL, NULL, + G_TYPE_NONE, 1, TF_TYPE_CALL_CONTENT); +} + + +static void +call_channel_async_initable_init (GAsyncInitableIface *asynciface) +{ + asynciface->init_async = tf_call_channel_init_async; + asynciface->init_finish = tf_call_channel_init_finish; +} + +static void +free_call_conference (gpointer data) +{ + struct CallConference *cc = data; + + gst_object_unref (cc->fsconference); + g_slice_free (struct CallConference, data); +} + +static void +free_participant (gpointer data) +{ + struct CallParticipant *cp = data; + + g_object_unref (cp->fsparticipant); + gst_object_unref (cp->fsconference); + g_slice_free (struct CallParticipant, cp); +} + +static void +tf_call_channel_init (TfCallChannel *self) +{ + self->fsconferences = g_hash_table_new_full (g_str_hash, g_str_equal, g_free, + free_call_conference); + + self->participants = g_ptr_array_new_with_free_func (free_participant); +} + + +static void +tf_call_channel_init_async (GAsyncInitable *initable, + int io_priority, + GCancellable *cancellable, + GAsyncReadyCallback callback, + gpointer user_data) +{ + TfCallChannel *self = TF_CALL_CHANNEL (initable); + GSimpleAsyncResult *res; + + if (cancellable != NULL) + { + g_simple_async_report_error_in_idle (G_OBJECT (self), callback, user_data, + G_IO_ERROR, G_IO_ERROR_NOT_INITIALIZED, + "TfCallChannel initialisation does not support cancellation"); + return; + } + + res = g_simple_async_result_new (G_OBJECT (self), callback, user_data, + tf_call_channel_init_async); + + tp_g_signal_connect_object (self->proxy, "content-added", + G_CALLBACK (content_added), self, 0); + tp_g_signal_connect_object (self->proxy, "content-removed", + G_CALLBACK (content_removed), self, 0); + + tp_proxy_prepare_async (self->proxy, NULL, channel_prepared, res); +} + +static gboolean +tf_call_channel_init_finish (GAsyncInitable *initable, + GAsyncResult *res, + GError **error) +{ + GSimpleAsyncResult *simple_res; + + g_return_val_if_fail (g_simple_async_result_is_valid (res, + G_OBJECT (initable), tf_call_channel_init_async), FALSE); + simple_res = G_SIMPLE_ASYNC_RESULT (res); + + if (g_simple_async_result_propagate_error (simple_res, error)) + return FALSE; + + return g_simple_async_result_get_op_res_gboolean (simple_res); +} + + +static void +tf_call_channel_dispose (GObject *object) +{ + TfCallChannel *self = TF_CALL_CHANNEL (object); + + g_debug (G_STRFUNC); + + /* Some of the contents may have more than our ref - if they're in the + middle of an async op, they're reffed by the async result. + In this case, unreffing them (implicitely) through destruction of + the hash table they're in will not dispose them just yet. + However, they keep an unreffed pointer to the call channel, and will, + when eventually disposed of, call upon the call channel to put their + conference back. Since that call channel will then be disposed of, + I think we can all agree that this is a bit unfortunate. + So we force dispose the contents as other objects already do, and + add checks to the content routines to bail out when the object has + already been disposed of. */ + if (self->contents) + { + g_ptr_array_free (self->contents, TRUE); + } + self->contents = NULL; + + if (self->participants) + g_ptr_array_unref (self->participants); + self->participants = NULL; + + if (self->fsconferences) + g_hash_table_unref (self->fsconferences); + self->fsconferences = NULL; + + if (self->proxy) + g_object_unref (self->proxy); + self->proxy = NULL; + + if (G_OBJECT_CLASS (tf_call_channel_parent_class)->dispose) + G_OBJECT_CLASS (tf_call_channel_parent_class)->dispose (object); +} + +static void +conf_into_ptr_array (gpointer key, gpointer value, gpointer data) +{ + struct CallConference *cc = value; + GPtrArray *array = data; + + g_ptr_array_add (array, cc->fsconference); +} + +static void +tf_call_channel_get_property (GObject *object, + guint property_id, + GValue *value, + GParamSpec *pspec) +{ + TfCallChannel *self = TF_CALL_CHANNEL (object); + + switch (property_id) + { + case PROP_FS_CONFERENCES: + { + GPtrArray *array = g_ptr_array_sized_new ( + g_hash_table_size (self->fsconferences)); + + g_ptr_array_set_free_func (array, gst_object_unref); + g_hash_table_foreach (self->fsconferences, conf_into_ptr_array, array); + g_value_take_boxed (value, array); + } + break; + default: + G_OBJECT_WARN_INVALID_PROPERTY_ID (object, property_id, pspec); + break; + } +} + +static void +content_ready (GObject *object, GAsyncResult *res, gpointer user_data) +{ + TfCallChannel *self = TF_CALL_CHANNEL (user_data); + TfCallContent *content = TF_CALL_CONTENT (object); + + if (g_async_initable_init_finish (G_ASYNC_INITABLE (object), res, NULL)) + { + g_signal_emit (self, signals[SIGNAL_CONTENT_ADDED], 0, content); + } + else + { + g_ptr_array_remove_fast (self->contents, content); + } + + g_object_unref (self); +} + +static gboolean +add_content (TfCallChannel *self, TpCallContent *content_proxy) +{ + GError *error = NULL; + TfCallContent *content; + guint i; + + /* Check if content already added */ + if (!self->contents) + return FALSE; + + for (i = 0; i < self->contents->len; i++) + { + if (tf_call_content_get_proxy (g_ptr_array_index (self->contents, i)) == + content_proxy) + return TRUE; + } + + content = tf_call_content_new_async (self, content_proxy, + &error, content_ready, g_object_ref (self)); + + if (error) + { + /* Error was already transmitted to the CM by TfCallContent */ + g_clear_error (&error); + g_object_unref (self); + return FALSE; + } + + g_ptr_array_add (self->contents, content); + + return TRUE; +} + +static void +content_added (TpCallChannel *proxy, + TpCallContent *content_proxy, + TfCallChannel *self) +{ + /* Ignore signals before we got the "Contents" property to avoid races that + * could cause the same content to be added twice + */ + + if (!self->contents) + return; + + add_content (self, content_proxy); +} + +static void +content_removed (TpCallChannel *proxy, + TpCallContent *content_proxy, + TpCallStateReason *reason, + TfCallChannel *self) +{ + guint i; + if (!self->contents) + return; + + for (i = 0; i < self->contents->len; i++) + { + + if (tf_call_content_get_proxy (g_ptr_array_index (self->contents, i)) == + content_proxy) + { + TfCallContent *content = g_ptr_array_index (self->contents, i); + + g_object_ref (content); + g_ptr_array_remove_index_fast (self->contents, i); + g_signal_emit (self, signals[SIGNAL_CONTENT_REMOVED], 0, content); + g_object_unref (content); + return; + } + } +} + +static void +free_content (gpointer data) +{ + TfCallContent *content = data; + + _tf_call_content_destroy (content); + g_object_unref (content); +} + +static void +channel_prepared (GObject *proxy, GAsyncResult *prepare_res, gpointer user_data) +{ + GSimpleAsyncResult *res = user_data; + TfCallChannel *self = + TF_CALL_CHANNEL (g_async_result_get_source_object (G_ASYNC_RESULT (res))); + GError *error = NULL; + GPtrArray *contents; + guint i; + + if (!tp_proxy_prepare_finish (proxy, prepare_res, &error)) + { + g_warning ("Preparing the channel: %s", + error->message); + g_simple_async_result_take_error (res, error); + goto out; + } + + if (tp_call_channel_has_hardware_streaming (TP_CALL_CHANNEL (proxy))) + { + g_warning ("Hardware streaming property is TRUE, ignoring"); + + g_simple_async_result_set_error (res, TP_ERROR, TP_ERROR_NOT_CAPABLE, + "This channel does hardware streaming, not handled here"); + goto out; + } + + contents = tp_call_channel_get_contents (TP_CALL_CHANNEL (proxy)); + + self->contents = g_ptr_array_new_with_free_func (free_content); + + for (i = 0; i < contents->len; i++) + if (!add_content (self, g_ptr_array_index (contents, i))) + break; + + g_simple_async_result_set_op_res_gboolean (res, TRUE); + +out: + g_simple_async_result_complete (res); + g_object_unref (res); + g_object_unref (self); +} + +void +tf_call_channel_new_async (TpChannel *channel, + GAsyncReadyCallback callback, + gpointer user_data) +{ + TfCallChannel *self = g_object_new (TF_TYPE_CALL_CHANNEL, NULL); + + self->proxy = g_object_ref (channel); + g_async_initable_init_async (G_ASYNC_INITABLE (self), 0, NULL, callback, + user_data); + + /* Ownership passed to async call */ + g_object_unref (self); +} + + +static gboolean +find_conf_func (gpointer key, gpointer value, gpointer data) +{ + FsConference *conf = data; + struct CallConference *cc = value; + + if (cc->fsconference == conf) + return TRUE; + else + return FALSE; +} + +static struct CallConference * +find_call_conference_by_conference (TfCallChannel *channel, + GstObject *conference) +{ + return g_hash_table_find (channel->fsconferences, find_conf_func, + conference); +} + +gboolean +tf_call_channel_bus_message (TfCallChannel *channel, + GstMessage *message) +{ + GError *error = NULL; + gchar *debug; + struct CallConference *cc; + guint i; + + cc = find_call_conference_by_conference (channel, GST_MESSAGE_SRC (message)); + if (!cc) + return FALSE; + + switch (GST_MESSAGE_TYPE (message)) + { + case GST_MESSAGE_WARNING: + gst_message_parse_warning (message, &error, &debug); + + g_warning ("session: %s (%s)", error->message, debug); + + g_error_free (error); + g_free (debug); + return TRUE; + case GST_MESSAGE_ERROR: + gst_message_parse_error (message, &error, &debug); + + g_warning ("session ERROR: %s (%s)", error->message, debug); + + tf_call_channel_error (channel); + + g_error_free (error); + g_free (debug); + return TRUE; + default: + break; + } + + for (i = 0; i < channel->contents->len; i++) + if (tf_call_content_bus_message (g_ptr_array_index (channel->contents, i), + message)) + return TRUE; + + return FALSE; +} + +void +tf_call_channel_error (TfCallChannel *channel) +{ + tp_call_channel_hangup_async (TP_CALL_CHANNEL (channel->proxy), + TP_CALL_STATE_CHANGE_REASON_UNKNOWN, "", "", NULL, NULL); +} + + +/* This always returns a reference, one should use _put_conference to unref it + */ +FsConference * +_tf_call_channel_get_conference (TfCallChannel *channel, + const gchar *conference_type) +{ + gchar *tmp; + struct CallConference *cc; + + cc = g_hash_table_lookup (channel->fsconferences, conference_type); + + if (cc) + { + cc->use_count++; + gst_object_ref (cc->fsconference); + return cc->fsconference; + } + + cc = g_slice_new (struct CallConference); + cc->use_count = 1; + cc->conference_type = g_strdup (conference_type); + + tmp = g_strdup_printf ("fs%sconference", conference_type); + cc->fsconference = FS_CONFERENCE (gst_element_factory_make (tmp, NULL)); + g_free (tmp); + + if (cc->fsconference == NULL) + { + g_slice_free (struct CallConference, cc); + return NULL; + } + + /* Take ownership of the conference */ + gst_object_ref_sink (cc->fsconference); + g_hash_table_insert (channel->fsconferences, cc->conference_type, cc); + + g_signal_emit (channel, signals[SIGNAL_FS_CONFERENCE_ADDED], 0, + cc->fsconference); + g_object_notify (G_OBJECT (channel), "fs-conferences"); + + gst_object_ref (cc->fsconference); + + return cc->fsconference; +} + +void +_tf_call_channel_put_conference (TfCallChannel *channel, + FsConference *conference) +{ + struct CallConference *cc; + + cc = find_call_conference_by_conference (channel, GST_OBJECT (conference)); + if (!cc) + { + g_warning ("Trying to put conference that does not exist"); + return; + } + + cc->use_count--; + + if (cc->use_count <= 0) + { + g_signal_emit (channel, signals[SIGNAL_FS_CONFERENCE_REMOVED], 0, + cc->fsconference); + g_hash_table_remove (channel->fsconferences, cc->conference_type); + g_object_notify (G_OBJECT (channel), "fs-conferences"); + } + + gst_object_unref (conference); +} + + +FsParticipant * +_tf_call_channel_get_participant (TfCallChannel *channel, + FsConference *fsconference, + guint contact_handle, + GError **error) +{ + guint i; + struct CallParticipant *cp; + FsParticipant *p; + + for (i = 0; i < channel->participants->len; i++) + { + cp = g_ptr_array_index (channel->participants, i); + + if (cp->fsconference == fsconference && + cp->handle == contact_handle) + { + cp->use_count++; + return g_object_ref (cp->fsparticipant); + } + } + + p = fs_conference_new_participant (fsconference, error); + if (!p) + return NULL; + + cp = g_slice_new (struct CallParticipant); + cp->use_count = 1; + cp->handle = contact_handle; + cp->fsconference = gst_object_ref (fsconference); + cp->fsparticipant = p; + g_ptr_array_add (channel->participants, cp); + + return p; +} + + +void +_tf_call_channel_put_participant (TfCallChannel *channel, + FsParticipant *participant) +{ + guint i; + + for (i = 0; i < channel->participants->len; i++) + { + struct CallParticipant *cp = g_ptr_array_index (channel->participants, i); + + if (cp->fsparticipant == participant) + { + cp->use_count--; + if (cp->use_count <= 0) + g_ptr_array_remove_index_fast (channel->participants, i); + else + gst_object_unref (cp->fsparticipant); + return; + } + } +} diff --git a/telepathy-farstream/call-channel.h b/telepathy-farstream/call-channel.h new file mode 100644 index 000000000..525e12f0f --- /dev/null +++ b/telepathy-farstream/call-channel.h @@ -0,0 +1,118 @@ +/* + * call-channel.c - Source for TfCallChannel + * Copyright (C) 2010 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 + */ + +#ifndef __TF_CALL_CHANNEL_H__ +#define __TF_CALL_CHANNEL_H__ + +#include <glib-object.h> + +#include <gst/gst.h> +#include <farstream/fs-conference.h> +#include <telepathy-glib/telepathy-glib.h> + +G_BEGIN_DECLS + +#define TF_TYPE_CALL_CHANNEL tf_call_channel_get_type() + +#define TF_CALL_CHANNEL(obj) \ + (G_TYPE_CHECK_INSTANCE_CAST ((obj), \ + TF_TYPE_CALL_CHANNEL, TfCallChannel)) + +#define TF_CALL_CHANNEL_CLASS(klass) \ + (G_TYPE_CHECK_CLASS_CAST ((klass), \ + TF_TYPE_CALL_CHANNEL, TfCallChannelClass)) + +#define TF_IS_CALL_CHANNEL(obj) \ + (G_TYPE_CHECK_INSTANCE_TYPE ((obj), TF_TYPE_CALL_CHANNEL)) + +#define TF_IS_CALL_CHANNEL_CLASS(klass) \ + (G_TYPE_CHECK_CLASS_TYPE ((klass), TF_TYPE_CALL_CHANNEL)) + +#define TF_CALL_CHANNEL_GET_CLASS(obj) \ + (G_TYPE_INSTANCE_GET_CLASS ((obj), \ + TF_TYPE_CALL_CHANNEL, TfCallChannelClass)) + +typedef struct _TfCallChannelPrivate TfCallChannelPrivate; + +/** + * TfCallChannel: + * + * All members of the object are private + */ + +typedef struct _TfCallChannel TfCallChannel; + +/** + * TfCallChannelClass: + * @parent_class: the parent #GObjecClass + * + * There are no overridable functions + */ + +typedef struct _TfCallChannelClass TfCallChannelClass; + + + +struct _TfCallChannel { + GObject parent; + + TpChannel *proxy; + + GHashTable *fsconferences; + + GPtrArray *contents; /* NULL before getting the first contents */ + + GPtrArray *participants; +}; + +struct _TfCallChannelClass{ + GObjectClass parent_class; +}; + + +GType tf_call_channel_get_type (void); + +void tf_call_channel_new_async (TpChannel *channel_proxy, + GAsyncReadyCallback callback, + gpointer user_data); + +void tf_call_channel_error (TfCallChannel *channel); + +gboolean tf_call_channel_bus_message (TfCallChannel *channel, + GstMessage *message); + +/* Private methods, only to be used inside TP-FS */ + +FsConference *_tf_call_channel_get_conference (TfCallChannel *channel, + const gchar *conference_type); +void _tf_call_channel_put_conference (TfCallChannel *channel, + FsConference *conference); + + +FsParticipant *_tf_call_channel_get_participant (TfCallChannel *channel, + FsConference *fsconference, + guint contact_handle, + GError **error); +void _tf_call_channel_put_participant (TfCallChannel *channel, + FsParticipant *participant); + +G_END_DECLS + +#endif /* __TF_CALL_CHANNEL_H__ */ + diff --git a/telepathy-farstream/call-content.c b/telepathy-farstream/call-content.c new file mode 100644 index 000000000..db1e3c68e --- /dev/null +++ b/telepathy-farstream/call-content.c @@ -0,0 +1,2553 @@ +/* + * call-content.c - Source for TfCallContent + * Copyright (C) 2010 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 + */ + +/** + * SECTION:call-content + + * @short_description: Handle the Content objects on a Call channel + * + * This class handles the org.freedesktop.Telepathy.Call1.Content, + * org.freedesktop.Telepathy.Call1.Content.Interface.Media, + * org.freedesktop.Telepathy.Call1.Content.Interface.VideoControl, + * org.freedesktop.Telepathy.Call1.Content.MediaDescription, + * prg.freedesktop.Telepathy.Call1.Content.MediaDescription.Interface.RTPHeaderExtensions + * and org.freedesktop.Telepathy.Call1.Content.MediaDescription.Interface.RTCPFeedback interfaces + * + */ + +/* TODO: + * + * In MediaDescription: + * - SSRCs + */ + +#include "config.h" + +#include "call-content.h" + +#include <telepathy-glib/telepathy-glib.h> +#include <telepathy-glib/telepathy-glib-dbus.h> +#include <telepathy-glib/proxy-subclass.h> +#include <farstream/fs-conference.h> +#include <farstream/fs-utils.h> +#include <farstream/fs-rtp.h> +#include <farstream/fs-element-added-notifier.h> + +#include <stdarg.h> +#include <string.h> + + +#include "call-stream.h" +#include "call-priv.h" +#include "utils.h" + +#define DTMF_TONE_VOLUME (8) + +struct _TfCallContent { + TfContent parent; + + TfCallChannel *call_channel; + FsConference *fsconference; + + TpCallContent *proxy; + + FsSession *fssession; + + TpProxy *current_media_description; + guint current_md_contact_handle; + GList *current_md_fscodecs; + GList *current_md_rtp_hdrext; + + gboolean current_has_rtp_hdrext; + gboolean current_has_rtcp_fb; + gboolean has_rtp_hdrext; + gboolean has_rtcp_fb; + + GList *last_sent_codecs; + + GPtrArray *streams; /* NULL before getting the first streams */ + /* Streams for which we don't have a session yet*/ + GList *outstanding_streams; + + GMutex mutex; + + gboolean remote_codecs_set; + + TpSendingState dtmf_sending_state; + guint current_dtmf_event; + + /* Content protected by the Mutex */ + GPtrArray *fsstreams; + guint fsstreams_cookie; + + gboolean got_media_description_property; + + /* AudioControl API */ + gint requested_input_volume; + gint requested_output_volume; + gint reported_input_volume; + gint reported_output_volume; + + /* VideoControl API */ + FsElementAddedNotifier *notifier; + + volatile gint bitrate; + volatile gint mtu; + gboolean manual_keyframes; + + guint framerate; + guint width; + guint height; +}; + +struct _TfCallContentClass { + TfContentClass parent_class; +}; + + +static void call_content_async_initable_init (GAsyncInitableIface *asynciface); + +G_DEFINE_TYPE_WITH_CODE (TfCallContent, tf_call_content, TF_TYPE_CONTENT, + G_IMPLEMENT_INTERFACE (G_TYPE_ASYNC_INITABLE, + call_content_async_initable_init)) + +#define TF_CALL_CONTENT_LOCK(self) g_mutex_lock (&(self)->mutex) +#define TF_CALL_CONTENT_UNLOCK(self) g_mutex_unlock (&(self)->mutex) + + +enum +{ + PROP_TF_CHANNEL = 1, + PROP_FS_CONFERENCE, + PROP_FS_SESSION, + PROP_SINK_PAD, + PROP_MEDIA_TYPE, + PROP_OBJECT_PATH, + PROP_REQUESTED_INPUT_VOLUME, + PROP_REQUESTED_OUTPUT_VOLUME, + PROP_REPORTED_INPUT_VOLUME, + PROP_REPORTED_OUTPUT_VOLUME, + PROP_FRAMERATE, + PROP_WIDTH, + PROP_HEIGHT +}; + +enum +{ + RESOLUTION_CHANGED = 0, + SIGNAL_COUNT +}; + +static guint signals[SIGNAL_COUNT] = {0}; + + +struct CallFsStream { + TfCallChannel *parent_channel; + guint use_count; + guint contact_handle; + FsParticipant *fsparticipant; + FsStream *fsstream; +}; + +static void +tf_call_content_get_property (GObject *object, + guint property_id, + GValue *value, + GParamSpec *pspec); +static void +tf_call_content_set_property (GObject *object, + guint property_id, + const GValue *value, + GParamSpec *pspec); + +static void tf_call_content_dispose (GObject *object); +static void tf_call_content_finalize (GObject *object); + +static void tf_call_content_init_async (GAsyncInitable *initable, + int io_priority, + GCancellable *cancellable, + GAsyncReadyCallback callback, + gpointer user_data); +static gboolean tf_call_content_init_finish (GAsyncInitable *initable, + GAsyncResult *res, + GError **error); + + +static void tf_call_content_try_sending_codecs (TfCallContent *self); +static FsStream * tf_call_content_get_existing_fsstream_by_handle ( + TfCallContent *content, guint contact_handle); + +static void src_pad_added (FsStream *fsstream, GstPad *pad, FsCodec *codec, + TfCallContent *content); +static GstIterator * tf_call_content_iterate_src_pads (TfContent *content, + guint *handles, guint handle_count); + +static void tf_call_content_error (TfCallContent *self, + TpCallStateChangeReason reason, + const gchar *detailed_reason, + const gchar *message_format, + ...) G_GNUC_PRINTF (4, 5); +static void tf_call_content_error_literal (TfCallContent *self, + TpCallStateChangeReason reason, + const gchar *detailed_reason, + const gchar *message); + +static void tf_call_content_error_impl (TfContent *content, + const gchar *message); +static void tf_call_content_sending_failed (TfContent *content, + const gchar *message); +static void tf_call_content_receiving_failed (TfContent *content, + guint *handles, guint handle_count, + const gchar *message); + +static void +tf_call_content_class_init (TfCallContentClass *klass) +{ + GObjectClass *object_class = G_OBJECT_CLASS (klass); + TfContentClass *content_class = TF_CONTENT_CLASS (klass); + + content_class->iterate_src_pads = tf_call_content_iterate_src_pads; + + content_class->content_error = tf_call_content_error_impl; + content_class->sending_failed = tf_call_content_sending_failed; + content_class->receiving_failed = tf_call_content_receiving_failed; + + object_class->dispose = tf_call_content_dispose; + object_class->finalize = tf_call_content_finalize; + object_class->get_property = tf_call_content_get_property; + object_class->set_property = tf_call_content_set_property; + + g_object_class_override_property (object_class, PROP_TF_CHANNEL, + "tf-channel"); + g_object_class_override_property (object_class, PROP_FS_CONFERENCE, + "fs-conference"); + g_object_class_override_property (object_class, PROP_FS_SESSION, + "fs-session"); + g_object_class_override_property (object_class, PROP_SINK_PAD, + "sink-pad"); + g_object_class_override_property (object_class, PROP_MEDIA_TYPE, + "media-type"); + g_object_class_override_property (object_class, PROP_OBJECT_PATH, + "object-path"); + + g_object_class_install_property (object_class, PROP_FRAMERATE, + g_param_spec_uint ("framerate", + "Framerate", + "The framerate as indicated by the VideoControl interface" + "or the media layer", + 0, G_MAXUINT, 0, + G_PARAM_READABLE | G_PARAM_STATIC_STRINGS)); + + g_object_class_install_property (object_class, PROP_REQUESTED_INPUT_VOLUME, + g_param_spec_int ("requested-input-volume", + "Requested input volume", + "The requested input volume indicated by the AudioControl interface", + -1, 255, -1, + G_PARAM_READWRITE | G_PARAM_STATIC_STRINGS)); + + g_object_class_install_property (object_class, PROP_REQUESTED_OUTPUT_VOLUME, + g_param_spec_int ("requested-output-volume", + "Requested output volume", + "The requested output volume indicated by the AudioControl interface", + -1, 255, -1, + G_PARAM_READWRITE | G_PARAM_STATIC_STRINGS)); + + g_object_class_install_property (object_class, PROP_REPORTED_INPUT_VOLUME, + g_param_spec_int ("reported-input-volume", + "Reported input volume", + "The input volume indicated by or the media layer", + -1, 255, -1, + G_PARAM_READWRITE | G_PARAM_STATIC_STRINGS)); + + g_object_class_install_property (object_class, PROP_REPORTED_OUTPUT_VOLUME, + g_param_spec_int ("reported-output-volume", + "Output volume", + "The output volume indicated by the the media layer", + -1, 255, -1, + G_PARAM_READWRITE | G_PARAM_STATIC_STRINGS)); + + g_object_class_install_property (object_class, PROP_WIDTH, + g_param_spec_uint ("width", + "Width", + "The video width indicated by the VideoControl interface" + "or the media layer", + 0, G_MAXUINT, 0, + G_PARAM_READABLE | G_PARAM_STATIC_STRINGS)); + + g_object_class_install_property (object_class, PROP_HEIGHT, + g_param_spec_uint ("height", + "Height", + "The video height as indicated by the VideoControl interface" + "or the media layer", + 0, G_MAXUINT, 0, + G_PARAM_READABLE | G_PARAM_STATIC_STRINGS)); + + signals[RESOLUTION_CHANGED] = g_signal_new ("resolution-changed", + G_OBJECT_CLASS_TYPE (klass), + G_SIGNAL_RUN_LAST, + 0, NULL, NULL, NULL, + G_TYPE_NONE, 2, G_TYPE_UINT, G_TYPE_UINT); +} + +static void +call_content_async_initable_init (GAsyncInitableIface *asynciface) +{ + asynciface->init_async = tf_call_content_init_async; + asynciface->init_finish = tf_call_content_init_finish; +} + + +static void +free_content_fsstream (gpointer data) +{ + struct CallFsStream *cfs = data; + + fs_stream_destroy (cfs->fsstream); + g_object_unref (cfs->fsstream); + _tf_call_channel_put_participant (cfs->parent_channel, cfs->fsparticipant); + g_slice_free (struct CallFsStream, cfs); +} + +static void +tf_call_content_init (TfCallContent *self) +{ + self->fsstreams = g_ptr_array_new (); + self->dtmf_sending_state = TP_SENDING_STATE_NONE; + + g_mutex_init (&self->mutex); + self->requested_input_volume = -1; + self->requested_output_volume = -1; + + self->reported_input_volume = -1; + self->reported_output_volume = -1; +} + +void +_tf_call_content_destroy (TfCallContent *self) +{ + if (self->streams) + { + g_ptr_array_free (self->streams, TRUE); + } + self->streams = NULL; + + if (self->fssession) + { + fs_session_destroy (self->fssession); + g_object_unref (self->fssession); + } + self->fssession = NULL; + + if (self->fsstreams) + { + while (self->fsstreams->len) + free_content_fsstream ( + g_ptr_array_remove_index_fast (self->fsstreams, 0)); + g_ptr_array_unref (self->fsstreams); + } + self->fsstreams = NULL; + + if (self->notifier) + g_object_unref (self->notifier); + self->notifier = NULL; + + if (self->fsconference) + _tf_call_channel_put_conference (self->call_channel, + self->fsconference); + self->fsconference = NULL; + + /* We do not hold a ref to the call channel, and use it as a flag to ensure + * we will bail out when disposed */ + self->call_channel = NULL; +} + +static void +tf_call_content_dispose (GObject *object) +{ + TfCallContent *self = TF_CALL_CONTENT (object); + + g_debug (G_STRFUNC); + + _tf_call_content_destroy (self); + + if (self->proxy) + g_object_unref (self->proxy); + self->proxy = NULL; + + if (G_OBJECT_CLASS (tf_call_content_parent_class)->dispose) + G_OBJECT_CLASS (tf_call_content_parent_class)->dispose (object); +} + + +static void +tf_call_content_finalize (GObject *object) +{ + TfCallContent *self = TF_CALL_CONTENT (object); + + fs_codec_list_destroy (self->last_sent_codecs); + self->last_sent_codecs = NULL; + + g_mutex_clear (&self->mutex); + + if (G_OBJECT_CLASS (tf_call_content_parent_class)->finalize) + G_OBJECT_CLASS (tf_call_content_parent_class)->finalize (object); +} + + +static void +tf_call_content_get_property (GObject *object, + guint property_id, + GValue *value, + GParamSpec *pspec) +{ + TfCallContent *self = TF_CALL_CONTENT (object); + + switch (property_id) + { + case PROP_TF_CHANNEL: + if (self->call_channel) + g_value_set_object (value, self->call_channel); + break; + case PROP_FS_CONFERENCE: + if (self->fsconference) + g_value_set_object (value, self->fsconference); + break; + case PROP_FS_SESSION: + if (self->fssession) + g_value_set_object (value, self->fssession); + break; + case PROP_SINK_PAD: + if (self->fssession) + g_object_get_property (G_OBJECT (self->fssession), "sink-pad", value); + break; + case PROP_MEDIA_TYPE: + g_value_set_enum (value, tf_call_content_get_fs_media_type (self)); + break; + case PROP_OBJECT_PATH: + g_object_get_property (G_OBJECT (self->proxy), "object-path", value); + break; + case PROP_REQUESTED_INPUT_VOLUME: + g_value_set_int (value, self->requested_input_volume); + break; + case PROP_REQUESTED_OUTPUT_VOLUME: + g_value_set_int (value, self->requested_output_volume); + break; + case PROP_REPORTED_INPUT_VOLUME: + g_value_set_int (value, self->reported_input_volume); + break; + case PROP_REPORTED_OUTPUT_VOLUME: + g_value_set_int (value, self->reported_output_volume); + break; + case PROP_FRAMERATE: + g_value_set_uint (value, self->framerate); + break; + case PROP_WIDTH: + g_value_set_uint (value, self->width); + break; + case PROP_HEIGHT: + g_value_set_uint (value, self->height); + break; + default: + G_OBJECT_WARN_INVALID_PROPERTY_ID (object, property_id, pspec); + break; + } +} + +static void +tf_call_content_set_property (GObject *object, + guint property_id, + const GValue *value, + GParamSpec *pspec) +{ + TfCallContent *self = TF_CALL_CONTENT (object); + + switch (property_id) + { + case PROP_REPORTED_INPUT_VOLUME: + /* Guard against early disposal */ + if (self->call_channel == NULL) + break; + + self->reported_input_volume = g_value_get_int (value); + tp_cli_call1_content_interface_audio_control1_call_report_input_volume ( + self->proxy, -1, self->reported_input_volume, + NULL, NULL, NULL, NULL); + + break; + case PROP_REPORTED_OUTPUT_VOLUME: + /* Guard against early disposal */ + if (self->call_channel == NULL) + break; + + self->reported_output_volume = g_value_get_int (value); + tp_cli_call1_content_interface_audio_control1_call_report_output_volume ( + self->proxy, -1, self->reported_output_volume, + NULL, NULL, NULL, NULL); + break; + default: + G_OBJECT_WARN_INVALID_PROPERTY_ID (object, property_id, pspec); + break; + } +} + +static void +add_stream (TfCallContent *self, TpCallStream *stream_proxy) +{ + g_ptr_array_add (self->streams, + tf_call_stream_new (self, stream_proxy)); +} + +static void +add_initial_streams (TfCallContent *self) +{ + GPtrArray *streams; + guint i; + + g_assert (self->fsconference); + g_assert (self->streams->len == 0); + + streams = tp_call_content_get_streams (self->proxy); + + for (i = 0; i < streams->len; i++) + add_stream (self, g_ptr_array_index (streams, i)); +} + +static void +tpparam_to_fsparam (gpointer key, gpointer value, gpointer user_data) +{ + gchar *name = key; + gchar *val = value; + FsCodec *fscodec = user_data; + + fs_codec_add_optional_parameter (fscodec, name, val); +} + +static GList * +tpcodecs_to_fscodecs (FsMediaType fsmediatype, const GPtrArray *tpcodecs, + gboolean does_avpf, GHashTable *rtcp_fb) +{ + GList *fscodecs = NULL; + guint i; + + for (i = 0; i < tpcodecs->len; i++) + { + GValueArray *tpcodec = g_ptr_array_index (tpcodecs, i); + guint pt; + gchar *name; + guint clock_rate; + guint channels; + GHashTable *params; + FsCodec *fscodec; + gchar *tmp; + GValueArray *feedback_params = NULL; + gboolean updated; + + tp_value_array_unpack (tpcodec, 6, &pt, &name, &clock_rate, &channels, + &updated, ¶ms); + + fscodec = fs_codec_new (pt, name, fsmediatype, clock_rate); + fscodec->channels = channels; + + g_hash_table_foreach (params, tpparam_to_fsparam, fscodec); + + if (does_avpf) + fscodec->minimum_reporting_interval = 0; + + if (rtcp_fb) + feedback_params = g_hash_table_lookup (rtcp_fb, GUINT_TO_POINTER (pt)); + + if (feedback_params) + { + guint rtcp_minimum_interval; + GPtrArray *messages; + guint j; + + tp_value_array_unpack (feedback_params, 2, &rtcp_minimum_interval, + &messages); + if (rtcp_minimum_interval != G_MAXUINT) + fscodec->minimum_reporting_interval = rtcp_minimum_interval; + + for (j = 0; j < messages->len ; j++) + { + GValueArray *message = g_ptr_array_index (messages, j); + const gchar *type, *subtype, *extra_params; + + tp_value_array_unpack (message, 3, &type, &subtype, + &extra_params); + + fs_codec_add_feedback_parameter (fscodec, type, subtype, + extra_params); + } + } + + tmp = fs_codec_to_string (fscodec); + g_debug ("%s", tmp); + g_free (tmp); + fscodecs = g_list_prepend (fscodecs, fscodec); + } + + fscodecs = g_list_reverse (fscodecs); + + return fscodecs; +} + +static GList * +tprtphdrext_to_fsrtphdrext (GPtrArray *rtp_hdrext) +{ + GQueue ret = G_QUEUE_INIT; + guint i; + + if (!rtp_hdrext) + return NULL; + + for (i = 0; i < rtp_hdrext->len; i++) + { + GValueArray *extension = g_ptr_array_index (rtp_hdrext, i); + guint id; + TpMediaStreamDirection direction; + const char *uri; + const gchar *parameters; + FsRtpHeaderExtension *ext; + + tp_value_array_unpack (extension, 4, &id, &direction, &uri, ¶meters); + + ext = fs_rtp_header_extension_new (id, + tpdirection_to_fsdirection (direction), uri); + + g_debug ("hdrext: " FS_RTP_HEADER_EXTENSION_FORMAT, + FS_RTP_HEADER_EXTENSION_ARGS (ext)); + + g_queue_push_tail (&ret, ext); + } + + return ret.head; +} + +static gboolean +object_has_property (GObject *object, const gchar *property) +{ + return g_object_class_find_property (G_OBJECT_GET_CLASS (object), + property) != NULL; +} + +static void +on_content_dtmf_change_requested (TpCallContent *proxy, + guchar arg_Event, + guint arg_State, + gpointer user_data, + GObject *weak_object) +{ + TfCallContent *self = TF_CALL_CONTENT (weak_object); + + /* Ignore the signal until we've got the original properties and codecs */ + if (!self->fssession || !self->remote_codecs_set) { + self->dtmf_sending_state = arg_State; + self->current_dtmf_event = arg_Event; + return; + } + + switch (arg_State) + { + case TP_SENDING_STATE_PENDING_STOP_SENDING: + if (self->dtmf_sending_state != TP_SENDING_STATE_SENDING) + { + tf_call_content_error (self, + TP_CALL_STATE_CHANGE_REASON_INTERNAL_ERROR, + TP_ERROR_STR_CONFUSED, + "Tried to stop a %u DTMF event while state is %d", + arg_Event, self->dtmf_sending_state); + } + + if (fs_session_stop_telephony_event (self->fssession)) + { + self->dtmf_sending_state = TP_SENDING_STATE_PENDING_STOP_SENDING; + } + else + { + tf_call_content_error (self, + TP_CALL_STATE_CHANGE_REASON_INTERNAL_ERROR, + TP_ERROR_STR_MEDIA_STREAMING_ERROR, + "Could not stop DTMF event %d", arg_Event); + tp_cli_call1_content_interface_media_call_acknowledge_dtmf_change ( + self->proxy, -1, arg_Event, TP_SENDING_STATE_SENDING, + NULL, NULL, NULL, NULL); + } + break; + case TP_SENDING_STATE_PENDING_SEND: + if (self->dtmf_sending_state != TP_SENDING_STATE_NONE) + { + tf_call_content_error (self, + TP_CALL_STATE_CHANGE_REASON_INTERNAL_ERROR, + TP_ERROR_STR_CONFUSED, + "Tried to start a new DTMF event %u while %d is already playing", + arg_Event, self->current_dtmf_event); + fs_session_stop_telephony_event (self->fssession); + } + + if (fs_session_start_telephony_event (self->fssession, + arg_Event, DTMF_TONE_VOLUME)) + { + self->current_dtmf_event = arg_Event; + self->dtmf_sending_state = TP_SENDING_STATE_PENDING_SEND; + } + else + { + tf_call_content_error (self, + TP_CALL_STATE_CHANGE_REASON_INTERNAL_ERROR, + TP_ERROR_STR_MEDIA_STREAMING_ERROR, + "Could not start DTMF event %d", arg_Event); + tp_cli_call1_content_interface_media_call_acknowledge_dtmf_change ( + self->proxy, -1, arg_Event, TP_SENDING_STATE_NONE, + NULL, NULL, NULL, NULL); + } + break; + default: + tf_call_content_error (self, + TP_CALL_STATE_CHANGE_REASON_INTERNAL_ERROR, + TP_ERROR_STR_CONFUSED, + "Invalid State %d in DTMFChangeRequested signal for event %d", + arg_State, arg_Event); + break; + } +} + + +static void +process_media_description_try_codecs (TfCallContent *self, FsStream *fsstream, + TpProxy *media_description, GList *fscodecs, GList *rtp_hdrext) +{ + gboolean success = TRUE; + GError *error = NULL; + + if (fscodecs != NULL) + { + GList *old_rtp_hdrext = NULL; + + if (object_has_property (G_OBJECT (fsstream), "rtp-header-extensions")) + { + g_object_get (fsstream, "rtp-header-extensions", &old_rtp_hdrext, + NULL); + g_object_set (fsstream, "rtp-header-extensions", rtp_hdrext, NULL); + } + + success = fs_stream_set_remote_codecs (fsstream, fscodecs, &error); + + if (success) + { + if (!self->remote_codecs_set) + on_content_dtmf_change_requested (NULL, self->current_dtmf_event, + self->dtmf_sending_state, NULL, G_OBJECT (self)); + self->remote_codecs_set = TRUE; + } + + if (!success && + object_has_property (G_OBJECT (fsstream), "rtp-header-extensions")) + g_object_set (fsstream, "rtp-header-extensions", old_rtp_hdrext, NULL); + + fs_rtp_header_extension_list_destroy (old_rtp_hdrext); + } + + fs_rtp_header_extension_list_destroy (rtp_hdrext); + fs_codec_list_destroy (fscodecs); + + if (success) + { + self->current_media_description = media_description; + tf_call_content_try_sending_codecs (self); + } + else + { + GValueArray *reason = tp_value_array_build (4, + G_TYPE_UINT, 0, + G_TYPE_UINT, TP_CALL_STATE_CHANGE_REASON_MEDIA_ERROR, + G_TYPE_STRING, TP_ERROR_STR_MEDIA_CODECS_INCOMPATIBLE, + G_TYPE_STRING, + "Remote codecs are not compatible with the local ones", + G_TYPE_INVALID); + + g_debug ("Rejecting Media Description"); + tp_cli_call1_content_media_description_call_reject (media_description, + -1, reason, NULL, NULL, NULL, NULL); + tp_value_array_free (reason); + g_object_unref (media_description); + } + g_clear_error (&error); +} + +static void +process_media_description (TfCallContent *self, + const gchar *media_description_objpath, + const GHashTable *properties) +{ + TpProxy *proxy; + GError *error = NULL; + FsStream *fsstream; + GPtrArray *codecs; + GPtrArray *rtp_hdrext = NULL; + GHashTable *rtcp_fb = NULL; + gboolean does_avpf = FALSE; + GList *fscodecs; + const gchar * const *interfaces; + guint i; + GList *fsrtp_hdrext; + guint contact_handle; + gboolean valid; + + /* Guard against early disposal */ + if (self->call_channel == NULL) + return; + + if (!tp_dbus_check_valid_object_path (media_description_objpath, &error)) + { + tf_call_content_error (self, + TP_CALL_STATE_CHANGE_REASON_INTERNAL_ERROR, TP_ERROR_STR_CONFUSED, + "Invalid MediaDescription path: %s", error->message); + g_clear_error (&error); + return; + } + + contact_handle = tp_asv_get_uint32 (properties, + TP_PROP_CALL1_CONTENT_MEDIA_DESCRIPTION_REMOTE_CONTACT, &valid); + if (!valid) + { + tf_call_content_error_literal (self, + TP_CALL_STATE_CHANGE_REASON_INTERNAL_ERROR, TP_ERROR_STR_CONFUSED, + "MediaDescription does not contain a valid contact handle"); + g_clear_error (&error); + return; + } + + codecs = tp_asv_get_boxed (properties, + TP_PROP_CALL1_CONTENT_MEDIA_DESCRIPTION_CODECS, TP_ARRAY_TYPE_CODEC_LIST); + + if (!codecs) + { + tf_call_content_error_literal (self, + TP_CALL_STATE_CHANGE_REASON_INTERNAL_ERROR, TP_ERROR_STR_CONFUSED, + "MediaDescription does not contain codecs"); + g_clear_error (&error); + return; + } + + tp_call_content_media_description_init_known_interfaces (); + proxy = g_object_new (TP_TYPE_PROXY, + "dbus-daemon", tp_proxy_get_dbus_daemon (self->proxy), + "bus-name", tp_proxy_get_bus_name (self->proxy), + "object-path", media_description_objpath, + NULL); + tp_proxy_add_interface_by_id (TP_PROXY (proxy), + TP_IFACE_QUARK_CALL1_CONTENT_MEDIA_DESCRIPTION); + + interfaces = tp_asv_get_strv (properties, + TP_PROP_CALL1_CONTENT_MEDIA_DESCRIPTION_INTERFACES); + + + self->current_has_rtcp_fb = FALSE; + self->current_has_rtp_hdrext = FALSE; + for (i = 0; interfaces[i]; i++) + { + if (!strcmp (interfaces[i], + TP_IFACE_CALL1_CONTENT_MEDIA_DESCRIPTION_INTERFACE_RTCP_FEEDBACK1)) + { + self->current_has_rtcp_fb = TRUE; + rtcp_fb = tp_asv_get_boxed (properties, + TP_PROP_CALL1_CONTENT_MEDIA_DESCRIPTION_INTERFACE_RTCP_FEEDBACK1_FEEDBACK_MESSAGES, + TP_HASH_TYPE_RTCP_FEEDBACK_MESSAGE_MAP); + does_avpf = tp_asv_get_boolean (properties, + TP_PROP_CALL1_CONTENT_MEDIA_DESCRIPTION_INTERFACE_RTCP_FEEDBACK1_DOES_AVPF, NULL); + } + else if (!strcmp (interfaces[i], + TP_IFACE_CALL1_CONTENT_MEDIA_DESCRIPTION_INTERFACE_RTP_HEADER_EXTENSIONS1)) + { + self->current_has_rtp_hdrext = TRUE; + rtp_hdrext = tp_asv_get_boxed (properties, + TP_PROP_CALL1_CONTENT_MEDIA_DESCRIPTION_INTERFACE_RTP_HEADER_EXTENSIONS1_HEADER_EXTENSIONS, + TP_ARRAY_TYPE_RTP_HEADER_EXTENSIONS_LIST); + } + } + + + g_debug ("Got MediaDescription %s", media_description_objpath); + fscodecs = tpcodecs_to_fscodecs (tf_call_content_get_fs_media_type (self), + codecs, does_avpf, rtcp_fb); + + fsrtp_hdrext = tprtphdrext_to_fsrtphdrext (rtp_hdrext); + + fsstream = tf_call_content_get_existing_fsstream_by_handle (self, + contact_handle); + self->current_md_contact_handle = contact_handle; + + if (!fsstream) + { + g_debug ("Delaying codec media_description processing"); + self->current_media_description = proxy; + self->current_md_fscodecs = fscodecs; + self->current_md_rtp_hdrext = fsrtp_hdrext; + return; + } + + process_media_description_try_codecs (self, fsstream, proxy, fscodecs, + fsrtp_hdrext); +} + +static void +on_content_video_keyframe_requested (TpCallContent *proxy, + gpointer user_data, + GObject *weak_object) +{ + TfCallContent *self = TF_CALL_CONTENT (weak_object); + GstPad *pad; + + /* Guard against early disposal */ + if (self->call_channel == NULL) + return; + + /* In case there is no session, ignore the request a new session should start + * with sending a KeyFrame in any case */ + if (self->fssession == NULL) + return; + + g_object_get (self->fssession, "sink-pad", &pad, NULL); + + if (pad == NULL) + { + g_warning ("Failed to get a pad for the keyframe request"); + return; + } + + g_message ("Sending out a keyframe request"); + gst_pad_send_event (pad, + gst_event_new_custom (GST_EVENT_CUSTOM_DOWNSTREAM, + gst_structure_new ("GstForceKeyUnit", + "all-headers", G_TYPE_BOOLEAN, TRUE, + NULL))); + + g_object_unref (pad); +} + +static void +on_content_video_resolution_changed (TpCallContent *proxy, + const GValueArray *resolution, + gpointer user_data, + GObject *weak_object) +{ + TfCallContent *self = TF_CALL_CONTENT (weak_object); + guint width, height; + + /* Guard against early disposal */ + if (self->call_channel == NULL) + return; + + tp_value_array_unpack ((GValueArray *)resolution, 2, + &width, &height, NULL); + + /* Can be 0 in the initial property dump, shouldn't be at any other time */ + if (width == 0 || height == 0) + return; + + self->width = width; + self->height = height; + + g_signal_emit (self, signals[RESOLUTION_CHANGED], 0, width, height); + g_signal_emit_by_name (self, "restart-source"); + + g_message ("requested video resolution: %dx%d", width, height); +} + +static void +on_content_video_bitrate_changed (TpCallContent *proxy, + guint bitrate, + gpointer user_data, + GObject *weak_object) +{ + TfCallContent *self = TF_CALL_CONTENT (weak_object); + + /* Guard against early disposal */ + if (self->call_channel == NULL) + return; + + g_message ("Setting bitrate to %d bits/s", bitrate); + self->bitrate = bitrate; + + if (self->fssession != NULL && self->bitrate > 0) + g_object_set (self->fssession, "send-bitrate", self->bitrate, NULL); +} + +static void +on_content_video_framerate_changed (TpCallContent *proxy, + guint framerate, + gpointer user_data, + GObject *weak_object) +{ + TfCallContent *self = TF_CALL_CONTENT (weak_object); + + /* Guard against early disposal */ + if (self->call_channel == NULL) + return; + + g_message ("updated framerate requested: %d", framerate); + + self->framerate = framerate; + g_object_notify (G_OBJECT (self), "framerate"); + g_signal_emit_by_name (self, "restart-source"); +} + +static void +on_content_video_mtu_changed (TpCallContent *proxy, + guint mtu, + gpointer user_data, + GObject *weak_object) +{ + TfCallContent *self = TF_CALL_CONTENT (weak_object); + + /* Guard against early disposal */ + if (self->call_channel == NULL) + return; + + g_atomic_int_set (&self->mtu, mtu); + + if (self->fsconference != NULL) + { + fs_element_added_notifier_remove (self->notifier, + GST_BIN (self->fsconference)); + + if (mtu > 0 || self->manual_keyframes) + fs_element_added_notifier_add (self->notifier, + GST_BIN (self->fsconference)); + } +} + +static void +streams_added (TpCallContent *proxy, + GPtrArray *streams, + TfCallContent *self) +{ + guint i; + + /* Ignore signals before we got the "Contents" property to avoid races that + * could cause the same content to be added twice + */ + + if (!self->streams) + return; + + for (i = 0; i < streams->len; i++) + add_stream (self, g_ptr_array_index (streams, i)); +} + +static void +streams_removed (TpCallContent *proxy, + const GPtrArray *streams, + TpCallStateReason *reason, + TfCallContent *self) +{ + guint i, j; + + if (!self->streams) + return; + + for (i = 0; i < streams->len; i++) + for (j = 0; j < self->streams->len; j++) + if (g_ptr_array_index (streams, i) == + tf_call_stream_get_proxy (g_ptr_array_index (self->streams, j))) + { + g_ptr_array_remove_index_fast (self->streams, j); + break; + } +} + +static void +got_content_media_properties (TpProxy *proxy, GHashTable *properties, + const GError *error, gpointer user_data, GObject *weak_object) +{ + TfCallContent *self = TF_CALL_CONTENT (weak_object); + GSimpleAsyncResult *res = user_data; + GValueArray *gva; + const gchar *media_description_objpath = NULL; + GHashTable *media_description_properties; + GError *myerror = NULL; + guint32 packetization; + const gchar *conference_type; + gboolean valid; + GList *codec_prefs; + guchar dtmf_event; + guint dtmf_state; + const GValue *dtmf_event_value; + + /* Guard against early disposal */ + if (self->call_channel == NULL) + goto disposed; + + if (error != NULL) + { + tf_call_content_error (self, + TP_CALL_STATE_CHANGE_REASON_INTERNAL_ERROR, + TP_ERROR_STR_MEDIA_STREAMING_ERROR, + "Error getting the Content's media properties: %s", error->message); + g_simple_async_result_set_from_error (res, error); + g_simple_async_result_complete (res); + g_object_unref (res); + return; + } + + packetization = tp_asv_get_uint32 (properties, "Packetization", &valid); + + if (!valid) + goto invalid_property; + + g_assert (self->fssession == NULL); + + switch (packetization) + { + case TP_CALL_CONTENT_PACKETIZATION_TYPE_RTP: + conference_type = "rtp"; + break; + case TP_CALL_CONTENT_PACKETIZATION_TYPE_RAW: + conference_type = "raw"; + break; + default: + tf_call_content_error (self, + TP_CALL_STATE_CHANGE_REASON_MEDIA_ERROR, + TP_ERROR_STR_MEDIA_UNSUPPORTED_TYPE, + "Could not create FsConference for type %d", packetization); + g_simple_async_result_set_error (res, G_IO_ERROR, G_IO_ERROR_NOT_FOUND, + "Could not create FsConference for type %d", packetization); + g_simple_async_result_complete (res); + g_object_unref (res); + return; + } + + self->fsconference = _tf_call_channel_get_conference (self->call_channel, + conference_type); + if (!self->fsconference) + { + tf_call_content_error (self, + TP_CALL_STATE_CHANGE_REASON_MEDIA_ERROR, + TP_ERROR_STR_MEDIA_UNSUPPORTED_TYPE, + "Could not create FsConference for type %s", conference_type); + g_simple_async_result_set_error (res, G_IO_ERROR, G_IO_ERROR_NOT_FOUND, + "Error getting the Content's properties: invalid type"); + g_simple_async_result_complete (res); + g_object_unref (res); + return; + } + + self->fssession = fs_conference_new_session (self->fsconference, + tf_call_content_get_fs_media_type (self), &myerror); + + if (!self->fssession) + { + tf_call_content_error (self, + TP_CALL_STATE_CHANGE_REASON_MEDIA_ERROR, + TP_ERROR_STR_MEDIA_UNSUPPORTED_TYPE, + "Could not create FsSession: %s", myerror != NULL ? myerror->message : "(unknown error)"); + g_simple_async_result_set_from_error (res, myerror); + g_simple_async_result_complete (res); + g_clear_error (&myerror); + g_object_unref (res); + return; + } + + if (self->notifier != NULL) + fs_element_added_notifier_add (self->notifier, + GST_BIN (self->fsconference)); + + /* Guard against early disposal */ + if (self->call_channel == NULL) + goto disposed; + + gva = tp_asv_get_boxed (properties, "MediaDescriptionOffer", + TP_STRUCT_TYPE_MEDIA_DESCRIPTION_OFFER); + if (gva == NULL) + { + goto invalid_property; + } + + codec_prefs = fs_utils_get_default_codec_preferences ( + GST_ELEMENT (self->fsconference)); + + if (codec_prefs) + { + if (!fs_session_set_codec_preferences (self->fssession, codec_prefs, + &myerror)) + { + g_warning ("Could not set codec preference: %s", myerror->message); + g_clear_error (&myerror); + } + } + + /* First complete so we get signalled and the preferences can be set, then + * start looking at the media_description. We only unref the result later, to avoid + * self possibly being disposed early */ + g_simple_async_result_set_op_res_gboolean (res, TRUE); + g_simple_async_result_complete (res); + + /* Guard against early disposal */ + if (self->call_channel == NULL) + goto disposed_already_completed; + + /* Now process outstanding streams */ + add_initial_streams (self); + + tp_g_signal_connect_object (self->proxy, "streams-added", + G_CALLBACK (streams_added), self, 0); + tp_g_signal_connect_object (self->proxy, "streams-removed", + G_CALLBACK (streams_removed), self, 0); + + tp_value_array_unpack (gva, 2, &media_description_objpath, + &media_description_properties); + + if (strcmp (media_description_objpath, "/")) + { + process_media_description (self, media_description_objpath, + media_description_properties); + } + self->got_media_description_property = TRUE; + + + dtmf_state = tp_asv_get_uint32 (properties, "CurrentDTMFState", &valid); + if (!valid) + goto invalid_property; + + dtmf_event_value = tp_asv_lookup (properties, "CurrentDTMFEvent"); + if (!dtmf_event_value || !G_VALUE_HOLDS_UCHAR (dtmf_event_value)) + goto invalid_property; + dtmf_event = g_value_get_uchar (dtmf_event_value); + + on_content_dtmf_change_requested (NULL, dtmf_event, dtmf_state, NULL, + G_OBJECT (self)); + + /* The async result holds a ref to self which may be the last one, so this + * comes after we're done with self */ + g_object_unref (res); + return; + + invalid_property: + tf_call_content_error_literal (self, + TP_CALL_STATE_CHANGE_REASON_INTERNAL_ERROR, + TP_ERROR_STR_CONFUSED, + "Error getting the Content's properties: invalid type"); + g_simple_async_result_set_error (res, G_IO_ERROR, G_IO_ERROR_NOT_FOUND, + "Error getting the Content's properties: invalid type"); + g_simple_async_result_complete (res); + g_object_unref (res); + return; + + disposed: + + g_simple_async_result_set_error (res, G_IO_ERROR, G_IO_ERROR_NOT_FOUND, + "Call content has been disposed of"); + g_simple_async_result_complete (res); + + /* fallthrough */ + disposed_already_completed: + g_object_unref (res); + return; +} + +static void +setup_content_media_properties (TfCallContent *self, GSimpleAsyncResult *res) +{ + GError *error = NULL; + + + if (tp_cli_call1_content_interface_media_connect_to_dtmf_change_requested ( + self->proxy, on_content_dtmf_change_requested, + NULL, NULL, G_OBJECT (self), &error) == NULL) + goto connect_failed; + + tp_cli_dbus_properties_call_get_all (TP_PROXY (self->proxy), -1, + TP_IFACE_CALL1_CONTENT_INTERFACE_MEDIA, + got_content_media_properties, res, NULL, G_OBJECT (self)); + + return; +connect_failed: + tf_call_content_error (self, + TP_CALL_STATE_CHANGE_REASON_INTERNAL_ERROR, + TP_ERROR_STR_CONFUSED, + "Could not connect to the DTMFChangeRequested signal: %s", + error->message); + g_simple_async_result_take_error (res, error); + g_simple_async_result_complete (res); + g_object_unref (res); +} + +static void +update_audio_control (TfCallContent *self, GHashTable *properties) +{ + gint32 input_volume, output_volume; + gboolean valid; + + input_volume = tp_asv_get_uint32 (properties, "RequestedInputVolume", &valid); + if (valid) + { + self->requested_input_volume = input_volume; + g_object_notify (G_OBJECT (self), "requested-input-volume"); + } + + output_volume = tp_asv_get_uint32 (properties, "RequestedOutputVolume", &valid); + if (valid) + { + self->requested_output_volume = output_volume; + g_object_notify (G_OBJECT (self), "requested-output-volume"); + } + +} + +static void +on_content_audio_control_properties_changed (TpProxy *proxy, + const gchar *interface_name, + GHashTable *changed, + const gchar **invalidated, + gpointer user_data, + GObject *weak_object) +{ + TfCallContent *self = TF_CALL_CONTENT (weak_object); + + if (tp_strdiff (interface_name, + TP_IFACE_CALL1_CONTENT_INTERFACE_AUDIO_CONTROL1)) + return; + + /* Guard against early disposal */ + if (self->call_channel == NULL) + return; + + update_audio_control (self, changed); +} + +static void +got_content_audio_control_properties (TpProxy *proxy, GHashTable *properties, + const GError *error, gpointer user_data, GObject *weak_object) +{ + TfCallContent *self = TF_CALL_CONTENT (weak_object); + GSimpleAsyncResult *res = user_data; + + if (error) + { + tf_call_content_error (self, + TP_CALL_STATE_CHANGE_REASON_INTERNAL_ERROR, + TP_ERROR_STR_CONFUSED, + "Error getting the Content's AudioControl properties: %s", + error->message); + g_simple_async_result_set_from_error (res, error); + goto error; + } + + /* Guard against early disposal */ + if (self->call_channel == NULL) + { + g_simple_async_result_set_error (res, G_IO_ERROR, G_IO_ERROR_NOT_FOUND, + "Call content has been disposed of"); + goto error; + } + + if (properties == NULL) + { + tf_call_content_error_literal (self, + TP_CALL_STATE_CHANGE_REASON_INTERNAL_ERROR, + TP_ERROR_STR_CONFUSED, + "Error getting the Content's AudioControl properties: " + "there are none"); + g_simple_async_result_set_error (res, G_IO_ERROR, G_IO_ERROR_NOT_FOUND, + "Error getting the AudioControl Content's properties: " + "there are none"); + goto error; + } + + update_audio_control (self, properties); + + setup_content_media_properties (self, res); + return; + +error: + g_simple_async_result_complete (res); + g_object_unref (res); + return; +} + +static void +setup_content_audio_control (TfCallContent *self, + GSimpleAsyncResult *res) +{ + GError *error = NULL; + + if (tp_cli_dbus_properties_connect_to_properties_changed (self->proxy, + on_content_audio_control_properties_changed, + NULL, NULL, G_OBJECT (self), &error) == NULL) + goto connect_failed; + + tp_cli_dbus_properties_call_get_all (self->proxy, -1, + TP_IFACE_CALL1_CONTENT_INTERFACE_AUDIO_CONTROL1, + got_content_audio_control_properties, res, NULL, G_OBJECT (self)); + + return; + +connect_failed: + tf_call_content_error (self, + TP_CALL_STATE_CHANGE_REASON_INTERNAL_ERROR, + TP_ERROR_STR_CONFUSED, + "Error getting the Content's VideoControl properties: %s", + error->message); + g_simple_async_result_take_error (res, error); + g_simple_async_result_complete (res); + g_object_unref (res); +} + +static void +content_video_element_added (FsElementAddedNotifier *notifier, + GstBin *conference, + GstElement *element, + TfCallContent *self) +{ + gint mtu = g_atomic_int_get (&self->mtu); + + if (G_UNLIKELY (mtu == 0 && !self->manual_keyframes)) + return; + + if (G_UNLIKELY (mtu > 0 && object_has_property (G_OBJECT (element), "mtu"))) + { + g_message ("Setting %d as mtu on payloader", mtu); + g_object_set (element, "mtu", mtu, NULL); + } + + if (G_UNLIKELY (self->manual_keyframes)) + { + if (object_has_property (G_OBJECT (element), "key-int-max")) + { + g_message ("Setting key-int-max to max uint"); + g_object_set (element, "key-int-max", G_MAXINT, NULL); + } + + if (object_has_property (G_OBJECT (element), "intra-period")) + { + g_message ("Setting intra-period to 0"); + g_object_set (element, "intra-period", 0, NULL); + } + } +} + +static void +got_content_video_control_properties (TpProxy *proxy, GHashTable *properties, + const GError *error, gpointer user_data, GObject *weak_object) +{ + TfCallContent *self = TF_CALL_CONTENT (weak_object); + GSimpleAsyncResult *res = user_data; + GValueArray *array; + guint32 bitrate, mtu; + gboolean valid; + gboolean manual_keyframes; + + if (error) + { + tf_call_content_error (self, + TP_CALL_STATE_CHANGE_REASON_INTERNAL_ERROR, + TP_ERROR_STR_CONFUSED, + "Error getting the Content's VideoControl properties: %s", + error->message); + g_simple_async_result_set_from_error (res, error); + goto error; + } + + /* Guard against early disposal */ + if (self->call_channel == NULL) + { + g_simple_async_result_set_error (res, G_IO_ERROR, G_IO_ERROR_NOT_FOUND, + "Call content has been disposed of"); + goto error; + } + + if (properties == NULL) + { + tf_call_content_error_literal (self, + TP_CALL_STATE_CHANGE_REASON_INTERNAL_ERROR, + TP_ERROR_STR_CONFUSED, + "Error getting the Content's VideoControl properties: " + "there are none"); + g_simple_async_result_set_error (res, G_IO_ERROR, G_IO_ERROR_NOT_FOUND, + "Error getting the VideoControl Content's properties: " + "there are none"); + goto error; + } + + + /* Only get the various variables, we will not have an FsSession untill the + * media properties are retrieved so no need to act just yet */ + bitrate = tp_asv_get_uint32 (properties, "Bitrate", &valid); + if (valid) + self->bitrate = bitrate; + + mtu = tp_asv_get_uint32 (properties, "MTU", &valid); + if (valid) + self->mtu = mtu; + + manual_keyframes = tp_asv_get_boolean (properties, + "ManualKeyFrames", &valid); + if (valid) + self->manual_keyframes = manual_keyframes; + + array = tp_asv_get_boxed (properties, "VideoResolution", + TP_STRUCT_TYPE_VIDEO_RESOLUTION); + if (array) + on_content_video_resolution_changed (TP_CALL_CONTENT (proxy), array, + NULL, G_OBJECT (self)); + + self->notifier = fs_element_added_notifier_new (); + g_signal_connect (self->notifier, "element-added", + G_CALLBACK (content_video_element_added), self); + + setup_content_media_properties (self, res); + return; + +error: + g_simple_async_result_complete (res); + g_object_unref (res); + return; +} + + +static void +setup_content_video_control (TfCallContent *self, GSimpleAsyncResult *res) +{ + GError *error = NULL; + + if (tp_cli_call1_content_interface_video_control1_connect_to_key_frame_requested ( + self->proxy, on_content_video_keyframe_requested, + NULL, NULL, G_OBJECT (self), &error) == NULL) + goto connect_failed; + + if (tp_cli_call1_content_interface_video_control1_connect_to_video_resolution_changed ( + self->proxy, on_content_video_resolution_changed, + NULL, NULL, G_OBJECT (self), &error) == NULL) + goto connect_failed; + + if (tp_cli_call1_content_interface_video_control1_connect_to_bitrate_changed ( + self->proxy, on_content_video_bitrate_changed, + NULL, NULL, G_OBJECT (self), NULL) == NULL) + goto connect_failed; + + if (tp_cli_call1_content_interface_video_control1_connect_to_framerate_changed ( + self->proxy, on_content_video_framerate_changed, + NULL, NULL, G_OBJECT (self), NULL) == NULL) + goto connect_failed; + + if (tp_cli_call1_content_interface_video_control1_connect_to_mtu_changed ( + self->proxy, on_content_video_mtu_changed, + NULL, NULL, G_OBJECT (self), NULL) == NULL) + goto connect_failed; + + tp_cli_dbus_properties_call_get_all (TP_PROXY (self->proxy), -1, + TP_IFACE_CALL1_CONTENT_INTERFACE_VIDEO_CONTROL1, + got_content_video_control_properties, res, NULL, G_OBJECT (self)); + + return; + +connect_failed: + tf_call_content_error (self, + TP_CALL_STATE_CHANGE_REASON_INTERNAL_ERROR, + TP_ERROR_STR_CONFUSED, + "Error getting the Content's VideoControl properties: %s", + error->message); + g_simple_async_result_take_error (res, error); + g_simple_async_result_complete (res); + g_object_unref (res); +} + +static void +new_media_description_offer (TpCallContent *proxy, + const gchar *arg_Media_Description, + GHashTable *arg_Properties, + gpointer user_data, + GObject *weak_object) +{ + TfCallContent *self = TF_CALL_CONTENT (weak_object); + + /* Guard against early disposal */ + if (self->call_channel == NULL) + return; + + /* Ignore signals before we get the first codec MediaDescription property */ + if (!self->got_media_description_property) + return; + + if (self->current_media_description) { + g_object_unref (self->current_media_description); + fs_codec_list_destroy (self->current_md_fscodecs); + fs_rtp_header_extension_list_destroy (self->current_md_rtp_hdrext); + self->current_media_description = NULL; + self->current_md_fscodecs = NULL; + self->current_md_rtp_hdrext = NULL; + } + + process_media_description (self, arg_Media_Description, arg_Properties); +} + + +static void +free_stream (gpointer data) +{ + TfCallStream *stream = data; + + _tf_call_stream_destroy (stream); + g_object_unref (stream); +} + + +static void +content_prepared (GObject *src, GAsyncResult *prepare_res, + gpointer user_data) +{ + TpCallContent *proxy = TP_CALL_CONTENT (src); + GSimpleAsyncResult *res = user_data; + TfCallContent *self = + TF_CALL_CONTENT (g_async_result_get_source_object (G_ASYNC_RESULT (res))); + GError *error = NULL; + + if (!tp_proxy_prepare_finish (proxy, prepare_res, &error)) + { + tf_call_content_error (self, + TP_CALL_STATE_CHANGE_REASON_INTERNAL_ERROR, + TP_ERROR_STR_CONFUSED, + "Error getting the Content's properties: %s", error->message); + g_simple_async_result_take_error (res, error); + g_simple_async_result_complete (res); + g_object_unref (res); + return; + } + + /* Guard against early disposal */ + if (self->call_channel == NULL) + { + g_simple_async_result_set_error (res, G_IO_ERROR, G_IO_ERROR_NOT_FOUND, + "Call content has been disposed of"); + g_simple_async_result_complete (res); + g_object_unref (res); + return; + } + + if (!tp_proxy_has_interface_by_id (proxy, + TP_IFACE_QUARK_CALL1_CONTENT_INTERFACE_MEDIA)) + { + tf_call_content_error_literal (self, + TP_CALL_STATE_CHANGE_REASON_INTERNAL_ERROR, + TP_ERROR_STR_CONFUSED, + "Content does not have the media interface," + " but HardwareStreaming was NOT true"); + g_simple_async_result_set_error (res, G_IO_ERROR, G_IO_ERROR_NOT_FOUND, + "Content does not have the media interface," + " but HardwareStreaming was NOT true"); + g_simple_async_result_complete (res); + g_object_unref (res); + return; + } + + self->streams = g_ptr_array_new_with_free_func (free_stream); + + tp_cli_call1_content_interface_media_connect_to_new_media_description_offer ( + self->proxy, new_media_description_offer, NULL, NULL, + G_OBJECT (self), &error); + + if (error) + { + tf_call_content_error (self, + TP_CALL_STATE_CHANGE_REASON_INTERNAL_ERROR, + TP_ERROR_STR_CONFUSED, + "Error connecting to NewCodecMediaDescription signal: %s", + error->message); + g_simple_async_result_set_from_error (res, error); + g_simple_async_result_complete (res); + g_object_unref (res); + g_clear_error (&error); + return; + } + + if (tp_proxy_has_interface_by_id (proxy, + TP_IFACE_QUARK_CALL1_CONTENT_INTERFACE_AUDIO_CONTROL1)) + setup_content_audio_control (self, res); + else if (tp_proxy_has_interface_by_id (proxy, + TP_IFACE_QUARK_CALL1_CONTENT_INTERFACE_VIDEO_CONTROL1)) + setup_content_video_control (self, res); + else + setup_content_media_properties (self, res); + + return; +} + + +static void +tf_call_content_init_async (GAsyncInitable *initable, + int io_priority, + GCancellable *cancellable, + GAsyncReadyCallback callback, + gpointer user_data) +{ + TfCallContent *self = TF_CALL_CONTENT (initable); + GSimpleAsyncResult *res; + + if (cancellable != NULL) + { + g_simple_async_report_error_in_idle (G_OBJECT (self), callback, user_data, + G_IO_ERROR, G_IO_ERROR_NOT_INITIALIZED, + "TfCallChannel initialisation does not support cancellation"); + return; + } + + + res = g_simple_async_result_new (G_OBJECT (self), callback, user_data, + tf_call_content_init_async); + + tp_proxy_prepare_async (self->proxy, NULL, content_prepared, res); +} + +static gboolean +tf_call_content_init_finish (GAsyncInitable *initable, + GAsyncResult *res, + GError **error) +{ + GSimpleAsyncResult *simple_res; + + g_return_val_if_fail (g_simple_async_result_is_valid (res, + G_OBJECT (initable), tf_call_content_init_async), FALSE); + simple_res = G_SIMPLE_ASYNC_RESULT (res); + + if (g_simple_async_result_propagate_error (simple_res, error)) + return FALSE; + + return g_simple_async_result_get_op_res_gboolean (simple_res); +} + +TfCallContent * +tf_call_content_new_async (TfCallChannel *call_channel, + TpCallContent *content_proxy, GError **error, + GAsyncReadyCallback callback, gpointer user_data) +{ + TfCallContent *self; + + g_return_val_if_fail (call_channel != NULL, NULL); + g_return_val_if_fail (content_proxy != NULL, NULL); + + self = g_object_new (TF_TYPE_CALL_CONTENT, NULL); + + self->call_channel = call_channel; + self->proxy = g_object_ref (content_proxy); + + g_async_initable_init_async (G_ASYNC_INITABLE (self), 0, NULL, + callback, user_data); + + return self; +} + +static gboolean +find_codec (GList *codecs, FsCodec *codec) +{ + GList *item; + + for (item = codecs; item ; item = item->next) + if (fs_codec_are_equal (item->data, codec)) + return TRUE; + + return FALSE; +} + + +static GHashTable * +fscodecs_to_media_descriptions (TfCallContent *self, GList *codecs) +{ + GPtrArray *tpcodecs = g_ptr_array_new (); + GList *item; + GList *resend_codecs = NULL; + GHashTable *retval; + GPtrArray *rtp_hdrext = NULL; + GHashTable *rtcp_fb = NULL; + GPtrArray *interfaces; + + if (self->last_sent_codecs) + resend_codecs = fs_session_codecs_need_resend (self->fssession, + self->last_sent_codecs, codecs); + + if (!self->current_media_description && !resend_codecs) + return NULL; + + if ((self->current_media_description && self->current_has_rtp_hdrext) + || self->has_rtp_hdrext) + rtp_hdrext = dbus_g_type_specialized_construct ( + TP_ARRAY_TYPE_RTP_HEADER_EXTENSIONS_LIST); + + if ((self->current_media_description && self->current_has_rtcp_fb) + || self->has_rtcp_fb) + rtcp_fb = dbus_g_type_specialized_construct ( + TP_HASH_TYPE_RTCP_FEEDBACK_MESSAGE_MAP); + + g_debug ("Local codecs:"); + + for (item = codecs; item; item = item->next) + { + FsCodec *fscodec = item->data; + GValue tpcodec = { 0, }; + GHashTable *params; + GList *param_item; + gboolean updated; + gchar *tmp; + + tmp = fs_codec_to_string (fscodec); + g_debug ("%s", tmp); + g_free (tmp); + + params = g_hash_table_new_full (g_str_hash, g_str_equal, g_free, g_free); + + for (param_item = fscodec->optional_params; + param_item; + param_item = param_item->next) + { + FsCodecParameter *param = (FsCodecParameter *) param_item->data; + + g_hash_table_insert (params, g_strdup (param->name), + g_strdup (param->value)); + } + + updated = find_codec (resend_codecs, fscodec); + + g_value_init (&tpcodec, TP_STRUCT_TYPE_CODEC); + g_value_take_boxed (&tpcodec, + dbus_g_type_specialized_construct (TP_STRUCT_TYPE_CODEC)); + + dbus_g_type_struct_set (&tpcodec, + 0, fscodec->id, + 1, fscodec->encoding_name, + 2, fscodec->clock_rate, + 3, fscodec->channels, + 4, updated, + 5, params, + G_MAXUINT); + + + g_hash_table_destroy (params); + + g_ptr_array_add (tpcodecs, g_value_get_boxed (&tpcodec)); + + if (rtcp_fb != NULL && + (fscodec->minimum_reporting_interval != G_MAXUINT || + fscodec->feedback_params)) + { + GPtrArray *messages = g_ptr_array_new (); + GList *item2; + + for (item2 = fscodec->feedback_params; item2; item2 = item2->next) + { + FsFeedbackParameter *fb = item2->data; + + g_ptr_array_add (messages, tp_value_array_build (3, + G_TYPE_STRING, fb->type, + G_TYPE_STRING, fb->subtype, + G_TYPE_STRING, fb->extra_params, + G_TYPE_INVALID)); + } + + g_hash_table_insert (rtcp_fb, GUINT_TO_POINTER (fscodec->id), + tp_value_array_build (2, + G_TYPE_UINT, + fscodec->minimum_reporting_interval != G_MAXUINT ? + fscodec->minimum_reporting_interval : 5000, + TP_ARRAY_TYPE_RTCP_FEEDBACK_MESSAGE_LIST, messages, + G_TYPE_INVALID)); + + g_boxed_free (TP_ARRAY_TYPE_RTCP_FEEDBACK_MESSAGE_LIST, messages); + } + } + + fs_codec_list_destroy (resend_codecs); + + + if (rtp_hdrext) + { + GList *fs_rtp_hdrexts; + + g_object_get (self->fssession, "rtp-header-extensions", &fs_rtp_hdrexts, + NULL); + + for (item = fs_rtp_hdrexts; item; item = item->next) + { + FsRtpHeaderExtension *hdrext = item->data; + + g_debug (FS_RTP_HEADER_EXTENSION_FORMAT, + FS_RTP_HEADER_EXTENSION_ARGS (hdrext)); + + g_ptr_array_add (rtp_hdrext, tp_value_array_build (4, + G_TYPE_UINT, hdrext->id, + G_TYPE_UINT, fsdirection_to_tpdirection (hdrext->direction), + G_TYPE_STRING, hdrext->uri, + G_TYPE_STRING, "", + G_TYPE_INVALID)); + } + + fs_rtp_header_extension_list_destroy (fs_rtp_hdrexts); + } + + retval = tp_asv_new ( + TP_PROP_CALL1_CONTENT_MEDIA_DESCRIPTION_CODECS, + TP_ARRAY_TYPE_CODEC_LIST, tpcodecs, + TP_PROP_CALL1_CONTENT_MEDIA_DESCRIPTION_FURTHER_NEGOTIATION_REQUIRED, + G_TYPE_BOOLEAN, !!resend_codecs, + NULL); + + interfaces = g_ptr_array_new (); + + if (rtp_hdrext) + { + tp_asv_take_boxed (retval, TP_PROP_CALL1_CONTENT_MEDIA_DESCRIPTION_INTERFACE_RTP_HEADER_EXTENSIONS1_HEADER_EXTENSIONS, + TP_ARRAY_TYPE_RTP_HEADER_EXTENSIONS_LIST, + rtp_hdrext); + g_ptr_array_add (interfaces, + g_strdup (TP_IFACE_CALL1_CONTENT_MEDIA_DESCRIPTION_INTERFACE_RTP_HEADER_EXTENSIONS1)); + } + + if (rtcp_fb) + { + tp_asv_set_boolean (retval, TP_PROP_CALL1_CONTENT_MEDIA_DESCRIPTION_INTERFACE_RTCP_FEEDBACK1_DOES_AVPF, g_hash_table_size (rtcp_fb)); + tp_asv_take_boxed (retval, TP_PROP_CALL1_CONTENT_MEDIA_DESCRIPTION_INTERFACE_RTCP_FEEDBACK1_FEEDBACK_MESSAGES, + TP_HASH_TYPE_RTCP_FEEDBACK_MESSAGE_MAP, rtcp_fb); + g_ptr_array_add (interfaces, + g_strdup (TP_IFACE_CALL1_CONTENT_MEDIA_DESCRIPTION_INTERFACE_RTP_HEADER_EXTENSIONS1)); + } + + g_ptr_array_add (interfaces, NULL); + tp_asv_take_boxed (retval, TP_PROP_CALL1_CONTENT_MEDIA_DESCRIPTION_INTERFACES, + G_TYPE_STRV, interfaces->pdata); + g_ptr_array_free (interfaces, FALSE); + + return retval; +} + +static void +media_description_updated_cb (TpCallContent *proxy, + const GError *error, + gpointer user_data, + GObject *weak_object) +{ + if (error == NULL) + g_debug ("Local media description set"); + else + g_debug ("Local media description error: %s", error->message); + +} + +static void +tf_call_content_try_sending_codecs (TfCallContent *self) +{ + GList *codecs; + GHashTable *media_description; + const gchar *codecs_prop = NULL; + guint i; + + if (self->current_md_fscodecs != NULL) + { + g_debug ("Ignoring updated codecs unprocessed media description" + " outstanding"); + return; + } + + g_debug ("updating local codecs: %d", TF_CONTENT (self)->sending_count); + + if (TF_CONTENT (self)->sending_count == 0) + codecs_prop = "codecs-without-config"; + else + codecs_prop = "codecs"; + + g_object_get (self->fssession, codecs_prop, &codecs, NULL); + + if (!codecs) + return; + + media_description = fscodecs_to_media_descriptions (self, codecs); + if (!media_description) + { + fs_codec_list_destroy (codecs); + return; + } + + TF_CALL_CONTENT_LOCK (self); + + for (i = 0; i < self->fsstreams->len; i++) + { + struct CallFsStream *cfs = g_ptr_array_index (self->fsstreams, i); + + tp_asv_set_uint32 (media_description, + TP_PROP_CALL1_CONTENT_MEDIA_DESCRIPTION_REMOTE_CONTACT, + cfs->contact_handle); + + if (self->current_media_description && + self->current_md_contact_handle == cfs->contact_handle) + { + g_debug ("Accepting Media Description for contact: %u", + cfs->contact_handle); + + tp_cli_call1_content_media_description_call_accept ( + self->current_media_description, -1, media_description, + NULL, NULL, NULL, NULL); + + g_object_unref (self->current_media_description); + self->current_media_description = NULL; + } + else + { + g_debug ("Updating local Media Description for contact %u", + cfs->contact_handle); + + tp_cli_call1_content_interface_media_call_update_local_media_description ( + self->proxy, -1, media_description, + media_description_updated_cb, NULL, NULL, NULL); + } + } + + TF_CALL_CONTENT_UNLOCK (self); + + if (media_description) + { + fs_codec_list_destroy (self->last_sent_codecs); + self->last_sent_codecs = codecs; + self->has_rtcp_fb = self->current_has_rtcp_fb; + self->has_rtp_hdrext = self->current_has_rtp_hdrext; + + g_boxed_free (TP_HASH_TYPE_MEDIA_DESCRIPTION_PROPERTIES, media_description); + } +} + +static void +tf_call_content_dtmf_started (TfCallContent *self, FsDTMFMethod method, + FsDTMFEvent event, guint8 volume) +{ + if (volume != DTMF_TONE_VOLUME) + { + tf_call_content_error (self, + TP_CALL_STATE_CHANGE_REASON_INTERNAL_ERROR, + TP_ERROR_STR_MEDIA_STREAMING_ERROR, + "DTMF volume is %d, while we use %d", volume, DTMF_TONE_VOLUME); + return; + } + + if (self->dtmf_sending_state != TP_SENDING_STATE_PENDING_SEND) + { + tf_call_content_error (self, + TP_CALL_STATE_CHANGE_REASON_INTERNAL_ERROR, + TP_ERROR_STR_MEDIA_STREAMING_ERROR, + "Farstream started a DTMFevent, but we were in the %d state", + self->dtmf_sending_state); + return; + } + + if (self->current_dtmf_event != event) + { + tf_call_content_error (self, + TP_CALL_STATE_CHANGE_REASON_INTERNAL_ERROR, + TP_ERROR_STR_MEDIA_STREAMING_ERROR, + "Farstream started the wrong dtmf event, got %d but " + "expected %d", event, self->current_dtmf_event); + return; + } + + tp_cli_call1_content_interface_media_call_acknowledge_dtmf_change ( + self->proxy, -1, event, TP_SENDING_STATE_SENDING, + NULL, NULL, NULL, NULL); + self->dtmf_sending_state = TP_SENDING_STATE_SENDING; +} + +static void +tf_call_content_dtmf_stopped (TfCallContent *self, FsDTMFMethod method) +{ + if (self->dtmf_sending_state != TP_SENDING_STATE_PENDING_STOP_SENDING) + { + tf_call_content_error (self, + TP_CALL_STATE_CHANGE_REASON_INTERNAL_ERROR, + TP_ERROR_STR_MEDIA_STREAMING_ERROR, + "Farstream stopped a DTMFevent, but we were in the %d state", + self->dtmf_sending_state); + return; + } + + tp_cli_call1_content_interface_media_call_acknowledge_dtmf_change ( + self->proxy, -1, self->current_dtmf_event, TP_SENDING_STATE_NONE, + NULL, NULL, NULL, NULL); + self->dtmf_sending_state = TP_SENDING_STATE_NONE; +} + + +gboolean +tf_call_content_bus_message (TfCallContent *content, + GstMessage *message) +{ + gboolean ret = TRUE; + FsDTMFMethod method; + FsDTMFEvent event; + guint8 volume; + FsCodec *codec; + GList *secondary_codecs; + FsError error_no; + const gchar *error_msg; + guint i; + + + /* Guard against early disposal */ + if (content->call_channel == NULL) + return FALSE; + + if (!content->fssession) + return FALSE; + + if (GST_MESSAGE_TYPE (message) != GST_MESSAGE_ELEMENT) + return FALSE; + + + if (fs_parse_error (G_OBJECT (content->fssession), message, &error_no, + &error_msg)) + { + GEnumClass *enumclass; + GEnumValue *enumvalue; + + enumclass = g_type_class_ref (FS_TYPE_ERROR); + enumvalue = g_enum_get_value (enumclass, error_no); + g_warning ("error (%s (%d)): %s", + enumvalue->value_nick, error_no, error_msg); + g_type_class_unref (enumclass); + + tf_call_content_error_literal (content, + TP_CALL_STATE_CHANGE_REASON_INTERNAL_ERROR, + TP_ERROR_STR_MEDIA_STREAMING_ERROR, error_msg); + } + else if (fs_session_parse_codecs_changed (content->fssession, message)) + { + g_debug ("Codecs changed"); + + tf_call_content_try_sending_codecs (content); + } + else if (fs_session_parse_telephony_event_started (content->fssession, + message, &method, &event, &volume)) + { + g_debug ("DTMF started: method: %d event: %u volume: %u", + method, event, volume); + + tf_call_content_dtmf_started (content, method, event, volume); + } + else if (fs_session_parse_telephony_event_stopped (content->fssession, + message, &method)) + { + g_debug ("DTMF stopped: method: %d", method); + + tf_call_content_dtmf_stopped (content, method); + } + else if (fs_session_parse_send_codec_changed (content->fssession, message, + &codec, &secondary_codecs)) + { + gchar *tmp; + guint j = 1; + + tmp = fs_codec_to_string (codec); + g_debug ("Send codec changed: %s", tmp); + g_free (tmp); + + while (secondary_codecs) + { + tmp = fs_codec_to_string (secondary_codecs->data); + g_debug ("Secondary send codec %u changed: %s", j++, tmp); + g_free (tmp); + secondary_codecs = secondary_codecs->next; + } + } + else + { + ret = FALSE; + } + + for (i = 0; i < content->streams->len; i++) + if (tf_call_stream_bus_message (g_ptr_array_index (content->streams, i), + message)) + return TRUE; + + return ret; +} + + +static void +tf_call_content_error (TfCallContent *self, + TpCallStateChangeReason reason, + const gchar *detailed_reason, + const gchar *message_format, + ...) +{ + gchar *message; + va_list valist; + + va_start (valist, message_format); + message = g_strdup_vprintf (message_format, valist); + va_end (valist); + + tf_call_content_error_literal (self, reason, detailed_reason, message); + g_free (message); +} + + +static void +tf_call_content_error_literal (TfCallContent *self, + TpCallStateChangeReason reason, + const gchar *detailed_reason, + const gchar *message) +{ + g_debug ("Content error: %s", message); + tp_cli_call1_content_interface_media_call_fail ( + self->proxy, -1, + tp_value_array_build (4, + G_TYPE_UINT, 0, + G_TYPE_UINT, reason, + G_TYPE_STRING, detailed_reason, + G_TYPE_STRING, message, + G_TYPE_INVALID), + NULL, NULL, NULL, NULL); +} + + +static void +tf_call_content_error_impl (TfContent *content, + const gchar *message) +{ + tf_call_content_error_literal (TF_CALL_CONTENT (content), + TP_CALL_STATE_CHANGE_REASON_INTERNAL_ERROR, + TP_ERROR_STR_MEDIA_STREAMING_ERROR, message); +} + + +static FsStream * +tf_call_content_get_existing_fsstream_by_handle (TfCallContent *content, + guint contact_handle) +{ + guint i; + + TF_CALL_CONTENT_LOCK (content); + + for (i = 0; i < content->fsstreams->len; i++) + { + struct CallFsStream *cfs = g_ptr_array_index (content->fsstreams, i); + + if (cfs->contact_handle == contact_handle) + { + cfs->use_count++; + TF_CALL_CONTENT_UNLOCK (content); + return cfs->fsstream; + } + } + + TF_CALL_CONTENT_UNLOCK (content); + + return NULL; +} + + +FsStream * +_tf_call_content_get_fsstream_by_handle (TfCallContent *content, + guint contact_handle, + FsStreamDirection dir, + const gchar *transmitter, + guint stream_transmitter_n_parameters, + GParameter *stream_transmitter_parameters, + GError **error) +{ + struct CallFsStream *cfs; + FsParticipant *p; + FsStream *s; + + s = tf_call_content_get_existing_fsstream_by_handle (content, + contact_handle); + if (s) + return s; + + p = _tf_call_channel_get_participant (content->call_channel, + content->fsconference, contact_handle, error); + if (!p) + return NULL; + + s = fs_session_new_stream (content->fssession, p, dir, error); + if (!s) + { + _tf_call_channel_put_participant (content->call_channel, p); + return NULL; + } + + if (!fs_stream_set_transmitter (s, transmitter, + stream_transmitter_parameters, stream_transmitter_n_parameters, + error)) + { + g_object_unref (s); + _tf_call_channel_put_participant (content->call_channel, p); + return NULL; + } + + cfs = g_slice_new (struct CallFsStream); + cfs->use_count = 1; + cfs->contact_handle = contact_handle; + cfs->parent_channel = content->call_channel; + cfs->fsparticipant = p; + cfs->fsstream = s; + + tp_g_signal_connect_object (s, "src-pad-added", + G_CALLBACK (src_pad_added), content, 0); + + g_ptr_array_add (content->fsstreams, cfs); + content->fsstreams_cookie ++; + if (content->current_media_description != NULL + && content->current_md_contact_handle == contact_handle) + { + GList *codecs = content->current_md_fscodecs; + TpProxy *current_media_description = content->current_media_description; + GList *rtp_hdrext = content->current_md_rtp_hdrext; + + content->current_md_fscodecs = NULL; + content->current_media_description = NULL; + content->current_md_rtp_hdrext = NULL; + + /* ownership transfers to try_codecs */ + process_media_description_try_codecs (content, s, + current_media_description, codecs, rtp_hdrext); + } + + return s; +} + +void +_tf_call_content_put_fsstream (TfCallContent *content, FsStream *fsstream) +{ + guint i; + struct CallFsStream *fs_cfs = NULL; + + TF_CALL_CONTENT_LOCK (content); + for (i = 0; i < content->fsstreams->len; i++) + { + struct CallFsStream *cfs = g_ptr_array_index (content->fsstreams, i); + + if (cfs->fsstream == fsstream) + { + cfs->use_count--; + if (cfs->use_count <= 0) + { + fs_cfs = g_ptr_array_remove_index_fast (content->fsstreams, i); + content->fsstreams_cookie++; + } + break; + } + } + TF_CALL_CONTENT_UNLOCK (content); + + if (fs_cfs) + free_content_fsstream (fs_cfs); +} + +FsMediaType +tf_call_content_get_fs_media_type (TfCallContent *content) +{ + return tp_media_type_to_fs (tp_call_content_get_media_type (content->proxy)); +} + +static void +src_pad_added (FsStream *fsstream, GstPad *pad, FsCodec *codec, + TfCallContent *content) +{ + guint handle = 0; + guint i; + + TF_CALL_CONTENT_LOCK (content); + + if (!content->fsstreams) + { + TF_CALL_CONTENT_UNLOCK (content); + return; + } + + for (i = 0; i < content->fsstreams->len; i++) + { + struct CallFsStream *cfs = g_ptr_array_index (content->fsstreams, i); + if (cfs->fsstream == fsstream) + { + handle = cfs->contact_handle; + break; + } + } + + TF_CALL_CONTENT_UNLOCK (content); + + _tf_content_emit_src_pad_added (TF_CONTENT (content), handle, + fsstream, pad, codec); +} + +struct StreamSrcPadIterator { + GstIterator iterator; + + GArray *handles; + GArray *handles_backup; + + TfCallContent *self; +}; + + +static void +streams_src_pads_iter_copy (const GstIterator *orig, GstIterator *copy) +{ + const struct StreamSrcPadIterator *iter_orig = + (const struct StreamSrcPadIterator *) orig; + struct StreamSrcPadIterator *iter = (struct StreamSrcPadIterator *) copy; + + iter->handles = g_array_sized_new (TRUE, FALSE, sizeof(guint), + iter_orig->handles->len ); + iter->handles_backup = g_array_sized_new (TRUE, FALSE, sizeof(guint), + iter_orig->handles_backup->len ); + g_array_append_vals (iter->handles, iter_orig->handles->data, + iter_orig->handles->len); + g_array_append_vals (iter->handles_backup, iter_orig->handles_backup->data, + iter_orig->handles_backup->len); + iter->self = g_object_ref (iter_orig->self); +} + + +static GstIteratorResult +streams_src_pads_iter_next (GstIterator *it, GValue *result) +{ + struct StreamSrcPadIterator *iter = (struct StreamSrcPadIterator *) it; + + guint i; + + if (iter->handles->len == 0) + return GST_ITERATOR_DONE; + + for (i = 0; i < iter->self->fsstreams->len; i++) + { + struct CallFsStream *cfs = g_ptr_array_index (iter->self->fsstreams, i); + + if (cfs->contact_handle == g_array_index (iter->handles, guint, 0)) + { + g_array_remove_index_fast (iter->handles, 0); + g_value_unset (result); + g_value_init (result, G_TYPE_POINTER); + g_value_set_pointer (result, cfs); + return GST_ITERATOR_OK; + } + } + + return GST_ITERATOR_ERROR; + +} + +static GstIteratorItem +streams_src_pads_iter_item (GstIterator *it, const GValue *item) +{ + struct CallFsStream *cfs = g_value_get_pointer (item); + + g_value_unset ((GValue*) item); + + gst_iterator_push (it, fs_stream_iterate_src_pads (cfs->fsstream)); + + return GST_ITERATOR_ITEM_SKIP; +} + +static void +streams_src_pads_iter_resync (GstIterator *it) +{ + struct StreamSrcPadIterator *iter = (struct StreamSrcPadIterator *) it; + + g_array_set_size (iter->handles, iter->handles_backup->len); + memcpy (iter->handles->data, iter->handles_backup->data, + iter->handles_backup->len * sizeof(guint)); +} + +static void +streams_src_pads_iter_free (GstIterator *it) +{ + struct StreamSrcPadIterator *iter = (struct StreamSrcPadIterator *) it; + + g_array_unref (iter->handles); + g_array_unref (iter->handles_backup); + g_object_unref (iter->self); +} + +static GstIterator * +tf_call_content_iterate_src_pads (TfContent *content, guint *handles, + guint handle_count) +{ + TfCallContent *self = TF_CALL_CONTENT (content); + struct StreamSrcPadIterator *iter; + + iter = (struct StreamSrcPadIterator *) gst_iterator_new ( + sizeof (struct StreamSrcPadIterator), GST_TYPE_PAD, + &self->mutex, &self->fsstreams_cookie, + streams_src_pads_iter_copy, + streams_src_pads_iter_next, + streams_src_pads_iter_item, + streams_src_pads_iter_resync, + streams_src_pads_iter_free); + + iter->handles = g_array_sized_new (TRUE, FALSE, sizeof(guint), handle_count); + iter->handles_backup = g_array_sized_new (TRUE, FALSE, sizeof(guint), + handle_count); + g_array_append_vals (iter->handles, handles, handle_count); + g_array_append_vals (iter->handles_backup, handles, handle_count); + iter->self = g_object_ref (self); + + return (GstIterator *) iter; +} + +static void +tf_call_content_sending_failed (TfContent *content, + const gchar *message) +{ + TfCallContent *self = TF_CALL_CONTENT (content); + guint i; + + if (!self->streams) + { + g_warning ("Too early, ignoring sending error"); + return; + } + + for (i = 0; i < self->streams->len; i++) + tf_call_stream_sending_failed (g_ptr_array_index (self->streams, i), + message); +} + + +static void +tf_call_content_receiving_failed (TfContent *content, + guint *handles, guint handle_count, + const gchar *message) +{ + TfCallContent *self = TF_CALL_CONTENT (content); + guint i; + + if (!self->streams) + { + g_warning ("Too early, ignoring sending error"); + return; + } + + for (i = 0; i < self->streams->len; i++) + tf_call_stream_receiving_failed (g_ptr_array_index (self->streams, i), + handles, handle_count, message); +} + + +TpCallContent * +tf_call_content_get_proxy (TfCallContent *content) +{ + g_return_val_if_fail (TF_IS_CALL_CONTENT (content), NULL); + + return content->proxy; +} diff --git a/telepathy-farstream/call-content.h b/telepathy-farstream/call-content.h new file mode 100644 index 000000000..499eead17 --- /dev/null +++ b/telepathy-farstream/call-content.h @@ -0,0 +1,109 @@ +/* + * call-content.h - Source for TfCallContent + * Copyright (C) 2010 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 + */ + +#ifndef __TF_CALL_CONTENT_H__ +#define __TF_CALL_CONTENT_H__ + +#include <glib-object.h> + +#include <gst/gst.h> +#include <telepathy-glib/telepathy-glib.h> + +#include "call-channel.h" +#include "content.h" +#include "content-priv.h" + +G_BEGIN_DECLS + +#define TF_TYPE_CALL_CONTENT tf_call_content_get_type() + +#define TF_CALL_CONTENT(obj) \ + (G_TYPE_CHECK_INSTANCE_CAST ((obj), \ + TF_TYPE_CALL_CONTENT, TfCallContent)) + +#define TF_CALL_CONTENT_CLASS(klass) \ + (G_TYPE_CHECK_CLASS_CAST ((klass), \ + TF_TYPE_CALL_CONTENT, TfCallContentClass)) + +#define TF_IS_CALL_CONTENT(obj) \ + (G_TYPE_CHECK_INSTANCE_TYPE ((obj), TF_TYPE_CALL_CONTENT)) + +#define TF_IS_CALL_CONTENT_CLASS(klass) \ + (G_TYPE_CHECK_CLASS_TYPE ((klass), TF_TYPE_CALL_CONTENT)) + +#define TF_CALL_CONTENT_GET_CLASS(obj) \ + (G_TYPE_INSTANCE_GET_CLASS ((obj), \ + TF_TYPE_CALL_CONTENT, TfCallContentClass)) + +typedef struct _TfCallContentPrivate TfCallContentPrivate; + +/** + * TfCallContent: + * + * All members of the object are private + */ + +typedef struct _TfCallContent TfCallContent; + +/** + * TfCallContentClass: + * @parent_class: the parent #GObjecClass + * + * There are no overridable functions + */ + +typedef struct _TfCallContentClass TfCallContentClass; + +GType tf_call_content_get_type (void); + +TfCallContent *tf_call_content_new_async ( + TfCallChannel *call_channel, + TpCallContent *content_proxy, + GError **error, + GAsyncReadyCallback callback, + gpointer user_data); + +gboolean tf_call_content_bus_message (TfCallContent *content, + GstMessage *message); + + +/* Private */ +FsStream *_tf_call_content_get_fsstream_by_handle (TfCallContent *content, + guint contact_handle, + FsStreamDirection dir, + const gchar *transmitter, + guint stream_transmitter_n_parameters, + GParameter *stream_transmitter_parameters, + GError **error); +void _tf_call_content_put_fsstream (TfCallContent *content, FsStream *fsstream); + +FsMediaType +tf_call_content_get_fs_media_type (TfCallContent *content); + + +gboolean +tf_call_content_bus_message (TfCallContent *content, GstMessage *message); + +TpCallContent * +tf_call_content_get_proxy (TfCallContent *content); + +G_END_DECLS + +#endif /* __TF_CALL_CONTENT_H__ */ + diff --git a/telepathy-farstream/call-priv.h b/telepathy-farstream/call-priv.h new file mode 100644 index 000000000..c89c056f4 --- /dev/null +++ b/telepathy-farstream/call-priv.h @@ -0,0 +1,33 @@ +/* + * call-priv.h + * Copyright (C) 2012 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 + */ + + +#ifndef __TF_CALL_PRIV_H__ +#define __TF_CALL_PRIV_H__ + +#include "call-stream.h" + +G_BEGIN_DECLS + +void _tf_call_content_destroy (TfCallContent *self); +void _tf_call_stream_destroy (TfCallStream *self); + +G_END_DECLS + +#endif /* __TF_CALL_PRIV_H__ */ diff --git a/telepathy-farstream/call-stream.c b/telepathy-farstream/call-stream.c new file mode 100644 index 000000000..4052c2481 --- /dev/null +++ b/telepathy-farstream/call-stream.c @@ -0,0 +1,1742 @@ +/* + * call-stream.c - Source for TfCallStream + * Copyright (C) 2010 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 + */ + +/** + * SECTION:tfcallstream + * + * @short_description: Handle the Stream objects for a Call1 channel + * + * This class handles the org.freedesktop.Telepathy.Call1.Stream, + * org.freedesktop.Telepathy.Call1.Stream.Interface.Media and + * org.freedesktop.Telepathy.Call1.Stream.Endpoint interfaces. + */ + +/* + * TODO: + * - Support multiple handles + * - Allow app to fail sending or receiving during call + * + * Endpoints: + * - Support multiple Endpoints (ie SIP forking with ICE) + * - Call SetControlling + * - Listen to CandidatePairSelected and call AcceptSelectedCandidatePair/RejectSelectedCandidatePair + * - Support IsICELite + */ + +#include "config.h" + +#include "call-stream.h" +#include "call-priv.h" + +#include <telepathy-glib/telepathy-glib.h> +#include <telepathy-glib/telepathy-glib-dbus.h> +#include <telepathy-glib/proxy-subclass.h> +#include <farstream/fs-conference.h> + +#include <stdarg.h> +#include <string.h> +#include <stdlib.h> + +#include "utils.h" + + +G_DEFINE_TYPE (TfCallStream, tf_call_stream, G_TYPE_OBJECT); + +static void tf_call_stream_dispose (GObject *object); +static void tf_call_stream_finalize (GObject *object); + +static void tf_call_stream_fail_literal (TfCallStream *self, + TpCallStateChangeReason reason, + const gchar *detailed_reason, + const gchar *message); + +static void tf_call_stream_fail (TfCallStream *self, + TpCallStateChangeReason reason, + const gchar *detailed_reason, + const gchar *message_format, + ...); + +static void _tf_call_stream_remove_endpoint (TfCallStream *self); + + +static void +tf_call_stream_class_init (TfCallStreamClass *klass) +{ + GObjectClass *object_class = G_OBJECT_CLASS (klass); + + object_class->dispose = tf_call_stream_dispose; + object_class->finalize = tf_call_stream_finalize; +} + +static void +tf_call_stream_init (TfCallStream *self) +{ + self->sending_state = TP_STREAM_FLOW_STATE_STOPPED; + self->receiving_state = TP_STREAM_FLOW_STATE_STOPPED; +} + +void +_tf_call_stream_destroy (TfCallStream *self) +{ + if (self->proxy) + g_object_unref (self->proxy); + self->proxy = NULL; + + if (self->fsstream) + { + g_object_set (self->fsstream, "direction", FS_DIRECTION_NONE, NULL); + + if (self->has_send_resource) + _tf_content_stop_sending (TF_CONTENT (self->call_content)); + self->has_send_resource = FALSE; + + if (self->has_receive_resource) + _tf_content_stop_receiving (TF_CONTENT (self->call_content), + &self->contact_handle, 1); + self->has_receive_resource = FALSE; + _tf_call_content_put_fsstream (self->call_content, self->fsstream); + } + + self->fsstream = NULL; + + if (self->endpoint) + _tf_call_stream_remove_endpoint (self); + + self->call_content = NULL; +} + +static void +tf_call_stream_dispose (GObject *object) +{ + TfCallStream *self = TF_CALL_STREAM (object); + + g_debug (G_STRFUNC); + + _tf_call_stream_destroy (self); + + if (G_OBJECT_CLASS (tf_call_stream_parent_class)->dispose) + G_OBJECT_CLASS (tf_call_stream_parent_class)->dispose (object); +} + +static void +tf_call_stream_finalize (GObject *object) +{ + TfCallStream *self = TF_CALL_STREAM (object); + + g_debug (G_STRFUNC); + + if (self->stun_servers) + g_boxed_free (TP_ARRAY_TYPE_SOCKET_ADDRESS_IP_LIST, self->stun_servers); + self->stun_servers = NULL; + + if (self->relay_info) + g_boxed_free (TP_ARRAY_TYPE_STRING_VARIANT_MAP_LIST, self->relay_info); + self->relay_info = NULL; + + if (G_OBJECT_CLASS (tf_call_stream_parent_class)->finalize) + G_OBJECT_CLASS (tf_call_stream_parent_class)->finalize (object); +} + + +static void +tf_call_stream_update_sending_state (TfCallStream *self) +{ + gboolean sending = FALSE; + FsStreamDirection dir; + + if (self->fsstream == NULL) + return; + + if (self->endpoint == NULL) + goto done; + + switch (self->sending_state) + { + case TP_STREAM_FLOW_STATE_PENDING_START: + if (self->has_send_resource) + sending = TRUE; + break; + case TP_STREAM_FLOW_STATE_STARTED: + sending = TRUE; + break; + default: + break; + } + + done: + g_object_get (self->fsstream, "direction", &dir, NULL); + if (sending) + g_object_set (self->fsstream, "direction", dir | FS_DIRECTION_SEND, NULL); + else + g_object_set (self->fsstream, "direction", dir & ~FS_DIRECTION_SEND, NULL); +} + +static void +sending_state_changed (TpCallStream *proxy, + guint arg_State, + gpointer user_data, GObject *weak_object) +{ + TfCallStream *self = TF_CALL_STREAM (weak_object); + + self->sending_state = arg_State; + + if (!self->fsstream) + return; + + switch (arg_State) + { + case TP_STREAM_FLOW_STATE_PENDING_START: + if (self->has_send_resource || + _tf_content_start_sending (TF_CONTENT (self->call_content))) + { + self->has_send_resource = TRUE; + + tp_cli_call1_stream_interface_media_call_complete_sending_state_change ( + proxy, -1, TP_STREAM_FLOW_STATE_STARTED, + NULL, NULL, NULL, NULL); + tf_call_stream_update_sending_state (self); + } + else + { + tp_cli_call1_stream_interface_media_call_report_sending_failure ( + proxy, -1, TP_CALL_STATE_CHANGE_REASON_INTERNAL_ERROR, + TP_ERROR_STR_MEDIA_STREAMING_ERROR, + "Could not start sending", NULL, NULL, NULL, NULL); + return; + } + break; + case TP_STREAM_FLOW_STATE_PENDING_STOP: + tf_call_stream_update_sending_state (self); + if (self->has_send_resource) + { + _tf_content_stop_sending (TF_CONTENT (self->call_content)); + + self->has_send_resource = FALSE; + } + tp_cli_call1_stream_interface_media_call_complete_sending_state_change ( + proxy, -1, TP_STREAM_FLOW_STATE_STOPPED, NULL, NULL, NULL, NULL); + break; + default: + break; + } +} + +static gboolean +tf_call_stream_start_receiving (TfCallStream *self, FsStreamDirection dir) +{ + if (self->has_receive_resource || + _tf_content_start_receiving (TF_CONTENT (self->call_content), + &self->contact_handle, 1)) + { + self->has_receive_resource = TRUE; + if (self->fsstream) + g_object_set (self->fsstream, + "direction", dir | FS_DIRECTION_RECV, NULL); + tp_cli_call1_stream_interface_media_call_complete_receiving_state_change ( + self->proxy, -1, TP_STREAM_FLOW_STATE_STARTED, + NULL, NULL, NULL, NULL); + return TRUE; + } + else + { + tp_cli_call1_stream_interface_media_call_report_receiving_failure ( + self->proxy, -1, TP_CALL_STATE_CHANGE_REASON_INTERNAL_ERROR, + TP_ERROR_STR_MEDIA_STREAMING_ERROR, + "Could not start receiving", NULL, NULL, NULL, NULL); + return FALSE; + } +} + +static void +receiving_state_changed (TpCallStream *proxy, + guint arg_State, + gpointer user_data, GObject *weak_object) +{ + TfCallStream *self = TF_CALL_STREAM (weak_object); + FsStreamDirection dir; + + self->receiving_state = arg_State; + + if (!self->fsstream) + return; + + g_object_get (self->fsstream, "direction", &dir, NULL); + + switch (arg_State) + { + case TP_STREAM_FLOW_STATE_PENDING_START: + tf_call_stream_start_receiving (self, dir); + break; + case TP_STREAM_FLOW_STATE_PENDING_STOP: + g_object_set (self->fsstream, + "direction", dir & ~FS_DIRECTION_RECV, NULL); + if (self->has_receive_resource) + { + _tf_content_stop_receiving (TF_CONTENT (self->call_content), + &self->contact_handle, 1); + + self->has_receive_resource = FALSE; + } + tp_cli_call1_stream_interface_media_call_complete_receiving_state_change ( + proxy, -1, TP_STREAM_FLOW_STATE_STOPPED, NULL, NULL, NULL, NULL); + break; + default: + break; + } +} + +static void +_tf_call_stream_push_remote_candidates (TfCallStream *self, + GList *fscandidates) +{ + gboolean ret; + GError *error = NULL; + + switch (self->transport_type) + { + case TP_STREAM_TRANSPORT_TYPE_RAW_UDP: + case TP_STREAM_TRANSPORT_TYPE_SHM: + case TP_STREAM_TRANSPORT_TYPE_MULTICAST: + ret = fs_stream_force_remote_candidates (self->fsstream, + fscandidates, &error); + break; + case TP_STREAM_TRANSPORT_TYPE_ICE: + case TP_STREAM_TRANSPORT_TYPE_GTALK_P2P: + case TP_STREAM_TRANSPORT_TYPE_WLM_2009: + ret = fs_stream_add_remote_candidates (self->fsstream, fscandidates, + &error); + break; + default: + ret = FALSE; + } + + if (!ret) + { + tf_call_stream_fail (self, + TP_CALL_STATE_CHANGE_REASON_INTERNAL_ERROR, + TP_ERROR_STR_MEDIA_STREAMING_ERROR, + "Error setting the remote candidates: %s", error->message); + g_clear_error (&error); + } + fs_candidate_list_destroy (fscandidates); +} + +static void +tf_call_stream_try_adding_fsstream (TfCallStream *self) +{ + gchar *transmitter; + GError *error = NULL; + guint n_params = 0; + GParameter params[6] = { {NULL,} }; + GList *preferred_local_candidates = NULL; + guint i; + FsStreamDirection dir = FS_DIRECTION_NONE; + + memset (params, 0, sizeof(params)); + + if (!self->server_info_retrieved || + !self->has_contact || + !self->has_media_properties) + return; + + switch (self->transport_type) + { + case TP_STREAM_TRANSPORT_TYPE_RAW_UDP: + transmitter = "rawudp"; + + g_debug ("Transmitter: rawudp"); + + switch (tf_call_content_get_fs_media_type (self->call_content)) + { + case TP_MEDIA_STREAM_TYPE_VIDEO: + preferred_local_candidates = g_list_prepend (NULL, + fs_candidate_new (NULL, FS_COMPONENT_RTP, FS_CANDIDATE_TYPE_HOST, + FS_NETWORK_PROTOCOL_UDP, NULL, 9078)); + break; + case TP_MEDIA_STREAM_TYPE_AUDIO: + preferred_local_candidates = g_list_prepend (NULL, + fs_candidate_new (NULL, FS_COMPONENT_RTP, FS_CANDIDATE_TYPE_HOST, + FS_NETWORK_PROTOCOL_UDP, NULL, 7078)); + default: + break; + } + + if (preferred_local_candidates) + { + params[n_params].name = "preferred-local-candidates"; + g_value_init (¶ms[n_params].value, FS_TYPE_CANDIDATE_LIST); + g_value_take_boxed (¶ms[n_params].value, + preferred_local_candidates); + n_params++; + } + break; + case TP_STREAM_TRANSPORT_TYPE_ICE: + case TP_STREAM_TRANSPORT_TYPE_GTALK_P2P: + case TP_STREAM_TRANSPORT_TYPE_WLM_2009: + transmitter = "nice"; + + params[n_params].name = "controlling-mode"; + g_value_init (¶ms[n_params].value, G_TYPE_BOOLEAN); + g_value_set_boolean (¶ms[n_params].value, self->controlling); + n_params++; + + params[n_params].name = "compatibility-mode"; + g_value_init (¶ms[n_params].value, G_TYPE_UINT); + switch (self->transport_type) + { + case TP_STREAM_TRANSPORT_TYPE_ICE: + g_value_set_uint (¶ms[n_params].value, 0); + break; + case TP_STREAM_TRANSPORT_TYPE_GTALK_P2P: + g_value_set_uint (¶ms[n_params].value, 1); + self->multiple_usernames = TRUE; + break; + case TP_STREAM_TRANSPORT_TYPE_WLM_2009: + g_value_set_uint (¶ms[n_params].value, 3); + break; + default: + break; + } + + g_debug ("Transmitter: nice: TpTransportType:%d controlling:%d", + self->transport_type, self->controlling); + + n_params++; + break; + case TP_STREAM_TRANSPORT_TYPE_SHM: + transmitter = "shm"; + params[n_params].name = "create-local-candidates"; + g_value_init (¶ms[n_params].value, G_TYPE_BOOLEAN); + g_value_set_boolean (¶ms[n_params].value, TRUE); + n_params++; + g_debug ("Transmitter: shm"); + break; + default: + tf_call_stream_fail (self, + TP_CALL_STATE_CHANGE_REASON_INTERNAL_ERROR, TP_ERROR_STR_CONFUSED, + "Unknown transport type %d", self->transport_type); + return; + } + + if (self->stun_servers->len) + { + GValueArray *gva = g_ptr_array_index (self->stun_servers, 0); + gchar *ip; + guint port; + gchar *conn_timeout_str; + + /* We only use the first STUN server if there are many */ + + tp_value_array_unpack (gva, 2, &ip, &port); + + params[n_params].name = "stun-ip"; + g_value_init (¶ms[n_params].value, G_TYPE_STRING); + g_value_set_string (¶ms[n_params].value, ip); + n_params++; + + params[n_params].name = "stun-port"; + g_value_init (¶ms[n_params].value, G_TYPE_UINT); + g_value_set_uint (¶ms[n_params].value, port); + n_params++; + + conn_timeout_str = getenv ("FS_CONN_TIMEOUT"); + if (conn_timeout_str) + { + gint conn_timeout = strtol (conn_timeout_str, NULL, 10); + + params[n_params].name = "stun-timeout"; + g_value_init (¶ms[n_params].value, G_TYPE_UINT); + g_value_set_uint (¶ms[n_params].value, conn_timeout); + n_params++; + } + } + + if (self->relay_info->len) + { + GPtrArray *fs_relay_info = NULL; + + for (i = 0; i < self->relay_info->len; i++) + { + GHashTable *one_relay = g_ptr_array_index(self->relay_info, i); + const gchar *type = NULL; + const gchar *ip; + guint32 port; + const gchar *username; + const gchar *password; + guint component; + GstStructure *s; + + ip = tp_asv_get_string (one_relay, "ip"); + port = tp_asv_get_uint32 (one_relay, "port", NULL); + type = tp_asv_get_string (one_relay, "type"); + username = tp_asv_get_string (one_relay, "username"); + password = tp_asv_get_string (one_relay, "password"); + component = tp_asv_get_uint32 (one_relay, "component", NULL); + + if (!ip || !port || !username || !password) + continue; + + if (!type) + type = "udp"; + + s = gst_structure_new ("relay-info", + "ip", G_TYPE_STRING, ip, + "port", G_TYPE_UINT, port, + "username", G_TYPE_STRING, username, + "password", G_TYPE_STRING, password, + "type", G_TYPE_STRING, type, + NULL); + + if (component) + gst_structure_set (s, "component", G_TYPE_UINT, component, NULL); + + + if (!fs_relay_info) + fs_relay_info = g_ptr_array_new_with_free_func ( + (GDestroyNotify)gst_structure_free); + + g_ptr_array_add (fs_relay_info, s); + } + + if (fs_relay_info) + { + params[n_params].name = "relay-info"; + g_value_init (¶ms[n_params].value, G_TYPE_PTR_ARRAY); + g_value_take_boxed (¶ms[n_params].value, fs_relay_info); + n_params++; + } + } + + if (self->receiving_state == TP_STREAM_FLOW_STATE_PENDING_START) + { + if (tf_call_stream_start_receiving (self, FS_DIRECTION_NONE)) + dir = FS_DIRECTION_RECV; + } + + self->fsstream = _tf_call_content_get_fsstream_by_handle (self->call_content, + self->contact_handle, + dir, + transmitter, + n_params, + params, + &error); + + for (i = 0; i < n_params; i++) + g_value_unset (¶ms[i].value); + + if (!self->fsstream) + { + tf_call_stream_fail (self, + TP_CALL_STATE_CHANGE_REASON_INTERNAL_ERROR, + TP_ERROR_STR_MEDIA_STREAMING_ERROR, + "Could not create FsStream: %s", error->message); + g_clear_error (&error); + return; + } + + _tf_call_stream_push_remote_candidates (self, self->stored_remote_candidates); + self->stored_remote_candidates = NULL; + + if (self->sending_state == TP_STREAM_FLOW_STATE_PENDING_START) + sending_state_changed (self->proxy, + self->sending_state, NULL, (GObject *) self); +} + +static void +server_info_retrieved (TpCallStream *proxy, + gpointer user_data, GObject *weak_object) +{ + TfCallStream *self = TF_CALL_STREAM (weak_object); + + if (self->proxy == NULL) + return; + + self->server_info_retrieved = TRUE; + + tf_call_stream_try_adding_fsstream (self); +} + +static void +relay_info_changed (TpCallStream *proxy, + const GPtrArray *arg_Relay_Info, + gpointer user_data, GObject *weak_object) +{ + TfCallStream *self = TF_CALL_STREAM (weak_object); + + if (self->proxy == NULL) + return; + + if (self->server_info_retrieved) + { + tf_call_stream_fail_literal (self, + TP_CALL_STATE_CHANGE_REASON_INTERNAL_ERROR, + TP_ERROR_STR_NOT_IMPLEMENTED, + "Changing relay servers after ServerInfoRetrived is not implemented"); + return; + } + + /* Ignore signals that come before the basic info has been retrived */ + if (!self->relay_info) + return; + + g_boxed_free (TP_ARRAY_TYPE_STRING_VARIANT_MAP_LIST, self->relay_info); + self->relay_info = g_boxed_copy (TP_ARRAY_TYPE_STRING_VARIANT_MAP_LIST, + arg_Relay_Info); +} + +static void +stun_servers_changed (TpCallStream *proxy, + const GPtrArray *arg_Servers, + gpointer user_data, GObject *weak_object) +{ + TfCallStream *self = TF_CALL_STREAM (weak_object); + + if (self->proxy == NULL) + return; + + if (self->server_info_retrieved) + { + tf_call_stream_fail_literal (self, + TP_CALL_STATE_CHANGE_REASON_INTERNAL_ERROR, + TP_ERROR_STR_NOT_IMPLEMENTED, + "Changing STUN servers after ServerInfoRetrived is not implemented"); + return; + } + + /* Ignore signals that come before the basic info has been retrived */ + if (!self->stun_servers) + return; + + g_boxed_free (TP_ARRAY_TYPE_SOCKET_ADDRESS_IP_LIST, self->stun_servers); + self->stun_servers = g_boxed_copy (TP_ARRAY_TYPE_SOCKET_ADDRESS_IP_LIST, + arg_Servers); +} + +static FsCandidateType +tpcandidate_type_to_fs (TpCallStreamCandidateType type) +{ + switch(type) + { + case TP_CALL_STREAM_CANDIDATE_TYPE_NONE: + g_warning ("Candidate type NONE, assigning to HOST"); + /* fallthrough */ + case TP_CALL_STREAM_CANDIDATE_TYPE_HOST: + return FS_CANDIDATE_TYPE_HOST; + case TP_CALL_STREAM_CANDIDATE_TYPE_SERVER_REFLEXIVE: + return FS_CANDIDATE_TYPE_SRFLX; + case TP_CALL_STREAM_CANDIDATE_TYPE_PEER_REFLEXIVE: + return FS_CANDIDATE_TYPE_PRFLX; + case TP_CALL_STREAM_CANDIDATE_TYPE_RELAY: + return FS_CANDIDATE_TYPE_RELAY; + case TP_CALL_STREAM_CANDIDATE_TYPE_MULTICAST: + return FS_CANDIDATE_TYPE_MULTICAST; + default: + g_warning ("Candidate type %d unknown, assigning to HOST", type); + return FS_CANDIDATE_TYPE_HOST; + } +} + +static FsNetworkProtocol +tpnetworkproto_to_fs (TpMediaStreamBaseProto proto) +{ + switch(proto) + { + case TP_MEDIA_STREAM_BASE_PROTO_UDP: + return FS_NETWORK_PROTOCOL_UDP; + case TP_MEDIA_STREAM_BASE_PROTO_TCP: + return FS_NETWORK_PROTOCOL_TCP; + default: + g_debug ("Network protocol %d unknown, assigning to UDP", proto); + return FS_NETWORK_PROTOCOL_UDP; + } +} + +static void +tf_call_stream_add_remote_candidates (TfCallStream *self, + const GPtrArray *candidates) +{ + GList *fscandidates = NULL; + guint i; + + /* No candidates to add, ignore. This could either be caused by the CM + * accidentally emitting an empty RemoteCandidatesAdded or when there are no + * remote candidates on the endpoint yet when we query it */ + if (candidates->len == 0) + return; + + for (i = 0; i < candidates->len; i++) + { + GValueArray *tpcandidate = g_ptr_array_index (candidates, i); + guint component; + gchar *ip; + guint port; + GHashTable *extra_info; + const gchar *foundation; + guint priority; + const gchar *username; + const gchar *password; + gboolean valid; + FsCandidate *cand; + guint type; + guint protocol; + guint ttl; + const gchar *base_ip; + guint base_port; + + tp_value_array_unpack (tpcandidate, 4, &component, &ip, &port, + &extra_info); + + foundation = tp_asv_get_string (extra_info, "foundation"); + if (!foundation) + foundation = ""; + priority = tp_asv_get_uint32 (extra_info, "priority", &valid); + if (!valid) + priority = 0; + + username = tp_asv_get_string (extra_info, "username"); + if (!username) + username = self->creds_username; + + password = tp_asv_get_string (extra_info, "password"); + if (!password) + password = self->creds_password; + + type = tp_asv_get_uint32 (extra_info, "type", &valid); + if (!valid) + type = TP_CALL_STREAM_CANDIDATE_TYPE_HOST; + + protocol = tp_asv_get_uint32 (extra_info, "protocol", &valid); + if (!valid) + protocol = TP_MEDIA_STREAM_BASE_PROTO_UDP; + + base_ip = tp_asv_get_string (extra_info, "base-ip"); + base_port = tp_asv_get_uint32 (extra_info, "base-port", &valid); + if (!valid) + base_port = 0; + + + ttl = tp_asv_get_uint32 (extra_info, "ttl", &valid); + if (!valid) + ttl = 0; + + g_debug ("Remote Candidate: %s c:%d tptype:%d tpproto: %d ip:%s port:%u prio:%d u/p:%s/%s ttl:%d base_ip:%s base_port:%d", + foundation, component, type, protocol, ip, port, priority, + username, password, ttl, base_ip, base_port); + + cand = fs_candidate_new (foundation, component, + tpcandidate_type_to_fs (type), tpnetworkproto_to_fs (protocol), + ip, port); + cand->priority = priority; + cand->username = g_strdup (username); + cand->password = g_strdup (password); + cand->ttl = ttl; + cand->base_ip = g_strdup (base_ip); + cand->base_port = base_port; + + fscandidates = g_list_append (fscandidates, cand); + } + + if (self->fsstream) + { + _tf_call_stream_push_remote_candidates (self, fscandidates); + } + else + { + self->stored_remote_candidates = + g_list_concat (self->stored_remote_candidates, fscandidates); + } +} + +static void +remote_candidates_added (TpProxy *proxy, + const GPtrArray *arg_Candidates, + gpointer user_data, GObject *weak_object) +{ + TfCallStream *self = TF_CALL_STREAM (weak_object); + + if (self->proxy == NULL) + return; + + if (!self->has_endpoint_properties) + return; + + if (self->endpoint != proxy) + return; + + tf_call_stream_add_remote_candidates (self, arg_Candidates); +} + +static void +remote_credentials_set (TpProxy *proxy, + const gchar *arg_Username, + const gchar *arg_Password, + gpointer user_data, GObject *weak_object) +{ + TfCallStream *self = TF_CALL_STREAM (weak_object); + + if (self->proxy == NULL) + return; + + if (self->endpoint != proxy) + return; + + if ((self->creds_username && strcmp (self->creds_username, arg_Username)) || + (self->creds_password && strcmp (self->creds_password, arg_Password))) + { + g_debug ("Remote credentials changed," + " remote is doing an ICE restart"); + /* Remote credentials changed, this will perform a ICE restart, so + * clear old remote candidates */ + fs_candidate_list_destroy (self->stored_remote_candidates); + self->stored_remote_candidates = NULL; + } + + g_free (self->creds_username); + g_free (self->creds_password); + self->creds_username = g_strdup (arg_Username); + self->creds_password = g_strdup (arg_Password); + + g_debug ("Credentials set: %s / %s", arg_Username, arg_Password); +} + + +static void +got_endpoint_properties (TpProxy *proxy, GHashTable *out_Properties, + const GError *error, gpointer user_data, GObject *weak_object) +{ + TfCallStream *self = TF_CALL_STREAM (weak_object); + GValueArray *credentials; + gchar *username, *password; + GPtrArray *candidates; + gboolean valid = FALSE; + guint transport_type; + + if (self->endpoint != proxy) + return; + + if (error) + { + tf_call_stream_fail (self, + TP_CALL_STATE_CHANGE_REASON_INTERNAL_ERROR, + TP_ERROR_STR_CONFUSED, + "Error getting the Streams's media properties: %s", error->message); + return; + } + + if (!out_Properties) + { + tf_call_stream_fail_literal (self, + TP_CALL_STATE_CHANGE_REASON_INTERNAL_ERROR, + TP_ERROR_STR_CONFUSED, + "Error getting the Stream's media properties: there are none"); + return; + } + + g_debug ("Got Endpoint Properties"); + + + credentials = tp_asv_get_boxed (out_Properties, "RemoteCredentials", + TP_STRUCT_TYPE_STREAM_CREDENTIALS); + if (!credentials) + goto invalid_property; + tp_value_array_unpack (credentials, 2, &username, &password); + if (username && username[0]) + self->creds_username = g_strdup (username); + if (password && password[0]) + self->creds_password = g_strdup (password); + + if (self->creds_username || self->creds_password) + g_debug ("Credentials set: %s / %s", username, password); + + candidates = tp_asv_get_boxed (out_Properties, "RemoteCandidates", + TP_ARRAY_TYPE_CANDIDATE_LIST); + if (!candidates) + goto invalid_property; + + transport_type = tp_asv_get_uint32 (out_Properties, "Transport", &valid); + if (!valid) + { + g_warning ("No valid transport"); + goto invalid_property; + } + + if (transport_type != self->transport_type) + { + if (transport_type != TP_STREAM_TRANSPORT_TYPE_RAW_UDP) + { + tf_call_stream_fail (self, + TP_CALL_STATE_CHANGE_REASON_INTERNAL_ERROR, + TP_ERROR_STR_INVALID_ARGUMENT, + "The Transport of a Endpoint can only be changed to rawudp: %d invalid", transport_type); + return; + } + self->transport_type = transport_type; + } + + self->has_endpoint_properties = TRUE; + + tf_call_stream_add_remote_candidates (self, candidates); + + tf_call_stream_update_sending_state (self); + + return; + + invalid_property: + tf_call_stream_fail_literal (self, + TP_CALL_STATE_CHANGE_REASON_INTERNAL_ERROR, + TP_ERROR_STR_CONFUSED, + "Error getting the Endpoint's properties: invalid type"); +} + +static void +tf_call_stream_add_endpoint (TfCallStream *self, const gchar *obj_path) +{ + GError *error = NULL; + + self->endpoint_objpath = g_strdup (obj_path); + + tp_call_stream_endpoint_init_known_interfaces (); + self->endpoint = g_object_new (TP_TYPE_PROXY, + "dbus-daemon", tp_proxy_get_dbus_daemon (self->proxy), + "bus-name", tp_proxy_get_bus_name (self->proxy), + "object-path", self->endpoint_objpath, + NULL); + tp_proxy_add_interface_by_id (TP_PROXY (self->endpoint), + TP_IFACE_QUARK_CALL1_STREAM_ENDPOINT); + + tp_cli_call1_stream_endpoint_connect_to_remote_credentials_set ( + TP_PROXY (self->endpoint), remote_credentials_set, NULL, NULL, + G_OBJECT (self), &error); + if (error) + { + tf_call_stream_fail (self, + TP_CALL_STATE_CHANGE_REASON_INTERNAL_ERROR, + TP_ERROR_STR_CONFUSED, + "Error connecting to RemoteCredentialsSet signal: %s", + error->message); + g_clear_error (&error); + return; + } + + tp_cli_call1_stream_endpoint_connect_to_remote_candidates_added ( + TP_PROXY (self->endpoint), remote_candidates_added, NULL, NULL, + G_OBJECT (self), &error); + if (error) + { + tf_call_stream_fail (self, + TP_CALL_STATE_CHANGE_REASON_INTERNAL_ERROR, + TP_ERROR_STR_CONFUSED, + "Error connecting to RemoteCandidatesAdded signal: %s", + error->message); + g_clear_error (&error); + return; + } + + tp_cli_dbus_properties_call_get_all (self->endpoint, -1, + TP_IFACE_CALL1_STREAM_ENDPOINT, + got_endpoint_properties, NULL, NULL, G_OBJECT (self)); +} + +static void +_tf_call_stream_remove_endpoint (TfCallStream *self) +{ + g_clear_object (&self->endpoint); + + self->has_endpoint_properties = FALSE; + self->multiple_usernames = FALSE; + self->controlling = FALSE; + + fs_candidate_list_destroy (self->stored_remote_candidates); + self->stored_remote_candidates = NULL; + + g_free (self->creds_username); + self->creds_username = NULL; + + g_free (self->creds_password); + self->creds_password = NULL; + + g_free (self->endpoint_objpath); + self->endpoint_objpath = NULL; + + tf_call_stream_update_sending_state (self); +} + +static void +endpoints_changed (TpCallStream *proxy, + const GPtrArray *arg_Endpoints_Added, + const GPtrArray *arg_Endpoints_Removed, + gpointer user_data, GObject *weak_object) +{ + TfCallStream *self = TF_CALL_STREAM (weak_object); + + if (self->proxy == NULL) + return; + + /* Ignore signals before getting the properties to avoid races */ + if (!self->has_media_properties) + return; + + if (arg_Endpoints_Removed->len == 1) + { + if (self->endpoint_objpath == NULL || + strcmp (self->endpoint_objpath, + g_ptr_array_index (arg_Endpoints_Removed, 0))) + + { + tf_call_stream_fail_literal (self, + TP_CALL_STATE_CHANGE_REASON_INTERNAL_ERROR, + TP_ERROR_STR_CONFUSED, + "Can not remove endpoint that has not been previously added"); + return; + } + _tf_call_stream_remove_endpoint (self); + } + else if (arg_Endpoints_Removed->len > 1) + { + tf_call_stream_fail_literal (self, + TP_CALL_STATE_CHANGE_REASON_INTERNAL_ERROR, + TP_ERROR_STR_NOT_IMPLEMENTED, + "Having more than one endpoint is not implemented"); + return; + } + + /* Nothing added, it's over */ + if (arg_Endpoints_Added->len == 0) + return; + + if (arg_Endpoints_Added->len > 1) + { + tf_call_stream_fail_literal (self, + TP_CALL_STATE_CHANGE_REASON_INTERNAL_ERROR, + TP_ERROR_STR_NOT_IMPLEMENTED, + "Having more than one endpoint is not implemented"); + return; + } + + if (self->endpoint_objpath) + { + if (strcmp (g_ptr_array_index (arg_Endpoints_Added, 0), + self->endpoint_objpath)) + tf_call_stream_fail_literal (self, + TP_CALL_STATE_CHANGE_REASON_INTERNAL_ERROR, + TP_ERROR_STR_NOT_IMPLEMENTED, + "Having more than one endpoint is not implemented"); + return; + } + + tf_call_stream_add_endpoint (self, + g_ptr_array_index (arg_Endpoints_Added, 0)); +} + + +static void +got_stream_media_properties (TpProxy *proxy, GHashTable *out_Properties, + const GError *error, gpointer user_data, GObject *weak_object) +{ + TfCallStream *self = TF_CALL_STREAM (weak_object); + GPtrArray *stun_servers; + GPtrArray *relay_info; + GPtrArray *endpoints; + gboolean valid; + + if (self->proxy == NULL) + return; + + if (error) + { + tf_call_stream_fail (self, + TP_CALL_STATE_CHANGE_REASON_INTERNAL_ERROR, + TP_ERROR_STR_CONFUSED, + "Error getting the Streams's media properties: %s", + error->message); + return; + } + + if (!out_Properties) + { + tf_call_stream_fail_literal (self, + TP_CALL_STATE_CHANGE_REASON_INTERNAL_ERROR, + TP_ERROR_STR_INVALID_ARGUMENT, + "Error getting the Stream's media properties: there are none"); + return; + } + + if (self->proxy == NULL) + return; + + self->transport_type = + tp_asv_get_uint32 (out_Properties, "Transport", &valid); + if (!valid) + { + g_warning ("No valid transport"); + goto invalid_property; + } + + stun_servers = tp_asv_get_boxed (out_Properties, "STUNServers", + TP_ARRAY_TYPE_SOCKET_ADDRESS_IP_LIST); + if (!stun_servers) + { + g_warning ("No valid STUN servers"); + goto invalid_property; + } + + relay_info = tp_asv_get_boxed (out_Properties, "RelayInfo", + TP_ARRAY_TYPE_STRING_VARIANT_MAP_LIST); + if (!relay_info) + { + g_warning ("No valid RelayInfo"); + goto invalid_property; + } + + self->server_info_retrieved = tp_asv_get_boolean (out_Properties, + "HasServerInfo", &valid); + if (!valid) + { + g_warning ("No valid server info"); + goto invalid_property; + } + + self->sending_state = tp_asv_get_uint32 (out_Properties, "SendingState", + &valid); + if (!valid) + { + g_warning ("No valid sending state"); + goto invalid_property; + } + + self->receiving_state = tp_asv_get_uint32 (out_Properties, + "ReceivingState", &valid); + if (!valid) + { + g_warning ("No valid receiving state"); + goto invalid_property; + } + +/* FIXME: controlling is on the endpoint + self->controlling = tp_asv_get_boolean (out_Properties, + "Controlling", &valid); + if (!valid) + { + g_warning ("No Controlling property"); + goto invalid_property; + } +*/ + self->stun_servers = g_boxed_copy (TP_ARRAY_TYPE_SOCKET_ADDRESS_IP_LIST, + stun_servers); + self->relay_info = g_boxed_copy (TP_ARRAY_TYPE_STRING_VARIANT_MAP_LIST, + relay_info); + + endpoints = tp_asv_get_boxed (out_Properties, "Endpoints", + TP_ARRAY_TYPE_OBJECT_PATH_LIST); + + if (endpoints->len > 1) + { + tf_call_stream_fail_literal (self, + TP_CALL_STATE_CHANGE_REASON_INTERNAL_ERROR, + TP_ERROR_STR_NOT_IMPLEMENTED, + "Having more than one endpoint is not implemented"); + return; + } + + if (endpoints->len == 1) + { + tf_call_stream_add_endpoint (self, g_ptr_array_index (endpoints, 0)); + } + + self->has_media_properties = TRUE; + + tf_call_stream_try_adding_fsstream (self); + + return; + invalid_property: + tf_call_stream_fail_literal (self, + TP_CALL_STATE_CHANGE_REASON_INTERNAL_ERROR, + TP_ERROR_STR_INVALID_ARGUMENT, + "Error getting the Stream's properties: invalid type"); + return; +} + +static void +ice_restart_requested (TpCallStream *proxy, + gpointer user_data, GObject *weak_object) +{ + TfCallStream *self = TF_CALL_STREAM (weak_object); + GError *myerror = NULL; + + if (!self->fsstream) + return; + + if (self->multiple_usernames) + { + tf_call_stream_fail_literal (self, + TP_CALL_STATE_CHANGE_REASON_INTERNAL_ERROR, + TP_ERROR_STR_INVALID_ARGUMENT, + "CM tried to ICE restart an ICE-6 or Google compatible connection"); + return; + } + + g_debug ("Restarting ICE"); + + if (fs_stream_add_remote_candidates (self->fsstream, NULL, &myerror)) + { + g_free (self->last_local_username); + g_free (self->last_local_password); + self->last_local_username = NULL; + self->last_local_password = NULL; + } + else + { + tf_call_stream_fail (self, + TP_CALL_STATE_CHANGE_REASON_INTERNAL_ERROR, + TP_ERROR_STR_MEDIA_STREAMING_ERROR, + "Error restarting the ICE process: %s", myerror->message); + g_clear_error (&myerror); + } +} + +static void +stream_prepared (GObject *src_object, GAsyncResult *res, gpointer user_data) +{ + TfCallStream *self = TF_CALL_STREAM (user_data); + TpProxy *proxy = TP_PROXY (src_object); + GError *error = NULL; + GHashTable *members; + GHashTableIter iter; + gpointer key, value; + + if (self->proxy == NULL) + return; + + if (!tp_proxy_prepare_finish (src_object, res, &error)) + { + tf_call_stream_fail (self, + TP_CALL_STATE_CHANGE_REASON_INTERNAL_ERROR, + TP_ERROR_STR_CONFUSED, + "Error preparing the stream Streams: %s", error->message); + g_clear_error (&error); + return; + } + + if (!tp_proxy_has_interface_by_id (proxy, + TP_IFACE_QUARK_CALL1_STREAM_INTERFACE_MEDIA)) + { + tf_call_stream_fail_literal (self, + TP_CALL_STATE_CHANGE_REASON_INTERNAL_ERROR, + TP_ERROR_STR_INVALID_ARGUMENT, + "Stream does not have the media interface," + " but HardwareStreaming was NOT true"); + return; + } + + members = tp_call_stream_get_remote_members (self->proxy); + + if (g_hash_table_size (members) != 1) + { + tf_call_stream_fail (self, + TP_CALL_STATE_CHANGE_REASON_INTERNAL_ERROR, + TP_ERROR_STR_NOT_IMPLEMENTED, + "Only one Member per Stream is supported, there are %d", + g_hash_table_size (members)); + return; + } + + g_hash_table_iter_init (&iter, members); + if (g_hash_table_iter_next (&iter, &key, &value)) + { + self->has_contact = TRUE; + self->contact_handle = tp_contact_get_handle (key); + } + + tp_cli_call1_stream_interface_media_connect_to_sending_state_changed ( + TP_CALL_STREAM (proxy), sending_state_changed, NULL, NULL, + G_OBJECT (self), &error); + if (error) + { + tf_call_stream_fail (self, + TP_CALL_STATE_CHANGE_REASON_INTERNAL_ERROR, "", + "Error connecting to SendingStateChanged signal: %s", + error->message); + g_clear_error (&error); + return; + } + + + tp_cli_call1_stream_interface_media_connect_to_receiving_state_changed ( + TP_CALL_STREAM (proxy), receiving_state_changed, NULL, NULL, + G_OBJECT (self), &error); + if (error) + { + tf_call_stream_fail (self, + TP_CALL_STATE_CHANGE_REASON_INTERNAL_ERROR, "", + "Error connecting to ReceivingStateChanged signal: %s", + error->message); + g_clear_error (&error); + return; + } + + tp_cli_call1_stream_interface_media_connect_to_server_info_retrieved ( + TP_CALL_STREAM (proxy), server_info_retrieved, NULL, NULL, + G_OBJECT (self), &error); + if (error) + { + tf_call_stream_fail (self, + TP_CALL_STATE_CHANGE_REASON_INTERNAL_ERROR, "", + "Error connecting to ServerInfoRetrived signal: %s", + error->message); + g_clear_error (&error); + return; + } + + tp_cli_call1_stream_interface_media_connect_to_stun_servers_changed ( + TP_CALL_STREAM (proxy), stun_servers_changed, NULL, NULL, + G_OBJECT (self), &error); + if (error) + { + tf_call_stream_fail (self, + TP_CALL_STATE_CHANGE_REASON_INTERNAL_ERROR, "", + "Error connecting to ServerInfoRetrived signal: %s", + error->message); + g_clear_error (&error); + return; + } + + + tp_cli_call1_stream_interface_media_connect_to_relay_info_changed ( + TP_CALL_STREAM (proxy), relay_info_changed, NULL, NULL, + G_OBJECT (self), &error); + if (error) + { + tf_call_stream_fail (self, + TP_CALL_STATE_CHANGE_REASON_INTERNAL_ERROR, "", + "Error connecting to ServerInfoRetrived signal: %s", + error->message); + g_clear_error (&error); + return; + } + + + tp_cli_call1_stream_interface_media_connect_to_endpoints_changed ( + TP_CALL_STREAM (proxy), endpoints_changed, NULL, NULL, + G_OBJECT (self), &error); + if (error) + { + tf_call_stream_fail (self, + TP_CALL_STATE_CHANGE_REASON_INTERNAL_ERROR, "", + "Error connecting to EndpointsChanged signal: %s", + error->message); + g_clear_error (&error); + return; + } + + + tp_cli_call1_stream_interface_media_connect_to_ice_restart_requested ( + TP_CALL_STREAM (proxy), ice_restart_requested, NULL, NULL, + G_OBJECT (self), &error); + if (error) + { + tf_call_stream_fail (self, + TP_CALL_STATE_CHANGE_REASON_INTERNAL_ERROR, "", + "Error connecting to ICERestartRequested signal: %s", + error->message); + g_clear_error (&error); + return; + } + + tp_cli_dbus_properties_call_get_all (TP_PROXY (self->proxy), -1, + TP_IFACE_CALL1_STREAM_INTERFACE_MEDIA, + got_stream_media_properties, NULL, NULL, G_OBJECT (self)); + + return; +} + +TfCallStream * +tf_call_stream_new (TfCallContent *call_content, + TpCallStream *stream_proxy) +{ + TfCallStream *self; + + g_assert (call_content != NULL); + g_assert (stream_proxy != NULL); + + self = g_object_new (TF_TYPE_CALL_STREAM, NULL); + + self->call_content = call_content; + self->proxy = g_object_ref (stream_proxy); + + tp_proxy_prepare_async (self->proxy, NULL, stream_prepared, + g_object_ref (self)); + + return self; +} + +static TpCallStreamCandidateType +fscandidatetype_to_tp (FsCandidateType type) +{ + switch(type) + { + case FS_CANDIDATE_TYPE_HOST: + return TP_CALL_STREAM_CANDIDATE_TYPE_HOST; + case FS_CANDIDATE_TYPE_SRFLX: + return TP_CALL_STREAM_CANDIDATE_TYPE_SERVER_REFLEXIVE; + case FS_CANDIDATE_TYPE_PRFLX: + return TP_CALL_STREAM_CANDIDATE_TYPE_PEER_REFLEXIVE; + case FS_CANDIDATE_TYPE_RELAY: + return TP_CALL_STREAM_CANDIDATE_TYPE_RELAY; + case FS_CANDIDATE_TYPE_MULTICAST: + return TP_CALL_STREAM_CANDIDATE_TYPE_MULTICAST; + default: + g_warning ("Unkown candidate type, assigning type NONE"); + return TP_CALL_STREAM_CANDIDATE_TYPE_NONE; + } +} + + +static TpMediaStreamBaseProto +fs_network_proto_to_tp (FsNetworkProtocol proto) +{ + switch (proto) + { + case FS_NETWORK_PROTOCOL_UDP: + return TP_MEDIA_STREAM_BASE_PROTO_UDP; + case FS_NETWORK_PROTOCOL_TCP: + return TP_MEDIA_STREAM_BASE_PROTO_TCP; + default: + g_warning ("Invalid protocl, assigning to UDP"); + return TP_MEDIA_STREAM_BASE_PROTO_UDP; + } +} + + +static GValueArray * +fscandidate_to_tpcandidate (TfCallStream *stream, FsCandidate *candidate) +{ + GHashTable *extra_info; + + extra_info = tp_asv_new (NULL, NULL); + + tp_asv_set_uint32 (extra_info, "type", + fscandidatetype_to_tp (candidate->type)); + + if (candidate->foundation) + tp_asv_set_string (extra_info, "foundation", candidate->foundation); + + tp_asv_set_uint32 (extra_info, "protocol", + fs_network_proto_to_tp (candidate->proto)); + + if (candidate->base_ip) + { + tp_asv_set_string (extra_info, "base-ip", candidate->base_ip); + tp_asv_set_uint32 (extra_info, "base-port", candidate->base_port); + } + + if (candidate->priority) + tp_asv_set_uint32 (extra_info, "priority", candidate->priority); + + + if (candidate->type == FS_CANDIDATE_TYPE_MULTICAST) + tp_asv_set_uint32 (extra_info, "ttl", candidate->ttl); + + if (stream->multiple_usernames) + { + if (candidate->username) + tp_asv_set_string (extra_info, "username", candidate->username); + if (candidate->password) + tp_asv_set_string (extra_info, "password", candidate->password); + } + + + return tp_value_array_build (4, + G_TYPE_UINT, candidate->component_id, + G_TYPE_STRING, candidate->ip, + G_TYPE_UINT, candidate->port, + TP_HASH_TYPE_CANDIDATE_INFO, extra_info, + G_TYPE_INVALID); +} + +static void +cb_fs_new_local_candidate (TfCallStream *stream, FsCandidate *candidate) +{ + GPtrArray *candidate_list; + + if (!stream->multiple_usernames) + { + if ((!stream->last_local_username && candidate->username) || + (!stream->last_local_password && candidate->password) || + (stream->last_local_username && + strcmp (candidate->username, stream->last_local_username)) || + (stream->last_local_password && + strcmp (candidate->password, stream->last_local_password))) + { + g_free (stream->last_local_username); + g_free (stream->last_local_password); + stream->last_local_username = g_strdup (candidate->username); + stream->last_local_password = g_strdup (candidate->password); + + if (!stream->last_local_username) + stream->last_local_username = g_strdup (""); + if (!stream->last_local_password) + stream->last_local_password = g_strdup (""); + + /* Add a callback to kill Call on errors */ + tp_cli_call1_stream_interface_media_call_set_credentials ( + stream->proxy, -1, stream->last_local_username, + stream->last_local_password, NULL, NULL, NULL, NULL); + + } + } + + g_debug ("Local Candidate: %s c:%d fstype:%d fsproto: %d ip:%s port:%u prio:%d u/p:%s/%s ttl:%d base_ip:%s base_port:%d", + candidate->foundation,candidate->component_id, candidate->type, + candidate->proto, candidate->ip, candidate->port, + candidate->priority, candidate->username, candidate->password, + candidate->ttl,candidate-> base_ip, candidate->base_port); + + + candidate_list = g_ptr_array_sized_new (1); + g_ptr_array_add (candidate_list, + fscandidate_to_tpcandidate (stream, candidate)); + + /* Should also check for errors */ + tp_cli_call1_stream_interface_media_call_add_candidates (stream->proxy, + -1, candidate_list, NULL, NULL, NULL, NULL); + + + g_boxed_free (TP_ARRAY_TYPE_CANDIDATE_LIST, candidate_list); +} + +static void +cb_fs_local_candidates_prepared (TfCallStream *stream) +{ + g_debug ("Local candidates prepared"); + + tp_cli_call1_stream_interface_media_call_finish_initial_candidates ( + stream->proxy, -1, NULL, NULL, NULL, NULL); +} + +static void +cb_fs_component_state_changed (TfCallStream *stream, guint component, + FsStreamState fsstate) +{ + TpStreamEndpointState state; + + if (!stream->endpoint) + return; + + switch (fsstate) + { + default: + g_warning ("Unknown Farstream state, returning ExhaustedCandidates"); + /* fall through */ + case FS_STREAM_STATE_FAILED: + state = TP_STREAM_ENDPOINT_STATE_EXHAUSTED_CANDIDATES; + break; + case FS_STREAM_STATE_DISCONNECTED: + case FS_STREAM_STATE_GATHERING: + case FS_STREAM_STATE_CONNECTING: + state = TP_STREAM_ENDPOINT_STATE_CONNECTING; + break; + case FS_STREAM_STATE_CONNECTED: + state = TP_STREAM_ENDPOINT_STATE_PROVISIONALLY_CONNECTED; + case FS_STREAM_STATE_READY: + state = TP_STREAM_ENDPOINT_STATE_FULLY_CONNECTED; + break; + } + + g_debug ("Endpoint state for component %u changed to %d (fs: %d)", + component, state, fsstate); + + tp_cli_call1_stream_endpoint_call_set_endpoint_state (stream->endpoint, + -1, component, state, NULL, NULL, NULL, NULL); +} + +static void +cb_fs_new_active_candidate_pair (TfCallStream *stream, + FsCandidate *local_candidate, + FsCandidate *remote_candidate) +{ + GValueArray *local_tp_candidate; + GValueArray *remote_tp_candidate; + + g_debug ("new active candidate pair local: %s (%d) remote: %s (%d)", + local_candidate->ip, local_candidate->port, + remote_candidate->ip, remote_candidate->port); + + if (!stream->endpoint) + return; + + local_tp_candidate = fscandidate_to_tpcandidate (stream, local_candidate); + remote_tp_candidate = fscandidate_to_tpcandidate (stream, remote_candidate); + + tp_cli_call1_stream_endpoint_call_set_selected_candidate_pair ( + stream->endpoint, -1, local_tp_candidate, remote_tp_candidate, + NULL, NULL, NULL, NULL); + + g_boxed_free (TP_STRUCT_TYPE_CANDIDATE, local_tp_candidate); + g_boxed_free (TP_STRUCT_TYPE_CANDIDATE, remote_tp_candidate); +} + +gboolean +tf_call_stream_bus_message (TfCallStream *stream, GstMessage *message) +{ + FsError errorno; + const gchar *msg; + FsCandidate *candidate; + guint component; + FsStreamState fsstate; + FsCandidate *local_candidate; + FsCandidate *remote_candidate; + + if (!stream->fsstream) + return FALSE; + + if (fs_parse_error (G_OBJECT (stream->fsstream), message, &errorno, &msg)) + { + GEnumClass *enumclass; + GEnumValue *enumvalue; + + enumclass = g_type_class_ref (FS_TYPE_ERROR); + enumvalue = g_enum_get_value (enumclass, errorno); + g_warning ("error (%s (%d)): %s", + enumvalue->value_nick, errorno, msg); + g_type_class_unref (enumclass); + + tf_call_stream_fail_literal (stream, + TP_CALL_STATE_CHANGE_REASON_INTERNAL_ERROR, + TP_ERROR_STR_MEDIA_STREAMING_ERROR, msg); + } + else if (fs_stream_parse_new_local_candidate (stream->fsstream, message, + &candidate)) + { + cb_fs_new_local_candidate (stream, candidate); + } + else if (fs_stream_parse_local_candidates_prepared (stream->fsstream, + message)) + { + cb_fs_local_candidates_prepared (stream); + } + else if (fs_stream_parse_component_state_changed (stream->fsstream, message, + &component, &fsstate)) + { + cb_fs_component_state_changed (stream, component, fsstate); + } + else if (fs_stream_parse_new_active_candidate_pair (stream->fsstream, message, + &local_candidate, &remote_candidate)) + { + cb_fs_new_active_candidate_pair (stream, local_candidate, + remote_candidate); + } + else + { + return FALSE; + } + + return TRUE; +} + +static void +tf_call_stream_fail_literal (TfCallStream *self, + TpCallStateChangeReason reason, + const gchar *detailed_reason, + const gchar *message) +{ + g_warning ("%s", message); + + if (self->proxy == NULL) + return; + + tp_cli_call1_stream_interface_media_call_fail ( + self->proxy, -1, + tp_value_array_build (4, + G_TYPE_UINT, 0, + G_TYPE_UINT, reason, + G_TYPE_STRING, detailed_reason, + G_TYPE_STRING, message, + G_TYPE_INVALID), + NULL, NULL, NULL, NULL); +} + + +static void +tf_call_stream_fail (TfCallStream *self, + TpCallStateChangeReason reason, + const gchar *detailed_reason, + const gchar *message_format, + ...) +{ + gchar *message; + va_list valist; + + va_start (valist, message_format); + message = g_strdup_vprintf (message_format, valist); + va_end (valist); + + tf_call_stream_fail_literal (self, reason, detailed_reason, message); + g_free (message); +} + +void +tf_call_stream_sending_failed (TfCallStream *self, const gchar *message) +{ + g_warning ("Reporting sending failure: %s", message); + + if (self->proxy == NULL) + return; + + tp_cli_call1_stream_interface_media_call_report_sending_failure ( + self->proxy, -1, TP_CALL_STATE_CHANGE_REASON_INTERNAL_ERROR, + TP_ERROR_STR_MEDIA_STREAMING_ERROR, + message, NULL, NULL, NULL, NULL); +} + + +void +tf_call_stream_receiving_failed (TfCallStream *self, + guint *handles, guint handle_count, + const gchar *message) +{ + if (self->proxy == NULL) + return; + + if (handle_count && handle_count > 0) + { + guint i; + + for (i = 0; i < handle_count; i++) + if (handles[i] == self->contact_handle) + goto ok; + return; + } + ok: + + g_warning ("Reporting receiving failure: %s", message); + + tp_cli_call1_stream_interface_media_call_report_receiving_failure ( + self->proxy, -1, TP_CALL_STATE_CHANGE_REASON_INTERNAL_ERROR, + TP_ERROR_STR_MEDIA_STREAMING_ERROR, + message, NULL, NULL, NULL, NULL); +} + + +TpCallStream * +tf_call_stream_get_proxy (TfCallStream *stream) +{ + g_return_val_if_fail (TF_IS_CALL_STREAM (stream), NULL); + + return stream->proxy; +} diff --git a/telepathy-farstream/call-stream.h b/telepathy-farstream/call-stream.h new file mode 100644 index 000000000..31961c1e8 --- /dev/null +++ b/telepathy-farstream/call-stream.h @@ -0,0 +1,134 @@ +/* + * call-stream.h - Source for TfCallStream + * Copyright (C) 2010 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 + */ + +#ifndef __TF_CALL_STREAM_H__ +#define __TF_CALL_STREAM_H__ + +#include <glib-object.h> + +#include <gst/gst.h> +#include <telepathy-glib/telepathy-glib.h> + +#include "call-channel.h" +#include "call-content.h" + +G_BEGIN_DECLS + +#define TF_TYPE_CALL_STREAM tf_call_stream_get_type() + +#define TF_CALL_STREAM(obj) \ + (G_TYPE_CHECK_INSTANCE_CAST ((obj), \ + TF_TYPE_CALL_STREAM, TfCallStream)) + +#define TF_CALL_STREAM_CLASS(klass) \ + (G_TYPE_CHECK_CLASS_CAST ((klass), \ + TF_TYPE_CALL_STREAM, TfCallStreamClass)) + +#define TF_IS_CALL_STREAM(obj) \ + (G_TYPE_CHECK_INSTANCE_TYPE ((obj), TF_TYPE_CALL_STREAM)) + +#define TF_IS_CALL_STREAM_CLASS(klass) \ + (G_TYPE_CHECK_CLASS_TYPE ((klass), TF_TYPE_CALL_STREAM)) + +#define TF_CALL_STREAM_GET_CLASS(obj) \ + (G_TYPE_INSTANCE_GET_CLASS ((obj), \ + TF_TYPE_CALL_STREAM, TfCallStreamClass)) + +typedef struct _TfCallStreamPrivate TfCallStreamPrivate; + +/** + * TfCallStream: + * + * All members of the object are private + */ + +typedef struct _TfCallStream TfCallStream; + +/** + * TfCallStreamClass: + * @parent_class: the parent #GObjecClass + * + * There are no overridable functions + */ + +typedef struct _TfCallStreamClass TfCallStreamClass; + + +struct _TfCallStream { + GObject parent; + + TfCallContent *call_content; + + TpCallStream *proxy; + + gboolean has_endpoint_properties; + gchar *endpoint_objpath; + TpProxy *endpoint; + gchar *creds_username; + gchar *creds_password; + GList *stored_remote_candidates; + gboolean multiple_usernames; + gboolean controlling; + + gchar *last_local_username; + gchar *last_local_password; + + TpStreamFlowState sending_state; + gboolean has_send_resource; + + TpStreamFlowState receiving_state; + gboolean has_receive_resource; + + gboolean has_contact; + guint contact_handle; + FsStream *fsstream; + + gboolean has_media_properties; + TpStreamTransportType transport_type; + gboolean server_info_retrieved; + GPtrArray *stun_servers; + GPtrArray *relay_info; +}; + +struct _TfCallStreamClass{ + GObjectClass parent_class; +}; + + +GType tf_call_stream_get_type (void); + +TfCallStream *tf_call_stream_new ( + TfCallContent *content, + TpCallStream *stream_proxy); + +gboolean tf_call_stream_bus_message (TfCallStream *stream, GstMessage *message); + +void tf_call_stream_sending_failed (TfCallStream *stream, const gchar *message); + +void tf_call_stream_receiving_failed (TfCallStream *stream, + guint *handles, guint handle_count, + const gchar *message); + +TpCallStream * +tf_call_stream_get_proxy (TfCallStream *stream); + + +G_END_DECLS + +#endif /* __TF_CALL_STREAM_H__ */ diff --git a/telepathy-farstream/channel-priv.h b/telepathy-farstream/channel-priv.h new file mode 100644 index 000000000..8fdee93a5 --- /dev/null +++ b/telepathy-farstream/channel-priv.h @@ -0,0 +1,28 @@ +#ifndef __TF_CHANNEL_PRIV_H__ +#define __TF_CHANNEL_PRIV_H__ + +#include "channel.h" + +G_BEGIN_DECLS + + +struct _TfChannel{ + GObject parent; + + /*< private >*/ + + TfChannelPrivate *priv; +}; + +struct _TfChannelClass{ + GObjectClass parent_class; + + /*< private >*/ + + gpointer unused[4]; +}; + + +G_END_DECLS + +#endif /* __TF_CHANNEL_PRIV_H__ */ diff --git a/telepathy-farstream/channel.c b/telepathy-farstream/channel.c new file mode 100644 index 000000000..10107c04e --- /dev/null +++ b/telepathy-farstream/channel.c @@ -0,0 +1,594 @@ +/* + * channel.c - Source for TfChannel + * Copyright (C) 2006-2007 Collabora Ltd. + * Copyright (C) 2006-2007 Nokia Corporation + * + * 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 + */ + +/** + * SECTION:channel + * @short_description: Handle the Call media interfaces on a Channel + * + * This class handles the media part of the + * org.freedesktop.Telepathy.Channel.Type.Call that has HardwareStreaming=FALSE + */ + +#include "config.h" + +#include <stdlib.h> + +#include <telepathy-glib/telepathy-glib.h> +#include <telepathy-glib/telepathy-glib-dbus.h> + +#include <farstream/fs-conference.h> + + +#include "channel.h" +#include "channel-priv.h" +#include "call-channel.h" +#include "content.h" + + +static void channel_async_initable_init (GAsyncInitableIface *asynciface); + +G_DEFINE_TYPE_WITH_CODE (TfChannel, tf_channel, G_TYPE_OBJECT, + G_IMPLEMENT_INTERFACE (G_TYPE_ASYNC_INITABLE, channel_async_initable_init)); + +struct _TfChannelPrivate +{ + TpChannel *channel_proxy; + + TfCallChannel *call_channel; + + gulong channel_invalidated_handler; + + gboolean closed; +}; + +enum +{ + SIGNAL_CLOSED, + SIGNAL_FS_CONFERENCE_ADDED, + SIGNAL_FS_CONFERENCE_REMOVED, + SIGNAL_CONTENT_ADDED, + SIGNAL_CONTENT_REMOVED, + SIGNAL_COUNT +}; + +static guint signals[SIGNAL_COUNT] = {0}; + +enum +{ + PROP_CHANNEL = 1, + PROP_OBJECT_PATH, + PROP_FS_CONFERENCES +}; + +static void shutdown_channel (TfChannel *self); + +static void channel_fs_conference_added (GObject *chan, + FsConference *conf, TfChannel *self); +static void channel_fs_conference_removed (GObject *chan, + FsConference *conf, TfChannel *self); + +static void tf_channel_init_async (GAsyncInitable *initable, + int io_priority, + GCancellable *cancellable, + GAsyncReadyCallback callback, + gpointer user_data); +static gboolean tf_channel_init_finish (GAsyncInitable *initable, + GAsyncResult *res, + GError **error); + +static void content_added (GObject *proxy, + TfContent *content, + TfChannel *self); +static void content_removed (GObject *proxy, + TfContent *content, + TfChannel *self); + + +static void +tf_channel_init (TfChannel *self) +{ + TfChannelPrivate *priv = G_TYPE_INSTANCE_GET_PRIVATE (self, + TF_TYPE_CHANNEL, TfChannelPrivate); + + self->priv = priv; +} + +static void +channel_async_initable_init (GAsyncInitableIface *asynciface) +{ + asynciface->init_async = tf_channel_init_async; + asynciface->init_finish = tf_channel_init_finish; +} + + +static void +tf_channel_get_property (GObject *object, + guint property_id, + GValue *value, + GParamSpec *pspec) +{ + TfChannel *self = TF_CHANNEL (object); + + switch (property_id) + { + case PROP_CHANNEL: + g_value_set_object (value, self->priv->channel_proxy); + break; + case PROP_OBJECT_PATH: + g_value_set_string (value, + tp_proxy_get_object_path (self->priv->channel_proxy)); + break; + case PROP_FS_CONFERENCES: + if (self->priv->call_channel) + { + g_object_get_property (G_OBJECT (self->priv->call_channel), + "fs-conferences", value); + } + break; + default: + G_OBJECT_WARN_INVALID_PROPERTY_ID (object, property_id, pspec); + break; + } +} + +static void +tf_channel_set_property (GObject *object, + guint property_id, + const GValue *value, + GParamSpec *pspec) +{ + TfChannel *self = TF_CHANNEL (object); + + switch (property_id) + { + case PROP_CHANNEL: + self->priv->channel_proxy = TP_CHANNEL (g_value_dup_object (value)); + break; + default: + G_OBJECT_WARN_INVALID_PROPERTY_ID (object, property_id, pspec); + break; + } +} + +static void channel_invalidated (TpChannel *channel_proxy, + guint domain, gint code, gchar *message, TfChannel *self); + +static void +call_channel_ready (GObject *obj, GAsyncResult *call_res, gpointer user_data) +{ + GError *error = NULL; + GSimpleAsyncResult *res = user_data; + TfChannel *self = TF_CHANNEL ( + g_async_result_get_source_object (G_ASYNC_RESULT (res))); + + self->priv->call_channel = TF_CALL_CHANNEL (g_async_initable_new_finish ( + G_ASYNC_INITABLE (obj), call_res, &error)); + + if (error) + { + shutdown_channel (self); + g_simple_async_result_set_op_res_gboolean (res, FALSE); + g_simple_async_result_set_from_error (res, error); + g_clear_error (&error); + } + else + { + g_simple_async_result_set_op_res_gboolean (res, TRUE); + + tp_g_signal_connect_object (self->priv->call_channel, + "fs-conference-added", G_CALLBACK (channel_fs_conference_added), + self, 0); + tp_g_signal_connect_object (self->priv->call_channel, + "fs-conference-removed", G_CALLBACK (channel_fs_conference_removed), + self, 0); + + tp_g_signal_connect_object (self->priv->call_channel, + "content_added", G_CALLBACK (content_added), + self, 0); + tp_g_signal_connect_object (self->priv->call_channel, + "content_removed", G_CALLBACK (content_removed), + self, 0); + } + + + g_simple_async_result_complete (res); + g_object_unref (res); + g_object_unref (self); +} + +static void +channel_prepared (GObject *obj, + GAsyncResult *proxy_res, + gpointer user_data) +{ + TpChannel *channel_proxy = TP_CHANNEL (obj); + TpProxy *as_proxy = (TpProxy *) channel_proxy; + GSimpleAsyncResult *res = user_data; + GError *error = NULL; + TfChannel *self = TF_CHANNEL ( + g_async_result_get_source_object (G_ASYNC_RESULT (res))); + + if (!tp_proxy_prepare_finish (channel_proxy, proxy_res, &error)) + { + g_simple_async_result_propagate_error (res, &error); + shutdown_channel (self); + goto error; + } + + if (self->priv->closed) + { + g_simple_async_result_set_error (res, TP_ERROR, TP_ERROR_CANCELLED, + "Channel already closed"); + goto error; + } + + if (tp_proxy_has_interface_by_id (as_proxy, + TP_IFACE_QUARK_CHANNEL_TYPE_CALL1)) + { + if (!TP_IS_CALL_CHANNEL (channel_proxy)) + { + g_simple_async_result_set_error (res, TP_ERROR, + TP_ERROR_INVALID_ARGUMENT, + "You must pass a TpCallChannel object if its a Call channel"); + g_simple_async_result_set_op_res_gboolean (res, FALSE); + g_simple_async_result_complete (res); + } + else + { + tf_call_channel_new_async (channel_proxy, call_channel_ready, res); + + self->priv->channel_invalidated_handler = g_signal_connect ( + self->priv->channel_proxy, + "invalidated", G_CALLBACK (channel_invalidated), self); + } + } + else + { + g_simple_async_result_set_error (res, TP_ERROR, TP_ERROR_NOT_IMPLEMENTED, + "Channel does not implement " TP_IFACE_CHANNEL_TYPE_CALL1); + goto error; + } + + g_object_unref (self); + return; + +error: + g_simple_async_result_set_op_res_gboolean (res, FALSE); + g_simple_async_result_complete (res); + + g_object_unref (res); + g_object_unref (self); +} + + +static void +tf_channel_init_async (GAsyncInitable *initable, + int io_priority, + GCancellable *cancellable, + GAsyncReadyCallback callback, + gpointer user_data) +{ + TfChannel *self = TF_CHANNEL (initable); + GSimpleAsyncResult *res; + + if (cancellable != NULL) + { + g_simple_async_report_error_in_idle (G_OBJECT (self), callback, user_data, + G_IO_ERROR, G_IO_ERROR_NOT_INITIALIZED, + "TfChannel initialisation does not support cancellation"); + return; + } + + res = g_simple_async_result_new (G_OBJECT (self), callback, user_data, + tf_channel_init_async); + tp_proxy_prepare_async (self->priv->channel_proxy, NULL, + channel_prepared, res); +} + +static gboolean +tf_channel_init_finish (GAsyncInitable *initable, + GAsyncResult *res, + GError **error) +{ + GSimpleAsyncResult *simple_res; + + g_return_val_if_fail (g_simple_async_result_is_valid (res, + G_OBJECT (initable), tf_channel_init_async), FALSE); + simple_res = G_SIMPLE_ASYNC_RESULT (res); + + g_simple_async_result_propagate_error (simple_res, error); + + return g_simple_async_result_get_op_res_gboolean (simple_res); +} + + +static void +tf_channel_dispose (GObject *object) +{ + TfChannel *self = TF_CHANNEL (object); + + g_debug (G_STRFUNC); + + tp_clear_object (&self->priv->call_channel); + + if (self->priv->channel_proxy) + { + TpChannel *tmp; + + if (self->priv->channel_invalidated_handler != 0) + g_signal_handler_disconnect (self->priv->channel_proxy, + self->priv->channel_invalidated_handler); + + tmp = self->priv->channel_proxy; + self->priv->channel_proxy = NULL; + g_object_unref (tmp); + } + + if (G_OBJECT_CLASS (tf_channel_parent_class)->dispose) + G_OBJECT_CLASS (tf_channel_parent_class)->dispose (object); +} + +static void +tf_channel_class_init (TfChannelClass *klass) +{ + GObjectClass *object_class = G_OBJECT_CLASS (klass); + + g_type_class_add_private (klass, sizeof (TfChannelPrivate)); + + object_class->set_property = tf_channel_set_property; + object_class->get_property = tf_channel_get_property; + + object_class->dispose = tf_channel_dispose; + + g_object_class_install_property (object_class, PROP_CHANNEL, + g_param_spec_object ("channel", + "TpChannel object", + "Telepathy channel object which this media channel should operate on", + TP_TYPE_CHANNEL, + G_PARAM_CONSTRUCT_ONLY | G_PARAM_READWRITE | G_PARAM_STATIC_STRINGS)); + + g_object_class_install_property (object_class, PROP_OBJECT_PATH, + g_param_spec_string ("object-path", + "channel object path", + "D-Bus object path of the Telepathy channel which this channel" + " operates on", + NULL, + G_PARAM_READABLE | G_PARAM_STATIC_STRINGS)); + + + g_object_class_install_property (object_class, PROP_FS_CONFERENCES, + g_param_spec_boxed ("fs-conferences", + "Farstream FsConferences objects", + "GPtrArray of Farstream FsConferences for this channel", + G_TYPE_PTR_ARRAY, + G_PARAM_READABLE | G_PARAM_STATIC_STRINGS)); + + /** + * TfChannel::closed: + * + * This function is called after a channel is closed, either because + * it has been closed by the connection manager or because we had a locally + * generated error. + */ + + signals[SIGNAL_CLOSED] = + g_signal_new ("closed", + G_OBJECT_CLASS_TYPE (klass), + G_SIGNAL_RUN_LAST, + 0, + NULL, NULL, NULL, + G_TYPE_NONE, 0); + + /** + * TfChannel::fs-conference-added: + * @tfchannel: the #TfChannel + * @conf: a #FsConference + * + * When this signal is emitted, the conference should be added to the + * application's pipeline. + */ + + signals[SIGNAL_FS_CONFERENCE_ADDED] = g_signal_new ("fs-conference-added", + G_OBJECT_CLASS_TYPE (klass), + G_SIGNAL_RUN_LAST, + 0, NULL, NULL, NULL, + G_TYPE_NONE, 1, FS_TYPE_CONFERENCE); + + /** + * TfChannel::fs-conference-removed: + * @tfchannel: the #TfChannel + * @conf: a #FsConference + * + * When this signal is emitted, the conference should be remove from the + * application's pipeline. + */ + + signals[SIGNAL_FS_CONFERENCE_REMOVED] = g_signal_new ("fs-conference-removed", + G_OBJECT_CLASS_TYPE (klass), + G_SIGNAL_RUN_LAST, + 0, NULL, NULL, NULL, + G_TYPE_NONE, 1, FS_TYPE_CONFERENCE); + + + /** + * TfChannel::content-added: + * @tfchannel: the #TfChannel + * @content: a #TfContent + * + * Tells the application that a content has been added. In the callback for + * this signal, the application should set its preferred codecs, and hook + * up to any signal from #TfContent it cares about. Special care should be + * made to connect #TfContent::src-pad-added as well + * as the #TfContent::start-sending and #TfContent::stop-sending signals. + */ + + signals[SIGNAL_CONTENT_ADDED] = g_signal_new ("content-added", + G_OBJECT_CLASS_TYPE (klass), + G_SIGNAL_RUN_LAST, + 0, NULL, NULL, NULL, + G_TYPE_NONE, 1, TF_TYPE_CONTENT); + + /** + * TfChannel::content-removed: + * @tfchannel: the #TfChannel + * @content: a #TfContent + * + * Tells the application that a content is being removed. + */ + + signals[SIGNAL_CONTENT_REMOVED] = g_signal_new ("content-removed", + G_OBJECT_CLASS_TYPE (klass), + G_SIGNAL_RUN_LAST, + 0, NULL, NULL, NULL, + G_TYPE_NONE, 1, TF_TYPE_CONTENT); +} + +static void +shutdown_channel (TfChannel *self) +{ + tp_clear_object (&self->priv->call_channel); + + if (self->priv->channel_proxy != NULL) + { + if (self->priv->channel_invalidated_handler) + { + g_signal_handler_disconnect ( + self->priv->channel_proxy, self->priv->channel_invalidated_handler); + self->priv->channel_invalidated_handler = 0; + } + } + + g_signal_emit (self, signals[SIGNAL_CLOSED], 0); + + self->priv->closed = TRUE; +} + +static void +channel_invalidated (TpChannel *channel_proxy, + guint domain, + gint code, + gchar *message, + TfChannel *self) +{ + shutdown_channel (self); +} + +/** + * tf_channel_new_async: + * @channel_proxy: a #TpChannel proxy + * @callback: a #GAsyncReadyCallback to call when the channel is ready + * @user_data: the data to pass to callback function + * + * Creates a new #TfChannel from an existing channel proxy, the new + * TfChannel object will be return in the async callback. + * + * The user must call tf_channel_new_finish() in the callback + * to get the finished object. + */ + +void +tf_channel_new_async (TpChannel *channel_proxy, + GAsyncReadyCallback callback, + gpointer user_data) +{ + g_return_if_fail (channel_proxy != NULL); + g_return_if_fail (callback != NULL); + + return g_async_initable_new_async (TF_TYPE_CHANNEL, + 0, NULL, callback, user_data, + "channel", channel_proxy, + NULL); +} + +/** + * tf_channel_bus_message: + * @channel: A #TfChannel + * @message: A #GstMessage received from the bus + * + * You must call this function on call messages received on the async bus. + * #GstMessages are not modified. + * + * Returns: %TRUE if the message has been handled, %FALSE otherwise + */ + +gboolean +tf_channel_bus_message (TfChannel *channel, + GstMessage *message) +{ + g_return_val_if_fail (channel != NULL, FALSE); + g_return_val_if_fail (message != NULL, FALSE); + + if (channel->priv->call_channel) + return tf_call_channel_bus_message (channel->priv->call_channel, + message); + + return FALSE; +} + +static void +channel_fs_conference_added (GObject *proxy, FsConference *conf, + TfChannel *self) +{ + g_object_notify (G_OBJECT (self), "fs-conferences"); + g_signal_emit (self, signals[SIGNAL_FS_CONFERENCE_ADDED], 0, + conf); +} + +static void +channel_fs_conference_removed (GObject *proxy, FsConference *conf, + TfChannel *self) +{ + g_object_notify (G_OBJECT (self), "fs-conferences"); + g_signal_emit (self, signals[SIGNAL_FS_CONFERENCE_REMOVED], 0, + conf); +} + +static void +content_added (GObject *proxy, TfContent *content, TfChannel *self) +{ + g_signal_emit (self, signals[SIGNAL_CONTENT_ADDED], 0, content); +} + +static void +content_removed (GObject *proxy, TfContent *content, TfChannel *self) +{ + g_signal_emit (self, signals[SIGNAL_CONTENT_REMOVED], 0, content); +} + +/** + * tf_channel_new_finish: + * @object: The #GObject + * @result: a #GAsyncResult + * @error: the location of a #GError or %NULL to ignore it + * + * Completes the construction of a TfChannel. + * + * Returns: a #TfChannel or %NULL if there was an error + * Since: 0.2.2 + */ + +TfChannel * +tf_channel_new_finish (GObject *object, + GAsyncResult *result, + GError **error) +{ + return (TfChannel *) g_async_initable_new_finish (G_ASYNC_INITABLE (object), + result, error); +} diff --git a/telepathy-farstream/channel.h b/telepathy-farstream/channel.h new file mode 100644 index 000000000..77b3038be --- /dev/null +++ b/telepathy-farstream/channel.h @@ -0,0 +1,65 @@ +#ifndef __TF_CHANNEL_H__ +#define __TF_CHANNEL_H__ + +#include <glib-object.h> +#include <gst/gst.h> +#include <telepathy-glib/telepathy-glib.h> + +G_BEGIN_DECLS + +#define TF_TYPE_CHANNEL tf_channel_get_type() + +#define TF_CHANNEL(obj) \ + (G_TYPE_CHECK_INSTANCE_CAST ((obj), \ + TF_TYPE_CHANNEL, TfChannel)) + +#define TF_CHANNEL_CLASS(klass) \ + (G_TYPE_CHECK_CLASS_CAST ((klass), \ + TF_TYPE_CHANNEL, TfChannelClass)) + +#define TF_IS_CHANNEL(obj) \ + (G_TYPE_CHECK_INSTANCE_TYPE ((obj), TF_TYPE_CHANNEL)) + +#define TF_IS_CHANNEL_CLASS(klass) \ + (G_TYPE_CHECK_CLASS_TYPE ((klass), TF_TYPE_CHANNEL)) + +#define TF_CHANNEL_GET_CLASS(obj) \ + (G_TYPE_INSTANCE_GET_CLASS ((obj), \ + TF_TYPE_CHANNEL, TfChannelClass)) + +typedef struct _TfChannelPrivate TfChannelPrivate; + +/** + * TfChannel: + * + * All members of the object are private + */ + +typedef struct _TfChannel TfChannel; + +/** + * TfChannelClass: + * @parent_class: the parent #GObjectClass + * + * There are no overridable functions + */ + +typedef struct _TfChannelClass TfChannelClass; + +GType tf_channel_get_type (void); + +void tf_channel_new_async (TpChannel *channel_proxy, + GAsyncReadyCallback callback, + gpointer user_data); + +TfChannel *tf_channel_new_finish (GObject *object, + GAsyncResult *result, + GError **error); + + +gboolean tf_channel_bus_message (TfChannel *channel, + GstMessage *message); + +G_END_DECLS + +#endif /* __TF_CHANNEL_H__ */ diff --git a/telepathy-farstream/content-priv.h b/telepathy-farstream/content-priv.h new file mode 100644 index 000000000..ab137da68 --- /dev/null +++ b/telepathy-farstream/content-priv.h @@ -0,0 +1,48 @@ + +#ifndef __TF_CONTENT_PRIV_H__ +#define __TF_CONTENT_PRIV_H__ + +#include <glib-object.h> + +#include <farstream/fs-conference.h> + + +G_BEGIN_DECLS + +struct _TfContent { + GObject parent; + + guint sending_count; +}; + +struct _TfContentClass{ + GObjectClass parent_class; + + void (*content_error) (TfContent *content, + const gchar *message); + + void (*sending_failed) (TfContent *content, + const gchar *message); + void (*receiving_failed) (TfContent *content, + guint *handles, guint handle_count, + const gchar *message); + + GstIterator * (*iterate_src_pads) (TfContent *content, guint *handle, + guint handle_count); +}; + +gboolean _tf_content_start_sending (TfContent *self); +void _tf_content_stop_sending (TfContent *self); +void _tf_content_mute_to_stop_sending (TfContent *self); + +void _tf_content_emit_src_pad_added (TfContent *self, guint handle, + FsStream *stream, GstPad *pad, FsCodec *codec); + +gboolean _tf_content_start_receiving (TfContent *self, guint *handles, + guint handle_count); +void _tf_content_stop_receiving (TfContent *self, + guint *handles, guint handle_count); + +G_END_DECLS + +#endif /* __TF_CONTENT_PRIV_H__ */ diff --git a/telepathy-farstream/content.c b/telepathy-farstream/content.c new file mode 100644 index 000000000..09989cd02 --- /dev/null +++ b/telepathy-farstream/content.c @@ -0,0 +1,551 @@ +#include "config.h" + +#include "content.h" +#include "content-priv.h" + +#include <farstream/fs-conference.h> + +#include "channel.h" + + +/** + * SECTION:content + * @short_description: Represent the Content of a channel handled by #TfChannel + * + * Objects of this class allow the user to handle the media side of a Telepathy + * channel handled by #TfChannel. + * + * This object is created by the #TfChannel and the user is notified + * of its creation by the #TfChannel::content-added signal. In the + * callback for this signal, the user should connect to the + * #TfContent::src-pad-added signal. + * + */ + + +G_DEFINE_ABSTRACT_TYPE (TfContent, tf_content, G_TYPE_OBJECT); + + +enum +{ + PROP_TF_CHANNEL = 1, + PROP_FS_CONFERENCE, + PROP_FS_SESSION, + PROP_MEDIA_TYPE, + PROP_SINK_PAD, + PROP_OBJECT_PATH +}; + +enum +{ + SIGNAL_START_SENDING, + SIGNAL_STOP_SENDING, + SIGNAL_SRC_PAD_ADDED, + SIGNAL_START_RECEIVING, + SIGNAL_STOP_RECEIVING, + SIGNAL_RESTART_SOURCE, + SIGNAL_COUNT +}; + +static guint signals[SIGNAL_COUNT] = {0}; + +static void +tf_content_get_property (GObject *object, + guint property_id, + GValue *value, + GParamSpec *pspec) +{ + switch (property_id) + { + /* Other properties need to be overwritten */ + default: + G_OBJECT_WARN_INVALID_PROPERTY_ID (object, property_id, pspec); + break; + } +} + +static void +tf_content_class_init (TfContentClass *klass) +{ + GObjectClass *object_class = G_OBJECT_CLASS (klass); + + object_class->get_property = tf_content_get_property; + + g_object_class_install_property (object_class, PROP_TF_CHANNEL, + g_param_spec_object ("tf-channel", + "Parent TfChannel object ", + "The Telepathy-Farstream Channel for this object", + TF_TYPE_CHANNEL, + G_PARAM_READABLE | G_PARAM_STATIC_STRINGS)); + + g_object_class_install_property (object_class, PROP_FS_CONFERENCE, + g_param_spec_object ("fs-conference", + "Farstream FsConference used by the Content ", + "The Farstream conference for this content " + "(could be the same as other contents)", + FS_TYPE_CONFERENCE, + G_PARAM_READABLE | G_PARAM_STATIC_STRINGS)); + + g_object_class_install_property (object_class, PROP_FS_SESSION, + g_param_spec_object ("fs-session", + "Farstream FsSession ", + "The Farstream session for this content", + FS_TYPE_SESSION, + G_PARAM_READABLE | G_PARAM_STATIC_STRINGS)); + + g_object_class_install_property (object_class, PROP_SINK_PAD, + g_param_spec_object ("sink-pad", + "Sink Pad", + "Sink GstPad for this content", + GST_TYPE_PAD, + G_PARAM_READABLE | G_PARAM_STATIC_STRINGS)); + + g_object_class_install_property (object_class, PROP_MEDIA_TYPE, + g_param_spec_enum ("media-type", + "MediaType", + "The FsMediaType for this content", + FS_TYPE_MEDIA_TYPE, + 0, + G_PARAM_READABLE | G_PARAM_STATIC_STRINGS)); + + g_object_class_install_property (object_class, PROP_OBJECT_PATH, + g_param_spec_string ("object-path", + "content object path", + "D-Bus object path of the Telepathy content which this content" + " operates on", + NULL, + G_PARAM_READABLE | G_PARAM_STATIC_STRINGS)); + + + /** + * TfContent::start-sending: + * @content: the #TfContent + * + * This signal is emitted when the connection manager ask to send media. + * For example, this can be used to open a camera, start recording from a + * microphone or play back a file. The application should start + * sending data on the #TfContent:sink-pad + * + * Returns: %TRUE if the application can start providing data or %FALSE + * otherwise + */ + + signals[SIGNAL_START_SENDING] = + g_signal_new ("start-sending", + G_OBJECT_CLASS_TYPE (klass), + G_SIGNAL_RUN_LAST, + 0, + g_signal_accumulator_true_handled, NULL, NULL, + G_TYPE_BOOLEAN, 0); + + /** + * TfContent::stop-sending: + * @content: the #TfContent + * + * This signal is emitted when the connection manager ask to stop + * sending media + */ + + signals[SIGNAL_STOP_SENDING] = + g_signal_new ("stop-sending", + G_OBJECT_CLASS_TYPE (klass), + G_SIGNAL_RUN_LAST, + 0, NULL, NULL, NULL, + G_TYPE_NONE, 0); + + /** + * TfContent::src-pad-added: + * @content: the #TfContent + * @handle: the handle of the remote party producing the content on this pad + * or 0 if unknown + * @stream: the #FsStream for this pad + * @pad: a #GstPad + * @codec: the #FsCodec for this pad + * + * This signal is emitted when a data is coming on a new pad. This signal + * is not emitted on the main thread, so special care must be made to lock + * the relevant data. When the callback returns from this signal, data will + * start flowing through the pad, so the application MUST connect a sink. + */ + + signals[SIGNAL_SRC_PAD_ADDED] = + g_signal_new ("src-pad-added", + G_OBJECT_CLASS_TYPE (klass), + G_SIGNAL_RUN_LAST, + 0, NULL, NULL, NULL, + G_TYPE_NONE, 4, + G_TYPE_UINT, FS_TYPE_STREAM, GST_TYPE_PAD, FS_TYPE_CODEC); + + /** + * TfContent::start-receiving: + * @content: the #TfContent + * @handles: a 0-terminated array of #guint containing the handles + * @handle_count: The number of handles in the @handles array + * + * This signal is emitted when the connection managers requests that the + * application prepares itself to start receiving data again from certain + * handles. + * + * This signal will only be emitted after the #TfContent::stop-receiving + * signal has succeeded. It will not be emitted right after + * #TfContent::src-pad-added. + * + * Returns: %TRUE if the application can start receiving data or %FALSE + * otherwise + */ + + signals[SIGNAL_START_RECEIVING] = + g_signal_new ("start-receiving", + G_OBJECT_CLASS_TYPE (klass), + G_SIGNAL_RUN_LAST, + 0, + g_signal_accumulator_true_handled, NULL, NULL, + G_TYPE_BOOLEAN, 2, G_TYPE_POINTER, G_TYPE_UINT); + + /** + * TfContent::stop-receiving: + * @content: the #TfContent + * @handles: a 0-terminated array of #guint containing the handles + * @handle_count: The number of handles in the @handles array + * + * This signal is emitted when the connection manager wants to tell the + * application that it is now allowed to stop receiving. + */ + + signals[SIGNAL_STOP_RECEIVING] = + g_signal_new ("stop-receiving", + G_OBJECT_CLASS_TYPE (klass), + G_SIGNAL_RUN_LAST, + 0, NULL, NULL, NULL, + G_TYPE_NONE, 2, G_TYPE_POINTER, G_TYPE_UINT); + + /** + * TfContent::restart-source: + * @content: the #TfContent + * + * This signal requests that the source be restarted so that the caps can + * be renegotiated with a new resolutions and framerate. + */ + + signals[SIGNAL_RESTART_SOURCE] = + g_signal_new ("restart-source", + G_OBJECT_CLASS_TYPE (klass), + G_SIGNAL_RUN_LAST, + 0, NULL, NULL, NULL, + G_TYPE_NONE, 0); +} + + +static void +tf_content_init (TfContent *self) +{ +} + + +gboolean +_tf_content_start_sending (TfContent *self) +{ + GValue instance = {0}; + GValue sending_success_val = {0,}; + gboolean sending_success; + + + if (self->sending_count) + { + self->sending_count ++; + return TRUE; + } + + g_value_init (&sending_success_val, G_TYPE_BOOLEAN); + g_value_set_boolean (&sending_success_val, TRUE); + + g_value_init (&instance, TF_TYPE_CONTENT); + g_value_set_object (&instance, self); + + g_debug ("Requesting that the application start sending"); + + g_signal_emitv (&instance, signals[SIGNAL_START_SENDING], 0, + &sending_success_val); + sending_success = g_value_get_boolean (&sending_success_val); + + g_value_unset (&instance); + + g_debug ("Request to start sending %s", + sending_success ? "succeeded" : "failed"); + + self->sending_count = 1; + + return sending_success; +} + +void +_tf_content_stop_sending (TfContent *self) +{ + self->sending_count --; + + if (self->sending_count == 0) + { + g_signal_emit (self, signals[SIGNAL_STOP_SENDING], 0); + } +} + + +void +_tf_content_emit_src_pad_added (TfContent *self, guint handle, + FsStream *stream, GstPad *pad, FsCodec *codec) +{ + g_signal_emit (self, signals[SIGNAL_SRC_PAD_ADDED], 0, handle, + stream, pad, codec); +} + +/** + * tf_content_error_literal: + * @content: a #TfContent + * @message: error Message + * + * Send a fatal streaming error to the Content to the CM, the effect is most + * likely that the content will be removed. + * + * Rename to: tf_content_error + */ + +void +tf_content_error_literal (TfContent *content, + const gchar *message) +{ + TfContentClass *klass = TF_CONTENT_GET_CLASS (content); + + g_return_if_fail (content != NULL); + g_return_if_fail (message != NULL); + + if (klass->content_error) + klass->content_error (content, message); + else + GST_WARNING ("content_error not defined in class: %s", message); +} + +/** + * tf_content_error: + * @content: a #TfContent + * @message_format: error Message with printf style formatting + * @...: Parameters to insert into the @message_format string + * + * Send a fatal streaming error to the Content to the CM, the effect is most + * likely that the content will be removed. + */ + +void +tf_content_error (TfContent *content, + const gchar *message_format, + ...) +{ + gchar *message; + va_list valist; + + g_return_if_fail (content != NULL); + g_return_if_fail (message_format != NULL); + + va_start (valist, message_format); + message = g_strdup_vprintf (message_format, valist); + va_end (valist); + + tf_content_error_literal (content, message); + g_free (message); +} + +/** + * tf_content_iterate_src_pads: + * @content: a #TfContent + * @handles: a 0 terminated array of #guint representing Telepathy handles + * @handle_count: the numner of handles in @handles + * + * Provides a iterator that can be used to iterate through all of the src + * pads that are are used to receive from a group of Telepathy handles. + * + * Returns: a #GstIterator + */ + +GstIterator * +tf_content_iterate_src_pads (TfContent *content, guint *handles, + guint handle_count) +{ + TfContentClass *klass = TF_CONTENT_GET_CLASS (content); + + g_return_val_if_fail (content != NULL, NULL); + + if (klass->iterate_src_pads) + return klass->iterate_src_pads (content, handles, handle_count); + else + GST_WARNING ("iterate_src_pads not defined in class"); + + return NULL; +} + +gboolean +_tf_content_start_receiving (TfContent *self, guint *handles, + guint handle_count) +{ + GValue instance_and_params[3] = {{0} , {0}, {0}}; + GValue receiving_success_val = {0,}; + gboolean receiving_success; + + g_value_init (&receiving_success_val, G_TYPE_BOOLEAN); + g_value_set_boolean (&receiving_success_val, TRUE); + + g_value_init (&instance_and_params[0], TF_TYPE_CONTENT); + g_value_set_object (&instance_and_params[0], self); + + g_value_init (&instance_and_params[1], G_TYPE_POINTER); + g_value_set_pointer (&instance_and_params[1], handles); + + g_value_init (&instance_and_params[2], G_TYPE_UINT); + g_value_set_uint (&instance_and_params[2], handle_count); + + g_debug ("Requesting that the application start receiving"); + + g_signal_emitv (instance_and_params, signals[SIGNAL_START_RECEIVING], 0, + &receiving_success_val); + receiving_success = g_value_get_boolean (&receiving_success_val); + + g_value_unset (&instance_and_params[0]); + + g_debug ("Request to start receiving %s", + receiving_success ? "succeeded" : "failed"); + + return receiving_success; +} + +void +_tf_content_stop_receiving (TfContent *self, guint *handles, + guint handle_count) +{ + g_debug ("Requesting that the application stop receiving"); + g_signal_emit (self, signals[SIGNAL_STOP_RECEIVING], 0, handles, + handle_count); +} + + +/** + * tf_content_sending_failed_literal: + * @content: a #TfContent + * @message: The error message + * + * Informs the Connection Manager that sending has failed for this + * content. This is a transient error and it may or not not end the Content + * and the call. + * + * Rename to: tf_content_sending_failed + */ + +void +tf_content_sending_failed_literal (TfContent *content, + const gchar *message) +{ + TfContentClass *klass = TF_CONTENT_GET_CLASS (content); + + g_return_if_fail (content != NULL); + g_return_if_fail (message != NULL); + + if (klass->content_error) + klass->sending_failed (content, message); + else + GST_WARNING ("sending_failed not defined in class, ignoring error: %s", + message); +} + +/** + * tf_content_sending_failed: + * @content: a #TfContent + * @message_format: Message with printf style formatting + * @...: Parameters to insert into the @message_format string + * + * Informs the Connection Manager that sending has failed for this + * content. This is a transient error and it may or not not end the Content + * and the call. + */ + +void +tf_content_sending_failed (TfContent *content, + const gchar *message_format, ...) +{ + gchar *message; + va_list valist; + + g_return_if_fail (content != NULL); + g_return_if_fail (message_format != NULL); + + va_start (valist, message_format); + message = g_strdup_vprintf (message_format, valist); + va_end (valist); + + tf_content_sending_failed_literal (content, message); + g_free (message); +} + +/** + * tf_content_receiving_failed_literal: + * @content: a #TfContent + * @handles: an array of #guint representing Telepathy handles, may be %NULL + * @handle_count: the numner of handles in @handles + * @message: The error message + * + * Informs the Connection Manager that receiving has failed for this + * content. This is a transient error and it may or not not end the Content + * and the call. + * + * If handles are not specific, it assumes that it is valid for all handles. + * + * Rename to: tf_content_receiving_failed + */ + +void +tf_content_receiving_failed_literal (TfContent *content, + guint *handles, guint handle_count, + const gchar *message) +{ + TfContentClass *klass = TF_CONTENT_GET_CLASS (content); + + g_return_if_fail (content != NULL); + g_return_if_fail (message != NULL); + + if (klass->content_error) + klass->receiving_failed (content, handles, handle_count, message); + else + GST_WARNING ("receiving_failed not defined in class, ignoring error: %s", + message); +} + + +/** + * tf_content_receiving_failed: + * @content: a #TfContent + * @handles: an array of #guint representing Telepathy handles, may be %NULL + * @handle_count: the numner of handles in @handles + * @message_format: Message with printf style formatting + * @...: Parameters to insert into the @message_format string + * + * Informs the Connection Manager that receiving has failed for this + * content. This is a transient error and it may or not not end the Content + * and the call. + * + * If handles are not specific, it assumes that it is valid for all handles. + */ + +void +tf_content_receiving_failed (TfContent *content, + guint *handles, guint handle_count, + const gchar *message_format, ...) +{ + gchar *message; + va_list valist; + + g_return_if_fail (content != NULL); + g_return_if_fail (message_format != NULL); + + va_start (valist, message_format); + message = g_strdup_vprintf (message_format, valist); + va_end (valist); + + tf_content_receiving_failed_literal (content, handles, handle_count, message); + g_free (message); +} diff --git a/telepathy-farstream/content.h b/telepathy-farstream/content.h new file mode 100644 index 000000000..97c5c3a7a --- /dev/null +++ b/telepathy-farstream/content.h @@ -0,0 +1,72 @@ +#ifndef __TF_CONTENT_H__ +#define __TF_CONTENT_H__ + +#include <glib-object.h> +#include <farstream/fs-conference.h> + +G_BEGIN_DECLS + +#define TF_TYPE_CONTENT tf_content_get_type() + +#define TF_CONTENT(obj) \ + (G_TYPE_CHECK_INSTANCE_CAST ((obj), \ + TF_TYPE_CONTENT, TfContent)) + +#define TF_CONTENT_CLASS(klass) \ + (G_TYPE_CHECK_CLASS_CAST ((klass), \ + TF_TYPE_CONTENT, TfContentClass)) + +#define TF_IS_CONTENT(obj) \ + (G_TYPE_CHECK_INSTANCE_TYPE ((obj), TF_TYPE_CONTENT)) + +#define TF_IS_CONTENT_CLASS(klass) \ + (G_TYPE_CHECK_CLASS_TYPE ((klass), TF_TYPE_CONTENT)) + +#define TF_CONTENT_GET_CLASS(obj) \ + (G_TYPE_INSTANCE_GET_CLASS ((obj), \ + TF_TYPE_CONTENT, TfContentClass)) + +typedef struct _TfContentPrivate TfContentPrivate; + +/** + * TfContent: + * + * This structure is private, this class is not subclassable. + */ + +typedef struct _TfContent TfContent; + +/** + * TfContentClass: + * + * This structure is private, this class is not subclassable. + */ + +typedef struct _TfContentClass TfContentClass; + +GType tf_content_get_type (void); + +void tf_content_error_literal (TfContent *content, + const gchar *message); +void tf_content_error (TfContent *content, + const gchar *message_format, ...) G_GNUC_PRINTF (2, 3); + + +void tf_content_sending_failed_literal (TfContent *content, + const gchar *message); +void tf_content_sending_failed (TfContent *content, + const gchar *message_format, ...) G_GNUC_PRINTF (2, 3); + +void tf_content_receiving_failed_literal (TfContent *content, + guint *handles, guint handle_count, + const gchar *message); +void tf_content_receiving_failed (TfContent *content, + guint *handles, guint handle_count, + const gchar *message_format, ...) G_GNUC_PRINTF (4, 5); + +GstIterator *tf_content_iterate_src_pads (TfContent *content, + guint *handles, guint handle_count); + +G_END_DECLS + +#endif /* __TF_CONTENT_H__ */ diff --git a/telepathy-farstream/examples/Makefile.am b/telepathy-farstream/examples/Makefile.am new file mode 100644 index 000000000..bd2dc0d38 --- /dev/null +++ b/telepathy-farstream/examples/Makefile.am @@ -0,0 +1,24 @@ +SUBDIRS=python + +noinst_PROGRAMS = call-handler + +LDADD = \ + $(top_builddir)/telepathy-farstream/libtelepathy-farstream-1.la \ + $(top_builddir)/telepathy-glib/libtelepathy-glib-1.la \ + $(top_builddir)/telepathy-glib/libtelepathy-glib-1-dbus.la \ + $(top_builddir)/telepathy-glib/libtelepathy-glib-1-core.la \ + $(GLIB_LIBS) \ + $(DBUS_LIBS) \ + $(GST_LIBS) \ + $(FARSTREAM_LIBS) \ + $(NULL) + +AM_CFLAGS = \ + -I$(top_srcdir) \ + -I$(top_builddir) \ + $(ERROR_CFLAGS) \ + $(GLIB_CFLAGS) \ + $(DBUS_CFLAGS) \ + $(GST_CFLAGS) \ + $(FARSTREAM_CFLAGS) \ + $(NULL) diff --git a/telepathy-farstream/examples/call-handler.c b/telepathy-farstream/examples/call-handler.c new file mode 100644 index 000000000..d26aea411 --- /dev/null +++ b/telepathy-farstream/examples/call-handler.c @@ -0,0 +1,646 @@ +/* + * 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 "config.h" + +#include <gst/gst.h> +#include <telepathy-glib/telepathy-glib.h> +#include <telepathy-glib/telepathy-glib-dbus.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, "requested-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 ! autoaudiosink", 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 ( + "videoconvert ! videoscale ! autovideosink", + TRUE, NULL); + break; + default: + g_warning ("Unknown media type"); + return; + } + + gst_bin_add (GST_BIN (context->pipeline), element); + sinkpad = gst_element_get_static_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, "requested-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 ( + "pulsesrc ! audio/x-raw, 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::requested-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 ( + "autovideosrc ! videorate drop-only=1 average-period=20000000000 ! videoscale ! videoconvert ! 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", + "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); + context->has_audio_src = TRUE; + break; + case FS_MEDIA_TYPE_VIDEO: + if (context->has_video_src) + goto out; + + element = setup_video_source (context, content); + context->has_video_src = TRUE; + break; + default: + g_warning ("Unknown media type"); + goto out; + } + + + gst_bin_add (GST_BIN (context->pipeline), element); + srcpad = gst_element_get_static_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_call_channel_accept_async (TP_CALL_CHANNEL (proxy), 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; + + 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, + "TpFsCallHandlerDemo", + 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_CALL1, + TP_PROP_CHANNEL_TARGET_HANDLE_TYPE, G_TYPE_UINT, + TP_HANDLE_TYPE_CONTACT, + TP_PROP_CHANNEL_TYPE_CALL1_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_CALL1, + TP_PROP_CHANNEL_TARGET_HANDLE_TYPE, G_TYPE_UINT, + TP_HANDLE_TYPE_CONTACT, + TP_PROP_CHANNEL_TYPE_CALL1_INITIAL_VIDEO, G_TYPE_BOOLEAN, + TRUE, + NULL)); + + tp_base_client_add_handler_capabilities_varargs (client, + TP_IFACE_CHANNEL_TYPE_CALL1 "/video/h264", + TP_TOKEN_CHANNEL_TYPE_CALL1_SHM, + TP_TOKEN_CHANNEL_TYPE_CALL1_ICE, + TP_TOKEN_CHANNEL_TYPE_CALL1_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; +} diff --git a/telepathy-farstream/examples/python/Makefile.am b/telepathy-farstream/examples/python/Makefile.am new file mode 100644 index 000000000..7b89027c2 --- /dev/null +++ b/telepathy-farstream/examples/python/Makefile.am @@ -0,0 +1,7 @@ +EXTRA_DIST = \ + README \ + callchannel.py \ + callhandler.py \ + callui.py \ + constants.py \ + util.py diff --git a/telepathy-farstream/examples/python/README b/telepathy-farstream/examples/python/README new file mode 100644 index 000000000..8007df6b5 --- /dev/null +++ b/telepathy-farstream/examples/python/README @@ -0,0 +1,5 @@ +Simple python example using telepathy-farstream in most minimal way possible. +Two programs are included: + +callui.py: Doesn't do anything with tp-fs, but allows the start of a Call call +callhandler.py: Simple handler that handles calls and handles the media diff --git a/telepathy-farstream/examples/python/callchannel.py b/telepathy-farstream/examples/python/callchannel.py new file mode 100644 index 000000000..f37c7243b --- /dev/null +++ b/telepathy-farstream/examples/python/callchannel.py @@ -0,0 +1,180 @@ +#!/usr/bin/env python +# +# callchannel.py +# Copyright (C) 2008-2010 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 + +import dbus +import dbus.glib +import gobject +import sys +from glib import GError + +import pygst +pygst.require("0.10") +import gst + +import tpfarstream +import farstream +from util import * +import gc + +from telepathy.client.channel import Channel +from telepathy.constants import ( + CONNECTION_HANDLE_TYPE_NONE, CONNECTION_HANDLE_TYPE_CONTACT, + CONNECTION_STATUS_CONNECTED, CONNECTION_STATUS_DISCONNECTED, + MEDIA_STREAM_STATE_CONNECTED + ) +from telepathy.interfaces import ( + CHANNEL_INTERFACE, CONN_INTERFACE, + CONNECTION_INTERFACE_REQUESTS, + CONNECTION_INTERFACE_CONTACT_CAPABILITIES, + CLIENT) + +from constants import * + +class CallChannel: + def __init__ (self, bus, connection, object_path, properties): + self.bus = bus + self.conn = connection + self.tfchannel = None + + self.obj = self.bus.get_object (self.conn.service_name, object_path) + self.obj.connect_to_signal ("CallStateChanged", + self.state_changed_cb, dbus_interface=CHANNEL_TYPE_CALL) + + self.pipeline = gst.Pipeline() + self.pipeline.get_bus().add_watch(self.async_handler) + + self.notifier = notifier = farstream.ElementAddedNotifier() + notifier.set_properties_from_file("element-properties") + notifier.add(self.pipeline) + + tpfarstream.tf_channel_new_async (connection.service_name, + connection.object_path, object_path, self.tpfs_created) + + def state_changed_cb(self, state, flags, reason, details): + print "* StateChanged:\n State: %s (%d)\n Flags: %s" % ( + call_state_to_s (state), state, call_flags_to_s (flags)) + + print "\tReason: actor: %d reason: %d dbus_reason: '%s'" % ( + reason[0], reason[1], reason[2]) + + print '\tDetails:' + for key, value in details.iteritems(): + print "\t %s: %s" % (key, value) + else: + print '\t None' + + if state == CALL_STATE_ENDED: + self.close() + + def accept (self): + self.obj.Accept(dbus_interface=CHANNEL_TYPE_CALL) + + def close (self): + print "Closing the channel" + # close and cleanup + self.obj.Close(dbus_interface=CHANNEL_INTERFACE) + + self.pipeline.set_state (gst.STATE_NULL) + self.pipeline = None + + self.tfchannel = None + self.notifier = None + + def async_handler (self, bus, message): + if self.tfchannel != None: + self.tfchannel.bus_message(message) + return True + + self.pipeline = gst.Pipeline() + + def tpfs_created (self, source, result): + tfchannel = self.tfchannel = source.new_finish(result) + tfchannel.connect ("fs-conference-added", self.conference_added) + tfchannel.connect ("content-added", self.content_added) + + + def src_pad_added (self, content, handle, stream, pad, codec): + type = content.get_property ("media-type") + if type == farstream.MEDIA_TYPE_AUDIO: + sink = gst.parse_bin_from_description("audioconvert ! audioresample ! audioconvert ! autoaudiosink", True) + elif type == farstream.MEDIA_TYPE_VIDEO: + sink = gst.parse_bin_from_description("ffmpegcolorspace ! videoscale ! autovideosink", True) + + self.pipeline.add(sink) + pad.link(sink.get_pad("sink")) + sink.set_state(gst.STATE_PLAYING) + + def get_codec_config (self, media_type): + if media_type == farstream.MEDIA_TYPE_VIDEO: + codecs = [ farstream.Codec(farstream.CODEC_ID_ANY, "H264", + farstream.MEDIA_TYPE_VIDEO, 0) ] + if self.conn.GetProtocol() == "sip" : + codecs += [ farstream.Codec(farstream.CODEC_ID_DISABLE, "THEORA", + farstream.MEDIA_TYPE_VIDEO, 0) ] + else: + codecs += [ farstream.Codec(farstream.CODEC_ID_ANY, "THEORA", + farstream.MEDIA_TYPE_VIDEO, 0) ] + codecs += [ + farstream.Codec(farstream.CODEC_ID_ANY, "H263", + farstream.MEDIA_TYPE_VIDEO, 0), + farstream.Codec(farstream.CODEC_ID_DISABLE, "DV", + farstream.MEDIA_TYPE_VIDEO, 0), + farstream.Codec(farstream.CODEC_ID_ANY, "JPEG", + farstream.MEDIA_TYPE_VIDEO, 0), + farstream.Codec(farstream.CODEC_ID_ANY, "MPV", + farstream.MEDIA_TYPE_VIDEO, 0), + ] + + else: + codecs = [ + farstream.Codec(farstream.CODEC_ID_ANY, "SPEEX", + farstream.MEDIA_TYPE_AUDIO, 16000 ), + farstream.Codec(farstream.CODEC_ID_ANY, "SPEEX", + farstream.MEDIA_TYPE_AUDIO, 8000 ) + ] + return codecs + + def content_added(self, channel, content): + sinkpad = content.get_property ("sink-pad") + + mtype = content.get_property ("media-type") + prefs = self.get_codec_config (mtype) + if prefs != None: + try: + content.set_codec_preferences(prefs) + except GError, e: + print e.message + + content.connect ("src-pad-added", self.src_pad_added) + + if mtype == farstream.MEDIA_TYPE_AUDIO: + src = gst.parse_bin_from_description("audiotestsrc is-live=1 ! " \ + "queue", True) + elif mtype == farstream.MEDIA_TYPE_VIDEO: + src = gst.parse_bin_from_description("videotestsrc is-live=1 ! " \ + "capsfilter caps=video/x-raw-yuv,width=320,height=240", True) + + self.pipeline.add(src) + src.get_pad("src").link(sinkpad) + src.set_state(gst.STATE_PLAYING) + + def conference_added (self, channel, conference): + self.pipeline.add(conference) + self.pipeline.set_state(gst.STATE_PLAYING) + diff --git a/telepathy-farstream/examples/python/callhandler.py b/telepathy-farstream/examples/python/callhandler.py new file mode 100644 index 000000000..71af24cd9 --- /dev/null +++ b/telepathy-farstream/examples/python/callhandler.py @@ -0,0 +1,116 @@ +# callhandler.py +# Copyright (C) 2008-2010 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 + +import gobject +# Need gio so GAsyncInitialbe is known +import gio + +import dbus +from dbus.mainloop.glib import DBusGMainLoop +DBusGMainLoop(set_as_default=True) + +from constants import * +from telepathy.interfaces import CHANNEL_INTERFACE, CLIENT, CLIENT_HANDLER +from telepathy.constants import CONNECTION_HANDLE_TYPE_CONTACT, CONNECTION_HANDLE_TYPE_ROOM +import telepathy + +from callchannel import CallChannel + +class CallHandler(dbus.service.Object, telepathy.server.DBusProperties): + def __init__(self, bus, bus_name = None): + self.bus = bus + if bus_name == None: + self.bus_name = "org.freedesktop.Telepathy.Client.CallDemo" \ + + bus.get_unique_name().replace(":", "_").replace(".","_") + else: + self.bus_name = bus_name + self.path = "/" + self.bus_name.replace(".", "/") + self._interfaces = set([CLIENT, CLIENT_HANDLER]) + self._prop_getters = {} + self._prop_setters = {} + + dbus.service.Object.__init__(self, bus, self.path) + telepathy.server.DBusProperties.__init__(self) + + self._name = dbus.service.BusName (self.bus_name, bus) + + self._implement_property_get (CLIENT, + { "Interfaces": self._get_interfaces } ) + self._implement_property_get (CLIENT_HANDLER, + { "HandlerChannelFilter": self._get_filters } ) + self._implement_property_get (CLIENT_HANDLER, + { "Capabilities": self._get_capabilities } ) + + def _get_interfaces(self): + return dbus.Array(self._interfaces, signature='s') + + def _get_filters(self): + return dbus.Array ([ + { CHANNEL_INTERFACE + ".ChannelType": CHANNEL_TYPE_CALL, + CHANNEL_INTERFACE + ".TargetHandleType": + CONNECTION_HANDLE_TYPE_CONTACT, + CALL_INITIAL_AUDIO: True, + }, + { CHANNEL_INTERFACE + ".ChannelType": CHANNEL_TYPE_CALL, + CHANNEL_INTERFACE + ".TargetHandleType": + CONNECTION_HANDLE_TYPE_CONTACT, + CALL_INITIAL_VIDEO: True, + }, + { CHANNEL_INTERFACE + ".ChannelType": CHANNEL_TYPE_CALL, + CHANNEL_INTERFACE + ".TargetHandleType": + CONNECTION_HANDLE_TYPE_ROOM, + CALL_INITIAL_AUDIO: True, + }, + { CHANNEL_INTERFACE + ".ChannelType": CHANNEL_TYPE_CALL, + CHANNEL_INTERFACE + ".TargetHandleType": + CONNECTION_HANDLE_TYPE_ROOM, + CALL_INITIAL_VIDEO: True, + } + ], + signature='a{sv}') + + def _get_capabilities(self): + return dbus.Array ([ + CHANNEL_TYPE_CALL + '/gtalk-p2p', + CHANNEL_TYPE_CALL + '/ice-udp', + CHANNEL_TYPE_CALL + '/video/h264', + ], signature='s') + + def do_handle_call_channel (self, requests, bus, conn, channel, properties): + cchannel = CallChannel(self.bus, conn, channel, properties) + cchannel.accept() + + @dbus.service.method(dbus_interface=CLIENT_HANDLER, + in_signature='ooa(oa{sv})aota{sv}', + async_callbacks= ('_success', '_error')) + def HandleChannels(self, account, connection, channels, + requests, time, info, _success, _error): + + conn = telepathy.client.Connection (connection[1:].replace('/','.'), + connection) + # Assume there can be only one + (channel, properties) = channels[0] + + _success() + self.do_handle_call_channel (requests, + self.bus, conn, channel, properties); + +if __name__ == '__main__': + gobject.threads_init() + loop = gobject.MainLoop() + CallHandler(dbus.SessionBus()) + loop.run() diff --git a/telepathy-farstream/examples/python/callui.py b/telepathy-farstream/examples/python/callui.py new file mode 100644 index 000000000..9e7558f6c --- /dev/null +++ b/telepathy-farstream/examples/python/callui.py @@ -0,0 +1,285 @@ +#!/usr/bin/env python +# +# callui.py +# Copyright (C) 2008-2010 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 + +import gobject +gobject.threads_init() + +import pygtk +import gtk + +gtk.gdk.threads_init() + +import dbus +from dbus.mainloop.glib import DBusGMainLoop +DBusGMainLoop(set_as_default=True) + +import sys +import time + +from telepathy.interfaces import * +from telepathy.constants import * + +from constants import * + +class CallChannelRequest: + def __init__ (self, bus, account_path, contact, + preferred_handler = "", audio = True, video = False, + calltype = HANDLE_TYPE_CONTACT): + self.bus = bus + self.cd = bus.get_object (CHANNEL_DISPATCHER, + '/' + CHANNEL_DISPATCHER.replace('.', '/')) + + props = { + CHANNEL_INTERFACE + ".ChannelType": CHANNEL_TYPE_CALL, + CHANNEL_INTERFACE + ".TargetHandleType": calltype, + CHANNEL_INTERFACE + ".TargetID": contact, + } + + if audio: + props[CHANNEL_TYPE_CALL + ".InitialAudio"] = True + if video: + props[CHANNEL_TYPE_CALL + ".InitialVideo"] = True + + self.request_path = req_path = self.cd.CreateChannel(account_path, + props, + 0, + preferred_handler, + dbus_interface = CHANNEL_DISPATCHER) + + self.req = self.bus.get_object (CHANNEL_DISPATCHER, req_path) + self.req.connect_to_signal("Failed", self.req_failed) + self.req.connect_to_signal("Succeeded", self.req_succeeded) + self.req.Proceed(dbus_interface = CHANNEL_REQUEST) + + def req_failed(self, error, message): + print "FAILURE: %s (%s)"% (error, message) + + def req_succeeded(self): + pass + +class Account: + CALL_CLASS = { + CHANNEL_INTERFACE + '.ChannelType': CHANNEL_TYPE_CALL, + CHANNEL_INTERFACE + '.TargetHandleType': HANDLE_TYPE_CONTACT + } + + def __init__(self, bus, path): + self.bus = bus + self.path = path + self.obj = bus.get_object (ACCOUNT_MANAGER, path) + self.properties = self.obj.GetAll (ACCOUNT, + dbus_interface=dbus.PROPERTIES_IFACE) + + def get_path(self): + return self.path + + def name(self): + return self.properties["DisplayName"] + + def has_connection(self): + return self.properties["Connection"] != "/" + + def get_contacts(self): + path = self.properties["Connection"] + if path == "/": + return [] + + conn = self.bus.get_object (path[1:].replace("/","."), path) + yours, channel, properties = conn.EnsureChannel ( + { CHANNEL_INTERFACE + ".ChannelType": CHANNEL_TYPE_CONTACT_LIST, + CHANNEL_INTERFACE + ".TargetHandleType": HANDLE_TYPE_LIST, + CHANNEL_INTERFACE + ".TargetID": "subscribe" + }, + dbus_interface = CONNECTION_INTERFACE_REQUESTS + ) + + subscribe = self.bus.get_object (conn.bus_name, channel) + members = subscribe.Get(CHANNEL_INTERFACE_GROUP, "Members", + dbus_interface = dbus.PROPERTIES_IFACE) + + caps = conn.GetContactCapabilities (members, + dbus_interface = CONNECTION_INTERFACE_CONTACT_CAPABILITIES) + members = caps.keys() + + for k, v in caps.iteritems(): + for c in v: + if c[0][CHANNEL_TYPE] == CHANNEL_TYPE_CALL: + break + else: + members.remove (k) + + attributes = conn.GetContactAttributes ( + dbus.Array(members, signature="u"), + dbus.Array([], signature="s"), + True) + + return map (lambda v: v[CONNECTION + "/contact-id"], + attributes.itervalues()) + + def supports_calls(self): + path = self.properties["Connection"] + if path == "/": + return False + + conn = self.bus.get_object (path[1:].replace("/","."), path) + classes = conn.Get (CONNECTION_INTERFACE_REQUESTS, + 'RequestableChannelClasses', dbus_interface=dbus.PROPERTIES_IFACE) + + return len ([c for c in classes if c[0] == self.CALL_CLASS]) > 0 + +class UI(gtk.Window): + WIDTH=240 + HEIGHT=-1 + def __init__ (self, bus): + gtk.Window.__init__(self) + self.connect('destroy', lambda x: gtk.main_quit()) + self.set_resizable(False) + self.set_size_request(self.WIDTH, self.HEIGHT) + + vbox = gtk.VBox(False, 3) + self.add(vbox) + + # call type combo box + self.type_store = gtk.ListStore ( + gobject.TYPE_STRING, + gobject.TYPE_UINT) + + self.type_store.append (("1-to-1", CONNECTION_HANDLE_TYPE_CONTACT)) + self.type_store.append (("Conference", + CONNECTION_HANDLE_TYPE_ROOM)) + + self.type_combo = combobox = gtk.ComboBox (self.type_store) + vbox.pack_start(combobox, False) + + renderer = gtk.CellRendererText() + combobox.pack_start(renderer, True) + combobox.set_attributes(renderer, text=0) + combobox.set_active (0) + + # account combo box + self.store = gtk.ListStore (gobject.TYPE_STRING, + gobject.TYPE_BOOLEAN, + gobject.TYPE_PYOBJECT) + self.store.set_sort_func(0, + (lambda m, i0, i1: + { True: -1, False: 1}[m.get(i0, 0) < m.get(i1, 0)] )) + self.store.set_sort_column_id(0, gtk.SORT_ASCENDING) + + f = self.store.filter_new() + f.set_visible_func(self.filter_visible) + self.account_combo = combobox = gtk.ComboBox(f) + vbox.pack_start(combobox, False) + + renderer = gtk.CellRendererText() + combobox.pack_start(renderer, True) + combobox.set_attributes(renderer, text=0) + combobox.connect('changed', self.account_selected) + + # contact entry box + self.contact_store = gtk.ListStore(gobject.TYPE_STRING) + + completion = gtk.EntryCompletion () + completion.set_model(self.contact_store) + completion.set_text_column(0) + + self.contact_store.set_sort_func(0, self.contact_sort) + self.contact_store.set_sort_column_id(0, gtk.SORT_ASCENDING) + + self.contact_combo = combobox = gtk.ComboBoxEntry(self.contact_store) + combobox.get_child().set_completion(completion) + + vbox.pack_start(combobox, False) + + bbox = gtk.HButtonBox() + bbox.set_layout(gtk.BUTTONBOX_END) + vbox.pack_start(bbox, True, False, 3) + + call = gtk.Button("Audio call") + call.connect("clicked", self.start_call) + bbox.add(call) + + call = gtk.Button("Video call") + call.connect("clicked", + lambda button: self.start_call(button, video=True)) + bbox.add(call) + + self.show_all() + + self.bus = bus + self.account_mgr = bus.get_object (ACCOUNT_MANAGER, + '/' + ACCOUNT_MANAGER.replace('.', '/')) + self.get_accounts() + + def start_call(self, button, audio=True, video=False): + i = self.type_combo.get_active_iter() + (calltype, ) = self.type_combo.get_model().get(i, 1) + + i = self.account_combo.get_active_iter() + (account, ) = self.account_combo.get_model().get(i, 2) + + contact = self.contact_combo.get_active_text().strip() + + print "* starting %s call" % ('video' if video else 'audio') + CallChannelRequest (self.bus, account.path, contact, + audio=audio, video=video, calltype=calltype) + + def contact_sort (self, model, i0, i1): + if model.get(i0, 0)[0] < model.get(i1, 0)[0]: + return -1 + else: + return 0 + + def filter_visible(self, model, titer): + return model.get(titer, 1)[0] + + def account_selected (self, combobox): + iter = combobox.get_active_iter() + if iter == None: + return None + + (account,) = combobox.get_model().get(iter, 2) + + self.contact_store.clear() + + map(lambda x: self.contact_store.insert (0, (x,)), + account.get_contacts()) + + def bail (self, *args): + print "BAILING" + print args + gtk.main_quit() + + def got_accounts(self, accounts): + for x in accounts: + a = Account(self.bus, x) + if a.supports_calls(): + self.store.insert(0, (a.name(), a.has_connection(), a)) + self.account_combo.set_active(0) + + def get_accounts (self): + self.account_mgr.Get(ACCOUNT_MANAGER, "ValidAccounts", + dbus_interface = dbus.PROPERTIES_IFACE, + reply_handler = self.got_accounts, + error_handler = self.bail) + +if __name__ == '__main__': + bus = dbus.SessionBus() + + UI(bus) + gtk.main() diff --git a/telepathy-farstream/examples/python/constants.py b/telepathy-farstream/examples/python/constants.py new file mode 100644 index 000000000..43af8baf2 --- /dev/null +++ b/telepathy-farstream/examples/python/constants.py @@ -0,0 +1,66 @@ +# constants.py +# Copyright (C) 2008-2010 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 + +from telepathy.interfaces import CHANNEL_INTERFACE + +CHANNEL = CHANNEL_INTERFACE + +CHANNEL_TYPE = CHANNEL + ".ChannelType" +CHANNEL_TYPE_CALL = CHANNEL + ".Type.Call1" +CALL_INITIAL_AUDIO = CHANNEL_TYPE_CALL + '.InitialAudio' +CALL_INITIAL_VIDEO = CHANNEL_TYPE_CALL + '.InitialVideo' +CALL_MUTABLE_CONTENTS = CHANNEL_TYPE_CALL + '.MutableContents' + +CALL_CONTENT = 'org.freedesktop.Telepathy.Call1.Content' +CALL_CONTENT_IFACE_MEDIA = \ + 'org.freedesktop.Telepathy.Call1.Content.Interface.Media' + +CALL_CONTENT_CODECOFFER = \ + 'org.freedesktop.Telepathy.Call1.Content.CodecOffer' + +CALL_STREAM = 'org.freedesktop.Telepathy.Call1.Stream' +CALL_STREAM_IFACE_MEDIA = \ + 'org.freedesktop.Telepathy.Call1.Stream.Interface.Media' + +CALL_STREAM_ENDPOINT = 'org.freedesktop.Telepathy.Call1.Stream.Endpoint' + +STREAM_TRANSPORT_RAW_UDP = 1 +STREAM_TRANSPORT_ICE_UDP = 2 +STREAM_TRANSPORT_GTALK_P2P = 3 +STREAM_TRANSPORT_WLM_2009 = 4 +STREAM_TRANSPORT_SHM = 5 +STREAM_TRANSPORT_MULTICAST = 6 +STREAM_TRANSPOR_DUMMY = 0xff + +CALL_STATE_UNKNOWN = 0 +CALL_STATE_PENDING_INITIATOR = 1 +CALL_STATE_PENDING_RECEIVER = 2 +CALL_STATE_ACCEPTED = 3 +CALL_STATE_ENDED = 4 + +CALL_FLAG_LOCALLY_RINGING = 1 +CALL_FLAG_QUEUED = 2 +CALL_FLAG_LOCALLY_HELD = 4 +CALL_FLAG_FORWARDED = 8 +CALL_FLAG_IN_PROGRESS = 16 +CALL_FLAG_CLEARING = 32 + +CALL_STATE_CHANGE_REASON_UNKNOWN = 0 +CALL_STATE_CHANGE_REASON_REQUESTED = 1 + +CONTENT_PACKETIZATION_RTP = 0 +CONTENT_PACKETIZATION_RAW = 1 diff --git a/telepathy-farstream/examples/python/element-properties b/telepathy-farstream/examples/python/element-properties new file mode 100644 index 000000000..40f706d6e --- /dev/null +++ b/telepathy-farstream/examples/python/element-properties @@ -0,0 +1,62 @@ +# Put the desired properties in the style of +# +# [element name] +# prop1=val1 + +[gstrtpbin] +latency=100 + +[x264enc] +byte-stream=1 +bframes=0 +b-adapt=0 +cabac=0 +dct8x8=0 +bitrate=256 +# tuned for zero latency +tune=0x4 +profile=1 +speed-preset=3 +sliced-threads=false + +[ffenc_h263] +rtp-payload-size=1 + +[theoraenc] +bitrate=256 + +[vp8enc] +bitrate=256000 +max-latency=1 +speed=2 +error-resilient=true + +# Work around bug in the re-timestamp slaving method in +# GStreamer (2 is skew) +[alsasrc] +slave-method=2 + +[osssrc] +slave-method=2 + +[oss4src] +slave-method=2 + +[sunaudiosrc] +slave-method=2 + +[rtph264pay] +config-interval=5 + +[rtppcmupay] +ptime-multiple=20000000 + +[rtppcmapay] +ptime-multiple=20000000 + +[gstrtpjitterbuffer] +do-lost=1 + +[ewh264enc] +profile=baseline +quality=5 diff --git a/telepathy-farstream/examples/python/util.py b/telepathy-farstream/examples/python/util.py new file mode 100644 index 000000000..bbad9c852 --- /dev/null +++ b/telepathy-farstream/examples/python/util.py @@ -0,0 +1,40 @@ +# util.py +# Copyright (C) 2008-2010 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 + +from constants import * + +def call_state_to_s(state): + return { + CALL_STATE_UNKNOWN: 'Unknown', + CALL_STATE_PENDING_INITIATOR: 'Pending Initiator', + CALL_STATE_PENDING_RECEIVER: 'Pending Receiver', + CALL_STATE_ACCEPTED: 'Accepted', + CALL_STATE_ENDED: 'Ended' + }[state] + +def call_flags_to_s(flags): + flag_strs = { + CALL_FLAG_LOCALLY_RINGING: 'Locally Ringing', + CALL_FLAG_QUEUED: 'Queued', + CALL_FLAG_LOCALLY_HELD: 'Locally Held', + CALL_FLAG_FORWARDED: 'Forwarded', + CALL_FLAG_IN_PROGRESS: 'In Progress', + CALL_FLAG_CLEARING: 'Clearing' + } + + return ' | '.join([ '%s (%d)' % (flag_strs[i], i) + for i in flag_strs.keys() if flags & i ]) or 'None' diff --git a/telepathy-farstream/telepathy-farstream-1.pc.in b/telepathy-farstream/telepathy-farstream-1.pc.in new file mode 100644 index 000000000..ed500a699 --- /dev/null +++ b/telepathy-farstream/telepathy-farstream-1.pc.in @@ -0,0 +1,11 @@ +prefix=@prefix@ +exec_prefix=@exec_prefix@ +libdir=@libdir@ +includedir=@includedir@ + +Name: Telepathy-Farstream-1 +Description: Library implementing the Telepathy Call API using Farstream +Version: @VERSION@ +Requires.private: dbus-glib-1 >= 0.73, glib-2.0 >= 2.10, gobject-2.0 >= 2.10, farstream-0.2, telepathy-glib-1 >= 0.99.1, telepathy-glib-1-dbus >= 0.99.1, gstreamer-1.0 +Libs: -L${libdir} -ltelepathy-farstream-1 +Cflags: -I${includedir}/telepathy-farstream-1 diff --git a/telepathy-farstream/telepathy-farstream-uninstalled-1.pc.in b/telepathy-farstream/telepathy-farstream-uninstalled-1.pc.in new file mode 100644 index 000000000..b6290e4d2 --- /dev/null +++ b/telepathy-farstream/telepathy-farstream-uninstalled-1.pc.in @@ -0,0 +1,11 @@ +prefix= +exec_prefix= +abs_top_srcdir=@abs_top_srcdir@ +abs_top_builddir=@abs_top_builddir@ + +Name: Telepathy-Farstream-1 +Description: Library implementing the Telepathy Call API using Farstream +Version: @VERSION@ +Requires.private: dbus-glib-1 >= 0.73, glib-2.0 >= 2.10, gobject-2.0 >= 2.10, farstream-0.2 +Libs: ${abs_top_builddir}/telepathy-farstream/libtelepathy-farstream-1.la +Cflags: -I${abs_top_srcdir} -I${abs_top_builddir} diff --git a/telepathy-farstream/telepathy-farstream.h b/telepathy-farstream/telepathy-farstream.h new file mode 100644 index 000000000..4a24fb275 --- /dev/null +++ b/telepathy-farstream/telepathy-farstream.h @@ -0,0 +1,27 @@ +/* + * telepathy-farstream.h - Source for TfCallChannel + * 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 + */ + +#ifndef __TF_FARSTREAM_H_ +#define __TF_FARSTREAM_H_ + +#include <telepathy-farstream/channel.h> +#include <telepathy-farstream/content.h> + +#endif /* __TF_FARSTREAM_H__ */ + diff --git a/telepathy-farstream/utils.h b/telepathy-farstream/utils.h new file mode 100644 index 000000000..fbf5fcc5d --- /dev/null +++ b/telepathy-farstream/utils.h @@ -0,0 +1,62 @@ +#ifndef __UTILS_H__ +#define __UTILS_H__ + +/* + * tp_media_type_to_fs: + * @type: A Telepathy Media Stream Type + * + * Converts a Telepathy Media Stream Type to the Farstream equivalent + * + * Return: A Farstream Stream Type + */ + +static inline FsMediaType +tp_media_type_to_fs (TpMediaStreamType type) +{ + switch (type) + { + case TP_MEDIA_STREAM_TYPE_AUDIO: + return FS_MEDIA_TYPE_AUDIO; + case TP_MEDIA_STREAM_TYPE_VIDEO: + return FS_MEDIA_TYPE_VIDEO; + default: + g_return_val_if_reached(0); + } +} + +static inline TpMediaStreamDirection +fsdirection_to_tpdirection (FsStreamDirection dir) +{ + switch (dir) { + case FS_DIRECTION_NONE: + return TP_MEDIA_STREAM_DIRECTION_NONE; + case FS_DIRECTION_SEND: + return TP_MEDIA_STREAM_DIRECTION_SEND; + case FS_DIRECTION_RECV: + return TP_MEDIA_STREAM_DIRECTION_RECEIVE; + case FS_DIRECTION_BOTH: + return TP_MEDIA_STREAM_DIRECTION_BIDIRECTIONAL; + default: + g_assert_not_reached (); + } +} + +static inline FsStreamDirection +tpdirection_to_fsdirection (TpMediaStreamDirection dir) +{ + switch (dir) { + case TP_MEDIA_STREAM_DIRECTION_NONE: + return FS_DIRECTION_NONE; + case TP_MEDIA_STREAM_DIRECTION_SEND: + return FS_DIRECTION_SEND; + case TP_MEDIA_STREAM_DIRECTION_RECEIVE: + return FS_DIRECTION_RECV; + case TP_MEDIA_STREAM_DIRECTION_BIDIRECTIONAL: + return FS_DIRECTION_BOTH; + default: + g_assert_not_reached (); + } +} + + +#endif /* __UTILS_H__ */ |