Re: FsAppTransmitter / dummy call in nullprpl

David Woodhouse <[email protected]> Sat, 03 Feb 2018 00:45:19 +0000
Newsgroups gmane.comp.gnome.gaim.devel,gmane.comp.telephony.farsight.devel
Message-ID <[email protected]>
On Fri, 2018-02-02 at 13:31 +0000, David Woodhouse wrote:
> Before I do the step of faking RTP myself, I started with purely adding
> rtpopuspay/rtpopusdepay in my pipelines and switching to
> fsrtpconference. And now I'm back to "I am clueless and I don't know
> why this doesn't work mode" and asking for help again... :)

Some progress... attached are a couple of patches against the Pidgin
repository. The first adds fs-app-transmitter and a hack in the null
PRPL which makes a dummy call, and works basically like the code in my
Chime PRPL.

The second converts it to use RTP, and I can get it working with a
couple of hacks.

Firstly, I need to *remove* libgstsrtp.so from my system. Unless I do
that, it fails to work:

0:00:15.265806297  5308 0x5573bd72c830 DEBUG               GST_CAPS gstpad.c:2215:gst_pad_link_check_compatible_unlocked:<tee1:src_1> src caps application/x-srtp
0:00:15.265814903  5308 0x5573bd72c830 DEBUG               GST_CAPS gstpad.c:2217:gst_pad_link_check_compatible_unlocked:<valve1:sink> sink caps application/x-rtp, media=(string)audio, payload=(int)[ 96, 127 ], clock-rate=(int)48000, encoding-name=(string){ OPUS, X-GST-OPUS-DRAFT-SPITTKA-00 }
0:00:15.265878067  5308 0x5573bd72c830 INFO                GST_PADS gstpad.c:2464:gst_pad_link_full: link between tee1:src_1 and valve1:sink failed: no common format

Secondly, I have to upgrade to the latest farstream (I've been testing
on Ubuntu 16.04 with farstream 0.2.7). Unless I do that, I can't find
any way to make OPUS work. Whatever I've put in fs-codecs.conf and
whatever I've put in farstream-{send,recv}-profile properties, I
haven't managed to get it to succeed.

After I've solved those, I'll turn my attention to the fact that
although the nullprpl works with those caveats, I still haven't got my
own code with the "real" GstAppSrc/GstAppSink pipeline working. I get
outbound audio from my mic to the network, but I never see a farstream-
recv-codecs-changed message, and the inbound pipeline isn't set up.

_______________________________________________
Devel mailing list
[email protected]
https://pidgin.im/cgi-bin/mailman/listinfo/devel
0002-Switch-to-RTP.patch (text/x-patch, 2.4 KB)
From 1f8c566bb506e5db5281c9b23cab3cc7ab22dd21 Mon Sep 17 00:00:00 2001
From: David Woodhouse <[email protected]>
Date: Fri, 2 Feb 2018 22:55:26 +0000
Subject: [PATCH 2/2] Switch to RTP

---
 libpurple/protocols/null/nullprpl.c | 13 ++++++++-----
 1 file changed, 8 insertions(+), 5 deletions(-)

diff --git a/libpurple/protocols/null/nullprpl.c b/libpurple/protocols/null/nullprpl.c
index 7e6cabe..b98c34a 100644
--- a/libpurple/protocols/null/nullprpl.c
+++ b/libpurple/protocols/null/nullprpl.c
@@ -220,17 +220,17 @@ static void nullprpl_dummy_call(PurplePluginAction *action)
   PurpleMediaManager *mgr = purple_media_manager_get();
   PurpleMedia *media = purple_media_manager_create_media(mgr,
 							 acct,
-							 "fsrawconference",
+							 "fsrtpconference",
 							 name,
 							 TRUE);
-  const gchar *caps = "audio/x-raw,format=(string)S16LE,layout=(string)interleaved,channels=(int)1";
+  const gchar *caps = "application/x-rtp,media=(string)audio,payload=(int)96,encoding-name=(string)OPUS";
 
   purple_media_add_stream(media, sess, name,
 			  PURPLE_MEDIA_AUDIO, TRUE,
 			  "app", 0, NULL);
   /* Without the capsfilter (even though there's another capsfilter almost immediately after it in the pipeline, it doesn't work */
-  srcpipe = g_strdup_printf("audiotestsrc format=time ! capsfilter caps=%s", caps);
-  sinkpipe = g_strdup_printf("audioconvert ! filesink location=/tmp/pidgin.s16 async=false");
+  srcpipe = g_strdup_printf("audiotestsrc ! opusenc ! rtpopuspay ! capsfilter caps=%s ", caps);
+  sinkpipe = g_strdup_printf("capsfilter caps=%s ! rtpopusdepay ! opusdec ! filesink location=/tmp/pidgin.s16 async=false", caps);
 
   cand = purple_media_candidate_new(NULL, 1,
 				    PURPLE_MEDIA_CANDIDATE_TYPE_HOST,
@@ -242,7 +242,10 @@ static void nullprpl_dummy_call(PurplePluginAction *action)
 
   cands = g_list_append (NULL, cand);
   codecs = g_list_append(NULL,
-				purple_media_codec_new(1, caps, PURPLE_MEDIA_AUDIO, 0));
+				purple_media_codec_new(96, "OPUS", PURPLE_MEDIA_AUDIO, 0));
+  purple_media_codec_add_optional_parameter(codecs->data, "farstream-recv-profile", "rtpopusdepay payload=96 ! opusdec");
+  purple_media_codec_add_optional_parameter(codecs->data, "farstream-send-profile", "opusenc  bitrate=16000 bitrate-type=vbr ! rtpopuspay payload=96");
+
   purple_media_add_remote_candidates(media, sess, name, cands);
   purple_media_set_remote_codecs(media, sess, name, codecs);
 
-- 
2.7.4
0001-Dummy-conference-hack-to-nullprpl.patch (text/x-patch, 52.5 KB)
From becbea54ea43c09698abc3a9264a08a246d73b61 Mon Sep 17 00:00:00 2001
From: David Woodhouse <[email protected]>
Date: Thu, 2 Nov 2017 14:55:26 +0000
Subject: [PATCH 1/2] Dummy conference hack to nullprpl

---
 configure.ac                                       |   2 +
 libpurple/media/backend-fs2.c                      |  12 +
 libpurple/protocols/null/Makefile.am               |  14 +-
 .../protocols/null/fs-app-stream-transmitter.c     | 458 ++++++++++
 .../protocols/null/fs-app-stream-transmitter.h     |  98 +++
 libpurple/protocols/null/fs-app-transmitter.c      | 924 +++++++++++++++++++++
 libpurple/protocols/null/fs-app-transmitter.h      | 124 +++
 libpurple/protocols/null/nullprpl.c                |  60 +-
 8 files changed, 1688 insertions(+), 4 deletions(-)
 create mode 100644 libpurple/protocols/null/fs-app-stream-transmitter.c
 create mode 100644 libpurple/protocols/null/fs-app-stream-transmitter.h
 create mode 100644 libpurple/protocols/null/fs-app-transmitter.c
 create mode 100644 libpurple/protocols/null/fs-app-transmitter.h

diff --git a/configure.ac b/configure.ac
index 71b9f7b..6ccf603 100644
--- a/configure.ac
+++ b/configure.ac
@@ -871,6 +871,8 @@ if test "x$enable_farstream" != "xno"; then
 		PKG_CHECK_MODULES(FARSTREAM, [farstream-0.2 >= 0.2.7], [
 			AC_SUBST(FARSTREAM_CFLAGS)
 			AC_SUBST(FARSTREAM_LIBS)
+			fsplugindir="$($PKG_CONFIG --variable=pluginsdir farstream-0.2)"
+			AC_SUBST(fsplugindir, ${fsplugindir})
 		], [
 		        enable_farstream="no"
 		])
diff --git a/libpurple/media/backend-fs2.c b/libpurple/media/backend-fs2.c
index 47ba4bc..2edfc84 100644
--- a/libpurple/media/backend-fs2.c
+++ b/libpurple/media/backend-fs2.c
@@ -994,6 +994,8 @@ gst_handle_message_element(GstBus *bus, GstMessage *msg,
 	static guint level_id = 0;
 	const GstStructure *structure = gst_message_get_structure(msg);
 
+	printf("%s %s\n", __func__, gst_structure_get_name(structure));
+	
 	if (level_id == 0)
 		level_id = g_signal_lookup("level", PURPLE_TYPE_MEDIA);
 
@@ -1588,6 +1590,7 @@ stream_info_cb(PurpleMedia *media, PurpleMediaInfoType type,
 		gchar *sid, gchar *name, gboolean local,
 		PurpleMediaBackendFs2 *self)
 {
+	printf("stream_info_cb %d\n", type);
 	if (type == PURPLE_MEDIA_INFO_ACCEPT && sid != NULL && name != NULL) {
 		PurpleMediaBackendFs2Stream *stream =
 				get_stream(self, sid, name);
@@ -2868,6 +2871,15 @@ purple_media_backend_fs2_send_dtmf(PurpleMediaBackend *self,
 	PurpleMediaBackendFs2Session *session;
 	FsDTMFEvent event;
 
+		printf("DUMP DOT FILE\n");
+		GstElement *pipeline;
+		PurpleMediaBackendFs2Private *priv =
+				PURPLE_MEDIA_BACKEND_FS2_GET_PRIVATE(self);
+
+		pipeline = purple_media_manager_get_pipeline(
+				purple_media_get_manager(priv->media));
+		GST_DEBUG_BIN_TO_DOT_FILE(GST_BIN(pipeline), GST_DEBUG_GRAPH_SHOW_ALL, "conference graph");
+
 	g_return_val_if_fail(PURPLE_IS_MEDIA_BACKEND_FS2(self), FALSE);
 
 	session = get_session(PURPLE_MEDIA_BACKEND_FS2(self), sess_id);
diff --git a/libpurple/protocols/null/Makefile.am b/libpurple/protocols/null/Makefile.am
index 3bc76ee..07cba59 100644
--- a/libpurple/protocols/null/Makefile.am
+++ b/libpurple/protocols/null/Makefile.am
@@ -14,10 +14,22 @@ libnull_la_LDFLAGS = -module -avoid-version
 st =
 pkg_LTLIBRARIES    = libnull.la
 libnull_la_SOURCES = $(NULLSOURCES)
-libnull_la_LIBADD  = $(GLIB_LIBS)
+libnull_la_LIBADD  = $(GLIB_LIBS) $(FARSTREAM_LIBS)
+
+fsplugin_LTLIBRARIES = libapp-transmitter.la
+
+libapp_transmitter_la_CPPFLAGS = $(FARSTREAM_CFLAGS)
+
+libapp_transmitter_la_SOURCES = fs-app-stream-transmitter.c fs-app-stream-transmitter.h \
+                                        fs-app-transmitter.c  fs-app-transmitter.h
+
+libapp_transmitter_la_LDFLAGS = -module -avoid-version -no-undefined
+
+libapp_transmitter_la_LIBADD = $(FARSTREAM_LIBS)
 
 AM_CPPFLAGS = \
 	-I$(top_srcdir)/libpurple \
 	-I$(top_builddir)/libpurple \
+	$(FARSTREAM_CFLAGS) \
 	$(GLIB_CFLAGS) \
 	$(DEBUG_CFLAGS)
diff --git a/libpurple/protocols/null/fs-app-stream-transmitter.c b/libpurple/protocols/null/fs-app-stream-transmitter.c
new file mode 100644
index 0000000..3ffa934
--- /dev/null
+++ b/libpurple/protocols/null/fs-app-stream-transmitter.c
@@ -0,0 +1,458 @@
+/*
+ * Farstream - Farstream Shared Memory Stream Transmitter
+ *
+ * Copyright 2009 Collabora Ltd.
+ *  @author: Olivier Crete <[email protected]>
+ * Copyright 2009 Nokia Corp.
+ *
+ * fs-app-stream-transmitter.c - A Farstream GstAppShared memory stream transmitter
+ *
+ * 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 Street, Fifth Floor, Boston, MA  02110-1301 USA
+ */
+
+
+/**
+ * SECTION:fs-app-stream-transmitter
+ * @short_description: A stream transmitter object for application pipelines
+ *
+ * The name of this transmitter is "app".
+ *
+ * This transmitter is meant to send and receive the data via a pipeline
+ * described by the application. The pipelines will typically start/end
+ * in an appsrc/appsink respectively, but a full pipeline description is
+ * required because codec and other conversions may be necessary when
+ * used with #FsRawConference, which does not handle those for itself.
+ *
+ * The pipelines for send (e.g. to appsink) and receive (e.g. from appsrc)
+ * are provided in the "ip" and "username" properties of the #FsCandidate,
+ * respectively. These pipelines are instantiated with
+ * gst_parse_bin_from_description().
+ *
+ * Typically the application would give a unique name to the appsrc and
+ * appsink elements, so that gst_bin_get_by_name() can be used to find
+ * and interact with them.
+ */
+
+#ifdef HAVE_CONFIG_H
+#include "config.h"
+#endif
+
+#include "fs-app-stream-transmitter.h"
+#include "fs-app-transmitter.h"
+
+#include <farstream/fs-candidate.h>
+#include <farstream/fs-conference.h>
+
+#include <gst/gst.h>
+
+#include <glib/gstdio.h>
+
+#include <string.h>
+#include <sys/types.h>
+
+#ifdef HAVE_UNISTD_H
+# include <unistd.h>
+#endif
+
+#include <stdlib.h>
+
+GST_DEBUG_CATEGORY_EXTERN (fs_app_transmitter_debug);
+#define GST_CAT_DEFAULT fs_app_transmitter_debug
+
+/* Signals */
+enum
+{
+  LAST_SIGNAL
+};
+
+/* props */
+enum
+{
+  PROP_0,
+  PROP_SENDING,
+};
+
+struct _FsAppStreamTransmitterPrivate
+{
+  /* We don't actually hold a ref to this,
+   * But since our parent FsStream can not exist without its parent
+   * FsSession, we should be safe
+   */
+  FsAppTransmitter *transmitter;
+
+  GMutex mutex;
+
+  /* Protected by the mutex */
+  gboolean sending;
+
+  /* Protected by the mutex */
+  FsCandidate **candidates;
+
+  AppSrc **app_src;
+  AppSink **app_sink;
+};
+
+#define FS_APP_STREAM_TRANSMITTER_GET_PRIVATE(o)  \
+  (G_TYPE_INSTANCE_GET_PRIVATE ((o), FS_TYPE_APP_STREAM_TRANSMITTER, \
+                                FsAppStreamTransmitterPrivate))
+
+#define FS_APP_STREAM_TRANSMITTER_LOCK(s) \
+  g_mutex_lock (&(s)->priv->mutex)
+#define FS_APP_STREAM_TRANSMITTER_UNLOCK(s) \
+  g_mutex_unlock (&(s)->priv->mutex)
+
+static void fs_app_stream_transmitter_class_init (FsAppStreamTransmitterClass *klass);
+static void fs_app_stream_transmitter_init (FsAppStreamTransmitter *self);
+static void fs_app_stream_transmitter_dispose (GObject *object);
+static void fs_app_stream_transmitter_finalize (GObject *object);
+
+static void fs_app_stream_transmitter_get_property (GObject *object,
+                                                guint prop_id,
+                                                GValue *value,
+                                                GParamSpec *pspec);
+static void fs_app_stream_transmitter_set_property (GObject *object,
+                                                guint prop_id,
+                                                const GValue *value,
+                                                GParamSpec *pspec);
+
+static gboolean fs_app_stream_transmitter_force_remote_candidates (
+    FsStreamTransmitter *streamtransmitter, GList *candidates,
+    GError **error);
+
+static gboolean
+fs_app_stream_transmitter_add_sink (FsAppStreamTransmitter *self,
+    FsCandidate *candidate, GError **error);
+
+
+static GObjectClass *parent_class = NULL;
+// static guint signals[LAST_SIGNAL] = { 0 };
+
+static GType type = 0;
+
+GType
+fs_app_stream_transmitter_get_type (void)
+{
+  return type;
+}
+
+GType
+fs_app_stream_transmitter_register_type (FsPlugin *module G_GNUC_UNUSED)
+{
+  static const GTypeInfo info = {
+    sizeof (FsAppStreamTransmitterClass),
+    NULL,
+    NULL,
+    (GClassInitFunc) fs_app_stream_transmitter_class_init,
+    NULL,
+    NULL,
+    sizeof (FsAppStreamTransmitter),
+    0,
+    (GInstanceInitFunc) fs_app_stream_transmitter_init
+  };
+
+  type = g_type_register_static (FS_TYPE_STREAM_TRANSMITTER,
+      "FsAppStreamTransmitter", &info, 0);
+
+  return type;
+}
+
+static void
+fs_app_stream_transmitter_class_init (FsAppStreamTransmitterClass *klass)
+{
+  GObjectClass *gobject_class = (GObjectClass *) klass;
+  FsStreamTransmitterClass *streamtransmitterclass =
+    FS_STREAM_TRANSMITTER_CLASS (klass);
+
+  parent_class = g_type_class_peek_parent (klass);
+
+  gobject_class->set_property = fs_app_stream_transmitter_set_property;
+  gobject_class->get_property = fs_app_stream_transmitter_get_property;
+
+  streamtransmitterclass->force_remote_candidates =
+    fs_app_stream_transmitter_force_remote_candidates;
+
+  g_object_class_override_property (gobject_class, PROP_SENDING, "sending");
+
+  gobject_class->dispose = fs_app_stream_transmitter_dispose;
+  gobject_class->finalize = fs_app_stream_transmitter_finalize;
+
+  g_type_class_add_private (klass, sizeof (FsAppStreamTransmitterPrivate));
+}
+
+static void
+fs_app_stream_transmitter_init (FsAppStreamTransmitter *self)
+{
+  /* member init */
+  self->priv = FS_APP_STREAM_TRANSMITTER_GET_PRIVATE (self);
+
+  self->priv->sending = TRUE;
+
+  g_mutex_init (&self->priv->mutex);
+}
+
+static void
+fs_app_stream_transmitter_dispose (GObject *object)
+{
+  FsAppStreamTransmitter *self = FS_APP_STREAM_TRANSMITTER (object);
+  gint c; /* component_id */
+
+  for (c = 1; c <= self->priv->transmitter->components; c++)
+  {
+    if (self->priv->app_src[c])
+    {
+      fs_app_transmitter_check_app_src (self->priv->transmitter,
+          self->priv->app_src[c], NULL);
+    }
+    self->priv->app_src[c] = NULL;
+
+    if (self->priv->app_sink[c])
+    {
+      fs_app_transmitter_check_app_sink (self->priv->transmitter,
+          self->priv->app_sink[c], NULL);
+    }
+    self->priv->app_sink[c] = NULL;
+  }
+
+  parent_class->dispose (object);
+}
+
+static void
+fs_app_stream_transmitter_finalize (GObject *object)
+{
+  FsAppStreamTransmitter *self = FS_APP_STREAM_TRANSMITTER (object);
+
+  g_free (self->priv->app_src);
+  g_free (self->priv->app_sink);
+  g_mutex_clear (&self->priv->mutex);
+
+  parent_class->finalize (object);
+}
+
+static void
+fs_app_stream_transmitter_get_property (GObject *object,
+                                           guint prop_id,
+                                           GValue *value,
+                                           GParamSpec *pspec)
+{
+  FsAppStreamTransmitter *self = FS_APP_STREAM_TRANSMITTER (object);
+
+  switch (prop_id)
+  {
+    case PROP_SENDING:
+      FS_APP_STREAM_TRANSMITTER_LOCK (self);
+      g_value_set_boolean (value, self->priv->sending);
+      FS_APP_STREAM_TRANSMITTER_UNLOCK (self);
+      break;
+    default:
+      G_OBJECT_WARN_INVALID_PROPERTY_ID (object, prop_id, pspec);
+      break;
+  }
+}
+
+static void
+fs_app_stream_transmitter_set_property (GObject *object,
+                                           guint prop_id,
+                                           const GValue *value,
+                                           GParamSpec *pspec)
+{
+  FsAppStreamTransmitter *self = FS_APP_STREAM_TRANSMITTER (object);
+
+  switch (prop_id) {
+    case PROP_SENDING:
+      FS_APP_STREAM_TRANSMITTER_LOCK (self);
+      self->priv->sending = g_value_get_boolean (value);
+      if (self->priv->app_sink[1])
+        fs_app_transmitter_sink_set_sending (self->priv->transmitter,
+            self->priv->app_sink[1], self->priv->sending);
+      FS_APP_STREAM_TRANSMITTER_UNLOCK (self);
+      break;
+    default:
+      G_OBJECT_WARN_INVALID_PROPERTY_ID (object, prop_id, pspec);
+      break;
+  }
+}
+
+static gboolean
+fs_app_stream_transmitter_build (FsAppStreamTransmitter *self,
+  GError **error)
+{
+  self->priv->app_src = g_new0 (AppSrc *,
+      self->priv->transmitter->components + 1);
+  self->priv->app_sink = g_new0 (AppSink *,
+      self->priv->transmitter->components + 1);
+
+  return TRUE;
+}
+
+static void
+got_buffer_func (GstBuffer *buffer, guint component, gpointer data)
+{
+  FsAppStreamTransmitter *self = FS_APP_STREAM_TRANSMITTER_CAST (data);
+
+  g_signal_emit_by_name (self, "known-source-packet-received", component,
+      buffer);
+}
+
+static void ready_cb (guint component, gchar *pipeline, gpointer data)
+{
+  FsAppStreamTransmitter *self = FS_APP_STREAM_TRANSMITTER_CAST (data);
+  FsCandidate *candidate = fs_candidate_new ("", component,
+      FS_CANDIDATE_TYPE_HOST, FS_NETWORK_PROTOCOL_UDP, pipeline, 0);
+
+  printf ("Emitting new local candidate with pipeline %s: %p\n", pipeline, candidate);
+
+  g_signal_emit_by_name (self, "new-local-candidate", candidate);
+  g_signal_emit_by_name (self, "local-candidates-prepared");
+
+  fs_candidate_destroy (candidate);
+}
+
+static gboolean
+fs_app_stream_transmitter_add_sink (FsAppStreamTransmitter *self,
+    FsCandidate *candidate, GError **error)
+{
+  if (!candidate->ip || !candidate->ip[0])
+    return TRUE;
+
+  if (self->priv->app_sink[candidate->component_id])
+  {
+    if (fs_app_transmitter_check_app_sink (self->priv->transmitter,
+            self->priv->app_sink[candidate->component_id], candidate->ip))
+      return TRUE;
+    self->priv->app_sink[candidate->component_id] = NULL;
+  }
+
+  self->priv->app_sink[candidate->component_id] =
+    fs_app_transmitter_get_app_sink (self->priv->transmitter,
+        candidate->component_id, candidate->ip, ready_cb, self, error);
+
+  if (self->priv->app_sink[candidate->component_id] == NULL)
+    return FALSE;
+
+  return TRUE;
+}
+
+
+static void
+disconnected_cb (guint component, gint id, gpointer data)
+{
+  FsAppStreamTransmitter *self = data;
+
+  g_signal_emit_by_name (self, "state-changed", component,
+      FS_STREAM_STATE_FAILED);
+}
+
+static gboolean
+fs_app_stream_transmitter_force_remote_candidate (
+    FsAppStreamTransmitter *self, FsCandidate *candidate,
+    GError **error)
+{
+  const gchar *pipeline;
+  if (!fs_app_stream_transmitter_add_sink (self, candidate, error))
+    return FALSE;
+
+  pipeline = candidate->username;
+
+  if (pipeline && pipeline[0])
+  {
+    if (self->priv->app_src[candidate->component_id])
+    {
+      if (fs_app_transmitter_check_app_src (self->priv->transmitter,
+              self->priv->app_src[candidate->component_id], pipeline))
+        return TRUE;
+      self->priv->app_src[candidate->component_id] = NULL;
+    }
+
+    self->priv->app_src[candidate->component_id] =
+      fs_app_transmitter_get_app_src (self->priv->transmitter,
+          candidate->component_id, pipeline, got_buffer_func, disconnected_cb,
+          self, error);
+
+    if (self->priv->app_src[candidate->component_id] == NULL)
+      return FALSE;
+  }
+
+  return TRUE;
+}
+
+/**
+ * fs_app_stream_transmitter_force_remote_candidates
+ */
+
+static gboolean
+fs_app_stream_transmitter_force_remote_candidates (
+    FsStreamTransmitter *streamtransmitter, GList *candidates,
+    GError **error)
+{
+  GList *item = NULL;
+  FsAppStreamTransmitter *self =
+    FS_APP_STREAM_TRANSMITTER (streamtransmitter);
+
+  for (item = candidates; item; item = g_list_next (item))
+  {
+    FsCandidate *candidate = item->data;
+
+    if (candidate->component_id == 0 ||
+        candidate->component_id > self->priv->transmitter->components) {
+      g_set_error (error, FS_ERROR, FS_ERROR_INVALID_ARGUMENTS,
+          "The candidate passed has an invalid component id %u (not in [1,%u])",
+          candidate->component_id, self->priv->transmitter->components);
+      return FALSE;
+    }
+
+    if ((!candidate->ip || !candidate->ip[0]) &&
+        (!candidate->username || !candidate->username[0]))
+    {
+      g_set_error (error, FS_ERROR, FS_ERROR_INVALID_ARGUMENTS,
+          "The candidate does not have a SINK pipeline in its ip"
+          " or a SRC pipeline in its username");
+      return FALSE;
+    }
+  }
+
+  for (item = candidates; item; item = g_list_next (item))
+    if (!fs_app_stream_transmitter_force_remote_candidate (self,
+            item->data, error))
+      return FALSE;
+
+
+  return TRUE;
+}
+
+
+FsAppStreamTransmitter *
+fs_app_stream_transmitter_newv (FsAppTransmitter *transmitter,
+  guint n_parameters, GParameter *parameters, GError **error)
+{
+  FsAppStreamTransmitter *streamtransmitter = NULL;
+
+  streamtransmitter = g_object_newv (FS_TYPE_APP_STREAM_TRANSMITTER,
+    n_parameters, parameters);
+
+  if (!streamtransmitter) {
+    g_set_error (error, FS_ERROR, FS_ERROR_CONSTRUCTION,
+      "Could not build the stream transmitter");
+    return NULL;
+  }
+
+  streamtransmitter->priv->transmitter = transmitter;
+
+  if (!fs_app_stream_transmitter_build (streamtransmitter, error)) {
+    g_object_unref (streamtransmitter);
+    return NULL;
+  }
+
+  return streamtransmitter;
+}
diff --git a/libpurple/protocols/null/fs-app-stream-transmitter.h b/libpurple/protocols/null/fs-app-stream-transmitter.h
new file mode 100644
index 0000000..049f453
--- /dev/null
+++ b/libpurple/protocols/null/fs-app-stream-transmitter.h
@@ -0,0 +1,98 @@
+/*
+ * Farstream - Farstream Shared Memory Stream Transmitter
+ *
+ * Copyright 2007-2008 Collabora Ltd.
+ *  @author: Olivier Crete <[email protected]>
+ * Copyright 2007-2008 Nokia Corp.
+ *
+ * fs-app-stream-transmitter.h - A Farstream Shared Memory stream transmitter
+ *
+ * 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 Street, Fifth Floor, Boston, MA  02110-1301 USA
+ */
+
+#ifndef __FS_APP_STREAM_TRANSMITTER_H__
+#define __FS_APP_STREAM_TRANSMITTER_H__
+
+#include <glib.h>
+#include <glib-object.h>
+
+#include <farstream/fs-stream-transmitter.h>
+#include <farstream/fs-plugin.h>
+#include "fs-app-transmitter.h"
+
+G_BEGIN_DECLS
+
+/* TYPE MACROS */
+#define FS_TYPE_APP_STREAM_TRANSMITTER \
+  (fs_app_stream_transmitter_get_type ())
+#define FS_APP_STREAM_TRANSMITTER(obj) \
+  (G_TYPE_CHECK_INSTANCE_CAST((obj), FS_TYPE_APP_STREAM_TRANSMITTER, \
+                              FsAppStreamTransmitter))
+#define FS_APP_STREAM_TRANSMITTER_CLASS(klass) \
+  (G_TYPE_CHECK_CLASS_CAST((klass), FS_TYPE_APP_STREAM_TRANSMITTER, \
+                           FsAppStreamTransmitterClass))
+#define FS_IS_APP_STREAM_TRANSMITTER(obj) \
+  (G_TYPE_CHECK_INSTANCE_TYPE((obj), FS_TYPE_APP_STREAM_TRANSMITTER))
+#define FS_IS_APP_STREAM_TRANSMITTER_CLASS(klass) \
+  (G_TYPE_CHECK_CLASS_TYPE((klass), FS_TYPE_APP_STREAM_TRANSMITTER))
+#define FS_APP_STREAM_TRANSMITTER_GET_CLASS(obj) \
+  (G_TYPE_INSTANCE_GET_CLASS ((obj), FS_TYPE_APP_STREAM_TRANSMITTER, \
+                              FsAppStreamTransmitterClass))
+#define FS_APP_STREAM_TRANSMITTER_CAST(obj) ((FsAppStreamTransmitter *) (obj))
+
+typedef struct _FsAppStreamTransmitter FsAppStreamTransmitter;
+typedef struct _FsAppStreamTransmitterClass FsAppStreamTransmitterClass;
+typedef struct _FsAppStreamTransmitterPrivate FsAppStreamTransmitterPrivate;
+
+/**
+ * FsAppStreamTransmitterClass:
+ * @parent_class: Our parent
+ *
+ * The Shared Memory stream transmitter class
+ */
+
+struct _FsAppStreamTransmitterClass
+{
+  FsStreamTransmitterClass parent_class;
+
+  /*virtual functions */
+  /*< private >*/
+};
+
+/**
+ * FsAppStreamTransmitter:
+ * @parent: Parent object
+ *
+ * All members are private, access them using methods and properties
+ */
+struct _FsAppStreamTransmitter
+{
+  FsStreamTransmitter parent;
+
+  /*< private >*/
+  FsAppStreamTransmitterPrivate *priv;
+};
+
+GType fs_app_stream_transmitter_register_type (FsPlugin *module);
+
+GType fs_app_stream_transmitter_get_type (void);
+
+FsAppStreamTransmitter *
+fs_app_stream_transmitter_newv (FsAppTransmitter *transmitter,
+  guint n_parameters, GParameter *parameters, GError **error);
+
+G_END_DECLS
+
+#endif /* __FS_APP_STREAM_TRANSMITTER_H__ */
diff --git a/libpurple/protocols/null/fs-app-transmitter.c b/libpurple/protocols/null/fs-app-transmitter.c
new file mode 100644
index 0000000..87cb5ec
--- /dev/null
+++ b/libpurple/protocols/null/fs-app-transmitter.c
@@ -0,0 +1,924 @@
+/*
+ * Farstream - Farstream App UDP Transmitter
+ *
+ * Copyright 2007-2008 Collabora Ltd.
+ *  @author: Olivier Crete <[email protected]>
+ * Copyright 2007-2008 Nokia Corp.
+ *
+ * fs-app-transmitter.c - A Farstream appsink/appsrc transmitter
+ *
+ * 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 Street, Fifth Floor, Boston, MA  02110-1301 USA
+ */
+
+/**
+ * SECTION:fs-app-transmitter
+ * @short_description: A transmitter for appsink/appsrc I/O
+ *
+ * This transmitter provides appsink/appsrc I/O
+ *
+ */
+
+#ifdef HAVE_CONFIG_H
+#include "config.h"
+#endif
+
+#include "fs-app-transmitter.h"
+#include "fs-app-stream-transmitter.h"
+
+#include <farstream/fs-conference.h>
+#include <farstream/fs-plugin.h>
+
+#include <string.h>
+#include <stdio.h>
+
+GST_DEBUG_CATEGORY (fs_app_transmitter_debug);
+#define GST_CAT_DEFAULT fs_app_transmitter_debug
+
+/* Signals */
+enum
+{
+  LAST_SIGNAL
+};
+
+/* props */
+enum
+{
+  PROP_0,
+  PROP_GST_SINK,
+  PROP_GST_SRC,
+  PROP_COMPONENTS,
+  PROP_DO_TIMESTAMP,
+};
+
+struct _FsAppTransmitterPrivate
+{
+  /* We hold references to this element */
+  GstElement *gst_sink;
+  GstElement *gst_src;
+
+  /* We don't hold a reference to these elements, they are owned
+     by the bins */
+  /* They are tables of pointers, one per component */
+  GstElement **funnels;
+  GstElement **tees;
+
+  gboolean do_timestamp;
+};
+
+#define FS_APP_TRANSMITTER_GET_PRIVATE(o)  \
+  (G_TYPE_INSTANCE_GET_PRIVATE ((o), FS_TYPE_APP_TRANSMITTER,   \
+      FsAppTransmitterPrivate))
+
+static void fs_app_transmitter_class_init (
+    FsAppTransmitterClass *klass);
+static void fs_app_transmitter_init (FsAppTransmitter *self);
+static void fs_app_transmitter_constructed (GObject *object);
+static void fs_app_transmitter_dispose (GObject *object);
+static void fs_app_transmitter_finalize (GObject *object);
+
+static void fs_app_transmitter_get_property (GObject *object,
+                                                guint prop_id,
+                                                GValue *value,
+                                                GParamSpec *pspec);
+static void fs_app_transmitter_set_property (GObject *object,
+                                                guint prop_id,
+                                                const GValue *value,
+                                                GParamSpec *pspec);
+
+static FsStreamTransmitter *fs_app_transmitter_new_stream_transmitter (
+    FsTransmitter *transmitter, FsParticipant *participant,
+    guint n_parameters, GParameter *parameters, GError **error);
+static GType fs_app_transmitter_get_stream_transmitter_type (
+    FsTransmitter *transmitter);
+
+
+static GObjectClass *parent_class = NULL;
+//static guint signals[LAST_SIGNAL] = { 0 };
+
+/*
+ * Private bin subclass
+ */
+
+enum {
+  BIN_SIGNAL_READY,
+  BIN_SIGNAL_DISCONNECTED,
+  BIN_LAST_SIGNAL
+};
+
+static guint bin_signals[BIN_LAST_SIGNAL] = { 0 };
+static GType app_bin_type = 0;
+gpointer app_bin_parent_class = NULL;
+
+typedef struct _FsAppBin
+{
+  GstBin parent;
+} FsAppBin;
+
+typedef struct _FsAppBinClass
+{
+  GstBinClass parent_class;
+} FsAppBinClass;
+
+static void fs_app_bin_init (FsAppBin *self)
+{
+}
+
+static GstElement *
+fs_app_bin_new (void)
+{
+  return g_object_new (app_bin_type, NULL);
+}
+
+static void
+fs_app_bin_handle_message (GstBin *bin, GstMessage *message)
+{
+  GstState old, new, pending;
+  GError *gerror;
+  gchar *msg;
+
+  switch (GST_MESSAGE_TYPE (message))
+  {
+    case GST_MESSAGE_STATE_CHANGED:
+      gst_message_parse_state_changed (message, &old, &new, &pending);
+
+      if (old == GST_STATE_PAUSED && new == GST_STATE_PLAYING)
+        g_signal_emit (bin, bin_signals[BIN_SIGNAL_READY], 0,
+            GST_MESSAGE_SRC (message));
+      break;
+    case GST_MESSAGE_ERROR:
+      gst_message_parse_error (message, &gerror, &msg);
+
+      if (g_error_matches (gerror, GST_RESOURCE_ERROR,
+              GST_RESOURCE_ERROR_READ))
+      {
+        g_signal_emit (bin, bin_signals[BIN_SIGNAL_DISCONNECTED], 0,
+            GST_MESSAGE_SRC (message));
+        gst_message_unref (message);
+        return;
+      }
+      break;
+    default:
+      break;
+  }
+
+  GST_BIN_CLASS (app_bin_parent_class)->handle_message (bin, message);
+}
+
+static void fs_app_bin_class_init (FsAppBinClass *klass)
+{
+  GstBinClass *bin_class = GST_BIN_CLASS (klass);
+
+  app_bin_parent_class = g_type_class_peek_parent (klass);
+
+  bin_signals[BIN_SIGNAL_READY] =
+    g_signal_new ("ready", G_TYPE_FROM_CLASS (klass),
+        G_SIGNAL_RUN_LAST, 0, NULL, NULL,
+        g_cclosure_marshal_VOID__OBJECT, G_TYPE_NONE, 1, GST_TYPE_ELEMENT);
+
+  bin_signals[BIN_SIGNAL_DISCONNECTED] =
+    g_signal_new ("disconnected", G_TYPE_FROM_CLASS (klass),
+        G_SIGNAL_RUN_LAST, 0, NULL, NULL,
+        g_cclosure_marshal_VOID__OBJECT, G_TYPE_NONE, 1, GST_TYPE_ELEMENT);
+
+  bin_class->handle_message = GST_DEBUG_FUNCPTR (fs_app_bin_handle_message);
+}
+
+/*
+ * Lets register the plugin
+ */
+
+static GType type = 0;
+
+GType
+fs_app_transmitter_get_type (void)
+{
+  g_assert (type);
+  return type;
+}
+
+GType
+fs_app_transmitter_register_type (FsPlugin *module);
+GType
+fs_app_transmitter_register_type (FsPlugin *module)
+{
+  static const GTypeInfo info = {
+    sizeof (FsAppTransmitterClass),
+    NULL,
+    NULL,
+    (GClassInitFunc) fs_app_transmitter_class_init,
+    NULL,
+    NULL,
+    sizeof (FsAppTransmitter),
+    0,
+    (GInstanceInitFunc) fs_app_transmitter_init
+  };
+
+  static const GTypeInfo bin_info = {
+    sizeof (FsAppBinClass),
+    NULL,
+    NULL,
+    (GClassInitFunc) fs_app_bin_class_init,
+    NULL,
+    NULL,
+    sizeof (FsAppBin),
+    0,
+    (GInstanceInitFunc) fs_app_bin_init
+  };
+
+
+  GST_DEBUG_CATEGORY_INIT (fs_app_transmitter_debug,
+      "fsapptransmitter", 0,
+      "Farstream app UDP transmitter");
+
+  fs_app_stream_transmitter_register_type (module);
+
+  type = g_type_register_static (FS_TYPE_TRANSMITTER, "FsAppTransmitter",
+      &info, 0);
+
+  app_bin_type = g_type_register_static (
+    GST_TYPE_BIN, "FsAppBin", &bin_info, 0);
+
+  return type;
+}
+
+#ifdef _FS_REGISTER_TYPE
+FS_INIT_PLUGIN (app, transmitter)
+#else /* <= 0.2.7 */
+FS_INIT_PLUGIN (fs_app_transmitter_register_type)
+#endif
+
+static void
+fs_app_transmitter_class_init (FsAppTransmitterClass *klass)
+{
+  GObjectClass *gobject_class = (GObjectClass *) klass;
+  FsTransmitterClass *transmitter_class = FS_TRANSMITTER_CLASS (klass);
+
+  parent_class = g_type_class_peek_parent (klass);
+
+  gobject_class->set_property = fs_app_transmitter_set_property;
+  gobject_class->get_property = fs_app_transmitter_get_property;
+
+  gobject_class->constructed = fs_app_transmitter_constructed;
+
+  g_object_class_override_property (gobject_class, PROP_GST_SRC, "gst-src");
+  g_object_class_override_property (gobject_class, PROP_GST_SINK, "gst-sink");
+  g_object_class_override_property (gobject_class, PROP_COMPONENTS,
+    "components");
+  g_object_class_override_property (gobject_class, PROP_DO_TIMESTAMP,
+    "do-timestamp");
+
+  transmitter_class->new_stream_transmitter =
+    fs_app_transmitter_new_stream_transmitter;
+  transmitter_class->get_stream_transmitter_type =
+    fs_app_transmitter_get_stream_transmitter_type;
+
+  gobject_class->dispose = fs_app_transmitter_dispose;
+  gobject_class->finalize = fs_app_transmitter_finalize;
+
+  g_type_class_add_private (klass, sizeof (FsAppTransmitterPrivate));
+}
+
+static void
+fs_app_transmitter_init (FsAppTransmitter *self)
+{
+
+  /* member init */
+  self->priv = FS_APP_TRANSMITTER_GET_PRIVATE (self);
+
+  self->components = 2;
+  self->priv->do_timestamp = TRUE;
+}
+
+static void
+fs_app_transmitter_constructed (GObject *object)
+{
+  FsAppTransmitter *self = FS_APP_TRANSMITTER_CAST (object);
+  FsTransmitter *trans = FS_TRANSMITTER_CAST (self);
+  GstPad *pad = NULL, *pad2 = NULL;
+  GstPad *ghostpad = NULL;
+  gchar *padname;
+  GstPadLinkReturn ret;
+  int c; /* component_id */
+
+
+  /* We waste one space in order to have the index be the component_id */
+  self->priv->funnels = g_new0 (GstElement *, self->components+1);
+  self->priv->tees = g_new0 (GstElement *, self->components+1);
+
+  /* First we need the src elemnet */
+
+  self->priv->gst_src = fs_app_bin_new ();
+
+  if (!self->priv->gst_src) {
+    trans->construction_error = g_error_new (FS_ERROR,
+      FS_ERROR_CONSTRUCTION,
+      "Could not build the transmitter src bin");
+    return;
+  }
+
+  gst_object_ref (self->priv->gst_src);
+
+
+  /* Second, we do the sink element */
+
+  self->priv->gst_sink = fs_app_bin_new ();
+
+  if (!self->priv->gst_sink) {
+    trans->construction_error = g_error_new (FS_ERROR,
+      FS_ERROR_CONSTRUCTION,
+      "Could not build the transmitter sink bin");
+    return;
+  }
+
+  g_object_set (G_OBJECT (self->priv->gst_sink),
+      "async-handling", TRUE,
+      NULL);
+
+  gst_object_ref (self->priv->gst_sink);
+
+  for (c = 1; c <= self->components; c++) {
+    GstElement *fakesink = NULL;
+
+    /* Lets create the RTP source funnel */
+
+    self->priv->funnels[c] = gst_element_factory_make ("funnel", NULL);
+
+    if (!self->priv->funnels[c]) {
+      trans->construction_error = g_error_new (FS_ERROR,
+        FS_ERROR_CONSTRUCTION,
+        "Could not make the funnel element");
+      return;
+    }
+
+    if (!gst_bin_add (GST_BIN (self->priv->gst_src),
+        self->priv->funnels[c])) {
+      trans->construction_error = g_error_new (FS_ERROR,
+        FS_ERROR_CONSTRUCTION,
+        "Could not add the funnel element to the transmitter src bin");
+    }
+
+    pad = gst_element_get_static_pad (self->priv->funnels[c], "src");
+    padname = g_strdup_printf ("src_%u", c);
+    ghostpad = gst_ghost_pad_new (padname, pad);
+    g_free (padname);
+    gst_object_unref (pad);
+
+    gst_pad_set_active (ghostpad, TRUE);
+    gst_element_add_pad (self->priv->gst_src, ghostpad);
+
+
+    /* Lets create the RTP sink tee */
+
+    self->priv->tees[c] = gst_element_factory_make ("tee", NULL);
+
+    if (!self->priv->tees[c]) {
+      trans->construction_error = g_error_new (FS_ERROR,
+        FS_ERROR_CONSTRUCTION,
+        "Could not make the tee element");
+      return;
+    }
+
+    if (!gst_bin_add (GST_BIN (self->priv->gst_sink),
+        self->priv->tees[c])) {
+      trans->construction_error = g_error_new (FS_ERROR,
+        FS_ERROR_CONSTRUCTION,
+        "Could not add the tee element to the transmitter sink bin");
+    }
+
+    pad = gst_element_get_static_pad (self->priv->tees[c], "sink");
+    padname = g_strdup_printf ("sink_%u", c);
+    ghostpad = gst_ghost_pad_new (padname, pad);
+    g_free (padname);
+    gst_object_unref (pad);
+
+    gst_pad_set_active (ghostpad, TRUE);
+    gst_element_add_pad (self->priv->gst_sink, ghostpad);
+
+    fakesink = gst_element_factory_make ("fakesink", NULL);
+
+    if (!fakesink) {
+      trans->construction_error = g_error_new (FS_ERROR,
+        FS_ERROR_CONSTRUCTION,
+        "Could not make the fakesink element");
+      return;
+    }
+
+    g_object_set (fakesink,
+        "async", FALSE,
+        "sync" , FALSE,
+        NULL);
+
+    if (!gst_bin_add (GST_BIN (self->priv->gst_sink), fakesink))
+    {
+      gst_object_unref (fakesink);
+      trans->construction_error = g_error_new (FS_ERROR,
+          FS_ERROR_CONSTRUCTION,
+          "Could not add the fakesink element to the transmitter sink bin");
+      return;
+    }
+
+    pad = gst_element_get_request_pad (self->priv->tees[c], "src_%u");
+    pad2 = gst_element_get_static_pad (fakesink, "sink");
+
+    ret = gst_pad_link (pad, pad2);
+
+    gst_object_unref (pad2);
+    gst_object_unref (pad);
+
+    if (GST_PAD_LINK_FAILED(ret)) {
+      trans->construction_error = g_error_new (FS_ERROR,
+          FS_ERROR_CONSTRUCTION,
+          "Could not link the tee to the fakesink");
+      return;
+    }
+  }
+
+  GST_CALL_PARENT (G_OBJECT_CLASS, constructed, (object));
+}
+
+static void
+fs_app_transmitter_dispose (GObject *object)
+{
+  FsAppTransmitter *self = FS_APP_TRANSMITTER (object);
+
+  if (self->priv->gst_src) {
+    gst_object_unref (self->priv->gst_src);
+    self->priv->gst_src = NULL;
+  }
+
+  if (self->priv->gst_sink) {
+    gst_object_unref (self->priv->gst_sink);
+    self->priv->gst_sink = NULL;
+  }
+
+  parent_class->dispose (object);
+}
+
+static void
+fs_app_transmitter_finalize (GObject *object)
+{
+  FsAppTransmitter *self = FS_APP_TRANSMITTER (object);
+
+  if (self->priv->funnels) {
+    g_free (self->priv->funnels);
+    self->priv->funnels = NULL;
+  }
+
+  if (self->priv->tees) {
+    g_free (self->priv->tees);
+    self->priv->tees = NULL;
+  }
+
+  parent_class->finalize (object);
+}
+
+static void
+fs_app_transmitter_get_property (GObject *object,
+                             guint prop_id,
+                             GValue *value,
+                             GParamSpec *pspec)
+{
+  FsAppTransmitter *self = FS_APP_TRANSMITTER (object);
+
+  switch (prop_id) {
+    case PROP_GST_SINK:
+      g_value_set_object (value, self->priv->gst_sink);
+      break;
+    case PROP_GST_SRC:
+      g_value_set_object (value, self->priv->gst_src);
+      break;
+    case PROP_COMPONENTS:
+      g_value_set_uint (value, self->components);
+      break;
+    case PROP_DO_TIMESTAMP:
+      g_value_set_boolean (value, self->priv->do_timestamp);
+      break;
+    default:
+      G_OBJECT_WARN_INVALID_PROPERTY_ID (object, prop_id, pspec);
+      break;
+  }
+}
+
+static void
+fs_app_transmitter_set_property (GObject *object,
+                                    guint prop_id,
+                                    const GValue *value,
+                                    GParamSpec *pspec)
+{
+  FsAppTransmitter *self = FS_APP_TRANSMITTER (object);
+
+  switch (prop_id) {
+    case PROP_COMPONENTS:
+      self->components = g_value_get_uint (value);
+      break;
+    case PROP_DO_TIMESTAMP:
+      self->priv->do_timestamp = g_value_get_boolean (value);
+      break;
+    default:
+      G_OBJECT_WARN_INVALID_PROPERTY_ID (object, prop_id, pspec);
+      break;
+  }
+}
+
+
+/**
+ * fs_app_transmitter_new_stream_app_transmitter:
+ * @transmitter: a #FsTranmitter
+ * @participant: the #FsParticipant for which the #FsStream using this
+ * new #FsStreamTransmitter is created
+ *
+ * This function will create a new #FsStreamTransmitter element for a
+ * specific participant for this #FsAppTransmitter
+ *
+ * Returns: a new #FsStreamTransmitter
+ */
+
+static FsStreamTransmitter *
+fs_app_transmitter_new_stream_transmitter (FsTransmitter *transmitter,
+  FsParticipant *participant, guint n_parameters, GParameter *parameters,
+  GError **error)
+{
+  FsAppTransmitter *self = FS_APP_TRANSMITTER (transmitter);
+
+  printf("New App transmitter\n");
+  return FS_STREAM_TRANSMITTER (fs_app_stream_transmitter_newv (
+        self, n_parameters, parameters, error));
+}
+
+static GType
+fs_app_transmitter_get_stream_transmitter_type (
+    FsTransmitter *transmitter)
+{
+  return FS_TYPE_APP_STREAM_TRANSMITTER;
+}
+
+
+struct _AppSrc {
+  guint component;
+  gchar *pipeline;
+  GstElement *src;
+  GstPad *funnelpad;
+
+  got_buffer got_buffer_func;
+  connection disconnected_func;
+  gpointer cb_data;
+  gulong buffer_probe;
+};
+
+
+static GstPadProbeReturn
+src_buffer_probe_cb (GstPad *pad, GstPadProbeInfo *info, gpointer user_data)
+{
+  AppSrc *app = user_data;
+  GstBuffer *buffer = GST_PAD_PROBE_INFO_BUFFER (info);
+
+  app->got_buffer_func (buffer, app->component, app->cb_data);
+
+  return TRUE;
+}
+
+
+static void
+disconnected_cb (GstBin *bin, GstElement *elem, AppSrc *app)
+{
+  if (elem != app->src)
+    return;
+
+  app->disconnected_func (app->component, 0, app->cb_data);
+}
+
+
+AppSrc *
+fs_app_transmitter_get_app_src (FsAppTransmitter *self,
+    guint component,
+    const gchar *pipeline,
+    got_buffer got_buffer_func,
+    connection disconnected_func,
+    gpointer cb_data,
+    GError **error)
+{
+  AppSrc *app = g_slice_new0 (AppSrc);
+  GstElement *elem;
+  GstPad *pad;
+
+  app->component = component;
+  app->got_buffer_func = got_buffer_func;
+  app->disconnected_func = disconnected_func;
+  app->cb_data = cb_data;
+
+  app->pipeline = g_strdup (pipeline);
+
+  elem = gst_parse_bin_from_description(pipeline, TRUE, NULL);
+  if (!elem)
+  {
+    g_set_error (error, FS_ERROR, FS_ERROR_CONSTRUCTION,
+        "Could not make appsrc");
+    goto error;
+  }
+
+  if (app->disconnected_func)
+    g_signal_connect (self->priv->gst_src, "disconnected",
+        G_CALLBACK (disconnected_cb), app);
+
+  if (!gst_bin_add (GST_BIN (self->priv->gst_src), elem))
+  {
+    g_set_error (error, FS_ERROR, FS_ERROR_CONSTRUCTION,
+        "Could not add recvonly filter to bin");
+    gst_object_unref (elem);
+    goto error;
+  }
+
+  app->src = elem;
+
+  app->funnelpad = gst_element_get_request_pad (self->priv->funnels[component],
+      "sink_%u");
+
+  if (!app->funnelpad)
+  {
+    g_set_error (error, FS_ERROR, FS_ERROR_CONSTRUCTION,
+        "Could not get funnelpad");
+    goto error;
+  }
+
+  pad = gst_element_get_static_pad (app->src, "src");
+  if (GST_PAD_LINK_FAILED (gst_pad_link (pad, app->funnelpad)))
+  {
+    g_set_error (error, FS_ERROR, FS_ERROR_CONSTRUCTION, "Could not link tee"
+        " and valve");
+    gst_object_unref (pad);
+    goto error;
+  }
+
+  gst_object_unref (pad);
+
+  if (got_buffer_func)
+    app->buffer_probe = gst_pad_add_probe (app->funnelpad,
+        GST_PAD_PROBE_TYPE_BUFFER,
+        src_buffer_probe_cb, app, NULL);
+
+  if (!gst_element_sync_state_with_parent (app->src))
+  {
+    g_set_error (error, FS_ERROR, FS_ERROR_CONSTRUCTION,
+        "Could not sync the state of the new appsrc with its parent");
+    goto error;
+  }
+
+  return app;
+
+ error:
+  fs_app_transmitter_check_app_src (self, app, NULL);
+  return NULL;
+}
+
+/*
+ * Returns: %TRUE if the pipeline is the same, other %FALSE and freeds the AppSrc
+ */
+
+gboolean
+fs_app_transmitter_check_app_src (FsAppTransmitter *self, AppSrc *app,
+    const gchar *pipeline)
+{
+  if (pipeline && !strcmp (pipeline, app->pipeline))
+    return TRUE;
+
+  if (app->buffer_probe)
+    gst_pad_remove_probe (app->funnelpad, app->buffer_probe);
+  app->buffer_probe = 0;
+
+  if (app->funnelpad) {
+    gst_element_release_request_pad (self->priv->funnels[app->component],
+        app->funnelpad);
+    gst_object_unref (app->funnelpad);
+  }
+  app->funnelpad = NULL;
+
+  if (app->src)
+  {
+    gst_element_set_locked_state (app->src, TRUE);
+    gst_element_set_state (app->src, GST_STATE_NULL);
+    gst_bin_remove (GST_BIN (self->priv->gst_src), app->src);
+  }
+  app->src = NULL;
+
+  g_free (app->pipeline);
+  g_slice_free (AppSrc, app);
+
+  return FALSE;
+}
+
+
+
+struct _AppSink {
+  guint component;
+  gchar *pipeline;
+  GstElement *sink;
+  GstElement *recvonly_filter;
+  GstPad *teepad;
+
+  ready ready_func;
+  gpointer cb_data;
+};
+
+
+static void
+ready_cb (GstBin *bin, GstElement *elem, AppSink *app)
+{
+  if (elem != app->sink)
+    return;
+
+  app->ready_func (app->component, app->pipeline, app->cb_data);
+}
+
+AppSink *
+fs_app_transmitter_get_app_sink (FsAppTransmitter *self,
+    guint component,
+    const gchar *pipeline,
+    ready ready_func,
+    gpointer cb_data,
+    GError **error)
+{
+  AppSink *app = g_slice_new0 (AppSink);
+  GstElement *elem;
+  GstPad *pad;
+
+  GST_DEBUG ("Trying to add app sink for c:%u pipeline '%s'", component, pipeline);
+
+  app->component = component;
+
+  app->pipeline = g_strdup (pipeline);
+
+  app->ready_func = ready_func;
+  app->cb_data = cb_data;
+
+  /* First add the sink */
+
+  elem = gst_parse_bin_from_description(pipeline, TRUE, NULL);
+  if (!elem)
+  {
+    g_set_error (error, FS_ERROR, FS_ERROR_CONSTRUCTION,
+        "Could not make appsink");
+    goto error;
+  }
+  g_object_set (elem,
+      "async", FALSE,
+      "sync" , FALSE,
+      NULL);
+
+  if (ready_func)
+    g_signal_connect (self->priv->gst_sink, "ready", G_CALLBACK (ready_cb),
+        app);
+
+  if (!gst_bin_add (GST_BIN (self->priv->gst_sink), elem))
+  {
+    g_set_error (error, FS_ERROR, FS_ERROR_CONSTRUCTION,
+        "Could not add appsink to bin");
+    gst_object_unref (elem);
+    goto error;
+  }
+
+  app->sink = elem;
+
+  /* Second add the recvonly filter */
+
+  elem = gst_element_factory_make ("valve", NULL);
+  if (!elem)
+  {
+    g_set_error (error, FS_ERROR, FS_ERROR_CONSTRUCTION,
+        "Could not make valve");
+    goto error;
+  }
+
+  if (!gst_bin_add (GST_BIN (self->priv->gst_sink), elem))
+  {
+    g_set_error (error, FS_ERROR, FS_ERROR_CONSTRUCTION,
+        "Could not add recvonly filter to bin");
+    gst_object_unref (elem);
+    goto error;
+  }
+
+  app->recvonly_filter = elem;
+
+  /* Third connect these */
+
+  if (!gst_element_link (app->recvonly_filter, app->sink))
+  {
+    g_set_error (error, FS_ERROR, FS_ERROR_CONSTRUCTION,
+        "Could not link recvonly filter and appsink");
+    GST_DEBUG_BIN_TO_DOT_FILE(GST_BIN(self->priv->gst_sink), GST_DEBUG_GRAPH_SHOW_ALL, "nolink");
+
+    goto error;
+  }
+
+  if (!gst_element_sync_state_with_parent (app->sink))
+  {
+    g_set_error (error, FS_ERROR, FS_ERROR_CONSTRUCTION,
+        "Could not sync the state of the new appsink with its parent");
+    goto error;
+  }
+
+  if (!gst_element_sync_state_with_parent (app->recvonly_filter))
+  {
+    g_set_error (error, FS_ERROR, FS_ERROR_CONSTRUCTION,
+        "Could not sync the state of the new recvonly filter  with its parent");
+    goto error;
+  }
+
+  app->teepad = gst_element_get_request_pad (self->priv->tees[component],
+      "src_%u");
+
+  if (!app->teepad)
+  {
+    g_set_error (error, FS_ERROR, FS_ERROR_CONSTRUCTION,
+        "Could not get teepad");
+    goto error;
+  }
+
+  pad = gst_element_get_static_pad (app->recvonly_filter, "sink");
+  if (GST_PAD_LINK_FAILED (gst_pad_link (app->teepad, pad)))
+  {
+    g_set_error (error, FS_ERROR, FS_ERROR_CONSTRUCTION, "Could not link tee"
+        " and valve");
+    gst_object_unref (pad);
+    goto error;
+  }
+  gst_object_unref (pad);
+
+  return app;
+
+ error:
+  fs_app_transmitter_check_app_sink (self, app, NULL);
+
+  return NULL;
+}
+
+gboolean
+fs_app_transmitter_check_app_sink (FsAppTransmitter *self, AppSink *app,
+    const gchar *pipeline)
+{
+  if (pipeline && !strcmp (pipeline, app->pipeline))
+    return TRUE;
+
+  if (pipeline)
+    GST_DEBUG ("Replacing app pipeline '%s' with '%s'", app->pipeline, pipeline);
+  else
+    GST_DEBUG ("Freeing app pipeline '%s'", app->pipeline);
+
+  if (app->teepad)
+  {
+    gst_element_release_request_pad (self->priv->tees[app->component],
+        app->teepad);
+    gst_object_unref (app->teepad);
+  }
+  app->teepad = NULL;
+
+  if (app->sink)
+  {
+    gst_element_set_locked_state (app->sink, TRUE);
+    gst_element_set_state (app->sink, GST_STATE_NULL);
+    gst_bin_remove (GST_BIN (self->priv->gst_sink), app->sink);
+  }
+  app->sink = NULL;
+
+  if (app->recvonly_filter)
+  {
+    gst_element_set_locked_state (app->recvonly_filter, TRUE);
+    gst_element_set_state (app->recvonly_filter, GST_STATE_NULL);
+    gst_bin_remove (GST_BIN (self->priv->gst_sink), app->recvonly_filter);
+  }
+  app->recvonly_filter = NULL;
+
+  g_free (app->pipeline);
+  g_slice_free (AppSink, app);
+
+  return FALSE;
+}
+
+
+void
+fs_app_transmitter_sink_set_sending (FsAppTransmitter *self, AppSink *app,
+    gboolean sending)
+{
+  GObjectClass *klass = G_OBJECT_GET_CLASS (app->recvonly_filter);
+
+  if (g_object_class_find_property (klass, "drop"))
+    g_object_set (app->recvonly_filter, "drop", !sending, NULL);
+
+  if (sending)
+    gst_element_send_event (app->sink,
+        gst_event_new_custom (GST_EVENT_CUSTOM_UPSTREAM,
+            gst_structure_new ("GstForceKeyUnit",
+              "all-headers", G_TYPE_BOOLEAN, TRUE,
+              NULL)));
+}
diff --git a/libpurple/protocols/null/fs-app-transmitter.h b/libpurple/protocols/null/fs-app-transmitter.h
new file mode 100644
index 0000000..2bfdadf
--- /dev/null
+++ b/libpurple/protocols/null/fs-app-transmitter.h
@@ -0,0 +1,124 @@
+/*
+ * Farstream - Farstream Shared Memory Transmitter
+ *
+ * Copyright 2007-2008 Collabora Ltd.
+ *  @author: Olivier Crete <[email protected]>
+ * Copyright 2007-2008 Nokia Corp.
+ *
+ * fs-app-transmitter.h - A Farstream Shared Memory transmitter
+ *
+ * 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 Street, Fifth Floor, Boston, MA  02110-1301 USA
+ */
+
+#ifndef __FS_APP_TRANSMITTER_H__
+#define __FS_APP_TRANSMITTER_H__
+
+#include <farstream/fs-transmitter.h>
+
+#include <gst/gst.h>
+
+G_BEGIN_DECLS
+
+/* TYPE MACROS */
+#define FS_TYPE_APP_TRANSMITTER \
+  (fs_app_transmitter_get_type ())
+#define FS_APP_TRANSMITTER(obj) \
+  (G_TYPE_CHECK_INSTANCE_CAST((obj), FS_TYPE_APP_TRANSMITTER, \
+    FsAppTransmitter))
+#define FS_APP_TRANSMITTER_CLASS(klass) \
+  (G_TYPE_CHECK_CLASS_CAST((klass), FS_TYPE_APP_TRANSMITTER, \
+    FsAppTransmitterClass))
+#define FS_IS_APP_TRANSMITTER(obj) \
+  (G_TYPE_CHECK_INSTANCE_TYPE((obj), FS_TYPE_APP_TRANSMITTER))
+#define FS_IS_APP_TRANSMITTER_CLASS(klass) \
+  (G_TYPE_CHECK_CLASS_TYPE((klass), FS_TYPE_APP_TRANSMITTER))
+#define FS_APP_TRANSMITTER_GET_CLASS(obj) \
+  (G_TYPE_INSTANCE_GET_CLASS ((obj), FS_TYPE_APP_TRANSMITTER, \
+    FsAppTransmitterClass))
+#define FS_APP_TRANSMITTER_CAST(obj) ((FsAppTransmitter *) (obj))
+
+typedef struct _FsAppTransmitter FsAppTransmitter;
+typedef struct _FsAppTransmitterClass FsAppTransmitterClass;
+typedef struct _FsAppTransmitterPrivate FsAppTransmitterPrivate;
+
+/**
+ * FsAppTransmitterClass:
+ * @parent_class: Our parent
+ *
+ * The Shared Memory transmitter class
+ */
+
+struct _FsAppTransmitterClass
+{
+  FsTransmitterClass parent_class;
+};
+
+/**
+ * FsAppTransmitter:
+ * @parent: Parent object
+ *
+ * All members are private, access them using methods and properties
+ */
+struct _FsAppTransmitter
+{
+  FsTransmitter parent;
+
+  /* The number of components (READONLY) */
+  gint components;
+
+  /*< private >*/
+  FsAppTransmitterPrivate *priv;
+};
+
+GType fs_app_transmitter_get_type (void);
+
+typedef struct _AppSrc AppSrc;
+typedef struct _AppSink AppSink;
+
+typedef void (*got_buffer) (GstBuffer *buffer, guint component, gpointer data);
+typedef void (*ready) (guint component, gchar *path, gpointer data);
+typedef void (*connection) (guint component, gint id, gpointer data);
+
+AppSrc *fs_app_transmitter_get_app_src (FsAppTransmitter *self,
+    guint component,
+    const gchar *path,
+    got_buffer got_buffer_func,
+    connection disconnected_func,
+    gpointer cb_data,
+    GError **error);
+
+gboolean fs_app_transmitter_check_app_src (FsAppTransmitter *self,
+    AppSrc *app,
+    const gchar *path);
+
+AppSink *fs_app_transmitter_get_app_sink (FsAppTransmitter *self,
+    guint component,
+    const gchar *path,
+    ready ready_func,
+    gpointer cb_data,
+    GError **error);
+
+gboolean fs_app_transmitter_check_app_sink (FsAppTransmitter *self,
+    AppSink *app,
+    const gchar *path);
+
+void fs_app_transmitter_sink_set_sending (FsAppTransmitter *self,
+    AppSink *app, gboolean sending);
+
+
+
+G_END_DECLS
+
+#endif /* __FS_APP_TRANSMITTER_H__ */
diff --git a/libpurple/protocols/null/nullprpl.c b/libpurple/protocols/null/nullprpl.c
index e2d8662..7e6cabe 100644
--- a/libpurple/protocols/null/nullprpl.c
+++ b/libpurple/protocols/null/nullprpl.c
@@ -204,14 +204,64 @@ static void nullprpl_input_user_info(PurplePluginAction *action)
   purple_account_request_change_user_info(acct);
 }
 
+#include <media.h>
+#include <media-gst.h>
+#include <mediamanager.h>
+
+static void nullprpl_dummy_call(PurplePluginAction *action)
+{
+  PurpleConnection *gc = (PurpleConnection *)action->context;
+  PurpleAccount *acct = purple_connection_get_account(gc);
+  PurpleMediaCandidate *cand;
+  GList *cands, *codecs;
+  const gchar *sess = "dummy session";
+  const gchar *name = "Dummy call";
+  gchar *srcpipe, *sinkpipe;
+  PurpleMediaManager *mgr = purple_media_manager_get();
+  PurpleMedia *media = purple_media_manager_create_media(mgr,
+							 acct,
+							 "fsrawconference",
+							 name,
+							 TRUE);
+  const gchar *caps = "audio/x-raw,format=(string)S16LE,layout=(string)interleaved,channels=(int)1";
+
+  purple_media_add_stream(media, sess, name,
+			  PURPLE_MEDIA_AUDIO, TRUE,
+			  "app", 0, NULL);
+  /* Without the capsfilter (even though there's another capsfilter almost immediately after it in the pipeline, it doesn't work */
+  srcpipe = g_strdup_printf("audiotestsrc format=time ! capsfilter caps=%s", caps);
+  sinkpipe = g_strdup_printf("audioconvert ! filesink location=/tmp/pidgin.s16 async=false");
+
+  cand = purple_media_candidate_new(NULL, 1,
+				    PURPLE_MEDIA_CANDIDATE_TYPE_HOST,
+				    PURPLE_MEDIA_NETWORK_PROTOCOL_UDP,
+				    sinkpipe, 0);
+  g_object_set(cand, "username", srcpipe, NULL);
+  g_free(sinkpipe);
+  g_free(srcpipe);
+
+  cands = g_list_append (NULL, cand);
+  codecs = g_list_append(NULL,
+				purple_media_codec_new(1, caps, PURPLE_MEDIA_AUDIO, 0));
+  purple_media_add_remote_candidates(media, sess, name, cands);
+  purple_media_set_remote_codecs(media, sess, name, codecs);
+
+  purple_media_stream_info(media, PURPLE_MEDIA_INFO_ACCEPT, sess, name, FALSE);
+  GST_DEBUG_BIN_TO_DOT_FILE(GST_BIN(purple_media_manager_get_pipeline(mgr)), GST_DEBUG_GRAPH_SHOW_ALL, "dummy conference graph");
+}
+
+
 /* this is set to the actions member of the PurplePluginInfo struct at the
  * bottom.
  */
 static GList *nullprpl_actions(PurplePlugin *plugin, gpointer context)
 {
-  PurplePluginAction *action = purple_plugin_action_new(
-    _("Set User Info..."), nullprpl_input_user_info);
-  return g_list_append(NULL, action);
+  GList *l = NULL;
+  l = g_list_append(l, purple_plugin_action_new(
+			_("Set User Info..."), nullprpl_input_user_info));
+  l = g_list_append(l, purple_plugin_action_new(
+			_("Dummy audio call..."), nullprpl_dummy_call));
+  return l;
 }
 
 
@@ -1126,6 +1176,10 @@ static PurplePluginProtocolInfo prpl_info =
   NULL                                 /* add_buddies_with_invite */
 };
 
+#include "fs-app-transmitter.h"
+#include <farstream/fs-plugin.h>
+extern GType fs_app_transmitter_register_type(void *);
+
 static void nullprpl_init(PurplePlugin *plugin)
 {
   /* see accountopt.h for information about user splits and protocol options */
-- 
2.7.4
smime.p7s (application/x-pkcs7-signature, 5.1 KB) - not displayed