Commit 1bc6f63d authored by Simon Morlat's avatar Simon Morlat

media_localip is guessed from signaling using call->dest_proxy. This ease the...

media_localip is guessed from signaling using call->dest_proxy. This ease the use of liblinphone on a machine with two interfaces (typically a one being a vpn).
Clean things around sal.c and sal_impl.c
parent 545d9ac0
......@@ -1300,4 +1300,138 @@ int sal_get_http_proxy_port(const Sal *sal) {
return belle_sip_stack_get_http_proxy_port(sal->stack);
}
static belle_sip_header_t * sal_body_handler_find_header(const SalBodyHandler *body_handler, const char *header_name) {
belle_sip_body_handler_t *bsbh = BELLE_SIP_BODY_HANDLER(body_handler);
const belle_sip_list_t *l = belle_sip_body_handler_get_headers(bsbh);
for (; l != NULL; l = l->next) {
belle_sip_header_t *header = BELLE_SIP_HEADER(l->data);
if (strcmp(belle_sip_header_get_name(header), header_name) == 0) {
return header;
}
}
return NULL;
}
SalBodyHandler * sal_body_handler_new(void) {
belle_sip_memory_body_handler_t *body_handler = belle_sip_memory_body_handler_new(NULL, NULL);
return (SalBodyHandler *)BELLE_SIP_BODY_HANDLER(body_handler);
}
SalBodyHandler * sal_body_handler_ref(SalBodyHandler *body_handler) {
return (SalBodyHandler *)belle_sip_object_ref(BELLE_SIP_OBJECT(body_handler));
}
void sal_body_handler_unref(SalBodyHandler *body_handler) {
belle_sip_object_unref(BELLE_SIP_OBJECT(body_handler));
}
const char * sal_body_handler_get_type(const SalBodyHandler *body_handler) {
belle_sip_header_content_type_t *content_type = BELLE_SIP_HEADER_CONTENT_TYPE(sal_body_handler_find_header(body_handler, "Content-Type"));
if (content_type != NULL) {
return belle_sip_header_content_type_get_type(content_type);
}
return NULL;
}
void sal_body_handler_set_type(SalBodyHandler *body_handler, const char *type) {
belle_sip_header_content_type_t *content_type = BELLE_SIP_HEADER_CONTENT_TYPE(sal_body_handler_find_header(body_handler, "Content-Type"));
if (content_type == NULL) {
content_type = belle_sip_header_content_type_new();
belle_sip_body_handler_add_header(BELLE_SIP_BODY_HANDLER(body_handler), BELLE_SIP_HEADER(content_type));
}
belle_sip_header_content_type_set_type(content_type, type);
}
const char * sal_body_handler_get_subtype(const SalBodyHandler *body_handler) {
belle_sip_header_content_type_t *content_type = BELLE_SIP_HEADER_CONTENT_TYPE(sal_body_handler_find_header(body_handler, "Content-Type"));
if (content_type != NULL) {
return belle_sip_header_content_type_get_subtype(content_type);
}
return NULL;
}
void sal_body_handler_set_subtype(SalBodyHandler *body_handler, const char *subtype) {
belle_sip_header_content_type_t *content_type = BELLE_SIP_HEADER_CONTENT_TYPE(sal_body_handler_find_header(body_handler, "Content-Type"));
if (content_type == NULL) {
content_type = belle_sip_header_content_type_new();
belle_sip_body_handler_add_header(BELLE_SIP_BODY_HANDLER(body_handler), BELLE_SIP_HEADER(content_type));
}
belle_sip_header_content_type_set_subtype(content_type, subtype);
}
const char * sal_body_handler_get_encoding(const SalBodyHandler *body_handler) {
belle_sip_header_t *content_encoding = sal_body_handler_find_header(body_handler, "Content-Encoding");
if (content_encoding != NULL) {
return belle_sip_header_get_unparsed_value(content_encoding);
}
return NULL;
}
void sal_body_handler_set_encoding(SalBodyHandler *body_handler, const char *encoding) {
belle_sip_header_t *content_encoding = sal_body_handler_find_header(body_handler, "Content-Encoding");
if (content_encoding != NULL) {
belle_sip_body_handler_remove_header_from_ptr(BELLE_SIP_BODY_HANDLER(body_handler), content_encoding);
}
belle_sip_body_handler_add_header(BELLE_SIP_BODY_HANDLER(body_handler), belle_sip_header_create("Content-Encoding", encoding));
}
void * sal_body_handler_get_data(const SalBodyHandler *body_handler) {
return belle_sip_memory_body_handler_get_buffer(BELLE_SIP_MEMORY_BODY_HANDLER(body_handler));
}
void sal_body_handler_set_data(SalBodyHandler *body_handler, void *data) {
belle_sip_memory_body_handler_set_buffer(BELLE_SIP_MEMORY_BODY_HANDLER(body_handler), data);
}
size_t sal_body_handler_get_size(const SalBodyHandler *body_handler) {
return belle_sip_body_handler_get_size(BELLE_SIP_BODY_HANDLER(body_handler));
}
void sal_body_handler_set_size(SalBodyHandler *body_handler, size_t size) {
belle_sip_header_content_length_t *content_length = BELLE_SIP_HEADER_CONTENT_LENGTH(sal_body_handler_find_header(body_handler, "Content-Length"));
if (content_length == NULL) {
content_length = belle_sip_header_content_length_new();
belle_sip_body_handler_add_header(BELLE_SIP_BODY_HANDLER(body_handler), BELLE_SIP_HEADER(content_length));
}
belle_sip_header_content_length_set_content_length(content_length, size);
belle_sip_body_handler_set_size(BELLE_SIP_BODY_HANDLER(body_handler), size);
}
bool_t sal_body_handler_is_multipart(const SalBodyHandler *body_handler) {
if (BELLE_SIP_IS_INSTANCE_OF(body_handler, belle_sip_multipart_body_handler_t)) return TRUE;
return FALSE;
}
SalBodyHandler * sal_body_handler_get_part(const SalBodyHandler *body_handler, int idx) {
const belle_sip_list_t *l = belle_sip_multipart_body_handler_get_parts(BELLE_SIP_MULTIPART_BODY_HANDLER(body_handler));
return (SalBodyHandler *)belle_sip_list_nth_data(l, idx);
}
SalBodyHandler * sal_body_handler_find_part_by_header(const SalBodyHandler *body_handler, const char *header_name, const char *header_value) {
const belle_sip_list_t *l = belle_sip_multipart_body_handler_get_parts(BELLE_SIP_MULTIPART_BODY_HANDLER(body_handler));
for (; l != NULL; l = l->next) {
belle_sip_body_handler_t *bsbh = BELLE_SIP_BODY_HANDLER(l->data);
const belle_sip_list_t *headers = belle_sip_body_handler_get_headers(bsbh);
for (; headers != NULL; headers = headers->next) {
belle_sip_header_t *header = BELLE_SIP_HEADER(headers->data);
if ((strcmp(belle_sip_header_get_name(header), header_name) == 0) && (strcmp(belle_sip_header_get_unparsed_value(header), header_value) == 0)) {
return (SalBodyHandler *)bsbh;
}
}
}
return NULL;
}
const char * sal_body_handler_get_header(const SalBodyHandler *body_handler, const char *header_name) {
belle_sip_header_t *header = sal_body_handler_find_header(body_handler, header_name);
if (header != NULL) {
return belle_sip_header_get_unparsed_value(header);
}
return NULL;
}
void *sal_get_stack_impl(Sal *sal) {
return sal->stack;
}
......@@ -821,3 +821,26 @@ void sal_op_set_event(SalOp *op, const char *eventname){
}
op->event = header;
}
const char* sal_op_get_public_address(SalOp *op, int *port) {
if (op && op->refresher) {
return belle_sip_refresher_get_public_address(op->refresher, port);
}
return NULL;
}
const char* sal_op_get_local_address(SalOp *op, int *port) {
if (op && op->refresher) {
return belle_sip_refresher_get_local_address(op->refresher, port);
}
return NULL;
}
char* sal_op_get_dialog_id(const SalOp *op) {
if (op->dialog != NULL) {
return ms_strdup_printf("%s;to-tag=%s;from-tag=%s", ((SalOpBase*)op)->call_id,
belle_sip_dialog_get_remote_tag(op->dialog), belle_sip_dialog_get_local_tag(op->dialog));
}
return NULL;
}
......@@ -1048,13 +1048,44 @@ static void linphone_call_outgoing_select_ip_version(LinphoneCall *call, Linphon
* Fill the local ip that routes to the internet according to the destination, or guess it by other special means (upnp).
*/
static void linphone_call_get_local_ip(LinphoneCall *call, const LinphoneAddress *remote_addr){
const char *ip;
const char *ip = NULL;
int af = call->af;
const char *dest = NULL;
if (linphone_core_get_firewall_policy(call->core)==LinphonePolicyUseNatAddress
&& (ip=linphone_core_get_nat_address_resolved(call->core))!=NULL){
strncpy(call->media_localip,ip,LINPHONE_IPADDR_SIZE);
return;
}
#ifdef BUILD_UPNP
else if (call->core->upnp != NULL && linphone_core_get_firewall_policy(call->core)==LinphonePolicyUseUpnp &&
linphone_upnp_context_get_state(call->core->upnp) == LinphoneUpnpStateOk) {
ip = linphone_upnp_context_get_external_ipaddress(call->core->upnp);
strncpy(call->media_localip,ip,LINPHONE_IPADDR_SIZE);
goto found;
}
#endif //BUILD_UPNP
/*next, sometime, override from config*/
if ((ip=lp_config_get_string(call->core->config,"rtp","bind_address",NULL)) != NULL)
goto found;
/*if a known proxy was identified for this call, then we may have a chance to take the local ip address
* from the socket that connect to this proxy */
if (call->dest_proxy && call->dest_proxy->op){
if ((ip = sal_op_get_local_address(call->dest_proxy->op, NULL)) != NULL){
ms_message("Found media local-ip from signaling.");
goto found;
}
}
/*in last resort, attempt to find the local ip that routes to destination if given as an IP address,
or the default route (dest=NULL)*/
if (call->dest_proxy == NULL) {
struct addrinfo hints;
struct addrinfo *res = NULL;
int err;
/*FIXME the following doesn't work for IPv6 address because of brakets*/
const char *domain = linphone_address_get_domain(remote_addr);
memset(&hints, 0, sizeof(hints));
hints.ai_family = AF_UNSPEC;
......@@ -1066,27 +1097,11 @@ static void linphone_call_get_local_ip(LinphoneCall *call, const LinphoneAddress
}
if (res != NULL) freeaddrinfo(res);
}
if (linphone_core_get_firewall_policy(call->core)==LinphonePolicyUseNatAddress
&& (ip=linphone_core_get_nat_address_resolved(call->core))!=NULL){
strncpy(call->media_localip,ip,LINPHONE_IPADDR_SIZE);
return;
}
#ifdef BUILD_UPNP
else if (call->core->upnp != NULL && linphone_core_get_firewall_policy(call->core)==LinphonePolicyUseUpnp &&
linphone_upnp_context_get_state(call->core->upnp) == LinphoneUpnpStateOk) {
ip = linphone_upnp_context_get_external_ipaddress(call->core->upnp);
strncpy(call->media_localip,ip,LINPHONE_IPADDR_SIZE);
return;
}
#endif //BUILD_UPNP
/*first nominal use case*/
/*the following cannot fail and puts result directly in media_localip*/
linphone_core_get_local_ip(call->core, af, dest, call->media_localip);
/*next, sometime, override from config*/
if ((ip=lp_config_get_string(call->core->config,"rtp","bind_address",NULL)))
strncpy(call->media_localip,ip,LINPHONE_IPADDR_SIZE);
return;
found:
strncpy(call->media_localip,ip,LINPHONE_IPADDR_SIZE);
}
static void linphone_call_destroy(LinphoneCall *obj);
......@@ -1140,6 +1155,7 @@ LinphoneCall * linphone_call_new_outgoing(struct _LinphoneCore *lc, LinphoneAddr
call->dir=LinphoneCallOutgoing;
call->core=lc;
call->dest_proxy=cfg;
linphone_call_outgoing_select_ip_version(call,to,cfg);
linphone_call_get_local_ip(call, to);
call->params = linphone_call_params_copy(params);
......@@ -1166,7 +1182,7 @@ LinphoneCall * linphone_call_new_outgoing(struct _LinphoneCore *lc, LinphoneAddr
if (params->referer){
call->referer=linphone_call_ref(params->referer);
}
call->dest_proxy=cfg;
linphone_call_create_op(call);
return call;
}
......
......@@ -1816,7 +1816,7 @@ static void linphone_core_init(LinphoneCore * lc, const LinphoneCoreVTable *vtab
/* Create the http provider in dual stack mode (ipv4 and ipv6.
* If this creates problem, we may need to implement parallel ipv6/ ipv4 http requests in belle-sip.
*/
lc->http_provider = belle_sip_stack_create_http_provider(sal_get_belle_sip_stack(lc->sal), "::0");
lc->http_provider = belle_sip_stack_create_http_provider(sal_get_stack_impl(lc->sal), "::0");
lc->http_crypto_config = belle_tls_crypto_config_new();
belle_http_provider_set_tls_crypto_config(lc->http_provider,lc->http_crypto_config);
......
......@@ -37,10 +37,10 @@
#include "ringtoneplayer.h"
#include "vcard.h"
#include <bctoolbox/port.h>
#include <bctoolbox/vfs.h>
#include <belle-sip/object.h>
#include <belle-sip/dict.h>
#include "bctoolbox/port.h"
#include "bctoolbox/vfs.h"
#include "belle-sip/belle-sip.h" /*we need this include for all http operations*/
#include <ctype.h>
......
......@@ -18,15 +18,13 @@ Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
*/
/**
This header files defines the Signaling Abstraction Layer.
The purpose of this layer is too allow experiment different call signaling
protocols and implementations under linphone, for example SIP, JINGLE...
This file contains SAL API functions that do not depend on the underlying implementation (like belle-sip).
**/
#ifdef HAVE_CONFIG_H
#include "config.h"
#endif
#include "sal/sal.h"
#include "bellesip_sal/sal_impl.h"
#include <ctype.h>
......@@ -619,14 +617,7 @@ const char *sal_op_get_network_origin(const SalOp *op){
const char* sal_op_get_call_id(const SalOp *op) {
return ((SalOpBase*)op)->call_id;
}
char* sal_op_get_dialog_id(const SalOp *op) {
if (op->dialog != NULL) {
return ms_strdup_printf("%s;to-tag=%s;from-tag=%s", ((SalOpBase*)op)->call_id,
belle_sip_dialog_get_remote_tag(op->dialog), belle_sip_dialog_get_local_tag(op->dialog));
}
return NULL;
}
void __sal_op_init(SalOp *b, Sal *sal){
memset(b,0,sizeof(SalOpBase));
((SalOpBase*)b)->root=sal;
......@@ -897,150 +888,12 @@ int sal_lines_get_value(const char *data, const char *key, char *value, size_t v
return FALSE;
}
static belle_sip_header_t * sal_body_handler_find_header(const SalBodyHandler *body_handler, const char *header_name) {
belle_sip_body_handler_t *bsbh = BELLE_SIP_BODY_HANDLER(body_handler);
const belle_sip_list_t *l = belle_sip_body_handler_get_headers(bsbh);
for (; l != NULL; l = l->next) {
belle_sip_header_t *header = BELLE_SIP_HEADER(l->data);
if (strcmp(belle_sip_header_get_name(header), header_name) == 0) {
return header;
}
}
return NULL;
}
SalBodyHandler * sal_body_handler_new(void) {
belle_sip_memory_body_handler_t *body_handler = belle_sip_memory_body_handler_new(NULL, NULL);
return (SalBodyHandler *)BELLE_SIP_BODY_HANDLER(body_handler);
}
SalBodyHandler * sal_body_handler_ref(SalBodyHandler *body_handler) {
return (SalBodyHandler *)belle_sip_object_ref(BELLE_SIP_OBJECT(body_handler));
}
void sal_body_handler_unref(SalBodyHandler *body_handler) {
belle_sip_object_unref(BELLE_SIP_OBJECT(body_handler));
}
const char * sal_body_handler_get_type(const SalBodyHandler *body_handler) {
belle_sip_header_content_type_t *content_type = BELLE_SIP_HEADER_CONTENT_TYPE(sal_body_handler_find_header(body_handler, "Content-Type"));
if (content_type != NULL) {
return belle_sip_header_content_type_get_type(content_type);
}
return NULL;
}
void sal_body_handler_set_type(SalBodyHandler *body_handler, const char *type) {
belle_sip_header_content_type_t *content_type = BELLE_SIP_HEADER_CONTENT_TYPE(sal_body_handler_find_header(body_handler, "Content-Type"));
if (content_type == NULL) {
content_type = belle_sip_header_content_type_new();
belle_sip_body_handler_add_header(BELLE_SIP_BODY_HANDLER(body_handler), BELLE_SIP_HEADER(content_type));
}
belle_sip_header_content_type_set_type(content_type, type);
}
const char * sal_body_handler_get_subtype(const SalBodyHandler *body_handler) {
belle_sip_header_content_type_t *content_type = BELLE_SIP_HEADER_CONTENT_TYPE(sal_body_handler_find_header(body_handler, "Content-Type"));
if (content_type != NULL) {
return belle_sip_header_content_type_get_subtype(content_type);
}
return NULL;
}
void sal_body_handler_set_subtype(SalBodyHandler *body_handler, const char *subtype) {
belle_sip_header_content_type_t *content_type = BELLE_SIP_HEADER_CONTENT_TYPE(sal_body_handler_find_header(body_handler, "Content-Type"));
if (content_type == NULL) {
content_type = belle_sip_header_content_type_new();
belle_sip_body_handler_add_header(BELLE_SIP_BODY_HANDLER(body_handler), BELLE_SIP_HEADER(content_type));
}
belle_sip_header_content_type_set_subtype(content_type, subtype);
}
const char * sal_body_handler_get_encoding(const SalBodyHandler *body_handler) {
belle_sip_header_t *content_encoding = sal_body_handler_find_header(body_handler, "Content-Encoding");
if (content_encoding != NULL) {
return belle_sip_header_get_unparsed_value(content_encoding);
}
return NULL;
}
void sal_body_handler_set_encoding(SalBodyHandler *body_handler, const char *encoding) {
belle_sip_header_t *content_encoding = sal_body_handler_find_header(body_handler, "Content-Encoding");
if (content_encoding != NULL) {
belle_sip_body_handler_remove_header_from_ptr(BELLE_SIP_BODY_HANDLER(body_handler), content_encoding);
}
belle_sip_body_handler_add_header(BELLE_SIP_BODY_HANDLER(body_handler), belle_sip_header_create("Content-Encoding", encoding));
}
void * sal_body_handler_get_data(const SalBodyHandler *body_handler) {
return belle_sip_memory_body_handler_get_buffer(BELLE_SIP_MEMORY_BODY_HANDLER(body_handler));
}
void sal_body_handler_set_data(SalBodyHandler *body_handler, void *data) {
belle_sip_memory_body_handler_set_buffer(BELLE_SIP_MEMORY_BODY_HANDLER(body_handler), data);
}
size_t sal_body_handler_get_size(const SalBodyHandler *body_handler) {
return belle_sip_body_handler_get_size(BELLE_SIP_BODY_HANDLER(body_handler));
}
void sal_body_handler_set_size(SalBodyHandler *body_handler, size_t size) {
belle_sip_header_content_length_t *content_length = BELLE_SIP_HEADER_CONTENT_LENGTH(sal_body_handler_find_header(body_handler, "Content-Length"));
if (content_length == NULL) {
content_length = belle_sip_header_content_length_new();
belle_sip_body_handler_add_header(BELLE_SIP_BODY_HANDLER(body_handler), BELLE_SIP_HEADER(content_length));
}
belle_sip_header_content_length_set_content_length(content_length, size);
belle_sip_body_handler_set_size(BELLE_SIP_BODY_HANDLER(body_handler), size);
}
bool_t sal_body_handler_is_multipart(const SalBodyHandler *body_handler) {
if (BELLE_SIP_IS_INSTANCE_OF(body_handler, belle_sip_multipart_body_handler_t)) return TRUE;
return FALSE;
}
SalBodyHandler * sal_body_handler_get_part(const SalBodyHandler *body_handler, int idx) {
const belle_sip_list_t *l = belle_sip_multipart_body_handler_get_parts(BELLE_SIP_MULTIPART_BODY_HANDLER(body_handler));
return (SalBodyHandler *)belle_sip_list_nth_data(l, idx);
}
SalBodyHandler * sal_body_handler_find_part_by_header(const SalBodyHandler *body_handler, const char *header_name, const char *header_value) {
const belle_sip_list_t *l = belle_sip_multipart_body_handler_get_parts(BELLE_SIP_MULTIPART_BODY_HANDLER(body_handler));
for (; l != NULL; l = l->next) {
belle_sip_body_handler_t *bsbh = BELLE_SIP_BODY_HANDLER(l->data);
const belle_sip_list_t *headers = belle_sip_body_handler_get_headers(bsbh);
for (; headers != NULL; headers = headers->next) {
belle_sip_header_t *header = BELLE_SIP_HEADER(headers->data);
if ((strcmp(belle_sip_header_get_name(header), header_name) == 0) && (strcmp(belle_sip_header_get_unparsed_value(header), header_value) == 0)) {
return (SalBodyHandler *)bsbh;
}
}
}
return NULL;
}
const char * sal_body_handler_get_header(const SalBodyHandler *body_handler, const char *header_name) {
belle_sip_header_t *header = sal_body_handler_find_header(body_handler, header_name);
if (header != NULL) {
return belle_sip_header_get_unparsed_value(header);
}
return NULL;
}
belle_sip_stack_t *sal_get_belle_sip_stack(Sal *sal) {
return sal->stack;
}
char* sal_op_get_public_uri(SalOp *op) {
if (op && op->refresher) {
return belle_sip_refresher_get_public_uri(op->refresher);
}
return NULL;
}
const char *sal_op_get_entity_tag(const SalOp* op) {
SalOpBase* op_base = (SalOpBase*)op;
return op_base->entity_tag;
}
void sal_op_set_entity_tag(SalOp *op, const char* entity_tag) {
SalOpBase* op_base = (SalOpBase*)op;
if (op_base->entity_tag != NULL){
......@@ -1051,3 +904,7 @@ void sal_op_set_entity_tag(SalOp *op, const char* entity_tag) {
else
op_base->entity_tag = NULL;
}
#ifdef BELLE_SIP_H
#error "You included belle-sip header other than just belle-sip/object.h in sal.c. This breaks design rules !"
#endif
......@@ -32,7 +32,8 @@ Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
#include "mediastreamer2/mediastream.h"
#include "ortp/rtpsession.h"
#include "belle-sip/belle-sip.h"
#include "belle-sip/object.h"
#include "belle-sip/mainloop.h"
#ifndef LINPHONE_PUBLIC
#define LINPHONE_PUBLIC MS2_PUBLIC
......@@ -906,8 +907,9 @@ const char * sal_body_handler_get_header(const SalBodyHandler *body_handler, con
/*this function parses a document with key=value pairs separated by new lines, and extracts the value for a given key*/
int sal_lines_get_value(const char *data, const char *key, char *value, size_t value_size);
LINPHONE_PUBLIC belle_sip_stack_t *sal_get_belle_sip_stack(Sal *sal);
char* sal_op_get_public_uri(SalOp *sal);
LINPHONE_PUBLIC void *sal_get_stack_impl(Sal *sal);
const char* sal_op_get_public_address(SalOp *sal, int *port);
const char* sal_op_get_local_address(SalOp *sal, int *port);
unsigned long sal_begin_background_task(const char *name, void (*max_time_reached)(void *), void *data);
void sal_end_background_task(unsigned long id);
......
mediastreamer2 @ cdc9e006
Subproject commit 16f293049e14e80ec8bc6e7e9ce240284b328a40
Subproject commit cdc9e006e05344b15639c01369c98be85bbad615
oRTP @ 574fb8b0
Subproject commit d33db729e2d4d7ce9db0b4df935c742f16486f94
Subproject commit 574fb8b0a21b8b2fb6ff5e093d99d92519d47583
......@@ -595,7 +595,7 @@ static void call_with_timeouted_bye(void) {
timer_config.T3=0;
timer_config.T4=5000;
belle_sip_stack_set_timer_config(sal_get_belle_sip_stack(pauline->lc->sal),&timer_config);
belle_sip_stack_set_timer_config(sal_get_stack_impl(pauline->lc->sal),&timer_config);
linphone_core_terminate_all_calls(pauline->lc);
BC_ASSERT_TRUE(wait_for(pauline->lc,marie->lc,&pauline->stat.number_of_LinphoneCallEnd,1));
......
Markdown is supported
0% or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment