quality_reporting.c 29.7 KB
Newer Older
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26
/*
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"
27 28
#include "ortp/rtpsession.h"

29 30
#include <math.h>

31 32
/***************************************************************************
 *  				TODO / REMINDER LIST
33
 ***************************************************************************
34
	rtt SR recuperer
35
 ***************************************************************************
36 37 38
 *  				END OF TODO / REMINDER LIST
 ****************************************************************************/

39
#define STR_REASSIGN(dest, src) {\
40 41 42 43
	if (dest != NULL) \
		ms_free(dest); \
	dest = src; \
}
44

45 46
/*since printf family functions are LOCALE dependent, float separator may differ
depending on the user's locale (LC_NUMERIC environment var).*/
47
static char * float_to_one_decimal_string(float f) {
48 49 50 51
	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);
52

53
	return ms_strdup_printf("%d.%d", floor_part, one_decimal_part);
54 55
}

56 57 58 59
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;

60
	#ifndef WIN32
61 62
		va_list cap;/*copy of our argument list: a va_list cannot be re-used (SIGSEGV on linux 64 bits)*/
		va_copy(cap,args);
63
		ret = belle_sip_snprintf_valist(*buff, *buff_size, offset, fmt, cap);
64
		va_end(cap);
65
	#else
66
		ret = belle_sip_snprintf_valist(*buff, *buff_size, offset, fmt, args);
67
	#endif
68

69
	/*if we are out of memory, we add some size to buffer*/
70
	if (ret == BELLE_SIP_BUFFER_OVERFLOW) {
71
		/*some compilers complain that size_t cannot be formatted as unsigned long, hence forcing cast*/
72
		ms_warning("QualityReporting: Buffer was too small to contain the whole report - increasing its size from %lu to %lu",
73
			(unsigned long)*buff_size, (unsigned long)*buff_size + 2048);
74 75 76 77
		*buff_size += 2048;
		*buff = (char *) ms_realloc(*buff, *buff_size);

		*offset = prevoffset;
78
		/*recall itself since we did not write all things into the buffer but
79
		only a part of it*/
80 81 82 83 84 85 86 87 88 89 90
		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);
}

91
static void reset_avg_metrics(reporting_session_report_t * report){
92
	int i;
93
	reporting_content_metrics_t * metrics[2] = {&report->local_metrics, &report->remote_metrics};
94 95 96 97 98 99 100 101

	for (i = 0; i < 2; i++) {
		metrics[i]->rtcp_xr_count = 0;
		metrics[i]->jitter_buffer.nominal = 0;
		metrics[i]->jitter_buffer.max = 0;

		metrics[i]->delay.round_trip_delay = 0;
	}
102
	report->last_report_date = ms_time(NULL);
103
}
104

105 106 107 108
#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
109

110 111 112 113 114 115
#define METRICS_PACKET_LOSS 1 << 0
#define METRICS_QUALITY_ESTIMATES 1 << 1
#define METRICS_SESSION_DESCRIPTION 1 << 2
#define METRICS_JITTER_BUFFER 1 << 3
#define METRICS_DELAY 1 << 4
#define METRICS_SIGNAL 1 << 5
116 117
#define METRICS_ADAPTIVE_ALGORITHM 1 << 6

118 119 120
static uint8_t are_metrics_filled(const reporting_content_metrics_t rm) {
	uint8_t ret = 0;

121 122
	IF_NUM_IN_RANGE(rm.packet_loss.network_packet_loss_rate, 0, 255, ret|=METRICS_PACKET_LOSS);
	IF_NUM_IN_RANGE(rm.packet_loss.jitter_buffer_discard_rate, 0, 255, ret|=METRICS_PACKET_LOSS);
123

124
	/*since these are same values than local ones, do not check them*/
125 126 127 128 129 130 131 132 133 134 135
	/*if (rm.session_description.payload_type != -1) ret|=METRICS_SESSION_DESCRIPTION;*/
	/*if (rm.session_description.payload_desc != NULL) ret|=METRICS_SESSION_DESCRIPTION;*/
	/*if (rm.session_description.sample_rate != -1) ret|=METRICS_SESSION_DESCRIPTION;*/
	/*if (rm.session_description.fmtp != NULL) ret|=METRICS_SESSION_DESCRIPTION;*/
	if (rm.session_description.frame_duration != -1) ret|=METRICS_SESSION_DESCRIPTION;
	if (rm.session_description.packet_loss_concealment != -1) ret|=METRICS_SESSION_DESCRIPTION;

	IF_NUM_IN_RANGE(rm.jitter_buffer.adaptive, 0, 3, ret|=METRICS_JITTER_BUFFER);
	IF_NUM_IN_RANGE(rm.jitter_buffer.abs_max, 0, 65535, ret|=METRICS_JITTER_BUFFER);

	IF_NUM_IN_RANGE(rm.delay.end_system_delay, 0, 65535, ret|=METRICS_DELAY);
136
	/*IF_NUM_IN_RANGE(rm.delay.symm_one_way_delay, 0, 65535, ret|=METRICS_DELAY);*/
137 138 139 140 141 142
	IF_NUM_IN_RANGE(rm.delay.interarrival_jitter, 0, 65535, ret|=METRICS_DELAY);
	IF_NUM_IN_RANGE(rm.delay.mean_abs_jitter, 0, 65535, ret|=METRICS_DELAY);

	if (rm.signal.level != 127) ret|=METRICS_SIGNAL;
	if (rm.signal.noise_level != 127) ret|=METRICS_SIGNAL;

143
	if (rm.qos_analyzer.timestamp!=NULL) ret|=METRICS_ADAPTIVE_ALGORITHM;
144
	if (rm.qos_analyzer.input_leg!=NULL) ret|=METRICS_ADAPTIVE_ALGORITHM;
145
	if (rm.qos_analyzer.input!=NULL) ret|=METRICS_ADAPTIVE_ALGORITHM;
146
	if (rm.qos_analyzer.output_leg!=NULL) ret|=METRICS_ADAPTIVE_ALGORITHM;
147
	if (rm.qos_analyzer.output!=NULL) ret|=METRICS_ADAPTIVE_ALGORITHM;
148 149 150 151 152 153 154 155

	if (rm.rtcp_xr_count>0){
		IF_NUM_IN_RANGE(rm.jitter_buffer.nominal/rm.rtcp_xr_count, 0, 65535, ret|=METRICS_JITTER_BUFFER);
		IF_NUM_IN_RANGE(rm.jitter_buffer.max/rm.rtcp_xr_count, 0, 65535, ret|=METRICS_JITTER_BUFFER);
		IF_NUM_IN_RANGE(rm.delay.round_trip_delay, 0, 65535, ret|=METRICS_DELAY);
		IF_NUM_IN_RANGE(rm.quality_estimates.moslq/rm.rtcp_xr_count, 1, 5, ret|=METRICS_QUALITY_ESTIMATES);
		IF_NUM_IN_RANGE(rm.quality_estimates.moscq/rm.rtcp_xr_count, 1, 5, ret|=METRICS_QUALITY_ESTIMATES);
	}
156 157

	return ret;
158
}
159

160 161 162 163 164 165 166 167 168 169 170
static bool_t quality_reporting_enabled(const LinphoneCall * call) {
	return (call->dest_proxy != NULL && linphone_proxy_config_quality_reporting_enabled(call->dest_proxy));
}

static bool_t media_report_enabled(LinphoneCall * call, int stats_type){
	if (! quality_reporting_enabled(call))
		return FALSE;

	if (stats_type == LINPHONE_CALL_STATS_VIDEO && !linphone_call_params_video_enabled(linphone_call_get_current_params(call)))
		return FALSE;

171
	return (call->log->reporting.reports[stats_type] != NULL);
172 173
}

174
static void append_metrics_to_buffer(char ** buffer, size_t * size, size_t * offset, const reporting_content_metrics_t rm) {
175 176 177 178
	char * timestamps_start_str = NULL;
	char * timestamps_stop_str = NULL;
	char * network_packet_loss_rate_str = NULL;
	char * jitter_buffer_discard_rate_str = NULL;
179
	/*char * gap_loss_density_str = NULL;*/
180 181
	char * moslq_str = NULL;
	char * moscq_str = NULL;
182
	uint8_t available_metrics = are_metrics_filled(rm);
183

184
	if (rm.timestamps.start > 0)
185
		timestamps_start_str = linphone_timestamp_to_rfc3339_string(rm.timestamps.start);
186
	if (rm.timestamps.stop > 0)
187 188 189 190 191 192
		timestamps_stop_str = linphone_timestamp_to_rfc3339_string(rm.timestamps.stop);

	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);

193 194 195 196 197 198 199 200 201 202 203 204 205
	if ((available_metrics & METRICS_SESSION_DESCRIPTION) != 0){
		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_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);
	}

	if ((available_metrics & METRICS_JITTER_BUFFER) != 0){
		append_to_buffer(buffer, size, offset, "\r\nJitterBuffer:");
			APPEND_IF_NUM_IN_RANGE(buffer, size, offset, " JBA=%d", rm.jitter_buffer.adaptive, 0, 3);
206 207 208 209
			if (rm.rtcp_xr_count){
				APPEND_IF_NUM_IN_RANGE(buffer, size, offset, " JBN=%d", rm.jitter_buffer.nominal/rm.rtcp_xr_count, 0, 65535);
				APPEND_IF_NUM_IN_RANGE(buffer, size, offset, " JBM=%d", rm.jitter_buffer.max/rm.rtcp_xr_count, 0, 65535);
			}
210 211 212
			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:");
213 214 215
			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));

216 217 218 219 220
			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:");*/
221
			/*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));*/
222 223 224 225 226 227 228 229
		/*	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);*/

	if ((available_metrics & METRICS_DELAY) != 0){
		append_to_buffer(buffer, size, offset, "\r\nDelay:");
230 231 232
			if (rm.rtcp_xr_count){
				APPEND_IF_NUM_IN_RANGE(buffer, size, offset, " RTD=%d", rm.delay.round_trip_delay/rm.rtcp_xr_count, 0, 65535);
			}
233 234 235 236 237 238 239 240 241 242 243
			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, " 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);
	}

	if ((available_metrics & METRICS_SIGNAL) != 0){
		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);
	}

244
	/*if quality estimates metrics are available, rtcp_xr_count should be always not null*/
245
	if ((available_metrics & METRICS_QUALITY_ESTIMATES) != 0){
246 247
		IF_NUM_IN_RANGE(rm.quality_estimates.moslq/rm.rtcp_xr_count, 1, 5, moslq_str = float_to_one_decimal_string(rm.quality_estimates.moslq/rm.rtcp_xr_count));
		IF_NUM_IN_RANGE(rm.quality_estimates.moscq/rm.rtcp_xr_count, 1, 5, moscq_str = float_to_one_decimal_string(rm.quality_estimates.moscq/rm.rtcp_xr_count));
248

249 250 251 252 253
		append_to_buffer(buffer, size, offset, "\r\nQualityEst:");
			APPEND_IF_NOT_NULL_STR(buffer, size, offset, " MOSLQ=%s", moslq_str);
			APPEND_IF_NOT_NULL_STR(buffer, size, offset, " MOSCQ=%s", moscq_str);
	}

254 255
	if ((available_metrics & METRICS_ADAPTIVE_ALGORITHM) != 0){
		append_to_buffer(buffer, size, offset, "\r\nAdaptiveAlg:");
256
			APPEND_IF_NOT_NULL_STR(buffer, size, offset, " TS=%s", rm.qos_analyzer.timestamp);
257
			APPEND_IF_NOT_NULL_STR(buffer, size, offset, " IN_LEG=%s", rm.qos_analyzer.input_leg);
258
			APPEND_IF_NOT_NULL_STR(buffer, size, offset, " IN=%s", rm.qos_analyzer.input);
259
			APPEND_IF_NOT_NULL_STR(buffer, size, offset, " OUT_LEG=%s", rm.qos_analyzer.output_leg);
260
			APPEND_IF_NOT_NULL_STR(buffer, size, offset, " OUT=%s", rm.qos_analyzer.output);
261 262
	}

263 264
	append_to_buffer(buffer, size, offset, "\r\n");

265 266 267 268
	ms_free(timestamps_start_str);
	ms_free(timestamps_stop_str);
	ms_free(network_packet_loss_rate_str);
	ms_free(jitter_buffer_discard_rate_str);
269
	/*ms_free(gap_loss_density_str);*/
270 271
	ms_free(moslq_str);
	ms_free(moscq_str);
272 273
}

274
static int send_report(LinphoneCall* call, reporting_session_report_t * report, const char * report_event) {
275
	LinphoneContent content = {0};
276
	LinphoneAddress *addr;
277
	int expires = -1;
278 279 280
	size_t offset = 0;
	size_t size = 2048;
	char * buffer;
281
	int ret = 0;
282

283 284 285 286 287 288
	/*if we are on a low bandwidth network, do not send reports to not overload it*/
	if (linphone_call_params_low_bandwidth_enabled(linphone_call_get_current_params(call))){
		ms_warning("QualityReporting[%p]: Avoid sending reports on low bandwidth network", call);
		return 1;
	}

289 290
	/*if the call was hung up too early, we might have invalid IPs information
	in that case, we abort the report since it's not useful data*/
291 292
	if (report->info.local_addr.ip == NULL || strlen(report->info.local_addr.ip) == 0
		|| report->info.remote_addr.ip == NULL || strlen(report->info.remote_addr.ip) == 0) {
293
		ms_warning("QualityReporting[%p]: Trying to submit a %s too early (call duration: %d sec) but %s IP could "
294
			"not be retrieved so dropping this report"
295
			, call
296
			, report_event
297 298
			, linphone_call_get_duration(call)
			, (report->info.local_addr.ip == NULL || strlen(report->info.local_addr.ip) == 0) ? "local" : "remote");
299
		return 2;
300 301
	}

302
	addr = linphone_address_new(linphone_proxy_config_get_quality_reporting_collector(call->dest_proxy));
303
	if (addr == NULL) {
304
		ms_warning("QualityReporting[%p]: Asked to submit reporting statistics but no collector address found"
305
			, call);
306
		return 3;
307 308
	}

309
	buffer = (char *) ms_malloc(size);
310 311 312
	content.type = ms_strdup("application");
	content.subtype = ms_strdup("vq-rtcpxr");

313
	append_to_buffer(&buffer, &size, &offset, "%s\r\n", report_event);
314 315 316 317 318 319 320 321 322 323 324
	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);
325

326 327
	append_to_buffer(&buffer, &size, &offset, "LocalMetrics:\r\n");
	append_metrics_to_buffer(&buffer, &size, &offset, report->local_metrics);
328

329
	if (are_metrics_filled(report->remote_metrics)!=0) {
330 331 332
		append_to_buffer(&buffer, &size, &offset, "RemoteMetrics:\r\n");
		append_metrics_to_buffer(&buffer, &size, &offset, report->remote_metrics);
	}
333 334 335
	APPEND_IF_NOT_NULL_STR(&buffer, &size, &offset, "DialogID: %s\r\n", report->dialog_id);

	content.data = buffer;
336
	content.size = strlen(buffer);
337

338
	/*(WIP) Memory leak: PUBLISH message is never freed (issue 1283)*/
339
	if (! linphone_core_publish(call->core, addr, "vq-rtcpxr", expires, &content)){
340
		ret=4;
341
	}
342
	linphone_address_destroy(addr);
343

344
	reset_avg_metrics(report);
345
	linphone_content_uninit(&content);
346
	return ret;
347 348
}

349 350 351
static const SalStreamDescription * get_media_stream_for_desc(const SalMediaDescription * smd, SalStreamType sal_stream_type) {
	int count;
	if (smd != NULL) {
352
		for (count = 0; count < smd->nb_streams; ++count) {
353 354
			if (smd->streams[count].type == sal_stream_type) {
				return &smd->streams[count];
355 356
			}
		}
357
	}
358 359
	return NULL;
}
360

361
static void update_ip(LinphoneCall * call, int stats_type) {
362
	SalStreamType sal_stream_type = (stats_type == LINPHONE_CALL_STATS_AUDIO) ? SalAudio : SalVideo;
363 364
	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);
365

366 367 368 369
	if (local_desc != NULL) {
		/*since this function might be called for video stream AFTER it has been uninitialized, local description might
		be invalid. In any other case, IP/port should be always filled and valid*/
		if (local_desc->rtp_addr != NULL && strlen(local_desc->rtp_addr) > 0) {
370 371
			call->log->reporting.reports[stats_type]->info.local_addr.port = local_desc->rtp_port;
			STR_REASSIGN(call->log->reporting.reports[stats_type]->info.local_addr.ip, ms_strdup(local_desc->rtp_addr));
372
		}
373
	}
374

375 376 377
	if (remote_desc != NULL) {
		/*port is always stored in stream description struct*/
		call->log->reporting.reports[stats_type]->info.remote_addr.port = remote_desc->rtp_port;
378

379 380 381 382 383
		/*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->reporting.reports[stats_type]->info.remote_addr.ip, ms_strdup(remote_desc->rtp_addr));
		} else {
			STR_REASSIGN(call->log->reporting.reports[stats_type]->info.remote_addr.ip, ms_strdup(sal_call_get_remote_media_description(call->op)->addr));
384
		}
385
	}
386 387
}

388
static void qos_analyzer_on_action_suggested(void *user_data, int datac, const char** data){
389
	reporting_content_metrics_t *metrics = (reporting_content_metrics_t*) user_data;
390
	char * appendbuf;
391

392 393 394
	appendbuf=ms_strdup_printf("%s%d;", metrics->qos_analyzer.timestamp?metrics->qos_analyzer.timestamp:"", ms_time(0));
	STR_REASSIGN(metrics->qos_analyzer.timestamp,appendbuf);

395 396 397 398 399 400 401
	STR_REASSIGN(metrics->qos_analyzer.input_leg, ms_strdup(data[0]));
	appendbuf=ms_strdup_printf("%s%s;", metrics->qos_analyzer.input?metrics->qos_analyzer.input:"", data[1]);
	STR_REASSIGN(metrics->qos_analyzer.input,appendbuf);

	STR_REASSIGN(metrics->qos_analyzer.output_leg, ms_strdup(data[2]));
	appendbuf=ms_strdup_printf("%s%s;", metrics->qos_analyzer.output?metrics->qos_analyzer.output:"", data[3]);
	STR_REASSIGN(metrics->qos_analyzer.output, appendbuf);
402 403
}

404
void linphone_reporting_update_ip(LinphoneCall * call) {
405
	update_ip(call, LINPHONE_CALL_STATS_AUDIO);
406
	update_ip(call, LINPHONE_CALL_STATS_VIDEO);
407
}
408

409
void linphone_reporting_update_media_info(LinphoneCall * call, int stats_type) {
410 411 412
	MediaStream * stream = NULL;
	const PayloadType * local_payload = NULL;
	const PayloadType * remote_payload = NULL;
413
	const LinphoneCallParams * current_params = linphone_call_get_current_params(call);
414
	reporting_session_report_t * report = call->log->reporting.reports[stats_type];
415

416
	if (!media_report_enabled(call, stats_type))
417 418
		return;

419

420
	STR_REASSIGN(report->info.call_id, ms_strdup(call->log->call_id));
421
	STR_REASSIGN(report->info.local_group, ms_strdup_printf("linphone-%s-%s-%s", (stats_type == LINPHONE_CALL_STATS_AUDIO ? "audio" : "video"),
422
		linphone_core_get_user_agent_name(), report->info.call_id));
423
	STR_REASSIGN(report->info.remote_group, ms_strdup_printf("linphone-%s-%s-%s", (stats_type == LINPHONE_CALL_STATS_AUDIO ? "audio" : "video"),
424
		linphone_call_get_remote_user_agent(call), report->info.call_id));
425

426
	if (call->dir == LinphoneCallIncoming) {
427 428 429
		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));
430
	} else {
431 432 433
		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));
434
	}
435

436
	STR_REASSIGN(report->dialog_id, sal_op_get_dialog_id(call->op));
437

438 439
	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);
440

441
	/*we use same timestamps for remote too*/
442 443
	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);
444

445
	/*yet we use the same payload config for local and remote, since this is the largest use case*/
446 447
	if (stats_type == LINPHONE_CALL_STATS_AUDIO && call->audiostream != NULL) {
		stream = &call->audiostream->ms;
448 449
		local_payload = linphone_call_params_get_used_audio_codec(current_params);
		remote_payload = local_payload;
450 451
	} else if (stats_type == LINPHONE_CALL_STATS_VIDEO && call->videostream != NULL) {
		stream = &call->videostream->ms;
452 453
		local_payload = linphone_call_params_get_used_video_codec(current_params);
		remote_payload = local_payload;
454
	}
455

456 457
	if (stream != NULL) {
		RtpSession * session = stream->sessions.rtp_session;
458

459 460 461
		report->info.local_addr.ssrc = rtp_session_get_send_ssrc(session);
		report->info.remote_addr.ssrc = rtp_session_get_recv_ssrc(session);
	}
462

463 464
	if (local_payload != NULL) {
		report->local_metrics.session_description.payload_type = local_payload->type;
465
		STR_REASSIGN(report->local_metrics.session_description.payload_desc, ms_strdup(local_payload->mime_type));
466
		report->local_metrics.session_description.sample_rate = local_payload->clock_rate;
467
		STR_REASSIGN(report->local_metrics.session_description.fmtp, ms_strdup(local_payload->recv_fmtp));
468
	}
469

470 471
	if (remote_payload != NULL) {
		report->remote_metrics.session_description.payload_type = remote_payload->type;
472
		STR_REASSIGN(report->remote_metrics.session_description.payload_desc, ms_strdup(remote_payload->mime_type));
473
		report->remote_metrics.session_description.sample_rate = remote_payload->clock_rate;
474
		STR_REASSIGN(report->remote_metrics.session_description.fmtp, ms_strdup(remote_payload->recv_fmtp));
475
	}
476 477
}

478 479 480 481 482
/* generate random float in interval ] 0.9 t ; 1.1 t [*/
static float reporting_rand(float t){
	return t * (.2f * (rand() / (RAND_MAX * 1.0f)) + 0.9f);
}

483
void linphone_reporting_on_rtcp_received(LinphoneCall *call, int stats_type) {
484
	reporting_session_report_t * report = call->log->reporting.reports[stats_type];
485 486
	reporting_content_metrics_t * metrics = NULL;
	LinphoneCallStats stats = call->stats[stats_type];
487
	mblk_t *block = NULL;
488

489 490
	int report_interval = linphone_proxy_config_get_quality_reporting_interval(call->dest_proxy);

491
	if (! media_report_enabled(call,stats_type))
492 493
		return;

494
	if (stats.updated == LINPHONE_CALL_STATS_RECEIVED_RTCP_UPDATE) {
495
		metrics = &report->remote_metrics;
496
		block = stats.received_rtcp;
497
	} else if (stats.updated == LINPHONE_CALL_STATS_SENT_RTCP_UPDATE) {
498
		metrics = &report->local_metrics;
499
		block = stats.sent_rtcp;
500
	}
501

502 503
	do{
		if (rtcp_is_XR(block) && (rtcp_XR_get_block_type(block) == RTCP_XR_VOIP_METRICS)){
504

505
			uint8_t config = rtcp_XR_voip_metrics_get_rx_config(block);
506

507
			metrics->rtcp_xr_count++;
508

509 510
			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;
511

512 513 514 515 516 517
			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->jitter_buffer.adaptive = (config >> 4) & 0x3;
			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);
518

519 520 521
			metrics->session_description.packet_loss_concealment = (config >> 6) & 0x3;

			metrics->delay.round_trip_delay += rtcp_XR_voip_metrics_get_round_trip_delay(block);
522
		}
523
	}while(rtcp_next_packet(block));
524

525 526 527
	/* check if we should send an interval report - use a random sending time to
	dispatch reports and avoid sending them too close from each other */
	if (report_interval>0 && ms_time(NULL)-report->last_report_date>reporting_rand(report_interval)){
528 529 530 531
		linphone_reporting_publish_interval_report(call);
	}
}

532 533
static int publish_report(LinphoneCall *call, const char *event_type){
	int ret = 0;
534 535 536
	int i;
	for (i = 0; i < 2; i++){
		if (media_report_enabled(call, i)){
537
			int sndret;
538
			linphone_reporting_update_media_info(call, i);
539 540 541 542 543 544
			sndret=send_report(call, call->log->reporting.reports[i], event_type);
			if (sndret>0){
				ret += 10+(i+1)*sndret;
			}
		} else{
			ret += i+1;
545
		}
546
	}
547
	return ret;
548
}
549 550 551 552

int linphone_reporting_publish_session_report(LinphoneCall* call, bool_t call_term) {
	char * session_type = call_term?"VQSessionReport: CallTerm":"VQSessionReport";
	return publish_report(call, session_type);
553
}
554

555 556
int linphone_reporting_publish_interval_report(LinphoneCall* call) {
	return publish_report(call, "VQIntervalReport");
557 558
}

559 560 561 562 563 564 565 566 567 568 569 570 571 572 573 574 575 576 577 578 579 580 581 582 583 584 585 586 587 588 589 590 591 592 593 594 595 596 597 598 599 600 601 602 603 604 605
void linphone_reporting_call_state_updated(LinphoneCall *call){
	LinphoneCallState state=linphone_call_get_state(call);
	bool_t enabled=media_report_enabled(call, LINPHONE_CALL_STATS_VIDEO);

	switch (state){
		case LinphoneCallStreamsRunning:{
			int i;
			MediaStream *streams[2] = {(MediaStream*) call->audiostream, (MediaStream *) call->videostream};
			MSQosAnalyzer *analyzer;
			for (i=0;i<2;i++){
				if (streams[i]==NULL||streams[i]->rc==NULL){
					ms_message("QualityReporting[%p] Cannot set on_action_suggested"
					" callback for %s stream because something is null", call, i?"video":"audio");
					continue;
				}

				analyzer=ms_bitrate_controller_get_qos_analyzer(streams[i]->rc);
				if (analyzer){
					ms_qos_analyzer_set_on_action_suggested(analyzer,
						qos_analyzer_on_action_suggested,
						&call->log->reporting.reports[i]->local_metrics);
				}
			}
			linphone_reporting_update_ip(call);
			if (!enabled && call->log->reporting.was_video_running){
				ms_message("QualityReporting[%p]: Send midterm report with status %d",
					call,
					send_report(call, call->log->reporting.reports[LINPHONE_CALL_STATS_VIDEO], "VQSessionReport")
				);
			}
			call->log->reporting.was_video_running=enabled;
			break;
		}
		case LinphoneCallEnd:{
			if (call->log->status==LinphoneCallSuccess || call->log->status==LinphoneCallAborted){
				ms_message("QualityReporting[%p]: Send end reports with status %d",
					call,
					linphone_reporting_publish_session_report(call, TRUE)
				);
			}
			break;
		}
		default:{
			break;
		}
	}
}
606

607 608 609 610
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};
611 612 613

	memset(rm, 0, sizeof(reporting_session_report_t));

614 615 616 617 618 619 620 621 622 623 624
	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;
625
		/*metrics[i]->jitter_buffer.rate = -1;*/
626 627 628
		metrics[i]->jitter_buffer.abs_max = -1;

		metrics[i]->delay.end_system_delay = -1;
629
		/*metrics[i]->delay.one_way_delay = -1;*/
630 631 632 633 634
		metrics[i]->delay.interarrival_jitter = -1;
		metrics[i]->delay.mean_abs_jitter = -1;

		metrics[i]->signal.level = 127;
		metrics[i]->signal.noise_level = 127;
635
	}
636 637

	reset_avg_metrics(rm);
638
	return rm;
639
}
640 641 642 643 644 645 646 647 648 649 650 651 652 653 654 655 656

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);
657
	if (report->local_metrics.qos_analyzer.timestamp != NULL) ms_free(report->local_metrics.qos_analyzer.timestamp);
658
	if (report->local_metrics.qos_analyzer.input_leg != NULL) ms_free(report->local_metrics.qos_analyzer.input_leg);
659
	if (report->local_metrics.qos_analyzer.input != NULL) ms_free(report->local_metrics.qos_analyzer.input);
660
	if (report->local_metrics.qos_analyzer.output_leg != NULL) ms_free(report->local_metrics.qos_analyzer.output_leg);
661
	if (report->local_metrics.qos_analyzer.output != NULL) ms_free(report->local_metrics.qos_analyzer.output);
662 663 664

	ms_free(report);
}
665

666