Revert "media: don't announce Opus codec"
[siplcs.git] / src / purple / purple-media.c
blobdd179d13f784ae42e8f30e34e7e326b3c449da7c
1 /**
2 * @file purple-media.c
4 * pidgin-sipe
6 * Copyright (C) 2010-12 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__) && (__GNUC__ >= 4) && (__GNUC_MINOR__ >= 2)
56 #if defined(__ARMEL__) || defined(__ARMEB__) || defined(__mips__) || defined(__sparc__) || (defined(__powerpc__) && defined(__NO_FPRS__))
57 #pragma GCC diagnostic ignored "-Wcast-align"
58 #endif
59 #endif
61 #include "media-gst.h"
63 struct sipe_backend_media {
64 PurpleMedia *m;
65 GSList *streams;
66 /**
67 * Number of media streams that were not yet locally accepted or rejected.
69 guint unconfirmed_streams;
72 struct sipe_backend_stream {
73 gchar *sessionid;
74 gchar *participant;
75 gboolean local_on_hold;
76 gboolean remote_on_hold;
77 gboolean accepted;
78 gboolean initialized_cb_was_fired;
81 static void
82 backend_stream_free(struct sipe_backend_stream *stream)
84 if (stream) {
85 g_free(stream->sessionid);
86 g_free(stream->participant);
87 g_free(stream);
91 static PurpleMediaSessionType sipe_media_to_purple(SipeMediaType type);
92 static PurpleMediaCandidateType sipe_candidate_type_to_purple(SipeCandidateType type);
93 static SipeCandidateType purple_candidate_type_to_sipe(PurpleMediaCandidateType type);
94 static PurpleMediaNetworkProtocol sipe_network_protocol_to_purple(SipeNetworkProtocol proto);
95 static SipeNetworkProtocol purple_network_protocol_to_sipe(PurpleMediaNetworkProtocol proto);
97 static void
98 maybe_signal_stream_initialized(struct sipe_media_call *call, gchar *sessionid)
100 if (call->stream_initialized_cb) {
101 struct sipe_backend_stream *stream;
102 stream = sipe_backend_media_get_stream_by_id(call->backend_private, sessionid);
104 if (sipe_backend_stream_initialized(call->backend_private, stream) &&
105 !stream->initialized_cb_was_fired) {
106 call->stream_initialized_cb(call, stream);
107 stream->initialized_cb_was_fired = TRUE;
112 static void
113 on_candidates_prepared_cb(SIPE_UNUSED_PARAMETER PurpleMedia *media,
114 gchar *sessionid,
115 SIPE_UNUSED_PARAMETER gchar *participant,
116 struct sipe_media_call *call)
118 maybe_signal_stream_initialized(call, sessionid);
121 static void
122 on_codecs_changed_cb(SIPE_UNUSED_PARAMETER PurpleMedia *media,
123 gchar *sessionid,
124 struct sipe_media_call *call)
126 maybe_signal_stream_initialized(call, sessionid);
129 static void
130 on_state_changed_cb(SIPE_UNUSED_PARAMETER PurpleMedia *media,
131 PurpleMediaState state,
132 gchar *sessionid,
133 gchar *participant,
134 struct sipe_media_call *call)
136 SIPE_DEBUG_INFO("sipe_media_state_changed_cb: %d %s %s\n", state, sessionid, participant);
137 if (state == PURPLE_MEDIA_STATE_END &&
138 !sessionid && !participant && call->media_end_cb)
139 call->media_end_cb(call);
142 void
143 capture_pipeline(const gchar *label) {
144 PurpleMediaManager *manager = purple_media_manager_get();
145 GstElement *pipeline = purple_media_manager_get_pipeline(manager);
146 GST_DEBUG_BIN_TO_DOT_FILE_WITH_TS(GST_BIN(pipeline), GST_DEBUG_GRAPH_SHOW_ALL, label);
149 static void
150 on_error_cb(SIPE_UNUSED_PARAMETER PurpleMedia *media, gchar *message,
151 struct sipe_media_call *call)
153 capture_pipeline("ERROR");
155 if (call->error_cb)
156 call->error_cb(call, message);
159 static void
160 on_stream_info_cb(SIPE_UNUSED_PARAMETER PurpleMedia *media,
161 PurpleMediaInfoType type,
162 gchar *sessionid,
163 gchar *participant,
164 gboolean local,
165 struct sipe_media_call *call)
167 if (type == PURPLE_MEDIA_INFO_ACCEPT) {
168 if (call->call_accept_cb && !sessionid && !participant)
169 call->call_accept_cb(call, local);
170 else if (sessionid && participant) {
171 struct sipe_backend_stream *stream;
172 stream = sipe_backend_media_get_stream_by_id(call->backend_private,
173 sessionid);
174 if (stream) {
175 if (!stream->accepted && local)
176 --call->backend_private->unconfirmed_streams;
177 stream->accepted = TRUE;
180 } else if (type == PURPLE_MEDIA_INFO_HOLD || type == PURPLE_MEDIA_INFO_UNHOLD) {
182 gboolean state = (type == PURPLE_MEDIA_INFO_HOLD);
184 if (sessionid) {
185 // Hold specific stream
186 struct sipe_backend_stream *stream;
187 stream = sipe_backend_media_get_stream_by_id(call->backend_private,
188 sessionid);
190 if (local)
191 stream->local_on_hold = state;
192 else
193 stream->remote_on_hold = state;
194 } else {
195 // Hold all streams
196 GSList *i = sipe_backend_media_get_streams(call->backend_private);
197 for (; i; i = i->next) {
198 struct sipe_backend_stream *stream = i->data;
200 if (local)
201 stream->local_on_hold = state;
202 else
203 stream->remote_on_hold = state;
207 if (call->call_hold_cb)
208 call->call_hold_cb(call, local, state);
209 } else if (type == PURPLE_MEDIA_INFO_HANGUP || type == PURPLE_MEDIA_INFO_REJECT) {
210 if (!sessionid && !participant) {
211 if (type == PURPLE_MEDIA_INFO_HANGUP && call->call_hangup_cb)
212 call->call_hangup_cb(call, local);
213 else if (type == PURPLE_MEDIA_INFO_REJECT && call->call_reject_cb && !local)
214 call->call_reject_cb(call, local);
215 } else if (sessionid && participant) {
216 struct sipe_backend_stream *stream;
217 stream = sipe_backend_media_get_stream_by_id(call->backend_private,
218 sessionid);
220 if (stream) {
221 call->backend_private->streams = g_slist_remove(call->backend_private->streams, stream);
222 backend_stream_free(stream);
223 if (local && --call->backend_private->unconfirmed_streams == 0 &&
224 call->call_reject_cb)
225 call->call_reject_cb(call, local);
231 struct sipe_backend_media *
232 sipe_backend_media_new(struct sipe_core_public *sipe_public,
233 struct sipe_media_call *call,
234 const gchar *participant,
235 gboolean initiator)
237 struct sipe_backend_media *media = g_new0(struct sipe_backend_media, 1);
238 struct sipe_backend_private *purple_private = sipe_public->backend_private;
239 PurpleMediaManager *manager = purple_media_manager_get();
240 GstElement *pipeline;
242 media->m = purple_media_manager_create_media(manager,
243 purple_private->account,
244 "fsrtpconference",
245 participant, initiator);
247 g_signal_connect(G_OBJECT(media->m), "candidates-prepared",
248 G_CALLBACK(on_candidates_prepared_cb), call);
249 g_signal_connect(G_OBJECT(media->m), "codecs-changed",
250 G_CALLBACK(on_codecs_changed_cb), call);
251 g_signal_connect(G_OBJECT(media->m), "stream-info",
252 G_CALLBACK(on_stream_info_cb), call);
253 g_signal_connect(G_OBJECT(media->m), "error",
254 G_CALLBACK(on_error_cb), call);
255 g_signal_connect(G_OBJECT(media->m), "state-changed",
256 G_CALLBACK(on_state_changed_cb), call);
258 /* On error, the pipeline is no longer in PLAYING state and libpurple
259 * will not switch it back to PLAYING, preventing any more calls until
260 * application restart. We switch the state ourselves here to negate
261 * effect of any error in previous call (if any). */
262 pipeline = purple_media_manager_get_pipeline(manager);
263 gst_element_set_state(pipeline, GST_STATE_PLAYING);
265 return media;
268 void
269 sipe_backend_media_free(struct sipe_backend_media *media)
271 if (media) {
272 GSList *stream = media->streams;
274 for (; stream; stream = g_slist_delete_link(stream, stream))
275 backend_stream_free(stream->data);
277 g_free(media);
281 void
282 sipe_backend_media_set_cname(struct sipe_backend_media *media, gchar *cname)
284 if (media) {
285 guint num_params = 3;
286 GParameter *params = g_new0(GParameter, num_params);
287 params[0].name = "sdes-cname";
288 g_value_init(&params[0].value, G_TYPE_STRING);
289 g_value_set_string(&params[0].value, cname);
290 params[1].name = "sdes-name";
291 g_value_init(&params[1].value, G_TYPE_STRING);
292 params[2].name = "sdes-tool";
293 g_value_init(&params[2].value, G_TYPE_STRING);
295 purple_media_set_params(media->m, num_params, params);
297 g_value_unset(&params[0].value);
298 g_free(params);
302 #define FS_CODECS_CONF \
303 "# Automatically created by SIPE plugin\n" \
304 "[video/H263]\n" \
305 "farsight-send-profile=videoscale ! ffmpegcolorspace ! fsvideoanyrate ! ffenc_h263 rtp-payload-size=30 ! rtph263pay\n" \
306 "\n" \
307 "[audio/PCMA]\n" \
308 "farsight-send-profile=audioconvert ! audioresample ! audioconvert ! alawenc ! rtppcmapay min-ptime=20000000 max-ptime=20000000\n" \
309 "\n" \
310 "[audio/PCMU]\n" \
311 "farsight-send-profile=audioconvert ! audioresample ! audioconvert ! mulawenc ! rtppcmupay min-ptime=20000000 max-ptime=20000000\n";
313 static void
314 ensure_codecs_conf()
316 gchar *filename;
317 filename = g_build_filename(purple_user_dir(), "fs-codec.conf", NULL);
319 if (!g_file_test(filename, G_FILE_TEST_EXISTS)) {
320 int fd = g_open(filename, O_CREAT | O_WRONLY, S_IRUSR | S_IWUSR);
321 gchar *fs_codecs_conf = FS_CODECS_CONF;
322 if ((fd < 0) || write(fd, fs_codecs_conf, strlen(fs_codecs_conf)) == -1)
323 SIPE_DEBUG_ERROR_NOFORMAT("Can not create fs-codec.conf!");
324 if (fd >= 0)
325 close(fd);
328 g_free(filename);
331 static void
332 append_relay(GValueArray *relay_info, const gchar *ip, guint port, gchar *type,
333 gchar *username, gchar *password)
335 GValue value;
336 GstStructure *gst_relay_info;
338 gst_relay_info = gst_structure_new("relay-info",
339 "ip", G_TYPE_STRING, ip,
340 "port", G_TYPE_UINT, port,
341 "relay-type", G_TYPE_STRING, type,
342 "username", G_TYPE_STRING, username,
343 "password", G_TYPE_STRING, password,
344 NULL);
346 if (gst_relay_info) {
347 memset(&value, 0, sizeof(GValue));
348 g_value_init(&value, GST_TYPE_STRUCTURE);
349 gst_value_set_structure(&value, gst_relay_info);
351 g_value_array_append(relay_info, &value);
352 gst_structure_free(gst_relay_info);
356 struct sipe_backend_media_relays *
357 sipe_backend_media_relays_convert(GSList *media_relays, gchar *username, gchar *password)
359 GValueArray *relay_info = g_value_array_new(0);
361 for (; media_relays; media_relays = media_relays->next) {\
362 struct sipe_media_relay *relay = media_relays->data;
364 /* Skip relays where IP could not be resolved. */
365 if (!relay->hostname)
366 continue;
368 if (relay->udp_port != 0)
369 append_relay(relay_info, relay->hostname, relay->udp_port,
370 "udp", username, password);
372 #ifdef HAVE_ICE_TCP
373 if (relay->tcp_port != 0)
374 append_relay(relay_info, relay->hostname, relay->tcp_port,
375 "tcp", username, password);
376 #endif
379 return (struct sipe_backend_media_relays *)relay_info;
382 void
383 sipe_backend_media_relays_free(struct sipe_backend_media_relays *media_relays)
385 g_value_array_free((GValueArray *)media_relays);
388 static guint
389 stream_demultiplex_cb(const gchar *buf, SIPE_UNUSED_PARAMETER gpointer *user_data)
391 guint8 payload_type = buf[1] & 0x7F;
392 if (payload_type >= 200 && payload_type <=204) {
393 // Looks like RTCP
394 return PURPLE_MEDIA_COMPONENT_RTCP;
395 } else {
396 // Looks like RTP
397 return PURPLE_MEDIA_COMPONENT_RTP;
401 struct sipe_backend_stream *
402 sipe_backend_media_add_stream(struct sipe_backend_media *media,
403 const gchar *id,
404 const gchar *participant,
405 SipeMediaType type,
406 SipeIceVersion ice_version,
407 gboolean initiator,
408 struct sipe_backend_media_relays *media_relays)
410 struct sipe_backend_stream *stream = NULL;
411 PurpleMediaSessionType prpl_type = sipe_media_to_purple(type);
412 GParameter *params = NULL;
413 guint params_cnt = 0;
414 gchar *transmitter;
416 if (ice_version != SIPE_ICE_NO_ICE) {
417 transmitter = "nice";
418 params_cnt = 4;
420 params = g_new0(GParameter, params_cnt);
422 params[0].name = "compatibility-mode";
423 g_value_init(&params[0].value, G_TYPE_UINT);
424 g_value_set_uint(&params[0].value,
425 ice_version == SIPE_ICE_DRAFT_6 ?
426 NICE_COMPATIBILITY_OC2007 :
427 NICE_COMPATIBILITY_OC2007R2);
429 params[1].name = "transport-protocols";
430 g_value_init(&params[1].value, G_TYPE_UINT);
431 #ifdef HAVE_ICE_TCP
432 g_value_set_uint(&params[1].value,
433 PURPLE_MEDIA_NETWORK_PROTOCOL_UDP |
434 PURPLE_MEDIA_NETWORK_PROTOCOL_TCP_ACTIVE |
435 PURPLE_MEDIA_NETWORK_PROTOCOL_TCP_PASSIVE);
436 #else
437 g_value_set_uint(&params[1].value,
438 PURPLE_MEDIA_NETWORK_PROTOCOL_UDP);
439 #endif
441 params[2].name = "demultiplex-func";
442 g_value_init(&params[2].value, G_TYPE_POINTER);
443 g_value_set_pointer(&params[2].value, stream_demultiplex_cb);
445 if (media_relays) {
446 params[3].name = "relay-info";
447 g_value_init(&params[3].value, G_TYPE_VALUE_ARRAY);
448 g_value_set_boxed(&params[3].value, media_relays);
449 } else
450 --params_cnt;
451 } else {
452 // TODO: session naming here, Communicator needs audio/video
453 transmitter = "rawudp";
454 //sessionid = "sipe-voice-rawudp";
456 /* To avoid Coverity FORWARD_NULL warning. params_cnt is
457 * still 0 so this is a no-op. libpurple API documentation
458 * doesn't specify if params can be NULL or not. */
459 params = g_new0(GParameter, 1);
462 ensure_codecs_conf();
464 if (purple_media_add_stream(media->m, id, participant, prpl_type,
465 initiator, transmitter, params_cnt,
466 params)) {
467 stream = g_new0(struct sipe_backend_stream, 1);
468 stream->sessionid = g_strdup(id);
469 stream->participant = g_strdup(participant);
470 stream->initialized_cb_was_fired = FALSE;
472 media->streams = g_slist_append(media->streams, stream);
473 if (!initiator)
474 ++media->unconfirmed_streams;
477 if ((params_cnt > 2) && media_relays)
478 g_value_unset(&params[3].value);
480 g_free(params);
482 return stream;
485 void
486 sipe_backend_media_remove_stream(struct sipe_backend_media *media,
487 struct sipe_backend_stream *stream)
489 g_return_if_fail(media && stream);
491 purple_media_end(media->m, stream->sessionid, NULL);
492 media->streams = g_slist_remove(media->streams, stream);
493 backend_stream_free(stream);
496 GSList *sipe_backend_media_get_streams(struct sipe_backend_media *media)
498 return media->streams;
501 struct sipe_backend_stream *
502 sipe_backend_media_get_stream_by_id(struct sipe_backend_media *media,
503 const gchar *id)
505 GSList *i;
506 for (i = media->streams; i; i = i->next) {
507 struct sipe_backend_stream *stream = i->data;
508 if (sipe_strequal(stream->sessionid, id))
509 return stream;
511 return NULL;
514 void
515 sipe_backend_media_add_remote_candidates(struct sipe_backend_media *media,
516 struct sipe_backend_stream *stream,
517 GList *candidates)
519 GList *udp_candidates = NULL;
521 #ifndef HAVE_ICE_TCP
522 while (candidates) {
523 PurpleMediaCandidate *candidate = candidates->data;
524 PurpleMediaNetworkProtocol proto;
526 proto = purple_media_candidate_get_protocol(candidate);
527 if (proto == PURPLE_MEDIA_NETWORK_PROTOCOL_UDP)
528 udp_candidates = g_list_append(udp_candidates, candidate);
530 candidates = candidates->next;
533 candidates = udp_candidates;
534 #endif
537 purple_media_add_remote_candidates(media->m, stream->sessionid,
538 stream->participant, candidates);
540 g_list_free(udp_candidates);
543 gboolean sipe_backend_media_is_initiator(struct sipe_backend_media *media,
544 struct sipe_backend_stream *stream)
546 return purple_media_is_initiator(media->m,
547 stream ? stream->sessionid : NULL,
548 stream ? stream->participant : NULL);
551 gboolean sipe_backend_media_accepted(struct sipe_backend_media *media)
553 return purple_media_accepted(media->m, NULL, NULL);
556 gboolean
557 sipe_backend_stream_initialized(struct sipe_backend_media *media,
558 struct sipe_backend_stream *stream)
560 g_return_val_if_fail(media, FALSE);
561 g_return_val_if_fail(stream, FALSE);
563 if (purple_media_candidates_prepared(media->m,
564 stream->sessionid,
565 stream->participant)) {
566 GList *codecs;
567 codecs = purple_media_get_codecs(media->m, stream->sessionid);
568 if (codecs) {
569 purple_media_codec_list_free(codecs);
570 return TRUE;
573 return FALSE;
576 GList *
577 sipe_backend_media_get_active_local_candidates(struct sipe_backend_media *media,
578 struct sipe_backend_stream *stream)
580 return purple_media_get_active_local_candidates(media->m,
581 stream->sessionid,
582 stream->participant);
585 GList *
586 sipe_backend_media_get_active_remote_candidates(struct sipe_backend_media *media,
587 struct sipe_backend_stream *stream)
589 return purple_media_get_active_remote_candidates(media->m,
590 stream->sessionid,
591 stream->participant);
594 const gchar *
595 sipe_backend_stream_get_id(struct sipe_backend_stream *stream)
597 return stream->sessionid;
600 void sipe_backend_stream_hold(struct sipe_backend_media *media,
601 struct sipe_backend_stream *stream,
602 gboolean local)
604 purple_media_stream_info(media->m, PURPLE_MEDIA_INFO_HOLD,
605 stream->sessionid, stream->participant,
606 local);
609 void sipe_backend_stream_unhold(struct sipe_backend_media *media,
610 struct sipe_backend_stream *stream,
611 gboolean local)
613 purple_media_stream_info(media->m, PURPLE_MEDIA_INFO_UNHOLD,
614 stream->sessionid, stream->participant,
615 local);
618 gboolean sipe_backend_stream_is_held(struct sipe_backend_stream *stream)
620 g_return_val_if_fail(stream, FALSE);
622 return stream->local_on_hold || stream->remote_on_hold;
625 struct sipe_backend_codec *
626 sipe_backend_codec_new(int id, const char *name, SipeMediaType type, guint clock_rate)
628 return (struct sipe_backend_codec *)purple_media_codec_new(id, name,
629 sipe_media_to_purple(type),
630 clock_rate);
633 void
634 sipe_backend_codec_free(struct sipe_backend_codec *codec)
636 if (codec)
637 g_object_unref(codec);
641 sipe_backend_codec_get_id(struct sipe_backend_codec *codec)
643 return purple_media_codec_get_id((PurpleMediaCodec *)codec);
646 gchar *
647 sipe_backend_codec_get_name(struct sipe_backend_codec *codec)
649 /* Not explicitly documented, but return value must be g_free()'d */
650 return purple_media_codec_get_encoding_name((PurpleMediaCodec *)codec);
653 guint
654 sipe_backend_codec_get_clock_rate(struct sipe_backend_codec *codec)
656 return purple_media_codec_get_clock_rate((PurpleMediaCodec *)codec);
659 void
660 sipe_backend_codec_add_optional_parameter(struct sipe_backend_codec *codec,
661 const gchar *name, const gchar *value)
663 purple_media_codec_add_optional_parameter((PurpleMediaCodec *)codec, name, value);
666 GList *
667 sipe_backend_codec_get_optional_parameters(struct sipe_backend_codec *codec)
669 return purple_media_codec_get_optional_parameters((PurpleMediaCodec *)codec);
672 gboolean
673 sipe_backend_set_remote_codecs(struct sipe_backend_media *media,
674 struct sipe_backend_stream *stream,
675 GList *codecs)
677 return purple_media_set_remote_codecs(media->m,
678 stream->sessionid,
679 stream->participant,
680 codecs);
683 GList*
684 sipe_backend_get_local_codecs(struct sipe_backend_media *media,
685 struct sipe_backend_stream *stream)
687 GList *codecs = purple_media_get_codecs(media->m,
688 stream->sessionid);
689 GList *i = codecs;
690 gboolean is_conference = (g_strstr_len(stream->participant,
691 strlen(stream->participant),
692 "app:conf:audio-video:") != NULL);
695 * Do not announce Theora. Its optional parameters are too long,
696 * Communicator rejects such SDP message and does not support the codec
697 * anyway.
699 * For some yet unknown reason, A/V conferencing server does not accept
700 * voice stream sent by SIPE when SIREN codec is in use. Nevertheless,
701 * we are able to decode incoming SIREN from server and with MSOC
702 * client, bidirectional call using the codec works. Until resolved,
703 * do not try to negotiate SIREN usage when conferencing. PCMA or PCMU
704 * seems to work properly in this scenario.
706 while (i) {
707 PurpleMediaCodec *codec = i->data;
708 gchar *encoding_name = purple_media_codec_get_encoding_name(codec);
710 if (sipe_strequal(encoding_name,"THEORA") ||
711 (is_conference && sipe_strequal(encoding_name,"SIREN"))) {
712 GList *tmp;
713 g_object_unref(codec);
714 tmp = i->next;
715 codecs = g_list_delete_link(codecs, i);
716 i = tmp;
717 } else
718 i = i->next;
720 g_free(encoding_name);
723 return codecs;
726 struct sipe_backend_candidate *
727 sipe_backend_candidate_new(const gchar *foundation,
728 SipeComponentType component,
729 SipeCandidateType type, SipeNetworkProtocol proto,
730 const gchar *ip, guint port,
731 const gchar *username,
732 const gchar *password)
734 PurpleMediaCandidate *c = purple_media_candidate_new(
735 /* Libnice and Farsight rely on non-NULL foundation to
736 * distinguish between candidates of a component. When NULL
737 * foundation is passed (ie. ICE draft 6 does not use foudation),
738 * use username instead. If no foundation is provided, Farsight
739 * may signal an active candidate different from the one actually
740 * in use. See Farsight's agent_new_selected_pair() in
741 * fs-nice-stream-transmitter.h where first candidate in the
742 * remote list is always selected when no foundation. */
743 foundation ? foundation : username,
744 component,
745 sipe_candidate_type_to_purple(type),
746 sipe_network_protocol_to_purple(proto),
748 port);
749 g_object_set(c, "username", username, "password", password, NULL);
750 return (struct sipe_backend_candidate *)c;
753 void
754 sipe_backend_candidate_free(struct sipe_backend_candidate *candidate)
756 if (candidate)
757 g_object_unref(candidate);
760 gchar *
761 sipe_backend_candidate_get_username(struct sipe_backend_candidate *candidate)
763 /* Not explicitly documented, but return value must be g_free()'d */
764 return purple_media_candidate_get_username((PurpleMediaCandidate*)candidate);
767 gchar *
768 sipe_backend_candidate_get_password(struct sipe_backend_candidate *candidate)
770 /* Not explicitly documented, but return value must be g_free()'d */
771 return purple_media_candidate_get_password((PurpleMediaCandidate*)candidate);
774 gchar *
775 sipe_backend_candidate_get_foundation(struct sipe_backend_candidate *candidate)
777 /* Not explicitly documented, but return value must be g_free()'d */
778 return purple_media_candidate_get_foundation((PurpleMediaCandidate*)candidate);
781 gchar *
782 sipe_backend_candidate_get_ip(struct sipe_backend_candidate *candidate)
784 /* Not explicitly documented, but return value must be g_free()'d */
785 return purple_media_candidate_get_ip((PurpleMediaCandidate*)candidate);
788 guint
789 sipe_backend_candidate_get_port(struct sipe_backend_candidate *candidate)
791 return purple_media_candidate_get_port((PurpleMediaCandidate*)candidate);
794 gchar *
795 sipe_backend_candidate_get_base_ip(struct sipe_backend_candidate *candidate)
797 /* Not explicitly documented, but return value must be g_free()'d */
798 return purple_media_candidate_get_base_ip((PurpleMediaCandidate*)candidate);
801 guint
802 sipe_backend_candidate_get_base_port(struct sipe_backend_candidate *candidate)
804 return purple_media_candidate_get_base_port((PurpleMediaCandidate*)candidate);
807 guint32
808 sipe_backend_candidate_get_priority(struct sipe_backend_candidate *candidate)
810 return purple_media_candidate_get_priority((PurpleMediaCandidate*)candidate);
813 void
814 sipe_backend_candidate_set_priority(struct sipe_backend_candidate *candidate, guint32 priority)
816 g_object_set(candidate, "priority", priority, NULL);
819 SipeComponentType
820 sipe_backend_candidate_get_component_type(struct sipe_backend_candidate *candidate)
822 return purple_media_candidate_get_component_id((PurpleMediaCandidate*)candidate);
825 SipeCandidateType
826 sipe_backend_candidate_get_type(struct sipe_backend_candidate *candidate)
828 PurpleMediaCandidateType type =
829 purple_media_candidate_get_candidate_type((PurpleMediaCandidate*)candidate);
830 return purple_candidate_type_to_sipe(type);
833 SipeNetworkProtocol
834 sipe_backend_candidate_get_protocol(struct sipe_backend_candidate *candidate)
836 PurpleMediaNetworkProtocol proto =
837 purple_media_candidate_get_protocol((PurpleMediaCandidate*)candidate);
838 return purple_network_protocol_to_sipe(proto);
841 static void
842 remove_lone_candidate_cb(SIPE_UNUSED_PARAMETER gpointer key,
843 gpointer value,
844 gpointer user_data)
846 GList *entry = value;
847 GList **candidates = user_data;
849 g_object_unref(entry->data);
850 *candidates = g_list_delete_link(*candidates, entry);
853 static GList *
854 ensure_candidate_pairs(GList *candidates)
856 GHashTable *lone_cand_links;
857 GList *i;
859 lone_cand_links = g_hash_table_new_full(g_str_hash, g_str_equal, g_free, NULL);
861 for (i = candidates; i; i = i->next) {
862 PurpleMediaCandidate *c = i->data;
863 gchar *foundation = purple_media_candidate_get_foundation(c);
865 if (g_hash_table_lookup(lone_cand_links, foundation)) {
866 g_hash_table_remove(lone_cand_links, foundation);
867 g_free(foundation);
868 } else {
869 g_hash_table_insert(lone_cand_links, foundation, i);
873 g_hash_table_foreach(lone_cand_links, remove_lone_candidate_cb, &candidates);
874 g_hash_table_destroy(lone_cand_links);
876 return candidates;
879 GList *
880 sipe_backend_get_local_candidates(struct sipe_backend_media *media,
881 struct sipe_backend_stream *stream)
883 GList *candidates = purple_media_get_local_candidates(media->m,
884 stream->sessionid,
885 stream->participant);
887 * Sometimes purple will not return complete list of candidates, even
888 * after "candidates-prepared" signal is emitted. This is a feature of
889 * libnice, namely affecting candidates discovered via UPnP. Nice does
890 * not wait until discovery is finished and can signal end of candidate
891 * gathering before all responses from UPnP enabled gateways are received.
893 * Remove any incomplete RTP+RTCP candidate pairs from the list.
895 candidates = ensure_candidate_pairs(candidates);
896 return candidates;
899 void
900 sipe_backend_media_accept(struct sipe_backend_media *media, gboolean local)
902 if (media)
903 purple_media_stream_info(media->m, PURPLE_MEDIA_INFO_ACCEPT,
904 NULL, NULL, local);
907 void
908 sipe_backend_media_hangup(struct sipe_backend_media *media, gboolean local)
910 if (media)
911 purple_media_stream_info(media->m, PURPLE_MEDIA_INFO_HANGUP,
912 NULL, NULL, local);
915 void
916 sipe_backend_media_reject(struct sipe_backend_media *media, gboolean local)
918 if (media)
919 purple_media_stream_info(media->m, PURPLE_MEDIA_INFO_REJECT,
920 NULL, NULL, local);
923 static PurpleMediaSessionType sipe_media_to_purple(SipeMediaType type)
925 switch (type) {
926 case SIPE_MEDIA_AUDIO: return PURPLE_MEDIA_AUDIO;
927 case SIPE_MEDIA_VIDEO: return PURPLE_MEDIA_VIDEO;
928 default: return PURPLE_MEDIA_NONE;
932 /*SipeMediaType purple_media_to_sipe(PurpleMediaSessionType type)
934 switch (type) {
935 case PURPLE_MEDIA_AUDIO: return SIPE_MEDIA_AUDIO;
936 case PURPLE_MEDIA_VIDEO: return SIPE_MEDIA_VIDEO;
937 default: return SIPE_MEDIA_AUDIO;
941 static PurpleMediaCandidateType
942 sipe_candidate_type_to_purple(SipeCandidateType type)
944 switch (type) {
945 case SIPE_CANDIDATE_TYPE_HOST: return PURPLE_MEDIA_CANDIDATE_TYPE_HOST;
946 case SIPE_CANDIDATE_TYPE_RELAY: return PURPLE_MEDIA_CANDIDATE_TYPE_RELAY;
947 case SIPE_CANDIDATE_TYPE_SRFLX: return PURPLE_MEDIA_CANDIDATE_TYPE_SRFLX;
948 case SIPE_CANDIDATE_TYPE_PRFLX: return PURPLE_MEDIA_CANDIDATE_TYPE_PRFLX;
949 default: return PURPLE_MEDIA_CANDIDATE_TYPE_HOST;
953 static SipeCandidateType
954 purple_candidate_type_to_sipe(PurpleMediaCandidateType type)
956 switch (type) {
957 case PURPLE_MEDIA_CANDIDATE_TYPE_HOST: return SIPE_CANDIDATE_TYPE_HOST;
958 case PURPLE_MEDIA_CANDIDATE_TYPE_RELAY: return SIPE_CANDIDATE_TYPE_RELAY;
959 case PURPLE_MEDIA_CANDIDATE_TYPE_SRFLX: return SIPE_CANDIDATE_TYPE_SRFLX;
960 case PURPLE_MEDIA_CANDIDATE_TYPE_PRFLX: return SIPE_CANDIDATE_TYPE_PRFLX;
961 default: return SIPE_CANDIDATE_TYPE_HOST;
965 static PurpleMediaNetworkProtocol
966 sipe_network_protocol_to_purple(SipeNetworkProtocol proto)
968 switch (proto) {
969 #ifdef HAVE_ICE_TCP
970 case SIPE_NETWORK_PROTOCOL_TCP_ACTIVE:
971 return PURPLE_MEDIA_NETWORK_PROTOCOL_TCP_ACTIVE;
972 case SIPE_NETWORK_PROTOCOL_TCP_PASSIVE:
973 return PURPLE_MEDIA_NETWORK_PROTOCOL_TCP_PASSIVE;
974 #else
975 case SIPE_NETWORK_PROTOCOL_TCP_ACTIVE:
976 case SIPE_NETWORK_PROTOCOL_TCP_PASSIVE:
977 return PURPLE_MEDIA_NETWORK_PROTOCOL_TCP;
978 #endif
979 case SIPE_NETWORK_PROTOCOL_UDP:
980 return PURPLE_MEDIA_NETWORK_PROTOCOL_UDP;
981 default:
982 return PURPLE_MEDIA_NETWORK_PROTOCOL_UDP;
986 static SipeNetworkProtocol
987 purple_network_protocol_to_sipe(PurpleMediaNetworkProtocol proto)
989 switch (proto) {
990 #ifdef HAVE_ICE_TCP
991 case PURPLE_MEDIA_NETWORK_PROTOCOL_TCP_ACTIVE:
992 return SIPE_NETWORK_PROTOCOL_TCP_ACTIVE;
993 case PURPLE_MEDIA_NETWORK_PROTOCOL_TCP_PASSIVE:
994 return SIPE_NETWORK_PROTOCOL_TCP_PASSIVE;
995 #else
996 case PURPLE_MEDIA_NETWORK_PROTOCOL_TCP:
997 return SIPE_NETWORK_PROTOCOL_TCP_ACTIVE;
998 #endif
999 case PURPLE_MEDIA_NETWORK_PROTOCOL_UDP:
1000 return SIPE_NETWORK_PROTOCOL_UDP;
1001 default:
1002 return SIPE_NETWORK_PROTOCOL_UDP;
1007 Local Variables:
1008 mode: c
1009 c-file-style: "bsd"
1010 indent-tabs-mode: t
1011 tab-width: 8
1012 End: