audio: depurple media stream
[siplcs.git] / src / core / sipe-media.c
blob99de6da5a29b0f3f97195b1349d25137d4bb946f
1 /**
2 * @file sipe-media.c
4 * pidgin-sipe
6 * Copyright (C) 2010 Jakub Adam <jakub.adam@tieto.com>
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 <string.h>
29 #include <stdlib.h>
30 #include <libpurple/mediamanager.h>
32 #include "sipe-core.h"
33 #include "sipe.h"
34 #include "sipmsg.h"
35 #include "sipe-session.h"
36 #include "sipe-media.h"
37 #include "sipe-dialog.h"
38 #include "sipe-utils.h"
39 #include "sipe-common.h"
41 gchar *
42 sipe_media_get_callid(sipe_media_call *call)
44 return call->dialog->callid;
47 void sipe_media_codec_list_free(GList *codecs)
49 for (; codecs; codecs = g_list_delete_link(codecs, codecs)) {
50 sipe_backend_codec_free(codecs->data);
54 void sipe_media_candidate_list_free(GList *candidates)
56 for (; candidates; candidates = g_list_delete_link(candidates, candidates)) {
57 sipe_backend_candidate_free(candidates->data);
61 static void
62 sipe_media_call_free(sipe_media_call *call)
64 if (call) {
65 sipe_utils_nameval_free(call->sdp_attrs);
66 if (call->invitation)
67 sipmsg_free(call->invitation);
68 sipe_media_codec_list_free(call->remote_codecs);
69 sipe_media_candidate_list_free(call->remote_candidates);
70 g_free(call);
74 static GList *
75 sipe_media_parse_remote_codecs(const sipe_media_call *call)
77 int i = 0;
78 const gchar *attr;
79 GList *codecs = NULL;
81 while ((attr = sipe_utils_nameval_find_instance(call->sdp_attrs, "rtpmap", i++))) {
82 gchar **tokens = g_strsplit_set(attr, " /", 3);
84 int id = atoi(tokens[0]);
85 gchar *name = tokens[1];
86 int clock_rate = atoi(tokens[2]);
87 SipeMediaType type = SIPE_MEDIA_AUDIO;
89 sipe_codec *codec = sipe_backend_codec_new(id, name, clock_rate, type);
91 codecs = g_list_append(codecs, codec);
92 g_strfreev(tokens);
95 return codecs;
98 static gint
99 codec_name_compare(sipe_codec* codec1, sipe_codec* codec2)
101 gchar *name1 = sipe_backend_codec_get_name(codec1);
102 gchar *name2 = sipe_backend_codec_get_name(codec2);
104 return g_strcmp0(name1, name2);
107 static GList *
108 sipe_media_prune_remote_codecs(sipe_media_call *call, GList *codecs)
110 GList *remote_codecs = codecs;
111 GList *local_codecs = sipe_backend_get_local_codecs(call);
112 GList *pruned_codecs = NULL;
114 while (remote_codecs) {
115 sipe_codec *c = remote_codecs->data;
117 if (g_list_find_custom(local_codecs, c, (GCompareFunc)codec_name_compare)) {
118 pruned_codecs = g_list_append(pruned_codecs, c);
119 remote_codecs->data = NULL;
121 remote_codecs = remote_codecs->next;
124 sipe_media_codec_list_free(codecs);
126 return pruned_codecs;
129 static GList *
130 sipe_media_parse_remote_candidates(sipe_media_call *call)
132 GSList *sdp_attrs = call->sdp_attrs;
133 sipe_candidate *candidate;
134 GList *candidates = NULL;
135 const gchar *attr;
136 int i = 0;
138 const gchar* username = sipe_utils_nameval_find(sdp_attrs, "ice-ufrag");
139 const gchar* password = sipe_utils_nameval_find(sdp_attrs, "ice-pwd");
141 while ((attr = sipe_utils_nameval_find_instance(sdp_attrs, "candidate", i++))) {
142 gchar **tokens;
143 gchar *foundation;
144 SipeComponentType component;
145 SipeNetworkProtocol protocol;
146 guint32 priority;
147 gchar* ip;
148 guint16 port;
149 SipeCandidateType type;
151 tokens = g_strsplit_set(attr, " ", 0);
153 foundation = tokens[0];
155 switch (atoi(tokens[1])) {
156 case 1:
157 component = SIPE_COMPONENT_RTP;
158 break;
159 case 2:
160 component = SIPE_COMPONENT_RTCP;
161 break;
162 default:
163 component = SIPE_COMPONENT_NONE;
166 if (sipe_strequal(tokens[2], "UDP"))
167 protocol = SIPE_NETWORK_PROTOCOL_UDP;
168 else {
169 // Ignore TCP candidates, at least for now...
170 g_strfreev(tokens);
171 continue;
174 priority = atoi(tokens[3]);
175 ip = tokens[4];
176 port = atoi(tokens[5]);
178 if (sipe_strequal(tokens[7], "host"))
179 type = SIPE_CANDIDATE_TYPE_HOST;
180 else if (sipe_strequal(tokens[7], "relay"))
181 type = SIPE_CANDIDATE_TYPE_RELAY;
182 else if (sipe_strequal(tokens[7], "srflx"))
183 type = SIPE_CANDIDATE_TYPE_SRFLX;
184 else {
185 g_strfreev(tokens);
186 continue;
189 candidate = sipe_backend_candidate_new(foundation, component,
190 type, protocol, ip, port);
191 g_object_set(candidate, "priority", priority, NULL);
192 candidates = g_list_append(candidates, candidate);
194 g_strfreev(tokens);
197 if (!candidates) {
198 // No a=candidate in SDP message, revert to OC2005 behaviour
199 candidate = sipe_backend_candidate_new("foundation",
200 SIPE_COMPONENT_RTP,
201 SIPE_CANDIDATE_TYPE_HOST,
202 SIPE_NETWORK_PROTOCOL_UDP,
203 call->remote_ip, call->remote_port);
204 candidates = g_list_append(candidates, candidate);
206 candidate = sipe_backend_candidate_new("foundation",
207 SIPE_COMPONENT_RTCP,
208 SIPE_CANDIDATE_TYPE_HOST,
209 SIPE_NETWORK_PROTOCOL_UDP,
210 call->remote_ip, call->remote_port + 1);
211 candidates = g_list_append(candidates, candidate);
213 // This seems to be pre-OC2007 R2 UAC
214 call->legacy_mode = TRUE;
217 if (username) {
218 GList *it = candidates;
219 while (it) {
220 sipe_backend_candidate_set_username_and_pwd(it->data, username, password);
221 it = it->next;
225 return candidates;
228 static gchar *
229 sipe_media_sdp_codec_ids_format(GList *codecs)
231 GString *result = g_string_new(NULL);
233 while (codecs) {
234 sipe_codec *c = codecs->data;
236 gchar *tmp = g_strdup_printf(" %d", sipe_backend_codec_get_id(c));
237 g_string_append(result,tmp);
238 g_free(tmp);
240 codecs = codecs->next;
243 return g_string_free(result, FALSE);
246 static gchar *
247 sipe_media_sdp_codecs_format(GList *codecs)
249 GString *result = g_string_new(NULL);
251 while (codecs) {
252 sipe_codec *c = codecs->data;
253 GList *params = NULL;
255 gchar *tmp = g_strdup_printf("a=rtpmap:%d %s/%d\r\n",
256 sipe_backend_codec_get_id(c),
257 sipe_backend_codec_get_name(c),
258 sipe_backend_codec_get_clock_rate(c));
260 g_string_append(result, tmp);
261 g_free(tmp);
263 if ((params = sipe_backend_codec_get_optional_parameters(c))) {
264 tmp = g_strdup_printf("a=fmtp:%d",sipe_backend_codec_get_id(c));
265 g_string_append(result, tmp);
266 g_free(tmp);
268 while (params) {
269 struct sipnameval* par = params->data;
270 tmp = g_strdup_printf(" %s=%s", par->name, par->value);
271 g_string_append(result, tmp);
272 g_free(tmp);
273 params = params->next;
275 g_string_append(result, "\r\n");
278 codecs = codecs->next;
281 return g_string_free(result, FALSE);
284 static gchar *
285 sipe_media_sdp_candidates_format(GList *candidates, sipe_media_call* call, gboolean remote_candidate)
287 GString *result = g_string_new("");
288 gchar *tmp;
289 gchar *username = sipe_backend_candidate_get_username(candidates->data);
290 gchar *password = sipe_backend_candidate_get_password(candidates->data);
291 guint16 rtcp_port = 0;
293 if (call->legacy_mode)
294 return g_string_free(result, FALSE);
296 tmp = g_strdup_printf("a=ice-ufrag:%s\r\na=ice-pwd:%s\r\n",username, password);
297 g_string_append(result, tmp);
298 g_free(tmp);
300 while (candidates) {
301 sipe_candidate *c = candidates->data;
303 guint16 port;
304 guint16 component;
305 gchar *protocol;
306 gchar *type;
308 port = sipe_backend_candidate_get_port(c);
310 switch (sipe_backend_candidate_get_component_type(c)) {
311 case SIPE_COMPONENT_RTP:
312 component = 1;
313 break;
314 case SIPE_COMPONENT_RTCP:
315 component = 2;
316 if (rtcp_port == 0)
317 rtcp_port = port;
318 break;
319 case SIPE_COMPONENT_NONE:
320 component = 0;
323 switch (sipe_backend_candidate_get_protocol(c)) {
324 case SIPE_NETWORK_PROTOCOL_TCP:
325 protocol = "TCP";
326 break;
327 case SIPE_NETWORK_PROTOCOL_UDP:
328 protocol = "UDP";
329 break;
332 switch (sipe_backend_candidate_get_type(c)) {
333 case SIPE_CANDIDATE_TYPE_HOST:
334 type = "host";
335 break;
336 case SIPE_CANDIDATE_TYPE_RELAY:
337 type = "relay";
338 break;
339 case SIPE_CANDIDATE_TYPE_SRFLX:
340 type = "srflx";
341 break;
342 default:
343 // TODO: error unknown/unsupported type
344 break;
347 tmp = g_strdup_printf("a=candidate:%s %u %s %u %s %d typ %s \r\n",
348 sipe_backend_candidate_get_foundation(c),
349 component,
350 protocol,
351 sipe_backend_candidate_get_priority(c),
352 sipe_backend_candidate_get_ip(c),
353 port,
354 type);
356 g_string_append(result, tmp);
357 g_free(tmp);
359 candidates = candidates->next;
362 if (remote_candidate) {
363 sipe_candidate *first = call->remote_candidates->data;
364 sipe_candidate *second = call->remote_candidates->next->data;
365 tmp = g_strdup_printf("a=remote-candidates:1 %s %u 2 %s %u\r\n",
366 sipe_backend_candidate_get_ip(first), sipe_backend_candidate_get_port(first),
367 sipe_backend_candidate_get_ip(second), sipe_backend_candidate_get_port(second));
369 g_string_append(result, tmp);
370 g_free(tmp);
374 if (rtcp_port != 0) {
375 tmp = g_strdup_printf("a=maxptime:200\r\na=rtcp:%u\r\n", rtcp_port);
376 g_string_append(result, tmp);
377 g_free(tmp);
380 return g_string_free(result, FALSE);
383 static gchar*
384 sipe_media_create_sdp(sipe_media_call *call, gboolean remote_candidate) {
385 GList *local_codecs = sipe_backend_get_local_codecs(call);
386 GList *local_candidates = sipe_backend_get_local_candidates(call, call->dialog->with);
388 // TODO: more sophisticated
389 guint16 local_port = sipe_backend_candidate_get_port(local_candidates->data);
390 const char *ip = sipe_utils_get_suitable_local_ip(-1);
392 gchar *sdp_codecs = sipe_media_sdp_codecs_format(local_codecs);
393 gchar *sdp_codec_ids = sipe_media_sdp_codec_ids_format(local_codecs);
394 gchar *sdp_candidates = sipe_media_sdp_candidates_format(local_candidates, call, remote_candidate);
395 gchar *inactive = call->state == SIPE_CALL_HELD ? "a=inactive\r\n" : "";
397 gchar *body = g_strdup_printf(
398 "v=0\r\n"
399 "o=- 0 0 IN IP4 %s\r\n"
400 "s=session\r\n"
401 "c=IN IP4 %s\r\n"
402 "b=CT:99980\r\n"
403 "t=0 0\r\n"
404 "m=audio %d RTP/AVP%s\r\n"
405 "%s"
406 "%s"
407 "%s"
408 "a=encryption:rejected\r\n"
409 ,ip, ip, local_port, sdp_codec_ids, sdp_candidates, inactive, sdp_codecs);
411 g_free(sdp_codecs);
412 g_free(sdp_codec_ids);
413 g_free(sdp_candidates);
415 return body;
418 static void
419 sipe_invite_call(struct sipe_account_data *sip)
421 gchar *hdr;
422 gchar *contact;
423 gchar *body;
424 sipe_media_call *call = sip->media_call;
425 struct sip_dialog *dialog = call->dialog;
427 contact = get_contact(sip);
428 hdr = g_strdup_printf(
429 "Supported: ms-sender\r\n"
430 "ms-keep-alive: UAC;hop-hop=yes\r\n"
431 "Contact: %s%s\r\n"
432 "Supported: Replaces\r\n"
433 "Content-Type: application/sdp\r\n",
434 contact,
435 call->state == SIPE_CALL_HELD ? ";+sip.rendering=\"no\"" : "");
436 g_free(contact);
438 body = sipe_media_create_sdp(call, TRUE);
440 send_sip_request(sip->gc, "INVITE", dialog->with, dialog->with, hdr, body,
441 dialog, NULL);
443 g_free(body);
444 g_free(hdr);
447 static void
448 notify_state_change(struct sipe_account_data *sip, gboolean local) {
449 if (local) {
450 sipe_invite_call(sip);
451 } else {
452 gchar* body = sipe_media_create_sdp(sip->media_call, TRUE);
453 send_sip_response(sip->gc, sip->media_call->invitation, 200, "OK", body);
454 g_free(body);
458 static gboolean
459 sipe_media_parse_sdp_frame(sipe_media_call* call, gchar *frame) {
460 gchar **lines = g_strsplit(frame, "\r\n", 0);
461 GSList *sdp_attrs = NULL;
462 gchar *remote_ip = NULL;
463 guint16 remote_port = 0;
464 gchar **ptr;
465 gboolean no_error = TRUE;
467 for (ptr = lines; *ptr != NULL; ++ptr) {
468 if (g_str_has_prefix(*ptr, "a=")) {
469 gchar **parts = g_strsplit(*ptr + 2, ":", 2);
470 if(!parts[0]) {
471 g_strfreev(parts);
472 sipe_utils_nameval_free(sdp_attrs);
473 sdp_attrs = NULL;
474 no_error = FALSE;
475 break;
477 sdp_attrs = sipe_utils_nameval_add(sdp_attrs, parts[0], parts[1]);
478 g_strfreev(parts);
480 } else if (g_str_has_prefix(*ptr, "o=")) {
481 gchar **parts = g_strsplit(*ptr + 2, " ", 6);
482 remote_ip = g_strdup(parts[5]);
483 g_strfreev(parts);
484 } else if (g_str_has_prefix(*ptr, "m=")) {
485 gchar **parts = g_strsplit(*ptr + 2, " ", 3);
486 remote_port = atoi(parts[1]);
487 g_strfreev(parts);
491 g_strfreev(lines);
493 if (no_error) {
494 sipe_utils_nameval_free(call->sdp_attrs);
495 call->sdp_attrs = sdp_attrs;
496 call->remote_ip = remote_ip;
497 call->remote_port = remote_port;
500 return no_error;
503 static struct sip_dialog *
504 sipe_media_dialog_init(struct sip_session* session, struct sipmsg *msg)
506 gchar *newTag = gentag();
507 const gchar *oldHeader;
508 gchar *newHeader;
509 struct sip_dialog *dialog;
511 oldHeader = sipmsg_find_header(msg, "To");
512 newHeader = g_strdup_printf("%s;tag=%s", oldHeader, newTag);
513 sipmsg_remove_header_now(msg, "To");
514 sipmsg_add_header_now(msg, "To", newHeader);
515 g_free(newHeader);
517 dialog = sipe_dialog_add(session);
518 dialog->callid = g_strdup(session->callid);
519 dialog->with = parse_from(sipmsg_find_header(msg, "From"));
520 sipe_dialog_parse(dialog, msg, FALSE);
522 return dialog;
525 static void candidates_prepared_cb(sipe_media_call *call)
527 if (!call->legacy_mode) {
528 PurpleAccount* account = purple_media_get_account(call->media);
530 if (!call->sdp_response)
531 call->sdp_response = sipe_media_create_sdp(call, FALSE);
533 send_sip_response(account->gc, call->invitation, 183, "Session Progress", call->sdp_response);
537 static void call_accept_cb(sipe_media_call *call, SIPE_UNUSED_PARAMETER gboolean local)
539 PurpleAccount* account = purple_media_get_account(call->media);
541 if (!call->sdp_response)
542 call->sdp_response = sipe_media_create_sdp(call, FALSE);
544 send_sip_response(account->gc, call->invitation, 200, "OK", call->sdp_response);
545 call->state = SIPE_CALL_RUNNING;
548 static void call_reject_cb(sipe_media_call *call, gboolean local)
550 if (local) {
551 PurpleAccount *account = purple_media_get_account(call->media);
552 send_sip_response(account->gc, call->invitation, 603, "Decline", NULL);
553 call->sip->media_call = NULL;
554 sipe_media_call_free(call);
558 static void call_hold_cb(sipe_media_call *call, gboolean local)
560 if (call->state == SIPE_CALL_HELD)
561 return;
563 call->state = SIPE_CALL_HELD;
564 notify_state_change(call->sip, local);
565 sipe_backend_media_hold(call->media, TRUE);
568 static void call_unhold_cb(sipe_media_call *call, gboolean local)
570 if (call->state == SIPE_CALL_RUNNING)
571 return;
573 call->state = SIPE_CALL_RUNNING;
574 notify_state_change(call->sip, local);
575 sipe_backend_media_unhold(call->media, TRUE);
578 static void call_hangup_cb(sipe_media_call *call, gboolean local)
580 call->state = SIPE_CALL_FINISHED;
581 if (local)
582 send_sip_request(call->sip->gc, "BYE", call->dialog->with, call->dialog->with,
583 NULL, NULL, call->dialog, NULL);
584 call->sip->media_call = NULL;
585 sipe_media_call_free(call);
588 static sipe_media_call *
589 sipe_media_call_init(struct sipmsg *msg)
591 sipe_media_call *call;
593 call = g_new0(sipe_media_call, 1);
595 if (sipe_media_parse_sdp_frame(call, msg->body) == FALSE) {
596 g_free(call);
597 return NULL;
600 call->invitation = msg;
601 call->legacy_mode = FALSE;
602 call->state = SIPE_CALL_CONNECTING;
603 call->remote_candidates = sipe_media_parse_remote_candidates(call);
605 call->candidates_prepared_cb = candidates_prepared_cb;
606 call->call_accept_cb = call_accept_cb;
607 call->call_reject_cb = call_reject_cb;
608 call->call_hold_cb = call_hold_cb;
609 call->call_unhold_cb = call_unhold_cb;
610 call->call_hangup_cb = call_hangup_cb;
612 return call;
615 void sipe_media_hold(struct sipe_account_data *sip) {
616 if (sip->media_call)
617 sipe_backend_media_hold(sip->media_call->media, FALSE);
620 void sipe_media_unhold(struct sipe_account_data *sip) {
621 if (sip->media_call)
622 sipe_backend_media_unhold(sip->media_call->media, FALSE);
625 void sipe_media_incoming_invite(struct sipe_account_data *sip, struct sipmsg *msg)
627 const gchar *callid = sipmsg_find_header(msg, "Call-ID");
629 sipe_media *media;
630 sipe_media_call *call;
631 struct sip_session *session;
632 struct sip_dialog *dialog;
634 if (sip->media_call) {
635 if (sipe_strequal(sip->media_call->dialog->callid, callid)) {
636 gchar *rsp;
638 call = sip->media_call;
640 sipmsg_free(call->invitation);
641 msg->dont_free = TRUE;
642 call->invitation = msg;
644 sipmsg_add_header(msg, "Supported", "Replaces");
646 sipe_utils_nameval_free(call->sdp_attrs);
647 call->sdp_attrs = NULL;
648 if (!sipe_media_parse_sdp_frame(call, msg->body)) {
649 // TODO: handle error
652 if (call->legacy_mode && call->state == SIPE_CALL_RUNNING) {
653 sipe_media_hold(sip);
654 return;
657 if (sipe_utils_nameval_find(call->sdp_attrs, "inactive")) {
658 sipe_media_hold(sip);
659 return;
662 if (call->state == SIPE_CALL_HELD) {
663 sipe_media_unhold(sip);
664 return;
667 call->remote_codecs = sipe_media_parse_remote_codecs(call);
668 call->remote_codecs = sipe_media_prune_remote_codecs(call, call->remote_codecs);
669 if (!call->remote_codecs) {
670 // TODO: error no remote codecs
672 if (sipe_backend_set_remote_codecs(call, call->dialog->with) == FALSE)
673 printf("ERROR SET REMOTE CODECS"); // TODO
675 rsp = sipe_media_create_sdp(sip->media_call, TRUE);
676 send_sip_response(sip->gc, msg, 200, "OK", rsp);
677 g_free(rsp);
678 } else {
679 // TODO: send Busy Here
680 printf("MEDIA SESSION ALREADY IN PROGRESS");
682 return;
685 call = sipe_media_call_init(msg);
687 session = sipe_session_find_or_add_chat_by_callid(sip, callid);
688 dialog = sipe_media_dialog_init(session, msg);
690 media = sipe_backend_media_new(call, sip->account, dialog->with, FALSE);
692 call->sip = sip;
693 call->session = session;
694 call->dialog = dialog;
695 call->media = media;
697 sipe_backend_media_add_stream(media, dialog->with, SIPE_MEDIA_AUDIO, !call->legacy_mode, FALSE);
699 purple_media_add_remote_candidates((PurpleMedia*)media, "sipe-voice", dialog->with,
700 call->remote_candidates);
702 call->remote_codecs = sipe_media_parse_remote_codecs(call);
703 call->remote_codecs = sipe_media_prune_remote_codecs(call, call->remote_codecs);
704 if (!call->remote_candidates || !call->remote_codecs) {
705 sipe_media_call_free(call);
706 sip->media_call = NULL;
707 printf("ERROR NO CANDIDATES OR CODECS");
708 return;
710 if (sipe_backend_set_remote_codecs(call, dialog->with) == FALSE)
711 printf("ERROR SET REMOTE CODECS"); // TODO
713 sip->media_call = call;
715 // TODO: copy message instead of this don't free thing
716 msg->dont_free = TRUE;
717 send_sip_response(sip->gc, msg, 180, "Ringing", NULL);
720 void sipe_media_hangup(struct sipe_account_data *sip)
722 if (sip->media_call)
723 sipe_backend_media_hangup(sip->media_call->media, FALSE);
727 Local Variables:
728 mode: c
729 c-file-style: "bsd"
730 indent-tabs-mode: t
731 tab-width: 8
732 End: