transport: move user agent code to core module
[siplcs.git] / src / purple / purple-media.c
blobd589009e908a1b4655dc5b7959dda4f668913bb0
1 /**
2 * @file purple-media.c
4 * pidgin-sipe
6 * Copyright (C) 2010-2018 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
23 #ifdef HAVE_CONFIG_H
24 #include "config.h"
25 #endif
27 #include "glib.h"
28 #include "glib/gstdio.h"
29 #include <fcntl.h>
30 #include <string.h>
31 #ifdef HAVE_UNISTD_H
32 #include <unistd.h>
33 #endif
35 #include "sipe-common.h"
37 #include "mediamanager.h"
38 #include "agent.h"
40 #ifdef _WIN32
41 /* wrappers for write() & friends for socket handling */
42 #include "win32/win32dep.h"
43 #endif
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
55 #if defined(__GNUC__)
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"
59 #endif
60 #endif
61 #endif
63 #include "media-gst.h"
64 #include <gst/rtp/gstrtcpbuffer.h>
65 #include <farstream/fs-session.h>
67 struct sipe_backend_media {
68 PurpleMedia *m;
69 /**
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;
78 gboolean accepted;
79 gboolean initialized_cb_was_fired;
81 gulong gst_bus_cb_id;
83 GObject *rtpsession;
84 gulong on_sending_rtcp_cb_id;
87 void
88 sipe_backend_media_stream_free(struct sipe_backend_media_stream *stream)
90 if (stream->gst_bus_cb_id != 0) {
91 GstElement *pipe;
93 pipe = purple_media_manager_get_pipeline(
94 purple_media_manager_get());
95 if (pipe) {
96 GstBus *bus;
98 bus = gst_element_get_bus(pipe);
99 g_signal_handler_disconnect(bus, stream->gst_bus_cb_id);
100 stream->gst_bus_cb_id = 0;
101 gst_object_unref(bus);
105 if (stream->rtpsession) {
106 g_clear_object(&stream->rtpsession);
109 g_free(stream);
112 static PurpleMediaSessionType sipe_media_to_purple(SipeMediaType type);
113 static PurpleMediaCandidateType sipe_candidate_type_to_purple(SipeCandidateType type);
114 static SipeCandidateType purple_candidate_type_to_sipe(PurpleMediaCandidateType type);
115 static PurpleMediaNetworkProtocol sipe_network_protocol_to_purple(SipeNetworkProtocol proto);
116 static SipeNetworkProtocol purple_network_protocol_to_sipe(PurpleMediaNetworkProtocol proto);
118 static void
119 maybe_signal_stream_initialized(struct sipe_media_call *call, gchar *sessionid)
121 if (call->stream_initialized_cb) {
122 struct sipe_media_stream *stream;
123 stream = sipe_core_media_get_stream_by_id(call, sessionid);
125 if (stream &&
126 sipe_backend_stream_initialized(call, stream) &&
127 !stream->backend_private->initialized_cb_was_fired) {
128 call->stream_initialized_cb(call, stream);
129 stream->backend_private->initialized_cb_was_fired = TRUE;
134 static void
135 on_candidates_prepared_cb(SIPE_UNUSED_PARAMETER PurpleMedia *media,
136 gchar *sessionid,
137 SIPE_UNUSED_PARAMETER gchar *participant,
138 struct sipe_media_call *call)
140 maybe_signal_stream_initialized(call, sessionid);
143 static void
144 on_codecs_changed_cb(SIPE_UNUSED_PARAMETER PurpleMedia *media,
145 gchar *sessionid,
146 struct sipe_media_call *call)
148 maybe_signal_stream_initialized(call, sessionid);
151 static void
152 on_state_changed_cb(SIPE_UNUSED_PARAMETER PurpleMedia *media,
153 PurpleMediaState state,
154 gchar *sessionid,
155 gchar *participant,
156 struct sipe_media_call *call)
158 SIPE_DEBUG_INFO("sipe_media_state_changed_cb: %d %s %s\n", state, sessionid, participant);
160 if (state == PURPLE_MEDIA_STATE_CONNECTED && sessionid && participant) {
161 struct sipe_media_stream *stream;
163 stream = sipe_core_media_get_stream_by_id(call, sessionid);
164 if (stream && stream->backend_private->rtpsession &&
165 stream->backend_private->on_sending_rtcp_cb_id != 0) {
166 struct sipe_backend_media_stream *backend_stream;
168 SIPE_DEBUG_INFO_NOFORMAT("Peer started sending. Ceasing"
169 " video source requests.");
171 backend_stream = stream->backend_private;
173 g_signal_handler_disconnect(backend_stream->rtpsession,
174 backend_stream->on_sending_rtcp_cb_id);
175 g_clear_object(&backend_stream->rtpsession);
176 backend_stream->on_sending_rtcp_cb_id = 0;
178 } else if (state == PURPLE_MEDIA_STATE_END) {
179 if (sessionid && participant) {
180 struct sipe_media_stream *stream =
181 sipe_core_media_get_stream_by_id(call, sessionid);
182 if (stream) {
183 sipe_core_media_stream_end(stream);
185 } else if (!sessionid && !participant && call->media_end_cb) {
186 call->media_end_cb(call);
191 void
192 capture_pipeline(const gchar *label) {
193 PurpleMediaManager *manager = purple_media_manager_get();
194 GstElement *pipeline = purple_media_manager_get_pipeline(manager);
195 GST_DEBUG_BIN_TO_DOT_FILE_WITH_TS(GST_BIN(pipeline), GST_DEBUG_GRAPH_SHOW_ALL, label);
198 static void
199 on_error_cb(SIPE_UNUSED_PARAMETER PurpleMedia *media, gchar *message,
200 struct sipe_media_call *call)
202 capture_pipeline("ERROR");
204 if (call->error_cb)
205 call->error_cb(call, message);
208 static void
209 on_stream_info_cb(PurpleMedia *media,
210 PurpleMediaInfoType type,
211 gchar *sessionid,
212 gchar *participant,
213 gboolean local,
214 struct sipe_media_call *call)
216 if (type == PURPLE_MEDIA_INFO_ACCEPT) {
217 if (call->call_accept_cb && !sessionid && !participant)
218 call->call_accept_cb(call, local);
219 else if (sessionid && participant) {
220 struct sipe_media_stream *stream;
221 stream = sipe_core_media_get_stream_by_id(call, sessionid);
222 if (stream) {
223 if (!stream->backend_private->accepted && local)
224 --call->backend_private->unconfirmed_streams;
225 stream->backend_private->accepted = TRUE;
228 } else if (type == PURPLE_MEDIA_INFO_HOLD || type == PURPLE_MEDIA_INFO_UNHOLD) {
230 gboolean state = (type == PURPLE_MEDIA_INFO_HOLD);
232 if (sessionid) {
233 // Hold specific stream
234 struct sipe_media_stream *stream;
235 stream = sipe_core_media_get_stream_by_id(call, sessionid);
237 if (stream) {
238 if (local)
239 stream->backend_private->local_on_hold = state;
240 else
241 stream->backend_private->remote_on_hold = state;
243 } else {
244 // Hold all streams
245 GList *session_ids = purple_media_get_session_ids(media);
247 for (; session_ids; session_ids = session_ids->next) {
248 struct sipe_media_stream *stream =
249 sipe_core_media_get_stream_by_id(call, session_ids->data);
251 if (stream) {
252 if (local)
253 stream->backend_private->local_on_hold = state;
254 else
255 stream->backend_private->remote_on_hold = state;
259 g_list_free(session_ids);
262 if (call->call_hold_cb)
263 call->call_hold_cb(call, local, state);
264 } else if (type == PURPLE_MEDIA_INFO_HANGUP || type == PURPLE_MEDIA_INFO_REJECT) {
265 if (!sessionid && !participant) {
266 if (type == PURPLE_MEDIA_INFO_HANGUP && call->call_hangup_cb)
267 call->call_hangup_cb(call, local);
268 else if (type == PURPLE_MEDIA_INFO_REJECT && call->call_reject_cb && !local)
269 call->call_reject_cb(call, local);
270 } else if (sessionid && participant) {
271 struct sipe_media_stream *stream;
272 stream = sipe_core_media_get_stream_by_id(call, sessionid);
274 #ifdef HAVE_XDATA
275 purple_media_manager_set_application_data_callbacks(
276 purple_media_manager_get(), media,
277 sessionid, participant, NULL, NULL, NULL);
278 #endif
280 if (stream) {
281 if (local && --call->backend_private->unconfirmed_streams == 0 &&
282 call->call_reject_cb)
283 call->call_reject_cb(call, local);
286 } else if (type == PURPLE_MEDIA_INFO_MUTE || type == PURPLE_MEDIA_INFO_UNMUTE) {
287 struct sipe_media_stream *stream =
288 sipe_core_media_get_stream_by_id(call, "audio");
290 if (stream && stream->mute_cb) {
291 stream->mute_cb(stream, type == PURPLE_MEDIA_INFO_MUTE);
296 static void
297 on_candidate_pair_established_cb(SIPE_UNUSED_PARAMETER PurpleMedia *media,
298 const gchar *sessionid,
299 SIPE_UNUSED_PARAMETER const gchar *participant,
300 SIPE_UNUSED_PARAMETER PurpleMediaCandidate *local_candidate,
301 SIPE_UNUSED_PARAMETER PurpleMediaCandidate *remote_candidate,
302 struct sipe_media_call *call)
304 struct sipe_media_stream *stream =
305 sipe_core_media_get_stream_by_id(call, sessionid);
307 if (!stream) {
308 return;
311 #ifdef HAVE_PURPLE_NEW_TCP_ENUMS
312 if (purple_media_candidate_get_protocol(local_candidate) != PURPLE_MEDIA_NETWORK_PROTOCOL_UDP) {
313 purple_media_set_send_rtcp_mux(media, sessionid, participant, TRUE);
315 #endif
317 sipe_core_media_stream_candidate_pair_established(stream);
320 struct sipe_backend_media *
321 sipe_backend_media_new(struct sipe_core_public *sipe_public,
322 struct sipe_media_call *call,
323 const gchar *participant,
324 SipeMediaCallFlags flags)
326 struct sipe_backend_media *media = g_new0(struct sipe_backend_media, 1);
327 struct sipe_backend_private *purple_private = sipe_public->backend_private;
328 PurpleMediaManager *manager = purple_media_manager_get();
329 GstElement *pipeline;
331 if (flags & SIPE_MEDIA_CALL_NO_UI) {
332 #ifdef HAVE_XDATA
333 media->m = purple_media_manager_create_private_media(manager,
334 purple_private->account, "fsrtpconference",
335 participant, flags & SIPE_MEDIA_CALL_INITIATOR);
336 #else
337 SIPE_DEBUG_ERROR_NOFORMAT("Purple doesn't support private media");
338 #endif
339 } else {
340 media->m = purple_media_manager_create_media(manager,
341 purple_private->account, "fsrtpconference",
342 participant, flags & SIPE_MEDIA_CALL_INITIATOR);
345 g_signal_connect(G_OBJECT(media->m), "candidates-prepared",
346 G_CALLBACK(on_candidates_prepared_cb), call);
347 g_signal_connect(G_OBJECT(media->m), "codecs-changed",
348 G_CALLBACK(on_codecs_changed_cb), call);
349 g_signal_connect(G_OBJECT(media->m), "stream-info",
350 G_CALLBACK(on_stream_info_cb), call);
351 g_signal_connect(G_OBJECT(media->m), "error",
352 G_CALLBACK(on_error_cb), call);
353 g_signal_connect(G_OBJECT(media->m), "state-changed",
354 G_CALLBACK(on_state_changed_cb), call);
355 g_signal_connect(G_OBJECT(media->m), "candidate-pair-established",
356 G_CALLBACK(on_candidate_pair_established_cb), call);
359 /* On error, the pipeline is no longer in PLAYING state and libpurple
360 * will not switch it back to PLAYING, preventing any more calls until
361 * application restart. We switch the state ourselves here to negate
362 * effect of any error in previous call (if any). */
363 pipeline = purple_media_manager_get_pipeline(manager);
364 gst_element_set_state(pipeline, GST_STATE_PLAYING);
366 return media;
369 void
370 sipe_backend_media_free(struct sipe_backend_media *media)
372 g_free(media);
375 void
376 sipe_backend_media_set_cname(struct sipe_backend_media *media, gchar *cname)
378 if (media) {
379 guint num_params = 3;
380 GParameter *params = g_new0(GParameter, num_params);
381 params[0].name = "sdes-cname";
382 g_value_init(&params[0].value, G_TYPE_STRING);
383 g_value_set_string(&params[0].value, cname);
384 params[1].name = "sdes-name";
385 g_value_init(&params[1].value, G_TYPE_STRING);
386 params[2].name = "sdes-tool";
387 g_value_init(&params[2].value, G_TYPE_STRING);
389 purple_media_set_params(media->m, num_params, params);
391 g_value_unset(&params[0].value);
392 g_free(params);
396 #define FS_CODECS_CONF \
397 "# Automatically created by SIPE plugin\n" \
398 "[video/H264]\n" \
399 "farstream-send-profile=videoscale ! videoconvert ! fsvideoanyrate ! x264enc ! video/x-h264,profile=constrained-baseline ! rtph264pay\n" \
400 "\n" \
401 "[application/X-DATA]\n" \
402 "id=127\n"
404 static void
405 ensure_codecs_conf()
407 gchar *filename;
408 const gchar *fs_codecs_conf = FS_CODECS_CONF;
409 GError *error = NULL;
411 filename = g_build_filename(purple_user_dir(), "fs-codec.conf", NULL);
413 g_file_set_contents(filename, fs_codecs_conf, strlen(fs_codecs_conf),
414 &error);
415 if (error) {
416 SIPE_DEBUG_ERROR("Couldn't create fs-codec.conf: %s",
417 error->message);
418 g_error_free(error);
421 g_free(filename);
424 static void
425 append_relay(struct sipe_backend_media_relays *relay_info, const gchar *ip,
426 guint port, gchar *type, gchar *username, gchar *password)
428 GstStructure *gst_relay_info;
430 gst_relay_info = gst_structure_new("relay-info",
431 "ip", G_TYPE_STRING, ip,
432 "port", G_TYPE_UINT, port,
433 "relay-type", G_TYPE_STRING, type,
434 "username", G_TYPE_STRING, username,
435 "password", G_TYPE_STRING, password,
436 NULL);
438 if (gst_relay_info) {
439 g_ptr_array_add((GPtrArray *)relay_info, gst_relay_info);
443 struct sipe_backend_media_relays *
444 sipe_backend_media_relays_convert(GSList *media_relays, gchar *username, gchar *password)
446 struct sipe_backend_media_relays *relay_info;
448 relay_info = (struct sipe_backend_media_relays *)
449 g_ptr_array_new_with_free_func((GDestroyNotify) gst_structure_free);
451 for (; media_relays; media_relays = media_relays->next) {\
452 struct sipe_media_relay *relay = media_relays->data;
454 /* Skip relays where IP could not be resolved. */
455 if (!relay->hostname)
456 continue;
458 if (relay->udp_port != 0)
459 append_relay(relay_info, relay->hostname, relay->udp_port,
460 "udp", username, password);
462 #ifdef HAVE_PURPLE_NEW_TCP_ENUMS
463 if (relay->tcp_port != 0) {
464 gchar *type = "tcp";
465 if (relay->tcp_port == 443)
466 type = "tls";
467 append_relay(relay_info, relay->hostname, relay->tcp_port,
468 type, username, password);
470 #endif
473 return relay_info;
476 void
477 sipe_backend_media_relays_free(struct sipe_backend_media_relays *media_relays)
479 g_ptr_array_unref((GPtrArray *)media_relays);
482 #ifdef HAVE_XDATA
483 static void
484 stream_readable_cb(SIPE_UNUSED_PARAMETER PurpleMediaManager *manager,
485 SIPE_UNUSED_PARAMETER PurpleMedia *media,
486 const gchar *session_id,
487 SIPE_UNUSED_PARAMETER const gchar *participant,
488 gpointer user_data)
490 struct sipe_media_call *call = (struct sipe_media_call *)user_data;
491 struct sipe_media_stream *stream;
493 SIPE_DEBUG_INFO("stream_readable_cb: %s is readable", session_id);
495 stream = sipe_core_media_get_stream_by_id(call, session_id);
497 if (stream) {
498 sipe_core_media_stream_readable(stream);
502 gssize
503 sipe_backend_media_stream_read(struct sipe_media_stream *stream,
504 guint8 *buffer, gsize len)
506 return purple_media_manager_receive_application_data(
507 purple_media_manager_get(),
508 stream->call->backend_private->m,
509 stream->id, stream->call->with, buffer, len, FALSE);
512 gssize
513 sipe_backend_media_stream_write(struct sipe_media_stream *stream,
514 guint8 *buffer, gsize len)
516 return purple_media_manager_send_application_data(
517 purple_media_manager_get(),
518 stream->call->backend_private->m,
519 stream->id, stream->call->with, buffer, len, FALSE);
522 static void
523 stream_writable_cb(SIPE_UNUSED_PARAMETER PurpleMediaManager *manager,
524 SIPE_UNUSED_PARAMETER PurpleMedia *media,
525 const gchar *session_id,
526 SIPE_UNUSED_PARAMETER const gchar *participant,
527 gboolean writable,
528 gpointer user_data)
530 struct sipe_media_call *call = (struct sipe_media_call *)user_data;
531 struct sipe_media_stream *stream;
533 stream = sipe_core_media_get_stream_by_id(call, session_id);
535 if (!stream) {
536 SIPE_DEBUG_ERROR("stream_writable_cb: stream %s not found!",
537 session_id);
538 return;
541 SIPE_DEBUG_INFO("stream_writable_cb: %s has become %swritable",
542 session_id, writable ? "" : "not ");
544 sipe_core_media_stream_writable(stream, writable);
546 #endif
548 static gboolean
549 write_ms_h264_video_source_request(GstRTCPBuffer *buffer, guint32 ssrc,
550 guint8 payload_type)
552 GstRTCPPacket packet;
553 guint8 *fci_data;
555 if (!gst_rtcp_buffer_add_packet(buffer, GST_RTCP_TYPE_PSFB, &packet)) {
556 return FALSE;
559 gst_rtcp_packet_fb_set_type(&packet, GST_RTCP_PSFB_TYPE_AFB);
560 gst_rtcp_packet_fb_set_sender_ssrc(&packet, ssrc);
561 gst_rtcp_packet_fb_set_media_ssrc(&packet, SIPE_MSRTP_VSR_SOURCE_ANY);
563 if (!gst_rtcp_packet_fb_set_fci_length(&packet,
564 SIPE_MSRTP_VSR_FCI_WORDLEN)) {
565 gst_rtcp_packet_remove(&packet);
566 return FALSE;
569 fci_data = gst_rtcp_packet_fb_get_fci(&packet);
571 sipe_core_msrtp_write_video_source_request(fci_data, payload_type);
573 return TRUE;
576 static gboolean
577 on_sending_rtcp_cb(SIPE_UNUSED_PARAMETER GObject *rtpsession,
578 GstBuffer *buffer,
579 SIPE_UNUSED_PARAMETER gboolean is_early,
580 FsSession *fssession)
582 gboolean was_changed = FALSE;
583 FsCodec *send_codec;
585 g_object_get(fssession, "current-send-codec", &send_codec, NULL);
586 if (!send_codec) {
587 return FALSE;
590 if (sipe_strequal(send_codec->encoding_name, "H264")) {
591 GstRTCPBuffer rtcp_buffer = GST_RTCP_BUFFER_INIT;
592 guint32 ssrc;
594 g_object_get(fssession, "ssrc", &ssrc, NULL);
596 gst_rtcp_buffer_map(buffer, GST_MAP_READWRITE, &rtcp_buffer);
597 was_changed = write_ms_h264_video_source_request(&rtcp_buffer,
598 ssrc, send_codec->id);
599 gst_rtcp_buffer_unmap(&rtcp_buffer);
602 fs_codec_destroy(send_codec);
604 return was_changed;
607 static GstPadProbeReturn
608 h264_buffer_cb(SIPE_UNUSED_PARAMETER GstPad *pad, GstPadProbeInfo *info,
609 SIPE_UNUSED_PARAMETER gpointer user_data)
611 GstBuffer *buffer;
612 GstMemory *memory;
613 GstMapInfo map;
614 guint8 *data;
615 guint8 nal_count = 0;
616 gsize pacsi_len;
618 buffer = gst_pad_probe_info_get_buffer(info);
620 // Count NALs in the buffer
621 gst_buffer_map(buffer, &map, GST_MAP_READ);
623 data = map.data;
624 while (data < map.data + map.size) {
625 guint32 size = GST_READ_UINT32_BE(data);
626 data += GST_READ_UINT32_BE(data) + sizeof (size);
627 ++nal_count;
630 gst_buffer_unmap(buffer, &map);
632 // Write PACSI (RFC6190 section 4.9)
633 memory = gst_allocator_alloc(NULL, 100, NULL);
634 gst_memory_map(memory, &map, GST_MAP_WRITE);
635 pacsi_len = sipe_core_msrtp_write_video_scalability_info(map.data,
636 nal_count);
637 gst_memory_unmap(memory, &map);
638 gst_memory_resize(memory, 0, pacsi_len);
640 buffer = gst_buffer_make_writable(buffer);
641 gst_buffer_insert_memory(buffer, 0, memory);
642 GST_PAD_PROBE_INFO_DATA(info) = buffer;
644 return GST_PAD_PROBE_OK;
647 static gint
648 find_payloader(GValue *value, GstCaps *rtpcaps)
650 gint result = 1;
651 GstElement *element;
652 GstPad *sinkpad;
653 GstCaps *caps;
655 element = g_value_get_object(value);
656 sinkpad = gst_element_get_static_pad(element, "sink");
657 caps = gst_pad_query_caps(sinkpad, NULL);
659 /* Elements are iterated from the most downstream. We're looking for the
660 * first that does NOT consume RTP frames. */
661 result = gst_caps_can_intersect(caps, rtpcaps);
663 gst_caps_unref(caps);
664 gst_object_unref(sinkpad);
666 return result;
669 static void
670 current_send_codec_changed_cb(FsSession *fssession,
671 SIPE_UNUSED_PARAMETER GParamSpec *pspec,
672 GstBin *fsconference)
674 FsCodec *send_codec;
676 g_object_get(fssession, "current-send-codec", &send_codec, NULL);
678 if (sipe_strequal(send_codec->encoding_name, "H264")) {
679 guint session_id;
680 gchar *sendbin_name;
681 GstBin *sendbin;
682 GstCaps *caps;
683 GstIterator *it;
684 GValue val = G_VALUE_INIT;
686 g_object_get(fssession, "id", &session_id, NULL);
688 sendbin_name = g_strdup_printf("send_%u_%u", session_id,
689 send_codec->id);
691 sendbin = GST_BIN(gst_bin_get_by_name(fsconference,
692 sendbin_name));
693 g_free(sendbin_name);
695 if (!sendbin) {
696 SIPE_DEBUG_ERROR("Couldn't find Farstream send bin for "
697 "session %d", session_id);
698 return;
701 caps = gst_caps_new_empty_simple("application/x-rtp");
702 it = gst_bin_iterate_sorted(sendbin);
703 if (gst_iterator_find_custom(it, (GCompareFunc)find_payloader,
704 &val, caps)) {
705 GstElement *payloader;
706 GstPad *sinkpad;
708 payloader = g_value_get_object(&val);
709 sinkpad = gst_element_get_static_pad(payloader, "sink");
710 if (sinkpad) {
711 gst_pad_add_probe(sinkpad,
712 GST_PAD_PROBE_TYPE_BUFFER,
713 h264_buffer_cb, NULL, NULL);
714 gst_object_unref(sinkpad);
716 g_value_unset(&val);
718 gst_caps_unref(caps);
720 gst_iterator_free(it);
721 gst_object_unref(sendbin);
724 fs_codec_destroy(send_codec);
727 static gint
728 find_sinkpad(GValue *value, GstPad *fssession_sinkpad)
730 GstElement *tee_srcpad = g_value_get_object(value);
732 return !(GST_PAD_PEER(tee_srcpad) == fssession_sinkpad);
735 static void
736 gst_bus_cb(GstBus *bus, GstMessage *msg, struct sipe_media_stream *stream)
738 PurpleMedia *m;
739 const GstStructure *s;
740 FsSession *fssession;
741 GstElement *tee;
742 GstPad *sinkpad;
743 GstIterator *it;
744 GValue val = G_VALUE_INIT;
746 if (GST_MESSAGE_TYPE(msg) != GST_MESSAGE_ELEMENT) {
747 return;
750 m = stream->call->backend_private->m;
752 s = gst_message_get_structure(msg);
753 if (!gst_structure_has_name(s, "farstream-codecs-changed")) {
754 return;
757 fssession = g_value_get_object(gst_structure_get_value(s, "session"));
758 g_return_if_fail(fssession);
760 tee = purple_media_get_tee(m, stream->id, NULL);
761 g_return_if_fail(tee);
763 g_object_get(fssession, "sink-pad", &sinkpad, NULL);
764 g_return_if_fail(sinkpad);
766 /* Check whether this message is from the FsSession we're waiting for.
767 * For this to be true, the tee we got from libpurple has to be linked
768 * to "sink-pad" of the message's FsSession. */
769 it = gst_element_iterate_src_pads(tee);
770 if (gst_iterator_find_custom(it, (GCompareFunc)find_sinkpad, &val,
771 sinkpad)) {
772 FsMediaType media_type;
774 if (stream->ssrc_range) {
775 g_object_set(fssession, "ssrc",
776 stream->ssrc_range->begin, NULL);
779 g_object_get(fssession, "media-type", &media_type, NULL);
781 if (media_type == FS_MEDIA_TYPE_VIDEO) {
782 GObject *rtpsession;
783 GstBin *fsconference;
785 g_object_get(fssession,
786 "internal-session", &rtpsession, NULL);
787 if (rtpsession) {
788 stream->backend_private->rtpsession =
789 gst_object_ref(rtpsession);
790 stream->backend_private->on_sending_rtcp_cb_id =
791 g_signal_connect(rtpsession,
792 "on-sending-rtcp",
793 G_CALLBACK(on_sending_rtcp_cb),
794 fssession);
796 g_object_unref (rtpsession);
799 g_object_get(fssession,
800 "conference", &fsconference, NULL);
802 g_signal_connect_object(fssession,
803 "notify::current-send-codec",
804 G_CALLBACK(current_send_codec_changed_cb),
805 fsconference, 0);
806 gst_object_unref(fsconference);
809 g_signal_handler_disconnect(bus,
810 stream->backend_private->gst_bus_cb_id);
811 stream->backend_private->gst_bus_cb_id = 0;
814 gst_iterator_free(it);
815 gst_object_unref(sinkpad);
818 struct sipe_backend_media_stream *
819 sipe_backend_media_add_stream(struct sipe_media_stream *stream,
820 SipeMediaType type,
821 SipeIceVersion ice_version,
822 gboolean initiator,
823 struct sipe_backend_media_relays *media_relays,
824 guint min_port, guint max_port)
826 struct sipe_backend_media *media = stream->call->backend_private;
827 struct sipe_backend_media_stream *backend_stream = NULL;
828 GstElement *pipe;
829 // Preallocate enough space for all potential parameters to fit.
830 GParameter *params = g_new0(GParameter, 6);
831 guint params_cnt = 0;
832 gchar *transmitter;
833 #ifdef HAVE_XDATA
834 PurpleMediaAppDataCallbacks callbacks = {
835 stream_readable_cb, stream_writable_cb
837 #endif
839 if (ice_version != SIPE_ICE_NO_ICE) {
840 transmitter = "nice";
842 params[params_cnt].name = "compatibility-mode";
843 g_value_init(&params[params_cnt].value, G_TYPE_UINT);
844 g_value_set_uint(&params[params_cnt].value,
845 ice_version == SIPE_ICE_DRAFT_6 ?
846 NICE_COMPATIBILITY_OC2007 :
847 NICE_COMPATIBILITY_OC2007R2);
848 ++params_cnt;
850 if (min_port != 0) {
851 params[params_cnt].name = "min-port";
852 g_value_init(&params[params_cnt].value, G_TYPE_UINT);
853 g_value_set_uint(&params[params_cnt].value, min_port);
854 ++params_cnt;
857 if (max_port != 0) {
858 params[params_cnt].name = "max-port";
859 g_value_init(&params[params_cnt].value, G_TYPE_UINT);
860 g_value_set_uint(&params[params_cnt].value, max_port);
861 ++params_cnt;
864 if (media_relays) {
865 params[params_cnt].name = "relay-info";
866 g_value_init(&params[params_cnt].value, G_TYPE_PTR_ARRAY);
867 g_value_set_boxed(&params[params_cnt].value, media_relays);
868 ++params_cnt;
871 if (type == SIPE_MEDIA_APPLICATION) {
872 params[params_cnt].name = "ice-udp";
873 g_value_init(&params[params_cnt].value, G_TYPE_BOOLEAN);
874 g_value_set_boolean(&params[params_cnt].value, FALSE);
875 ++params_cnt;
877 params[params_cnt].name = "reliable";
878 g_value_init(&params[params_cnt].value, G_TYPE_BOOLEAN);
879 g_value_set_boolean(&params[params_cnt].value, TRUE);
880 ++params_cnt;
882 } else {
883 // TODO: session naming here, Communicator needs audio/video
884 transmitter = "rawudp";
885 //sessionid = "sipe-voice-rawudp";
888 ensure_codecs_conf();
890 #ifdef HAVE_XDATA
891 if (type == SIPE_MEDIA_APPLICATION) {
892 purple_media_manager_set_application_data_callbacks(
893 purple_media_manager_get(),
894 media->m, stream->id, stream->call->with,
895 &callbacks, stream->call, NULL);
897 #endif
899 backend_stream = g_new0(struct sipe_backend_media_stream, 1);
901 pipe = purple_media_manager_get_pipeline(purple_media_manager_get());
902 if (pipe) {
903 GstBus *bus;
905 bus = gst_element_get_bus(pipe);
906 backend_stream->gst_bus_cb_id = g_signal_connect(bus, "message",
907 G_CALLBACK(gst_bus_cb), stream);
908 gst_object_unref(bus);
911 if (purple_media_add_stream(media->m, stream->id, stream->call->with,
912 sipe_media_to_purple(type),
913 initiator, transmitter, params_cnt,
914 params)) {
915 if (!initiator)
916 ++media->unconfirmed_streams;
917 } else {
918 sipe_backend_media_stream_free(backend_stream);
919 backend_stream = NULL;
922 g_free(params);
924 return backend_stream;
927 void
928 sipe_backend_media_stream_end(struct sipe_media_call *media,
929 struct sipe_media_stream *stream)
931 purple_media_end(media->backend_private->m, stream->id, NULL);
934 void
935 sipe_backend_media_add_remote_candidates(struct sipe_media_call *media,
936 struct sipe_media_stream *stream,
937 GList *candidates)
939 GList *udp_candidates = NULL;
941 #ifndef HAVE_PURPLE_NEW_TCP_ENUMS
942 /* Keep only UDP candidates in the list to set. */
943 while (candidates) {
944 PurpleMediaCandidate *candidate = candidates->data;
945 PurpleMediaNetworkProtocol proto;
947 proto = purple_media_candidate_get_protocol(candidate);
948 if (proto == PURPLE_MEDIA_NETWORK_PROTOCOL_UDP)
949 udp_candidates = g_list_append(udp_candidates, candidate);
951 candidates = candidates->next;
954 candidates = udp_candidates;
955 #endif
957 purple_media_add_remote_candidates(media->backend_private->m,
958 stream->id, media->with, candidates);
960 g_list_free(udp_candidates);
963 gboolean sipe_backend_media_is_initiator(struct sipe_media_call *media,
964 struct sipe_media_stream *stream)
966 return purple_media_is_initiator(media->backend_private->m,
967 stream ? stream->id : NULL,
968 stream ? media->with : NULL);
971 gboolean sipe_backend_media_accepted(struct sipe_backend_media *media)
973 return purple_media_accepted(media->m, NULL, NULL);
976 gboolean
977 sipe_backend_stream_initialized(struct sipe_media_call *media,
978 struct sipe_media_stream *stream)
980 g_return_val_if_fail(media, FALSE);
981 g_return_val_if_fail(stream, FALSE);
983 if (purple_media_candidates_prepared(media->backend_private->m,
984 stream->id, media->with)) {
985 GList *codecs;
986 codecs = purple_media_get_codecs(media->backend_private->m,
987 stream->id);
988 if (codecs) {
989 purple_media_codec_list_free(codecs);
990 return TRUE;
993 return FALSE;
996 static GList *
997 duplicate_tcp_candidates(GList *candidates)
999 GList *i;
1000 GList *result = NULL;
1002 for (i = candidates; i; i = i->next) {
1003 PurpleMediaCandidate *candidate = i->data;
1004 PurpleMediaNetworkProtocol protocol =
1005 purple_media_candidate_get_protocol(candidate);
1006 guint component_id =
1007 purple_media_candidate_get_component_id(candidate);
1009 if (protocol != PURPLE_MEDIA_NETWORK_PROTOCOL_UDP) {
1010 PurpleMediaCandidate *c2;
1012 if (component_id != PURPLE_MEDIA_COMPONENT_RTP) {
1013 /* Ignore TCP candidates for other than
1014 * the first component. */
1015 g_object_unref(candidate);
1016 continue;
1019 c2 = purple_media_candidate_copy(candidate);
1020 g_object_set(c2,
1021 "component-id", PURPLE_MEDIA_COMPONENT_RTCP,
1022 NULL);
1023 result = g_list_append(result, c2);
1026 result = g_list_append(result, candidate);
1029 g_list_free(candidates);
1031 return result;
1034 GList *
1035 sipe_backend_media_stream_get_active_local_candidates(struct sipe_media_stream *stream)
1037 GList *candidates = purple_media_get_active_local_candidates(
1038 stream->call->backend_private->m, stream->id,
1039 stream->call->with);
1040 return duplicate_tcp_candidates(candidates);
1043 GList *
1044 sipe_backend_media_stream_get_active_remote_candidates(struct sipe_media_stream *stream)
1046 GList *candidates = purple_media_get_active_remote_candidates(
1047 stream->call->backend_private->m, stream->id,
1048 stream->call->with);
1049 return duplicate_tcp_candidates(candidates);
1052 #ifdef HAVE_SRTP
1053 void
1054 sipe_backend_media_set_encryption_keys(struct sipe_media_call *media,
1055 struct sipe_media_stream *stream,
1056 const guchar *encryption_key,
1057 const guchar *decryption_key)
1059 purple_media_set_encryption_parameters(media->backend_private->m,
1060 stream->id,
1061 "aes-128-icm",
1062 "hmac-sha1-80",
1063 (gchar *)encryption_key, SIPE_SRTP_KEY_LEN);
1064 purple_media_set_decryption_parameters(media->backend_private->m,
1065 stream->id, media->with,
1066 "aes-128-icm",
1067 "hmac-sha1-80",
1068 (gchar *)decryption_key, SIPE_SRTP_KEY_LEN);
1070 #else
1071 void
1072 sipe_backend_media_set_encryption_keys(SIPE_UNUSED_PARAMETER struct sipe_media_call *media,
1073 SIPE_UNUSED_PARAMETER struct sipe_media_stream *stream,
1074 SIPE_UNUSED_PARAMETER const guchar *encryption_key,
1075 SIPE_UNUSED_PARAMETER const guchar *decryption_key)
1077 #endif
1079 void sipe_backend_stream_hold(struct sipe_media_call *media,
1080 struct sipe_media_stream *stream,
1081 gboolean local)
1083 purple_media_stream_info(media->backend_private->m, PURPLE_MEDIA_INFO_HOLD,
1084 stream->id, media->with, local);
1087 void sipe_backend_stream_unhold(struct sipe_media_call *media,
1088 struct sipe_media_stream *stream,
1089 gboolean local)
1091 purple_media_stream_info(media->backend_private->m, PURPLE_MEDIA_INFO_UNHOLD,
1092 stream->id, media->with, local);
1095 gboolean sipe_backend_stream_is_held(struct sipe_media_stream *stream)
1097 g_return_val_if_fail(stream, FALSE);
1099 return stream->backend_private->local_on_hold ||
1100 stream->backend_private->remote_on_hold;
1103 struct sipe_backend_codec *
1104 sipe_backend_codec_new(int id, const char *name, SipeMediaType type,
1105 guint clock_rate, guint channels)
1107 PurpleMediaCodec *codec;
1109 if (sipe_strcase_equal(name, "X-H264UC")) {
1110 name = "H264";
1113 codec = purple_media_codec_new(id, name, sipe_media_to_purple(type),
1114 clock_rate);
1115 g_object_set(codec, "channels", channels, NULL);
1117 return (struct sipe_backend_codec *)codec;
1120 void
1121 sipe_backend_codec_free(struct sipe_backend_codec *codec)
1123 if (codec)
1124 g_object_unref(codec);
1128 sipe_backend_codec_get_id(struct sipe_backend_codec *codec)
1130 return purple_media_codec_get_id((PurpleMediaCodec *)codec);
1133 gchar *
1134 sipe_backend_codec_get_name(struct sipe_backend_codec *codec)
1136 /* Not explicitly documented, but return value must be g_free()'d */
1137 return purple_media_codec_get_encoding_name((PurpleMediaCodec *)codec);
1140 guint
1141 sipe_backend_codec_get_clock_rate(struct sipe_backend_codec *codec)
1143 return purple_media_codec_get_clock_rate((PurpleMediaCodec *)codec);
1146 void
1147 sipe_backend_codec_add_optional_parameter(struct sipe_backend_codec *codec,
1148 const gchar *name, const gchar *value)
1150 purple_media_codec_add_optional_parameter((PurpleMediaCodec *)codec, name, value);
1153 GList *
1154 sipe_backend_codec_get_optional_parameters(struct sipe_backend_codec *codec)
1156 return purple_media_codec_get_optional_parameters((PurpleMediaCodec *)codec);
1159 gboolean
1160 sipe_backend_set_remote_codecs(struct sipe_media_call *media,
1161 struct sipe_media_stream *stream,
1162 GList *codecs)
1164 gboolean result;
1166 /* Lync offers multichannel audio as a codec with the same encoding name
1167 * as the mono variant, but a different payload type and an extra
1168 * encoding parameter:
1170 * a=rtpmap:117 G722/8000/2
1171 * a=rtpmap:9 G722/8000
1173 * Since avenc_g722 from gst-libav can encode only one audio channel, ignore
1174 * multichannel codecs we were offered by the remote host.
1176 GList *tmp = NULL;
1177 PurpleMediaSessionType type;
1179 for (; codecs; codecs = codecs->next) {
1180 PurpleMediaCodec *codec = codecs->data;
1182 g_object_get(codec, "media-type", &type, NULL);
1184 if (type == PURPLE_MEDIA_AUDIO &&
1185 purple_media_codec_get_channels(codec) > 1) {
1186 continue;
1189 tmp = g_list_append(tmp, codec);
1192 result = purple_media_set_remote_codecs(media->backend_private->m,
1193 stream->id, media->with,
1194 tmp);
1195 g_list_free(tmp);
1197 return result;
1200 GList*
1201 sipe_backend_get_local_codecs(struct sipe_media_call *media,
1202 struct sipe_media_stream *stream)
1204 GList *codecs = purple_media_get_codecs(media->backend_private->m,
1205 stream->id);
1206 GList *i = codecs;
1207 gboolean is_conference = (g_strstr_len(media->with, strlen(media->with),
1208 "app:conf:audio-video:") != NULL);
1211 * Do not announce Theora. Its optional parameters are too long,
1212 * Communicator rejects such SDP message and does not support the codec
1213 * anyway.
1215 * For some yet unknown reason, A/V conferencing server does not accept
1216 * voice stream sent by SIPE when SIREN codec is in use. Nevertheless,
1217 * we are able to decode incoming SIREN from server and with MSOC
1218 * client, bidirectional call using the codec works. Until resolved,
1219 * do not try to negotiate SIREN usage when conferencing. PCMA or PCMU
1220 * seems to work properly in this scenario.
1222 while (i) {
1223 PurpleMediaCodec *codec = i->data;
1224 gchar *encoding_name = purple_media_codec_get_encoding_name(codec);
1226 if (sipe_strequal(encoding_name,"THEORA") ||
1227 (is_conference && sipe_strequal(encoding_name,"SIREN"))) {
1228 GList *tmp;
1229 g_object_unref(codec);
1230 tmp = i->next;
1231 codecs = g_list_delete_link(codecs, i);
1232 i = tmp;
1233 } else if (sipe_strequal(encoding_name, "H264")) {
1235 * Sanitize H264 codec:
1236 * - the encoding name must be "X-H264UC"
1237 * - remove "sprop-parameter-sets" parameter which is
1238 * rejected by Lync
1239 * - add "packetization-mode" parameter if not already
1240 * present
1243 PurpleMediaCodec *new_codec;
1244 GList *it;
1246 new_codec = purple_media_codec_new(
1247 purple_media_codec_get_id(codec),
1248 "X-H264UC",
1249 PURPLE_MEDIA_VIDEO,
1250 purple_media_codec_get_clock_rate(codec));
1252 g_object_set(new_codec, "channels",
1253 purple_media_codec_get_channels(codec),
1254 NULL);
1256 it = purple_media_codec_get_optional_parameters(codec);
1258 for (; it; it = g_list_next(it)) {
1259 PurpleKeyValuePair *pair = it->data;
1261 if (sipe_strequal(pair->key, "sprop-parameter-sets")) {
1262 continue;
1265 purple_media_codec_add_optional_parameter(new_codec,
1266 pair->key, pair->value);
1269 if (!purple_media_codec_get_optional_parameter(new_codec,
1270 "packetization-mode", NULL)) {
1271 purple_media_codec_add_optional_parameter(new_codec,
1272 "packetization-mode",
1273 "1;mst-mode=NI-TC");
1276 i->data = new_codec;
1278 g_object_unref(codec);
1279 } else
1280 i = i->next;
1282 g_free(encoding_name);
1285 return codecs;
1288 struct sipe_backend_candidate *
1289 sipe_backend_candidate_new(const gchar *foundation,
1290 SipeComponentType component,
1291 SipeCandidateType type, SipeNetworkProtocol proto,
1292 const gchar *ip, guint port,
1293 const gchar *username,
1294 const gchar *password)
1296 PurpleMediaCandidate *c = purple_media_candidate_new(
1297 /* Libnice and Farsight rely on non-NULL foundation to
1298 * distinguish between candidates of a component. When NULL
1299 * foundation is passed (ie. ICE draft 6 does not use foudation),
1300 * use username instead. If no foundation is provided, Farsight
1301 * may signal an active candidate different from the one actually
1302 * in use. See Farsight's agent_new_selected_pair() in
1303 * fs-nice-stream-transmitter.h where first candidate in the
1304 * remote list is always selected when no foundation. */
1305 foundation ? foundation : username,
1306 component,
1307 sipe_candidate_type_to_purple(type),
1308 sipe_network_protocol_to_purple(proto),
1310 port);
1311 g_object_set(c, "username", username, "password", password, NULL);
1312 return (struct sipe_backend_candidate *)c;
1315 void
1316 sipe_backend_candidate_free(struct sipe_backend_candidate *candidate)
1318 if (candidate)
1319 g_object_unref(candidate);
1322 gchar *
1323 sipe_backend_candidate_get_username(struct sipe_backend_candidate *candidate)
1325 /* Not explicitly documented, but return value must be g_free()'d */
1326 return purple_media_candidate_get_username((PurpleMediaCandidate*)candidate);
1329 gchar *
1330 sipe_backend_candidate_get_password(struct sipe_backend_candidate *candidate)
1332 /* Not explicitly documented, but return value must be g_free()'d */
1333 return purple_media_candidate_get_password((PurpleMediaCandidate*)candidate);
1336 gchar *
1337 sipe_backend_candidate_get_foundation(struct sipe_backend_candidate *candidate)
1339 /* Not explicitly documented, but return value must be g_free()'d */
1340 return purple_media_candidate_get_foundation((PurpleMediaCandidate*)candidate);
1343 gchar *
1344 sipe_backend_candidate_get_ip(struct sipe_backend_candidate *candidate)
1346 /* Not explicitly documented, but return value must be g_free()'d */
1347 return purple_media_candidate_get_ip((PurpleMediaCandidate*)candidate);
1350 guint
1351 sipe_backend_candidate_get_port(struct sipe_backend_candidate *candidate)
1353 return purple_media_candidate_get_port((PurpleMediaCandidate*)candidate);
1356 gchar *
1357 sipe_backend_candidate_get_base_ip(struct sipe_backend_candidate *candidate)
1359 /* Not explicitly documented, but return value must be g_free()'d */
1360 return purple_media_candidate_get_base_ip((PurpleMediaCandidate*)candidate);
1363 guint
1364 sipe_backend_candidate_get_base_port(struct sipe_backend_candidate *candidate)
1366 return purple_media_candidate_get_base_port((PurpleMediaCandidate*)candidate);
1369 guint32
1370 sipe_backend_candidate_get_priority(struct sipe_backend_candidate *candidate)
1372 return purple_media_candidate_get_priority((PurpleMediaCandidate*)candidate);
1375 void
1376 sipe_backend_candidate_set_priority(struct sipe_backend_candidate *candidate, guint32 priority)
1378 g_object_set(candidate, "priority", priority, NULL);
1381 SipeComponentType
1382 sipe_backend_candidate_get_component_type(struct sipe_backend_candidate *candidate)
1384 return purple_media_candidate_get_component_id((PurpleMediaCandidate*)candidate);
1387 SipeCandidateType
1388 sipe_backend_candidate_get_type(struct sipe_backend_candidate *candidate)
1390 PurpleMediaCandidateType type =
1391 purple_media_candidate_get_candidate_type((PurpleMediaCandidate*)candidate);
1392 return purple_candidate_type_to_sipe(type);
1395 SipeNetworkProtocol
1396 sipe_backend_candidate_get_protocol(struct sipe_backend_candidate *candidate)
1398 PurpleMediaNetworkProtocol proto =
1399 purple_media_candidate_get_protocol((PurpleMediaCandidate*)candidate);
1400 return purple_network_protocol_to_sipe(proto);
1404 * libnice can return a candidate list with duplicates. It is currently
1405 * unknown if this is a bug in libnice or a configuration error in Skype
1406 * for Business setups.
1408 * While this is not a bug in SIPE, by removing these duplicates we make
1409 * sure that SIPE doesn't generate incorrect SDP messages.
1411 static GList *
1412 filter_duplicate_candidates(GList *candidates)
1414 GHashTable *seen = g_hash_table_new_full(g_str_hash, g_str_equal,
1415 g_free, NULL);
1416 GList *result = NULL;
1417 GList *it;
1419 for (it = candidates; it; it = it->next) {
1420 PurpleMediaCandidate *c = it->data;
1421 gchar *foundation = purple_media_candidate_get_foundation(c);
1422 gchar *ip = purple_media_candidate_get_ip(c);
1423 gchar *base_ip = purple_media_candidate_get_base_ip(c);
1424 gchar *id = g_strdup_printf("%s %d %d %d %s %d %d %s %d",
1425 foundation ? foundation : "-",
1426 purple_media_candidate_get_component_id(c),
1427 purple_media_candidate_get_protocol(c),
1428 purple_media_candidate_get_priority(c),
1429 ip ? ip : "-",
1430 purple_media_candidate_get_port(c),
1431 purple_media_candidate_get_candidate_type(c),
1432 base_ip ? base_ip : "-",
1433 purple_media_candidate_get_base_port(c)
1436 g_free(base_ip);
1437 g_free(ip);
1438 g_free(foundation);
1440 if (g_hash_table_lookup(seen, id)) {
1441 SIPE_DEBUG_INFO("filter_duplicate_candidates: dropping '%s'",
1442 id);
1443 g_free(id);
1444 g_object_unref(c);
1445 } else {
1446 g_hash_table_insert(seen, id, GUINT_TO_POINTER(TRUE));
1447 result = g_list_append(result, c);
1451 g_hash_table_destroy(seen);
1452 g_list_free(candidates);
1454 return result;
1457 static void
1458 remove_lone_candidate_cb(SIPE_UNUSED_PARAMETER gpointer key,
1459 gpointer value,
1460 gpointer user_data)
1462 GList *entry = value;
1463 GList **candidates = user_data;
1465 g_object_unref(entry->data);
1466 *candidates = g_list_delete_link(*candidates, entry);
1469 static GList *
1470 ensure_candidate_pairs(GList *candidates)
1472 GHashTable *lone_cand_links;
1473 GList *i;
1475 lone_cand_links = g_hash_table_new_full(g_str_hash, g_str_equal, g_free, NULL);
1477 for (i = candidates; i; i = i->next) {
1478 PurpleMediaCandidate *c = i->data;
1479 gchar *foundation = purple_media_candidate_get_foundation(c);
1481 if (g_hash_table_lookup(lone_cand_links, foundation)) {
1482 g_hash_table_remove(lone_cand_links, foundation);
1483 g_free(foundation);
1484 } else {
1485 g_hash_table_insert(lone_cand_links, foundation, i);
1489 g_hash_table_foreach(lone_cand_links, remove_lone_candidate_cb, &candidates);
1490 g_hash_table_destroy(lone_cand_links);
1492 return candidates;
1495 GList *
1496 sipe_backend_get_local_candidates(struct sipe_media_call *media,
1497 struct sipe_media_stream *stream)
1499 GList *candidates =
1500 purple_media_get_local_candidates(media->backend_private->m,
1501 stream->id,
1502 media->with);
1503 candidates = filter_duplicate_candidates(candidates);
1504 candidates = duplicate_tcp_candidates(candidates);
1507 * Sometimes purple will not return complete list of candidates, even
1508 * after "candidates-prepared" signal is emitted. This is a feature of
1509 * libnice, namely affecting candidates discovered via UPnP. Nice does
1510 * not wait until discovery is finished and can signal end of candidate
1511 * gathering before all responses from UPnP enabled gateways are received.
1513 * Remove any incomplete RTP+RTCP candidate pairs from the list.
1515 candidates = ensure_candidate_pairs(candidates);
1516 return candidates;
1519 void
1520 sipe_backend_media_accept(struct sipe_backend_media *media, gboolean local)
1522 if (media)
1523 purple_media_stream_info(media->m, PURPLE_MEDIA_INFO_ACCEPT,
1524 NULL, NULL, local);
1527 void
1528 sipe_backend_media_hangup(struct sipe_backend_media *media, gboolean local)
1530 if (media)
1531 purple_media_stream_info(media->m, PURPLE_MEDIA_INFO_HANGUP,
1532 NULL, NULL, local);
1535 void
1536 sipe_backend_media_reject(struct sipe_backend_media *media, gboolean local)
1538 if (media)
1539 purple_media_stream_info(media->m, PURPLE_MEDIA_INFO_REJECT,
1540 NULL, NULL, local);
1543 static PurpleMediaSessionType sipe_media_to_purple(SipeMediaType type)
1545 switch (type) {
1546 case SIPE_MEDIA_AUDIO: return PURPLE_MEDIA_AUDIO;
1547 case SIPE_MEDIA_VIDEO: return PURPLE_MEDIA_VIDEO;
1548 #ifdef HAVE_XDATA
1549 case SIPE_MEDIA_APPLICATION: return PURPLE_MEDIA_APPLICATION;
1550 #endif
1551 default: return PURPLE_MEDIA_NONE;
1555 static PurpleMediaCandidateType
1556 sipe_candidate_type_to_purple(SipeCandidateType type)
1558 switch (type) {
1559 case SIPE_CANDIDATE_TYPE_HOST: return PURPLE_MEDIA_CANDIDATE_TYPE_HOST;
1560 case SIPE_CANDIDATE_TYPE_RELAY: return PURPLE_MEDIA_CANDIDATE_TYPE_RELAY;
1561 case SIPE_CANDIDATE_TYPE_SRFLX: return PURPLE_MEDIA_CANDIDATE_TYPE_SRFLX;
1562 case SIPE_CANDIDATE_TYPE_PRFLX: return PURPLE_MEDIA_CANDIDATE_TYPE_PRFLX;
1563 default: return PURPLE_MEDIA_CANDIDATE_TYPE_HOST;
1567 static SipeCandidateType
1568 purple_candidate_type_to_sipe(PurpleMediaCandidateType type)
1570 switch (type) {
1571 case PURPLE_MEDIA_CANDIDATE_TYPE_HOST: return SIPE_CANDIDATE_TYPE_HOST;
1572 case PURPLE_MEDIA_CANDIDATE_TYPE_RELAY: return SIPE_CANDIDATE_TYPE_RELAY;
1573 case PURPLE_MEDIA_CANDIDATE_TYPE_SRFLX: return SIPE_CANDIDATE_TYPE_SRFLX;
1574 case PURPLE_MEDIA_CANDIDATE_TYPE_PRFLX: return SIPE_CANDIDATE_TYPE_PRFLX;
1575 default: return SIPE_CANDIDATE_TYPE_HOST;
1579 static PurpleMediaNetworkProtocol
1580 sipe_network_protocol_to_purple(SipeNetworkProtocol proto)
1582 switch (proto) {
1583 #ifdef HAVE_PURPLE_NEW_TCP_ENUMS
1584 case SIPE_NETWORK_PROTOCOL_TCP_ACTIVE:
1585 return PURPLE_MEDIA_NETWORK_PROTOCOL_TCP_ACTIVE;
1586 case SIPE_NETWORK_PROTOCOL_TCP_PASSIVE:
1587 return PURPLE_MEDIA_NETWORK_PROTOCOL_TCP_PASSIVE;
1588 case SIPE_NETWORK_PROTOCOL_TCP_SO:
1589 return PURPLE_MEDIA_NETWORK_PROTOCOL_TCP_SO;
1590 #else
1591 case SIPE_NETWORK_PROTOCOL_TCP_ACTIVE:
1592 case SIPE_NETWORK_PROTOCOL_TCP_PASSIVE:
1593 case SIPE_NETWORK_PROTOCOL_TCP_SO:
1594 return PURPLE_MEDIA_NETWORK_PROTOCOL_TCP;
1595 #endif
1596 default:
1597 case SIPE_NETWORK_PROTOCOL_UDP:
1598 return PURPLE_MEDIA_NETWORK_PROTOCOL_UDP;
1602 static SipeNetworkProtocol
1603 purple_network_protocol_to_sipe(PurpleMediaNetworkProtocol proto)
1605 switch (proto) {
1606 #ifdef HAVE_PURPLE_NEW_TCP_ENUMS
1607 case PURPLE_MEDIA_NETWORK_PROTOCOL_TCP_ACTIVE:
1608 return SIPE_NETWORK_PROTOCOL_TCP_ACTIVE;
1609 case PURPLE_MEDIA_NETWORK_PROTOCOL_TCP_PASSIVE:
1610 return SIPE_NETWORK_PROTOCOL_TCP_PASSIVE;
1611 case PURPLE_MEDIA_NETWORK_PROTOCOL_TCP_SO:
1612 return SIPE_NETWORK_PROTOCOL_TCP_SO;
1613 #else
1614 case PURPLE_MEDIA_NETWORK_PROTOCOL_TCP:
1615 return SIPE_NETWORK_PROTOCOL_TCP_ACTIVE;
1616 #endif
1617 default:
1618 case PURPLE_MEDIA_NETWORK_PROTOCOL_UDP:
1619 return SIPE_NETWORK_PROTOCOL_UDP;
1623 #ifdef HAVE_SRTP
1624 SipeEncryptionPolicy
1625 sipe_backend_media_get_encryption_policy(struct sipe_core_public *sipe_public)
1627 PurpleAccount *account = sipe_public->backend_private->account;
1629 const char *policy =
1630 purple_account_get_string(account, "encryption-policy",
1631 "obey-server");
1633 if (sipe_strequal(policy, "disabled")) {
1634 return SIPE_ENCRYPTION_POLICY_REJECTED;
1635 } else if (sipe_strequal(policy, "optional")) {
1636 return SIPE_ENCRYPTION_POLICY_OPTIONAL;
1637 } else if (sipe_strequal(policy, "required")) {
1638 return SIPE_ENCRYPTION_POLICY_REQUIRED;
1639 } else {
1640 return SIPE_ENCRYPTION_POLICY_OBEY_SERVER;
1643 #else
1644 SipeEncryptionPolicy
1645 sipe_backend_media_get_encryption_policy(SIPE_UNUSED_PARAMETER struct sipe_core_public *sipe_public)
1647 return SIPE_ENCRYPTION_POLICY_REJECTED;
1649 #endif
1652 Local Variables:
1653 mode: c
1654 c-file-style: "bsd"
1655 indent-tabs-mode: t
1656 tab-width: 8
1657 End: