6 * Copyright (C) 2010-2019 SIPE Project <http://sipe.sourceforge.net/>
8 * This program is free software; you can redistribute it and/or modify
9 * it under the terms of the GNU General Public License as published by
10 * the Free Software Foundation; either version 2 of the License, or
11 * (at your option) any later version.
13 * This program is distributed in the hope that it will be useful,
14 * but WITHOUT ANY WARRANTY; without even the implied warranty of
15 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
16 * GNU General Public License for more details.
18 * You should have received a copy of the GNU General Public License
19 * along with this program; if not, write to the Free Software
20 * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
28 #include "glib/gstdio.h"
35 #include "sipe-common.h"
37 #include "mediamanager.h"
42 /* wrappers for write() & friends for socket handling */
43 #include "win32/win32dep.h"
46 #if PURPLE_VERSION_CHECK(3,0,0)
47 /* nothing here yet */
49 #define purple_config_dir purple_user_dir
52 #include "sipe-backend.h"
53 #include "sipe-core.h"
55 #include "purple-private.h"
58 * GStreamer interfaces fail to compile on ARM architecture with -Wcast-align
60 * Diagnostic #pragma was added in GCC 4.2.0
63 #if ((__GNUC__ == 4) && (__GNUC_MINOR__ >= 2)) || (__GNUC__ >= 5)
64 #if defined(__ARMEL__) || defined(__ARMEB__) || defined(__hppa__) || defined(__mips__) || defined(__sparc__) || (defined(__powerpc__) && defined(__NO_FPRS__))
65 #pragma GCC diagnostic ignored "-Wcast-align"
70 #include "media-gst.h"
71 #include <gst/rtp/gstrtcpbuffer.h>
72 #include <farstream/fs-session.h>
74 struct sipe_backend_media
{
77 * Number of media streams that were not yet locally accepted or rejected.
79 guint unconfirmed_streams
;
82 struct sipe_backend_media_stream
{
83 gboolean local_on_hold
;
84 gboolean remote_on_hold
;
86 gboolean initialized_cb_was_fired
;
91 gulong on_sending_rtcp_cb_id
;
95 sipe_backend_media_stream_free(struct sipe_backend_media_stream
*stream
)
97 if (stream
->gst_bus_cb_id
!= 0) {
100 pipe
= purple_media_manager_get_pipeline(
101 purple_media_manager_get());
105 bus
= gst_element_get_bus(pipe
);
106 g_signal_handler_disconnect(bus
, stream
->gst_bus_cb_id
);
107 stream
->gst_bus_cb_id
= 0;
108 gst_object_unref(bus
);
112 if (stream
->rtpsession
) {
113 g_clear_object(&stream
->rtpsession
);
119 static PurpleMediaSessionType
sipe_media_to_purple(SipeMediaType type
);
120 static PurpleMediaCandidateType
sipe_candidate_type_to_purple(SipeCandidateType type
);
121 static SipeCandidateType
purple_candidate_type_to_sipe(PurpleMediaCandidateType type
);
122 static PurpleMediaNetworkProtocol
sipe_network_protocol_to_purple(SipeNetworkProtocol proto
);
123 static SipeNetworkProtocol
purple_network_protocol_to_sipe(PurpleMediaNetworkProtocol proto
);
126 maybe_signal_stream_initialized(struct sipe_media_call
*call
, gchar
*sessionid
)
128 if (call
->stream_initialized_cb
) {
129 struct sipe_media_stream
*stream
;
130 stream
= sipe_core_media_get_stream_by_id(call
, sessionid
);
133 sipe_backend_stream_initialized(call
, stream
) &&
134 !stream
->backend_private
->initialized_cb_was_fired
) {
135 stream
->backend_private
->initialized_cb_was_fired
= TRUE
;
136 call
->stream_initialized_cb(call
, stream
);
142 on_candidates_prepared_cb(SIPE_UNUSED_PARAMETER PurpleMedia
*media
,
144 SIPE_UNUSED_PARAMETER gchar
*participant
,
145 struct sipe_media_call
*call
)
147 maybe_signal_stream_initialized(call
, sessionid
);
151 on_codecs_changed_cb(SIPE_UNUSED_PARAMETER PurpleMedia
*media
,
153 struct sipe_media_call
*call
)
155 maybe_signal_stream_initialized(call
, sessionid
);
159 on_state_changed_cb(SIPE_UNUSED_PARAMETER PurpleMedia
*media
,
160 PurpleMediaState state
,
163 struct sipe_media_call
*call
)
165 SIPE_DEBUG_INFO("sipe_media_state_changed_cb: %d %s %s\n", state
, sessionid
, participant
);
167 if (state
== PURPLE_MEDIA_STATE_CONNECTED
&& sessionid
&& participant
) {
168 struct sipe_media_stream
*stream
;
170 stream
= sipe_core_media_get_stream_by_id(call
, sessionid
);
171 if (stream
&& stream
->backend_private
->rtpsession
&&
172 stream
->backend_private
->on_sending_rtcp_cb_id
!= 0) {
173 struct sipe_backend_media_stream
*backend_stream
;
175 SIPE_DEBUG_INFO_NOFORMAT("Peer started sending. Ceasing"
176 " video source requests.");
178 backend_stream
= stream
->backend_private
;
180 g_signal_handler_disconnect(backend_stream
->rtpsession
,
181 backend_stream
->on_sending_rtcp_cb_id
);
182 g_clear_object(&backend_stream
->rtpsession
);
183 backend_stream
->on_sending_rtcp_cb_id
= 0;
185 } else if (state
== PURPLE_MEDIA_STATE_END
) {
186 if (sessionid
&& participant
) {
187 struct sipe_media_stream
*stream
=
188 sipe_core_media_get_stream_by_id(call
, sessionid
);
190 sipe_core_media_stream_end(stream
);
192 } else if (!sessionid
&& !participant
&& call
->media_end_cb
) {
193 call
->media_end_cb(call
);
199 capture_pipeline(const gchar
*label
) {
200 PurpleMediaManager
*manager
= purple_media_manager_get();
201 GstElement
*pipeline
= purple_media_manager_get_pipeline(manager
);
202 GST_DEBUG_BIN_TO_DOT_FILE_WITH_TS(GST_BIN(pipeline
), GST_DEBUG_GRAPH_SHOW_ALL
, label
);
206 on_error_cb(SIPE_UNUSED_PARAMETER PurpleMedia
*media
, gchar
*message
,
207 struct sipe_media_call
*call
)
209 capture_pipeline("ERROR");
212 call
->error_cb(call
, message
);
216 on_stream_info_cb(PurpleMedia
*media
,
217 PurpleMediaInfoType type
,
221 struct sipe_media_call
*call
)
223 if (type
== PURPLE_MEDIA_INFO_ACCEPT
) {
224 if (call
->call_accept_cb
&& !sessionid
&& !participant
)
225 call
->call_accept_cb(call
, local
);
226 else if (sessionid
&& participant
) {
227 struct sipe_media_stream
*stream
;
228 stream
= sipe_core_media_get_stream_by_id(call
, sessionid
);
230 if (!stream
->backend_private
->accepted
&& local
)
231 --call
->backend_private
->unconfirmed_streams
;
232 stream
->backend_private
->accepted
= TRUE
;
235 } else if (type
== PURPLE_MEDIA_INFO_HOLD
|| type
== PURPLE_MEDIA_INFO_UNHOLD
) {
237 gboolean state
= (type
== PURPLE_MEDIA_INFO_HOLD
);
240 // Hold specific stream
241 struct sipe_media_stream
*stream
;
242 stream
= sipe_core_media_get_stream_by_id(call
, sessionid
);
246 stream
->backend_private
->local_on_hold
= state
;
248 stream
->backend_private
->remote_on_hold
= state
;
252 GList
*session_ids
= purple_media_get_session_ids(media
);
254 for (; session_ids
; session_ids
= session_ids
->next
) {
255 struct sipe_media_stream
*stream
=
256 sipe_core_media_get_stream_by_id(call
, session_ids
->data
);
260 stream
->backend_private
->local_on_hold
= state
;
262 stream
->backend_private
->remote_on_hold
= state
;
266 g_list_free(session_ids
);
269 if (call
->call_hold_cb
)
270 call
->call_hold_cb(call
, local
, state
);
271 } else if (type
== PURPLE_MEDIA_INFO_HANGUP
|| type
== PURPLE_MEDIA_INFO_REJECT
) {
272 if (!sessionid
&& !participant
) {
273 if (type
== PURPLE_MEDIA_INFO_HANGUP
&& call
->call_hangup_cb
)
274 call
->call_hangup_cb(call
, local
);
275 else if (type
== PURPLE_MEDIA_INFO_REJECT
&& call
->call_reject_cb
&& !local
)
276 call
->call_reject_cb(call
, local
);
277 } else if (sessionid
&& participant
) {
278 struct sipe_media_stream
*stream
;
279 stream
= sipe_core_media_get_stream_by_id(call
, sessionid
);
282 purple_media_manager_set_application_data_callbacks(
283 purple_media_manager_get(), media
,
284 sessionid
, participant
, NULL
, NULL
, NULL
);
288 if (local
&& --call
->backend_private
->unconfirmed_streams
== 0 &&
289 call
->call_reject_cb
)
290 call
->call_reject_cb(call
, local
);
293 } else if (type
== PURPLE_MEDIA_INFO_MUTE
|| type
== PURPLE_MEDIA_INFO_UNMUTE
) {
294 struct sipe_media_stream
*stream
=
295 sipe_core_media_get_stream_by_id(call
, "audio");
297 if (stream
&& stream
->mute_cb
) {
298 stream
->mute_cb(stream
, type
== PURPLE_MEDIA_INFO_MUTE
);
304 on_candidate_pair_established_cb(SIPE_UNUSED_PARAMETER PurpleMedia
*media
,
305 const gchar
*sessionid
,
306 SIPE_UNUSED_PARAMETER
const gchar
*participant
,
307 SIPE_UNUSED_PARAMETER PurpleMediaCandidate
*local_candidate
,
308 SIPE_UNUSED_PARAMETER PurpleMediaCandidate
*remote_candidate
,
309 struct sipe_media_call
*call
)
311 struct sipe_media_stream
*stream
=
312 sipe_core_media_get_stream_by_id(call
, sessionid
);
318 #ifdef HAVE_PURPLE_NEW_TCP_ENUMS
319 if (purple_media_candidate_get_protocol(local_candidate
) != PURPLE_MEDIA_NETWORK_PROTOCOL_UDP
) {
320 purple_media_set_send_rtcp_mux(media
, sessionid
, participant
, TRUE
);
324 sipe_core_media_stream_candidate_pair_established(stream
);
327 struct sipe_backend_media
*
328 sipe_backend_media_new(struct sipe_core_public
*sipe_public
,
329 struct sipe_media_call
*call
,
330 const gchar
*participant
,
331 SipeMediaCallFlags flags
)
333 struct sipe_backend_media
*media
= g_new0(struct sipe_backend_media
, 1);
334 struct sipe_backend_private
*purple_private
= sipe_public
->backend_private
;
335 PurpleMediaManager
*manager
= purple_media_manager_get();
336 GstElement
*pipeline
;
338 if (flags
& SIPE_MEDIA_CALL_NO_UI
) {
340 media
->m
= purple_media_manager_create_private_media(manager
,
341 purple_private
->account
, "fsrtpconference",
342 participant
, flags
& SIPE_MEDIA_CALL_INITIATOR
);
344 SIPE_DEBUG_ERROR_NOFORMAT("Purple doesn't support private media");
347 media
->m
= purple_media_manager_create_media(manager
,
348 purple_private
->account
, "fsrtpconference",
349 participant
, flags
& SIPE_MEDIA_CALL_INITIATOR
);
352 g_signal_connect(G_OBJECT(media
->m
), "candidates-prepared",
353 G_CALLBACK(on_candidates_prepared_cb
), call
);
354 g_signal_connect(G_OBJECT(media
->m
), "codecs-changed",
355 G_CALLBACK(on_codecs_changed_cb
), call
);
356 g_signal_connect(G_OBJECT(media
->m
), "stream-info",
357 G_CALLBACK(on_stream_info_cb
), call
);
358 g_signal_connect(G_OBJECT(media
->m
), "error",
359 G_CALLBACK(on_error_cb
), call
);
360 g_signal_connect(G_OBJECT(media
->m
), "state-changed",
361 G_CALLBACK(on_state_changed_cb
), call
);
362 g_signal_connect(G_OBJECT(media
->m
), "candidate-pair-established",
363 G_CALLBACK(on_candidate_pair_established_cb
), call
);
366 /* On error, the pipeline is no longer in PLAYING state and libpurple
367 * will not switch it back to PLAYING, preventing any more calls until
368 * application restart. We switch the state ourselves here to negate
369 * effect of any error in previous call (if any). */
370 pipeline
= purple_media_manager_get_pipeline(manager
);
371 gst_element_set_state(pipeline
, GST_STATE_PLAYING
);
377 sipe_backend_media_free(struct sipe_backend_media
*media
)
383 sipe_backend_media_set_cname(struct sipe_backend_media
*media
, gchar
*cname
)
386 guint num_params
= 3;
387 GParameter
*params
= g_new0(GParameter
, num_params
);
388 params
[0].name
= "sdes-cname";
389 g_value_init(¶ms
[0].value
, G_TYPE_STRING
);
390 g_value_set_string(¶ms
[0].value
, cname
);
391 params
[1].name
= "sdes-name";
392 g_value_init(¶ms
[1].value
, G_TYPE_STRING
);
393 params
[2].name
= "sdes-tool";
394 g_value_init(¶ms
[2].value
, G_TYPE_STRING
);
396 purple_media_set_params(media
->m
, num_params
, params
);
398 g_value_unset(¶ms
[0].value
);
403 #define FS_CODECS_CONF \
404 "# Automatically created by SIPE plugin\n" \
406 "farstream-send-profile=videoscale ! videoconvert ! fsvideoanyrate ! x264enc ! video/x-h264,profile=constrained-baseline ! rtph264pay\n" \
408 "[application/X-DATA]\n" \
415 const gchar
*fs_codecs_conf
= FS_CODECS_CONF
;
416 GError
*error
= NULL
;
418 filename
= g_build_filename(purple_config_dir(), "fs-codec.conf", NULL
);
420 g_file_set_contents(filename
, fs_codecs_conf
, strlen(fs_codecs_conf
),
423 SIPE_DEBUG_ERROR("Couldn't create fs-codec.conf: %s",
432 append_relay(struct sipe_backend_media_relays
*relay_info
, const gchar
*ip
,
433 guint port
, gchar
*type
, gchar
*username
, gchar
*password
)
435 GstStructure
*gst_relay_info
;
437 gst_relay_info
= gst_structure_new("relay-info",
438 "ip", G_TYPE_STRING
, ip
,
439 "port", G_TYPE_UINT
, port
,
440 "relay-type", G_TYPE_STRING
, type
,
441 "username", G_TYPE_STRING
, username
,
442 "password", G_TYPE_STRING
, password
,
445 if (gst_relay_info
) {
446 g_ptr_array_add((GPtrArray
*)relay_info
, gst_relay_info
);
450 struct sipe_backend_media_relays
*
451 sipe_backend_media_relays_convert(GSList
*media_relays
, gchar
*username
, gchar
*password
)
453 struct sipe_backend_media_relays
*relay_info
;
455 relay_info
= (struct sipe_backend_media_relays
*)
456 g_ptr_array_new_with_free_func((GDestroyNotify
) gst_structure_free
);
458 for (; media_relays
; media_relays
= media_relays
->next
) {\
459 struct sipe_media_relay
*relay
= media_relays
->data
;
461 /* Skip relays where IP could not be resolved. */
462 if (!relay
->hostname
)
465 if (relay
->udp_port
!= 0)
466 append_relay(relay_info
, relay
->hostname
, relay
->udp_port
,
467 "udp", username
, password
);
469 #ifdef HAVE_PURPLE_NEW_TCP_ENUMS
470 if (relay
->tcp_port
!= 0) {
472 if (relay
->tcp_port
== 443)
474 append_relay(relay_info
, relay
->hostname
, relay
->tcp_port
,
475 type
, username
, password
);
484 sipe_backend_media_relays_free(struct sipe_backend_media_relays
*media_relays
)
486 g_ptr_array_unref((GPtrArray
*)media_relays
);
491 stream_readable_cb(SIPE_UNUSED_PARAMETER PurpleMediaManager
*manager
,
492 SIPE_UNUSED_PARAMETER PurpleMedia
*media
,
493 const gchar
*session_id
,
494 SIPE_UNUSED_PARAMETER
const gchar
*participant
,
497 struct sipe_media_call
*call
= (struct sipe_media_call
*)user_data
;
498 struct sipe_media_stream
*stream
;
500 SIPE_DEBUG_INFO("stream_readable_cb: %s is readable", session_id
);
502 stream
= sipe_core_media_get_stream_by_id(call
, session_id
);
505 sipe_core_media_stream_readable(stream
);
510 sipe_backend_media_stream_read(struct sipe_media_stream
*stream
,
511 guint8
*buffer
, gsize len
)
513 return purple_media_manager_receive_application_data(
514 purple_media_manager_get(),
515 stream
->call
->backend_private
->m
,
516 stream
->id
, stream
->call
->with
, buffer
, len
, FALSE
);
520 sipe_backend_media_stream_write(struct sipe_media_stream
*stream
,
521 guint8
*buffer
, gsize len
)
523 return purple_media_manager_send_application_data(
524 purple_media_manager_get(),
525 stream
->call
->backend_private
->m
,
526 stream
->id
, stream
->call
->with
, buffer
, len
, FALSE
);
530 stream_writable_cb(SIPE_UNUSED_PARAMETER PurpleMediaManager
*manager
,
531 SIPE_UNUSED_PARAMETER PurpleMedia
*media
,
532 const gchar
*session_id
,
533 SIPE_UNUSED_PARAMETER
const gchar
*participant
,
537 struct sipe_media_call
*call
= (struct sipe_media_call
*)user_data
;
538 struct sipe_media_stream
*stream
;
540 stream
= sipe_core_media_get_stream_by_id(call
, session_id
);
543 SIPE_DEBUG_ERROR("stream_writable_cb: stream %s not found!",
548 SIPE_DEBUG_INFO("stream_writable_cb: %s has become %swritable",
549 session_id
, writable
? "" : "not ");
551 sipe_core_media_stream_writable(stream
, writable
);
556 write_ms_h264_video_source_request(GstRTCPBuffer
*buffer
, guint32 ssrc
,
559 GstRTCPPacket packet
;
562 if (!gst_rtcp_buffer_add_packet(buffer
, GST_RTCP_TYPE_PSFB
, &packet
)) {
566 gst_rtcp_packet_fb_set_type(&packet
, GST_RTCP_PSFB_TYPE_AFB
);
567 gst_rtcp_packet_fb_set_sender_ssrc(&packet
, ssrc
);
568 gst_rtcp_packet_fb_set_media_ssrc(&packet
, SIPE_MSRTP_VSR_SOURCE_ANY
);
570 if (!gst_rtcp_packet_fb_set_fci_length(&packet
,
571 SIPE_MSRTP_VSR_FCI_WORDLEN
)) {
572 gst_rtcp_packet_remove(&packet
);
576 fci_data
= gst_rtcp_packet_fb_get_fci(&packet
);
578 sipe_core_msrtp_write_video_source_request(fci_data
, payload_type
);
584 on_sending_rtcp_cb(SIPE_UNUSED_PARAMETER GObject
*rtpsession
,
586 SIPE_UNUSED_PARAMETER gboolean is_early
,
587 FsSession
*fssession
)
589 gboolean was_changed
= FALSE
;
592 g_object_get(fssession
, "current-send-codec", &send_codec
, NULL
);
597 if (sipe_strequal(send_codec
->encoding_name
, "H264")) {
598 GstRTCPBuffer rtcp_buffer
= GST_RTCP_BUFFER_INIT
;
601 g_object_get(fssession
, "ssrc", &ssrc
, NULL
);
603 gst_rtcp_buffer_map(buffer
, GST_MAP_READWRITE
, &rtcp_buffer
);
604 was_changed
= write_ms_h264_video_source_request(&rtcp_buffer
,
605 ssrc
, send_codec
->id
);
606 gst_rtcp_buffer_unmap(&rtcp_buffer
);
609 fs_codec_destroy(send_codec
);
614 static GstPadProbeReturn
615 h264_buffer_cb(SIPE_UNUSED_PARAMETER GstPad
*pad
, GstPadProbeInfo
*info
,
616 SIPE_UNUSED_PARAMETER gpointer user_data
)
622 guint8 nal_count
= 0;
625 buffer
= gst_pad_probe_info_get_buffer(info
);
627 // Count NALs in the buffer
628 gst_buffer_map(buffer
, &map
, GST_MAP_READ
);
631 while (data
< map
.data
+ map
.size
) {
632 guint32 size
= GST_READ_UINT32_BE(data
);
633 data
+= GST_READ_UINT32_BE(data
) + sizeof (size
);
637 gst_buffer_unmap(buffer
, &map
);
639 // Write PACSI (RFC6190 section 4.9)
640 memory
= gst_allocator_alloc(NULL
, 100, NULL
);
641 gst_memory_map(memory
, &map
, GST_MAP_WRITE
);
642 pacsi_len
= sipe_core_msrtp_write_video_scalability_info(map
.data
,
644 gst_memory_unmap(memory
, &map
);
645 gst_memory_resize(memory
, 0, pacsi_len
);
647 buffer
= gst_buffer_make_writable(buffer
);
648 gst_buffer_insert_memory(buffer
, 0, memory
);
649 GST_PAD_PROBE_INFO_DATA(info
) = buffer
;
651 return GST_PAD_PROBE_OK
;
655 find_payloader(GValue
*value
, GstCaps
*rtpcaps
)
662 element
= g_value_get_object(value
);
663 sinkpad
= gst_element_get_static_pad(element
, "sink");
664 caps
= gst_pad_query_caps(sinkpad
, NULL
);
666 /* Elements are iterated from the most downstream. We're looking for the
667 * first that does NOT consume RTP frames. */
668 result
= gst_caps_can_intersect(caps
, rtpcaps
);
670 gst_caps_unref(caps
);
671 gst_object_unref(sinkpad
);
677 current_send_codec_changed_cb(FsSession
*fssession
,
678 SIPE_UNUSED_PARAMETER GParamSpec
*pspec
,
679 GstBin
*fsconference
)
683 g_object_get(fssession
, "current-send-codec", &send_codec
, NULL
);
685 if (sipe_strequal(send_codec
->encoding_name
, "H264")) {
691 GValue val
= G_VALUE_INIT
;
693 g_object_get(fssession
, "id", &session_id
, NULL
);
695 sendbin_name
= g_strdup_printf("send_%u_%u", session_id
,
698 sendbin
= GST_BIN(gst_bin_get_by_name(fsconference
,
700 g_free(sendbin_name
);
703 SIPE_DEBUG_ERROR("Couldn't find Farstream send bin for "
704 "session %d", session_id
);
708 caps
= gst_caps_new_empty_simple("application/x-rtp");
709 it
= gst_bin_iterate_sorted(sendbin
);
710 if (gst_iterator_find_custom(it
, (GCompareFunc
)find_payloader
,
712 GstElement
*payloader
;
715 payloader
= g_value_get_object(&val
);
716 sinkpad
= gst_element_get_static_pad(payloader
, "sink");
718 gst_pad_add_probe(sinkpad
,
719 GST_PAD_PROBE_TYPE_BUFFER
,
720 h264_buffer_cb
, NULL
, NULL
);
721 gst_object_unref(sinkpad
);
725 gst_caps_unref(caps
);
727 gst_iterator_free(it
);
728 gst_object_unref(sendbin
);
731 fs_codec_destroy(send_codec
);
735 find_sinkpad(GValue
*value
, GstPad
*fssession_sinkpad
)
737 GstElement
*tee_srcpad
= g_value_get_object(value
);
739 return !(GST_PAD_PEER(tee_srcpad
) == fssession_sinkpad
);
743 gst_bus_cb(GstBus
*bus
, GstMessage
*msg
, struct sipe_media_stream
*stream
)
746 const GstStructure
*s
;
747 FsSession
*fssession
;
751 GValue val
= G_VALUE_INIT
;
753 if (GST_MESSAGE_TYPE(msg
) != GST_MESSAGE_ELEMENT
) {
757 m
= stream
->call
->backend_private
->m
;
759 s
= gst_message_get_structure(msg
);
760 if (!gst_structure_has_name(s
, "farstream-codecs-changed")) {
764 fssession
= g_value_get_object(gst_structure_get_value(s
, "session"));
765 g_return_if_fail(fssession
);
767 tee
= purple_media_get_tee(m
, stream
->id
, NULL
);
768 g_return_if_fail(tee
);
770 g_object_get(fssession
, "sink-pad", &sinkpad
, NULL
);
771 g_return_if_fail(sinkpad
);
773 /* Check whether this message is from the FsSession we're waiting for.
774 * For this to be true, the tee we got from libpurple has to be linked
775 * to "sink-pad" of the message's FsSession. */
776 it
= gst_element_iterate_src_pads(tee
);
777 if (gst_iterator_find_custom(it
, (GCompareFunc
)find_sinkpad
, &val
,
779 FsMediaType media_type
;
781 if (stream
->ssrc_range
) {
782 g_object_set(fssession
, "ssrc",
783 stream
->ssrc_range
->begin
, NULL
);
786 g_object_get(fssession
, "media-type", &media_type
, NULL
);
788 if (media_type
== FS_MEDIA_TYPE_VIDEO
) {
790 GstBin
*fsconference
;
792 g_object_get(fssession
,
793 "internal-session", &rtpsession
, NULL
);
795 stream
->backend_private
->rtpsession
=
796 gst_object_ref(rtpsession
);
797 stream
->backend_private
->on_sending_rtcp_cb_id
=
798 g_signal_connect(rtpsession
,
800 G_CALLBACK(on_sending_rtcp_cb
),
803 g_object_unref (rtpsession
);
806 g_object_get(fssession
,
807 "conference", &fsconference
, NULL
);
809 g_signal_connect_object(fssession
,
810 "notify::current-send-codec",
811 G_CALLBACK(current_send_codec_changed_cb
),
813 gst_object_unref(fsconference
);
816 g_signal_handler_disconnect(bus
,
817 stream
->backend_private
->gst_bus_cb_id
);
818 stream
->backend_private
->gst_bus_cb_id
= 0;
819 g_value_unset (&val
);
822 gst_iterator_free(it
);
823 gst_object_unref(sinkpad
);
826 struct sipe_backend_media_stream
*
827 sipe_backend_media_add_stream(struct sipe_media_stream
*stream
,
829 SipeIceVersion ice_version
,
831 struct sipe_backend_media_relays
*media_relays
,
832 guint min_port
, guint max_port
)
834 struct sipe_backend_media
*media
= stream
->call
->backend_private
;
835 struct sipe_backend_media_stream
*backend_stream
= NULL
;
837 // Preallocate enough space for all potential parameters to fit.
838 GParameter
*params
= g_new0(GParameter
, 7);
839 guint params_cnt
= 0;
842 PurpleMediaAppDataCallbacks callbacks
= {
843 stream_readable_cb
, stream_writable_cb
847 if (ice_version
!= SIPE_ICE_NO_ICE
) {
848 transmitter
= "nice";
850 params
[params_cnt
].name
= "compatibility-mode";
851 g_value_init(¶ms
[params_cnt
].value
, G_TYPE_UINT
);
852 g_value_set_uint(¶ms
[params_cnt
].value
,
853 ice_version
== SIPE_ICE_DRAFT_6
?
854 NICE_COMPATIBILITY_OC2007
:
855 NICE_COMPATIBILITY_OC2007R2
);
859 params
[params_cnt
].name
= "min-port";
860 g_value_init(¶ms
[params_cnt
].value
, G_TYPE_UINT
);
861 g_value_set_uint(¶ms
[params_cnt
].value
, min_port
);
866 params
[params_cnt
].name
= "max-port";
867 g_value_init(¶ms
[params_cnt
].value
, G_TYPE_UINT
);
868 g_value_set_uint(¶ms
[params_cnt
].value
, max_port
);
873 params
[params_cnt
].name
= "relay-info";
874 g_value_init(¶ms
[params_cnt
].value
, G_TYPE_PTR_ARRAY
);
875 g_value_set_boxed(¶ms
[params_cnt
].value
, media_relays
);
879 if (type
== SIPE_MEDIA_APPLICATION
) {
880 params
[params_cnt
].name
= "ice-udp";
881 g_value_init(¶ms
[params_cnt
].value
, G_TYPE_BOOLEAN
);
882 g_value_set_boolean(¶ms
[params_cnt
].value
, FALSE
);
885 params
[params_cnt
].name
= "reliable";
886 g_value_init(¶ms
[params_cnt
].value
, G_TYPE_BOOLEAN
);
887 g_value_set_boolean(¶ms
[params_cnt
].value
, TRUE
);
891 /* Don't use the globally defined stun server (it is used by
892 * default in backend-fs2) because it generates srflx local
893 * candidates that confuse SfB when they contribute to valid
894 * pairs in controlled mode:
896 * ms-client-diagnostics: 21
897 * "Call failed to establish due to a media connectivity
898 * failure where one endpoint is of unknown type"
900 * These candidates will be discovered anyway during the
901 * conncheck as prflx, and they will be accepted and nominated
902 * by SfB when having this type. */
903 params
[params_cnt
].name
= "stun-ip";
904 g_value_init(¶ms
[params_cnt
].value
, G_TYPE_STRING
);
905 g_value_set_string(¶ms
[params_cnt
].value
, NULL
);
908 // TODO: session naming here, Communicator needs audio/video
909 transmitter
= "rawudp";
910 //sessionid = "sipe-voice-rawudp";
913 ensure_codecs_conf();
916 if (type
== SIPE_MEDIA_APPLICATION
) {
917 purple_media_manager_set_application_data_callbacks(
918 purple_media_manager_get(),
919 media
->m
, stream
->id
, stream
->call
->with
,
920 &callbacks
, stream
->call
, NULL
);
924 backend_stream
= g_new0(struct sipe_backend_media_stream
, 1);
926 pipe
= purple_media_manager_get_pipeline(purple_media_manager_get());
930 bus
= gst_element_get_bus(pipe
);
931 backend_stream
->gst_bus_cb_id
= g_signal_connect(bus
, "message",
932 G_CALLBACK(gst_bus_cb
), stream
);
933 gst_object_unref(bus
);
936 if (purple_media_add_stream(media
->m
, stream
->id
, stream
->call
->with
,
937 sipe_media_to_purple(type
),
938 initiator
, transmitter
, params_cnt
,
941 ++media
->unconfirmed_streams
;
943 sipe_backend_media_stream_free(backend_stream
);
944 backend_stream
= NULL
;
949 return backend_stream
;
953 sipe_backend_media_stream_end(struct sipe_media_call
*media
,
954 struct sipe_media_stream
*stream
)
956 purple_media_end(media
->backend_private
->m
, stream
->id
, NULL
);
960 sipe_backend_media_add_remote_candidates(struct sipe_media_call
*media
,
961 struct sipe_media_stream
*stream
,
964 GList
*udp_candidates
= NULL
;
966 #ifndef HAVE_PURPLE_NEW_TCP_ENUMS
967 /* Keep only UDP candidates in the list to set. */
969 PurpleMediaCandidate
*candidate
= candidates
->data
;
970 PurpleMediaNetworkProtocol proto
;
972 proto
= purple_media_candidate_get_protocol(candidate
);
973 if (proto
== PURPLE_MEDIA_NETWORK_PROTOCOL_UDP
)
974 udp_candidates
= g_list_append(udp_candidates
, candidate
);
976 candidates
= candidates
->next
;
979 candidates
= udp_candidates
;
982 purple_media_add_remote_candidates(media
->backend_private
->m
,
983 stream
->id
, media
->with
, candidates
);
985 g_list_free(udp_candidates
);
988 gboolean
sipe_backend_media_is_initiator(struct sipe_media_call
*media
,
989 struct sipe_media_stream
*stream
)
991 return purple_media_is_initiator(media
->backend_private
->m
,
992 stream
? stream
->id
: NULL
,
993 stream
? media
->with
: NULL
);
996 gboolean
sipe_backend_media_accepted(struct sipe_backend_media
*media
)
998 return purple_media_accepted(media
->m
, NULL
, NULL
);
1002 sipe_backend_stream_initialized(struct sipe_media_call
*media
,
1003 struct sipe_media_stream
*stream
)
1005 g_return_val_if_fail(media
, FALSE
);
1006 g_return_val_if_fail(stream
, FALSE
);
1008 if (purple_media_candidates_prepared(media
->backend_private
->m
,
1009 stream
->id
, media
->with
)) {
1011 codecs
= purple_media_get_codecs(media
->backend_private
->m
,
1014 purple_media_codec_list_free(codecs
);
1022 duplicate_tcp_candidates(GList
*candidates
)
1025 GList
*result
= NULL
;
1027 for (i
= candidates
; i
; i
= i
->next
) {
1028 PurpleMediaCandidate
*candidate
= i
->data
;
1029 PurpleMediaNetworkProtocol protocol
=
1030 purple_media_candidate_get_protocol(candidate
);
1031 guint component_id
=
1032 purple_media_candidate_get_component_id(candidate
);
1034 if (protocol
!= PURPLE_MEDIA_NETWORK_PROTOCOL_UDP
) {
1035 PurpleMediaCandidate
*c2
;
1037 if (component_id
!= PURPLE_MEDIA_COMPONENT_RTP
) {
1038 /* Ignore TCP candidates for other than
1039 * the first component. */
1040 g_object_unref(candidate
);
1044 c2
= purple_media_candidate_copy(candidate
);
1046 "component-id", PURPLE_MEDIA_COMPONENT_RTCP
,
1048 result
= g_list_append(result
, c2
);
1051 result
= g_list_append(result
, candidate
);
1054 g_list_free(candidates
);
1060 sipe_backend_media_stream_get_active_local_candidates(struct sipe_media_stream
*stream
)
1062 GList
*candidates
= purple_media_get_active_local_candidates(
1063 stream
->call
->backend_private
->m
, stream
->id
,
1064 stream
->call
->with
);
1065 return duplicate_tcp_candidates(candidates
);
1069 sipe_backend_media_stream_get_active_remote_candidates(struct sipe_media_stream
*stream
)
1071 GList
*candidates
= purple_media_get_active_remote_candidates(
1072 stream
->call
->backend_private
->m
, stream
->id
,
1073 stream
->call
->with
);
1074 return duplicate_tcp_candidates(candidates
);
1079 sipe_backend_media_set_encryption_keys(struct sipe_media_call
*media
,
1080 struct sipe_media_stream
*stream
,
1081 const guchar
*encryption_key
,
1082 const guchar
*decryption_key
)
1084 purple_media_set_encryption_parameters(media
->backend_private
->m
,
1088 (gchar
*)encryption_key
, SIPE_SRTP_KEY_LEN
);
1089 purple_media_set_decryption_parameters(media
->backend_private
->m
,
1090 stream
->id
, media
->with
,
1093 (gchar
*)decryption_key
, SIPE_SRTP_KEY_LEN
);
1097 sipe_backend_media_set_encryption_keys(SIPE_UNUSED_PARAMETER
struct sipe_media_call
*media
,
1098 SIPE_UNUSED_PARAMETER
struct sipe_media_stream
*stream
,
1099 SIPE_UNUSED_PARAMETER
const guchar
*encryption_key
,
1100 SIPE_UNUSED_PARAMETER
const guchar
*decryption_key
)
1104 #if defined(HAVE_SRTP) && PURPLE_VERSION_CHECK(2,14,0)
1106 sipe_backend_media_set_require_encryption(struct sipe_media_call
*media
,
1107 struct sipe_media_stream
*stream
,
1108 const gboolean require_encryption
)
1110 purple_media_set_require_encryption(media
->backend_private
->m
,
1111 stream
->id
, media
->with
, require_encryption
);
1115 sipe_backend_media_set_require_encryption(SIPE_UNUSED_PARAMETER
struct sipe_media_call
*media
,
1116 SIPE_UNUSED_PARAMETER
struct sipe_media_stream
*stream
,
1117 SIPE_UNUSED_PARAMETER
const gboolean require_encryption
)
1121 void sipe_backend_stream_hold(struct sipe_media_call
*media
,
1122 struct sipe_media_stream
*stream
,
1125 purple_media_stream_info(media
->backend_private
->m
, PURPLE_MEDIA_INFO_HOLD
,
1126 stream
->id
, media
->with
, local
);
1129 void sipe_backend_stream_unhold(struct sipe_media_call
*media
,
1130 struct sipe_media_stream
*stream
,
1133 purple_media_stream_info(media
->backend_private
->m
, PURPLE_MEDIA_INFO_UNHOLD
,
1134 stream
->id
, media
->with
, local
);
1137 gboolean
sipe_backend_stream_is_held(struct sipe_media_stream
*stream
)
1139 g_return_val_if_fail(stream
, FALSE
);
1141 return stream
->backend_private
->local_on_hold
||
1142 stream
->backend_private
->remote_on_hold
;
1145 struct sipe_backend_codec
*
1146 sipe_backend_codec_new(int id
, const char *name
, SipeMediaType type
,
1147 guint clock_rate
, guint channels
)
1149 PurpleMediaCodec
*codec
;
1151 if (sipe_strcase_equal(name
, "X-H264UC")) {
1155 codec
= purple_media_codec_new(id
, name
, sipe_media_to_purple(type
),
1157 g_object_set(codec
, "channels", channels
, NULL
);
1159 return (struct sipe_backend_codec
*)codec
;
1163 sipe_backend_codec_free(struct sipe_backend_codec
*codec
)
1166 g_object_unref(codec
);
1170 sipe_backend_codec_get_id(struct sipe_backend_codec
*codec
)
1172 return purple_media_codec_get_id((PurpleMediaCodec
*)codec
);
1176 sipe_backend_codec_get_name(struct sipe_backend_codec
*codec
)
1178 /* Not explicitly documented, but return value must be g_free()'d */
1179 return purple_media_codec_get_encoding_name((PurpleMediaCodec
*)codec
);
1183 sipe_backend_codec_get_clock_rate(struct sipe_backend_codec
*codec
)
1185 return purple_media_codec_get_clock_rate((PurpleMediaCodec
*)codec
);
1189 sipe_backend_codec_add_optional_parameter(struct sipe_backend_codec
*codec
,
1190 const gchar
*name
, const gchar
*value
)
1192 purple_media_codec_add_optional_parameter((PurpleMediaCodec
*)codec
, name
, value
);
1196 sipe_backend_codec_get_optional_parameters(struct sipe_backend_codec
*codec
)
1198 return purple_media_codec_get_optional_parameters((PurpleMediaCodec
*)codec
);
1202 sipe_backend_set_remote_codecs(struct sipe_media_call
*media
,
1203 struct sipe_media_stream
*stream
,
1208 /* Lync offers multichannel audio as a codec with the same encoding name
1209 * as the mono variant, but a different payload type and an extra
1210 * encoding parameter:
1212 * a=rtpmap:117 G722/8000/2
1213 * a=rtpmap:9 G722/8000
1215 * Since avenc_g722 from gst-libav can encode only one audio channel, ignore
1216 * multichannel codecs we were offered by the remote host.
1219 PurpleMediaSessionType type
;
1221 for (; codecs
; codecs
= codecs
->next
) {
1222 PurpleMediaCodec
*codec
= codecs
->data
;
1224 g_object_get(codec
, "media-type", &type
, NULL
);
1226 if (type
== PURPLE_MEDIA_AUDIO
&&
1227 purple_media_codec_get_channels(codec
) > 1) {
1231 tmp
= g_list_append(tmp
, codec
);
1234 result
= purple_media_set_remote_codecs(media
->backend_private
->m
,
1235 stream
->id
, media
->with
,
1243 sipe_backend_get_local_codecs(struct sipe_media_call
*media
,
1244 struct sipe_media_stream
*stream
)
1246 GList
*codecs
= purple_media_get_codecs(media
->backend_private
->m
,
1249 gboolean is_conference
= (g_strstr_len(media
->with
, strlen(media
->with
),
1250 "app:conf:audio-video:") != NULL
);
1253 * Do not announce Theora. Its optional parameters are too long,
1254 * Communicator rejects such SDP message and does not support the codec
1257 * For some yet unknown reason, A/V conferencing server does not accept
1258 * voice stream sent by SIPE when SIREN codec is in use. Nevertheless,
1259 * we are able to decode incoming SIREN from server and with MSOC
1260 * client, bidirectional call using the codec works. Until resolved,
1261 * do not try to negotiate SIREN usage when conferencing. PCMA or PCMU
1262 * seems to work properly in this scenario.
1265 PurpleMediaCodec
*codec
= i
->data
;
1266 gchar
*encoding_name
= purple_media_codec_get_encoding_name(codec
);
1268 if (sipe_strequal(encoding_name
,"THEORA") ||
1269 (is_conference
&& sipe_strequal(encoding_name
,"SIREN"))) {
1271 g_object_unref(codec
);
1273 codecs
= g_list_delete_link(codecs
, i
);
1275 } else if (sipe_strequal(encoding_name
, "H264")) {
1277 * Sanitize H264 codec:
1278 * - the encoding name must be "X-H264UC"
1279 * - remove "sprop-parameter-sets" parameter which is
1281 * - add "packetization-mode" parameter if not already
1285 PurpleMediaCodec
*new_codec
;
1288 new_codec
= purple_media_codec_new(
1289 purple_media_codec_get_id(codec
),
1292 purple_media_codec_get_clock_rate(codec
));
1294 g_object_set(new_codec
, "channels",
1295 purple_media_codec_get_channels(codec
),
1298 it
= purple_media_codec_get_optional_parameters(codec
);
1300 for (; it
; it
= g_list_next(it
)) {
1301 PurpleKeyValuePair
*pair
= it
->data
;
1303 if (sipe_strequal(pair
->key
, "sprop-parameter-sets")) {
1307 purple_media_codec_add_optional_parameter(new_codec
,
1308 pair
->key
, pair
->value
);
1311 if (!purple_media_codec_get_optional_parameter(new_codec
,
1312 "packetization-mode", NULL
)) {
1313 purple_media_codec_add_optional_parameter(new_codec
,
1314 "packetization-mode",
1315 "1;mst-mode=NI-TC");
1318 i
->data
= new_codec
;
1320 g_object_unref(codec
);
1324 g_free(encoding_name
);
1330 struct sipe_backend_candidate
*
1331 sipe_backend_candidate_new(const gchar
*foundation
,
1332 SipeComponentType component
,
1333 SipeCandidateType type
, SipeNetworkProtocol proto
,
1334 const gchar
*ip
, guint port
,
1335 const gchar
*username
,
1336 const gchar
*password
)
1338 PurpleMediaCandidate
*c
= purple_media_candidate_new(
1339 /* Libnice and Farsight rely on non-NULL foundation to
1340 * distinguish between candidates of a component. When NULL
1341 * foundation is passed (ie. ICE draft 6 does not use foudation),
1342 * use username instead. If no foundation is provided, Farsight
1343 * may signal an active candidate different from the one actually
1344 * in use. See Farsight's agent_new_selected_pair() in
1345 * fs-nice-stream-transmitter.h where first candidate in the
1346 * remote list is always selected when no foundation. */
1347 foundation
? foundation
: username
,
1349 sipe_candidate_type_to_purple(type
),
1350 sipe_network_protocol_to_purple(proto
),
1353 g_object_set(c
, "username", username
, "password", password
, NULL
);
1354 return (struct sipe_backend_candidate
*)c
;
1358 sipe_backend_candidate_free(struct sipe_backend_candidate
*candidate
)
1361 g_object_unref(candidate
);
1365 sipe_backend_candidate_get_username(struct sipe_backend_candidate
*candidate
)
1367 /* Not explicitly documented, but return value must be g_free()'d */
1368 return purple_media_candidate_get_username((PurpleMediaCandidate
*)candidate
);
1372 sipe_backend_candidate_get_password(struct sipe_backend_candidate
*candidate
)
1374 /* Not explicitly documented, but return value must be g_free()'d */
1375 return purple_media_candidate_get_password((PurpleMediaCandidate
*)candidate
);
1379 sipe_backend_candidate_get_foundation(struct sipe_backend_candidate
*candidate
)
1381 /* Not explicitly documented, but return value must be g_free()'d */
1382 return purple_media_candidate_get_foundation((PurpleMediaCandidate
*)candidate
);
1386 sipe_backend_candidate_get_ip(struct sipe_backend_candidate
*candidate
)
1388 /* Not explicitly documented, but return value must be g_free()'d */
1389 return purple_media_candidate_get_ip((PurpleMediaCandidate
*)candidate
);
1393 sipe_backend_candidate_get_port(struct sipe_backend_candidate
*candidate
)
1395 return purple_media_candidate_get_port((PurpleMediaCandidate
*)candidate
);
1399 sipe_backend_candidate_get_base_ip(struct sipe_backend_candidate
*candidate
)
1401 /* Not explicitly documented, but return value must be g_free()'d */
1402 return purple_media_candidate_get_base_ip((PurpleMediaCandidate
*)candidate
);
1406 sipe_backend_candidate_get_base_port(struct sipe_backend_candidate
*candidate
)
1408 return purple_media_candidate_get_base_port((PurpleMediaCandidate
*)candidate
);
1412 sipe_backend_candidate_get_priority(struct sipe_backend_candidate
*candidate
)
1414 return purple_media_candidate_get_priority((PurpleMediaCandidate
*)candidate
);
1418 sipe_backend_candidate_set_priority(struct sipe_backend_candidate
*candidate
, guint32 priority
)
1420 g_object_set(candidate
, "priority", priority
, NULL
);
1424 sipe_backend_candidate_get_component_type(struct sipe_backend_candidate
*candidate
)
1426 return purple_media_candidate_get_component_id((PurpleMediaCandidate
*)candidate
);
1430 sipe_backend_candidate_get_type(struct sipe_backend_candidate
*candidate
)
1432 PurpleMediaCandidateType type
=
1433 purple_media_candidate_get_candidate_type((PurpleMediaCandidate
*)candidate
);
1434 return purple_candidate_type_to_sipe(type
);
1438 sipe_backend_candidate_get_protocol(struct sipe_backend_candidate
*candidate
)
1440 PurpleMediaNetworkProtocol proto
=
1441 purple_media_candidate_get_protocol((PurpleMediaCandidate
*)candidate
);
1442 return purple_network_protocol_to_sipe(proto
);
1446 * libnice can return a candidate list with duplicates. It is currently
1447 * unknown if this is a bug in libnice or a configuration error in Skype
1448 * for Business setups.
1450 * While this is not a bug in SIPE, by removing these duplicates we make
1451 * sure that SIPE doesn't generate incorrect SDP messages.
1454 filter_duplicate_candidates(GList
*candidates
)
1456 GHashTable
*seen
= g_hash_table_new_full(g_str_hash
, g_str_equal
,
1458 GList
*result
= NULL
;
1461 for (it
= candidates
; it
; it
= it
->next
) {
1462 PurpleMediaCandidate
*c
= it
->data
;
1463 gchar
*foundation
= purple_media_candidate_get_foundation(c
);
1464 gchar
*ip
= purple_media_candidate_get_ip(c
);
1465 gchar
*base_ip
= purple_media_candidate_get_base_ip(c
);
1466 gchar
*id
= g_strdup_printf("%s %d %d %d %s %d %d %s %d",
1467 foundation
? foundation
: "-",
1468 purple_media_candidate_get_component_id(c
),
1469 purple_media_candidate_get_protocol(c
),
1470 purple_media_candidate_get_priority(c
),
1472 purple_media_candidate_get_port(c
),
1473 purple_media_candidate_get_candidate_type(c
),
1474 base_ip
? base_ip
: "-",
1475 purple_media_candidate_get_base_port(c
)
1482 if (g_hash_table_lookup(seen
, id
)) {
1483 SIPE_DEBUG_INFO("filter_duplicate_candidates: dropping '%s'",
1488 g_hash_table_insert(seen
, id
, GUINT_TO_POINTER(TRUE
));
1489 result
= g_list_append(result
, c
);
1493 g_hash_table_destroy(seen
);
1494 g_list_free(candidates
);
1500 remove_lone_candidate_cb(SIPE_UNUSED_PARAMETER gpointer key
,
1504 GList
*entry
= value
;
1505 GList
**candidates
= user_data
;
1507 g_object_unref(entry
->data
);
1508 *candidates
= g_list_delete_link(*candidates
, entry
);
1512 ensure_candidate_pairs(GList
*candidates
)
1514 GHashTable
*lone_cand_links
;
1517 lone_cand_links
= g_hash_table_new_full(g_str_hash
, g_str_equal
, g_free
, NULL
);
1519 for (i
= candidates
; i
; i
= i
->next
) {
1520 PurpleMediaCandidate
*c
= i
->data
;
1521 gchar
*foundation
= purple_media_candidate_get_foundation(c
);
1523 if (g_hash_table_lookup(lone_cand_links
, foundation
)) {
1524 g_hash_table_remove(lone_cand_links
, foundation
);
1527 g_hash_table_insert(lone_cand_links
, foundation
, i
);
1531 g_hash_table_foreach(lone_cand_links
, remove_lone_candidate_cb
, &candidates
);
1532 g_hash_table_destroy(lone_cand_links
);
1538 sipe_backend_get_local_candidates(struct sipe_media_call
*media
,
1539 struct sipe_media_stream
*stream
)
1542 purple_media_get_local_candidates(media
->backend_private
->m
,
1545 candidates
= filter_duplicate_candidates(candidates
);
1546 candidates
= duplicate_tcp_candidates(candidates
);
1549 * Sometimes purple will not return complete list of candidates, even
1550 * after "candidates-prepared" signal is emitted. This is a feature of
1551 * libnice, namely affecting candidates discovered via UPnP. Nice does
1552 * not wait until discovery is finished and can signal end of candidate
1553 * gathering before all responses from UPnP enabled gateways are received.
1555 * Remove any incomplete RTP+RTCP candidate pairs from the list.
1557 candidates
= ensure_candidate_pairs(candidates
);
1562 sipe_backend_media_accept(struct sipe_backend_media
*media
, gboolean local
)
1565 purple_media_stream_info(media
->m
, PURPLE_MEDIA_INFO_ACCEPT
,
1570 sipe_backend_media_hangup(struct sipe_backend_media
*media
, gboolean local
)
1573 purple_media_stream_info(media
->m
, PURPLE_MEDIA_INFO_HANGUP
,
1578 sipe_backend_media_reject(struct sipe_backend_media
*media
, gboolean local
)
1581 purple_media_stream_info(media
->m
, PURPLE_MEDIA_INFO_REJECT
,
1585 static PurpleMediaSessionType
sipe_media_to_purple(SipeMediaType type
)
1588 case SIPE_MEDIA_AUDIO
: return PURPLE_MEDIA_AUDIO
;
1589 case SIPE_MEDIA_VIDEO
: return PURPLE_MEDIA_VIDEO
;
1591 case SIPE_MEDIA_APPLICATION
: return PURPLE_MEDIA_APPLICATION
;
1593 default: return PURPLE_MEDIA_NONE
;
1597 static PurpleMediaCandidateType
1598 sipe_candidate_type_to_purple(SipeCandidateType type
)
1601 case SIPE_CANDIDATE_TYPE_HOST
: return PURPLE_MEDIA_CANDIDATE_TYPE_HOST
;
1602 case SIPE_CANDIDATE_TYPE_RELAY
: return PURPLE_MEDIA_CANDIDATE_TYPE_RELAY
;
1603 case SIPE_CANDIDATE_TYPE_SRFLX
: return PURPLE_MEDIA_CANDIDATE_TYPE_SRFLX
;
1604 case SIPE_CANDIDATE_TYPE_PRFLX
: return PURPLE_MEDIA_CANDIDATE_TYPE_PRFLX
;
1605 default: return PURPLE_MEDIA_CANDIDATE_TYPE_HOST
;
1609 static SipeCandidateType
1610 purple_candidate_type_to_sipe(PurpleMediaCandidateType type
)
1613 case PURPLE_MEDIA_CANDIDATE_TYPE_HOST
: return SIPE_CANDIDATE_TYPE_HOST
;
1614 case PURPLE_MEDIA_CANDIDATE_TYPE_RELAY
: return SIPE_CANDIDATE_TYPE_RELAY
;
1615 case PURPLE_MEDIA_CANDIDATE_TYPE_SRFLX
: return SIPE_CANDIDATE_TYPE_SRFLX
;
1616 case PURPLE_MEDIA_CANDIDATE_TYPE_PRFLX
: return SIPE_CANDIDATE_TYPE_PRFLX
;
1617 default: return SIPE_CANDIDATE_TYPE_HOST
;
1621 static PurpleMediaNetworkProtocol
1622 sipe_network_protocol_to_purple(SipeNetworkProtocol proto
)
1625 #ifdef HAVE_PURPLE_NEW_TCP_ENUMS
1626 case SIPE_NETWORK_PROTOCOL_TCP_ACTIVE
:
1627 return PURPLE_MEDIA_NETWORK_PROTOCOL_TCP_ACTIVE
;
1628 case SIPE_NETWORK_PROTOCOL_TCP_PASSIVE
:
1629 return PURPLE_MEDIA_NETWORK_PROTOCOL_TCP_PASSIVE
;
1630 case SIPE_NETWORK_PROTOCOL_TCP_SO
:
1631 return PURPLE_MEDIA_NETWORK_PROTOCOL_TCP_SO
;
1633 case SIPE_NETWORK_PROTOCOL_TCP_ACTIVE
:
1634 case SIPE_NETWORK_PROTOCOL_TCP_PASSIVE
:
1635 case SIPE_NETWORK_PROTOCOL_TCP_SO
:
1636 return PURPLE_MEDIA_NETWORK_PROTOCOL_TCP
;
1639 case SIPE_NETWORK_PROTOCOL_UDP
:
1640 return PURPLE_MEDIA_NETWORK_PROTOCOL_UDP
;
1644 static SipeNetworkProtocol
1645 purple_network_protocol_to_sipe(PurpleMediaNetworkProtocol proto
)
1648 #ifdef HAVE_PURPLE_NEW_TCP_ENUMS
1649 case PURPLE_MEDIA_NETWORK_PROTOCOL_TCP_ACTIVE
:
1650 return SIPE_NETWORK_PROTOCOL_TCP_ACTIVE
;
1651 case PURPLE_MEDIA_NETWORK_PROTOCOL_TCP_PASSIVE
:
1652 return SIPE_NETWORK_PROTOCOL_TCP_PASSIVE
;
1653 case PURPLE_MEDIA_NETWORK_PROTOCOL_TCP_SO
:
1654 return SIPE_NETWORK_PROTOCOL_TCP_SO
;
1656 case PURPLE_MEDIA_NETWORK_PROTOCOL_TCP
:
1657 return SIPE_NETWORK_PROTOCOL_TCP_ACTIVE
;
1660 case PURPLE_MEDIA_NETWORK_PROTOCOL_UDP
:
1661 return SIPE_NETWORK_PROTOCOL_UDP
;
1666 SipeEncryptionPolicy
1667 sipe_backend_media_get_encryption_policy(struct sipe_core_public
*sipe_public
)
1669 PurpleAccount
*account
= sipe_public
->backend_private
->account
;
1671 const char *policy
=
1672 purple_account_get_string(account
, "encryption-policy",
1675 if (sipe_strequal(policy
, "disabled")) {
1676 return SIPE_ENCRYPTION_POLICY_REJECTED
;
1677 } else if (sipe_strequal(policy
, "optional")) {
1678 return SIPE_ENCRYPTION_POLICY_OPTIONAL
;
1679 } else if (sipe_strequal(policy
, "required")) {
1680 return SIPE_ENCRYPTION_POLICY_REQUIRED
;
1682 return SIPE_ENCRYPTION_POLICY_OBEY_SERVER
;
1686 SipeEncryptionPolicy
1687 sipe_backend_media_get_encryption_policy(SIPE_UNUSED_PARAMETER
struct sipe_core_public
*sipe_public
)
1689 return SIPE_ENCRYPTION_POLICY_REJECTED
;