media: align TCP RTP+RTCP candidate pairs
[siplcs.git] / src / purple / purple-media.c
blob50a6299c0dd8b0c101b71f6e4c11e22f5b97b310
1 /**
2 * @file purple-media.c
4 * pidgin-sipe
6 * Copyright (C) 2010-15 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(__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"
65 struct sipe_backend_media {
66 PurpleMedia *m;
67 GSList *streams;
68 /**
69 * Number of media streams that were not yet locally accepted or rejected.
71 guint unconfirmed_streams;
74 struct sipe_backend_stream {
75 gchar *sessionid;
76 gchar *participant;
77 gboolean local_on_hold;
78 gboolean remote_on_hold;
79 gboolean accepted;
80 gboolean initialized_cb_was_fired;
83 #if PURPLE_VERSION_CHECK(3,0,0)
84 #define SIPE_RELAYS_G_TYPE G_TYPE_PTR_ARRAY
85 #else
86 #define SIPE_RELAYS_G_TYPE G_TYPE_VALUE_ARRAY
87 #endif
89 static void
90 backend_stream_free(struct sipe_backend_stream *stream)
92 if (stream) {
93 g_free(stream->sessionid);
94 g_free(stream->participant);
95 g_free(stream);
99 static PurpleMediaSessionType sipe_media_to_purple(SipeMediaType type);
100 static PurpleMediaCandidateType sipe_candidate_type_to_purple(SipeCandidateType type);
101 static SipeCandidateType purple_candidate_type_to_sipe(PurpleMediaCandidateType type);
102 static PurpleMediaNetworkProtocol sipe_network_protocol_to_purple(SipeNetworkProtocol proto);
103 static SipeNetworkProtocol purple_network_protocol_to_sipe(PurpleMediaNetworkProtocol proto);
105 static void
106 maybe_signal_stream_initialized(struct sipe_media_call *call, gchar *sessionid)
108 if (call->stream_initialized_cb) {
109 struct sipe_backend_stream *stream;
110 stream = sipe_backend_media_get_stream_by_id(call->backend_private, sessionid);
112 if (sipe_backend_stream_initialized(call->backend_private, stream) &&
113 !stream->initialized_cb_was_fired) {
114 call->stream_initialized_cb(call, stream);
115 stream->initialized_cb_was_fired = TRUE;
120 static void
121 on_candidates_prepared_cb(SIPE_UNUSED_PARAMETER PurpleMedia *media,
122 gchar *sessionid,
123 SIPE_UNUSED_PARAMETER gchar *participant,
124 struct sipe_media_call *call)
126 maybe_signal_stream_initialized(call, sessionid);
129 static void
130 on_codecs_changed_cb(SIPE_UNUSED_PARAMETER PurpleMedia *media,
131 gchar *sessionid,
132 struct sipe_media_call *call)
134 maybe_signal_stream_initialized(call, sessionid);
137 static void
138 on_state_changed_cb(SIPE_UNUSED_PARAMETER PurpleMedia *media,
139 PurpleMediaState state,
140 gchar *sessionid,
141 gchar *participant,
142 struct sipe_media_call *call)
144 SIPE_DEBUG_INFO("sipe_media_state_changed_cb: %d %s %s\n", state, sessionid, participant);
145 if (state == PURPLE_MEDIA_STATE_END &&
146 !sessionid && !participant && call->media_end_cb)
147 call->media_end_cb(call);
150 void
151 capture_pipeline(const gchar *label) {
152 PurpleMediaManager *manager = purple_media_manager_get();
153 GstElement *pipeline = purple_media_manager_get_pipeline(manager);
154 GST_DEBUG_BIN_TO_DOT_FILE_WITH_TS(GST_BIN(pipeline), GST_DEBUG_GRAPH_SHOW_ALL, label);
157 static void
158 on_error_cb(SIPE_UNUSED_PARAMETER PurpleMedia *media, gchar *message,
159 struct sipe_media_call *call)
161 capture_pipeline("ERROR");
163 if (call->error_cb)
164 call->error_cb(call, message);
167 static void
168 on_stream_info_cb(SIPE_UNUSED_PARAMETER PurpleMedia *media,
169 PurpleMediaInfoType type,
170 gchar *sessionid,
171 gchar *participant,
172 gboolean local,
173 struct sipe_media_call *call)
175 if (type == PURPLE_MEDIA_INFO_ACCEPT) {
176 if (call->call_accept_cb && !sessionid && !participant)
177 call->call_accept_cb(call, local);
178 else if (sessionid && participant) {
179 struct sipe_backend_stream *stream;
180 stream = sipe_backend_media_get_stream_by_id(call->backend_private,
181 sessionid);
182 if (stream) {
183 if (!stream->accepted && local)
184 --call->backend_private->unconfirmed_streams;
185 stream->accepted = TRUE;
188 } else if (type == PURPLE_MEDIA_INFO_HOLD || type == PURPLE_MEDIA_INFO_UNHOLD) {
190 gboolean state = (type == PURPLE_MEDIA_INFO_HOLD);
192 if (sessionid) {
193 // Hold specific stream
194 struct sipe_backend_stream *stream;
195 stream = sipe_backend_media_get_stream_by_id(call->backend_private,
196 sessionid);
198 if (local)
199 stream->local_on_hold = state;
200 else
201 stream->remote_on_hold = state;
202 } else {
203 // Hold all streams
204 GSList *i = sipe_backend_media_get_streams(call->backend_private);
205 for (; i; i = i->next) {
206 struct sipe_backend_stream *stream = i->data;
208 if (local)
209 stream->local_on_hold = state;
210 else
211 stream->remote_on_hold = state;
215 if (call->call_hold_cb)
216 call->call_hold_cb(call, local, state);
217 } else if (type == PURPLE_MEDIA_INFO_HANGUP || type == PURPLE_MEDIA_INFO_REJECT) {
218 if (!sessionid && !participant) {
219 if (type == PURPLE_MEDIA_INFO_HANGUP && call->call_hangup_cb)
220 call->call_hangup_cb(call, local);
221 else if (type == PURPLE_MEDIA_INFO_REJECT && call->call_reject_cb && !local)
222 call->call_reject_cb(call, local);
223 } else if (sessionid && participant) {
224 struct sipe_backend_stream *stream;
225 stream = sipe_backend_media_get_stream_by_id(call->backend_private,
226 sessionid);
228 if (stream) {
229 call->backend_private->streams = g_slist_remove(call->backend_private->streams, stream);
230 backend_stream_free(stream);
231 if (local && --call->backend_private->unconfirmed_streams == 0 &&
232 call->call_reject_cb)
233 call->call_reject_cb(call, local);
239 static void
240 on_candidate_pair_established_cb(SIPE_UNUSED_PARAMETER PurpleMedia *media,
241 SIPE_UNUSED_PARAMETER const gchar *sessionid,
242 SIPE_UNUSED_PARAMETER const gchar *participant,
243 SIPE_UNUSED_PARAMETER PurpleMediaCandidate *local_candidate,
244 SIPE_UNUSED_PARAMETER PurpleMediaCandidate *remote_candidate,
245 SIPE_UNUSED_PARAMETER struct sipe_media_call *call)
247 #if PURPLE_VERSION_CHECK(2,10,12) || PURPLE_VERSION_CHECK(3,0,0)
248 if (purple_media_candidate_get_protocol(local_candidate) != PURPLE_MEDIA_NETWORK_PROTOCOL_UDP) {
249 purple_media_set_send_rtcp_mux(media, sessionid, participant, TRUE);
251 #endif
254 struct sipe_backend_media *
255 sipe_backend_media_new(struct sipe_core_public *sipe_public,
256 struct sipe_media_call *call,
257 const gchar *participant,
258 gboolean initiator)
260 struct sipe_backend_media *media = g_new0(struct sipe_backend_media, 1);
261 struct sipe_backend_private *purple_private = sipe_public->backend_private;
262 PurpleMediaManager *manager = purple_media_manager_get();
263 GstElement *pipeline;
265 media->m = purple_media_manager_create_media(manager,
266 purple_private->account,
267 "fsrtpconference",
268 participant, initiator);
270 g_signal_connect(G_OBJECT(media->m), "candidates-prepared",
271 G_CALLBACK(on_candidates_prepared_cb), call);
272 g_signal_connect(G_OBJECT(media->m), "codecs-changed",
273 G_CALLBACK(on_codecs_changed_cb), call);
274 g_signal_connect(G_OBJECT(media->m), "stream-info",
275 G_CALLBACK(on_stream_info_cb), call);
276 g_signal_connect(G_OBJECT(media->m), "error",
277 G_CALLBACK(on_error_cb), call);
278 g_signal_connect(G_OBJECT(media->m), "state-changed",
279 G_CALLBACK(on_state_changed_cb), call);
280 g_signal_connect(G_OBJECT(media->m), "candidate-pair-established",
281 G_CALLBACK(on_candidate_pair_established_cb), call);
284 /* On error, the pipeline is no longer in PLAYING state and libpurple
285 * will not switch it back to PLAYING, preventing any more calls until
286 * application restart. We switch the state ourselves here to negate
287 * effect of any error in previous call (if any). */
288 pipeline = purple_media_manager_get_pipeline(manager);
289 gst_element_set_state(pipeline, GST_STATE_PLAYING);
291 return media;
294 void
295 sipe_backend_media_free(struct sipe_backend_media *media)
297 if (media) {
298 GSList *stream = media->streams;
300 for (; stream; stream = g_slist_delete_link(stream, stream))
301 backend_stream_free(stream->data);
303 g_free(media);
307 void
308 sipe_backend_media_set_cname(struct sipe_backend_media *media, gchar *cname)
310 if (media) {
311 guint num_params = 3;
312 GParameter *params = g_new0(GParameter, num_params);
313 params[0].name = "sdes-cname";
314 g_value_init(&params[0].value, G_TYPE_STRING);
315 g_value_set_string(&params[0].value, cname);
316 params[1].name = "sdes-name";
317 g_value_init(&params[1].value, G_TYPE_STRING);
318 params[2].name = "sdes-tool";
319 g_value_init(&params[2].value, G_TYPE_STRING);
321 purple_media_set_params(media->m, num_params, params);
323 g_value_unset(&params[0].value);
324 g_free(params);
328 #define FS_CODECS_CONF \
329 "# Automatically created by SIPE plugin\n" \
330 "[video/H263]\n" \
331 "farsight-send-profile=videoscale ! ffmpegcolorspace ! fsvideoanyrate ! ffenc_h263 rtp-payload-size=30 ! rtph263pay\n" \
332 "\n" \
333 "[audio/PCMA]\n" \
334 "farsight-send-profile=audioconvert ! audioresample ! audioconvert ! alawenc ! rtppcmapay min-ptime=20000000 max-ptime=20000000\n" \
335 "\n" \
336 "[audio/PCMU]\n" \
337 "farsight-send-profile=audioconvert ! audioresample ! audioconvert ! mulawenc ! rtppcmupay min-ptime=20000000 max-ptime=20000000\n";
339 static void
340 ensure_codecs_conf()
342 gchar *filename;
343 filename = g_build_filename(purple_user_dir(), "fs-codec.conf", NULL);
345 if (!g_file_test(filename, G_FILE_TEST_EXISTS)) {
346 int fd = g_open(filename, O_CREAT | O_WRONLY, S_IRUSR | S_IWUSR);
347 gchar *fs_codecs_conf = FS_CODECS_CONF;
348 if ((fd < 0) || write(fd, fs_codecs_conf, strlen(fs_codecs_conf)) == -1)
349 SIPE_DEBUG_ERROR_NOFORMAT("Can not create fs-codec.conf!");
350 if (fd >= 0)
351 close(fd);
354 g_free(filename);
357 static void
358 append_relay(struct sipe_backend_media_relays *relay_info, const gchar *ip,
359 guint port, gchar *type, gchar *username, gchar *password)
361 GstStructure *gst_relay_info;
363 gst_relay_info = gst_structure_new("relay-info",
364 "ip", G_TYPE_STRING, ip,
365 "port", G_TYPE_UINT, port,
366 "relay-type", G_TYPE_STRING, type,
367 "username", G_TYPE_STRING, username,
368 "password", G_TYPE_STRING, password,
369 NULL);
371 if (gst_relay_info) {
372 #if PURPLE_VERSION_CHECK(3,0,0)
373 g_ptr_array_add((GPtrArray *)relay_info, gst_relay_info);
374 #else
375 GValue value;
376 memset(&value, 0, sizeof(GValue));
377 g_value_init(&value, GST_TYPE_STRUCTURE);
378 gst_value_set_structure(&value, gst_relay_info);
380 g_value_array_append((GValueArray *)relay_info, &value);
381 gst_structure_free(gst_relay_info);
382 #endif
386 struct sipe_backend_media_relays *
387 sipe_backend_media_relays_convert(GSList *media_relays, gchar *username, gchar *password)
389 struct sipe_backend_media_relays *relay_info;
391 relay_info = (struct sipe_backend_media_relays *)
392 #if PURPLE_VERSION_CHECK(3,0,0)
393 g_ptr_array_new_with_free_func((GDestroyNotify) gst_structure_free);
394 #else
395 g_value_array_new(0);
396 #endif
398 for (; media_relays; media_relays = media_relays->next) {\
399 struct sipe_media_relay *relay = media_relays->data;
401 /* Skip relays where IP could not be resolved. */
402 if (!relay->hostname)
403 continue;
405 if (relay->udp_port != 0)
406 append_relay(relay_info, relay->hostname, relay->udp_port,
407 "udp", username, password);
409 #ifdef HAVE_ICE_TCP
410 if (relay->tcp_port != 0) {
411 gchar *type = "tcp";
412 if (relay->tcp_port == 443)
413 type = "tls";
414 append_relay(relay_info, relay->hostname, relay->tcp_port,
415 type, username, password);
417 #endif
420 return relay_info;
423 void
424 sipe_backend_media_relays_free(struct sipe_backend_media_relays *media_relays)
426 #if !PURPLE_VERSION_CHECK(3,0,0)
427 g_value_array_free((GValueArray *)media_relays);
428 #else
429 g_ptr_array_unref((GPtrArray *)media_relays);
430 #endif
433 struct sipe_backend_stream *
434 sipe_backend_media_add_stream(struct sipe_backend_media *media,
435 const gchar *id,
436 const gchar *participant,
437 SipeMediaType type,
438 SipeIceVersion ice_version,
439 gboolean initiator,
440 struct sipe_backend_media_relays *media_relays)
442 struct sipe_backend_stream *stream = NULL;
443 PurpleMediaSessionType prpl_type = sipe_media_to_purple(type);
444 // Preallocate enough space for all potential parameters to fit.
445 GParameter *params = g_new0(GParameter, 5);
446 guint params_cnt = 0;
447 gchar *transmitter;
448 GValue *relay_info = NULL;
450 if (ice_version != SIPE_ICE_NO_ICE) {
451 transmitter = "nice";
453 params[params_cnt].name = "compatibility-mode";
454 g_value_init(&params[params_cnt].value, G_TYPE_UINT);
455 g_value_set_uint(&params[params_cnt].value,
456 ice_version == SIPE_ICE_DRAFT_6 ?
457 NICE_COMPATIBILITY_OC2007 :
458 NICE_COMPATIBILITY_OC2007R2);
459 ++params_cnt;
461 if (media_relays) {
462 params[params_cnt].name = "relay-info";
463 g_value_init(&params[params_cnt].value, SIPE_RELAYS_G_TYPE);
464 g_value_set_boxed(&params[params_cnt].value, media_relays);
465 relay_info = &params[params_cnt].value;
466 ++params_cnt;
468 } else {
469 // TODO: session naming here, Communicator needs audio/video
470 transmitter = "rawudp";
471 //sessionid = "sipe-voice-rawudp";
474 ensure_codecs_conf();
476 if (purple_media_add_stream(media->m, id, participant, prpl_type,
477 initiator, transmitter, params_cnt,
478 params)) {
479 stream = g_new0(struct sipe_backend_stream, 1);
480 stream->sessionid = g_strdup(id);
481 stream->participant = g_strdup(participant);
482 stream->initialized_cb_was_fired = FALSE;
484 media->streams = g_slist_append(media->streams, stream);
485 if (!initiator)
486 ++media->unconfirmed_streams;
489 if (relay_info) {
490 g_value_unset(relay_info);
493 g_free(params);
495 return stream;
498 void
499 sipe_backend_media_remove_stream(struct sipe_backend_media *media,
500 struct sipe_backend_stream *stream)
502 g_return_if_fail(media && stream);
504 purple_media_end(media->m, stream->sessionid, NULL);
505 media->streams = g_slist_remove(media->streams, stream);
506 backend_stream_free(stream);
509 GSList *sipe_backend_media_get_streams(struct sipe_backend_media *media)
511 return media->streams;
514 struct sipe_backend_stream *
515 sipe_backend_media_get_stream_by_id(struct sipe_backend_media *media,
516 const gchar *id)
518 GSList *i;
519 for (i = media->streams; i; i = i->next) {
520 struct sipe_backend_stream *stream = i->data;
521 if (sipe_strequal(stream->sessionid, id))
522 return stream;
524 return NULL;
527 void
528 sipe_backend_media_add_remote_candidates(struct sipe_backend_media *media,
529 struct sipe_backend_stream *stream,
530 GList *candidates)
532 GList *udp_candidates = NULL;
534 #ifndef HAVE_ICE_TCP
535 while (candidates) {
536 PurpleMediaCandidate *candidate = candidates->data;
537 PurpleMediaNetworkProtocol proto;
539 proto = purple_media_candidate_get_protocol(candidate);
540 if (proto == PURPLE_MEDIA_NETWORK_PROTOCOL_UDP)
541 udp_candidates = g_list_append(udp_candidates, candidate);
543 candidates = candidates->next;
546 candidates = udp_candidates;
547 #endif
550 purple_media_add_remote_candidates(media->m, stream->sessionid,
551 stream->participant, candidates);
553 g_list_free(udp_candidates);
556 gboolean sipe_backend_media_is_initiator(struct sipe_backend_media *media,
557 struct sipe_backend_stream *stream)
559 return purple_media_is_initiator(media->m,
560 stream ? stream->sessionid : NULL,
561 stream ? stream->participant : NULL);
564 gboolean sipe_backend_media_accepted(struct sipe_backend_media *media)
566 return purple_media_accepted(media->m, NULL, NULL);
569 gboolean
570 sipe_backend_stream_initialized(struct sipe_backend_media *media,
571 struct sipe_backend_stream *stream)
573 g_return_val_if_fail(media, FALSE);
574 g_return_val_if_fail(stream, FALSE);
576 if (purple_media_candidates_prepared(media->m,
577 stream->sessionid,
578 stream->participant)) {
579 GList *codecs;
580 codecs = purple_media_get_codecs(media->m, stream->sessionid);
581 if (codecs) {
582 purple_media_codec_list_free(codecs);
583 return TRUE;
586 return FALSE;
589 static GList *
590 duplicate_tcp_candidates(GList *candidates)
592 GList *i;
593 GList *result = NULL;
595 for (i = candidates; i; i = i->next) {
596 PurpleMediaCandidate *candidate = i->data;
597 PurpleMediaNetworkProtocol protocol =
598 purple_media_candidate_get_protocol(candidate);
599 guint component_id =
600 purple_media_candidate_get_component_id(candidate);
602 if (protocol != PURPLE_MEDIA_NETWORK_PROTOCOL_UDP) {
603 PurpleMediaCandidate *c2;
605 if (component_id != PURPLE_MEDIA_COMPONENT_RTP) {
606 /* Ignore TCP candidates for other than
607 * the first component. */
608 g_object_unref(candidate);
609 continue;
612 c2 = purple_media_candidate_copy(candidate);
613 g_object_set(c2,
614 "component-id", PURPLE_MEDIA_COMPONENT_RTCP,
615 NULL);
616 result = g_list_append(result, c2);
619 result = g_list_append(result, candidate);
622 g_list_free(candidates);
624 return result;
627 GList *
628 sipe_backend_media_get_active_local_candidates(struct sipe_backend_media *media,
629 struct sipe_backend_stream *stream)
631 GList *candidates = purple_media_get_active_local_candidates(
632 media->m, stream->sessionid, stream->participant);
633 return duplicate_tcp_candidates(candidates);
636 GList *
637 sipe_backend_media_get_active_remote_candidates(struct sipe_backend_media *media,
638 struct sipe_backend_stream *stream)
640 GList *candidates = purple_media_get_active_remote_candidates(
641 media->m, stream->sessionid, stream->participant);
642 return duplicate_tcp_candidates(candidates);
645 const gchar *
646 sipe_backend_stream_get_id(struct sipe_backend_stream *stream)
648 return stream->sessionid;
651 void sipe_backend_stream_hold(struct sipe_backend_media *media,
652 struct sipe_backend_stream *stream,
653 gboolean local)
655 purple_media_stream_info(media->m, PURPLE_MEDIA_INFO_HOLD,
656 stream->sessionid, stream->participant,
657 local);
660 void sipe_backend_stream_unhold(struct sipe_backend_media *media,
661 struct sipe_backend_stream *stream,
662 gboolean local)
664 purple_media_stream_info(media->m, PURPLE_MEDIA_INFO_UNHOLD,
665 stream->sessionid, stream->participant,
666 local);
669 gboolean sipe_backend_stream_is_held(struct sipe_backend_stream *stream)
671 g_return_val_if_fail(stream, FALSE);
673 return stream->local_on_hold || stream->remote_on_hold;
676 struct sipe_backend_codec *
677 sipe_backend_codec_new(int id, const char *name, SipeMediaType type, guint clock_rate)
679 return (struct sipe_backend_codec *)purple_media_codec_new(id, name,
680 sipe_media_to_purple(type),
681 clock_rate);
684 void
685 sipe_backend_codec_free(struct sipe_backend_codec *codec)
687 if (codec)
688 g_object_unref(codec);
692 sipe_backend_codec_get_id(struct sipe_backend_codec *codec)
694 return purple_media_codec_get_id((PurpleMediaCodec *)codec);
697 gchar *
698 sipe_backend_codec_get_name(struct sipe_backend_codec *codec)
700 /* Not explicitly documented, but return value must be g_free()'d */
701 return purple_media_codec_get_encoding_name((PurpleMediaCodec *)codec);
704 guint
705 sipe_backend_codec_get_clock_rate(struct sipe_backend_codec *codec)
707 return purple_media_codec_get_clock_rate((PurpleMediaCodec *)codec);
710 void
711 sipe_backend_codec_add_optional_parameter(struct sipe_backend_codec *codec,
712 const gchar *name, const gchar *value)
714 purple_media_codec_add_optional_parameter((PurpleMediaCodec *)codec, name, value);
717 GList *
718 sipe_backend_codec_get_optional_parameters(struct sipe_backend_codec *codec)
720 return purple_media_codec_get_optional_parameters((PurpleMediaCodec *)codec);
723 gboolean
724 sipe_backend_set_remote_codecs(struct sipe_backend_media *media,
725 struct sipe_backend_stream *stream,
726 GList *codecs)
728 return purple_media_set_remote_codecs(media->m,
729 stream->sessionid,
730 stream->participant,
731 codecs);
734 GList*
735 sipe_backend_get_local_codecs(struct sipe_backend_media *media,
736 struct sipe_backend_stream *stream)
738 GList *codecs = purple_media_get_codecs(media->m,
739 stream->sessionid);
740 GList *i = codecs;
741 gboolean is_conference = (g_strstr_len(stream->participant,
742 strlen(stream->participant),
743 "app:conf:audio-video:") != NULL);
746 * Do not announce Theora. Its optional parameters are too long,
747 * Communicator rejects such SDP message and does not support the codec
748 * anyway.
750 * For some yet unknown reason, A/V conferencing server does not accept
751 * voice stream sent by SIPE when SIREN codec is in use. Nevertheless,
752 * we are able to decode incoming SIREN from server and with MSOC
753 * client, bidirectional call using the codec works. Until resolved,
754 * do not try to negotiate SIREN usage when conferencing. PCMA or PCMU
755 * seems to work properly in this scenario.
757 while (i) {
758 PurpleMediaCodec *codec = i->data;
759 gchar *encoding_name = purple_media_codec_get_encoding_name(codec);
761 if (sipe_strequal(encoding_name,"THEORA") ||
762 (is_conference && sipe_strequal(encoding_name,"SIREN"))) {
763 GList *tmp;
764 g_object_unref(codec);
765 tmp = i->next;
766 codecs = g_list_delete_link(codecs, i);
767 i = tmp;
768 } else
769 i = i->next;
771 g_free(encoding_name);
774 return codecs;
777 struct sipe_backend_candidate *
778 sipe_backend_candidate_new(const gchar *foundation,
779 SipeComponentType component,
780 SipeCandidateType type, SipeNetworkProtocol proto,
781 const gchar *ip, guint port,
782 const gchar *username,
783 const gchar *password)
785 PurpleMediaCandidate *c = purple_media_candidate_new(
786 /* Libnice and Farsight rely on non-NULL foundation to
787 * distinguish between candidates of a component. When NULL
788 * foundation is passed (ie. ICE draft 6 does not use foudation),
789 * use username instead. If no foundation is provided, Farsight
790 * may signal an active candidate different from the one actually
791 * in use. See Farsight's agent_new_selected_pair() in
792 * fs-nice-stream-transmitter.h where first candidate in the
793 * remote list is always selected when no foundation. */
794 foundation ? foundation : username,
795 component,
796 sipe_candidate_type_to_purple(type),
797 sipe_network_protocol_to_purple(proto),
799 port);
800 g_object_set(c, "username", username, "password", password, NULL);
801 return (struct sipe_backend_candidate *)c;
804 void
805 sipe_backend_candidate_free(struct sipe_backend_candidate *candidate)
807 if (candidate)
808 g_object_unref(candidate);
811 gchar *
812 sipe_backend_candidate_get_username(struct sipe_backend_candidate *candidate)
814 /* Not explicitly documented, but return value must be g_free()'d */
815 return purple_media_candidate_get_username((PurpleMediaCandidate*)candidate);
818 gchar *
819 sipe_backend_candidate_get_password(struct sipe_backend_candidate *candidate)
821 /* Not explicitly documented, but return value must be g_free()'d */
822 return purple_media_candidate_get_password((PurpleMediaCandidate*)candidate);
825 gchar *
826 sipe_backend_candidate_get_foundation(struct sipe_backend_candidate *candidate)
828 /* Not explicitly documented, but return value must be g_free()'d */
829 return purple_media_candidate_get_foundation((PurpleMediaCandidate*)candidate);
832 gchar *
833 sipe_backend_candidate_get_ip(struct sipe_backend_candidate *candidate)
835 /* Not explicitly documented, but return value must be g_free()'d */
836 return purple_media_candidate_get_ip((PurpleMediaCandidate*)candidate);
839 guint
840 sipe_backend_candidate_get_port(struct sipe_backend_candidate *candidate)
842 return purple_media_candidate_get_port((PurpleMediaCandidate*)candidate);
845 gchar *
846 sipe_backend_candidate_get_base_ip(struct sipe_backend_candidate *candidate)
848 /* Not explicitly documented, but return value must be g_free()'d */
849 return purple_media_candidate_get_base_ip((PurpleMediaCandidate*)candidate);
852 guint
853 sipe_backend_candidate_get_base_port(struct sipe_backend_candidate *candidate)
855 return purple_media_candidate_get_base_port((PurpleMediaCandidate*)candidate);
858 guint32
859 sipe_backend_candidate_get_priority(struct sipe_backend_candidate *candidate)
861 return purple_media_candidate_get_priority((PurpleMediaCandidate*)candidate);
864 void
865 sipe_backend_candidate_set_priority(struct sipe_backend_candidate *candidate, guint32 priority)
867 g_object_set(candidate, "priority", priority, NULL);
870 SipeComponentType
871 sipe_backend_candidate_get_component_type(struct sipe_backend_candidate *candidate)
873 return purple_media_candidate_get_component_id((PurpleMediaCandidate*)candidate);
876 SipeCandidateType
877 sipe_backend_candidate_get_type(struct sipe_backend_candidate *candidate)
879 PurpleMediaCandidateType type =
880 purple_media_candidate_get_candidate_type((PurpleMediaCandidate*)candidate);
881 return purple_candidate_type_to_sipe(type);
884 SipeNetworkProtocol
885 sipe_backend_candidate_get_protocol(struct sipe_backend_candidate *candidate)
887 PurpleMediaNetworkProtocol proto =
888 purple_media_candidate_get_protocol((PurpleMediaCandidate*)candidate);
889 return purple_network_protocol_to_sipe(proto);
892 static void
893 remove_lone_candidate_cb(SIPE_UNUSED_PARAMETER gpointer key,
894 gpointer value,
895 gpointer user_data)
897 GList *entry = value;
898 GList **candidates = user_data;
900 g_object_unref(entry->data);
901 *candidates = g_list_delete_link(*candidates, entry);
904 static GList *
905 ensure_candidate_pairs(GList *candidates)
907 GHashTable *lone_cand_links;
908 GList *i;
910 lone_cand_links = g_hash_table_new_full(g_str_hash, g_str_equal, g_free, NULL);
912 for (i = candidates; i; i = i->next) {
913 PurpleMediaCandidate *c = i->data;
914 gchar *foundation = purple_media_candidate_get_foundation(c);
916 if (g_hash_table_lookup(lone_cand_links, foundation)) {
917 g_hash_table_remove(lone_cand_links, foundation);
918 g_free(foundation);
919 } else {
920 g_hash_table_insert(lone_cand_links, foundation, i);
924 g_hash_table_foreach(lone_cand_links, remove_lone_candidate_cb, &candidates);
925 g_hash_table_destroy(lone_cand_links);
927 return candidates;
930 GList *
931 sipe_backend_get_local_candidates(struct sipe_backend_media *media,
932 struct sipe_backend_stream *stream)
934 GList *candidates = purple_media_get_local_candidates(media->m,
935 stream->sessionid,
936 stream->participant);
937 candidates = duplicate_tcp_candidates(candidates);
940 * Sometimes purple will not return complete list of candidates, even
941 * after "candidates-prepared" signal is emitted. This is a feature of
942 * libnice, namely affecting candidates discovered via UPnP. Nice does
943 * not wait until discovery is finished and can signal end of candidate
944 * gathering before all responses from UPnP enabled gateways are received.
946 * Remove any incomplete RTP+RTCP candidate pairs from the list.
948 candidates = ensure_candidate_pairs(candidates);
949 return candidates;
952 void
953 sipe_backend_media_accept(struct sipe_backend_media *media, gboolean local)
955 if (media)
956 purple_media_stream_info(media->m, PURPLE_MEDIA_INFO_ACCEPT,
957 NULL, NULL, local);
960 void
961 sipe_backend_media_hangup(struct sipe_backend_media *media, gboolean local)
963 if (media)
964 purple_media_stream_info(media->m, PURPLE_MEDIA_INFO_HANGUP,
965 NULL, NULL, local);
968 void
969 sipe_backend_media_reject(struct sipe_backend_media *media, gboolean local)
971 if (media)
972 purple_media_stream_info(media->m, PURPLE_MEDIA_INFO_REJECT,
973 NULL, NULL, local);
976 static PurpleMediaSessionType sipe_media_to_purple(SipeMediaType type)
978 switch (type) {
979 case SIPE_MEDIA_AUDIO: return PURPLE_MEDIA_AUDIO;
980 case SIPE_MEDIA_VIDEO: return PURPLE_MEDIA_VIDEO;
981 default: return PURPLE_MEDIA_NONE;
985 /*SipeMediaType purple_media_to_sipe(PurpleMediaSessionType type)
987 switch (type) {
988 case PURPLE_MEDIA_AUDIO: return SIPE_MEDIA_AUDIO;
989 case PURPLE_MEDIA_VIDEO: return SIPE_MEDIA_VIDEO;
990 default: return SIPE_MEDIA_AUDIO;
994 static PurpleMediaCandidateType
995 sipe_candidate_type_to_purple(SipeCandidateType type)
997 switch (type) {
998 case SIPE_CANDIDATE_TYPE_HOST: return PURPLE_MEDIA_CANDIDATE_TYPE_HOST;
999 case SIPE_CANDIDATE_TYPE_RELAY: return PURPLE_MEDIA_CANDIDATE_TYPE_RELAY;
1000 case SIPE_CANDIDATE_TYPE_SRFLX: return PURPLE_MEDIA_CANDIDATE_TYPE_SRFLX;
1001 case SIPE_CANDIDATE_TYPE_PRFLX: return PURPLE_MEDIA_CANDIDATE_TYPE_PRFLX;
1002 default: return PURPLE_MEDIA_CANDIDATE_TYPE_HOST;
1006 static SipeCandidateType
1007 purple_candidate_type_to_sipe(PurpleMediaCandidateType type)
1009 switch (type) {
1010 case PURPLE_MEDIA_CANDIDATE_TYPE_HOST: return SIPE_CANDIDATE_TYPE_HOST;
1011 case PURPLE_MEDIA_CANDIDATE_TYPE_RELAY: return SIPE_CANDIDATE_TYPE_RELAY;
1012 case PURPLE_MEDIA_CANDIDATE_TYPE_SRFLX: return SIPE_CANDIDATE_TYPE_SRFLX;
1013 case PURPLE_MEDIA_CANDIDATE_TYPE_PRFLX: return SIPE_CANDIDATE_TYPE_PRFLX;
1014 default: return SIPE_CANDIDATE_TYPE_HOST;
1018 static PurpleMediaNetworkProtocol
1019 sipe_network_protocol_to_purple(SipeNetworkProtocol proto)
1021 switch (proto) {
1022 #ifdef HAVE_ICE_TCP
1023 case SIPE_NETWORK_PROTOCOL_TCP_ACTIVE:
1024 return PURPLE_MEDIA_NETWORK_PROTOCOL_TCP_ACTIVE;
1025 case SIPE_NETWORK_PROTOCOL_TCP_PASSIVE:
1026 return PURPLE_MEDIA_NETWORK_PROTOCOL_TCP_PASSIVE;
1027 case SIPE_NETWORK_PROTOCOL_TCP_SO:
1028 return PURPLE_MEDIA_NETWORK_PROTOCOL_TCP_SO;
1029 #else
1030 case SIPE_NETWORK_PROTOCOL_TCP_ACTIVE:
1031 case SIPE_NETWORK_PROTOCOL_TCP_PASSIVE:
1032 case SIPE_NETWORK_PROTOCOL_TCP_SO:
1033 return PURPLE_MEDIA_NETWORK_PROTOCOL_TCP;
1034 #endif
1035 default:
1036 case SIPE_NETWORK_PROTOCOL_UDP:
1037 return PURPLE_MEDIA_NETWORK_PROTOCOL_UDP;
1041 static SipeNetworkProtocol
1042 purple_network_protocol_to_sipe(PurpleMediaNetworkProtocol proto)
1044 switch (proto) {
1045 #ifdef HAVE_ICE_TCP
1046 case PURPLE_MEDIA_NETWORK_PROTOCOL_TCP_ACTIVE:
1047 return SIPE_NETWORK_PROTOCOL_TCP_ACTIVE;
1048 case PURPLE_MEDIA_NETWORK_PROTOCOL_TCP_PASSIVE:
1049 return SIPE_NETWORK_PROTOCOL_TCP_PASSIVE;
1050 case PURPLE_MEDIA_NETWORK_PROTOCOL_TCP_SO:
1051 return SIPE_NETWORK_PROTOCOL_TCP_SO;
1052 #else
1053 case PURPLE_MEDIA_NETWORK_PROTOCOL_TCP:
1054 return SIPE_NETWORK_PROTOCOL_TCP_ACTIVE;
1055 #endif
1056 default:
1057 case PURPLE_MEDIA_NETWORK_PROTOCOL_UDP:
1058 return SIPE_NETWORK_PROTOCOL_UDP;
1063 Local Variables:
1064 mode: c
1065 c-file-style: "bsd"
1066 indent-tabs-mode: t
1067 tab-width: 8
1068 End: