quality_reporting.c 25 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
	// to discuss
35 36
		// For codecs that are able to change sample rates, the lowest and highest sample rates MUST be reported (e.g., 8000;16000).
		// moslq == moscq
37
		// video: what happens if doing stop/resume?
38 39
		// one time value: average? worst value?
		// rlq value: need algo to compute it
40 41 42 43
/***************************************************************************
 *  				END OF TODO / REMINDER LIST
 ****************************************************************************/

44
#define STR_REASSIGN(dest, src) {\
45 46 47 48
	if (dest != NULL) \
		ms_free(dest); \
	dest = src; \
}
49

50 51 52
// 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) {
53 54 55 56
	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);
57 58 59 60

	return ms_strdup_printf(_("%d.%d"), floor_part, one_decimal_part);
}

61 62 63 64
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;

65
	#ifndef WIN32
66 67
		va_list cap;/*copy of our argument list: a va_list cannot be re-used (SIGSEGV on linux 64 bits)*/
		va_copy(cap,args);
68
		ret = belle_sip_snprintf_valist(*buff, *buff_size, offset, fmt, cap);
69
		va_end(cap);
70
	#else
71
		ret = belle_sip_snprintf_valist(*buff, *buff_size, offset, fmt, args);
72
	#endif
73 74 75 76 77 78 79 80 81

	// 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
82
		// only a part of it
83 84 85 86 87 88 89 90 91 92 93
		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);
}

94

95 96 97 98
#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
99 100 101 102 103 104 105

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

106
	// since these are same values than local ones, do not check them
107 108 109 110 111 112 113 114 115 116
	// 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);
117
	IF_NUM_IN_RANGE(rm.jitter_buffer.abs_max, 0, 65535, return TRUE);
118 119 120 121 122 123 124 125 126 127 128 129 130 131 132

	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;
}
133

134
static void append_metrics_to_buffer(char ** buffer, size_t * size, size_t * offset, const reporting_content_metrics_t rm) {
135 136 137 138 139 140 141 142
	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;

143
	if (rm.timestamps.start > 0)
144
		timestamps_start_str = linphone_timestamp_to_rfc3339_string(rm.timestamps.start);
145
	if (rm.timestamps.stop > 0)
146 147 148 149 150 151 152 153 154 155 156 157 158 159 160 161 162 163 164 165 166 167 168 169 170 171
		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);
172
		// APPEND_IF_NUM_IN_RANGE(buffer, size, offset, " JBR=%d", rm.jitter_buffer.rate, 0, 15);
173 174
		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);
175
		APPEND_IF_NUM_IN_RANGE(buffer, size, offset, " JBX=%d",  rm.jitter_buffer.abs_max, 0, 65535);
176 177 178 179 180 181 182 183 184 185

	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);
186
	// 	append_to_buffer(buffer, size, offset, " GMIN=%d", rm.burst_gap_loss.min_gap_threshold);
187 188 189 190 191 192 193 194 195 196 197 198 199 200 201 202 203 204 205 206 207 208 209 210 211 212 213 214 215 216

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

217 218 219 220 221 222 223
	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);
224 225
}

226
static void reporting_publish(const LinphoneCall* call, const reporting_session_report_t * report) {
227
	LinphoneContent content = {0};
228
	LinphoneAddress *addr;
229
	int expires = -1;
230 231 232 233
	size_t offset = 0;
	size_t size = 2048;
	char * buffer;

234 235 236 237 238 239 240
	// 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;
	}
241

242
	buffer = (char *) ms_malloc(size);
243 244 245 246 247 248 249 250 251 252 253 254 255 256 257
	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);
258

259 260
	append_to_buffer(&buffer, &size, &offset, "LocalMetrics:\r\n");
	append_metrics_to_buffer(&buffer, &size, &offset, report->local_metrics);
261

262 263 264 265
	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);
	}
266 267 268
	APPEND_IF_NOT_NULL_STR(&buffer, &size, &offset, "DialogID: %s\r\n", report->dialog_id);

	content.data = buffer;
269
	content.size = strlen((char*)content.data);
270 271


272 273
	addr = linphone_address_new(call->dest_proxy->reg_statistics_collector);
	if (addr != NULL) {
274 275 276 277 278
		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");
	}
279 280

	linphone_content_uninit(&content);
281 282
}

283 284 285 286 287 288
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];
289 290
			}
		}
291 292 293
	}
	if (smd == NULL || count == smd->n_total_streams) {
		ms_warning("Could not find the associated stream of type %d", sal_stream_type);
294
	}
295

296 297
	return NULL;
}
298

299 300 301 302 303
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);
304

305 306 307
		// 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;
308
			STR_REASSIGN(call->log->reports[stats_type]->info.local_addr.ip, ms_strdup(local_desc->rtp_addr));
309
		}
310

311 312 313 314 315 316
		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) {
317
				STR_REASSIGN(call->log->reports[stats_type]->info.remote_addr.ip, ms_strdup(remote_desc->rtp_addr));
318
			} else {
319
				STR_REASSIGN(call->log->reports[stats_type]->info.remote_addr.ip, ms_strdup(sal_call_get_remote_media_description(call->op)->addr));
320 321
			}
		}
322
	}
323 324
}

325
static bool_t reporting_enabled(const LinphoneCall * call) {
326 327
	return (call->dest_proxy != NULL && linphone_proxy_config_send_statistics_enabled(call->dest_proxy));
}
328

329
void linphone_reporting_update_ip(LinphoneCall * call) {
330 331 332 333
	// 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

334
	if (! reporting_enabled(call))
335 336
		return;

337 338 339 340
	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);
341
	}
342
}
343

344 345 346 347 348
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;
349 350
	const LinphoneCallParams * current_params = linphone_call_get_current_params(call);

351
	if (! reporting_enabled(call))
352 353
		return;

354 355 356 357 358
	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));
359

360
	if (call->dir == LinphoneCallIncoming) {
361 362 363
		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));
364
	} else {
365 366 367
		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));
368
	}
369

370
	STR_REASSIGN(report->dialog_id, sal_op_get_dialog_id(call->op));
371

372 373
	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);
374

375 376 377
	//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);
378

379
	// yet we use the same payload config for local and remote, since this is the largest use case
380 381
	if (stats_type == LINPHONE_CALL_STATS_AUDIO && call->audiostream != NULL) {
		stream = &call->audiostream->ms;
382 383
		local_payload = linphone_call_params_get_used_audio_codec(current_params);
		remote_payload = local_payload;
384 385
	} else if (stats_type == LINPHONE_CALL_STATS_VIDEO && call->videostream != NULL) {
		stream = &call->videostream->ms;
386 387
		local_payload = linphone_call_params_get_used_video_codec(current_params);
		remote_payload = local_payload;
388
	}
389

390 391
	if (stream != NULL) {
		RtpSession * session = stream->sessions.rtp_session;
392

393 394 395
		report->info.local_addr.ssrc = rtp_session_get_send_ssrc(session);
		report->info.remote_addr.ssrc = rtp_session_get_recv_ssrc(session);
	}
396

397 398
	if (local_payload != NULL) {
		report->local_metrics.session_description.payload_type = local_payload->type;
399
		STR_REASSIGN(report->local_metrics.session_description.payload_desc, ms_strdup(local_payload->mime_type));
400
		report->local_metrics.session_description.sample_rate = local_payload->clock_rate;
401
		STR_REASSIGN(report->local_metrics.session_description.fmtp, ms_strdup(local_payload->recv_fmtp));
402
	}
403

404 405
	if (remote_payload != NULL) {
		report->remote_metrics.session_description.payload_type = remote_payload->type;
406
		STR_REASSIGN(report->remote_metrics.session_description.payload_desc, ms_strdup(remote_payload->mime_type));
407
		report->remote_metrics.session_description.sample_rate = remote_payload->clock_rate;
408
		STR_REASSIGN(report->remote_metrics.session_description.fmtp, ms_strdup(remote_payload->recv_fmtp));
409
	}
410 411
}

412
void linphone_reporting_call_stats_updated(LinphoneCall *call, int stats_type) {
413
	reporting_session_report_t * report = call->log->reports[stats_type];
414 415 416
	reporting_content_metrics_t * metrics = NULL;

	LinphoneCallStats stats = call->stats[stats_type];
417
	mblk_t *block = NULL;
418

419
	if (! reporting_enabled(call))
420 421
		return;

422
	if (stats.updated == LINPHONE_CALL_STATS_RECEIVED_RTCP_UPDATE) {
423
		metrics = &report->remote_metrics;
424 425 426 427
		if (rtcp_is_XR(stats.received_rtcp) == TRUE) {
			block = stats.received_rtcp;
		}
	} else if (stats.updated == LINPHONE_CALL_STATS_SENT_RTCP_UPDATE) {
428
		metrics = &report->local_metrics;
429 430 431 432 433 434 435 436 437 438 439 440 441 442 443 444 445 446 447 448 449 450 451 452 453 454 455 456
		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: {
				uint8_t config;

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

				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;
			}
		}
	}
457
}
458 459

void linphone_reporting_publish(LinphoneCall* call) {
460
	if (! reporting_enabled(call))
461 462
		return;

463

464 465
	if (call->log->reports[LINPHONE_CALL_STATS_AUDIO] != NULL) {
		reporting_publish(call, call->log->reports[LINPHONE_CALL_STATS_AUDIO]);
466 467
	}

468
	if (call->log->reports[LINPHONE_CALL_STATS_VIDEO] != NULL
469 470 471 472 473 474 475 476 477 478 479 480 481 482 483 484 485 486 487 488 489
		&& 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;
490
		// metrics[i]->jitter_buffer.rate = -1;
491 492 493 494 495 496 497 498 499 500 501 502 503
		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;
504
	}
505
	return rm;
506
}
507 508 509 510 511 512 513 514 515 516 517 518 519 520 521 522 523 524 525 526

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