6 * Copyright (C) 2010-2016 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"
41 /* wrappers for write() & friends for socket handling */
42 #include "win32/win32dep.h"
45 #include "sipe-backend.h"
46 #include "sipe-core.h"
48 #include "purple-private.h"
51 * GStreamer interfaces fail to compile on ARM architecture with -Wcast-align
53 * Diagnostic #pragma was added in GCC 4.2.0
56 #if ((__GNUC__ == 4) && (__GNUC_MINOR__ >= 2)) || (__GNUC__ >= 5)
57 #if defined(__ARMEL__) || defined(__ARMEB__) || defined(__hppa__) || defined(__mips__) || defined(__sparc__) || (defined(__powerpc__) && defined(__NO_FPRS__))
58 #pragma GCC diagnostic ignored "-Wcast-align"
63 #include "media-gst.h"
64 #include <gst/rtp/gstrtcpbuffer.h>
65 #include <farstream/fs-session.h>
67 struct sipe_backend_media
{
70 * Number of media streams that were not yet locally accepted or rejected.
72 guint unconfirmed_streams
;
75 struct sipe_backend_media_stream
{
76 gboolean local_on_hold
;
77 gboolean remote_on_hold
;
79 gboolean initialized_cb_was_fired
;
84 #if PURPLE_VERSION_CHECK(3,0,0)
85 #define SIPE_RELAYS_G_TYPE G_TYPE_PTR_ARRAY
87 #define SIPE_RELAYS_G_TYPE G_TYPE_VALUE_ARRAY
91 sipe_backend_media_stream_free(struct sipe_backend_media_stream
*stream
)
93 if (stream
->gst_bus_cb_id
!= 0) {
96 pipe
= purple_media_manager_get_pipeline(
97 purple_media_manager_get());
101 bus
= gst_element_get_bus(pipe
);
102 g_signal_handler_disconnect(bus
, stream
->gst_bus_cb_id
);
103 stream
->gst_bus_cb_id
= 0;
104 gst_object_unref(bus
);
111 static PurpleMediaSessionType
sipe_media_to_purple(SipeMediaType type
);
112 static PurpleMediaCandidateType
sipe_candidate_type_to_purple(SipeCandidateType type
);
113 static SipeCandidateType
purple_candidate_type_to_sipe(PurpleMediaCandidateType type
);
114 static PurpleMediaNetworkProtocol
sipe_network_protocol_to_purple(SipeNetworkProtocol proto
);
115 static SipeNetworkProtocol
purple_network_protocol_to_sipe(PurpleMediaNetworkProtocol proto
);
118 maybe_signal_stream_initialized(struct sipe_media_call
*call
, gchar
*sessionid
)
120 if (call
->stream_initialized_cb
) {
121 struct sipe_media_stream
*stream
;
122 stream
= sipe_core_media_get_stream_by_id(call
, sessionid
);
124 if (sipe_backend_stream_initialized(call
, stream
) &&
125 !stream
->backend_private
->initialized_cb_was_fired
) {
126 call
->stream_initialized_cb(call
, stream
);
127 stream
->backend_private
->initialized_cb_was_fired
= TRUE
;
133 on_candidates_prepared_cb(SIPE_UNUSED_PARAMETER PurpleMedia
*media
,
135 SIPE_UNUSED_PARAMETER gchar
*participant
,
136 struct sipe_media_call
*call
)
138 maybe_signal_stream_initialized(call
, sessionid
);
142 on_codecs_changed_cb(SIPE_UNUSED_PARAMETER PurpleMedia
*media
,
144 struct sipe_media_call
*call
)
146 maybe_signal_stream_initialized(call
, sessionid
);
150 on_state_changed_cb(SIPE_UNUSED_PARAMETER PurpleMedia
*media
,
151 PurpleMediaState state
,
154 struct sipe_media_call
*call
)
156 SIPE_DEBUG_INFO("sipe_media_state_changed_cb: %d %s %s\n", state
, sessionid
, participant
);
157 if (state
== PURPLE_MEDIA_STATE_END
) {
158 if (sessionid
&& participant
) {
159 struct sipe_media_stream
*stream
=
160 sipe_core_media_get_stream_by_id(call
, sessionid
);
162 sipe_core_media_stream_end(stream
);
164 } else if (!sessionid
&& !participant
&& call
->media_end_cb
) {
165 call
->media_end_cb(call
);
171 capture_pipeline(const gchar
*label
) {
172 PurpleMediaManager
*manager
= purple_media_manager_get();
173 GstElement
*pipeline
= purple_media_manager_get_pipeline(manager
);
174 GST_DEBUG_BIN_TO_DOT_FILE_WITH_TS(GST_BIN(pipeline
), GST_DEBUG_GRAPH_SHOW_ALL
, label
);
178 on_error_cb(SIPE_UNUSED_PARAMETER PurpleMedia
*media
, gchar
*message
,
179 struct sipe_media_call
*call
)
181 capture_pipeline("ERROR");
184 call
->error_cb(call
, message
);
188 on_stream_info_cb(PurpleMedia
*media
,
189 PurpleMediaInfoType type
,
193 struct sipe_media_call
*call
)
195 if (type
== PURPLE_MEDIA_INFO_ACCEPT
) {
196 if (call
->call_accept_cb
&& !sessionid
&& !participant
)
197 call
->call_accept_cb(call
, local
);
198 else if (sessionid
&& participant
) {
199 struct sipe_media_stream
*stream
;
200 stream
= sipe_core_media_get_stream_by_id(call
, sessionid
);
202 if (!stream
->backend_private
->accepted
&& local
)
203 --call
->backend_private
->unconfirmed_streams
;
204 stream
->backend_private
->accepted
= TRUE
;
207 } else if (type
== PURPLE_MEDIA_INFO_HOLD
|| type
== PURPLE_MEDIA_INFO_UNHOLD
) {
209 gboolean state
= (type
== PURPLE_MEDIA_INFO_HOLD
);
212 // Hold specific stream
213 struct sipe_media_stream
*stream
;
214 stream
= sipe_core_media_get_stream_by_id(call
, sessionid
);
217 stream
->backend_private
->local_on_hold
= state
;
219 stream
->backend_private
->remote_on_hold
= state
;
222 GList
*session_ids
= purple_media_get_session_ids(media
);
224 for (; session_ids
; session_ids
= session_ids
->next
) {
225 struct sipe_media_stream
*stream
=
226 sipe_core_media_get_stream_by_id(call
, session_ids
->data
);
229 stream
->backend_private
->local_on_hold
= state
;
231 stream
->backend_private
->remote_on_hold
= state
;
234 g_list_free(session_ids
);
237 if (call
->call_hold_cb
)
238 call
->call_hold_cb(call
, local
, state
);
239 } else if (type
== PURPLE_MEDIA_INFO_HANGUP
|| type
== PURPLE_MEDIA_INFO_REJECT
) {
240 if (!sessionid
&& !participant
) {
241 if (type
== PURPLE_MEDIA_INFO_HANGUP
&& call
->call_hangup_cb
)
242 call
->call_hangup_cb(call
, local
);
243 else if (type
== PURPLE_MEDIA_INFO_REJECT
&& call
->call_reject_cb
&& !local
)
244 call
->call_reject_cb(call
, local
);
245 } else if (sessionid
&& participant
) {
246 struct sipe_media_stream
*stream
;
247 stream
= sipe_core_media_get_stream_by_id(call
, sessionid
);
250 purple_media_manager_set_application_data_callbacks(
251 purple_media_manager_get(), media
,
252 sessionid
, participant
, NULL
, NULL
, NULL
);
256 if (local
&& --call
->backend_private
->unconfirmed_streams
== 0 &&
257 call
->call_reject_cb
)
258 call
->call_reject_cb(call
, local
);
261 } else if (type
== PURPLE_MEDIA_INFO_MUTE
|| type
== PURPLE_MEDIA_INFO_UNMUTE
) {
262 struct sipe_media_stream
*stream
=
263 sipe_core_media_get_stream_by_id(call
, "audio");
265 if (stream
&& stream
->mute_cb
) {
266 stream
->mute_cb(stream
, type
== PURPLE_MEDIA_INFO_MUTE
);
272 on_candidate_pair_established_cb(SIPE_UNUSED_PARAMETER PurpleMedia
*media
,
273 const gchar
*sessionid
,
274 SIPE_UNUSED_PARAMETER
const gchar
*participant
,
275 SIPE_UNUSED_PARAMETER PurpleMediaCandidate
*local_candidate
,
276 SIPE_UNUSED_PARAMETER PurpleMediaCandidate
*remote_candidate
,
277 struct sipe_media_call
*call
)
279 struct sipe_media_stream
*stream
=
280 sipe_core_media_get_stream_by_id(call
, sessionid
);
286 #ifdef HAVE_PURPLE_NEW_TCP_ENUMS
287 if (purple_media_candidate_get_protocol(local_candidate
) != PURPLE_MEDIA_NETWORK_PROTOCOL_UDP
) {
288 purple_media_set_send_rtcp_mux(media
, sessionid
, participant
, TRUE
);
292 sipe_core_media_stream_candidate_pair_established(stream
);
295 struct sipe_backend_media
*
296 sipe_backend_media_new(struct sipe_core_public
*sipe_public
,
297 struct sipe_media_call
*call
,
298 const gchar
*participant
,
299 SipeMediaCallFlags flags
)
301 struct sipe_backend_media
*media
= g_new0(struct sipe_backend_media
, 1);
302 struct sipe_backend_private
*purple_private
= sipe_public
->backend_private
;
303 PurpleMediaManager
*manager
= purple_media_manager_get();
304 GstElement
*pipeline
;
306 if (flags
& SIPE_MEDIA_CALL_NO_UI
) {
308 media
->m
= purple_media_manager_create_private_media(manager
,
309 purple_private
->account
, "fsrtpconference",
310 participant
, flags
& SIPE_MEDIA_CALL_INITIATOR
);
312 SIPE_DEBUG_ERROR_NOFORMAT("Purple doesn't support private media");
315 media
->m
= purple_media_manager_create_media(manager
,
316 purple_private
->account
, "fsrtpconference",
317 participant
, flags
& SIPE_MEDIA_CALL_INITIATOR
);
320 g_signal_connect(G_OBJECT(media
->m
), "candidates-prepared",
321 G_CALLBACK(on_candidates_prepared_cb
), call
);
322 g_signal_connect(G_OBJECT(media
->m
), "codecs-changed",
323 G_CALLBACK(on_codecs_changed_cb
), call
);
324 g_signal_connect(G_OBJECT(media
->m
), "stream-info",
325 G_CALLBACK(on_stream_info_cb
), call
);
326 g_signal_connect(G_OBJECT(media
->m
), "error",
327 G_CALLBACK(on_error_cb
), call
);
328 g_signal_connect(G_OBJECT(media
->m
), "state-changed",
329 G_CALLBACK(on_state_changed_cb
), call
);
330 g_signal_connect(G_OBJECT(media
->m
), "candidate-pair-established",
331 G_CALLBACK(on_candidate_pair_established_cb
), call
);
334 /* On error, the pipeline is no longer in PLAYING state and libpurple
335 * will not switch it back to PLAYING, preventing any more calls until
336 * application restart. We switch the state ourselves here to negate
337 * effect of any error in previous call (if any). */
338 pipeline
= purple_media_manager_get_pipeline(manager
);
339 gst_element_set_state(pipeline
, GST_STATE_PLAYING
);
345 sipe_backend_media_free(struct sipe_backend_media
*media
)
351 sipe_backend_media_set_cname(struct sipe_backend_media
*media
, gchar
*cname
)
354 guint num_params
= 3;
355 GParameter
*params
= g_new0(GParameter
, num_params
);
356 params
[0].name
= "sdes-cname";
357 g_value_init(¶ms
[0].value
, G_TYPE_STRING
);
358 g_value_set_string(¶ms
[0].value
, cname
);
359 params
[1].name
= "sdes-name";
360 g_value_init(¶ms
[1].value
, G_TYPE_STRING
);
361 params
[2].name
= "sdes-tool";
362 g_value_init(¶ms
[2].value
, G_TYPE_STRING
);
364 purple_media_set_params(media
->m
, num_params
, params
);
366 g_value_unset(¶ms
[0].value
);
371 #define FS_CODECS_CONF \
372 "# Automatically created by SIPE plugin\n" \
373 "[application/X-DATA]\n" \
380 const gchar
*fs_codecs_conf
= FS_CODECS_CONF
;
381 GError
*error
= NULL
;
383 filename
= g_build_filename(purple_user_dir(), "fs-codec.conf", NULL
);
385 g_file_set_contents(filename
, fs_codecs_conf
, strlen(fs_codecs_conf
),
388 SIPE_DEBUG_ERROR("Couldn't create fs-codec.conf: %s",
397 append_relay(struct sipe_backend_media_relays
*relay_info
, const gchar
*ip
,
398 guint port
, gchar
*type
, gchar
*username
, gchar
*password
)
400 GstStructure
*gst_relay_info
;
402 gst_relay_info
= gst_structure_new("relay-info",
403 "ip", G_TYPE_STRING
, ip
,
404 "port", G_TYPE_UINT
, port
,
405 "relay-type", G_TYPE_STRING
, type
,
406 "username", G_TYPE_STRING
, username
,
407 "password", G_TYPE_STRING
, password
,
410 if (gst_relay_info
) {
411 #if PURPLE_VERSION_CHECK(3,0,0)
412 g_ptr_array_add((GPtrArray
*)relay_info
, gst_relay_info
);
415 memset(&value
, 0, sizeof(GValue
));
416 g_value_init(&value
, GST_TYPE_STRUCTURE
);
417 gst_value_set_structure(&value
, gst_relay_info
);
419 g_value_array_append((GValueArray
*)relay_info
, &value
);
420 gst_structure_free(gst_relay_info
);
425 struct sipe_backend_media_relays
*
426 sipe_backend_media_relays_convert(GSList
*media_relays
, gchar
*username
, gchar
*password
)
428 struct sipe_backend_media_relays
*relay_info
;
430 relay_info
= (struct sipe_backend_media_relays
*)
431 #if PURPLE_VERSION_CHECK(3,0,0)
432 g_ptr_array_new_with_free_func((GDestroyNotify
) gst_structure_free
);
434 g_value_array_new(0);
437 for (; media_relays
; media_relays
= media_relays
->next
) {\
438 struct sipe_media_relay
*relay
= media_relays
->data
;
440 /* Skip relays where IP could not be resolved. */
441 if (!relay
->hostname
)
444 if (relay
->udp_port
!= 0)
445 append_relay(relay_info
, relay
->hostname
, relay
->udp_port
,
446 "udp", username
, password
);
448 #ifdef HAVE_PURPLE_NEW_TCP_ENUMS
449 if (relay
->tcp_port
!= 0) {
451 if (relay
->tcp_port
== 443)
453 append_relay(relay_info
, relay
->hostname
, relay
->tcp_port
,
454 type
, username
, password
);
463 sipe_backend_media_relays_free(struct sipe_backend_media_relays
*media_relays
)
465 #if !PURPLE_VERSION_CHECK(3,0,0)
466 g_value_array_free((GValueArray
*)media_relays
);
468 g_ptr_array_unref((GPtrArray
*)media_relays
);
474 stream_readable_cb(SIPE_UNUSED_PARAMETER PurpleMediaManager
*manager
,
475 SIPE_UNUSED_PARAMETER PurpleMedia
*media
,
476 const gchar
*session_id
,
477 SIPE_UNUSED_PARAMETER
const gchar
*participant
,
480 struct sipe_media_call
*call
= (struct sipe_media_call
*)user_data
;
481 struct sipe_media_stream
*stream
;
483 SIPE_DEBUG_INFO("stream_readable_cb: %s is readable", session_id
);
485 stream
= sipe_core_media_get_stream_by_id(call
, session_id
);
488 sipe_core_media_stream_readable(stream
);
493 sipe_backend_media_stream_read(struct sipe_media_stream
*stream
,
494 guint8
*buffer
, gsize len
)
496 return purple_media_manager_receive_application_data(
497 purple_media_manager_get(),
498 stream
->call
->backend_private
->m
,
499 stream
->id
, stream
->call
->with
, buffer
, len
, FALSE
);
503 sipe_backend_media_stream_write(struct sipe_media_stream
*stream
,
504 guint8
*buffer
, gsize len
)
506 return purple_media_manager_send_application_data(
507 purple_media_manager_get(),
508 stream
->call
->backend_private
->m
,
509 stream
->id
, stream
->call
->with
, buffer
, len
, FALSE
);
513 stream_writable_cb(SIPE_UNUSED_PARAMETER PurpleMediaManager
*manager
,
514 SIPE_UNUSED_PARAMETER PurpleMedia
*media
,
515 const gchar
*session_id
,
516 SIPE_UNUSED_PARAMETER
const gchar
*participant
,
520 struct sipe_media_call
*call
= (struct sipe_media_call
*)user_data
;
521 struct sipe_media_stream
*stream
;
523 stream
= sipe_core_media_get_stream_by_id(call
, session_id
);
526 SIPE_DEBUG_ERROR("stream_writable_cb: stream %s not found!",
531 SIPE_DEBUG_INFO("stream_writable_cb: %s has become %swritable",
532 session_id
, writable
? "" : "not ");
534 sipe_core_media_stream_writable(stream
, writable
);
539 write_ms_h264_video_source_request(GstRTCPBuffer
*buffer
, guint32 ssrc
,
542 GstRTCPPacket packet
;
545 if (!gst_rtcp_buffer_add_packet(buffer
, GST_RTCP_TYPE_PSFB
, &packet
)) {
549 gst_rtcp_packet_fb_set_type(&packet
, GST_RTCP_PSFB_TYPE_AFB
);
550 gst_rtcp_packet_fb_set_sender_ssrc(&packet
, ssrc
);
551 gst_rtcp_packet_fb_set_media_ssrc(&packet
, SIPE_MSRTP_VSR_SOURCE_ANY
);
553 if (!gst_rtcp_packet_fb_set_fci_length(&packet
,
554 SIPE_MSRTP_VSR_FCI_WORDLEN
)) {
555 gst_rtcp_packet_remove(&packet
);
559 fci_data
= gst_rtcp_packet_fb_get_fci(&packet
);
561 sipe_core_msrtp_write_video_source_request(fci_data
, payload_type
);
567 on_sending_rtcp_cb(SIPE_UNUSED_PARAMETER GObject
*rtpsession
,
569 SIPE_UNUSED_PARAMETER gboolean is_early
,
570 FsSession
*fssession
)
572 gboolean was_changed
= FALSE
;
575 g_object_get(fssession
, "current-send-codec", &send_codec
, NULL
);
580 if (sipe_strequal(send_codec
->encoding_name
, "H264")) {
581 GstRTCPBuffer rtcp_buffer
= GST_RTCP_BUFFER_INIT
;
584 g_object_get(fssession
, "ssrc", &ssrc
, NULL
);
586 gst_rtcp_buffer_map(buffer
, GST_MAP_READWRITE
, &rtcp_buffer
);
587 was_changed
= write_ms_h264_video_source_request(&rtcp_buffer
,
588 ssrc
, send_codec
->id
);
589 gst_rtcp_buffer_unmap(&rtcp_buffer
);
592 fs_codec_destroy(send_codec
);
598 find_sinkpad(GValue
*value
, GstPad
*fssession_sinkpad
)
600 GstElement
*tee_srcpad
= g_value_get_object(value
);
602 return !(GST_PAD_PEER(tee_srcpad
) == fssession_sinkpad
);
606 gst_bus_cb(GstBus
*bus
, GstMessage
*msg
, struct sipe_media_stream
*stream
)
608 const GstStructure
*s
;
609 FsSession
*fssession
;
613 GValue val
= G_VALUE_INIT
;
615 if (GST_MESSAGE_TYPE(msg
) != GST_MESSAGE_ELEMENT
) {
619 s
= gst_message_get_structure(msg
);
620 if (!gst_structure_has_name(s
, "farstream-codecs-changed")) {
624 fssession
= g_value_get_object(gst_structure_get_value(s
, "session"));
625 g_return_if_fail(fssession
);
627 tee
= purple_media_get_tee(stream
->call
->backend_private
->m
, stream
->id
,
629 g_return_if_fail(tee
);
631 g_object_get(fssession
, "sink-pad", &sinkpad
, NULL
);
632 g_return_if_fail(sinkpad
);
634 /* Check whether this message is from the FsSession we're waiting for.
635 * For this to be true, the tee we got from libpurple has to be linked
636 * to "sink-pad" of the message's FsSession. */
637 it
= gst_element_iterate_src_pads(tee
);
638 if (gst_iterator_find_custom(it
, (GCompareFunc
)find_sinkpad
, &val
,
642 g_object_get(fssession
, "internal-session", &rtpsession
, NULL
);
644 g_signal_connect(rtpsession
, "on-sending-rtcp",
645 G_CALLBACK(on_sending_rtcp_cb
),
648 g_object_unref (rtpsession
);
651 g_signal_handler_disconnect(bus
,
652 stream
->backend_private
->gst_bus_cb_id
);
653 stream
->backend_private
->gst_bus_cb_id
= 0;
656 gst_iterator_free(it
);
657 gst_object_unref(sinkpad
);
660 struct sipe_backend_media_stream
*
661 sipe_backend_media_add_stream(struct sipe_media_stream
*stream
,
663 SipeIceVersion ice_version
,
665 struct sipe_backend_media_relays
*media_relays
,
666 guint min_port
, guint max_port
)
668 struct sipe_backend_media
*media
= stream
->call
->backend_private
;
669 struct sipe_backend_media_stream
*backend_stream
= NULL
;
671 // Preallocate enough space for all potential parameters to fit.
672 GParameter
*params
= g_new0(GParameter
, 6);
673 guint params_cnt
= 0;
675 GValue
*relay_info
= NULL
;
677 PurpleMediaAppDataCallbacks callbacks
= {
678 stream_readable_cb
, stream_writable_cb
682 if (ice_version
!= SIPE_ICE_NO_ICE
) {
683 transmitter
= "nice";
685 params
[params_cnt
].name
= "compatibility-mode";
686 g_value_init(¶ms
[params_cnt
].value
, G_TYPE_UINT
);
687 g_value_set_uint(¶ms
[params_cnt
].value
,
688 ice_version
== SIPE_ICE_DRAFT_6
?
689 NICE_COMPATIBILITY_OC2007
:
690 NICE_COMPATIBILITY_OC2007R2
);
694 params
[params_cnt
].name
= "min-port";
695 g_value_init(¶ms
[params_cnt
].value
, G_TYPE_UINT
);
696 g_value_set_uint(¶ms
[params_cnt
].value
, min_port
);
701 params
[params_cnt
].name
= "max-port";
702 g_value_init(¶ms
[params_cnt
].value
, G_TYPE_UINT
);
703 g_value_set_uint(¶ms
[params_cnt
].value
, max_port
);
708 params
[params_cnt
].name
= "relay-info";
709 g_value_init(¶ms
[params_cnt
].value
, SIPE_RELAYS_G_TYPE
);
710 g_value_set_boxed(¶ms
[params_cnt
].value
, media_relays
);
711 relay_info
= ¶ms
[params_cnt
].value
;
715 if (type
== SIPE_MEDIA_APPLICATION
) {
716 params
[params_cnt
].name
= "ice-udp";
717 g_value_init(¶ms
[params_cnt
].value
, G_TYPE_BOOLEAN
);
718 g_value_set_boolean(¶ms
[params_cnt
].value
, FALSE
);
721 params
[params_cnt
].name
= "reliable";
722 g_value_init(¶ms
[params_cnt
].value
, G_TYPE_BOOLEAN
);
723 g_value_set_boolean(¶ms
[params_cnt
].value
, TRUE
);
727 // TODO: session naming here, Communicator needs audio/video
728 transmitter
= "rawudp";
729 //sessionid = "sipe-voice-rawudp";
732 ensure_codecs_conf();
735 if (type
== SIPE_MEDIA_APPLICATION
) {
736 purple_media_manager_set_application_data_callbacks(
737 purple_media_manager_get(),
738 media
->m
, stream
->id
, stream
->call
->with
,
739 &callbacks
, stream
->call
, NULL
);
743 backend_stream
= g_new0(struct sipe_backend_media_stream
, 1);
745 pipe
= purple_media_manager_get_pipeline(purple_media_manager_get());
746 if (type
== SIPE_MEDIA_VIDEO
&& pipe
) {
749 bus
= gst_element_get_bus(pipe
);
750 backend_stream
->gst_bus_cb_id
= g_signal_connect(bus
, "message",
751 G_CALLBACK(gst_bus_cb
), stream
);
752 gst_object_unref(bus
);
755 if (purple_media_add_stream(media
->m
, stream
->id
, stream
->call
->with
,
756 sipe_media_to_purple(type
),
757 initiator
, transmitter
, params_cnt
,
760 ++media
->unconfirmed_streams
;
762 sipe_backend_media_stream_free(backend_stream
);
766 g_value_unset(relay_info
);
771 return backend_stream
;
775 sipe_backend_media_stream_end(struct sipe_media_call
*media
,
776 struct sipe_media_stream
*stream
)
778 purple_media_end(media
->backend_private
->m
, stream
->id
, NULL
);
782 sipe_backend_media_add_remote_candidates(struct sipe_media_call
*media
,
783 struct sipe_media_stream
*stream
,
786 GList
*udp_candidates
= NULL
;
788 #ifndef HAVE_PURPLE_NEW_TCP_ENUMS
789 /* Keep only UDP candidates in the list to set. */
791 PurpleMediaCandidate
*candidate
= candidates
->data
;
792 PurpleMediaNetworkProtocol proto
;
794 proto
= purple_media_candidate_get_protocol(candidate
);
795 if (proto
== PURPLE_MEDIA_NETWORK_PROTOCOL_UDP
)
796 udp_candidates
= g_list_append(udp_candidates
, candidate
);
798 candidates
= candidates
->next
;
801 candidates
= udp_candidates
;
804 purple_media_add_remote_candidates(media
->backend_private
->m
,
805 stream
->id
, media
->with
, candidates
);
807 g_list_free(udp_candidates
);
810 gboolean
sipe_backend_media_is_initiator(struct sipe_media_call
*media
,
811 struct sipe_media_stream
*stream
)
813 return purple_media_is_initiator(media
->backend_private
->m
,
814 stream
? stream
->id
: NULL
,
815 stream
? media
->with
: NULL
);
818 gboolean
sipe_backend_media_accepted(struct sipe_backend_media
*media
)
820 return purple_media_accepted(media
->m
, NULL
, NULL
);
824 sipe_backend_stream_initialized(struct sipe_media_call
*media
,
825 struct sipe_media_stream
*stream
)
827 g_return_val_if_fail(media
, FALSE
);
828 g_return_val_if_fail(stream
, FALSE
);
830 if (purple_media_candidates_prepared(media
->backend_private
->m
,
831 stream
->id
, media
->with
)) {
833 codecs
= purple_media_get_codecs(media
->backend_private
->m
,
836 purple_media_codec_list_free(codecs
);
844 duplicate_tcp_candidates(GList
*candidates
)
847 GList
*result
= NULL
;
849 for (i
= candidates
; i
; i
= i
->next
) {
850 PurpleMediaCandidate
*candidate
= i
->data
;
851 PurpleMediaNetworkProtocol protocol
=
852 purple_media_candidate_get_protocol(candidate
);
854 purple_media_candidate_get_component_id(candidate
);
856 if (protocol
!= PURPLE_MEDIA_NETWORK_PROTOCOL_UDP
) {
857 PurpleMediaCandidate
*c2
;
859 if (component_id
!= PURPLE_MEDIA_COMPONENT_RTP
) {
860 /* Ignore TCP candidates for other than
861 * the first component. */
862 g_object_unref(candidate
);
866 c2
= purple_media_candidate_copy(candidate
);
868 "component-id", PURPLE_MEDIA_COMPONENT_RTCP
,
870 result
= g_list_append(result
, c2
);
873 result
= g_list_append(result
, candidate
);
876 g_list_free(candidates
);
882 sipe_backend_media_stream_get_active_local_candidates(struct sipe_media_stream
*stream
)
884 GList
*candidates
= purple_media_get_active_local_candidates(
885 stream
->call
->backend_private
->m
, stream
->id
,
887 return duplicate_tcp_candidates(candidates
);
891 sipe_backend_media_stream_get_active_remote_candidates(struct sipe_media_stream
*stream
)
893 GList
*candidates
= purple_media_get_active_remote_candidates(
894 stream
->call
->backend_private
->m
, stream
->id
,
896 return duplicate_tcp_candidates(candidates
);
901 sipe_backend_media_set_encryption_keys(struct sipe_media_call
*media
,
902 struct sipe_media_stream
*stream
,
903 const guchar
*encryption_key
,
904 const guchar
*decryption_key
)
906 purple_media_set_encryption_parameters(media
->backend_private
->m
,
910 (gchar
*)encryption_key
, SIPE_SRTP_KEY_LEN
);
911 purple_media_set_decryption_parameters(media
->backend_private
->m
,
912 stream
->id
, media
->with
,
915 (gchar
*)decryption_key
, SIPE_SRTP_KEY_LEN
);
919 sipe_backend_media_set_encryption_keys(SIPE_UNUSED_PARAMETER
struct sipe_media_call
*media
,
920 SIPE_UNUSED_PARAMETER
struct sipe_media_stream
*stream
,
921 SIPE_UNUSED_PARAMETER
const guchar
*encryption_key
,
922 SIPE_UNUSED_PARAMETER
const guchar
*decryption_key
)
926 void sipe_backend_stream_hold(struct sipe_media_call
*media
,
927 struct sipe_media_stream
*stream
,
930 purple_media_stream_info(media
->backend_private
->m
, PURPLE_MEDIA_INFO_HOLD
,
931 stream
->id
, media
->with
, local
);
934 void sipe_backend_stream_unhold(struct sipe_media_call
*media
,
935 struct sipe_media_stream
*stream
,
938 purple_media_stream_info(media
->backend_private
->m
, PURPLE_MEDIA_INFO_UNHOLD
,
939 stream
->id
, media
->with
, local
);
942 gboolean
sipe_backend_stream_is_held(struct sipe_media_stream
*stream
)
944 g_return_val_if_fail(stream
, FALSE
);
946 return stream
->backend_private
->local_on_hold
||
947 stream
->backend_private
->remote_on_hold
;
950 struct sipe_backend_codec
*
951 sipe_backend_codec_new(int id
, const char *name
, SipeMediaType type
, guint clock_rate
)
953 if (sipe_strequal(name
, "X-H264UC")) {
957 return (struct sipe_backend_codec
*)purple_media_codec_new(id
, name
,
958 sipe_media_to_purple(type
),
963 sipe_backend_codec_free(struct sipe_backend_codec
*codec
)
966 g_object_unref(codec
);
970 sipe_backend_codec_get_id(struct sipe_backend_codec
*codec
)
972 return purple_media_codec_get_id((PurpleMediaCodec
*)codec
);
976 sipe_backend_codec_get_name(struct sipe_backend_codec
*codec
)
978 /* Not explicitly documented, but return value must be g_free()'d */
979 return purple_media_codec_get_encoding_name((PurpleMediaCodec
*)codec
);
983 sipe_backend_codec_get_clock_rate(struct sipe_backend_codec
*codec
)
985 return purple_media_codec_get_clock_rate((PurpleMediaCodec
*)codec
);
989 sipe_backend_codec_add_optional_parameter(struct sipe_backend_codec
*codec
,
990 const gchar
*name
, const gchar
*value
)
992 purple_media_codec_add_optional_parameter((PurpleMediaCodec
*)codec
, name
, value
);
996 sipe_backend_codec_get_optional_parameters(struct sipe_backend_codec
*codec
)
998 return purple_media_codec_get_optional_parameters((PurpleMediaCodec
*)codec
);
1002 sipe_backend_set_remote_codecs(struct sipe_media_call
*media
,
1003 struct sipe_media_stream
*stream
,
1006 return purple_media_set_remote_codecs(media
->backend_private
->m
,
1007 stream
->id
, media
->with
, codecs
);
1011 sipe_backend_get_local_codecs(struct sipe_media_call
*media
,
1012 struct sipe_media_stream
*stream
)
1014 GList
*codecs
= purple_media_get_codecs(media
->backend_private
->m
,
1017 gboolean is_conference
= (g_strstr_len(media
->with
, strlen(media
->with
),
1018 "app:conf:audio-video:") != NULL
);
1021 * Do not announce Theora. Its optional parameters are too long,
1022 * Communicator rejects such SDP message and does not support the codec
1025 * For some yet unknown reason, A/V conferencing server does not accept
1026 * voice stream sent by SIPE when SIREN codec is in use. Nevertheless,
1027 * we are able to decode incoming SIREN from server and with MSOC
1028 * client, bidirectional call using the codec works. Until resolved,
1029 * do not try to negotiate SIREN usage when conferencing. PCMA or PCMU
1030 * seems to work properly in this scenario.
1033 PurpleMediaCodec
*codec
= i
->data
;
1034 gchar
*encoding_name
= purple_media_codec_get_encoding_name(codec
);
1036 if (sipe_strequal(encoding_name
,"THEORA") ||
1037 (is_conference
&& sipe_strequal(encoding_name
,"SIREN"))) {
1039 g_object_unref(codec
);
1041 codecs
= g_list_delete_link(codecs
, i
);
1043 } else if (sipe_strequal(encoding_name
, "H264")) {
1045 * Sanitize H264 codec:
1046 * - the encoding name must be "X-H264UC"
1047 * - remove "sprop-parameter-sets" parameter which is
1049 * - add "packetization-mode" parameter if not already
1053 PurpleMediaCodec
*new_codec
;
1056 new_codec
= purple_media_codec_new(
1057 purple_media_codec_get_id(codec
),
1060 purple_media_codec_get_clock_rate(codec
));
1062 g_object_set(new_codec
, "channels",
1063 purple_media_codec_get_channels(codec
),
1066 it
= purple_media_codec_get_optional_parameters(codec
);
1068 for (; it
; it
= g_list_next(it
)) {
1069 PurpleKeyValuePair
*pair
= it
->data
;
1071 if (sipe_strequal(pair
->key
, "sprop-parameter-sets")) {
1075 purple_media_codec_add_optional_parameter(new_codec
,
1076 pair
->key
, pair
->value
);
1079 if (!purple_media_codec_get_optional_parameter(new_codec
,
1080 "packetization-mode", NULL
)) {
1081 purple_media_codec_add_optional_parameter(new_codec
,
1082 "packetization-mode",
1083 "1;mst-mode=NI-TC");
1086 i
->data
= new_codec
;
1088 g_object_unref(codec
);
1092 g_free(encoding_name
);
1098 struct sipe_backend_candidate
*
1099 sipe_backend_candidate_new(const gchar
*foundation
,
1100 SipeComponentType component
,
1101 SipeCandidateType type
, SipeNetworkProtocol proto
,
1102 const gchar
*ip
, guint port
,
1103 const gchar
*username
,
1104 const gchar
*password
)
1106 PurpleMediaCandidate
*c
= purple_media_candidate_new(
1107 /* Libnice and Farsight rely on non-NULL foundation to
1108 * distinguish between candidates of a component. When NULL
1109 * foundation is passed (ie. ICE draft 6 does not use foudation),
1110 * use username instead. If no foundation is provided, Farsight
1111 * may signal an active candidate different from the one actually
1112 * in use. See Farsight's agent_new_selected_pair() in
1113 * fs-nice-stream-transmitter.h where first candidate in the
1114 * remote list is always selected when no foundation. */
1115 foundation
? foundation
: username
,
1117 sipe_candidate_type_to_purple(type
),
1118 sipe_network_protocol_to_purple(proto
),
1121 g_object_set(c
, "username", username
, "password", password
, NULL
);
1122 return (struct sipe_backend_candidate
*)c
;
1126 sipe_backend_candidate_free(struct sipe_backend_candidate
*candidate
)
1129 g_object_unref(candidate
);
1133 sipe_backend_candidate_get_username(struct sipe_backend_candidate
*candidate
)
1135 /* Not explicitly documented, but return value must be g_free()'d */
1136 return purple_media_candidate_get_username((PurpleMediaCandidate
*)candidate
);
1140 sipe_backend_candidate_get_password(struct sipe_backend_candidate
*candidate
)
1142 /* Not explicitly documented, but return value must be g_free()'d */
1143 return purple_media_candidate_get_password((PurpleMediaCandidate
*)candidate
);
1147 sipe_backend_candidate_get_foundation(struct sipe_backend_candidate
*candidate
)
1149 /* Not explicitly documented, but return value must be g_free()'d */
1150 return purple_media_candidate_get_foundation((PurpleMediaCandidate
*)candidate
);
1154 sipe_backend_candidate_get_ip(struct sipe_backend_candidate
*candidate
)
1156 /* Not explicitly documented, but return value must be g_free()'d */
1157 return purple_media_candidate_get_ip((PurpleMediaCandidate
*)candidate
);
1161 sipe_backend_candidate_get_port(struct sipe_backend_candidate
*candidate
)
1163 return purple_media_candidate_get_port((PurpleMediaCandidate
*)candidate
);
1167 sipe_backend_candidate_get_base_ip(struct sipe_backend_candidate
*candidate
)
1169 /* Not explicitly documented, but return value must be g_free()'d */
1170 return purple_media_candidate_get_base_ip((PurpleMediaCandidate
*)candidate
);
1174 sipe_backend_candidate_get_base_port(struct sipe_backend_candidate
*candidate
)
1176 return purple_media_candidate_get_base_port((PurpleMediaCandidate
*)candidate
);
1180 sipe_backend_candidate_get_priority(struct sipe_backend_candidate
*candidate
)
1182 return purple_media_candidate_get_priority((PurpleMediaCandidate
*)candidate
);
1186 sipe_backend_candidate_set_priority(struct sipe_backend_candidate
*candidate
, guint32 priority
)
1188 g_object_set(candidate
, "priority", priority
, NULL
);
1192 sipe_backend_candidate_get_component_type(struct sipe_backend_candidate
*candidate
)
1194 return purple_media_candidate_get_component_id((PurpleMediaCandidate
*)candidate
);
1198 sipe_backend_candidate_get_type(struct sipe_backend_candidate
*candidate
)
1200 PurpleMediaCandidateType type
=
1201 purple_media_candidate_get_candidate_type((PurpleMediaCandidate
*)candidate
);
1202 return purple_candidate_type_to_sipe(type
);
1206 sipe_backend_candidate_get_protocol(struct sipe_backend_candidate
*candidate
)
1208 PurpleMediaNetworkProtocol proto
=
1209 purple_media_candidate_get_protocol((PurpleMediaCandidate
*)candidate
);
1210 return purple_network_protocol_to_sipe(proto
);
1214 remove_lone_candidate_cb(SIPE_UNUSED_PARAMETER gpointer key
,
1218 GList
*entry
= value
;
1219 GList
**candidates
= user_data
;
1221 g_object_unref(entry
->data
);
1222 *candidates
= g_list_delete_link(*candidates
, entry
);
1226 ensure_candidate_pairs(GList
*candidates
)
1228 GHashTable
*lone_cand_links
;
1231 lone_cand_links
= g_hash_table_new_full(g_str_hash
, g_str_equal
, g_free
, NULL
);
1233 for (i
= candidates
; i
; i
= i
->next
) {
1234 PurpleMediaCandidate
*c
= i
->data
;
1235 gchar
*foundation
= purple_media_candidate_get_foundation(c
);
1237 if (g_hash_table_lookup(lone_cand_links
, foundation
)) {
1238 g_hash_table_remove(lone_cand_links
, foundation
);
1241 g_hash_table_insert(lone_cand_links
, foundation
, i
);
1245 g_hash_table_foreach(lone_cand_links
, remove_lone_candidate_cb
, &candidates
);
1246 g_hash_table_destroy(lone_cand_links
);
1252 sipe_backend_get_local_candidates(struct sipe_media_call
*media
,
1253 struct sipe_media_stream
*stream
)
1256 purple_media_get_local_candidates(media
->backend_private
->m
,
1259 candidates
= duplicate_tcp_candidates(candidates
);
1262 * Sometimes purple will not return complete list of candidates, even
1263 * after "candidates-prepared" signal is emitted. This is a feature of
1264 * libnice, namely affecting candidates discovered via UPnP. Nice does
1265 * not wait until discovery is finished and can signal end of candidate
1266 * gathering before all responses from UPnP enabled gateways are received.
1268 * Remove any incomplete RTP+RTCP candidate pairs from the list.
1270 candidates
= ensure_candidate_pairs(candidates
);
1275 sipe_backend_media_accept(struct sipe_backend_media
*media
, gboolean local
)
1278 purple_media_stream_info(media
->m
, PURPLE_MEDIA_INFO_ACCEPT
,
1283 sipe_backend_media_hangup(struct sipe_backend_media
*media
, gboolean local
)
1286 purple_media_stream_info(media
->m
, PURPLE_MEDIA_INFO_HANGUP
,
1291 sipe_backend_media_reject(struct sipe_backend_media
*media
, gboolean local
)
1294 purple_media_stream_info(media
->m
, PURPLE_MEDIA_INFO_REJECT
,
1298 static PurpleMediaSessionType
sipe_media_to_purple(SipeMediaType type
)
1301 case SIPE_MEDIA_AUDIO
: return PURPLE_MEDIA_AUDIO
;
1302 case SIPE_MEDIA_VIDEO
: return PURPLE_MEDIA_VIDEO
;
1304 case SIPE_MEDIA_APPLICATION
: return PURPLE_MEDIA_APPLICATION
;
1306 default: return PURPLE_MEDIA_NONE
;
1310 static PurpleMediaCandidateType
1311 sipe_candidate_type_to_purple(SipeCandidateType type
)
1314 case SIPE_CANDIDATE_TYPE_HOST
: return PURPLE_MEDIA_CANDIDATE_TYPE_HOST
;
1315 case SIPE_CANDIDATE_TYPE_RELAY
: return PURPLE_MEDIA_CANDIDATE_TYPE_RELAY
;
1316 case SIPE_CANDIDATE_TYPE_SRFLX
: return PURPLE_MEDIA_CANDIDATE_TYPE_SRFLX
;
1317 case SIPE_CANDIDATE_TYPE_PRFLX
: return PURPLE_MEDIA_CANDIDATE_TYPE_PRFLX
;
1318 default: return PURPLE_MEDIA_CANDIDATE_TYPE_HOST
;
1322 static SipeCandidateType
1323 purple_candidate_type_to_sipe(PurpleMediaCandidateType type
)
1326 case PURPLE_MEDIA_CANDIDATE_TYPE_HOST
: return SIPE_CANDIDATE_TYPE_HOST
;
1327 case PURPLE_MEDIA_CANDIDATE_TYPE_RELAY
: return SIPE_CANDIDATE_TYPE_RELAY
;
1328 case PURPLE_MEDIA_CANDIDATE_TYPE_SRFLX
: return SIPE_CANDIDATE_TYPE_SRFLX
;
1329 case PURPLE_MEDIA_CANDIDATE_TYPE_PRFLX
: return SIPE_CANDIDATE_TYPE_PRFLX
;
1330 default: return SIPE_CANDIDATE_TYPE_HOST
;
1334 static PurpleMediaNetworkProtocol
1335 sipe_network_protocol_to_purple(SipeNetworkProtocol proto
)
1338 #ifdef HAVE_PURPLE_NEW_TCP_ENUMS
1339 case SIPE_NETWORK_PROTOCOL_TCP_ACTIVE
:
1340 return PURPLE_MEDIA_NETWORK_PROTOCOL_TCP_ACTIVE
;
1341 case SIPE_NETWORK_PROTOCOL_TCP_PASSIVE
:
1342 return PURPLE_MEDIA_NETWORK_PROTOCOL_TCP_PASSIVE
;
1343 case SIPE_NETWORK_PROTOCOL_TCP_SO
:
1344 return PURPLE_MEDIA_NETWORK_PROTOCOL_TCP_SO
;
1346 case SIPE_NETWORK_PROTOCOL_TCP_ACTIVE
:
1347 case SIPE_NETWORK_PROTOCOL_TCP_PASSIVE
:
1348 case SIPE_NETWORK_PROTOCOL_TCP_SO
:
1349 return PURPLE_MEDIA_NETWORK_PROTOCOL_TCP
;
1352 case SIPE_NETWORK_PROTOCOL_UDP
:
1353 return PURPLE_MEDIA_NETWORK_PROTOCOL_UDP
;
1357 static SipeNetworkProtocol
1358 purple_network_protocol_to_sipe(PurpleMediaNetworkProtocol proto
)
1361 #ifdef HAVE_PURPLE_NEW_TCP_ENUMS
1362 case PURPLE_MEDIA_NETWORK_PROTOCOL_TCP_ACTIVE
:
1363 return SIPE_NETWORK_PROTOCOL_TCP_ACTIVE
;
1364 case PURPLE_MEDIA_NETWORK_PROTOCOL_TCP_PASSIVE
:
1365 return SIPE_NETWORK_PROTOCOL_TCP_PASSIVE
;
1366 case PURPLE_MEDIA_NETWORK_PROTOCOL_TCP_SO
:
1367 return SIPE_NETWORK_PROTOCOL_TCP_SO
;
1369 case PURPLE_MEDIA_NETWORK_PROTOCOL_TCP
:
1370 return SIPE_NETWORK_PROTOCOL_TCP_ACTIVE
;
1373 case PURPLE_MEDIA_NETWORK_PROTOCOL_UDP
:
1374 return SIPE_NETWORK_PROTOCOL_UDP
;
1379 SipeEncryptionPolicy
1380 sipe_backend_media_get_encryption_policy(struct sipe_core_public
*sipe_public
)
1382 PurpleAccount
*account
= sipe_public
->backend_private
->account
;
1384 const char *policy
=
1385 purple_account_get_string(account
, "encryption-policy",
1388 if (sipe_strequal(policy
, "disabled")) {
1389 return SIPE_ENCRYPTION_POLICY_REJECTED
;
1390 } else if (sipe_strequal(policy
, "optional")) {
1391 return SIPE_ENCRYPTION_POLICY_OPTIONAL
;
1392 } else if (sipe_strequal(policy
, "required")) {
1393 return SIPE_ENCRYPTION_POLICY_REQUIRED
;
1395 return SIPE_ENCRYPTION_POLICY_OBEY_SERVER
;
1399 SipeEncryptionPolicy
1400 sipe_backend_media_get_encryption_policy(SIPE_UNUSED_PARAMETER
struct sipe_core_public
*sipe_public
)
1402 return SIPE_ENCRYPTION_POLICY_REJECTED
;