diff --git a/build/android/Android.mk b/build/android/Android.mk
index 18fbbdbfd..d815a11a9 100755
--- a/build/android/Android.mk
+++ b/build/android/Android.mk
@@ -64,7 +64,8 @@ LOCAL_SRC_FILES := \
xml.c \
xml2lpc.c \
lpc2xml.c \
- remote_provisioning.c
+ remote_provisioning.c \
+ quality_reporting.c
ifndef LINPHONE_VERSION
LINPHONE_VERSION = "Devel"
diff --git a/build/vsx/LibLinphone/LibLinphone.vcxproj b/build/vsx/LibLinphone/LibLinphone.vcxproj
index 9bc9aadf9..bc1f56695 100644
--- a/build/vsx/LibLinphone/LibLinphone.vcxproj
+++ b/build/vsx/LibLinphone/LibLinphone.vcxproj
@@ -203,6 +203,7 @@
+
@@ -275,4 +276,4 @@
-
\ No newline at end of file
+
diff --git a/coreapi/CMakeLists.txt b/coreapi/CMakeLists.txt
index 8b2650950..05ae3d811 100644
--- a/coreapi/CMakeLists.txt
+++ b/coreapi/CMakeLists.txt
@@ -74,6 +74,7 @@ set(SOURCE_FILES
offeranswer.c
presence.c
proxy.c
+ quality_reporting.c
remote_provisioning.c
sal.c
siplogin.c
diff --git a/coreapi/Makefile.am b/coreapi/Makefile.am
index 68d78447f..5edc54059 100644
--- a/coreapi/Makefile.am
+++ b/coreapi/Makefile.am
@@ -56,6 +56,7 @@ liblinphone_la_SOURCES=\
xml2lpc.c \
lpc2xml.c \
remote_provisioning.c \
+ quality_reporting.c \
$(GITVERSION_FILE)
if BUILD_UPNP
diff --git a/coreapi/linphonecall.c b/coreapi/linphonecall.c
index 8167f2425..2519bc687 100644
--- a/coreapi/linphonecall.c
+++ b/coreapi/linphonecall.c
@@ -393,7 +393,7 @@ static int find_port_offset(LinphoneCore *lc, int stream_index, int base_port){
int tried_port;
int existing_port;
bool_t already_used=FALSE;
-
+
for(offset=0;offset<100;offset+=2){
tried_port=base_port+offset;
already_used=FALSE;
@@ -480,10 +480,10 @@ static void linphone_call_init_common(LinphoneCall *call, LinphoneAddress *from,
linphone_core_get_audio_port_range(call->core, &min_port, &max_port);
port_config_set(call,0,min_port,max_port);
-
+
linphone_core_get_video_port_range(call->core, &min_port, &max_port);
port_config_set(call,1,min_port,max_port);
-
+
linphone_call_init_stats(&call->stats[LINPHONE_CALL_STATS_AUDIO], LINPHONE_CALL_STATS_AUDIO);
linphone_call_init_stats(&call->stats[LINPHONE_CALL_STATS_VIDEO], LINPHONE_CALL_STATS_VIDEO);
}
@@ -768,7 +768,6 @@ void linphone_call_set_state(LinphoneCall *call, LinphoneCallState cstate, const
return;
}
}
-
ms_message("Call %p: moving from state %s to %s",call,linphone_call_state_to_string(call->state),
linphone_call_state_to_string(cstate));
@@ -777,6 +776,7 @@ void linphone_call_set_state(LinphoneCall *call, LinphoneCallState cstate, const
Indeed it does not change the state of the call (still paused or running)*/
call->state=cstate;
}
+
if (cstate==LinphoneCallEnd || cstate==LinphoneCallError){
switch(call->non_op_error.reason){
case SalReasonDeclined:
@@ -795,9 +795,17 @@ void linphone_call_set_state(LinphoneCall *call, LinphoneCallState cstate, const
call->media_start_time=time(NULL);
}
+ if (cstate == LinphoneCallStreamsRunning) {
+ linphone_reporting_update_ip(call);
+ }
+
if (lc->vtable.call_state_changed)
lc->vtable.call_state_changed(lc,call,cstate,message);
if (cstate==LinphoneCallReleased){
+
+ if (call->log->status == LinphoneCallSuccess)
+ linphone_reporting_publish(call);
+
if (call->op!=NULL) {
/*transfer the last error so that it can be obtained even in Released state*/
if (call->non_op_error.reason==SalReasonNone){
@@ -1301,7 +1309,7 @@ const char *linphone_call_params_get_session_name(const LinphoneCallParams *cp){
/**
* Set the session name of the media session (ie in SDP). Subject from the SIP message (which is different) can be set using linphone_call_params_set_custom_header().
* @param cp the call parameters.
- * @param name the session name
+ * @param name the session name
**/
void linphone_call_params_set_session_name(LinphoneCallParams *cp, const char *name){
if (cp->session_name){
@@ -1451,17 +1459,17 @@ static void _linphone_call_prepare_ice_for_stream(LinphoneCall *call, int stream
int linphone_call_prepare_ice(LinphoneCall *call, bool_t incoming_offer){
SalMediaDescription *remote;
bool_t has_video=FALSE;
-
+
if ((linphone_core_get_firewall_policy(call->core) == LinphonePolicyUseIce) && (call->ice_session != NULL)){
if (incoming_offer){
remote=sal_call_get_remote_media_description(call->op);
has_video=linphone_core_media_description_contains_video_stream(remote);
}else has_video=call->params.has_video;
-
+
_linphone_call_prepare_ice_for_stream(call,0,TRUE);
if (has_video) _linphone_call_prepare_ice_for_stream(call,1,TRUE);
/*start ICE gathering*/
- if (incoming_offer)
+ if (incoming_offer)
linphone_core_update_ice_from_remote_media_description(call,remote); /*this may delete the ice session*/
if (call->ice_session && !ice_session_candidates_gathered(call->ice_session)){
if (call->audiostream->ms.state==MSStreamInitialized)
@@ -1536,7 +1544,7 @@ void linphone_call_init_audio_stream(LinphoneCall *call){
call->audiostream_app_evq = ortp_ev_queue_new();
rtp_session_register_event_queue(audiostream->ms.sessions.rtp_session,call->audiostream_app_evq);
-
+
_linphone_call_prepare_ice_for_stream(call,0,FALSE);
}
@@ -1548,7 +1556,7 @@ void linphone_call_init_video_stream(LinphoneCall *call){
int video_recv_buf_size=lp_config_get_int(lc->config,"video","recv_buf_size",0);
int dscp=linphone_core_get_video_dscp(lc);
const char *display_filter=linphone_core_get_video_display_filter(lc);
-
+
if (call->sessions[1].rtp_session==NULL){
call->videostream=video_stream_new(call->media_ports[1].rtp_port,call->media_ports[1].rtcp_port, call->af==AF_INET6);
}else{
@@ -1956,7 +1964,7 @@ static void linphone_call_start_video_stream(LinphoneCall *call, const char *cna
#ifdef VIDEO_ENABLED
LinphoneCore *lc=call->core;
int used_pt=-1;
-
+
/* look for savp stream first */
const SalStreamDescription *vstream=sal_media_description_find_stream(call->resultdesc,
SalProtoRtpSavp,SalVideo);
@@ -1978,8 +1986,9 @@ static void linphone_call_start_video_stream(LinphoneCall *call, const char *cna
const char *rtp_addr=vstream->rtp_addr[0]!='\0' ? vstream->rtp_addr : call->resultdesc->addr;
const char *rtcp_addr=vstream->rtcp_addr[0]!='\0' ? vstream->rtcp_addr : call->resultdesc->addr;
const SalStreamDescription *local_st_desc=sal_media_description_find_stream(call->localdesc,vstream->proto,SalVideo);
-
+
call->video_profile=make_profile(call,call->resultdesc,vstream,&used_pt);
+
if (used_pt!=-1){
VideoStreamDir dir=VideoStreamSendRecv;
MSWebCam *cam=lc->video_conf.device;
@@ -2143,7 +2152,7 @@ void linphone_call_update_crypto_parameters(LinphoneCall *call, SalMediaDescript
SalStreamDescription *old_stream;
SalStreamDescription *new_stream;
const SalStreamDescription *local_st_desc;
-
+
local_st_desc = sal_media_description_find_stream(call->localdesc, SalProtoRtpSavp, SalAudio);
old_stream = sal_media_description_find_stream(old_md, SalProtoRtpSavp, SalAudio);
new_stream = sal_media_description_find_stream(new_md, SalProtoRtpSavp, SalAudio);
@@ -2202,6 +2211,7 @@ static void linphone_call_log_fill_stats(LinphoneCallLog *log, MediaStream *st){
void linphone_call_stop_audio_stream(LinphoneCall *call) {
if (call->audiostream!=NULL) {
+ linphone_reporting_update(call, LINPHONE_CALL_STATS_AUDIO);
media_stream_reclaim_sessions(&call->audiostream->ms,&call->sessions[0]);
rtp_session_unregister_event_queue(call->audiostream->ms.sessions.rtp_session,call->audiostream_app_evq);
ortp_ev_queue_flush(call->audiostream_app_evq);
@@ -2230,6 +2240,7 @@ void linphone_call_stop_audio_stream(LinphoneCall *call) {
void linphone_call_stop_video_stream(LinphoneCall *call) {
#ifdef VIDEO_ENABLED
if (call->videostream!=NULL){
+ linphone_reporting_update(call, LINPHONE_CALL_STATS_VIDEO);
media_stream_reclaim_sessions(&call->videostream->ms,&call->sessions[1]);
rtp_session_unregister_event_queue(call->videostream->ms.sessions.rtp_session,call->videostream_app_evq);
ortp_ev_queue_flush(call->videostream_app_evq);
@@ -2756,6 +2767,8 @@ void linphone_call_background_tasks(LinphoneCall *call, bool_t one_second_elapse
evd->packet = NULL;
call->stats[LINPHONE_CALL_STATS_VIDEO].updated = LINPHONE_CALL_STATS_RECEIVED_RTCP_UPDATE;
update_local_stats(&call->stats[LINPHONE_CALL_STATS_VIDEO],(MediaStream*)call->videostream);
+ if (linphone_call_params_video_enabled(linphone_call_get_current_params(call)))
+ linphone_reporting_call_stats_updated(call, LINPHONE_CALL_STATS_VIDEO);
if (lc->vtable.call_stats_updated)
lc->vtable.call_stats_updated(lc, call, &call->stats[LINPHONE_CALL_STATS_VIDEO]);
} else if (evt == ORTP_EVENT_RTCP_PACKET_EMITTED) {
@@ -2766,6 +2779,8 @@ void linphone_call_background_tasks(LinphoneCall *call, bool_t one_second_elapse
evd->packet = NULL;
call->stats[LINPHONE_CALL_STATS_VIDEO].updated = LINPHONE_CALL_STATS_SENT_RTCP_UPDATE;
update_local_stats(&call->stats[LINPHONE_CALL_STATS_VIDEO],(MediaStream*)call->videostream);
+ if (linphone_call_params_video_enabled(linphone_call_get_current_params(call)))
+ linphone_reporting_call_stats_updated(call, LINPHONE_CALL_STATS_VIDEO);
if (lc->vtable.call_stats_updated)
lc->vtable.call_stats_updated(lc, call, &call->stats[LINPHONE_CALL_STATS_VIDEO]);
} else if ((evt == ORTP_EVENT_ICE_SESSION_PROCESSING_FINISHED) || (evt == ORTP_EVENT_ICE_GATHERING_FINISHED)
@@ -2801,6 +2816,7 @@ void linphone_call_background_tasks(LinphoneCall *call, bool_t one_second_elapse
evd->packet = NULL;
call->stats[LINPHONE_CALL_STATS_AUDIO].updated = LINPHONE_CALL_STATS_RECEIVED_RTCP_UPDATE;
update_local_stats(&call->stats[LINPHONE_CALL_STATS_AUDIO],(MediaStream*)call->audiostream);
+ linphone_reporting_call_stats_updated(call, LINPHONE_CALL_STATS_AUDIO);
if (lc->vtable.call_stats_updated)
lc->vtable.call_stats_updated(lc, call, &call->stats[LINPHONE_CALL_STATS_AUDIO]);
} else if (evt == ORTP_EVENT_RTCP_PACKET_EMITTED) {
@@ -2811,6 +2827,7 @@ void linphone_call_background_tasks(LinphoneCall *call, bool_t one_second_elapse
evd->packet = NULL;
call->stats[LINPHONE_CALL_STATS_AUDIO].updated = LINPHONE_CALL_STATS_SENT_RTCP_UPDATE;
update_local_stats(&call->stats[LINPHONE_CALL_STATS_AUDIO],(MediaStream*)call->audiostream);
+ linphone_reporting_call_stats_updated(call, LINPHONE_CALL_STATS_AUDIO);
if (lc->vtable.call_stats_updated)
lc->vtable.call_stats_updated(lc, call, &call->stats[LINPHONE_CALL_STATS_AUDIO]);
} else if ((evt == ORTP_EVENT_ICE_SESSION_PROCESSING_FINISHED) || (evt == ORTP_EVENT_ICE_GATHERING_FINISHED)
diff --git a/coreapi/linphonecore.c b/coreapi/linphonecore.c
index dbd8eb07a..0697e349f 100644
--- a/coreapi/linphonecore.c
+++ b/coreapi/linphonecore.c
@@ -23,6 +23,7 @@ Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA.
#include "sipsetup.h"
#include "lpconfig.h"
#include "private.h"
+#include "quality_reporting.h"
#include
#include
@@ -127,6 +128,9 @@ LinphoneCallLog * linphone_call_log_new(LinphoneCall *call, LinphoneAddress *fro
cl->to=to;
cl->status=LinphoneCallAborted; /*default status*/
cl->quality=-1;
+
+ cl->reports[LINPHONE_CALL_STATS_AUDIO]=linphone_reporting_new();
+ cl->reports[LINPHONE_CALL_STATS_VIDEO]=linphone_reporting_new();
return cl;
}
@@ -390,6 +394,9 @@ void linphone_call_log_destroy(LinphoneCallLog *cl){
if (cl->to!=NULL) linphone_address_destroy(cl->to);
if (cl->refkey!=NULL) ms_free(cl->refkey);
if (cl->call_id) ms_free(cl->call_id);
+ if (cl->reports[LINPHONE_CALL_STATS_AUDIO]!=NULL) linphone_reporting_destroy(cl->reports[LINPHONE_CALL_STATS_AUDIO]);
+ if (cl->reports[LINPHONE_CALL_STATS_VIDEO]!=NULL) linphone_reporting_destroy(cl->reports[LINPHONE_CALL_STATS_VIDEO]);
+
ms_free(cl);
}
@@ -607,7 +614,7 @@ static void sound_config_read(LinphoneCore *lc)
linphone_core_set_play_file(lc,lp_config_get_string(lc->config,"sound","hold_music",PACKAGE_SOUND_DIR "/" HOLD_MUSIC));
lc->sound_conf.latency=0;
-#ifndef __ios
+#ifndef __ios
tmp=TRUE;
#else
tmp=FALSE; /* on iOS we have builtin echo cancellation.*/
@@ -626,7 +633,7 @@ static void sound_config_read(LinphoneCore *lc)
/*just parse requested stream feature once at start to print out eventual errors*/
linphone_core_get_audio_features(lc);
-
+
_linphone_core_set_tone(lc,LinphoneReasonBusy,LinphoneToneBusy,NULL);
}
@@ -640,7 +647,7 @@ static void certificates_config_read(LinphoneCore *lc)
#endif
linphone_core_set_root_ca(lc,rootca);
linphone_core_verify_server_certificates(lc,lp_config_get_int(lc->config,"sip","verify_server_certs",TRUE));
- linphone_core_verify_server_cn(lc,lp_config_get_int(lc->config,"sip","verify_server_cn",TRUE));
+ linphone_core_verify_server_cn(lc,lp_config_get_int(lc->config,"sip","verify_server_cn",TRUE));
}
static void sip_config_read(LinphoneCore *lc)
@@ -663,12 +670,12 @@ static void sip_config_read(LinphoneCore *lc)
}
linphone_core_enable_ipv6(lc,ipv6);
memset(&tr,0,sizeof(tr));
-
+
tr.udp_port=lp_config_get_int(lc->config,"sip","sip_port",5060);
tr.tcp_port=lp_config_get_int(lc->config,"sip","sip_tcp_port",5060);
/*we are not listening inbound connection for tls, port has no meaning*/
tr.tls_port=lp_config_get_int(lc->config,"sip","sip_tls_port",LC_SIP_TRANSPORT_RANDOM);
-
+
certificates_config_read(lc);
/*setting the dscp must be done before starting the transports, otherwise it is not taken into effect*/
sal_set_dscp(lc->sal,linphone_core_get_sip_dscp(lc));
@@ -703,7 +710,7 @@ static void sip_config_read(LinphoneCore *lc)
tmp=lp_config_get_int(lc->config,"sip","in_call_timeout",0);
linphone_core_set_in_call_timeout(lc,tmp);
-
+
tmp=lp_config_get_int(lc->config,"sip","delayed_timeout",4);
linphone_core_set_delayed_timeout(lc,tmp);
@@ -975,8 +982,8 @@ static void video_config_read(LinphoneCore *lc){
int capture, display, self_view;
int automatic_video=1;
#endif
- const char *str;
-#ifdef VIDEO_ENABLED
+ const char *str;
+#ifdef VIDEO_ENABLED
LinphoneVideoPolicy vpol;
memset(&vpol, 0, sizeof(LinphoneVideoPolicy));
#endif
@@ -1039,7 +1046,7 @@ bool_t linphone_core_tunnel_available(void){
/**
* Enable adaptive rate control.
- *
+ *
* @ingroup media_parameters
*
* Adaptive rate control consists in using RTCP feedback provided information to dynamically
@@ -1053,7 +1060,7 @@ void linphone_core_enable_adaptive_rate_control(LinphoneCore *lc, bool_t enabled
/**
* Returns whether adaptive rate control is enabled.
- *
+ *
* @ingroup media_parameters
*
* See linphone_core_enable_adaptive_rate_control().
@@ -1073,7 +1080,7 @@ bool_t linphone_core_rtcp_enabled(const LinphoneCore *lc){
* calls (within SDP messages) so that the remote end can have
* sufficient knowledge to properly configure its audio & video
* codec output bitrate to not overflow available bandwidth.
- *
+ *
* @ingroup media_parameters
*
* @param lc the LinphoneCore object
@@ -1159,7 +1166,7 @@ void linphone_core_set_upload_ptime(LinphoneCore *lc, int ptime){
* Set audio packetization time linphone will send (in absence of requirement from peer)
* A value of 0 stands for the current codec default packetization time.
*
- *
+ *
* @ingroup media_parameters
**/
int linphone_core_get_upload_ptime(LinphoneCore *lc){
@@ -1180,14 +1187,14 @@ const char * linphone_core_get_version(void){
static void linphone_core_assign_payload_type(LinphoneCore *lc, PayloadType *const_pt, int number, const char *recv_fmtp){
PayloadType *pt;
-
+
#ifdef ANDROID
if (const_pt->channels==2){
ms_message("Stereo %s codec not supported on this platform.",const_pt->mime_type);
return;
}
#endif
-
+
pt=payload_type_clone(const_pt);
if (number==-1){
/*look for a free number */
@@ -1251,10 +1258,10 @@ void linphone_core_set_state(LinphoneCore *lc, LinphoneGlobalState gstate, const
static void misc_config_read(LinphoneCore *lc) {
LpConfig *config=lc->config;
const char *uuid;
-
+
lc->max_call_logs=lp_config_get_int(config,"misc","history_max_size",15);
lc->max_calls=lp_config_get_int(config,"misc","max_calls",NB_MAX_CALLS);
-
+
uuid=lp_config_get_string(config,"misc","uuid",NULL);
if (!uuid){
char tmp[64];
@@ -1290,12 +1297,12 @@ static void linphone_core_start(LinphoneCore * lc) {
void linphone_configuring_terminated(LinphoneCore *lc, LinphoneConfiguringState state, const char *message) {
if (lc->vtable.configuring_status)
lc->vtable.configuring_status(lc, state, message);
-
+
if (state == LinphoneConfiguringSuccessful) {
if (linphone_core_is_provisioning_transient(lc) == TRUE)
linphone_core_set_provisioning_uri(lc, NULL);
}
-
+
linphone_core_start(lc);
}
@@ -1342,7 +1349,7 @@ static void linphone_core_init(LinphoneCore * lc, const LinphoneCoreVTable *vtab
linphone_core_assign_payload_type(lc,&payload_type_mp4v,99,"profile-level-id=3");
linphone_core_assign_payload_type(lc,&payload_type_h264,102,"profile-level-id=42801F");
linphone_core_assign_payload_type(lc,&payload_type_vp8,103,NULL);
-
+
linphone_core_assign_payload_type(lc,&payload_type_theora,97,NULL);
linphone_core_assign_payload_type(lc,&payload_type_x_snow,-1,NULL);
/* due to limited space in SDP, we have to disable this h264 line which is normally no more necessary */
@@ -1372,7 +1379,7 @@ static void linphone_core_init(LinphoneCore * lc, const LinphoneCoreVTable *vtab
linphone_core_assign_payload_type(lc,&payload_type_opus,-1,"useinbandfec=1; usedtx=0; cbr=1");
linphone_core_assign_payload_type(lc,&payload_type_isac,-1,NULL);
linphone_core_handle_static_payloads(lc);
-
+
ms_init();
/* create a mediastreamer2 event queue and set it as global */
/* This allows to run event's callback in linphone_core_iterate() */
@@ -1386,13 +1393,13 @@ static void linphone_core_init(LinphoneCore * lc, const LinphoneCoreVTable *vtab
lc->network_last_check = 0;
lc->network_last_status = FALSE;
-
+
lc->http_provider = belle_sip_stack_create_http_provider(sal_get_belle_sip_stack(lc->sal), "0.0.0.0");
lc->http_verify_policy = belle_tls_verify_policy_new();
belle_http_provider_set_tls_verify_policy(lc->http_provider,lc->http_verify_policy);
-
+
certificates_config_read(lc);
-
+
remote_provisioning_uri = linphone_core_get_provisioning_uri(lc);
if (remote_provisioning_uri == NULL) {
linphone_configuring_terminated(lc, LinphoneConfiguringSkipped, NULL);
@@ -1940,7 +1947,7 @@ static int apply_transports(LinphoneCore *lc){
/*first of all invalidate all current registrations so that we can register again with new transports*/
__linphone_core_invalidate_registers(lc);
-
+
if (lc->sip_conf.ipv6_enabled)
anyaddr="::0";
else
@@ -2212,7 +2219,7 @@ void linphone_core_iterate(LinphoneCore *lc){
int elapsed;
bool_t one_second_elapsed=FALSE;
const char *remote_provisioning_uri = NULL;
-
+
if (linphone_core_get_global_state(lc) == LinphoneGlobalStartup) {
if (sal_get_root_ca(lc->sal)) {
belle_tls_verify_policy_t *tls_policy = belle_tls_verify_policy_new();
@@ -2223,7 +2230,7 @@ void linphone_core_iterate(LinphoneCore *lc){
if (lc->vtable.display_status)
lc->vtable.display_status(lc, _("Configuring"));
linphone_core_set_state(lc, LinphoneGlobalConfiguring, "Configuring");
-
+
remote_provisioning_uri = linphone_core_get_provisioning_uri(lc);
if (remote_provisioning_uri) {
int err = linphone_remote_provisioning_download_and_apply(lc, remote_provisioning_uri);
@@ -2246,7 +2253,7 @@ void linphone_core_iterate(LinphoneCore *lc){
if (ecs==LinphoneEcCalibratorDone){
int len=lp_config_get_int(lc->config,"sound","ec_tail_len",0);
int margin=len/2;
-
+
lp_config_set_int(lc->config, "sound", "ec_delay",MAX(lc->ecc->delay-margin,0));
} else if (ecs == LinphoneEcCalibratorFailed) {
lp_config_set_int(lc->config, "sound", "ec_delay", -1);/*use default value from soundcard*/
@@ -2359,7 +2366,7 @@ void linphone_core_iterate(LinphoneCore *lc){
/**
* Interpret a call destination as supplied by the user, and returns a fully qualified
* LinphoneAddress.
- *
+ *
* @ingroup call_control
*
* A sip address should look like DisplayName .
@@ -2378,7 +2385,7 @@ LinphoneAddress * linphone_core_interpret_url(LinphoneCore *lc, const char *url)
LinphoneProxyConfig *proxy=lc->default_proxy;
char *tmpurl;
LinphoneAddress *uri;
-
+
if (*url=='\0') return NULL;
if (is_enum(url,&enum_domain)){
@@ -2428,7 +2435,7 @@ LinphoneAddress * linphone_core_interpret_url(LinphoneCore *lc, const char *url)
if (uri!=NULL){
return uri;
}
-
+
return NULL;
}
@@ -2466,7 +2473,7 @@ const char * linphone_core_get_route(LinphoneCore *lc){
* Start a new call as a consequence of a transfer request received from a call.
* This function is for advanced usage: the execution of transfers is automatically managed by the LinphoneCore. However if an application
* wants to have control over the call parameters for the new call, it should call this function immediately during the LinphoneCallRefered notification.
- * @see LinphoneCoreVTable::call_state_changed
+ * @see LinphoneCoreVTable::call_state_changed
* @param lc the LinphoneCore
* @param call a call that has just been notified about LinphoneCallRefered state event.
* @param params the call parameters to be applied to the new call.
@@ -2475,13 +2482,13 @@ const char * linphone_core_get_route(LinphoneCore *lc){
LinphoneCall * linphone_core_start_refered_call(LinphoneCore *lc, LinphoneCall *call, const LinphoneCallParams *params){
LinphoneCallParams *cp=params ? linphone_call_params_copy(params) : linphone_core_create_default_call_parameters(lc);
LinphoneCall *newcall;
-
+
if (call->state!=LinphoneCallPaused){
ms_message("Automatically pausing current call to accept transfer.");
_linphone_core_pause_call(lc,call);
call->was_automatically_paused=TRUE;
}
-
+
if (!params){
cp->has_video = call->current_params.has_video; /*start the call to refer-target with video enabled if original call had video*/
}
@@ -2521,7 +2528,7 @@ void linphone_core_notify_refer_state(LinphoneCore *lc, LinphoneCall *referer, L
nodes with the basic SIP routing policy in order to get a workable
system.
*/
-
+
static MSList *make_routes_for_proxy(LinphoneProxyConfig *proxy, const LinphoneAddress *dest){
MSList *ret=NULL;
const char *local_route=linphone_proxy_config_get_route(proxy);
@@ -2816,7 +2823,7 @@ LinphoneCall * linphone_core_invite_address_with_params(LinphoneCore *lc, const
bool_t defer = FALSE;
linphone_core_preempt_sound_resources(lc);
-
+
if(!linphone_core_can_we_add_call(lc)){
if (lc->vtable.display_warning)
lc->vtable.display_warning(lc,_("Sorry, we have reached the maximum number of simultaneous calls"));
@@ -2848,7 +2855,7 @@ LinphoneCall * linphone_core_invite_address_with_params(LinphoneCore *lc, const
linphone_call_set_state (call,LinphoneCallOutgoingInit,"Starting outgoing call");
call->log->start_date_time=time(NULL);
linphone_call_init_media_streams(call);
-
+
if (linphone_core_get_firewall_policy(call->core) == LinphonePolicyUseIce) {
/* Defer the start of the call after the ICE gathering process. */
if (linphone_call_prepare_ice(call,FALSE)==1)
@@ -2880,7 +2887,7 @@ LinphoneCall * linphone_core_invite_address_with_params(LinphoneCore *lc, const
defer = TRUE;
}
}
-
+
if (defer==FALSE) linphone_core_start_invite(lc,call,NULL);
if (real_url!=NULL) ms_free(real_url);
@@ -2893,7 +2900,7 @@ LinphoneCall * linphone_core_invite_address_with_params(LinphoneCore *lc, const
* @ingroup call_control
* The remote endpoint is expected to issue a new call to the specified destination.
* The current call remains active and thus can be later paused or terminated.
- *
+ *
* It is possible to follow the progress of the transfer provided that transferee sends notification about it.
* In this case, the transfer_state_changed callback of the #LinphoneCoreVTable is invoked to notify of the state of the new call at the other party.
* The notified states are #LinphoneCallOutgoingInit , #LinphoneCallOutgoingProgress, #LinphoneCallOutgoingRinging and #LinphoneCallOutgoingConnected.
@@ -2925,7 +2932,7 @@ int linphone_core_transfer_call(LinphoneCore *lc, LinphoneCall *call, const char
* @param lc linphone core object
* @param call a running call you want to transfer
* @param dest a running call whose remote person will receive the transfer
- *
+ *
* @ingroup call_control
*
* The transfered call is supposed to be in paused state, so that it is able to accept the transfer immediately.
@@ -2933,7 +2940,7 @@ int linphone_core_transfer_call(LinphoneCore *lc, LinphoneCall *call, const char
* This method will send a transfer request to the transfered person. The phone of the transfered is then
* expected to automatically call to the destination of the transfer. The receiver of the transfer will then automatically
* close the call with us (the 'dest' call).
- *
+ *
* It is possible to follow the progress of the transfer provided that transferee sends notification about it.
* In this case, the transfer_state_changed callback of the #LinphoneCoreVTable is invoked to notify of the state of the new call at the other party.
* The notified states are #LinphoneCallOutgoingInit , #LinphoneCallOutgoingProgress, #LinphoneCallOutgoingRinging and #LinphoneCallOutgoingConnected.
@@ -2958,7 +2965,7 @@ bool_t linphone_core_inc_invite_pending(LinphoneCore*lc){
bool_t linphone_core_media_description_has_srtp(const SalMediaDescription *md){
int i;
if (md->n_active_streams==0) return FALSE;
-
+
for(i=0;in_active_streams;i++){
const SalStreamDescription *sd=&md->streams[i];
if (sd->proto!=SalProtoRtpSavp){
@@ -3031,11 +3038,11 @@ void linphone_core_notify_incoming_call(LinphoneCore *lc, LinphoneCall *call){
if (call->state==LinphoneCallIncomingReceived){
/*try to be best-effort in giving real local or routable contact address for 100Rel case*/
linphone_call_set_contact_op(call);
-
+
if (propose_early_media || ringback_tone!=NULL){
linphone_core_accept_early_media(lc,call);
}else sal_call_notify_ringing(call->op,FALSE);
-
+
if (sal_call_get_replaces(call->op)!=NULL && lp_config_get_int(lc->config,"sip","auto_answer_replacing_calls",1)){
linphone_core_accept_call(lc,call);
}
@@ -3061,7 +3068,7 @@ void linphone_core_notify_incoming_call(LinphoneCore *lc, LinphoneCall *call){
int linphone_core_accept_early_media_with_params(LinphoneCore* lc, LinphoneCall* call, const LinphoneCallParams* params) {
if (call->state==LinphoneCallIncomingReceived){
SalMediaDescription* md;
-
+
/*try to be best-effort in giving real local or routable contact address for 100Rel case*/
linphone_call_set_contact_op(call);
@@ -3072,7 +3079,7 @@ int linphone_core_accept_early_media_with_params(LinphoneCore* lc, LinphoneCall*
sal_call_set_local_media_description ( call->op,call->localdesc );
sal_op_set_sent_custom_header ( call->op,params->custom_headers );
}
-
+
sal_call_notify_ringing(call->op,TRUE);
linphone_call_set_state(call,LinphoneCallIncomingEarlyMedia,"Incoming call early media");
@@ -3202,15 +3209,15 @@ int linphone_core_update_call(LinphoneCore *lc, LinphoneCall *call, const Linpho
/**
* @ingroup call_control
* When receiving a #LinphoneCallUpdatedByRemote state notification, prevent LinphoneCore from performing an automatic answer.
- *
+ *
* When receiving a #LinphoneCallUpdatedByRemote state notification (ie an incoming reINVITE), the default behaviour of
* LinphoneCore is to automatically answer the reINIVTE with call parameters unchanged.
* However when for example when the remote party updated the call to propose a video stream, it can be useful
- * to prompt the user before answering. This can be achieved by calling linphone_core_defer_call_update() during
+ * to prompt the user before answering. This can be achieved by calling linphone_core_defer_call_update() during
* the call state notifiacation, to deactivate the automatic answer that would just confirm the audio but reject the video.
* Then, when the user responds to dialog prompt, it becomes possible to call linphone_core_accept_call_update() to answer
* the reINVITE, with eventually video enabled in the LinphoneCallParams argument.
- *
+ *
* @return 0 if successful, -1 if the linphone_core_defer_call_update() was done outside a #LinphoneCallUpdatedByRemote notification, which is illegal.
**/
int linphone_core_defer_call_update(LinphoneCore *lc, LinphoneCall *call){
@@ -3416,7 +3423,7 @@ int linphone_core_accept_call_with_params(LinphoneCore *lc, LinphoneCall *call,
sal_call_set_local_media_description(call->op,call->localdesc);
sal_op_set_sent_custom_header(call->op,params->custom_headers);
}
-
+
/*give a chance a set card prefered sampling frequency*/
if (call->localdesc->streams[0].max_rate>0) {
ms_message ("configuring prefered card sampling rate to [%i]",call->localdesc->streams[0].max_rate);
@@ -3425,7 +3432,7 @@ int linphone_core_accept_call_with_params(LinphoneCore *lc, LinphoneCall *call,
if (lc->sound_conf.capt_sndcard)
ms_snd_card_set_preferred_sample_rate(lc->sound_conf.capt_sndcard, call->localdesc->streams[0].max_rate);
}
-
+
if (!was_ringing && call->audiostream->ms.state==MSStreamInitialized){
audio_stream_prepare_sound(call->audiostream,lc->sound_conf.play_sndcard,lc->sound_conf.capt_sndcard);
}
@@ -3543,9 +3550,9 @@ int linphone_core_terminate_call(LinphoneCore *lc, LinphoneCall *the_call)
/**
* Decline a pending incoming call, with a reason.
- *
+ *
* @ingroup call_control
- *
+ *
* @param lc the linphone core
* @param call the LinphoneCall, must be in the IncomingReceived state.
* @param reason the reason for rejecting the call: LinphoneReasonDeclined or LinphoneReasonBusy
@@ -3679,12 +3686,12 @@ int linphone_core_pause_all_calls(LinphoneCore *lc){
void linphone_core_preempt_sound_resources(LinphoneCore *lc){
LinphoneCall *current_call;
-
+
if (linphone_core_is_in_conference(lc)){
linphone_core_leave_conference(lc);
return;
}
-
+
current_call=linphone_core_get_current_call(lc);
if(current_call != NULL){
ms_message("Pausing automatically the current call.");
@@ -3703,7 +3710,7 @@ void linphone_core_preempt_sound_resources(LinphoneCore *lc){
int linphone_core_resume_call(LinphoneCore *lc, LinphoneCall *call){
char temp[255]={0};
const char *subject="Call resuming";
-
+
if(call->state!=LinphoneCallPaused ){
ms_warning("we cannot resume a call that has not been established and paused before");
return -1;
@@ -3718,7 +3725,7 @@ int linphone_core_resume_call(LinphoneCore *lc, LinphoneCall *call){
}
call->was_automatically_paused=FALSE;
-
+
/* Stop playing music immediately. If remote side is a conference it
prevents the participants to hear it while the 200OK comes back.*/
if (call->audiostream) audio_stream_play(call->audiostream, NULL);
@@ -3754,7 +3761,7 @@ static int remote_address_compare(LinphoneCall *call, const LinphoneAddress *rad
* @param lc
* @param remote_address
* @return the LinphoneCall of the call if found
- *
+ *
* @ingroup call_control
*/
LinphoneCall *linphone_core_get_call_by_remote_address(LinphoneCore *lc, const char *remote_address){
@@ -4357,7 +4364,7 @@ const char *linphone_core_get_root_ca(LinphoneCore *lc){
/**
* Specify whether the tls server certificate must be verified when connecting to a SIP/TLS server.
- *
+ *
* @ingroup initializing
**/
void linphone_core_verify_server_certificates(LinphoneCore *lc, bool_t yesno){
@@ -4474,7 +4481,7 @@ void linphone_core_mute_mic(LinphoneCore *lc, bool_t val){
if ( linphone_core_get_rtp_no_xmit_on_audio_mute(lc) ){
audio_stream_mute_rtp(st,val);
}
-
+
}
}
@@ -4560,7 +4567,7 @@ void linphone_core_set_stun_server(LinphoneCore *lc, const char *server){
if (server)
lc->net_conf.stun_server=ms_strdup(server);
else lc->net_conf.stun_server=NULL;
-
+
/* each time the stun server is changed, we must clean the resolved cached addrinfo*/
if (lc->net_conf.stun_addrinfo){
freeaddrinfo(lc->net_conf.stun_addrinfo);
@@ -4570,7 +4577,7 @@ void linphone_core_set_stun_server(LinphoneCore *lc, const char *server){
if (lc->net_conf.stun_server){
linphone_core_resolve_stun_server(lc);
}
-
+
if (linphone_core_ready(lc))
lp_config_set_string(lc->config,"net","stun_server",lc->net_conf.stun_server);
}
@@ -4627,7 +4634,7 @@ const char *linphone_core_get_nat_address_resolved(LinphoneCore *lc)
char ipstring [INET6_ADDRSTRLEN];
if (lc->net_conf.nat_address==NULL) return NULL;
-
+
if (parse_hostname_to_addr (lc->net_conf.nat_address, &ss, &ss_len, 5060)<0) {
return lc->net_conf.nat_address;
}
@@ -4996,7 +5003,7 @@ int linphone_core_set_static_picture(LinphoneCore *lc, const char *path) {
const char *linphone_core_get_static_picture(LinphoneCore *lc) {
const char *path=NULL;
#ifdef VIDEO_ENABLED
- path=ms_static_image_get_default_image();
+ path=ms_static_image_get_default_image();
#else
ms_warning("Video support not compiled.");
#endif
@@ -5071,7 +5078,7 @@ static void unset_video_window_id(LinphoneCore *lc, bool_t preview, unsigned lon
LinphoneCall *call;
MSList *elem;
#endif
-
+
if (id!=0 && id!=-1) {
ms_error("Invalid use of unset_video_window_id()");
return;
@@ -5235,7 +5242,7 @@ static MSVideoSizeDef supported_resolutions[]={
#if !TARGET_OS_MAC || TARGET_OS_IPHONE /* OS_MAC is 1 for iPhone, but we need QVGA */
{ { MS_VIDEO_SIZE_QVGA_W, MS_VIDEO_SIZE_QVGA_H } , "qvga" },
#endif
- { { MS_VIDEO_SIZE_QCIF_W, MS_VIDEO_SIZE_QCIF_H } , "qcif" },
+ { { MS_VIDEO_SIZE_QCIF_W, MS_VIDEO_SIZE_QCIF_H } , "qcif" },
{ { 0,0 } , NULL }
};
@@ -5480,7 +5487,7 @@ void linphone_core_play_named_tone(LinphoneCore *lc, LinphoneToneID toneid){
def.frequencies[0]=620;
def.interval=250;
def.repeat_count=3;
-
+
break;
default:
ms_warning("Unhandled tone id.");
@@ -5546,7 +5553,7 @@ int linphone_core_get_mtu(const LinphoneCore *lc){
* Sets the maximum transmission unit size in bytes.
* This information is useful for sending RTP packets.
* Default value is 1500.
- *
+ *
* @ingroup media_parameters
**/
void linphone_core_set_mtu(LinphoneCore *lc, int mtu){
@@ -5640,7 +5647,7 @@ void sip_config_uninit(LinphoneCore *lc)
int i;
sip_config_t *config=&lc->sip_conf;
bool_t still_registered=TRUE;
-
+
lp_config_set_int(lc->config,"sip","guess_hostname",config->guess_hostname);
lp_config_set_string(lc->config,"sip","contact",config->contact);
lp_config_set_int(lc->config,"sip","inc_timeout",config->inc_timeout);
@@ -5655,7 +5662,7 @@ void sip_config_uninit(LinphoneCore *lc)
LinphoneProxyConfig *cfg=(LinphoneProxyConfig*)(elem->data);
linphone_proxy_config_edit(cfg); /* to unregister */
}
-
+
ms_message("Unregistration started.");
for (i=0;i<20&&still_registered;i++){
@@ -5687,7 +5694,7 @@ void sip_config_uninit(LinphoneCore *lc)
ms_message("Tunnel destroyed.");
}
#endif
-
+
sal_reset_transports(lc->sal);
sal_unlisten_ports(lc->sal); /*to make sure no new messages are received*/
if (lc->http_provider) {
@@ -5893,7 +5900,7 @@ static void linphone_core_uninit(LinphoneCore *lc)
linphone_presence_model_unref(lc->presence_model);
}
linphone_core_free_payload_types(lc);
-
+
linphone_core_message_storage_close(lc);
ms_exit();
linphone_core_set_state(lc,LinphoneGlobalOff,"Off");
@@ -5902,7 +5909,7 @@ static void linphone_core_uninit(LinphoneCore *lc)
static void set_network_reachable(LinphoneCore* lc,bool_t isReachable, time_t curtime){
// second get the list of available proxies
const MSList *elem=linphone_core_get_proxy_config_list(lc);
-
+
if (lc->network_reachable==isReachable) return; // no change, ignore.
ms_message("Network state is now [%s]",isReachable?"UP":"DOWN");
@@ -5919,7 +5926,7 @@ static void set_network_reachable(LinphoneCore* lc,bool_t isReachable, time_t cu
}
lc->netup_time=curtime;
lc->network_reachable=isReachable;
-
+
if (!lc->network_reachable){
linphone_core_invalidate_friend_subscriptions(lc);
sal_reset_transports(lc->sal);
@@ -6393,7 +6400,7 @@ int linphone_core_set_media_encryption(LinphoneCore *lc, LinphoneMediaEncryption
LinphoneMediaEncryption linphone_core_get_media_encryption(LinphoneCore *lc) {
const char* menc = lp_config_get_string(lc->config, "sip", "media_encryption", NULL);
-
+
if (menc == NULL)
return LinphoneMediaEncryptionNone;
else if (strcmp(menc, "srtp")==0)
@@ -6429,10 +6436,10 @@ const char* linphone_core_get_device_identifier(const LinphoneCore *lc) {
/**
* Set the DSCP field for SIP signaling channel.
- *
+ *
* @ingroup network_parameters
* * The DSCP defines the quality of service in IP packets.
- *
+ *
**/
void linphone_core_set_sip_dscp(LinphoneCore *lc, int dscp){
sal_set_dscp(lc->sal,dscp);
@@ -6444,10 +6451,10 @@ void linphone_core_set_sip_dscp(LinphoneCore *lc, int dscp){
/**
* Get the DSCP field for SIP signaling channel.
- *
+ *
* @ingroup network_parameters
* * The DSCP defines the quality of service in IP packets.
- *
+ *
**/
int linphone_core_get_sip_dscp(const LinphoneCore *lc){
return lp_config_get_int(lc->config,"sip","dscp",0x1a);
@@ -6458,7 +6465,7 @@ int linphone_core_get_sip_dscp(const LinphoneCore *lc){
*
* @ingroup network_parameters
* The DSCP defines the quality of service in IP packets.
- *
+ *
**/
void linphone_core_set_audio_dscp(LinphoneCore *lc, int dscp){
if (linphone_core_ready(lc))
@@ -6470,7 +6477,7 @@ void linphone_core_set_audio_dscp(LinphoneCore *lc, int dscp){
*
* @ingroup network_parameters
* The DSCP defines the quality of service in IP packets.
- *
+ *
**/
int linphone_core_get_audio_dscp(const LinphoneCore *lc){
return lp_config_get_int(lc->config,"rtp","audio_dscp",0x2e);
@@ -6481,12 +6488,12 @@ int linphone_core_get_audio_dscp(const LinphoneCore *lc){
*
* @ingroup network_parameters
* The DSCP defines the quality of service in IP packets.
- *
+ *
**/
void linphone_core_set_video_dscp(LinphoneCore *lc, int dscp){
if (linphone_core_ready(lc))
lp_config_set_int_hex(lc->config,"rtp","video_dscp",dscp);
-
+
}
/**
@@ -6494,7 +6501,7 @@ void linphone_core_set_video_dscp(LinphoneCore *lc, int dscp){
*
* @ingroup network_parameters
* The DSCP defines the quality of service in IP packets.
- *
+ *
**/
int linphone_core_get_video_dscp(const LinphoneCore *lc){
return lp_config_get_int(lc->config,"rtp","video_dscp",0);
diff --git a/coreapi/linphonecore.h b/coreapi/linphonecore.h
index 7448341aa..ea03d9874 100644
--- a/coreapi/linphonecore.h
+++ b/coreapi/linphonecore.h
@@ -812,6 +812,17 @@ LINPHONE_PUBLIC void linphone_proxy_config_enable_publish(LinphoneProxyConfig *o
LINPHONE_PUBLIC void linphone_proxy_config_set_dial_escape_plus(LinphoneProxyConfig *cfg, bool_t val);
LINPHONE_PUBLIC void linphone_proxy_config_set_dial_prefix(LinphoneProxyConfig *cfg, const char *prefix);
+/**
+ * Indicates either or not, quality statistics during call should be stored and sent to a collector at termination.
+ * @param cfg #LinphoneProxyConfig object
+ * @param val if true, quality statistics publish will be stored and sent to the collector
+ *
+ */
+LINPHONE_PUBLIC void linphone_proxy_config_enable_statistics(LinphoneProxyConfig *cfg, bool_t val);
+LINPHONE_PUBLIC bool_t linphone_proxy_config_send_statistics_enabled(LinphoneProxyConfig *cfg);
+LINPHONE_PUBLIC void linphone_proxy_config_set_statistics_collector(LinphoneProxyConfig *cfg, const char *collector);
+LINPHONE_PUBLIC const char *linphone_proxy_config_get_statistics_collector(const LinphoneProxyConfig *obj);
+
/**
* Get the registration state of the given proxy config.
* @param[in] obj #LinphoneProxyConfig object.
diff --git a/coreapi/presence.c b/coreapi/presence.c
index af4c5184d..fa4b97014 100644
--- a/coreapi/presence.c
+++ b/coreapi/presence.c
@@ -170,7 +170,7 @@ static time_t parse_timestamp(const char *timestamp) {
return seconds - timezone;
}
-static char * timestamp_to_string(time_t timestamp) {
+char * linphone_timestamp_to_rfc3339_string(time_t timestamp) {
char timestamp_str[22];
struct tm *ret;
#ifndef WIN32
@@ -1467,7 +1467,7 @@ void linphone_subscription_new(LinphoneCore *lc, SalOp *op, const char *from){
char *tmp;
LinphoneAddress *uri;
LinphoneProxyConfig *cfg;
-
+
uri=linphone_address_new(from);
linphone_address_clean(uri);
tmp=linphone_address_as_string(uri);
@@ -1482,7 +1482,7 @@ void linphone_subscription_new(LinphoneCore *lc, SalOp *op, const char *from){
}
}
}
-
+
/* check if we answer to this subscription */
if (linphone_find_friend_by_address(lc->friends,uri,&lf)!=NULL){
lf->insub=op;
@@ -1604,7 +1604,7 @@ static void write_xml_presence_note_obj(LinphonePresenceNote *note, struct _pres
static int write_xml_presence_timestamp(xmlTextWriterPtr writer, time_t timestamp) {
int err;
- char *timestamp_str = timestamp_to_string(timestamp);
+ char *timestamp_str = linphone_timestamp_to_rfc3339_string(timestamp);
err = xmlTextWriterWriteElement(writer, (const xmlChar *)"timestamp", (const xmlChar *)timestamp_str);
if (timestamp_str) ms_free(timestamp_str);
return err;
diff --git a/coreapi/private.h b/coreapi/private.h
index 6634a3344..2d2d9fb21 100644
--- a/coreapi/private.h
+++ b/coreapi/private.h
@@ -34,6 +34,7 @@ extern "C" {
#include "linphonecore_utils.h"
#include "sal/sal.h"
#include "sipsetup.h"
+#include "quality_reporting.h"
#include
#include
@@ -115,6 +116,8 @@ struct _LinphoneCallLog{
float quality;
time_t start_date_time; /**Start date of the call in seconds as expressed in a time_t */
char* call_id; /**unique id of a call*/
+
+ reporting_session_report_t * reports[2]; /**sip_conf.proxies,i=0;elem!=NULL;elem=ms_list_next(elem),i++){
LinphoneProxyConfig *cfg=(LinphoneProxyConfig*)elem->data;
linphone_proxy_config_write_to_config_file(lc->config,cfg,i);
@@ -46,9 +46,10 @@ static void linphone_proxy_config_init(LinphoneCore* lc, LinphoneProxyConfig *ob
const char *identity = lc ? lp_config_get_default_string(lc->config, "proxy", "reg_identity", NULL) : NULL;
const char *proxy = lc ? lp_config_get_default_string(lc->config, "proxy", "reg_proxy", NULL) : NULL;
const char *route = lc ? lp_config_get_default_string(lc->config, "proxy", "reg_route", NULL) : NULL;
+ const char *statistics_collector = lc ? lp_config_get_default_string(lc->config, "proxy", "reg_statistics_collector", NULL) : NULL;
const char *contact_params = lc ? lp_config_get_default_string(lc->config, "proxy", "contact_parameters", NULL) : NULL;
const char *contact_uri_params = lc ? lp_config_get_default_string(lc->config, "proxy", "contact_uri_parameters", NULL) : NULL;
-
+
memset(obj, 0, sizeof(LinphoneProxyConfig));
obj->magic = linphone_proxy_config_magic;
obj->expires = lc ? lp_config_get_default_int(lc->config, "proxy", "reg_expires", 3600) : 3600;
@@ -59,6 +60,8 @@ static void linphone_proxy_config_init(LinphoneCore* lc, LinphoneProxyConfig *ob
obj->reg_identity = identity ? ms_strdup(identity) : NULL;
obj->reg_proxy = proxy ? ms_strdup(proxy) : NULL;
obj->reg_route = route ? ms_strdup(route) : NULL;
+ obj->reg_statistics_collector = statistics_collector ? ms_strdup(statistics_collector) : NULL;
+ obj->send_statistics = lc ? lp_config_get_default_int(lc->config, "proxy", "send_statistics", 0) : 0;
obj->contact_params = contact_params ? ms_strdup(contact_params) : NULL;
obj->contact_uri_params = contact_uri_params ? ms_strdup(contact_uri_params) : NULL;
}
@@ -85,7 +88,7 @@ LinphoneProxyConfig * linphone_core_create_proxy_config(LinphoneCore *lc) {
/**
* Destroys a proxy config.
- *
+ *
* @note: LinphoneProxyConfig that have been removed from LinphoneCore with
* linphone_core_remove_proxy_config() must not be freed.
**/
@@ -93,6 +96,7 @@ void linphone_proxy_config_destroy(LinphoneProxyConfig *obj){
if (obj->reg_proxy!=NULL) ms_free(obj->reg_proxy);
if (obj->reg_identity!=NULL) ms_free(obj->reg_identity);
if (obj->reg_route!=NULL) ms_free(obj->reg_route);
+ if (obj->reg_statistics_collector!=NULL) ms_free(obj->reg_statistics_collector);
if (obj->ssctx!=NULL) sip_setup_context_free(obj->ssctx);
if (obj->realm!=NULL) ms_free(obj->realm);
if (obj->type!=NULL) ms_free(obj->type);
@@ -123,10 +127,10 @@ bool_t linphone_proxy_config_is_registered(const LinphoneProxyConfig *obj){
int linphone_proxy_config_set_server_addr(LinphoneProxyConfig *obj, const char *server_addr){
LinphoneAddress *addr=NULL;
char *modified=NULL;
-
+
if (obj->reg_proxy!=NULL) ms_free(obj->reg_proxy);
obj->reg_proxy=NULL;
-
+
if (server_addr!=NULL && strlen(server_addr)>0){
if (strstr(server_addr,"sip:")==NULL && strstr(server_addr,"sips:")==NULL){
modified=ms_strdup_printf("sip:%s",server_addr);
@@ -149,7 +153,7 @@ int linphone_proxy_config_set_server_addr(LinphoneProxyConfig *obj, const char *
/**
* Sets the user identity as a SIP address.
*
- * This identity is normally formed with display name, username and domain, such
+ * This identity is normally formed with display name, username and domain, such
* as:
* Alice
* The REGISTER messages will have from and to set to this identity.
@@ -297,14 +301,14 @@ LinphoneAddress *guess_contact_for_register(LinphoneProxyConfig *obj){
LinphoneAddress *ret=NULL;
LinphoneAddress *proxy=linphone_address_new(obj->reg_proxy);
const char *host;
-
+
if (proxy==NULL) return NULL;
host=linphone_address_get_domain(proxy);
if (host!=NULL){
int localport = -1;
const char *localip = NULL;
LinphoneAddress *contact=linphone_address_new(obj->reg_identity);
-
+
linphone_address_clean(contact);
if (obj->contact_params) {
@@ -374,7 +378,7 @@ void linphone_proxy_config_refresh_register(LinphoneProxyConfig *obj){
/**
- * Sets a dialing prefix to be automatically prepended when inviting a number with
+ * Sets a dialing prefix to be automatically prepended when inviting a number with
* linphone_core_invite();
* This dialing prefix shall usually be the country code of the country where the user is living.
*
@@ -390,7 +394,7 @@ void linphone_proxy_config_set_dial_prefix(LinphoneProxyConfig *cfg, const char
/**
* Returns dialing prefix.
*
- *
+ *
**/
const char *linphone_proxy_config_get_dial_prefix(const LinphoneProxyConfig *cfg){
return cfg->dial_prefix;
@@ -413,6 +417,37 @@ void linphone_proxy_config_set_dial_escape_plus(LinphoneProxyConfig *cfg, bool_t
bool_t linphone_proxy_config_get_dial_escape_plus(const LinphoneProxyConfig *cfg){
return cfg->dial_escape_plus;
}
+
+void linphone_proxy_config_enable_statistics(LinphoneProxyConfig *cfg, bool_t val){
+ cfg->send_statistics = val;
+}
+
+bool_t linphone_proxy_config_send_statistics_enabled(LinphoneProxyConfig *cfg){
+ // ensure that collector address is set too!
+ return cfg->send_statistics && cfg->reg_statistics_collector != NULL;
+}
+
+void linphone_proxy_config_set_statistics_collector(LinphoneProxyConfig *cfg, const char *collector){
+ if (collector!=NULL && strlen(collector)>0){
+ LinphoneAddress *addr=linphone_address_new(collector);
+ if (!addr || linphone_address_get_username(addr)==NULL){
+ ms_warning("Invalid sip collector identity: %s",collector);
+ if (addr)
+ linphone_address_destroy(addr);
+ } else {
+ if (cfg->reg_statistics_collector != NULL)
+ ms_free(cfg->reg_statistics_collector);
+ cfg->reg_statistics_collector = ms_strdup(collector);
+ linphone_address_destroy(addr);
+ }
+ }
+}
+
+const char *linphone_proxy_config_get_statistics_collector(const LinphoneProxyConfig *cfg){
+ return cfg->reg_statistics_collector;
+}
+
+
/*
* http://en.wikipedia.org/wiki/Telephone_numbering_plan
* http://en.wikipedia.org/wiki/Telephone_numbers_in_Europe
@@ -423,7 +458,7 @@ typedef struct dial_plan{
char ccc[8]; /*country calling code*/
int nnl; /*maximum national number length*/
const char * icp; /*international call prefix, ex: 00 in europe*/
-
+
}dial_plan_t;
/* TODO: fill with information for all countries over the world*/
@@ -708,7 +743,7 @@ static void lookup_dial_plan(const char *ccc, dial_plan_t *plan){
static bool_t is_a_phone_number(const char *username){
const char *p;
for(p=username;*p!='\0';++p){
- if (isdigit(*p) ||
+ if (isdigit(*p) ||
*p==' ' ||
*p=='.' ||
*p=='-' ||
@@ -736,12 +771,12 @@ static char *flatten_number(const char *number){
static void replace_plus(const char *src, char *dest, size_t destlen, const char *icp){
int i=0;
-
+
if (icp && src[0]=='+' && (destlen>(i=strlen(icp))) ){
src++;
strcpy(dest,icp);
}
-
+
for(;(idial_prefix==NULL || proxy->dial_prefix[0]=='\0'){
/*no prefix configured, nothing else to do*/
strncpy(result,flatten,result_len);
@@ -824,7 +859,7 @@ void linphone_proxy_config_set_realm(LinphoneProxyConfig *cfg, const char *realm
int linphone_proxy_config_send_publish(LinphoneProxyConfig *proxy, LinphonePresenceModel *presence){
int err=0;
-
+
if (proxy->state==LinphoneRegistrationOk || proxy->state==LinphoneRegistrationCleared){
if (proxy->publish_op==NULL){
proxy->publish_op=sal_op_new(proxy->lc->sal);
@@ -1015,7 +1050,7 @@ void linphone_core_set_default_proxy(LinphoneCore *lc, LinphoneProxyConfig *conf
lc->default_proxy=config;
if (linphone_core_ready(lc))
lp_config_set_int(lc->config,"sip","default_proxy",linphone_core_get_default_proxy(lc,NULL));
-}
+}
void linphone_core_set_default_proxy_index(LinphoneCore *lc, int index){
if (index<0) linphone_core_set_default_proxy(lc,NULL);
@@ -1059,6 +1094,9 @@ void linphone_proxy_config_write_to_config_file(LpConfig *config, LinphoneProxyC
if (obj->reg_route!=NULL){
lp_config_set_string(config,key,"reg_route",obj->reg_route);
}
+ if (obj->reg_statistics_collector!=NULL){
+ lp_config_set_string(config,key,"reg_statistics_collector",obj->reg_statistics_collector);
+ }
if (obj->reg_identity!=NULL){
lp_config_set_string(config,key,"reg_identity",obj->reg_identity);
}
@@ -1072,6 +1110,7 @@ void linphone_proxy_config_write_to_config_file(LpConfig *config, LinphoneProxyC
lp_config_set_int(config,key,"reg_sendregister",obj->reg_sendregister);
lp_config_set_int(config,key,"publish",obj->publish);
lp_config_set_int(config,key,"dial_escape_plus",obj->dial_escape_plus);
+ lp_config_set_int(config,key,"send_statistics",obj->send_statistics);
lp_config_set_string(config,key,"dial_prefix",obj->dial_prefix);
lp_config_set_int(config,key,"privacy",obj->privacy);
}
@@ -1085,7 +1124,7 @@ LinphoneProxyConfig *linphone_proxy_config_new_from_config_file(LpConfig *config
const char *proxy;
LinphoneProxyConfig *cfg;
char key[50];
-
+
sprintf(key,"proxy_%i",index);
if (!lp_config_has_section(config,key)){
@@ -1094,29 +1133,33 @@ LinphoneProxyConfig *linphone_proxy_config_new_from_config_file(LpConfig *config
cfg=linphone_proxy_config_new();
- identity=lp_config_get_string(config,key,"reg_identity",NULL);
+ identity=lp_config_get_string(config,key,"reg_identity",NULL);
proxy=lp_config_get_string(config,key,"reg_proxy",NULL);
-
+
linphone_proxy_config_set_identity(cfg,identity);
linphone_proxy_config_set_server_addr(cfg,proxy);
-
+
tmp=lp_config_get_string(config,key,"reg_route",NULL);
if (tmp!=NULL) linphone_proxy_config_set_route(cfg,tmp);
+ tmp=lp_config_get_string(config,key,"reg_statistics_collector",NULL);
+ if (tmp!=NULL) linphone_proxy_config_set_statistics_collector(cfg,tmp);
+ linphone_proxy_config_enable_statistics(cfg,lp_config_get_int(config,key,"send_statistics",0));
+
linphone_proxy_config_set_contact_parameters(cfg,lp_config_get_string(config,key,"contact_parameters",NULL));
-
+
linphone_proxy_config_set_contact_uri_parameters(cfg,lp_config_get_string(config,key,"contact_uri_parameters",NULL));
-
+
linphone_proxy_config_expires(cfg,lp_config_get_int(config,key,"reg_expires",lp_config_get_default_int(config,"proxy","reg_expires",600)));
linphone_proxy_config_enableregister(cfg,lp_config_get_int(config,key,"reg_sendregister",0));
-
+
linphone_proxy_config_enable_publish(cfg,lp_config_get_int(config,key,"publish",0));
linphone_proxy_config_set_dial_escape_plus(cfg,lp_config_get_int(config,key,"dial_escape_plus",lp_config_get_default_int(config,"proxy","dial_escape_plus",0)));
linphone_proxy_config_set_dial_prefix(cfg,lp_config_get_string(config,key,"dial_prefix",lp_config_get_default_string(config,"proxy","dial_prefix",NULL)));
-
+
tmp=lp_config_get_string(config,key,"type",NULL);
- if (tmp!=NULL && strlen(tmp)>0)
+ if (tmp!=NULL && strlen(tmp)>0)
linphone_proxy_config_set_sip_setup(cfg,tmp);
linphone_proxy_config_set_privacy(cfg,lp_config_get_int(config,key,"privacy",lp_config_get_default_int(config,"proxy","privacy",LinphonePrivacyDefault)));
@@ -1155,7 +1198,7 @@ static void linphone_proxy_config_activate_sip_setup(LinphoneProxyConfig *cfg){
ms_error("Could not retrieve proxy uri !");
}
}
-
+
}
SipSetup *linphone_proxy_config_get_sip_setup(LinphoneProxyConfig *cfg){
@@ -1170,7 +1213,7 @@ static bool_t can_register(LinphoneProxyConfig *cfg){
LinphoneCore *lc=cfg->lc;
#ifdef BUILD_UPNP
if (linphone_core_get_firewall_policy(lc)==LinphonePolicyUseUpnp){
- if(lc->sip_conf.register_only_when_upnp_is_ok &&
+ if(lc->sip_conf.register_only_when_upnp_is_ok &&
(lc->upnp == NULL || !linphone_upnp_context_is_ready_for_register(lc->upnp))) {
return FALSE;
}
@@ -1326,7 +1369,7 @@ void * linphone_proxy_config_get_user_data(LinphoneProxyConfig *cr) {
void linphone_proxy_config_set_state(LinphoneProxyConfig *cfg, LinphoneRegistrationState state, const char *message){
LinphoneCore *lc=cfg->lc;
bool_t update_friends=FALSE;
-
+
if (cfg->state!=state || state==LinphoneRegistrationOk) { /*allow multiple notification of LinphoneRegistrationOk for refreshing*/
ms_message("Proxy config [%p] for identity [%s] moving from state [%s] to [%s]" , cfg,
linphone_proxy_config_get_identity(cfg),
@@ -1337,7 +1380,7 @@ void linphone_proxy_config_set_state(LinphoneProxyConfig *cfg, LinphoneRegistrat
|| (state!=LinphoneRegistrationOk && cfg->state==LinphoneRegistrationOk);
}
cfg->state=state;
-
+
if (update_friends){
linphone_core_update_friends_subscriptions(lc,cfg,TRUE);
}
diff --git a/coreapi/quality_reporting.c b/coreapi/quality_reporting.c
new file mode 100644
index 000000000..fb64a4b28
--- /dev/null
+++ b/coreapi/quality_reporting.c
@@ -0,0 +1,524 @@
+/*
+linphone
+Copyright (C) 2014 - Belledonne Communications, Grenoble, France
+
+This program is free software; you can redistribute it and/or
+modify it under the terms of the GNU General Public License
+as published by the Free Software Foundation; either version 2
+of the License, or (at your option) any later version.
+
+This program is distributed in the hope that it will be useful,
+but WITHOUT ANY WARRANTY; without even the implied warranty of
+MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+GNU General Public License for more details.
+
+You should have received a copy of the GNU General Public License
+along with this program; if not, write to the Free Software
+Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA.
+*/
+
+#ifdef HAVE_CONFIG_H
+#include "../config.h"
+#endif
+
+#include "linphonecore.h"
+#include "private.h"
+#include "sal/sal.h"
+#include "ortp/rtpsession.h"
+
+#include
+
+/***************************************************************************
+ * TODO / REMINDER LIST
+ ****************************************************************************/
+ // to discuss
+ // For codecs that are able to change sample rates, the lowest and highest sample rates MUST be reported (e.g., 8000;16000).
+ // moslq == moscq
+ // video: what happens if doing stop/resume?
+ // one time value: average? worst value?
+ // rlq value: need algo to compute it
+/***************************************************************************
+ * END OF TODO / REMINDER LIST
+ ****************************************************************************/
+
+#define STR_REASSIGN(dest, src) {\
+ if (dest != NULL) \
+ ms_free(dest); \
+ dest = src; \
+}
+
+// since printf family functions are LOCALE dependent, float separator may differ
+// depending on the user's locale (LC_NUMERIC env var).
+static char * float_to_one_decimal_string(float f) {
+ float rounded_f = floorf(f * 10 + .5f) / 10;
+
+ int floor_part = (int) rounded_f;
+ int one_decimal_part = floorf (10 * (rounded_f - floor_part) + .5f);
+
+ return ms_strdup_printf(_("%d.%d"), floor_part, one_decimal_part);
+}
+
+static void append_to_buffer_valist(char **buff, size_t *buff_size, size_t *offset, const char *fmt, va_list args) {
+ belle_sip_error_code ret;
+ size_t prevoffset = *offset;
+
+ #ifndef WIN32
+ va_list cap;/*copy of our argument list: a va_list cannot be re-used (SIGSEGV on linux 64 bits)*/
+ va_copy(cap,args);
+ ret = belle_sip_snprintf_valist(*buff, *buff_size, offset, fmt, cap);
+ va_end(cap);
+ #else
+ ret = belle_sip_snprintf_valist(*buff, *buff_size, offset, fmt, args);
+ #endif
+
+ // if we are out of memory, we add some size to buffer
+ if (ret == BELLE_SIP_BUFFER_OVERFLOW) {
+ ms_warning("Buffer was too small to contain the whole report - doubling its size from %lu to %lu", *buff_size, 2 * *buff_size);
+ *buff_size += 2048;
+ *buff = (char *) ms_realloc(*buff, *buff_size);
+
+ *offset = prevoffset;
+ // recall myself since we did not write all things into the buffer but
+ // only a part of it
+ append_to_buffer_valist(buff, buff_size, offset, fmt, args);
+ }
+}
+
+static void append_to_buffer(char **buff, size_t *buff_size, size_t *offset, const char *fmt, ...) {
+ va_list args;
+ va_start(args, fmt);
+ append_to_buffer_valist(buff, buff_size, offset, fmt, args);
+ va_end(args);
+}
+
+
+#define APPEND_IF_NOT_NULL_STR(buffer, size, offset, fmt, arg) if (arg != NULL) append_to_buffer(buffer, size, offset, fmt, arg)
+#define APPEND_IF_NUM_IN_RANGE(buffer, size, offset, fmt, arg, inf, sup) if (inf <= arg && arg <= sup) append_to_buffer(buffer, size, offset, fmt, arg)
+#define APPEND_IF(buffer, size, offset, fmt, arg, cond) if (cond) append_to_buffer(buffer, size, offset, fmt, arg)
+#define IF_NUM_IN_RANGE(num, inf, sup, statement) if (inf <= num && num <= sup) statement
+
+static bool_t are_metrics_filled(const reporting_content_metrics_t rm) {
+ IF_NUM_IN_RANGE(rm.packet_loss.network_packet_loss_rate, 0, 255, return TRUE);
+ IF_NUM_IN_RANGE(rm.packet_loss.jitter_buffer_discard_rate, 0, 255, return TRUE);
+ IF_NUM_IN_RANGE(rm.quality_estimates.moslq, 1, 5, return TRUE);
+ IF_NUM_IN_RANGE(rm.quality_estimates.moscq, 1, 5, return TRUE);
+
+ // since these are same values than local ones, do not check them
+ // if (rm.session_description.payload_type != -1) return TRUE;
+ // if (rm.session_description.payload_desc != NULL) return TRUE;
+ // if (rm.session_description.sample_rate != -1) return TRUE;
+ if (rm.session_description.frame_duration != -1) return TRUE;
+ // if (rm.session_description.fmtp != NULL) return TRUE;
+ if (rm.session_description.packet_loss_concealment != -1) return TRUE;
+
+ IF_NUM_IN_RANGE(rm.jitter_buffer.adaptive, 0, 3, return TRUE);
+ IF_NUM_IN_RANGE(rm.jitter_buffer.nominal, 0, 65535, return TRUE);
+ IF_NUM_IN_RANGE(rm.jitter_buffer.max, 0, 65535, return TRUE);
+ IF_NUM_IN_RANGE(rm.jitter_buffer.abs_max, 0, 65535, return TRUE);
+
+ IF_NUM_IN_RANGE(rm.delay.round_trip_delay, 0, 65535, return TRUE);
+ IF_NUM_IN_RANGE(rm.delay.end_system_delay, 0, 65535, return TRUE);
+ IF_NUM_IN_RANGE(rm.delay.symm_one_way_delay, 0, 65535, return TRUE);
+ IF_NUM_IN_RANGE(rm.delay.interarrival_jitter, 0, 65535, return TRUE);
+ IF_NUM_IN_RANGE(rm.delay.mean_abs_jitter, 0, 65535, return TRUE);
+
+ if (rm.signal.level != 127) return TRUE;
+ if (rm.signal.noise_level != 127) return TRUE;
+
+ IF_NUM_IN_RANGE(rm.quality_estimates.rlq, 1, 120, return TRUE);
+ IF_NUM_IN_RANGE(rm.quality_estimates.rcq, 1, 120, return TRUE);
+
+ return FALSE;
+}
+
+static void append_metrics_to_buffer(char ** buffer, size_t * size, size_t * offset, const reporting_content_metrics_t rm) {
+ char * timestamps_start_str = NULL;
+ char * timestamps_stop_str = NULL;
+ char * network_packet_loss_rate_str = NULL;
+ char * jitter_buffer_discard_rate_str = NULL;
+ // char * gap_loss_density_str = NULL;
+ char * moslq_str = NULL;
+ char * moscq_str = NULL;
+
+ if (rm.timestamps.start > 0)
+ timestamps_start_str = linphone_timestamp_to_rfc3339_string(rm.timestamps.start);
+ if (rm.timestamps.stop > 0)
+ timestamps_stop_str = linphone_timestamp_to_rfc3339_string(rm.timestamps.stop);
+
+ IF_NUM_IN_RANGE(rm.packet_loss.network_packet_loss_rate, 0, 255, network_packet_loss_rate_str = float_to_one_decimal_string(rm.packet_loss.network_packet_loss_rate / 256));
+ IF_NUM_IN_RANGE(rm.packet_loss.jitter_buffer_discard_rate, 0, 255, jitter_buffer_discard_rate_str = float_to_one_decimal_string(rm.packet_loss.jitter_buffer_discard_rate / 256));
+ // IF_NUM_IN_RANGE(rm.burst_gap_loss.gap_loss_density, 0, 10, gap_loss_density_str = float_to_one_decimal_string(rm.burst_gap_loss.gap_loss_density));
+ IF_NUM_IN_RANGE(rm.quality_estimates.moslq, 1, 5, moslq_str = float_to_one_decimal_string(rm.quality_estimates.moslq));
+ IF_NUM_IN_RANGE(rm.quality_estimates.moscq, 1, 5, moscq_str = float_to_one_decimal_string(rm.quality_estimates.moscq));
+
+ append_to_buffer(buffer, size, offset, "Timestamps:");
+ APPEND_IF_NOT_NULL_STR(buffer, size, offset, " START=%s", timestamps_start_str);
+ APPEND_IF_NOT_NULL_STR(buffer, size, offset, " STOP=%s", timestamps_stop_str);
+
+ append_to_buffer(buffer, size, offset, "\r\nSessionDesc:");
+ APPEND_IF(buffer, size, offset, " PT=%d", rm.session_description.payload_type, rm.session_description.payload_type != -1);
+ APPEND_IF_NOT_NULL_STR(buffer, size, offset, " PD=%s", rm.session_description.payload_desc);
+ APPEND_IF(buffer, size, offset, " SR=%d", rm.session_description.sample_rate, rm.session_description.sample_rate != -1);
+ APPEND_IF(buffer, size, offset, " FD=%d", rm.session_description.frame_duration, rm.session_description.frame_duration != -1);
+ // append_to_buffer(buffer, size, offset, " FO=%d", rm.session_description.frame_ocets);
+ // append_to_buffer(buffer, size, offset, " FPP=%d", rm.session_description.frames_per_sec);
+ // append_to_buffer(buffer, size, offset, " PPS=%d", rm.session_description.packets_per_sec);
+ APPEND_IF_NOT_NULL_STR(buffer, size, offset, " FMTP=\"%s\"", rm.session_description.fmtp);
+ APPEND_IF(buffer, size, offset, " PLC=%d", rm.session_description.packet_loss_concealment, rm.session_description.packet_loss_concealment != -1);
+ // APPEND_IF_NOT_NULL_STR(buffer, size, offset, " SSUP=%s", rm.session_description.silence_suppression_state);
+
+ append_to_buffer(buffer, size, offset, "\r\nJitterBuffer:");
+ APPEND_IF_NUM_IN_RANGE(buffer, size, offset, " JBA=%d", rm.jitter_buffer.adaptive, 0, 3);
+ // APPEND_IF_NUM_IN_RANGE(buffer, size, offset, " JBR=%d", rm.jitter_buffer.rate, 0, 15);
+ APPEND_IF_NUM_IN_RANGE(buffer, size, offset, " JBN=%d", rm.jitter_buffer.nominal, 0, 65535);
+ APPEND_IF_NUM_IN_RANGE(buffer, size, offset, " JBM=%d", rm.jitter_buffer.max, 0, 65535);
+ APPEND_IF_NUM_IN_RANGE(buffer, size, offset, " JBX=%d", rm.jitter_buffer.abs_max, 0, 65535);
+
+ append_to_buffer(buffer, size, offset, "\r\nPacketLoss:");
+ APPEND_IF_NOT_NULL_STR(buffer, size, offset, " NLR=%s", network_packet_loss_rate_str);
+ APPEND_IF_NOT_NULL_STR(buffer, size, offset, " JDR=%s", jitter_buffer_discard_rate_str);
+
+ // append_to_buffer(buffer, size, offset, "\r\nBurstGapLoss:");
+ // append_to_buffer(buffer, size, offset, " BLD=%d", rm.burst_gap_loss.burst_loss_density);
+ // append_to_buffer(buffer, size, offset, " BD=%d", rm.burst_gap_loss.burst_duration);
+ // APPEND_IF_NOT_NULL_STR(buffer, size, offset, " GLD=%s", gap_loss_density_str);
+ // append_to_buffer(buffer, size, offset, " GD=%d", rm.burst_gap_loss.gap_duration);
+ // append_to_buffer(buffer, size, offset, " GMIN=%d", rm.burst_gap_loss.min_gap_threshold);
+
+ append_to_buffer(buffer, size, offset, "\r\nDelay:");
+ APPEND_IF_NUM_IN_RANGE(buffer, size, offset, " RTD=%d", rm.delay.round_trip_delay, 0, 65535);
+ APPEND_IF_NUM_IN_RANGE(buffer, size, offset, " ESD=%d", rm.delay.end_system_delay, 0, 65535);
+ // APPEND_IF_NUM_IN_RANGE(buffer, size, offset, " OWD=%d", rm.delay.one_way_delay, 0, 65535);
+ APPEND_IF_NUM_IN_RANGE(buffer, size, offset, " SOWD=%d", rm.delay.symm_one_way_delay, 0, 65535);
+ APPEND_IF_NUM_IN_RANGE(buffer, size, offset, " IAJ=%d", rm.delay.interarrival_jitter, 0, 65535);
+ APPEND_IF_NUM_IN_RANGE(buffer, size, offset, " MAJ=%d", rm.delay.mean_abs_jitter, 0, 65535);
+
+ append_to_buffer(buffer, size, offset, "\r\nSignal:");
+ APPEND_IF(buffer, size, offset, " SL=%d", rm.signal.level, rm.signal.level != 127);
+ APPEND_IF(buffer, size, offset, " NL=%d", rm.signal.noise_level, rm.signal.noise_level != 127);
+ // append_to_buffer(buffer, size, offset, " RERL=%d", rm.signal.residual_echo_return_loss);
+
+ append_to_buffer(buffer, size, offset, "\r\nQualityEst:");
+ APPEND_IF_NUM_IN_RANGE(buffer, size, offset, " RLQ=%d", rm.quality_estimates.rlq, 1, 120);
+ // APPEND_IF_NOT_NULL_STR(buffer, size, offset, " RLQEstAlg=%s", rm.quality_estimates.rlqestalg);
+ APPEND_IF_NUM_IN_RANGE(buffer, size, offset, " RCQ=%d", rm.quality_estimates.rcq, 1, 120);
+ // APPEND_IF_NOT_NULL_STR(buffer, size, offset, " RCQEstAlgo=%s", rm.quality_estimates.rcqestalg);
+ // append_to_buffer(buffer, size, offset, " EXTRI=%d", rm.quality_estimates.extri);
+ // APPEND_IF_NOT_NULL_STR(buffer, size, offset, " ExtRIEstAlg=%s", rm.quality_estimates.extriestalg);
+ // append_to_buffer(buffer, size, offset, " EXTRO=%d", rm.quality_estimates.extro);
+ // APPEND_IF_NOT_NULL_STR(buffer, size, offset, " ExtROEstAlg=%s", rm.quality_estimates.extroutestalg);
+ APPEND_IF_NOT_NULL_STR(buffer, size, offset, " MOSLQ=%s", moslq_str);
+ // APPEND_IF_NOT_NULL_STR(buffer, size, offset, " MOSLQEstAlgo=%s", rm.quality_estimates.moslqestalg);
+ APPEND_IF_NOT_NULL_STR(buffer, size, offset, " MOSCQ=%s", moscq_str);
+ // APPEND_IF_NOT_NULL_STR(buffer, size, offset, " MOSCQEstAlgo=%s", rm.quality_estimates.moscqestalg);
+ // APPEND_IF_NOT_NULL_STR(buffer, size, offset, " QoEEstAlg=%s", rm.quality_estimates.qoestalg);
+ append_to_buffer(buffer, size, offset, "\r\n");
+
+ ms_free(timestamps_start_str);
+ ms_free(timestamps_stop_str);
+ ms_free(network_packet_loss_rate_str);
+ ms_free(jitter_buffer_discard_rate_str);
+ // ms_free(gap_loss_density_str);
+ ms_free(moslq_str);
+ ms_free(moscq_str);
+}
+
+static void reporting_publish(const LinphoneCall* call, const reporting_session_report_t * report) {
+ LinphoneContent content = {0};
+ LinphoneAddress *addr;
+ int expires = -1;
+ size_t offset = 0;
+ size_t size = 2048;
+ char * buffer;
+
+ // if the call was hungup too early, we might have invalid IPs information
+ // in that case, we abort the report since it's not useful data
+ if (strlen(report->info.local_addr.ip) == 0 || strlen(report->info.remote_addr.ip) == 0) {
+ ms_warning("The call was hang up too early (duration: %d sec) and IP could "
+ "not be retrieved so dropping this report", linphone_call_get_duration(call));
+ return;
+ }
+
+ buffer = (char *) ms_malloc(size);
+ content.type = ms_strdup("application");
+ content.subtype = ms_strdup("vq-rtcpxr");
+
+ append_to_buffer(&buffer, &size, &offset, "VQSessionReport: CallTerm\r\n");
+ append_to_buffer(&buffer, &size, &offset, "CallID: %s\r\n", report->info.call_id);
+ append_to_buffer(&buffer, &size, &offset, "LocalID: %s\r\n", report->info.local_id);
+ append_to_buffer(&buffer, &size, &offset, "RemoteID: %s\r\n", report->info.remote_id);
+ append_to_buffer(&buffer, &size, &offset, "OrigID: %s\r\n", report->info.orig_id);
+
+ APPEND_IF_NOT_NULL_STR(&buffer, &size, &offset, "LocalGroup: %s\r\n", report->info.local_group);
+ APPEND_IF_NOT_NULL_STR(&buffer, &size, &offset, "RemoteGroup: %s\r\n", report->info.remote_group);
+ append_to_buffer(&buffer, &size, &offset, "LocalAddr: IP=%s PORT=%d SSRC=%d\r\n", report->info.local_addr.ip, report->info.local_addr.port, report->info.local_addr.ssrc);
+ APPEND_IF_NOT_NULL_STR(&buffer, &size, &offset, "LocalMAC: %s\r\n", report->info.local_mac_addr);
+ append_to_buffer(&buffer, &size, &offset, "RemoteAddr: IP=%s PORT=%d SSRC=%d\r\n", report->info.remote_addr.ip, report->info.remote_addr.port, report->info.remote_addr.ssrc);
+ APPEND_IF_NOT_NULL_STR(&buffer, &size, &offset, "RemoteMAC: %s\r\n", report->info.remote_mac_addr);
+
+ append_to_buffer(&buffer, &size, &offset, "LocalMetrics:\r\n");
+ append_metrics_to_buffer(&buffer, &size, &offset, report->local_metrics);
+
+ if (are_metrics_filled(report->remote_metrics)) {
+ append_to_buffer(&buffer, &size, &offset, "RemoteMetrics:\r\n");
+ append_metrics_to_buffer(&buffer, &size, &offset, report->remote_metrics);
+ }
+ APPEND_IF_NOT_NULL_STR(&buffer, &size, &offset, "DialogID: %s\r\n", report->dialog_id);
+
+ content.data = buffer;
+ content.size = strlen((char*)content.data);
+
+
+ addr = linphone_address_new(call->dest_proxy->reg_statistics_collector);
+ if (addr != NULL) {
+ linphone_core_publish(call->core, addr, "vq-rtcpxr", expires, &content);
+ linphone_address_destroy(addr);
+ } else {
+ ms_warning("Asked to submit reporting statistics but no collector address found");
+ }
+
+ linphone_content_uninit(&content);
+}
+
+static const SalStreamDescription * get_media_stream_for_desc(const SalMediaDescription * smd, SalStreamType sal_stream_type) {
+ int count;
+ if (smd != NULL) {
+ for (count = 0; count < smd->n_total_streams; ++count) {
+ if (smd->streams[count].type == sal_stream_type) {
+ return &smd->streams[count];
+ }
+ }
+ }
+ if (smd == NULL || count == smd->n_total_streams) {
+ ms_warning("Could not find the associated stream of type %d", sal_stream_type);
+ }
+
+ return NULL;
+}
+
+static void reporting_update_ip(LinphoneCall * call, int stats_type) {
+ SalStreamType sal_stream_type = (stats_type == LINPHONE_CALL_STATS_AUDIO) ? SalAudio : SalVideo;
+ if (call->log->reports[stats_type] != NULL) {
+ const SalStreamDescription * local_desc = get_media_stream_for_desc(call->localdesc, sal_stream_type);
+ const SalStreamDescription * remote_desc = get_media_stream_for_desc(sal_call_get_remote_media_description(call->op), sal_stream_type);
+
+ // local info are always up-to-date and correct
+ if (local_desc != NULL) {
+ call->log->reports[stats_type]->info.local_addr.port = local_desc->rtp_port;
+ STR_REASSIGN(call->log->reports[stats_type]->info.local_addr.ip, ms_strdup(local_desc->rtp_addr));
+ }
+
+ if (remote_desc != NULL) {
+ // port is always stored in stream description struct
+ call->log->reports[stats_type]->info.remote_addr.port = remote_desc->rtp_port;
+
+ // for IP it can be not set if we are using a direct route
+ if (remote_desc->rtp_addr != NULL && strlen(remote_desc->rtp_addr) > 0) {
+ STR_REASSIGN(call->log->reports[stats_type]->info.remote_addr.ip, ms_strdup(remote_desc->rtp_addr));
+ } else {
+ STR_REASSIGN(call->log->reports[stats_type]->info.remote_addr.ip, ms_strdup(sal_call_get_remote_media_description(call->op)->addr));
+ }
+ }
+ }
+}
+
+static bool_t reporting_enabled(const LinphoneCall * call) {
+ return (call->dest_proxy != NULL && linphone_proxy_config_send_statistics_enabled(call->dest_proxy));
+}
+
+void linphone_reporting_update_ip(LinphoneCall * call) {
+ // This function can be called in two different cases:
+ // - 1) at start when call is starting, remote ip/port info might be the proxy ones to which callee is registered
+ // - 2) later, if we found a direct route between caller and callee with ICE/Stun, ip/port are updated for the direct route access
+
+ if (! reporting_enabled(call))
+ return;
+
+ reporting_update_ip(call, LINPHONE_CALL_STATS_AUDIO);
+
+ if (linphone_call_params_video_enabled(linphone_call_get_current_params(call))) {
+ reporting_update_ip(call, LINPHONE_CALL_STATS_VIDEO);
+ }
+}
+
+void linphone_reporting_update(LinphoneCall * call, int stats_type) {
+ reporting_session_report_t * report = call->log->reports[stats_type];
+ MediaStream * stream = NULL;
+ const PayloadType * local_payload = NULL;
+ const PayloadType * remote_payload = NULL;
+ const LinphoneCallParams * current_params = linphone_call_get_current_params(call);
+
+ if (! reporting_enabled(call))
+ return;
+
+ STR_REASSIGN(report->info.call_id, ms_strdup(call->log->call_id));
+ STR_REASSIGN(report->info.local_group, ms_strdup_printf(_("linphone-%s-%s-%s"), (stats_type == LINPHONE_CALL_STATS_AUDIO ? "audio" : "video"),
+ linphone_core_get_user_agent_name(), report->info.call_id));
+ STR_REASSIGN(report->info.remote_group, ms_strdup_printf(_("linphone-%s-%s-%s"), (stats_type == LINPHONE_CALL_STATS_AUDIO ? "audio" : "video"),
+ linphone_call_get_remote_user_agent(call), report->info.call_id));
+
+ if (call->dir == LinphoneCallIncoming) {
+ STR_REASSIGN(report->info.remote_id, linphone_address_as_string(call->log->from));
+ STR_REASSIGN(report->info.local_id, linphone_address_as_string(call->log->to));
+ STR_REASSIGN(report->info.orig_id, ms_strdup(report->info.remote_id));
+ } else {
+ STR_REASSIGN(report->info.remote_id, linphone_address_as_string(call->log->to));
+ STR_REASSIGN(report->info.local_id, linphone_address_as_string(call->log->from));
+ STR_REASSIGN(report->info.orig_id, ms_strdup(report->info.local_id));
+ }
+
+ STR_REASSIGN(report->dialog_id, sal_op_get_dialog_id(call->op));
+
+ report->local_metrics.timestamps.start = call->log->start_date_time;
+ report->local_metrics.timestamps.stop = call->log->start_date_time + linphone_call_get_duration(call);
+
+ //we use same timestamps for remote too
+ report->remote_metrics.timestamps.start = call->log->start_date_time;
+ report->remote_metrics.timestamps.stop = call->log->start_date_time + linphone_call_get_duration(call);
+
+ // yet we use the same payload config for local and remote, since this is the largest use case
+ if (stats_type == LINPHONE_CALL_STATS_AUDIO && call->audiostream != NULL) {
+ stream = &call->audiostream->ms;
+ local_payload = linphone_call_params_get_used_audio_codec(current_params);
+ remote_payload = local_payload;
+ } else if (stats_type == LINPHONE_CALL_STATS_VIDEO && call->videostream != NULL) {
+ stream = &call->videostream->ms;
+ local_payload = linphone_call_params_get_used_video_codec(current_params);
+ remote_payload = local_payload;
+ }
+
+ if (stream != NULL) {
+ RtpSession * session = stream->sessions.rtp_session;
+
+ report->info.local_addr.ssrc = rtp_session_get_send_ssrc(session);
+ report->info.remote_addr.ssrc = rtp_session_get_recv_ssrc(session);
+ }
+
+ if (local_payload != NULL) {
+ report->local_metrics.session_description.payload_type = local_payload->type;
+ STR_REASSIGN(report->local_metrics.session_description.payload_desc, ms_strdup(local_payload->mime_type));
+ report->local_metrics.session_description.sample_rate = local_payload->clock_rate;
+ STR_REASSIGN(report->local_metrics.session_description.fmtp, ms_strdup(local_payload->recv_fmtp));
+ }
+
+ if (remote_payload != NULL) {
+ report->remote_metrics.session_description.payload_type = remote_payload->type;
+ STR_REASSIGN(report->remote_metrics.session_description.payload_desc, ms_strdup(remote_payload->mime_type));
+ report->remote_metrics.session_description.sample_rate = remote_payload->clock_rate;
+ STR_REASSIGN(report->remote_metrics.session_description.fmtp, ms_strdup(remote_payload->recv_fmtp));
+ }
+}
+
+void linphone_reporting_call_stats_updated(LinphoneCall *call, int stats_type) {
+ reporting_session_report_t * report = call->log->reports[stats_type];
+ reporting_content_metrics_t * metrics = NULL;
+
+ LinphoneCallStats stats = call->stats[stats_type];
+ mblk_t *block = NULL;
+
+ if (! reporting_enabled(call))
+ return;
+
+ if (stats.updated == LINPHONE_CALL_STATS_RECEIVED_RTCP_UPDATE) {
+ metrics = &report->remote_metrics;
+ if (rtcp_is_XR(stats.received_rtcp) == TRUE) {
+ block = stats.received_rtcp;
+ }
+ } else if (stats.updated == LINPHONE_CALL_STATS_SENT_RTCP_UPDATE) {
+ metrics = &report->local_metrics;
+ if (rtcp_is_XR(stats.sent_rtcp) == TRUE) {
+ block = stats.sent_rtcp;
+ }
+ }
+ if (block != NULL) {
+ switch (rtcp_XR_get_block_type(block)) {
+ case RTCP_XR_VOIP_METRICS: {
+ metrics->quality_estimates.rcq = rtcp_XR_voip_metrics_get_r_factor(block);
+ metrics->quality_estimates.moslq = rtcp_XR_voip_metrics_get_mos_lq(block) / 10.f;
+ metrics->quality_estimates.moscq = rtcp_XR_voip_metrics_get_mos_cq(block) / 10.f;
+
+ metrics->jitter_buffer.nominal = rtcp_XR_voip_metrics_get_jb_nominal(block);
+ metrics->jitter_buffer.max = rtcp_XR_voip_metrics_get_jb_maximum(block);
+ metrics->jitter_buffer.abs_max = rtcp_XR_voip_metrics_get_jb_abs_max(block);
+ metrics->packet_loss.network_packet_loss_rate = rtcp_XR_voip_metrics_get_loss_rate(block);
+ metrics->packet_loss.jitter_buffer_discard_rate = rtcp_XR_voip_metrics_get_discard_rate(block);
+
+ uint8_t config = rtcp_XR_voip_metrics_get_rx_config(block);
+ metrics->session_description.packet_loss_concealment = (config >> 6) & 0x3;
+ metrics->jitter_buffer.adaptive = (config >> 4) & 0x3;
+ break;
+ } default: {
+ break;
+ }
+ }
+ }
+}
+
+void linphone_reporting_publish(LinphoneCall* call) {
+ if (! reporting_enabled(call))
+ return;
+
+
+ if (call->log->reports[LINPHONE_CALL_STATS_AUDIO] != NULL) {
+ reporting_publish(call, call->log->reports[LINPHONE_CALL_STATS_AUDIO]);
+ }
+
+ if (call->log->reports[LINPHONE_CALL_STATS_VIDEO] != NULL
+ && linphone_call_params_video_enabled(linphone_call_get_current_params(call))) {
+ reporting_publish(call, call->log->reports[LINPHONE_CALL_STATS_VIDEO]);
+ }
+}
+
+reporting_session_report_t * linphone_reporting_new() {
+ int i;
+ reporting_session_report_t * rm = ms_new0(reporting_session_report_t,1);
+
+ reporting_content_metrics_t * metrics[2] = {&rm->local_metrics, &rm->remote_metrics};
+ for (i = 0; i < 2; i++) {
+ metrics[i]->session_description.payload_type = -1;
+ metrics[i]->session_description.sample_rate = -1;
+ metrics[i]->session_description.frame_duration = -1;
+
+ metrics[i]->packet_loss.network_packet_loss_rate = -1;
+ metrics[i]->packet_loss.jitter_buffer_discard_rate = -1;
+
+ metrics[i]->session_description.packet_loss_concealment = -1;
+
+ metrics[i]->jitter_buffer.adaptive = -1;
+ // metrics[i]->jitter_buffer.rate = -1;
+ metrics[i]->jitter_buffer.nominal = -1;
+ metrics[i]->jitter_buffer.max = -1;
+ metrics[i]->jitter_buffer.abs_max = -1;
+
+ metrics[i]->delay.round_trip_delay = -1;
+ metrics[i]->delay.end_system_delay = -1;
+ // metrics[i]->delay.one_way_delay = -1;
+ metrics[i]->delay.symm_one_way_delay = -1;
+ metrics[i]->delay.interarrival_jitter = -1;
+ metrics[i]->delay.mean_abs_jitter = -1;
+
+ metrics[i]->signal.level = 127;
+ metrics[i]->signal.noise_level = 127;
+ }
+ return rm;
+}
+
+void linphone_reporting_destroy(reporting_session_report_t * report) {
+ if (report->info.call_id != NULL) ms_free(report->info.call_id);
+ if (report->info.local_id != NULL) ms_free(report->info.local_id);
+ if (report->info.remote_id != NULL) ms_free(report->info.remote_id);
+ if (report->info.orig_id != NULL) ms_free(report->info.orig_id);
+ if (report->info.local_addr.ip != NULL) ms_free(report->info.local_addr.ip);
+ if (report->info.remote_addr.ip != NULL) ms_free(report->info.remote_addr.ip);
+ if (report->info.local_group != NULL) ms_free(report->info.local_group);
+ if (report->info.remote_group != NULL) ms_free(report->info.remote_group);
+ if (report->info.local_mac_addr != NULL) ms_free(report->info.local_mac_addr);
+ if (report->info.remote_mac_addr != NULL) ms_free(report->info.remote_mac_addr);
+ if (report->dialog_id != NULL) ms_free(report->dialog_id);
+ if (report->local_metrics.session_description.fmtp != NULL) ms_free(report->local_metrics.session_description.fmtp);
+ if (report->local_metrics.session_description.payload_desc != NULL) ms_free(report->local_metrics.session_description.payload_desc);
+ if (report->remote_metrics.session_description.fmtp != NULL) ms_free(report->remote_metrics.session_description.fmtp);
+ if (report->remote_metrics.session_description.payload_desc != NULL) ms_free(report->remote_metrics.session_description.payload_desc);
+
+ ms_free(report);
+}
diff --git a/coreapi/quality_reporting.h b/coreapi/quality_reporting.h
new file mode 100644
index 000000000..c817feba9
--- /dev/null
+++ b/coreapi/quality_reporting.h
@@ -0,0 +1,149 @@
+/*
+linphone
+Copyright (C) 2014 - Belledonne Communications, Grenoble, France
+
+This program is free software; you can redistribute it and/or
+modify it under the terms of the GNU General Public License
+as published by the Free Software Foundation; either version 2
+of the License, or (at your option) any later version.
+
+This program is distributed in the hope that it will be useful,
+but WITHOUT ANY WARRANTY; without even the implied warranty of
+MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+GNU General Public License for more details.
+
+You should have received a copy of the GNU General Public License
+along with this program; if not, write to the Free Software
+Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA.
+*/
+
+#ifndef quality_reporting_h
+#define quality_reporting_h
+
+#include "linphonecore.h"
+
+#ifdef __cplusplus
+extern "C"{
+#endif
+
+typedef struct reporting_addr {
+ char * ip;
+ int port;
+ uint32_t ssrc;
+} reporting_addr_t;
+
+typedef struct reporting_content_metrics {
+ // timestamps - mandatory
+ struct {
+ time_t start;
+ time_t stop;
+ } timestamps;
+
+ // session description - optional
+ struct {
+ int payload_type;
+ char * payload_desc; // mime type
+ int sample_rate; // clock rate
+ int frame_duration; // to check (ptime?) - audio only
+ // int frame_ocets;
+ // int frames_per_sec;
+ // int packets_per_sec;
+ char * fmtp;
+ int packet_loss_concealment; // in voip metrics - audio only
+ // char * silence_suppression_state;
+ } session_description;
+
+ // jitter buffet - optional
+ struct {
+ int adaptive; // constant
+ // int rate; // constant
+ int nominal; // no may vary during the call <- average? worst score?
+ int max; // no may vary during the call <- average?
+ int abs_max; // constant
+ } jitter_buffer;
+
+ // packet loss - optional
+ struct {
+ float network_packet_loss_rate; // voip metrics (loss rate) + conversion
+ float jitter_buffer_discard_rate; //idem
+ } packet_loss;
+
+ // burst gap loss - optional
+ // (no) currently not implemented
+ // struct {
+ // int burst_loss_density;
+ // int burst_duration;
+ // float gap_loss_density;
+ // int gap_duration;
+ // int min_gap_threshold;
+ // } burst_gap_loss;
+
+ // delay - optional
+ struct {
+ int round_trip_delay; // no - vary
+ int end_system_delay; // no - not implemented yet
+ // int one_way_delay;
+ int symm_one_way_delay; // no - vary (depends on round_trip_delay) + not implemented (depends on end_system_delay)
+ int interarrival_jitter; // no - not implemented yet
+ int mean_abs_jitter; // to check
+ } delay;
+
+ // signal - optional
+ struct {
+ int level; // no - vary
+ int noise_level; // no - vary
+ // int residual_echo_return_loss;
+ } signal;
+
+ // quality estimates - optional
+ struct {
+ int rlq; // linked to moslq - in [0..120]
+ int rcq; //voip metrics R factor - no - vary or avg in [0..120]
+ float moslq; // no - vary or avg - voip metrics - in [0..4.9]
+ float moscq; // no - vary or avg - voip metrics - in [0..4.9]
+
+
+ // int extri;
+ // int extro;
+ // char * rlqestalg;
+ // char * rcqestalg;
+ // char * moslqestalg;
+ // char * moscqestalg;
+ // char * extriestalg;
+ // char * extroutestalg;
+ // char * qoestalg;
+ } quality_estimates;
+} reporting_content_metrics_t;
+
+typedef struct reporting_session_report {
+ struct {
+ char * call_id;
+ char * local_id;
+ char * remote_id;
+ char * orig_id;
+ reporting_addr_t local_addr;
+ reporting_addr_t remote_addr;
+ char * local_group;
+ char * remote_group;
+
+ char * local_mac_addr; // optional
+ char * remote_mac_addr; // optional
+ } info;
+
+ reporting_content_metrics_t local_metrics;
+ reporting_content_metrics_t remote_metrics; // optional
+
+ char * dialog_id; // optional
+} reporting_session_report_t;
+
+reporting_session_report_t * linphone_reporting_new();
+void linphone_reporting_destroy(reporting_session_report_t * report);
+void linphone_reporting_update(LinphoneCall * call, int stats_type);
+void linphone_reporting_update_ip(LinphoneCall * call);
+void linphone_reporting_publish(LinphoneCall* call);
+void linphone_reporting_call_stats_updated(LinphoneCall *call, int stats_type);
+#ifdef __cplusplus
+}
+#endif
+
+#endif
diff --git a/coreapi/sal.c b/coreapi/sal.c
index 6f2256ac9..5404210f3 100644
--- a/coreapi/sal.c
+++ b/coreapi/sal.c
@@ -17,9 +17,9 @@ along with this program; if not, write to the Free Software
Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA.
*/
-/**
+/**
This header files defines the Signaling Abstraction Layer.
- The purpose of this layer is too allow experiment different call signaling
+ The purpose of this layer is too allow experiment different call signaling
protocols and implementations under linphone, for example SIP, JINGLE...
**/
#ifdef HAVE_CONFIG_H
@@ -39,7 +39,7 @@ const char* sal_transport_to_string(SalTransport transport) {
default: {
ms_fatal("Unexpected transport [%i]",transport);
return NULL;
- }
+ }
}
}
@@ -372,6 +372,14 @@ 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;
@@ -401,17 +409,17 @@ void __sal_op_free(SalOp *op){
sal_address_destroy(b->to_address);
b->to_address=NULL;
}
-
+
if (b->service_route){
sal_address_destroy(b->service_route);
b->service_route=NULL;
}
-
+
if (b->origin_address){
sal_address_destroy(b->origin_address);
b->origin_address=NULL;
}
-
+
if (b->from) {
ms_free(b->from);
b->from=NULL;
@@ -616,7 +624,7 @@ static int line_get_value(const char *input, const char *key, char *value, size_
int sal_lines_get_value(const char *data, const char *key, char *value, size_t value_size){
int read=0;
-
+
do{
if (line_get_value(data,key,value,value_size,&read))
return TRUE;
@@ -626,7 +634,7 @@ int sal_lines_get_value(const char *data, const char *key, char *value, size_t v
}
int sal_body_has_type(const SalBody *body, const char *type, const char *subtype){
- return body->type && body->subtype
+ return body->type && body->subtype
&& strcmp(body->type,type)==0
&& strcmp(body->subtype,subtype)==0;
}
diff --git a/include/sal/sal.h b/include/sal/sal.h
index d0215c89c..0d5cea3cc 100644
--- a/include/sal/sal.h
+++ b/include/sal/sal.h
@@ -17,9 +17,9 @@ along with this program; if not, write to the Free Software
Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA.
*/
-/**
+/**
This header files defines the Signaling Abstraction Layer.
- The purpose of this layer is too allow experiment different call signaling
+ The purpose of this layer is too allow experiment different call signaling
protocols and implementations under linphone, for example SIP, JINGLE...
**/
@@ -571,6 +571,7 @@ const SalAddress *sal_op_get_network_origin_address(const SalOp *op);
const char *sal_op_get_remote_ua(const SalOp *op);
void *sal_op_get_user_pointer(const SalOp *op);
const char* sal_op_get_call_id(const SalOp *op);
+char* sal_op_get_dialog_id(const SalOp *op);
const SalAddress* sal_op_get_service_route(const SalOp *op);
void sal_op_set_service_route(SalOp *op,const SalAddress* service_route);
diff --git a/tester/call_tester.c b/tester/call_tester.c
index facec8769..391893951 100644
--- a/tester/call_tester.c
+++ b/tester/call_tester.c
@@ -119,10 +119,10 @@ void liblinphone_tester_check_rtcp(LinphoneCoreManager* caller, LinphoneCoreMana
c1=linphone_core_get_current_call(caller->lc);
c2=linphone_core_get_current_call(callee->lc);
-
+
CU_ASSERT_PTR_NOT_NULL(c1);
CU_ASSERT_PTR_NOT_NULL(c2);
-
+
if (!c1 || !c2) return;
for (i=0; i<24 /*=12s need at least one exchange of SR to maybe 10s*/; i++) {
@@ -339,7 +339,7 @@ static void cancelled_call(void) {
static void disable_all_codecs_except_one(LinphoneCore *lc, const char *mime){
const MSList *elem=linphone_core_get_audio_codecs(lc);
PayloadType *pt;
-
+
for(;elem!=NULL;elem=elem->next){
pt=(PayloadType*)elem->data;
linphone_core_enable_payload_type(lc,pt,FALSE);
@@ -374,7 +374,7 @@ static void call_with_dns_time_out(void) {
LinphoneCoreManager* marie = linphone_core_manager_new2( "empty_rc", FALSE);
LCSipTransports transport = {9773,0,0,0};
int i;
-
+
linphone_core_set_sip_transports(marie->lc,&transport);
linphone_core_iterate(marie->lc);
sal_set_dns_timeout(marie->lc->sal,0);
@@ -382,7 +382,7 @@ static void call_with_dns_time_out(void) {
for(i=0;i<10;i++){
ms_usleep(200000);
linphone_core_iterate(marie->lc);
- }
+ }
CU_ASSERT_EQUAL(marie->stat.number_of_LinphoneCallOutgoingInit,1);
CU_ASSERT_EQUAL(marie->stat.number_of_LinphoneCallOutgoingProgress,1);
CU_ASSERT_EQUAL(marie->stat.number_of_LinphoneCallError,1);
@@ -395,21 +395,21 @@ static void early_cancelled_call(void) {
LinphoneCoreManager* pauline = linphone_core_manager_new2( "empty_rc",FALSE);
LinphoneCall* out_call = linphone_core_invite_address(pauline->lc,marie->identity);
-
+
CU_ASSERT_TRUE(wait_for(pauline->lc,marie->lc,&pauline->stat.number_of_LinphoneCallOutgoingInit,1));
linphone_core_terminate_call(pauline->lc,out_call);
-
+
/*since everything is executed in a row, no response can be received from the server, thus the CANCEL cannot be sent.
It will ring at Marie's side.*/
-
+
CU_ASSERT_TRUE(wait_for(pauline->lc,marie->lc,&pauline->stat.number_of_LinphoneCallEnd,1));
-
+
CU_ASSERT_EQUAL(pauline->stat.number_of_LinphoneCallEnd,1);
-
+
CU_ASSERT_TRUE(wait_for(pauline->lc,marie->lc,&marie->stat.number_of_LinphoneCallIncomingReceived,1));
/* now the CANCEL should have been sent and the the call at marie's side should terminate*/
CU_ASSERT_TRUE(wait_for(pauline->lc,marie->lc,&marie->stat.number_of_LinphoneCallEnd,1));
-
+
CU_ASSERT_TRUE(wait_for(pauline->lc,marie->lc,&pauline->stat.number_of_LinphoneCallReleased,1));
linphone_core_manager_destroy(marie);
@@ -449,7 +449,7 @@ static void early_declined_call(void) {
CU_ASSERT_TRUE(wait_for_until(pauline->lc,marie->lc,&pauline->stat.number_of_LinphoneCallError,1,33000));
CU_ASSERT_EQUAL(pauline->stat.number_of_LinphoneCallError,1);
/* FIXME http://git.linphone.org/mantis/view.php?id=757
-
+
CU_ASSERT_EQUAL(linphone_call_get_reason(out_call),LinphoneReasonBusy);
*/
if (ms_list_size(linphone_core_get_call_logs(pauline->lc))>0) {
@@ -489,7 +489,7 @@ static void call_declined(void) {
static void call_terminated_by_caller(void) {
LinphoneCoreManager* marie = linphone_core_manager_new( "marie_rc");
LinphoneCoreManager* pauline = linphone_core_manager_new( "pauline_rc");
-
+
CU_ASSERT_TRUE(call(pauline,marie));
/*just to sleep*/
linphone_core_terminate_all_calls(pauline->lc);
@@ -503,7 +503,7 @@ static void call_terminated_by_caller(void) {
static void call_with_no_sdp(void) {
LinphoneCoreManager* marie = linphone_core_manager_new( "marie_rc");
LinphoneCoreManager* pauline = linphone_core_manager_new( "pauline_rc");
-
+
linphone_core_enable_sdp_200_ack(marie->lc,TRUE);
CU_ASSERT_TRUE(call(marie,pauline));
@@ -523,7 +523,7 @@ static bool_t check_ice(LinphoneCoreManager* caller, LinphoneCoreManager* callee
c1=linphone_core_get_current_call(caller->lc);
c2=linphone_core_get_current_call(callee->lc);
-
+
CU_ASSERT_PTR_NOT_NULL(c1);
CU_ASSERT_PTR_NOT_NULL(c2);
@@ -556,7 +556,7 @@ static bool_t check_ice(LinphoneCoreManager* caller, LinphoneCoreManager* callee
static void _call_with_ice(bool_t caller_with_ice, bool_t callee_with_ice, bool_t random_ports) {
LinphoneCoreManager* marie = linphone_core_manager_new( "marie_rc");
LinphoneCoreManager* pauline = linphone_core_manager_new( "pauline_rc");
-
+
if (callee_with_ice){
linphone_core_set_firewall_policy(marie->lc,LinphonePolicyUseIce);
linphone_core_set_stun_server(marie->lc,"stun.linphone.org");
@@ -565,7 +565,7 @@ static void _call_with_ice(bool_t caller_with_ice, bool_t callee_with_ice, bool_
linphone_core_set_firewall_policy(pauline->lc,LinphonePolicyUseIce);
linphone_core_set_stun_server(pauline->lc,"stun.linphone.org");
}
-
+
if (random_ports){
linphone_core_set_audio_port(marie->lc,-1);
linphone_core_set_video_port(marie->lc,-1);
@@ -580,10 +580,10 @@ static void _call_with_ice(bool_t caller_with_ice, bool_t callee_with_ice, bool_
/*wait for the ICE reINVITE to complete*/
CU_ASSERT_TRUE(wait_for(pauline->lc,marie->lc,&pauline->stat.number_of_LinphoneCallStreamsRunning,2));
CU_ASSERT_TRUE(wait_for(pauline->lc,marie->lc,&marie->stat.number_of_LinphoneCallStreamsRunning,2));
-
+
CU_ASSERT_TRUE(check_ice(pauline,marie,LinphoneIceStateHostConnection));
}
-
+
liblinphone_tester_check_rtcp(marie,pauline);
/*then close the call*/
linphone_core_terminate_all_calls(pauline->lc);
@@ -629,17 +629,17 @@ static void call_with_custom_headers(void) {
ms_free(tmp);
linphone_address_destroy(marie->identity);
marie->identity=marie_identity;
-
+
params=linphone_core_create_default_call_parameters(marie->lc);
linphone_call_params_add_custom_header(params,"Weather","bad");
linphone_call_params_add_custom_header(params,"Working","yes");
-
+
CU_ASSERT_TRUE(call_with_caller_params(pauline,marie,params));
linphone_call_params_destroy(params);
-
+
call_marie=linphone_core_get_current_call(marie->lc);
call_pauline=linphone_core_get_current_call(pauline->lc);
-
+
CU_ASSERT_PTR_NOT_NULL(call_marie);
CU_ASSERT_PTR_NOT_NULL(call_pauline);
@@ -824,12 +824,12 @@ static void call_with_video_added(void) {
static void call_with_video_added_random_ports(void) {
LinphoneCoreManager* marie = linphone_core_manager_new( "marie_rc");
LinphoneCoreManager* pauline = linphone_core_manager_new( "pauline_rc");
-
+
linphone_core_set_audio_port(marie->lc,-1);
linphone_core_set_video_port(marie->lc,-1);
linphone_core_set_audio_port(pauline->lc,-1);
linphone_core_set_video_port(pauline->lc,-1);
-
+
CU_ASSERT_TRUE(call(pauline,marie));
CU_ASSERT_TRUE(add_video(pauline,marie));
@@ -922,17 +922,17 @@ static void _call_with_media_relay(bool_t random_ports) {
LinphoneCoreManager* pauline = linphone_core_manager_new( "pauline_rc");
linphone_core_set_user_agent(marie->lc,"Natted Linphone",NULL);
linphone_core_set_user_agent(pauline->lc,"Natted Linphone",NULL);
-
+
if (random_ports){
linphone_core_set_audio_port(marie->lc,-1);
linphone_core_set_video_port(marie->lc,-1);
linphone_core_set_audio_port(pauline->lc,-1);
linphone_core_set_video_port(pauline->lc,-1);
}
-
+
CU_ASSERT_TRUE(call(pauline,marie));
liblinphone_tester_check_rtcp(pauline,marie);
-
+
#ifdef VIDEO_ENABLED
CU_ASSERT_TRUE(add_video(pauline,marie));
liblinphone_tester_check_rtcp(pauline,marie);
@@ -1018,7 +1018,7 @@ static void call_with_privacy2(void) {
LinphoneProxyConfig* pauline_proxy;
params=linphone_core_create_default_call_parameters(pauline->lc);
linphone_call_params_set_privacy(params,LinphonePrivacyId);
-
+
linphone_core_get_default_proxy(pauline->lc,&pauline_proxy);
linphone_proxy_config_edit(pauline_proxy);
linphone_proxy_config_enable_register(pauline_proxy,FALSE);
@@ -1212,8 +1212,6 @@ static void simple_conference(void) {
ms_list_free(lcs);
}
-
-
static void srtp_call() {
call_base(LinphoneMediaEncryptionSRTP,FALSE,FALSE,LinphonePolicyNoFirewall);
}
@@ -1349,19 +1347,19 @@ static void early_media_call(void) {
CU_ASSERT_EQUAL(marie->stat.number_of_LinphoneCallIncomingEarlyMedia,1);
CU_ASSERT_EQUAL(pauline->stat.number_of_LinphoneCallOutgoingEarlyMedia,1);
-
+
wait_for_until(pauline->lc,marie->lc,NULL,0,1000);
-
+
/*added because a bug related to early-media caused the Connected state to be reached two times*/
CU_ASSERT_EQUAL(marie->stat.number_of_LinphoneCallConnected,1);
-
+
/*just to sleep*/
linphone_core_terminate_all_calls(marie->lc);
CU_ASSERT_TRUE(wait_for(pauline->lc,marie->lc,&pauline->stat.number_of_LinphoneCallEnd,1));
CU_ASSERT_TRUE(wait_for(pauline->lc,marie->lc,&marie->stat.number_of_LinphoneCallEnd,1));
-
-
+
+
linphone_core_manager_destroy(marie);
linphone_core_manager_destroy(pauline);
}
@@ -1378,7 +1376,7 @@ static void early_media_call_with_ringing(void){
/*
Marie calls Pauline, and after the call has rung, transitions to an early_media session
*/
-
+
/*use playfile for callee to avoid locking on capture card*/
linphone_core_use_files (pauline->lc,TRUE);
snprintf(hellopath,sizeof(hellopath), "%s/sounds/hello8000.wav", liblinphone_tester_file_prefix);
@@ -1414,7 +1412,7 @@ static void early_media_call_with_ringing(void){
ms_list_free(lcs);
-
+
linphone_core_manager_destroy(marie);
linphone_core_manager_destroy(pauline);
}
@@ -1537,9 +1535,9 @@ static void simple_call_transfer(void) {
CU_ASSERT_TRUE(wait_for_list(lcs,&marie->stat.number_of_LinphoneCallPaused,1,2000));
/*marie calling laure*/
CU_ASSERT_TRUE(wait_for_list(lcs,&marie->stat.number_of_LinphoneCallOutgoingProgress,1,2000));
-
+
CU_ASSERT_PTR_NOT_NULL(linphone_call_get_transfer_target_call(marie_calling_pauline));
-
+
CU_ASSERT_TRUE(wait_for_list(lcs,&pauline->stat.number_of_LinphoneTransferCallOutgoingInit,1,2000));
CU_ASSERT_TRUE(wait_for_list(lcs,&laure->stat.number_of_LinphoneCallIncomingReceived,1,2000));
CU_ASSERT_TRUE(wait_for_list(lcs,&marie->stat.number_of_LinphoneCallOutgoingRinging,1,2000));
@@ -1549,11 +1547,11 @@ static void simple_call_transfer(void) {
CU_ASSERT_TRUE(wait_for_list(lcs,&laure->stat.number_of_LinphoneCallStreamsRunning,1,2000));
CU_ASSERT_TRUE(wait_for_list(lcs,&marie->stat.number_of_LinphoneCallConnected,1,2000));
CU_ASSERT_TRUE(wait_for_list(lcs,&marie->stat.number_of_LinphoneCallStreamsRunning,1,2000));
-
+
marie_calling_laure=linphone_core_get_current_call(marie->lc);
CU_ASSERT_PTR_NOT_NULL_FATAL(marie_calling_laure);
CU_ASSERT_TRUE(linphone_call_get_transferer_call(marie_calling_laure)==marie_calling_pauline);
-
+
CU_ASSERT_TRUE(wait_for_list(lcs,&pauline->stat.number_of_LinphoneTransferCallConnected,1,2000));
/*terminate marie to pauline call*/
@@ -1587,11 +1585,11 @@ static void unattended_call_transfer(void) {
linphone_core_transfer_call(marie->lc,pauline_called_by_marie,laure_identity);
CU_ASSERT_TRUE(wait_for_list(lcs,&pauline->stat.number_of_LinphoneCallRefered,1,2000));
-
+
/*marie ends the call */
linphone_core_terminate_call(marie->lc,pauline_called_by_marie);
CU_ASSERT_TRUE(wait_for_list(lcs,&pauline->stat.number_of_LinphoneCallEnd,1,2000));
-
+
/*Pauline starts the transfer*/
CU_ASSERT_TRUE(wait_for_list(lcs,&pauline->stat.number_of_LinphoneCallOutgoingInit,1,2000));
CU_ASSERT_TRUE(wait_for_list(lcs,&pauline->stat.number_of_LinphoneCallOutgoingProgress,1,2000));
@@ -1628,21 +1626,21 @@ static void unattended_call_transfer_with_error(void) {
linphone_core_transfer_call(marie->lc,pauline_called_by_marie,"unknown_user");
CU_ASSERT_TRUE(wait_for_list(lcs,&pauline->stat.number_of_LinphoneCallRefered,1,2000));
-
+
/*Pauline starts the transfer*/
CU_ASSERT_TRUE(wait_for_list(lcs,&pauline->stat.number_of_LinphoneCallOutgoingInit,1,2000));
/* and immediately get an error*/
CU_ASSERT_TRUE(wait_for_list(lcs,&pauline->stat.number_of_LinphoneCallError,1,2000));
-
+
/*the error must be reported back to marie*/
CU_ASSERT_TRUE(wait_for_list(lcs,&marie->stat.number_of_LinphoneTransferCallError,1,2000));
/*and pauline should resume the call automatically*/
CU_ASSERT_TRUE(wait_for_list(lcs,&pauline->stat.number_of_LinphoneCallResuming,1,2000));
-
+
/*and call should be resumed*/
CU_ASSERT_TRUE(wait_for_list(lcs,&marie->stat.number_of_LinphoneCallStreamsRunning,1,2000));
-
+
linphone_core_manager_destroy(marie);
linphone_core_manager_destroy(pauline);
ms_list_free(lcs);
@@ -1796,7 +1794,7 @@ static void call_established_with_rejected_incoming_reinvite(void) {
LinphoneCoreManager* pauline = linphone_core_manager_new( "pauline_rc");
CU_ASSERT_TRUE(call(pauline,marie));
-
+
/*wait for ACK to be transmitted before going to reINVITE*/
wait_for_until(marie->lc,pauline->lc,NULL,0,1000);
@@ -1841,7 +1839,7 @@ static void call_redirect(void){
/*
Marie calls Pauline, which will redirect the call to Laure via a 302
*/
-
+
/*use playfile for callee to avoid locking on capture card*/
linphone_core_use_files (pauline->lc,TRUE);
linphone_core_use_files (laure->lc,TRUE);
@@ -1879,7 +1877,7 @@ static void call_redirect(void){
CU_ASSERT_TRUE(wait_for_list(lcs,&marie->stat.number_of_LinphoneCallEnd,1,1000));
ms_list_free(lcs);
-
+
linphone_core_manager_destroy(marie);
linphone_core_manager_destroy(pauline);
linphone_core_manager_destroy(laure);
@@ -1968,6 +1966,98 @@ static void call_rejected_without_403_because_wrong_credentials_no_auth_req_cb()
call_rejected_because_wrong_credentials_with_params("tester-no-403",FALSE);
}
+void create_call_for_statistics_tests(
+ LinphoneCoreManager* marie,
+ LinphoneCoreManager* pauline,
+ LinphoneCall** call_marie,
+ LinphoneCall** call_pauline) {
+ CU_ASSERT_TRUE(call(pauline,marie));
+ *call_marie = linphone_core_get_current_call(marie->lc);
+ *call_pauline = linphone_core_get_current_call(pauline->lc);
+ CU_ASSERT_PTR_NOT_NULL(*call_marie);
+ CU_ASSERT_PTR_NOT_NULL(*call_pauline);
+}
+
+static void statistics_not_used_without_config() {
+ LinphoneCoreManager* marie = linphone_core_manager_new( "marie_rc");
+ LinphoneCoreManager* pauline = linphone_core_manager_new( "pauline_rc");
+ LinphoneCall* call_marie = NULL;
+ LinphoneCall* call_pauline = NULL;
+
+ create_call_for_statistics_tests(marie, pauline, &call_marie, &call_pauline);
+
+ // marie has stats collection enabled since pauline has not
+ CU_ASSERT_TRUE(linphone_proxy_config_send_statistics_enabled(call_marie->dest_proxy));
+ CU_ASSERT_FALSE(linphone_proxy_config_send_statistics_enabled(call_pauline->dest_proxy));
+
+ CU_ASSERT_EQUAL(strcmp("sip:collector@sip.example.org",
+ linphone_proxy_config_get_statistics_collector(call_marie->dest_proxy)), 0);
+
+ // this field should be already filled
+ CU_ASSERT_PTR_NOT_NULL(call_marie->log->reports[0]->info.local_addr.ip);
+ CU_ASSERT_PTR_NULL(call_pauline->log->reports[0]->info.local_addr.ip);
+
+ // but not this one since it is updated at the end of call
+ CU_ASSERT_PTR_NULL(call_marie->log->reports[0]->dialog_id);
+
+ linphone_core_manager_destroy(marie);
+ linphone_core_manager_destroy(pauline);
+}
+static void statistics_not_sent_if_call_not_started() {
+ LinphoneCoreManager* marie = linphone_core_manager_new( "marie_rc");
+ LinphoneCoreManager* pauline = linphone_core_manager_new( "pauline_rc");
+ LinphoneCallLog* out_call_log;
+ LinphoneCall* out_call;
+
+ linphone_core_set_max_calls(pauline->lc,0);
+ out_call = linphone_core_invite(marie->lc,"pauline");
+ linphone_call_ref(out_call);
+
+ CU_ASSERT_TRUE(wait_for(marie->lc,pauline->lc,&marie->stat.number_of_LinphoneCallError,1));
+ CU_ASSERT_EQUAL(marie->stat.number_of_LinphoneCallError,1);
+
+ if (ms_list_size(linphone_core_get_call_logs(marie->lc))>0) {
+ CU_ASSERT_PTR_NOT_NULL(out_call_log=(LinphoneCallLog*)(linphone_core_get_call_logs(marie->lc)->data));
+ CU_ASSERT_EQUAL(linphone_call_log_get_status(out_call_log),LinphoneCallAborted);
+ }
+ linphone_call_unref(out_call);
+
+ // wait a few time...
+ wait_for(marie->lc,NULL,NULL,0);
+ // since the callee was busy, there should be no publish to do
+ CU_ASSERT_EQUAL(marie->stat.number_of_LinphonePublishProgress,0);
+ CU_ASSERT_EQUAL(marie->stat.number_of_LinphonePublishOk,0);
+
+ linphone_core_manager_destroy(marie);
+ linphone_core_manager_destroy(pauline);
+}
+static void statistics_sent_at_call_termination() {
+ // int return_code = -1;
+ LinphoneCoreManager* marie = linphone_core_manager_new( "marie_rc");
+ LinphoneCoreManager* pauline = linphone_core_manager_new( "pauline_rc");
+ LinphoneCall* call_marie = NULL;
+ LinphoneCall* call_pauline = NULL;
+
+ create_call_for_statistics_tests(marie, pauline, &call_marie, &call_pauline);
+
+ linphone_core_terminate_all_calls(marie->lc);
+ CU_ASSERT_TRUE(wait_for(marie->lc,pauline->lc,&marie->stat.number_of_LinphoneCallReleased,1));
+ CU_ASSERT_TRUE(wait_for(pauline->lc,NULL,&pauline->stat.number_of_LinphoneCallReleased,1));
+
+ CU_ASSERT_PTR_NULL(linphone_core_get_current_call(marie->lc));
+ CU_ASSERT_PTR_NULL(linphone_core_get_current_call(pauline->lc));
+
+ // now dialog id should be filled
+ CU_ASSERT_PTR_NOT_NULL(call_marie->log->reports[0]->dialog_id);
+
+ // PUBLISH submission to the collector should be ok
+ CU_ASSERT_TRUE(wait_for(marie->lc,NULL,&marie->stat.number_of_LinphonePublishProgress,1));
+ CU_ASSERT_TRUE(wait_for(marie->lc,NULL,&marie->stat.number_of_LinphonePublishOk,1));
+
+ linphone_core_manager_destroy(marie);
+ linphone_core_manager_destroy(pauline);
+}
+
#ifdef VIDEO_ENABLED
#endif
@@ -2027,7 +2117,10 @@ test_t call_tests[] = {
{ "Call established with rejected RE-INVITE",call_established_with_rejected_reinvite},
{ "Call established with rejected incoming RE-INVITE", call_established_with_rejected_incoming_reinvite },
{ "Call established with rejected RE-INVITE in error", call_established_with_rejected_reinvite_with_error},
- { "Call redirected by callee", call_redirect}
+ { "Call redirected by callee", call_redirect},
+ { "Call statistics not used if no config", statistics_not_used_without_config},
+ { "Call statistics not sent if call did not start", statistics_not_sent_if_call_not_started},
+ { "Call statistics sent if call ended normally", statistics_sent_at_call_termination},
};
test_suite_t call_test_suite = {
@@ -2037,4 +2130,3 @@ test_suite_t call_test_suite = {
sizeof(call_tests) / sizeof(call_tests[0]),
call_tests
};
-
diff --git a/tester/rcfiles/marie_rc b/tester/rcfiles/marie_rc
index 7b7645800..60fa3d118 100644
--- a/tester/rcfiles/marie_rc
+++ b/tester/rcfiles/marie_rc
@@ -22,6 +22,8 @@ reg_expires=3600
reg_sendregister=1
publish=0
dial_escape_plus=0
+reg_statistics_collector=sip:collector@sip.example.org
+send_statistics=1
[friend_0]
url="Paupoche"