purple: add support for latest appstreamcli
[siplcs.git] / src / purple / purple-media.c
blob1b1bcffd34d2a653ddd9beeded97d8d62d5adf4d
1 /**
2 * @file purple-media.c
4 * pidgin-sipe
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
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"
39 #include "version.h"
41 #ifdef _WIN32
42 /* wrappers for write() & friends for socket handling */
43 #include "win32/win32dep.h"
44 #endif
46 #if PURPLE_VERSION_CHECK(3,0,0)
47 /* nothing here yet */
48 #else
49 #define purple_config_dir purple_user_dir
50 #endif
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
62 #if defined(__GNUC__)
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"
66 #endif
67 #endif
68 #endif
70 #include "media-gst.h"
71 #include <gst/rtp/gstrtcpbuffer.h>
72 #include <farstream/fs-session.h>
74 struct sipe_backend_media {
75 PurpleMedia *m;
76 /**
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;
85 gboolean accepted;
86 gboolean initialized_cb_was_fired;
88 gulong gst_bus_cb_id;
90 GObject *rtpsession;
91 gulong on_sending_rtcp_cb_id;
94 void
95 sipe_backend_media_stream_free(struct sipe_backend_media_stream *stream)
97 if (stream->gst_bus_cb_id != 0) {
98 GstElement *pipe;
100 pipe = purple_media_manager_get_pipeline(
101 purple_media_manager_get());
102 if (pipe) {
103 GstBus *bus;
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);
116 g_free(stream);
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);
125 static void
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);
132 if (stream &&
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);
141 static void
142 on_candidates_prepared_cb(SIPE_UNUSED_PARAMETER PurpleMedia *media,
143 gchar *sessionid,
144 SIPE_UNUSED_PARAMETER gchar *participant,
145 struct sipe_media_call *call)
147 maybe_signal_stream_initialized(call, sessionid);
150 static void
151 on_codecs_changed_cb(SIPE_UNUSED_PARAMETER PurpleMedia *media,
152 gchar *sessionid,
153 struct sipe_media_call *call)
155 maybe_signal_stream_initialized(call, sessionid);
158 static void
159 on_state_changed_cb(SIPE_UNUSED_PARAMETER PurpleMedia *media,
160 PurpleMediaState state,
161 gchar *sessionid,
162 gchar *participant,
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);
189 if (stream) {
190 sipe_core_media_stream_end(stream);
192 } else if (!sessionid && !participant && call->media_end_cb) {
193 call->media_end_cb(call);
198 void
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);
205 static void
206 on_error_cb(SIPE_UNUSED_PARAMETER PurpleMedia *media, gchar *message,
207 struct sipe_media_call *call)
209 capture_pipeline("ERROR");
211 if (call->error_cb)
212 call->error_cb(call, message);
215 static void
216 on_stream_info_cb(PurpleMedia *media,
217 PurpleMediaInfoType type,
218 gchar *sessionid,
219 gchar *participant,
220 gboolean local,
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);
229 if (stream) {
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);
239 if (sessionid) {
240 // Hold specific stream
241 struct sipe_media_stream *stream;
242 stream = sipe_core_media_get_stream_by_id(call, sessionid);
244 if (stream) {
245 if (local)
246 stream->backend_private->local_on_hold = state;
247 else
248 stream->backend_private->remote_on_hold = state;
250 } else {
251 // Hold all streams
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);
258 if (stream) {
259 if (local)
260 stream->backend_private->local_on_hold = state;
261 else
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);
281 #ifdef HAVE_XDATA
282 purple_media_manager_set_application_data_callbacks(
283 purple_media_manager_get(), media,
284 sessionid, participant, NULL, NULL, NULL);
285 #endif
287 if (stream) {
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);
303 static void
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);
314 if (!stream) {
315 return;
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);
322 #endif
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) {
339 #ifdef HAVE_XDATA
340 media->m = purple_media_manager_create_private_media(manager,
341 purple_private->account, "fsrtpconference",
342 participant, flags & SIPE_MEDIA_CALL_INITIATOR);
343 #else
344 SIPE_DEBUG_ERROR_NOFORMAT("Purple doesn't support private media");
345 #endif
346 } else {
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);
373 return media;
376 void
377 sipe_backend_media_free(struct sipe_backend_media *media)
379 g_free(media);
382 void
383 sipe_backend_media_set_cname(struct sipe_backend_media *media, gchar *cname)
385 if (media) {
386 guint num_params = 3;
387 GParameter *params = g_new0(GParameter, num_params);
388 params[0].name = "sdes-cname";
389 g_value_init(&params[0].value, G_TYPE_STRING);
390 g_value_set_string(&params[0].value, cname);
391 params[1].name = "sdes-name";
392 g_value_init(&params[1].value, G_TYPE_STRING);
393 params[2].name = "sdes-tool";
394 g_value_init(&params[2].value, G_TYPE_STRING);
396 purple_media_set_params(media->m, num_params, params);
398 g_value_unset(&params[0].value);
399 g_free(params);
403 #define FS_CODECS_CONF \
404 "# Automatically created by SIPE plugin\n" \
405 "[video/H264]\n" \
406 "farstream-send-profile=videoscale ! videoconvert ! fsvideoanyrate ! x264enc ! video/x-h264,profile=constrained-baseline ! rtph264pay\n" \
407 "\n" \
408 "[application/X-DATA]\n" \
409 "id=127\n"
411 static void
412 ensure_codecs_conf()
414 gchar *filename;
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),
421 &error);
422 if (error) {
423 SIPE_DEBUG_ERROR("Couldn't create fs-codec.conf: %s",
424 error->message);
425 g_error_free(error);
428 g_free(filename);
431 static void
432 append_relay(struct sipe_backend_media_relays *relay_info, const gchar *ip,
433 guint port, const 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,
443 NULL);
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)
463 continue;
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) {
471 const gchar *type = "tcp";
472 if (relay->tcp_port == 443)
473 type = "tls";
474 append_relay(relay_info, relay->hostname, relay->tcp_port,
475 type, username, password);
477 #endif
480 return relay_info;
483 void
484 sipe_backend_media_relays_free(struct sipe_backend_media_relays *media_relays)
486 g_ptr_array_unref((GPtrArray *)media_relays);
489 #ifdef HAVE_XDATA
490 static void
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,
495 gpointer user_data)
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);
504 if (stream) {
505 sipe_core_media_stream_readable(stream);
509 gssize
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);
519 gssize
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);
529 static void
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,
534 gboolean writable,
535 gpointer user_data)
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);
542 if (!stream) {
543 SIPE_DEBUG_ERROR("stream_writable_cb: stream %s not found!",
544 session_id);
545 return;
548 SIPE_DEBUG_INFO("stream_writable_cb: %s has become %swritable",
549 session_id, writable ? "" : "not ");
551 sipe_core_media_stream_writable(stream, writable);
553 #endif
555 static gboolean
556 write_ms_h264_video_source_request(GstRTCPBuffer *buffer, guint32 ssrc,
557 guint8 payload_type)
559 GstRTCPPacket packet;
560 guint8 *fci_data;
562 if (!gst_rtcp_buffer_add_packet(buffer, GST_RTCP_TYPE_PSFB, &packet)) {
563 return FALSE;
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);
573 return FALSE;
576 fci_data = gst_rtcp_packet_fb_get_fci(&packet);
578 sipe_core_msrtp_write_video_source_request(fci_data, payload_type);
580 return TRUE;
583 static gboolean
584 on_sending_rtcp_cb(SIPE_UNUSED_PARAMETER GObject *rtpsession,
585 GstBuffer *buffer,
586 SIPE_UNUSED_PARAMETER gboolean is_early,
587 FsSession *fssession)
589 gboolean was_changed = FALSE;
590 FsCodec *send_codec;
592 g_object_get(fssession, "current-send-codec", &send_codec, NULL);
593 if (!send_codec) {
594 return FALSE;
597 if (sipe_strequal(send_codec->encoding_name, "H264")) {
598 GstRTCPBuffer rtcp_buffer = GST_RTCP_BUFFER_INIT;
599 guint32 ssrc;
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);
611 return was_changed;
614 static GstPadProbeReturn
615 h264_buffer_cb(SIPE_UNUSED_PARAMETER GstPad *pad, GstPadProbeInfo *info,
616 SIPE_UNUSED_PARAMETER gpointer user_data)
618 GstBuffer *buffer;
619 GstMemory *memory;
620 GstMapInfo map;
621 guint8 *data;
622 guint8 nal_count = 0;
623 gsize pacsi_len;
625 buffer = gst_pad_probe_info_get_buffer(info);
627 // Count NALs in the buffer
628 gst_buffer_map(buffer, &map, GST_MAP_READ);
630 data = map.data;
631 while (data < map.data + map.size) {
632 guint32 size = GST_READ_UINT32_BE(data);
633 data += GST_READ_UINT32_BE(data) + sizeof (size);
634 ++nal_count;
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,
643 nal_count);
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;
654 static gint
655 find_payloader(GValue *value, GstCaps *rtpcaps)
657 gint result = 1;
658 GstElement *element;
659 GstPad *sinkpad;
660 GstCaps *caps;
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);
673 return result;
676 static void
677 current_send_codec_changed_cb(FsSession *fssession,
678 SIPE_UNUSED_PARAMETER GParamSpec *pspec,
679 GstBin *fsconference)
681 FsCodec *send_codec;
683 g_object_get(fssession, "current-send-codec", &send_codec, NULL);
685 if (sipe_strequal(send_codec->encoding_name, "H264")) {
686 guint session_id;
687 gchar *sendbin_name;
688 GstBin *sendbin;
689 GstCaps *caps;
690 GstIterator *it;
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,
696 send_codec->id);
698 sendbin = GST_BIN(gst_bin_get_by_name(fsconference,
699 sendbin_name));
700 g_free(sendbin_name);
702 if (!sendbin) {
703 SIPE_DEBUG_ERROR("Couldn't find Farstream send bin for "
704 "session %d", session_id);
705 return;
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,
711 &val, caps)) {
712 GstElement *payloader;
713 GstPad *sinkpad;
715 payloader = g_value_get_object(&val);
716 sinkpad = gst_element_get_static_pad(payloader, "sink");
717 if (sinkpad) {
718 gst_pad_add_probe(sinkpad,
719 GST_PAD_PROBE_TYPE_BUFFER,
720 h264_buffer_cb, NULL, NULL);
721 gst_object_unref(sinkpad);
723 g_value_unset(&val);
725 gst_caps_unref(caps);
727 gst_iterator_free(it);
728 gst_object_unref(sendbin);
731 fs_codec_destroy(send_codec);
734 static gint
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);
742 static void
743 gst_bus_cb(GstBus *bus, GstMessage *msg, struct sipe_media_stream *stream)
745 PurpleMedia *m;
746 const GstStructure *s;
747 FsSession *fssession;
748 GstElement *tee;
749 GstPad *sinkpad;
750 GstIterator *it;
751 GValue val = G_VALUE_INIT;
753 if (GST_MESSAGE_TYPE(msg) != GST_MESSAGE_ELEMENT) {
754 return;
757 m = stream->call->backend_private->m;
759 s = gst_message_get_structure(msg);
760 if (!gst_structure_has_name(s, "farstream-codecs-changed")) {
761 return;
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,
778 sinkpad)) {
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) {
789 GObject *rtpsession;
790 GstBin *fsconference;
792 g_object_get(fssession,
793 "internal-session", &rtpsession, NULL);
794 if (rtpsession) {
795 stream->backend_private->rtpsession =
796 gst_object_ref(rtpsession);
797 stream->backend_private->on_sending_rtcp_cb_id =
798 g_signal_connect(rtpsession,
799 "on-sending-rtcp",
800 G_CALLBACK(on_sending_rtcp_cb),
801 fssession);
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),
812 fsconference, 0);
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,
828 SipeMediaType type,
829 SipeIceVersion ice_version,
830 gboolean initiator,
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;
836 GstElement *pipe;
837 // Preallocate enough space for all potential parameters to fit.
838 GParameter *params = g_new0(GParameter, 7);
839 guint params_cnt = 0;
840 const gchar *transmitter;
841 #ifdef HAVE_XDATA
842 PurpleMediaAppDataCallbacks callbacks = {
843 stream_readable_cb, stream_writable_cb
845 #endif
847 if (ice_version != SIPE_ICE_NO_ICE) {
848 transmitter = "nice";
850 params[params_cnt].name = "compatibility-mode";
851 g_value_init(&params[params_cnt].value, G_TYPE_UINT);
852 g_value_set_uint(&params[params_cnt].value,
853 ice_version == SIPE_ICE_DRAFT_6 ?
854 NICE_COMPATIBILITY_OC2007 :
855 NICE_COMPATIBILITY_OC2007R2);
856 ++params_cnt;
858 if (min_port != 0) {
859 params[params_cnt].name = "min-port";
860 g_value_init(&params[params_cnt].value, G_TYPE_UINT);
861 g_value_set_uint(&params[params_cnt].value, min_port);
862 ++params_cnt;
865 if (max_port != 0) {
866 params[params_cnt].name = "max-port";
867 g_value_init(&params[params_cnt].value, G_TYPE_UINT);
868 g_value_set_uint(&params[params_cnt].value, max_port);
869 ++params_cnt;
872 if (media_relays) {
873 params[params_cnt].name = "relay-info";
874 g_value_init(&params[params_cnt].value, G_TYPE_PTR_ARRAY);
875 g_value_set_boxed(&params[params_cnt].value, media_relays);
876 ++params_cnt;
879 if (type == SIPE_MEDIA_APPLICATION) {
880 params[params_cnt].name = "ice-udp";
881 g_value_init(&params[params_cnt].value, G_TYPE_BOOLEAN);
882 g_value_set_boolean(&params[params_cnt].value, FALSE);
883 ++params_cnt;
885 params[params_cnt].name = "reliable";
886 g_value_init(&params[params_cnt].value, G_TYPE_BOOLEAN);
887 g_value_set_boolean(&params[params_cnt].value, TRUE);
888 ++params_cnt;
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(&params[params_cnt].value, G_TYPE_STRING);
905 g_value_set_string(&params[params_cnt].value, NULL);
906 ++params_cnt;
907 } else {
908 // TODO: session naming here, Communicator needs audio/video
909 transmitter = "rawudp";
910 //sessionid = "sipe-voice-rawudp";
913 ensure_codecs_conf();
915 #ifdef HAVE_XDATA
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);
922 #endif
924 backend_stream = g_new0(struct sipe_backend_media_stream, 1);
926 pipe = purple_media_manager_get_pipeline(purple_media_manager_get());
927 if (pipe) {
928 GstBus *bus;
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,
939 params)) {
940 if (!initiator)
941 ++media->unconfirmed_streams;
942 } else {
943 sipe_backend_media_stream_free(backend_stream);
944 backend_stream = NULL;
947 g_free(params);
949 return backend_stream;
952 void
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);
959 void
960 sipe_backend_media_add_remote_candidates(struct sipe_media_call *media,
961 struct sipe_media_stream *stream,
962 GList *candidates)
964 GList *udp_candidates = NULL;
966 #ifndef HAVE_PURPLE_NEW_TCP_ENUMS
967 /* Keep only UDP candidates in the list to set. */
968 while (candidates) {
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;
980 #endif
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);
1001 gboolean
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)) {
1010 GList *codecs;
1011 codecs = purple_media_get_codecs(media->backend_private->m,
1012 stream->id);
1013 if (codecs) {
1014 purple_media_codec_list_free(codecs);
1015 return TRUE;
1018 return FALSE;
1021 static GList *
1022 duplicate_tcp_candidates(GList *candidates)
1024 GList *i;
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);
1041 continue;
1044 c2 = purple_media_candidate_copy(candidate);
1045 g_object_set(c2,
1046 "component-id", PURPLE_MEDIA_COMPONENT_RTCP,
1047 NULL);
1048 result = g_list_append(result, c2);
1051 result = g_list_append(result, candidate);
1054 g_list_free(candidates);
1056 return result;
1059 GList *
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);
1068 GList *
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);
1077 #ifdef HAVE_SRTP
1078 void
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,
1085 stream->id,
1086 "aes-128-icm",
1087 "hmac-sha1-80",
1088 (gchar *)encryption_key, SIPE_SRTP_KEY_LEN);
1089 purple_media_set_decryption_parameters(media->backend_private->m,
1090 stream->id, media->with,
1091 "aes-128-icm",
1092 "hmac-sha1-80",
1093 (gchar *)decryption_key, SIPE_SRTP_KEY_LEN);
1095 #else
1096 void
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)
1102 #endif
1104 #if defined(HAVE_SRTP) && PURPLE_VERSION_CHECK(2,14,0)
1105 void
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);
1113 #else
1114 void
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)
1119 #endif
1121 void sipe_backend_stream_hold(struct sipe_media_call *media,
1122 struct sipe_media_stream *stream,
1123 gboolean local)
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,
1131 gboolean local)
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")) {
1152 name = "H264";
1155 codec = purple_media_codec_new(id, name, sipe_media_to_purple(type),
1156 clock_rate);
1157 g_object_set(codec, "channels", channels, NULL);
1159 return (struct sipe_backend_codec *)codec;
1162 void
1163 sipe_backend_codec_free(struct sipe_backend_codec *codec)
1165 if (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);
1175 gchar *
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);
1182 guint
1183 sipe_backend_codec_get_clock_rate(struct sipe_backend_codec *codec)
1185 return purple_media_codec_get_clock_rate((PurpleMediaCodec *)codec);
1188 void
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);
1195 GList *
1196 sipe_backend_codec_get_optional_parameters(struct sipe_backend_codec *codec)
1198 return purple_media_codec_get_optional_parameters((PurpleMediaCodec *)codec);
1201 gboolean
1202 sipe_backend_set_remote_codecs(struct sipe_media_call *media,
1203 struct sipe_media_stream *stream,
1204 GList *codecs)
1206 gboolean result;
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.
1218 GList *tmp = NULL;
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) {
1228 continue;
1231 tmp = g_list_append(tmp, codec);
1234 result = purple_media_set_remote_codecs(media->backend_private->m,
1235 stream->id, media->with,
1236 tmp);
1237 g_list_free(tmp);
1239 return result;
1242 GList*
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,
1247 stream->id);
1248 GList *i = codecs;
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
1255 * anyway.
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.
1264 while (i) {
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"))) {
1270 GList *tmp;
1271 g_object_unref(codec);
1272 tmp = i->next;
1273 codecs = g_list_delete_link(codecs, i);
1274 i = tmp;
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
1280 * rejected by Lync
1281 * - add "packetization-mode" parameter if not already
1282 * present
1285 PurpleMediaCodec *new_codec;
1286 GList *it;
1288 new_codec = purple_media_codec_new(
1289 purple_media_codec_get_id(codec),
1290 "X-H264UC",
1291 PURPLE_MEDIA_VIDEO,
1292 purple_media_codec_get_clock_rate(codec));
1294 g_object_set(new_codec, "channels",
1295 purple_media_codec_get_channels(codec),
1296 NULL);
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")) {
1304 continue;
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);
1321 } else
1322 i = i->next;
1324 g_free(encoding_name);
1327 return codecs;
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,
1348 component,
1349 sipe_candidate_type_to_purple(type),
1350 sipe_network_protocol_to_purple(proto),
1352 port);
1353 g_object_set(c, "username", username, "password", password, NULL);
1354 return (struct sipe_backend_candidate *)c;
1357 void
1358 sipe_backend_candidate_free(struct sipe_backend_candidate *candidate)
1360 if (candidate)
1361 g_object_unref(candidate);
1364 gchar *
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);
1371 gchar *
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);
1378 gchar *
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);
1385 gchar *
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);
1392 guint
1393 sipe_backend_candidate_get_port(struct sipe_backend_candidate *candidate)
1395 return purple_media_candidate_get_port((PurpleMediaCandidate*)candidate);
1398 gchar *
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);
1405 guint
1406 sipe_backend_candidate_get_base_port(struct sipe_backend_candidate *candidate)
1408 return purple_media_candidate_get_base_port((PurpleMediaCandidate*)candidate);
1411 guint32
1412 sipe_backend_candidate_get_priority(struct sipe_backend_candidate *candidate)
1414 return purple_media_candidate_get_priority((PurpleMediaCandidate*)candidate);
1417 void
1418 sipe_backend_candidate_set_priority(struct sipe_backend_candidate *candidate, guint32 priority)
1420 g_object_set(candidate, "priority", priority, NULL);
1423 SipeComponentType
1424 sipe_backend_candidate_get_component_type(struct sipe_backend_candidate *candidate)
1426 return purple_media_candidate_get_component_id((PurpleMediaCandidate*)candidate);
1429 SipeCandidateType
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);
1437 SipeNetworkProtocol
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.
1453 static GList *
1454 filter_duplicate_candidates(GList *candidates)
1456 GHashTable *seen = g_hash_table_new_full(g_str_hash, g_str_equal,
1457 g_free, NULL);
1458 GList *result = NULL;
1459 GList *it;
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),
1471 ip ? ip : "-",
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)
1478 g_free(base_ip);
1479 g_free(ip);
1480 g_free(foundation);
1482 if (g_hash_table_lookup(seen, id)) {
1483 SIPE_DEBUG_INFO("filter_duplicate_candidates: dropping '%s'",
1484 id);
1485 g_free(id);
1486 g_object_unref(c);
1487 } else {
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);
1496 return result;
1499 static void
1500 remove_lone_candidate_cb(SIPE_UNUSED_PARAMETER gpointer key,
1501 gpointer value,
1502 gpointer user_data)
1504 GList *entry = value;
1505 GList **candidates = user_data;
1507 g_object_unref(entry->data);
1508 *candidates = g_list_delete_link(*candidates, entry);
1511 static GList *
1512 ensure_candidate_pairs(GList *candidates)
1514 GHashTable *lone_cand_links;
1515 GList *i;
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);
1525 g_free(foundation);
1526 } else {
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);
1534 return candidates;
1537 GList *
1538 sipe_backend_get_local_candidates(struct sipe_media_call *media,
1539 struct sipe_media_stream *stream)
1541 GList *candidates =
1542 purple_media_get_local_candidates(media->backend_private->m,
1543 stream->id,
1544 media->with);
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);
1558 return candidates;
1561 void
1562 sipe_backend_media_accept(struct sipe_backend_media *media, gboolean local)
1564 if (media)
1565 purple_media_stream_info(media->m, PURPLE_MEDIA_INFO_ACCEPT,
1566 NULL, NULL, local);
1569 void
1570 sipe_backend_media_hangup(struct sipe_backend_media *media, gboolean local)
1572 if (media)
1573 purple_media_stream_info(media->m, PURPLE_MEDIA_INFO_HANGUP,
1574 NULL, NULL, local);
1577 void
1578 sipe_backend_media_reject(struct sipe_backend_media *media, gboolean local)
1580 if (media)
1581 purple_media_stream_info(media->m, PURPLE_MEDIA_INFO_REJECT,
1582 NULL, NULL, local);
1585 static PurpleMediaSessionType sipe_media_to_purple(SipeMediaType type)
1587 switch (type) {
1588 case SIPE_MEDIA_AUDIO: return PURPLE_MEDIA_AUDIO;
1589 case SIPE_MEDIA_VIDEO: return PURPLE_MEDIA_VIDEO;
1590 #ifdef HAVE_XDATA
1591 case SIPE_MEDIA_APPLICATION: return PURPLE_MEDIA_APPLICATION;
1592 #endif
1593 default: return PURPLE_MEDIA_NONE;
1597 static PurpleMediaCandidateType
1598 sipe_candidate_type_to_purple(SipeCandidateType type)
1600 switch (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)
1612 switch (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)
1624 switch (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;
1632 #else
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;
1637 #endif
1638 default:
1639 case SIPE_NETWORK_PROTOCOL_UDP:
1640 return PURPLE_MEDIA_NETWORK_PROTOCOL_UDP;
1644 static SipeNetworkProtocol
1645 purple_network_protocol_to_sipe(PurpleMediaNetworkProtocol proto)
1647 switch (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;
1655 #else
1656 case PURPLE_MEDIA_NETWORK_PROTOCOL_TCP:
1657 return SIPE_NETWORK_PROTOCOL_TCP_ACTIVE;
1658 #endif
1659 default:
1660 case PURPLE_MEDIA_NETWORK_PROTOCOL_UDP:
1661 return SIPE_NETWORK_PROTOCOL_UDP;
1665 #ifdef HAVE_SRTP
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",
1673 "obey-server");
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;
1681 } else {
1682 return SIPE_ENCRYPTION_POLICY_OBEY_SERVER;
1685 #else
1686 SipeEncryptionPolicy
1687 sipe_backend_media_get_encryption_policy(SIPE_UNUSED_PARAMETER struct sipe_core_public *sipe_public)
1689 return SIPE_ENCRYPTION_POLICY_REJECTED;
1691 #endif
1694 Local Variables:
1695 mode: c
1696 c-file-style: "bsd"
1697 indent-tabs-mode: t
1698 tab-width: 8
1699 End: