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; uint8_t available_metrics = are_metrics_filled(rm); 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); 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); 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); 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); } 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:"); 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)); 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:");*/ /*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));*/ /* 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:"); if (rm.rtcp_xr_count+rm.rtcp_sr_count){ APPEND_IF_NUM_IN_RANGE(buffer, size, offset, " RTD=%d", rm.delay.round_trip_delay/(rm.rtcp_xr_count+rm.rtcp_sr_count), 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, " 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); } /*if quality estimates metrics are available, rtcp_xr_count should be always not null*/ if ((available_metrics & METRICS_QUALITY_ESTIMATES) != 0){ 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, "\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); } if (rm.user_agent!=NULL){ append_to_buffer(buffer, size, offset, "\r\nLinphoneExt:"); APPEND_IF_NOT_NULL_STR(buffer, size, offset, " UA=\"%s\"", rm.user_agent); } 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 int send_report(LinphoneCall* call, reporting_session_report_t * report, const char * report_event) { LinphoneContent *content; int expires = -1; size_t offset = 0; size_t size = 2048; char * buffer; int ret = 0; LinphoneEvent *lev; LinphoneAddress *request_uri; char * domain; const char* route; /*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); ret = 1; goto end; } /*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*/ 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) { ms_warning("QualityReporting[%p]: Trying to submit a %s too early (call duration: %d sec) but %s IP could " "not be retrieved so dropping this report" , call , report_event , linphone_call_get_duration(call) , (report->info.local_addr.ip == NULL || strlen(report->info.local_addr.ip) == 0) ? "local" : "remote"); ret = 2; goto end; } buffer = (char *) belle_sip_malloc(size); content = linphone_content_new(); linphone_content_set_type(content, "application"); linphone_content_set_subtype(content, "vq-rtcpxr"); append_to_buffer(&buffer, &size, &offset, "%s\r\n", report_event); 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_addr.id); append_to_buffer(&buffer, &size, &offset, "RemoteID: %s\r\n", report->info.remote_addr.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_addr.group); APPEND_IF_NOT_NULL_STR(&buffer, &size, &offset, "RemoteGroup: %s\r\n", report->info.remote_addr.group); append_to_buffer(&buffer, &size, &offset, "LocalAddr: IP=%s PORT=%d SSRC=%u\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_addr.mac); append_to_buffer(&buffer, &size, &offset, "RemoteAddr: IP=%s PORT=%d SSRC=%u\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_addr.mac); 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)!=0) { 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); if (report->qos_analyzer.timestamp!=NULL){ append_to_buffer(&buffer, &size, &offset, "AdaptiveAlg:"); APPEND_IF_NOT_NULL_STR(&buffer, &size, &offset, " NAME=\"%s\"", report->qos_analyzer.name); APPEND_IF_NOT_NULL_STR(&buffer, &size, &offset, " TS=\"%s\"", report->qos_analyzer.timestamp); APPEND_IF_NOT_NULL_STR(&buffer, &size, &offset, " IN_LEG=\"%s\"", report->qos_analyzer.input_leg); APPEND_IF_NOT_NULL_STR(&buffer, &size, &offset, " IN=\"%s\"", report->qos_analyzer.input); APPEND_IF_NOT_NULL_STR(&buffer, &size, &offset, " OUT_LEG=\"%s\"", report->qos_analyzer.output_leg); APPEND_IF_NOT_NULL_STR(&buffer, &size, &offset, " OUT=\"%s\"", report->qos_analyzer.output); append_to_buffer(&buffer, &size, &offset, "\r\n"); } #if TARGET_OS_IPHONE { size_t namesize; char *machine; sysctlbyname("hw.machine", NULL, &namesize, NULL, 0); machine = malloc(namesize); sysctlbyname("hw.machine", machine, &namesize, NULL, 0); APPEND_IF_NOT_NULL_STR(&buffer, &size, &offset, "Device: %s\r\n", machine); } #endif linphone_content_set_buffer(content, buffer, strlen(buffer)); ms_free(buffer); if (call->log->reporting.on_report_sent != NULL) { SalStreamType type = report == call->log->reporting.reports[0] ? LINPHONE_CALL_STATS_AUDIO : report == call->log->reporting.reports[1] ? LINPHONE_CALL_STATS_VIDEO : LINPHONE_CALL_STATS_TEXT; call->log->reporting.on_report_sent(call, type, content); } route = linphone_proxy_config_get_quality_reporting_collector(call->dest_proxy); domain = ms_strdup_printf("sip:%s", linphone_proxy_config_get_domain(call->dest_proxy)); request_uri = linphone_address_new(route ? route : domain); ms_free(domain); lev=linphone_core_create_publish(call->core, request_uri, "vq-rtcpxr", expires); if (route) { ms_message("Publishing report with custom route %s", route); sal_op_set_route(lev->op, route); } if (linphone_event_send_publish(lev, content) != 0){ linphone_event_unref(lev); lev=NULL; ret=4; } else { reset_avg_metrics(report); STR_REASSIGN(report->qos_analyzer.timestamp, NULL); STR_REASSIGN(report->qos_analyzer.input_leg, NULL); STR_REASSIGN(report->qos_analyzer.input, NULL); STR_REASSIGN(report->qos_analyzer.output_leg, NULL); STR_REASSIGN(report->qos_analyzer.output, NULL); } linphone_address_destroy(request_uri); linphone_content_unref(content); end: ms_message("QualityReporting[%p]: Send '%s' with status %d", call, report_event, ret ); return ret; }
static int send_report(LinphoneCall* call, reporting_session_report_t * report, const char * report_event) { LinphoneContent *content = linphone_content_new(); LinphoneAddress *addr; int expires = -1; size_t offset = 0; size_t size = 2048; char * buffer; int ret = 0; /*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); ret = 1; goto end; } /*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*/ 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) { ms_warning("QualityReporting[%p]: Trying to submit a %s too early (call duration: %d sec) but %s IP could " "not be retrieved so dropping this report" , call , report_event , linphone_call_get_duration(call) , (report->info.local_addr.ip == NULL || strlen(report->info.local_addr.ip) == 0) ? "local" : "remote"); ret = 2; goto end; } addr = linphone_address_new(linphone_proxy_config_get_quality_reporting_collector(call->dest_proxy)); if (addr == NULL) { ms_warning("QualityReporting[%p]: Asked to submit reporting statistics but no collector address found" , call); ret = 3; goto end; } buffer = (char *) belle_sip_malloc(size); linphone_content_set_type(content, "application"); linphone_content_set_subtype(content, "vq-rtcpxr"); append_to_buffer(&buffer, &size, &offset, "%s\r\n", report_event); 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_addr.id); append_to_buffer(&buffer, &size, &offset, "RemoteID: %s\r\n", report->info.remote_addr.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_addr.group); APPEND_IF_NOT_NULL_STR(&buffer, &size, &offset, "RemoteGroup: %s\r\n", report->info.remote_addr.group); append_to_buffer(&buffer, &size, &offset, "LocalAddr: IP=%s PORT=%d SSRC=%u\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_addr.mac); append_to_buffer(&buffer, &size, &offset, "RemoteAddr: IP=%s PORT=%d SSRC=%u\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_addr.mac); 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)!=0) { 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); if (report->qos_analyzer.timestamp!=NULL){ append_to_buffer(&buffer, &size, &offset, "AdaptiveAlg:"); APPEND_IF_NOT_NULL_STR(&buffer, &size, &offset, " NAME=\"%s\"", report->qos_analyzer.name); APPEND_IF_NOT_NULL_STR(&buffer, &size, &offset, " TS=\"%s\"", report->qos_analyzer.timestamp); APPEND_IF_NOT_NULL_STR(&buffer, &size, &offset, " IN_LEG=\"%s\"", report->qos_analyzer.input_leg); APPEND_IF_NOT_NULL_STR(&buffer, &size, &offset, " IN=\"%s\"", report->qos_analyzer.input); APPEND_IF_NOT_NULL_STR(&buffer, &size, &offset, " OUT_LEG=\"%s\"", report->qos_analyzer.output_leg); APPEND_IF_NOT_NULL_STR(&buffer, &size, &offset, " OUT=\"%s\"", report->qos_analyzer.output); append_to_buffer(&buffer, &size, &offset, "\r\n"); } linphone_content_set_buffer(content, buffer, strlen(buffer)); ms_free(buffer); if (call->log->reporting.on_report_sent != NULL){ call->log->reporting.on_report_sent( call, (report==call->log->reporting.reports[0])?LINPHONE_CALL_STATS_AUDIO:LINPHONE_CALL_STATS_VIDEO, content); } if (! linphone_core_publish(call->core, addr, "vq-rtcpxr", expires, content)){ ret=4; } else { reset_avg_metrics(report); STR_REASSIGN(report->qos_analyzer.timestamp, NULL); STR_REASSIGN(report->qos_analyzer.input_leg, NULL); STR_REASSIGN(report->qos_analyzer.input, NULL); STR_REASSIGN(report->qos_analyzer.output_leg, NULL); STR_REASSIGN(report->qos_analyzer.output, NULL); } linphone_address_destroy(addr); linphone_content_unref(content); end: ms_message("QualityReporting[%p]: Send '%s' with status %d", call, report_event, ret ); return ret; }