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"