diff libpurple/media.c @ 23797:e1c8ec1259de

Updates voice and video to use Farsight 2, gets XMPP voice conferences closer to XEP-0167, and fixes a lot of bugs.
author Mike Ruprecht <maiku@soc.pidgin.im>
date Fri, 23 May 2008 02:42:32 +0000
parents befeece4dd48
children 6bf2dfb350c0
line wrap: on
line diff
--- a/libpurple/media.c	Sun Apr 13 17:53:46 2008 +0000
+++ b/libpurple/media.c	Fri May 23 02:42:32 2008 +0000
@@ -29,6 +29,7 @@
 
 #include "connection.h"
 #include "media.h"
+#include "marshallers.h"
 
 #include "debug.h"
 
@@ -36,11 +37,11 @@
 #ifdef USE_GSTPROPS
 
 #include <gst/interfaces/propertyprobe.h>
-#include <farsight/farsight.h>
+#include <gst/farsight/fs-conference-iface.h>
 
 struct _PurpleMediaPrivate
 {
-	FarsightSession *farsight_session;
+	FsConference *conference;
 
 	char *name;
 	PurpleConnection *connection;
@@ -49,8 +50,23 @@
 	GstElement *video_src;
 	GstElement *video_sink;
 
-	FarsightStream *audio_stream;
-	FarsightStream *video_stream;
+	FsSession *audio_session;
+	FsSession *video_session;
+
+	GList *participants; 	/* FsParticipant list */
+	GList *audio_streams;	/* FsStream list */
+	GList *video_streams;	/* FsStream list */
+
+	/* might be able to just combine these two */
+	GstElement *audio_pipeline;
+	GstElement *video_pipeline;
+
+	/* this will need to be stored/handled per stream
+	 * once having multiple streams is supported */
+	GList *local_candidates;
+
+	FsCandidate *local_candidate;
+	FsCandidate *remote_candidate;
 };
 
 #define PURPLE_MEDIA_GET_PRIVATE(obj) (G_TYPE_INSTANCE_GET_PRIVATE((obj), PURPLE_TYPE_MEDIA, PurpleMediaPrivate))
@@ -73,21 +89,23 @@
 	REJECT,
 	GOT_HANGUP,
 	GOT_ACCEPT,
+	CANDIDATES_PREPARED,
+	CANDIDATE_PAIR,
 	LAST_SIGNAL
 };
 static guint purple_media_signals[LAST_SIGNAL] = {0};
 
 enum {
 	PROP_0,
-	PROP_FARSIGHT_SESSION,
+	PROP_FS_CONFERENCE,
 	PROP_NAME,
 	PROP_CONNECTION,
 	PROP_AUDIO_SRC,
 	PROP_AUDIO_SINK,
 	PROP_VIDEO_SRC,
 	PROP_VIDEO_SINK,
-	PROP_VIDEO_STREAM,
-	PROP_AUDIO_STREAM
+	PROP_VIDEO_SESSION,
+	PROP_AUDIO_SESSION
 };
 
 GType
@@ -113,7 +131,6 @@
 	return type;
 }
 
-
 static void
 purple_media_class_init (PurpleMediaClass *klass)
 {
@@ -124,11 +141,11 @@
 	gobject_class->set_property = purple_media_set_property;
 	gobject_class->get_property = purple_media_get_property;
 
-	g_object_class_install_property(gobject_class, PROP_FARSIGHT_SESSION,
-			g_param_spec_object("farsight-session",
-			"Farsight session",
-			"The FarsightSession associated with this media.",
-			FARSIGHT_TYPE_SESSION,
+	g_object_class_install_property(gobject_class, PROP_FS_CONFERENCE,
+			g_param_spec_object("farsight-conference",
+			"Farsight conference",
+			"The FsConference associated with this media.",
+			FS_TYPE_CONFERENCE,
 			G_PARAM_CONSTRUCT_ONLY | G_PARAM_READWRITE));
 
 	g_object_class_install_property(gobject_class, PROP_NAME,
@@ -172,18 +189,18 @@
 			GST_TYPE_ELEMENT,
 			G_PARAM_READWRITE));
 
-	g_object_class_install_property(gobject_class, PROP_VIDEO_STREAM,
-			g_param_spec_object("video-stream",
+	g_object_class_install_property(gobject_class, PROP_VIDEO_SESSION,
+			g_param_spec_object("video-session",
 			"Video stream",
 			"The FarsightStream used for video",
-			FARSIGHT_TYPE_STREAM,
+			FS_TYPE_SESSION,
 			G_PARAM_READWRITE));
 
-	g_object_class_install_property(gobject_class, PROP_AUDIO_STREAM,
-			g_param_spec_object("audio-stream",
+	g_object_class_install_property(gobject_class, PROP_AUDIO_SESSION,
+			g_param_spec_object("audio-session",
 			"Audio stream",
 			"The FarsightStream used for audio",
-			FARSIGHT_TYPE_STREAM,
+			FS_TYPE_SESSION,
 			G_PARAM_READWRITE));
 
 	purple_media_signals[READY] = g_signal_new("ready", G_TYPE_FROM_CLASS(klass),
@@ -214,6 +231,14 @@
 					 G_SIGNAL_RUN_LAST, 0, NULL, NULL,
 					 g_cclosure_marshal_VOID__VOID,
 					 G_TYPE_NONE, 0);
+	purple_media_signals[CANDIDATES_PREPARED] = g_signal_new("candidates-prepared", G_TYPE_FROM_CLASS(klass),
+					 G_SIGNAL_RUN_LAST, 0, NULL, NULL,
+					 g_cclosure_marshal_VOID__VOID,
+					 G_TYPE_NONE, 0);
+	purple_media_signals[CANDIDATE_PAIR] = g_signal_new("candidate-pair", G_TYPE_FROM_CLASS(klass),
+					 G_SIGNAL_RUN_LAST, 0, NULL, NULL,
+					 purple_smarshal_VOID__BOXED_BOXED,
+					 G_TYPE_NONE, 2, FS_TYPE_CANDIDATE, FS_TYPE_CANDIDATE);
 
 	g_type_class_add_private(klass, sizeof(PurpleMediaPrivate));
 }
@@ -241,11 +266,11 @@
 	media = PURPLE_MEDIA(object);
 
 	switch (prop_id) {
-		case PROP_FARSIGHT_SESSION:
-			if (media->priv->farsight_session)
-				g_object_unref(media->priv->farsight_session);
-			media->priv->farsight_session = g_value_get_object(value);
-			g_object_ref(media->priv->farsight_session);
+		case PROP_FS_CONFERENCE:
+			if (media->priv->conference)
+				g_object_unref(media->priv->conference);
+			media->priv->conference = g_value_get_object(value);
+			g_object_ref(media->priv->conference);
 			break;
 		case PROP_NAME:
 			g_free(media->priv->name);
@@ -259,12 +284,16 @@
 				gst_object_unref(media->priv->audio_src);
 			media->priv->audio_src = g_value_get_object(value);
 			gst_object_ref(media->priv->audio_src);
+			gst_bin_add(GST_BIN(purple_media_get_audio_pipeline(media)),
+				    media->priv->audio_src);
 			break;
 		case PROP_AUDIO_SINK:
 			if (media->priv->audio_sink)
 				gst_object_unref(media->priv->audio_sink);
 			media->priv->audio_sink = g_value_get_object(value);
 			gst_object_ref(media->priv->audio_sink);
+			gst_bin_add(GST_BIN(purple_media_get_audio_pipeline(media)),
+				    media->priv->audio_sink);
 			break;
 		case PROP_VIDEO_SRC:
 			if (media->priv->video_src)
@@ -278,17 +307,17 @@
 			media->priv->video_sink = g_value_get_object(value);
 			gst_object_ref(media->priv->video_sink);
 			break;
-		case PROP_VIDEO_STREAM:
-			if (media->priv->video_stream)
-				g_object_unref(media->priv->video_stream);
-			media->priv->video_stream = g_value_get_object(value);
-			gst_object_ref(media->priv->video_stream);
+		case PROP_VIDEO_SESSION:
+			if (media->priv->video_session)
+				g_object_unref(media->priv->video_session);
+			media->priv->video_session = g_value_get_object(value);
+			gst_object_ref(media->priv->video_session);
 			break;
-		case PROP_AUDIO_STREAM:
-			if (media->priv->audio_stream)
-				g_object_unref(media->priv->audio_stream);
-			media->priv->audio_stream = g_value_get_object(value);
-			gst_object_ref(media->priv->audio_stream);
+		case PROP_AUDIO_SESSION:
+			if (media->priv->audio_session)
+				g_object_unref(media->priv->audio_session);
+			media->priv->audio_session = g_value_get_object(value);
+			gst_object_ref(media->priv->audio_session);
 			break;
 
 		default:	
@@ -306,8 +335,8 @@
 	media = PURPLE_MEDIA(object);
 
 	switch (prop_id) {
-		case PROP_FARSIGHT_SESSION:
-			g_value_set_object(value, media->priv->farsight_session);
+		case PROP_FS_CONFERENCE:
+			g_value_set_object(value, media->priv->conference);
 			break;
 		case PROP_NAME:
 			g_value_set_string(value, media->priv->name);
@@ -327,11 +356,11 @@
 		case PROP_VIDEO_SINK:
 			g_value_set_object(value, media->priv->video_sink);
 			break;
-		case PROP_VIDEO_STREAM:
-			g_value_set_object(value, media->priv->video_stream);
+		case PROP_VIDEO_SESSION:
+			g_value_set_object(value, media->priv->video_session);
 			break;
-		case PROP_AUDIO_STREAM:
-			g_value_set_object(value, media->priv->audio_stream);
+		case PROP_AUDIO_SESSION:
+			g_value_set_object(value, media->priv->audio_session);
 			break;
 
 		default:	
@@ -415,12 +444,12 @@
 GstElement *
 purple_media_get_audio_pipeline(PurpleMedia *media)
 {
-	FarsightStream *stream;
-	g_object_get(G_OBJECT(media), "audio-stream", &stream, NULL);
-printf("stream: %d\n\n\n", stream);
-GstElement *l = farsight_stream_get_pipeline(stream);
-printf("Element: %d\n", l);
-	return farsight_stream_get_pipeline(stream);
+	if (!media->priv->audio_pipeline) {
+		media->priv->audio_pipeline = gst_pipeline_new(media->priv->name);
+		gst_bin_add(GST_BIN(media->priv->audio_pipeline), GST_ELEMENT(media->priv->conference));
+	}
+
+	return media->priv->audio_pipeline;
 }
 
 PurpleConnection *
@@ -631,5 +660,237 @@
 	purple_debug_info("media", "purple_media_audio_init_recv end\n");
 }
 
+static void
+purple_media_new_local_candidate(FsStream *stream,
+				  FsCandidate *local_candidate,
+				  PurpleMedia *media)
+{
+	purple_debug_info("media", "got new local candidate: %s\n", local_candidate->candidate_id);
+	media->priv->local_candidates = g_list_append(media->priv->local_candidates, 
+						      fs_candidate_copy(local_candidate));
+}
+
+static void
+purple_media_candidates_prepared(FsStream *stream, PurpleMedia *media)
+{
+	g_signal_emit(media, purple_media_signals[CANDIDATES_PREPARED], 0);
+}
+
+/* callback called when a pair of transport candidates (local and remote)
+ * has been established */
+static void
+purple_media_candidate_pair_established(FsStream *stream,
+					 FsCandidate *native_candidate,
+					 FsCandidate *remote_candidate,
+					 PurpleMedia *media)
+{
+	media->priv->local_candidate = fs_candidate_copy(native_candidate);
+	media->priv->remote_candidate = fs_candidate_copy(remote_candidate);
+
+	purple_debug_info("media", "candidate pair established\n");
+	g_signal_emit(media, purple_media_signals[CANDIDATE_PAIR], 0,
+		      media->priv->local_candidate,
+		      media->priv->remote_candidate);
+}
+
+static void
+purple_media_src_pad_added(FsStream *stream, GstPad *srcpad,
+			    FsCodec *codec, PurpleMedia *media)
+{
+	GstElement *pipeline = purple_media_get_audio_pipeline(media);
+	GstPad *sinkpad = gst_element_get_static_pad(purple_media_get_audio_sink(media), "ghostsink");
+	purple_debug_info("media", "connecting new src pad: %s\n", 
+			  gst_pad_link(srcpad, sinkpad) == GST_PAD_LINK_OK ? "success" : "failure");
+	gst_element_set_state(pipeline, GST_STATE_PLAYING);
+}
+
+static void
+purple_media_add_stream_internal(PurpleMedia *media, FsSession **session, GList **streams,
+				 GstElement *src, const gchar *who, FsMediaType type,
+				 FsStreamDirection type_direction, const gchar *transmitter)
+{
+	char *cname = NULL;
+	FsParticipant *participant = NULL;
+	GList *l = NULL;
+	FsStream *stream = NULL;
+	FsParticipant *p = NULL;
+	FsStreamDirection *direction = NULL;
+	FsSession *s = NULL;
+
+	if (!*session) {
+		*session = fs_conference_new_session(media->priv->conference, type, NULL);
+		if (src) {
+			GstPad *sinkpad;
+			GstPad *srcpad;
+			g_object_get(*session, "sink-pad", &sinkpad, NULL);
+			srcpad = gst_element_get_static_pad(src, "ghostsrc");
+			purple_debug_info("media", "connecting pad: %s\n", 
+					  gst_pad_link(srcpad, sinkpad) == GST_PAD_LINK_OK
+					  ? "success" : "failure");
+		}
+	}
+	
+	for (l = media->priv->participants; l != NULL; l = g_list_next(l)) {
+		g_object_get(l->data, "cname", cname, NULL);
+		if (!strcmp(cname, who)) {
+			g_free(cname);
+			participant = l->data;
+			break;
+		}
+		g_free(cname);
+	}
+
+	if (!participant) {
+		participant = fs_conference_new_participant(media->priv->conference, (gchar*)who, NULL);
+		media->priv->participants = g_list_prepend(media->priv->participants, participant);
+	}
+	
+	for (l = *streams; l != NULL; l = g_list_next(l)) {
+		g_object_get(l->data, "participant", &p, "direction", &direction, "session", &s, NULL);
+
+		if (participant == p && *session == s) {
+			stream = l->data;
+			break;
+		}
+	}
+
+	if (!stream) {
+		stream = fs_session_new_stream(*session, participant, 
+					       type_direction, transmitter, 0, NULL, NULL);
+		*streams = g_list_prepend(*streams, stream);
+		/* callback for new local candidate (new local candidate retreived) */
+		g_signal_connect(G_OBJECT(stream),
+				 "new-local-candidate", G_CALLBACK(purple_media_new_local_candidate), media);
+		/* callback for source pad added (new stream source ready) */
+		g_signal_connect(G_OBJECT(stream),
+				 "src-pad-added", G_CALLBACK(purple_media_src_pad_added), media);
+		/* callback for local candidates prepared (local candidates ready to send) */
+		g_signal_connect(G_OBJECT(stream), 
+				 "local-candidates-prepared", 
+				 G_CALLBACK(purple_media_candidates_prepared), media);
+		/* callback for new active candidate pair (established connection) */
+		g_signal_connect(G_OBJECT(stream),
+				 "new-active-candidate-pair", 
+				 G_CALLBACK(purple_media_candidate_pair_established), media);
+	} else if (*direction != type_direction) {	
+		/* change direction */
+		g_object_set(stream, "direction", type_direction, NULL);
+	}
+}
+
+void
+purple_media_add_stream(PurpleMedia *media, const gchar *who,
+			PurpleMediaStreamType type,
+			const gchar *transmitter)
+{
+	FsStreamDirection type_direction;
+
+	if (type & PURPLE_MEDIA_AUDIO) {
+		if (type & PURPLE_MEDIA_SEND_AUDIO && type & PURPLE_MEDIA_RECV_AUDIO)
+			type_direction = FS_DIRECTION_BOTH;
+		else if (type & PURPLE_MEDIA_SEND_AUDIO)
+			type_direction = FS_DIRECTION_SEND;
+		else if (type & PURPLE_MEDIA_RECV_AUDIO)
+			type_direction = FS_DIRECTION_RECV;
+		else
+			type_direction = FS_DIRECTION_NONE;
+
+		purple_media_add_stream_internal(media, &media->priv->audio_session,
+						 &media->priv->audio_streams,
+				 		 media->priv->audio_src, who,
+						 FS_MEDIA_TYPE_AUDIO, type_direction,
+						 transmitter);
+	}
+	if (type & PURPLE_MEDIA_VIDEO) {
+		if (type & PURPLE_MEDIA_SEND_VIDEO && type & PURPLE_MEDIA_RECV_VIDEO)
+			type_direction = FS_DIRECTION_BOTH;
+		else if (type & PURPLE_MEDIA_SEND_VIDEO)
+			type_direction = FS_DIRECTION_SEND;
+		else if (type & PURPLE_MEDIA_RECV_VIDEO)
+			type_direction = FS_DIRECTION_RECV;
+		else
+			type_direction = FS_DIRECTION_NONE;
+
+		purple_media_add_stream_internal(media, &media->priv->video_session,
+						 &media->priv->video_streams,
+				 		 media->priv->video_src, who,
+						 FS_MEDIA_TYPE_VIDEO, type_direction,
+						 transmitter);
+	}
+}
+
+void
+purple_media_remove_stream(PurpleMedia *media, const gchar *who, PurpleMediaStreamType type)
+{
+	
+}
+
+static FsStream *
+purple_media_get_audio_stream(PurpleMedia *media, const gchar *name)
+{
+	GList *streams = media->priv->audio_streams;
+	for (; streams; streams = streams->next) {
+		FsParticipant *participant;
+		gchar *cname;
+		g_object_get(streams->data, "participant", &participant, NULL);
+		g_object_get(participant, "cname", &cname, NULL);
+
+		if (!strcmp(cname, name)) {
+			return streams->data;
+		}
+	}
+
+	return NULL;
+}
+
+GList *
+purple_media_get_local_audio_codecs(PurpleMedia *media)
+{
+	GList *codecs;
+	g_object_get(G_OBJECT(media->priv->audio_session), "local-codecs", &codecs, NULL);
+	return codecs;
+}
+
+GList *
+purple_media_get_local_audio_candidates(PurpleMedia *media)
+{
+	return media->priv->local_candidates;
+}
+
+GList *
+purple_media_get_negotiated_audio_codecs(PurpleMedia *media)
+{
+	GList *codec_intersection;
+	g_object_get(media->priv->audio_session, "negotiated-codecs", &codec_intersection, NULL);
+	return codec_intersection;
+}
+
+void
+purple_media_add_remote_audio_candidates(PurpleMedia *media, const gchar *name, GList *remote_candidates)
+{
+	FsStream *stream = purple_media_get_audio_stream(media, name);
+	GList *candidates = remote_candidates;
+	for (; candidates; candidates = candidates->next)
+		fs_stream_add_remote_candidate(stream, candidates->data, NULL);
+}
+
+FsCandidate *
+purple_media_get_local_candidate(PurpleMedia *media)
+{
+	return media->priv->local_candidate;
+}
+
+FsCandidate *
+purple_media_get_remote_candidate(PurpleMedia *media)
+{
+	return media->priv->remote_candidate;
+}
+
+void
+purple_media_set_remote_audio_codecs(PurpleMedia *media, const gchar *name, GList *codecs)
+{
+	fs_stream_set_remote_codecs(purple_media_get_audio_stream(media, name), codecs, NULL);
+}
+
 #endif  /* USE_GSTPROPS */
 #endif  /* USE_FARSIGHT */