Mercurial > pidgin
changeset 26997:72bcdcb0629f
Add Gmail video support.
Thanks to Eion for all his testing help.
author | Mike Ruprecht <maiku@soc.pidgin.im> |
---|---|
date | Tue, 02 Jun 2009 05:00:20 +0000 |
parents | 4605fe3c43bb |
children | f0621e47ccf3 |
files | libpurple/media.c libpurple/protocols/jabber/disco.c libpurple/protocols/jabber/google.c libpurple/protocols/jabber/google.h libpurple/protocols/jabber/jabber.c libpurple/protocols/jabber/jabber.h libpurple/protocols/jabber/presence.c |
diffstat | 7 files changed, 249 insertions(+), 49 deletions(-) [+] |
line wrap: on
line diff
--- a/libpurple/media.c Mon Jun 01 10:33:38 2009 +0000 +++ b/libpurple/media.c Tue Jun 02 05:00:20 2009 +0000 @@ -43,6 +43,7 @@ #ifdef USE_VV #include <gst/farsight/fs-conference-iface.h> +#include <gst/farsight/fs-element-added-notifier.h> /** @copydoc _PurpleMediaSession */ typedef struct _PurpleMediaSession PurpleMediaSession; @@ -2380,6 +2381,18 @@ stream->connected_cb_id = purple_timeout_add(0, (GSourceFunc)purple_media_connected_cb, stream); } + +static void +purple_media_element_added_cb(FsElementAddedNotifier *self, + GstBin *bin, GstElement *element, gpointer user_data) +{ + /* + * Hack to make H264 work with Gmail video. + */ + if (!strncmp(GST_ELEMENT_NAME(element), "x264", 4)) { + g_object_set(GST_OBJECT(element), "cabac", FALSE, NULL); + } +} #endif /* USE_VV */ gboolean @@ -2456,6 +2469,19 @@ g_object_set(G_OBJECT(session->session), "no-rtcp-timeout", 0, NULL); + /* + * Hack to make x264 work with Gmail video. + */ + if (is_nice && !strcmp(sess_id, "google-video")) { + FsElementAddedNotifier *notifier = + fs_element_added_notifier_new(); + g_signal_connect(G_OBJECT(notifier), "element-added", + G_CALLBACK(purple_media_element_added_cb), + stream); + fs_element_added_notifier_add(notifier, + GST_BIN(media->priv->conference)); + } + fs_codec_list_destroy(codec_conf); session->id = g_strdup(sess_id); @@ -2670,7 +2696,8 @@ PurpleMediaStream *stream; g_return_val_if_fail(PURPLE_IS_MEDIA(media), NULL); stream = purple_media_get_stream(media, sess_id, participant); - return purple_media_candidate_list_from_fs(stream->local_candidates); + return stream ? purple_media_candidate_list_from_fs( + stream->local_candidates) : NULL; #else return NULL; #endif
--- a/libpurple/protocols/jabber/disco.c Mon Jun 01 10:33:38 2009 +0000 +++ b/libpurple/protocols/jabber/disco.c Tue Jun 02 05:00:20 2009 +0000 @@ -148,6 +148,18 @@ */ xmlnode *feature = xmlnode_new_child(query, "feature"); xmlnode_set_attrib(feature, "var", "http://www.google.com/xmpp/protocol/voice/v1"); + } else if (g_str_equal(node, CAPS0115_NODE "#" "video-v1")) { + /* + * HUGE HACK! We advertise this ext (see jabber_presence_create_js + * where we add <c/> to the <presence/>) for the Google Talk + * clients that don't actually check disco#info features. + * + * This specific feature is redundant but is what + * node='http://mail.google.com/xmpp/client/caps', ver='1.1' + * advertises as 'video-v1'. + */ + xmlnode *feature = xmlnode_new_child(query, "feature"); + xmlnode_set_attrib(feature, "var", "http://www.google.com/xmpp/protocol/video/v1"); #endif } else { xmlnode *error, *inf;
--- a/libpurple/protocols/jabber/google.c Mon Jun 01 10:33:38 2009 +0000 +++ b/libpurple/protocols/jabber/google.c Tue Jun 02 05:00:20 2009 +0000 @@ -36,6 +36,9 @@ #ifdef USE_VV +#define NS_GOOGLE_VIDEO "http://www.google.com/session/video" +#define NS_GOOGLE_PHONE "http://www.google.com/session/phone" + typedef struct { char *id; char *initiator; @@ -55,6 +58,7 @@ PurpleMedia *media; JabberStream *js; char *remote_jid; + gboolean video; } GoogleSession; static gboolean @@ -104,9 +108,13 @@ google_session_send_candidates(PurpleMedia *media, gchar *session_id, gchar *participant, GoogleSession *session) { - GList *candidates = purple_media_get_local_candidates(session->media, "google-voice", - session->remote_jid); + GList *candidates = purple_media_get_local_candidates( + session->media, session_id, session->remote_jid); PurpleMediaCandidate *transport; + gboolean video = FALSE; + + if (!strcmp(session_id, "google-video")) + video = TRUE; for (;candidates;candidates = candidates->next) { JabberIq *iq; @@ -114,11 +122,10 @@ PurpleMediaCandidateType type; xmlnode *sess; xmlnode *candidate; + guint component_id; transport = (PurpleMediaCandidate*)(candidates->data); - - if (purple_media_candidate_get_component_id(transport) - != PURPLE_MEDIA_COMPONENT_RTP) - continue; + component_id = purple_media_candidate_get_component_id( + transport); iq = jabber_iq_new(session->js, JABBER_IQ_SET); sess = google_session_create_xmlnode(session, "candidates"); @@ -139,7 +146,11 @@ xmlnode_set_attrib(candidate, "address", ip); xmlnode_set_attrib(candidate, "port", port); - xmlnode_set_attrib(candidate, "name", "rtp"); + xmlnode_set_attrib(candidate, "name", + component_id == PURPLE_MEDIA_COMPONENT_RTP ? + video ? "video_rtp" : "rtp" : + component_id == PURPLE_MEDIA_COMPONENT_RTCP ? + video ? "video_rtcp" : "rtcp" : "none"); xmlnode_set_attrib(candidate, "username", username); /* * As of this writing, Farsight 2 in Google compatibility @@ -205,13 +216,38 @@ google_session_send_candidates(session->media, "google-voice", session->remote_jid, session); + google_session_send_candidates(session->media, + "google-video", session->remote_jid, + session); xmlnode_set_attrib(iq->node, "to", session->remote_jid); xmlnode_set_attrib(iq->node, "from", me); sess = google_session_create_xmlnode(session, "accept"); } xmlnode_insert_child(iq->node, sess); desc = xmlnode_new_child(sess, "description"); - xmlnode_set_namespace(desc, "http://www.google.com/session/phone"); + if (session->video) + xmlnode_set_namespace(desc, NS_GOOGLE_VIDEO); + else + xmlnode_set_namespace(desc, NS_GOOGLE_PHONE); + + codecs = purple_media_get_codecs(media, "google-video"); + + for (iter = codecs; iter; iter = g_list_next(iter)) { + PurpleMediaCodec *codec = (PurpleMediaCodec*)iter->data; + gchar *id = g_strdup_printf("%d", + purple_media_codec_get_id(codec)); + gchar *encoding_name = + purple_media_codec_get_encoding_name(codec); + payload = xmlnode_new_child(desc, "payload-type"); + xmlnode_set_attrib(payload, "id", id); + xmlnode_set_attrib(payload, "name", encoding_name); + xmlnode_set_attrib(payload, "width", "320"); + xmlnode_set_attrib(payload, "height", "200"); + xmlnode_set_attrib(payload, "framerate", "30"); + g_free(encoding_name); + g_free(id); + } + purple_media_codec_list_free(codecs); codecs = purple_media_get_codecs(media, "google-voice"); @@ -224,6 +260,8 @@ gchar *clock_rate = g_strdup_printf("%d", purple_media_codec_get_clock_rate(codec)); payload = xmlnode_new_child(desc, "payload-type"); + if (session->video) + xmlnode_set_namespace(payload, NS_GOOGLE_PHONE); xmlnode_set_attrib(payload, "id", id); xmlnode_set_attrib(payload, "name", encoding_name); xmlnode_set_attrib(payload, "clockrate", clock_rate); @@ -235,10 +273,14 @@ jabber_iq_send(iq); - if (is_initiator) + if (is_initiator) { google_session_send_candidates(session->media, "google-voice", session->remote_jid, session); + google_session_send_candidates(session->media, + "google-video", session->remote_jid, + session); + } g_signal_handlers_disconnect_by_func(G_OBJECT(session->media), G_CALLBACK(google_session_ready), session); @@ -339,6 +381,9 @@ session->js = js; session->remote_jid = jid; + if (type & PURPLE_MEDIA_VIDEO) + session->video = TRUE; + session->media = purple_media_manager_create_media( purple_media_manager_get(), purple_connection_get_account(js->gc), @@ -349,8 +394,12 @@ params = jabber_google_session_get_params(js, &num_params); if (purple_media_add_stream(session->media, "google-voice", - session->remote_jid, PURPLE_MEDIA_AUDIO, - TRUE, "nice", num_params, params) == FALSE) { + session->remote_jid, PURPLE_MEDIA_AUDIO, + TRUE, "nice", num_params, params) == FALSE || + (session->video && purple_media_add_stream( + session->media, "google-video", + session->remote_jid, PURPLE_MEDIA_VIDEO, + TRUE, "nice", num_params, params) == FALSE)) { purple_media_error(session->media, "Error adding stream."); purple_media_stream_info(session->media, PURPLE_MEDIA_INFO_HANGUP, NULL, NULL, TRUE); @@ -378,10 +427,10 @@ google_session_handle_initiate(JabberStream *js, GoogleSession *session, xmlnode *sess, const char *iq_id) { JabberIq *result; - GList *codecs = NULL; + GList *codecs = NULL, *video_codecs = NULL; xmlnode *desc_element, *codec_element; PurpleMediaCodec *codec; - const char *id, *encoding_name, *clock_rate; + const char *xmlns; GParameter *params; guint num_params; @@ -390,6 +439,19 @@ return; } + desc_element = xmlnode_get_child(sess, "description"); + xmlns = xmlnode_get_namespace(desc_element); + + if (purple_strequal(xmlns, NS_GOOGLE_PHONE)) + session->video = FALSE; + else if (purple_strequal(xmlns, NS_GOOGLE_VIDEO)) + session->video = TRUE; + else { + purple_debug_error("jabber", "Received initiate with " + "invalid namespace %s.\n", xmlns); + return; + } + session->media = purple_media_manager_create_media( purple_media_manager_get(), purple_connection_get_account(js->gc), @@ -401,7 +463,11 @@ if (purple_media_add_stream(session->media, "google-voice", session->remote_jid, PURPLE_MEDIA_AUDIO, FALSE, - "nice", num_params, params) == FALSE) { + "nice", num_params, params) == FALSE || + (session->video && purple_media_add_stream( + session->media, "google-video", + session->remote_jid, PURPLE_MEDIA_VIDEO, + FALSE, "nice", num_params, params) == FALSE)) { purple_media_error(session->media, "Error adding stream."); purple_media_stream_info(session->media, PURPLE_MEDIA_INFO_HANGUP, NULL, NULL, TRUE); @@ -412,23 +478,55 @@ g_free(params); - desc_element = xmlnode_get_child(sess, "description"); + for (codec_element = xmlnode_get_child(desc_element, "payload-type"); + codec_element; codec_element = codec_element->next) { + const char *id, *encoding_name, *clock_rate, + *width, *height, *framerate; + gboolean video; + if (codec_element->name && + strcmp(codec_element->name, "payload-type")) + continue; - for (codec_element = xmlnode_get_child(desc_element, "payload-type"); - codec_element; - codec_element = xmlnode_get_next_twin(codec_element)) { + xmlns = xmlnode_get_namespace(codec_element); encoding_name = xmlnode_get_attrib(codec_element, "name"); id = xmlnode_get_attrib(codec_element, "id"); - clock_rate = xmlnode_get_attrib(codec_element, "clockrate"); + + if (!session->video || + (xmlns && !strcmp(xmlns, NS_GOOGLE_PHONE))) { + clock_rate = xmlnode_get_attrib( + codec_element, "clockrate"); + video = FALSE; + } else { + width = xmlnode_get_attrib(codec_element, "width"); + height = xmlnode_get_attrib(codec_element, "height"); + framerate = xmlnode_get_attrib( + codec_element, "framerate"); + clock_rate = "90000"; + video = TRUE; + } if (id) { - codec = purple_media_codec_new(atoi(id), encoding_name, PURPLE_MEDIA_AUDIO, - clock_rate ? atoi(clock_rate) : 0); - codecs = g_list_append(codecs, codec); + codec = purple_media_codec_new(atoi(id), encoding_name, + video ? PURPLE_MEDIA_VIDEO : + PURPLE_MEDIA_AUDIO, + clock_rate ? atoi(clock_rate) : 0); + if (video) + video_codecs = g_list_append( + video_codecs, codec); + else + codecs = g_list_append(codecs, codec); } } - purple_media_set_remote_codecs(session->media, "google-voice", session->remote_jid, codecs); + if (codecs) + purple_media_set_remote_codecs(session->media, "google-voice", + session->remote_jid, codecs); + if (video_codecs) + purple_media_set_remote_codecs(session->media, "google-video", + session->remote_jid, video_codecs); + + purple_media_codec_list_free(codecs); + purple_media_codec_list_free(video_codecs); g_signal_connect_swapped(G_OBJECT(session->media), "accepted", G_CALLBACK(google_session_ready), session); @@ -442,8 +540,6 @@ g_signal_connect(G_OBJECT(session->media), "stream-info", G_CALLBACK(google_session_stream_info_cb), session); - purple_media_codec_list_free(codecs); - result = jabber_iq_new(js, JABBER_IQ_RESULT); jabber_iq_set_id(result, iq_id); xmlnode_set_attrib(result->node, "to", session->remote_jid); @@ -454,19 +550,22 @@ google_session_handle_candidates(JabberStream *js, GoogleSession *session, xmlnode *sess, const char *iq_id) { JabberIq *result; - GList *list = NULL; + GList *list = NULL, *video_list = NULL; xmlnode *cand; static int name = 0; char n[4]; - for (cand = xmlnode_get_child(sess, "candidate"); cand; cand = xmlnode_get_next_twin(cand)) { + for (cand = xmlnode_get_child(sess, "candidate"); cand; + cand = xmlnode_get_next_twin(cand)) { PurpleMediaCandidate *info; + const gchar *cname = xmlnode_get_attrib(cand, "name"); const gchar *type = xmlnode_get_attrib(cand, "type"); const gchar *protocol = xmlnode_get_attrib(cand, "protocol"); const gchar *address = xmlnode_get_attrib(cand, "address"); const gchar *port = xmlnode_get_attrib(cand, "port"); + guint component_id; - if (type && address && port) { + if (cname && type && address && port) { PurpleMediaCandidateType candidate_type; g_snprintf(n, sizeof(n), "S%d", name++); @@ -480,7 +579,13 @@ else candidate_type = PURPLE_MEDIA_CANDIDATE_TYPE_HOST; - info = purple_media_candidate_new(n, PURPLE_MEDIA_COMPONENT_RTP, + if (purple_strequal(cname, "rtcp") || + purple_strequal(cname, "video_rtcp")) + component_id = PURPLE_MEDIA_COMPONENT_RTCP; + else + component_id = PURPLE_MEDIA_COMPONENT_RTP; + + info = purple_media_candidate_new(n, component_id, candidate_type, purple_strequal(protocol, "udp") ? PURPLE_MEDIA_NETWORK_PROTOCOL_UDP : @@ -489,12 +594,23 @@ atoi(port)); g_object_set(info, "username", xmlnode_get_attrib(cand, "username"), "password", xmlnode_get_attrib(cand, "password"), NULL); - list = g_list_append(list, info); + if (!strncmp(cname, "video_", 6)) + video_list = g_list_append(video_list, info); + else + list = g_list_append(list, info); } } - purple_media_add_remote_candidates(session->media, "google-voice", session->remote_jid, list); + if (list) + purple_media_add_remote_candidates( + session->media, "google-voice", + session->remote_jid, list); + if (video_list) + purple_media_add_remote_candidates( + session->media, "google-video", + session->remote_jid, video_list); purple_media_candidate_list_free(list); + purple_media_candidate_list_free(video_list); result = jabber_iq_new(js, JABBER_IQ_RESULT); jabber_iq_set_id(result, iq_id); @@ -506,28 +622,57 @@ google_session_handle_accept(JabberStream *js, GoogleSession *session, xmlnode *sess, const char *iq_id) { xmlnode *desc_element = xmlnode_get_child(sess, "description"); - xmlnode *codec_element = xmlnode_get_child(desc_element, "payload-type"); - GList *codecs = NULL; + xmlnode *codec_element = xmlnode_get_child( + desc_element, "payload-type"); + GList *codecs = NULL, *video_codecs = NULL; JabberIq *result = NULL; + const gchar *xmlns = xmlnode_get_namespace(desc_element); + gboolean video = (xmlns && !strcmp(xmlns, NS_GOOGLE_VIDEO)); + + for (; codec_element; codec_element = codec_element->next) { + const gchar *xmlns, *encoding_name, *id, + *clock_rate, *width, *height, *framerate; + gboolean video_codec = FALSE; + + if (!purple_strequal(codec_element->name, "payload-type")) + continue; - for (; codec_element; codec_element = - xmlnode_get_next_twin(codec_element)) { - const gchar *encoding_name = - xmlnode_get_attrib(codec_element, "name"); - const gchar *id = xmlnode_get_attrib(codec_element, "id"); - const gchar *clock_rate = - xmlnode_get_attrib(codec_element, "clockrate"); + xmlns = xmlnode_get_namespace(codec_element); + encoding_name = xmlnode_get_attrib(codec_element, "name"); + id = xmlnode_get_attrib(codec_element, "id"); + + if (!video || purple_strequal(xmlns, NS_GOOGLE_PHONE)) + clock_rate = xmlnode_get_attrib( + codec_element, "clockrate"); + else { + clock_rate = "90000"; + width = xmlnode_get_attrib(codec_element, "width"); + height = xmlnode_get_attrib(codec_element, "height"); + framerate = xmlnode_get_attrib( + codec_element, "framerate"); + video_codec = TRUE; + } if (id && encoding_name) { - PurpleMediaCodec *codec = purple_media_codec_new(atoi(id), - encoding_name, PURPLE_MEDIA_AUDIO, + PurpleMediaCodec *codec = purple_media_codec_new( + atoi(id), encoding_name, + video_codec ? PURPLE_MEDIA_VIDEO : + PURPLE_MEDIA_AUDIO, clock_rate ? atoi(clock_rate) : 0); - codecs = g_list_append(codecs, codec); + if (video_codec) + video_codecs = g_list_append( + video_codecs, codec); + else + codecs = g_list_append(codecs, codec); } } - purple_media_set_remote_codecs(session->media, "google-voice", - session->remote_jid, codecs); + if (codecs) + purple_media_set_remote_codecs(session->media, "google-voice", + session->remote_jid, codecs); + if (video_codecs) + purple_media_set_remote_codecs(session->media, "google-video", + session->remote_jid, video_codecs); purple_media_stream_info(session->media, PURPLE_MEDIA_INFO_ACCEPT, NULL, NULL, FALSE);
--- a/libpurple/protocols/jabber/google.h Mon Jun 01 10:33:38 2009 +0000 +++ b/libpurple/protocols/jabber/google.h Tue Jun 02 05:00:20 2009 +0000 @@ -28,6 +28,7 @@ #include "media.h" #define GOOGLE_VOICE_CAP "http://www.google.com/xmpp/protocol/voice/v1" +#define GOOGLE_VIDEO_CAP "http://www.google.com/xmpp/protocol/video/v1" #define GOOGLE_JINGLE_INFO_NAMESPACE "google:jingleinfo" void jabber_gmail_init(JabberStream *js);
--- a/libpurple/protocols/jabber/jabber.c Mon Jun 01 10:33:38 2009 +0000 +++ b/libpurple/protocols/jabber/jabber.c Tue Jun 02 05:00:20 2009 +0000 @@ -2949,7 +2949,7 @@ return (caps & (PURPLE_MEDIA_CAPS_AUDIO | PURPLE_MEDIA_CAPS_AUDIO_SINGLE_DIRECTION)); } -static gboolean +gboolean jabber_video_enabled(JabberStream *js, const char *namespace) { PurpleMediaManager *manager = purple_media_manager_get(); @@ -3189,8 +3189,12 @@ caps |= PURPLE_MEDIA_CAPS_MODIFY_SESSION | PURPLE_MEDIA_CAPS_CHANGE_DIRECTION; } - if (jabber_resource_has_capability(jbr, GOOGLE_VOICE_CAP)) + if (jabber_resource_has_capability(jbr, GOOGLE_VOICE_CAP)) { caps |= PURPLE_MEDIA_CAPS_AUDIO; + if (jabber_resource_has_capability(jbr, + GOOGLE_VIDEO_CAP)) + caps |= PURPLE_MEDIA_CAPS_AUDIO_VIDEO; + } return caps; }
--- a/libpurple/protocols/jabber/jabber.h Mon Jun 01 10:33:38 2009 +0000 +++ b/libpurple/protocols/jabber/jabber.h Tue Jun 02 05:00:20 2009 +0000 @@ -366,6 +366,7 @@ GList *jabber_actions(PurplePlugin *plugin, gpointer context); gboolean jabber_audio_enabled(JabberStream *js, const char *unused); +gboolean jabber_video_enabled(JabberStream *js, const char *unused); gboolean jabber_initiate_media(PurpleAccount *account, const char *who, PurpleMediaSessionType type); PurpleMediaCaps jabber_get_media_caps(PurpleAccount *account, const char *who);
--- a/libpurple/protocols/jabber/presence.c Mon Jun 01 10:33:38 2009 +0000 +++ b/libpurple/protocols/jabber/presence.c Tue Jun 02 05:00:20 2009 +0000 @@ -245,6 +245,7 @@ { xmlnode *show, *status, *presence, *pri, *c; const char *show_string = NULL; + gboolean audio_enabled, video_enabled; presence = xmlnode_new("presence"); @@ -300,9 +301,18 @@ * just assume that if we specify a 'voice-v1' ext (ignoring that * these are to be assigned no semantic value), we support receiving voice * calls. + * + * Ditto for 'video-v1'. */ - if (jabber_audio_enabled(js, NULL /* unused */)) + audio_enabled = jabber_audio_enabled(js, NULL /* unused */); + video_enabled = jabber_video_enabled(js, NULL /* unused */); + + if (audio_enabled && video_enabled) + xmlnode_set_attrib(c, "ext", "voice-v1 video-v1"); + else if (audio_enabled) xmlnode_set_attrib(c, "ext", "voice-v1"); + else if (video_enabled) + xmlnode_set_attrib(c, "ext", "video-v1"); #endif return presence;