diff --git a/configure.ac b/configure.ac index ca21239e2..e7881b084 100644 --- a/configure.ac +++ b/configure.ac @@ -423,10 +423,10 @@ AS_IF([test "x$enable_purple" != xno], [PKG_CHECK_MODULES(_SIPE_MEDIA_RECHECK_PLEASE_IGNORE, [$purple_pkgconfig >= 2.8.0], dnl check purple pkgconfig for gstreamer version [gstreamer_pkgconfig=`$PKG_CONFIG --variable=gstreamer $purple_pkgconfig` - AS_IF([test "x$gstreamer_pkgconfig" == x], - [AS_IF([test "x$purple_pkgconfig" == xpurple-3], + AS_IF([test "x$gstreamer_pkgconfig" = x], + [AS_IF([test "x$purple_pkgconfig" = xpurple-3], [gstreamer_pkgconfig=1.0])]) - AS_IF([test "x$gstreamer_pkgconfig" == x1.0], + AS_IF([test "x$gstreamer_pkgconfig" = x1.0], [gstreamer_pkgconfig="gstreamer-$gstreamer_pkgconfig gstreamer-rtp-$gstreamer_pkgconfig" AC_MSG_NOTICE([using packages "$gstreamer_pkgconfig".]) @@ -535,18 +535,18 @@ AS_IF([test "x$with_purple_vv" != xno], AM_CONDITIONAL(SIPE_WITH_VV, [test "x$with_purple_vv" != xno]) dnl raw data RTP streams enable Lync file transfer -AM_CONDITIONAL(SIPE_HAVE_XDATA, [test "x$ac_have_xdata" == xyes]) -AS_IF([test "x$ac_have_xdata" == xyes], +AM_CONDITIONAL(SIPE_HAVE_XDATA, [test "x$ac_have_xdata" = xyes]) +AS_IF([test "x$ac_have_xdata" = xyes], [AC_DEFINE(HAVE_XDATA, 1, [Define if we have raw data RTP in media backend.])]) dnl enable appshare support -AM_CONDITIONAL(SIPE_HAVE_APPSHARE, [test "x$ac_have_appshare" == xyes]) -AS_IF([test "x$ac_have_appshare" == xyes], +AM_CONDITIONAL(SIPE_HAVE_APPSHARE, [test "x$ac_have_appshare" = xyes]) +AS_IF([test "x$ac_have_appshare" = xyes], [AC_DEFINE(HAVE_APPSHARE, 1, [Define to 1 if we have appshare support.])]) dnl RDP server for sharing local desktop AM_CONDITIONAL(SIPE_HAVE_APPSHARE_SERVER, [test "x$ac_have_appshare_server" = xyes]) -AS_IF([test "x$ac_have_appshare_server" == xyes], +AS_IF([test "x$ac_have_appshare_server" = xyes], [AC_DEFINE(HAVE_APPSHARE_SERVER, 1, [Define if appshare server is enabled.])]) dnl telepathy code parts rely on interfaces that require GValueArray. This diff --git a/src/api/sipe-backend.h b/src/api/sipe-backend.h index 2e69a56a0..d4577ec18 100644 --- a/src/api/sipe-backend.h +++ b/src/api/sipe-backend.h @@ -395,6 +395,7 @@ struct sipe_media_stream { struct sipe_media_call *call; gchar *id; struct ssrc_range *ssrc_range; + guint32 media_source_id; void (*candidate_pairs_established_cb)(struct sipe_media_stream *); void (*read_cb)(struct sipe_media_stream *); diff --git a/src/api/sipe-core.h b/src/api/sipe-core.h index cde0a9cd5..910b11f00 100644 --- a/src/api/sipe-core.h +++ b/src/api/sipe-core.h @@ -499,9 +499,11 @@ sipe_core_media_stream_end(struct sipe_media_stream *stream); * * @param sipe_public (in) SIPE core data. * @param chat_session (in) chat session structure + * @param with_video (in) TRUE if a video call should be created. */ void sipe_core_media_connect_conference(struct sipe_core_public *sipe_public, - struct sipe_chat_session *chat_session); + struct sipe_chat_session *chat_session, + gboolean with_video); /** * Retrieves the media call in progress @@ -683,9 +685,11 @@ void sipe_core_status_set(struct sipe_core_public *sipe_public, * of @c buffer MUST be at least @c SIPE_MSRTP_VSR_HEADER_LEN + * @c SIPE_MSRTP_VSR_ENTRY_LEN. * @param payload_type (in) payload ID of the codec negotiated with the peer. + * @param media_source_id (in) ID of the video stream to request. */ void sipe_core_msrtp_write_video_source_request(guint8 *buffer, - guint8 payload_type); + guint8 payload_type, + guint32 media_source_id); /** * Fills @buffer with customized Payload Content Scalability Information packet diff --git a/src/core/sipe-conf.c b/src/core/sipe-conf.c index 6ed29fda3..95ad4621c 100644 --- a/src/core/sipe-conf.c +++ b/src/core/sipe-conf.c @@ -281,7 +281,8 @@ process_invite_conf_focus_response(struct sipe_core_private *sipe_private, #ifdef HAVE_VV if (session->is_call) sipe_core_media_connect_conference(SIPE_CORE_PUBLIC, - session->chat_session); + session->chat_session, + FALSE); #endif } sipe_xml_free(xn_response); @@ -1169,35 +1170,32 @@ process_conference_av_endpoint(const sipe_xml *endpoint, struct sip_session *session) { const sipe_xml *media; - const gchar *new_entity; - if (!sipe_strequal(user_uri, self_uri)) { - /* We are interested only in our own endpoint data. */ - return; - } + if (sipe_strequal(user_uri, self_uri)) { + const gchar *new_entity = sipe_xml_attribute(endpoint, "entity"); - new_entity = sipe_xml_attribute(endpoint, "entity"); - if (!sipe_strequal(session->audio_video_entity, new_entity)) { - g_free(session->audio_video_entity); - session->audio_video_entity = g_strdup(new_entity); + if (!sipe_strequal(session->audio_video_entity, new_entity)) { + g_free(session->audio_video_entity); + session->audio_video_entity = g_strdup(new_entity); + } } - session->audio_media_id = 0; - media = sipe_xml_child(endpoint, "media"); for (; media; media = sipe_xml_twin(media)) { gchar *type = sipe_xml_data(sipe_xml_child(media, "type")); - if (sipe_strequal(type, "audio")) { - session->audio_media_id = - sipe_xml_int_attribute(media, "id", 0); + if (sipe_strequal(type, "audio") && sipe_strequal(user_uri, self_uri)) { + session->audio_media_id = sipe_xml_int_attribute(media, "id", 0); + } else if (sipe_strequal(type, "video") && session->video_media_source_id == 0) { + const sipe_xml *child = sipe_xml_child(media, "media-source-id"); + const gchar *data = sipe_xml_data(child); + + if (data) { + session->video_media_source_id = atoi(data); + } } g_free(type); - - if (session->audio_media_id != 0) { - break; - } } } @@ -1209,7 +1207,8 @@ call_accept_cb(struct sipe_core_private *sipe_private, struct conf_accept_ctx *c if (session) { sipe_core_media_connect_conference(SIPE_CORE_PUBLIC, - session->chat_session); + session->chat_session, + FALSE); } } diff --git a/src/core/sipe-media.c b/src/core/sipe-media.c index 7fc1794eb..109224a88 100644 --- a/src/core/sipe-media.c +++ b/src/core/sipe-media.c @@ -106,7 +106,7 @@ struct sipe_media_stream_private { #define SIPE_MEDIA_STREAM ((struct sipe_media_stream *) stream_private) #define SIPE_MEDIA_STREAM_PRIVATE ((struct sipe_media_stream_private *) stream) -#define SIPE_MEDIA_STREAM_CONNECTION_TIMEOUT_SECONDS 30 +#define SIPE_MEDIA_STREAM_CONNECTION_TIMEOUT_SECONDS 120 #define SIPE_MEDIA_CALL_RINGING_TIMEOUT_SECONDS 60 #define SIPE_MEDIA_CALL_TIMEOUT_SECONDS 120 @@ -426,13 +426,51 @@ get_encryption_policy(struct sipe_core_private *sipe_private) return result; } +static GList * +get_local_codecs(struct sipe_media_call_private *call_private, + struct sipe_media_stream_private *stream_private) +{ + struct sipe_core_private *sipe_private = call_private->sipe_private; + gboolean is_conference = g_strstr_len(SIPE_MEDIA_CALL->with, + strlen(SIPE_MEDIA_CALL->with), + "app:conf:audio-video:") != NULL; + GList *codecs = sipe_backend_get_local_codecs(SIPE_MEDIA_CALL, + SIPE_MEDIA_STREAM); + GList *i; + + for (i = codecs; i; i = i->next) { + struct sipe_backend_codec *codec = i->data; + char *name = sipe_backend_codec_get_name(codec); + + if (/* Do not announce Theora. Its optional parameters are too + * long, Communicator rejects such SDP message and does not + * support the codec anyway. */ + sipe_strequal(name,"THEORA") || + /* For an unknown reason, MS Lync A/V conferencing server + * does not accept SIPE audio encoded with SIREN. Disable + * the codec when SIPE is connected to a server version + * prior Skype for Business. */ + (!SIPE_CORE_PRIVATE_FLAG_IS(SFB) && is_conference && + sipe_strequal(name,"SIREN"))) { + GList *tmp; + sipe_backend_codec_free(codec); + tmp = i->next; + codecs = g_list_delete_link(codecs, i); + i = tmp; + } + + g_free(name); + } + + return codecs; +} + static struct sdpmedia * media_stream_to_sdpmedia(struct sipe_media_call_private *call_private, struct sipe_media_stream_private *stream_private) { struct sdpmedia *sdpmedia = g_new0(struct sdpmedia, 1); - GList *codecs = sipe_backend_get_local_codecs(SIPE_MEDIA_CALL, - SIPE_MEDIA_STREAM); + GList *codecs = get_local_codecs(call_private, stream_private); SipeEncryptionPolicy encryption_policy = get_encryption_policy(call_private->sipe_private); guint rtcp_port = 0; @@ -809,6 +847,10 @@ update_call_from_remote_sdp(struct sipe_media_call_private* call_private, backend_candidates); sipe_media_candidate_list_free(backend_candidates); + if (sipe_strequal(stream->id, "video") && call_private->conference_session) { + stream->media_source_id = call_private->conference_session->video_media_source_id; + } + SIPE_MEDIA_STREAM_PRIVATE->remote_candidates_and_codecs_set = TRUE; return TRUE; @@ -1386,6 +1428,8 @@ sipe_media_stream_add(struct sipe_media_call *call, const gchar *id, ssrc_count); } + SIPE_MEDIA_STREAM->media_source_id = SIPE_MSRTP_VSR_SOURCE_ANY; + SIPE_MEDIA_STREAM->backend_private = sipe_backend_media_add_stream(SIPE_MEDIA_STREAM, type, ice_version, @@ -1529,7 +1573,8 @@ conference_audio_muted_cb(struct sipe_media_stream *stream, gboolean is_muted) } void sipe_core_media_connect_conference(struct sipe_core_public *sipe_public, - struct sipe_chat_session *chat_session) + struct sipe_chat_session *chat_session, + gboolean with_video) { struct sipe_core_private *sipe_private = SIPE_CORE_PRIVATE; struct sipe_media_call_private *call_private; @@ -1567,6 +1612,8 @@ void sipe_core_media_connect_conference(struct sipe_core_public *sipe_public, call_private->conference_session = session; SIPE_MEDIA_CALL->call_reject_cb = av_call_reject_cb; + g_free(av_uri); + stream = sipe_media_stream_add(SIPE_MEDIA_CALL, "audio", SIPE_MEDIA_AUDIO, call_private->ice_version, @@ -1577,11 +1624,23 @@ void sipe_core_media_connect_conference(struct sipe_core_public *sipe_public, _("Error creating audio stream")); sipe_media_hangup(call_private); + return; } stream->mute_cb = conference_audio_muted_cb; - g_free(av_uri); + if (with_video) { + stream = sipe_media_stream_add(SIPE_MEDIA_CALL, "video", + SIPE_MEDIA_VIDEO, + call_private->ice_version, + TRUE, VIDEO_SSRC_COUNT); + if (!stream) { + sipe_backend_notify_error(sipe_public, + _("Error occurred"), + _("Error creating video stream")); + sipe_media_hangup(call_private); + } + } // Processing continues in stream_initialized_cb } diff --git a/src/core/sipe-msrtp.c b/src/core/sipe-msrtp.c index d342f23d4..06945c21e 100644 --- a/src/core/sipe-msrtp.c +++ b/src/core/sipe-msrtp.c @@ -104,7 +104,8 @@ enum void sipe_core_msrtp_write_video_source_request(guint8 *buffer, - guint8 payload_type) + guint8 payload_type, + guint32 media_source_id) { static guint8 bit_rate_histogram[] = { 0, 1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0 @@ -123,7 +124,7 @@ sipe_core_msrtp_write_video_source_request(guint8 *buffer, SIPE_WRITE_UINT16_BE(buffer, SIPE_MSRTP_VSR_HEADER_LEN + SIPE_MSRTP_VSR_ENTRY_LEN); // Requested Media Source ID - SIPE_WRITE_UINT32_BE(buffer, SIPE_MSRTP_VSR_SOURCE_ANY); + SIPE_WRITE_UINT32_BE(buffer, media_source_id); SIPE_WRITE_UINT16_BE(buffer, 1); // Request Id SIPE_WRITE_UINT16_BE(buffer, 0); // Reserve1 SIPE_WRITE_UINT8(buffer, 0); // Version diff --git a/src/core/sipe-session.h b/src/core/sipe-session.h index d0faedc84..ad90b36ed 100644 --- a/src/core/sipe-session.h +++ b/src/core/sipe-session.h @@ -70,6 +70,7 @@ struct sip_session { GHashTable *conf_unconfirmed_messages; gchar *audio_video_entity; guint audio_media_id; + guint video_media_source_id; /* * Media call related fields diff --git a/src/purple/purple-chat.c b/src/purple/purple-chat.c index bfe66c482..9435a5260 100644 --- a/src/purple/purple-chat.c +++ b/src/purple/purple-chat.c @@ -257,13 +257,26 @@ static void sipe_purple_chat_menu_lock_cb(SIPE_UNUSED_PARAMETER PurpleChat *chat #ifdef HAVE_VV -static void sipe_purple_chat_menu_join_call_cb(SIPE_UNUSED_PARAMETER PurpleChat *chat, - PurpleConversation *conv) +static void +join_conference_call(PurpleConversation *conv, gboolean with_video) { struct sipe_core_public *sipe_public = PURPLE_CONV_TO_SIPE_CORE_PUBLIC; struct sipe_chat_session *chat_session = sipe_purple_chat_get_session(conv); - SIPE_DEBUG_INFO("sipe_purple_chat_join_call_cb: %p %p", conv, chat_session); - sipe_core_media_connect_conference(sipe_public, chat_session); + sipe_core_media_connect_conference(sipe_public, chat_session, with_video); +} + +static void sipe_purple_chat_menu_join_call_cb(SIPE_UNUSED_PARAMETER PurpleChat *chat, + PurpleConversation *conv) +{ + SIPE_DEBUG_INFO("sipe_purple_chat_join_call_cb: %p", conv); + join_conference_call(conv, FALSE); +} + +static void sipe_purple_chat_menu_join_video_call_cb(SIPE_UNUSED_PARAMETER PurpleChat *chat, + PurpleConversation *conv) +{ + SIPE_DEBUG_INFO("sipe_purple_chat_join_video_call_cb: %p", conv); + join_conference_call(conv, TRUE); } #ifdef HAVE_APPSHARE @@ -349,12 +362,15 @@ sipe_purple_chat_menu(PurpleChat *chat) case SIPE_CHAT_TYPE_MULTIPARTY: #ifdef HAVE_VV if (!sipe_core_media_get_call(PURPLE_CONV_TO_SIPE_CORE_PUBLIC)) { - act = NULL; + act = purple_menu_action_new(_("Join conference video call"), + PURPLE_CALLBACK(sipe_purple_chat_menu_join_video_call_cb), + conv, NULL); + menu = g_list_prepend(menu, act); + act = purple_menu_action_new(_("Join conference call"), PURPLE_CALLBACK(sipe_purple_chat_menu_join_call_cb), conv, NULL); - if (act) - menu = g_list_prepend(menu, act); + menu = g_list_prepend(menu, act); } #ifdef HAVE_APPSHARE role = sipe_core_conf_get_appshare_role(PURPLE_CONV_TO_SIPE_CORE_PUBLIC, diff --git a/src/purple/purple-media.c b/src/purple/purple-media.c index d589009e9..8f62fac97 100644 --- a/src/purple/purple-media.c +++ b/src/purple/purple-media.c @@ -547,7 +547,7 @@ stream_writable_cb(SIPE_UNUSED_PARAMETER PurpleMediaManager *manager, static gboolean write_ms_h264_video_source_request(GstRTCPBuffer *buffer, guint32 ssrc, - guint8 payload_type) + guint8 payload_type, guint32 media_source_id) { GstRTCPPacket packet; guint8 *fci_data; @@ -558,7 +558,7 @@ write_ms_h264_video_source_request(GstRTCPBuffer *buffer, guint32 ssrc, gst_rtcp_packet_fb_set_type(&packet, GST_RTCP_PSFB_TYPE_AFB); gst_rtcp_packet_fb_set_sender_ssrc(&packet, ssrc); - gst_rtcp_packet_fb_set_media_ssrc(&packet, SIPE_MSRTP_VSR_SOURCE_ANY); + gst_rtcp_packet_fb_set_media_ssrc(&packet, media_source_id); if (!gst_rtcp_packet_fb_set_fci_length(&packet, SIPE_MSRTP_VSR_FCI_WORDLEN)) { @@ -568,7 +568,8 @@ write_ms_h264_video_source_request(GstRTCPBuffer *buffer, guint32 ssrc, fci_data = gst_rtcp_packet_fb_get_fci(&packet); - sipe_core_msrtp_write_video_source_request(fci_data, payload_type); + sipe_core_msrtp_write_video_source_request(fci_data, payload_type, + media_source_id); return TRUE; } @@ -590,12 +591,14 @@ on_sending_rtcp_cb(SIPE_UNUSED_PARAMETER GObject *rtpsession, if (sipe_strequal(send_codec->encoding_name, "H264")) { GstRTCPBuffer rtcp_buffer = GST_RTCP_BUFFER_INIT; guint32 ssrc; + struct sipe_media_stream *stream; g_object_get(fssession, "ssrc", &ssrc, NULL); + stream = g_object_get_data(G_OBJECT(fssession), "sipe-media-stream"); gst_rtcp_buffer_map(buffer, GST_MAP_READWRITE, &rtcp_buffer); was_changed = write_ms_h264_video_source_request(&rtcp_buffer, - ssrc, send_codec->id); + ssrc, send_codec->id, stream->media_source_id); gst_rtcp_buffer_unmap(&rtcp_buffer); } @@ -776,6 +779,10 @@ gst_bus_cb(GstBus *bus, GstMessage *msg, struct sipe_media_stream *stream) stream->ssrc_range->begin, NULL); } + g_object_set_data(G_OBJECT(fssession), + "sipe-media-stream", + stream); + g_object_get(fssession, "media-type", &media_type, NULL); if (media_type == FS_MEDIA_TYPE_VIDEO) { @@ -1204,33 +1211,12 @@ sipe_backend_get_local_codecs(struct sipe_media_call *media, GList *codecs = purple_media_get_codecs(media->backend_private->m, stream->id); GList *i = codecs; - gboolean is_conference = (g_strstr_len(media->with, strlen(media->with), - "app:conf:audio-video:") != NULL); - /* - * Do not announce Theora. Its optional parameters are too long, - * Communicator rejects such SDP message and does not support the codec - * anyway. - * - * For some yet unknown reason, A/V conferencing server does not accept - * voice stream sent by SIPE when SIREN codec is in use. Nevertheless, - * we are able to decode incoming SIREN from server and with MSOC - * client, bidirectional call using the codec works. Until resolved, - * do not try to negotiate SIREN usage when conferencing. PCMA or PCMU - * seems to work properly in this scenario. - */ while (i) { PurpleMediaCodec *codec = i->data; gchar *encoding_name = purple_media_codec_get_encoding_name(codec); - if (sipe_strequal(encoding_name,"THEORA") || - (is_conference && sipe_strequal(encoding_name,"SIREN"))) { - GList *tmp; - g_object_unref(codec); - tmp = i->next; - codecs = g_list_delete_link(codecs, i); - i = tmp; - } else if (sipe_strequal(encoding_name, "H264")) { + if (sipe_strequal(encoding_name, "H264")) { /* * Sanitize H264 codec: * - the encoding name must be "X-H264UC"