#include "asterisk.h"
-#include <math.h> /* for sqrt, MAX */
#include <sched.h> /* for sched_yield */
#include <sys/time.h> /* for timeval */
#include <time.h> /* for time_t */
int ast_rtp_instance_destroy(struct ast_rtp_instance *instance)
{
+ if (!instance) {
+ return 0;
+ }
+ if (ast_debug_rtp_is_allowed) {
+ char buffer[4][512];
+ ast_debug_rtp(1, "%s:\n"
+ " RTT: %s\n"
+ " Loss: %s\n"
+ " Jitter: %s\n"
+ " MES: %s\n",
+ instance->channel_uniqueid,
+ ast_rtp_instance_get_quality(instance, AST_RTP_INSTANCE_STAT_FIELD_QUALITY_RTT,
+ buffer[0], sizeof(buffer[0])),
+ ast_rtp_instance_get_quality(instance, AST_RTP_INSTANCE_STAT_FIELD_QUALITY_LOSS,
+ buffer[1], sizeof(buffer[1])),
+ ast_rtp_instance_get_quality(instance, AST_RTP_INSTANCE_STAT_FIELD_QUALITY_JITTER,
+ buffer[2], sizeof(buffer[2])),
+ ast_rtp_instance_get_quality(instance, AST_RTP_INSTANCE_STAT_FIELD_QUALITY_MES,
+ buffer[3], sizeof(buffer[3]))
+ );
+ }
+
ao2_cleanup(instance);
return 0;
stat = AST_RTP_INSTANCE_STAT_COMBINED_LOSS;
} else if (field == AST_RTP_INSTANCE_STAT_FIELD_QUALITY_RTT) {
stat = AST_RTP_INSTANCE_STAT_COMBINED_RTT;
+ } else if (field == AST_RTP_INSTANCE_STAT_FIELD_QUALITY_MES) {
+ stat = AST_RTP_INSTANCE_STAT_COMBINED_MES;
} else {
return NULL;
}
/* Now actually fill the buffer with the good information */
if (field == AST_RTP_INSTANCE_STAT_FIELD_QUALITY) {
- snprintf(buf, size, "ssrc=%u;themssrc=%u;lp=%u;rxjitter=%f;rxcount=%u;txjitter=%f;txcount=%u;rlp=%u;rtt=%f",
- stats.local_ssrc, stats.remote_ssrc, stats.rxploss, stats.rxjitter, stats.rxcount, stats.txjitter, stats.txcount, stats.txploss, stats.rtt);
+ snprintf(buf, size, "ssrc=%u;themssrc=%u;lp=%u;rxjitter=%f;rxcount=%u;"
+ "txjitter=%f;txcount=%u;rlp=%u;rtt=%f;rxmes=%f;txmes=%f",
+ stats.local_ssrc, stats.remote_ssrc, stats.rxploss, stats.rxjitter,
+ stats.rxcount, stats.txjitter, stats.txcount, stats.txploss, stats.rtt,
+ stats.rxmes, stats.txmes);
} else if (field == AST_RTP_INSTANCE_STAT_FIELD_QUALITY_JITTER) {
- snprintf(buf, size, "minrxjitter=%f;maxrxjitter=%f;avgrxjitter=%f;stdevrxjitter=%f;reported_minjitter=%f;reported_maxjitter=%f;reported_avgjitter=%f;reported_stdevjitter=%f;",
- stats.local_minjitter, stats.local_maxjitter, stats.local_normdevjitter, sqrt(stats.local_stdevjitter), stats.remote_minjitter, stats.remote_maxjitter, stats.remote_normdevjitter, sqrt(stats.remote_stdevjitter));
+ snprintf(buf, size, "minrxjitter=%010.6f;maxrxjitter=%010.6f;avgrxjitter=%010.6f;stdevrxjitter=%010.6f;mintxjitter=%010.6f;maxtxjitter=%010.6f;avgtxjitter=%010.6f;stdevtxjitter=%010.6f;",
+ stats.local_minjitter, stats.local_maxjitter, stats.local_normdevjitter, stats.local_stdevjitter, stats.remote_minjitter, stats.remote_maxjitter, stats.remote_normdevjitter, stats.remote_stdevjitter);
} else if (field == AST_RTP_INSTANCE_STAT_FIELD_QUALITY_LOSS) {
- snprintf(buf, size, "minrxlost=%f;maxrxlost=%f;avgrxlost=%f;stdevrxlost=%f;reported_minlost=%f;reported_maxlost=%f;reported_avglost=%f;reported_stdevlost=%f;",
- stats.local_minrxploss, stats.local_maxrxploss, stats.local_normdevrxploss, sqrt(stats.local_stdevrxploss), stats.remote_minrxploss, stats.remote_maxrxploss, stats.remote_normdevrxploss, sqrt(stats.remote_stdevrxploss));
+ snprintf(buf, size, " minrxlost=%010.6f; maxrxlost=%010.6f; avgrxlost=%010.6f; stdevrxlost=%010.6f; mintxlost=%010.6f; maxtxlost=%010.6f; avgtxlost=%010.6f; stdevtxlost=%010.6f;",
+ stats.local_minrxploss, stats.local_maxrxploss, stats.local_normdevrxploss, stats.local_stdevrxploss, stats.remote_minrxploss, stats.remote_maxrxploss, stats.remote_normdevrxploss, stats.remote_stdevrxploss);
} else if (field == AST_RTP_INSTANCE_STAT_FIELD_QUALITY_RTT) {
- snprintf(buf, size, "minrtt=%f;maxrtt=%f;avgrtt=%f;stdevrtt=%f;", stats.minrtt, stats.maxrtt, stats.normdevrtt, stats.stdevrtt);
+ snprintf(buf, size, " minrtt=%010.6f; maxrtt=%010.6f; avgrtt=%010.6f; stdevrtt=%010.6f;", stats.minrtt, stats.maxrtt, stats.normdevrtt, stats.stdevrtt);
+ } else if (field == AST_RTP_INSTANCE_STAT_FIELD_QUALITY_MES) {
+ snprintf(buf, size, " minrxmes=%010.6f; maxrxmes=%010.6f; avgrxmes=%010.6f; stdevrxmes=%010.6f; mintxmes=%010.6f; maxtxmes=%010.6f; avgtxmes=%010.6f; stdevtxmes=%010.6f;",
+ stats.local_minmes, stats.local_maxmes,
+ stats.local_normdevmes, stats.local_stdevmes,
+ stats.remote_minmes, stats.remote_maxmes,
+ stats.remote_normdevmes, stats.remote_stdevmes);
}
return buf;
}
}
+ quality = ast_rtp_instance_get_quality(instance,
+ AST_RTP_INSTANCE_STAT_FIELD_QUALITY_MES, quality_buf, sizeof(quality_buf));
+ if (quality) {
+ pbx_builtin_setvar_helper(chan, "RTPAUDIOQOSMES", quality);
+ if (bridge) {
+ pbx_builtin_setvar_helper(bridge, "RTPAUDIOQOSMESBRIDGED", quality);
+ }
+ }
+
ast_channel_stage_snapshot_done(chan);
ast_channel_unlock(chan);
if (bridge) {
struct ast_json *to = ast_json_object_get(payload->blob, "to");
struct ast_json *from = ast_json_object_get(payload->blob, "from");
struct ast_json *rtt = ast_json_object_get(payload->blob, "rtt");
+ struct ast_json *mes = ast_json_object_get(payload->blob, "mes");
if (to) {
ast_str_append(&packet_string, 0, "To: %s\r\n", ast_json_string_get(to));
}
if (rtt) {
ast_str_append(&packet_string, 0, "RTT: %4.4f\r\n", ast_json_real_get(rtt));
}
+ if (mes) {
+ ast_str_append(&packet_string, 0, "MES: %4.1f\r\n", ast_json_real_get(mes));
+ }
}
ast_str_append(&packet_string, 0, "SSRC: 0x%.8x\r\n", ssrc);
SET_AST_JSON_OBJ(j_res, "normdevrtt", ast_json_real_create(stats->normdevrtt));
SET_AST_JSON_OBJ(j_res, "stdevrtt", ast_json_real_create(stats->stdevrtt));
+ SET_AST_JSON_OBJ(j_res, "txmes", ast_json_integer_create(stats->txmes));
+ SET_AST_JSON_OBJ(j_res, "rxmes", ast_json_integer_create(stats->rxmes));
+
+ SET_AST_JSON_OBJ(j_res, "remote_maxmes", ast_json_real_create(stats->remote_maxmes));
+ SET_AST_JSON_OBJ(j_res, "remote_minmes", ast_json_real_create(stats->remote_minmes));
+ SET_AST_JSON_OBJ(j_res, "remote_normdevmes", ast_json_real_create(stats->remote_normdevmes));
+ SET_AST_JSON_OBJ(j_res, "remote_stdevmes", ast_json_real_create(stats->remote_stdevmes));
+
+ SET_AST_JSON_OBJ(j_res, "local_maxmes", ast_json_real_create(stats->local_maxmes));
+ SET_AST_JSON_OBJ(j_res, "local_minmes", ast_json_real_create(stats->local_minmes));
+ SET_AST_JSON_OBJ(j_res, "local_normdevmes", ast_json_real_create(stats->local_normdevmes));
+ SET_AST_JSON_OBJ(j_res, "local_stdevmes", ast_json_real_create(stats->local_stdevmes));
+
return j_res;
}
#define DEFAULT_STUN_SOFTWARE_ATTRIBUTE 1
#define DEFAULT_DTLS_MTU 1200
+/*!
+ * Because both ends usually don't start sending RTP
+ * at the same time, some of the calculations like
+ * rtt and jitter will probably be unstable for a while
+ * so we'll skip some received packets before starting
+ * analyzing. This just affects analyzing; we still
+ * process the RTP as normal.
+ */
+#define RTP_IGNORE_FIRST_PACKETS_COUNT 15
+
extern struct ast_srtp_res *res_srtp;
extern struct ast_srtp_policy_res *res_srtp_policy;
unsigned int lastovidtimestamp;
unsigned int lastitexttimestamp;
unsigned int lastotexttimestamp;
+ int prevrxseqno; /*!< Previous received packeted sequence number, from the network */
int lastrxseqno; /*!< Last received sequence number, from the network */
- int expectedrxseqno; /*!< Next expected sequence number, from the network */
+ int expectedrxseqno; /*!< Next expected sequence number, from the network */
AST_VECTOR(, int) missing_seqno; /*!< A vector of sequence numbers we never received */
int expectedseqno; /*!< Next expected sequence number, from the core */
unsigned short seedrxseqno; /*!< What sequence number did they start with?*/
- unsigned int seedrxts; /*!< What RTP timestamp did they start with? */
unsigned int rxcount; /*!< How many packets have we received? */
unsigned int rxoctetcount; /*!< How many octets have we received? should be rxcount *160*/
unsigned int txcount; /*!< How many packets have we sent? */
unsigned int txoctetcount; /*!< How many octets have we sent? (txcount*160)*/
unsigned int cycles; /*!< Shifted count of sequence number cycles */
- double rxjitter; /*!< Interarrival jitter at the moment in seconds to be reported */
- double rxtransit; /*!< Relative transit time for previous packet */
struct ast_format *lasttxformat;
struct ast_format *lastrxformat;
+ /*
+ * RX RTP Timestamp and Jitter calculation.
+ */
+ double rxstart; /*!< RX time of the first packet in the session in seconds since EPOCH. */
+ double rxstart_stable; /*!< RX time of the first packet after RTP_IGNORE_FIRST_PACKETS_COUNT */
+ unsigned int remote_seed_rx_rtp_ts; /*!< RTP timestamp of first RX packet. */
+ unsigned int remote_seed_rx_rtp_ts_stable; /*!< RTP timestamp of first packet after RTP_IGNORE_FIRST_PACKETS_COUNT */
+ unsigned int last_transit_time_samples; /*!< The last transit time in samples */
+ double rxjitter; /*!< Last calculated Interarrival jitter in seconds. */
+ double rxjitter_samples; /*!< Last calculated Interarrival jitter in samples. */
+ double rxmes; /*!< Media Experince Score at the moment to be reported */
+
/* DTMF Reception Variables */
char resp; /*!< The current digit being processed */
unsigned int last_seqno; /*!< The last known sequence number for any DTMF packet */
int send_payload;
int send_duration;
unsigned int flags;
- struct timeval rxcore;
struct timeval txcore;
- double drxcore; /*!< The double representation of the first received packet */
+
struct timeval dtmfmute;
struct ast_smoother *smoother;
unsigned short seqno; /*!< Sequence number, RFC 3550, page 13. */
unsigned int asymmetric_codec; /*!< Indicate if asymmetric send/receive codecs are allowed */
struct ast_rtp_instance *bundled; /*!< The RTP instance we are bundled to */
+ /*!
+ * \brief The RTP instance owning us (used for debugging purposes)
+ * We don't hold a reference to the instance because it created
+ * us in the first place. It can't go away.
+ */
+ struct ast_rtp_instance *owner;
int stream_num; /*!< Stream num for this RTP instance */
AST_VECTOR(, struct rtp_ssrc_mapping) ssrc_mapping; /*!< Mappings of SSRC to RTP instances */
struct ast_sockaddr bind_address; /*!< Requested bind address for the sockets */
unsigned int lastsrtxcount; /*!< Transmit packet count when last SR sent */
double accumulated_transit; /*!< accumulated a-dlsr-lsr */
double rtt; /*!< Last reported rtt */
- unsigned int reported_jitter; /*!< The contents of their last jitter entry in the RR */
+ double reported_jitter; /*!< The contents of their last jitter entry in the RR in seconds */
unsigned int reported_lost; /*!< Reported lost packets in their RR */
double reported_maxjitter; /*!< Maximum reported interarrival jitter */
double stdevrtt; /*!< Standard deviation of calculated round trip time */
unsigned int rtt_count; /*!< Calculated round trip time count */
+ double reported_mes; /*!< The calculated MES from their last RR */
+ double reported_maxmes; /*!< Maximum reported mes */
+ double reported_minmes; /*!< Minimum reported mes */
+ double reported_normdev_mes; /*!< Mean of reported mes */
+ double reported_stdev_mes; /*!< Standard deviation of reported mes */
+ unsigned int reported_mes_count; /*!< Reported mes count */
+
+ double maxrxmes; /*!< Maximum of calculated mes */
+ double minrxmes; /*!< Minimum of calculated mes */
+ double normdev_rxmes; /*!< Mean of calculated mes */
+ double stdev_rxmes; /*!< Standard deviation of calculated mes */
+ unsigned int rxmes_count; /*!< mes count */
+
/* VP8: sequence number for the RTCP FIR FCI */
int firseq;
static void ast_rtp_set_stream_num(struct ast_rtp_instance *instance, int stream_num);
static int ast_rtp_extension_enable(struct ast_rtp_instance *instance, enum ast_rtp_extension extension);
static int ast_rtp_bundle(struct ast_rtp_instance *child, struct ast_rtp_instance *parent);
+static void update_reported_mes_stats(struct ast_rtp *rtp);
+static void update_local_mes_stats(struct ast_rtp *rtp);
#if defined(HAVE_OPENSSL) && (OPENSSL_VERSION_NUMBER >= 0x10001000L) && !defined(OPENSSL_NO_SRTP)
static int ast_rtp_activate(struct ast_rtp_instance *instance);
if (!(rtp = ast_calloc(1, sizeof(*rtp)))) {
return -1;
}
-
+ rtp->owner = instance;
/* Set default parameters on the newly created RTP structure */
rtp->ssrc = ast_random();
ast_uuid_generate_str(rtp->cname, sizeof(rtp->cname));
rtp->seqno = ast_random() & 0x7fff;
rtp->expectedrxseqno = -1;
rtp->expectedseqno = -1;
+ rtp->rxstart = -1;
rtp->sched = sched;
ast_sockaddr_copy(&rtp->bind_address, addr);
-
/* Transport creation operations can grab the RTP data from the instance, so set it */
ast_rtp_instance_set_data(instance, rtp);
AST_VECTOR_FREE(&rtp->missing_seqno);
/* Finally destroy ourselves */
+ rtp->owner = NULL;
ast_free(rtp);
return 0;
/* Compute statistics */
calculate_lost_packet_statistics(rtp, &lost_packets, &fraction_lost);
+ /*
+ * update_local_mes_stats must be called AFTER
+ * calculate_lost_packet_statistics
+ */
+ update_local_mes_stats(rtp);
gettimeofday(&now, NULL);
rtcp_report->reception_report_count = rtp->themssrc_valid ? 1 : 0;
report_block->lost_count.fraction = (fraction_lost & 0xff);
report_block->lost_count.packets = (lost_packets & 0xffffff);
report_block->highest_seq_no = (rtp->cycles | (rtp->lastrxseqno & 0xffff));
- report_block->ia_jitter = (unsigned int)(rtp->rxjitter * ast_rtp_get_rate(rtp->f.subclass.format));
+ report_block->ia_jitter = (unsigned int)rtp->rxjitter_samples;
report_block->lsr = rtp->rtcp->themrxlsr;
/* If we haven't received an SR report, DLSR should be 0 */
if (!ast_tvzero(rtp->rtcp->rxlsr)) {
ast_verbose(" Sent octets: %u\n", rtcp_report->sender_information.octet_count);
}
if (report_block) {
+ int rate = ast_rtp_get_rate(rtp->f.subclass.format);
ast_verbose(" Report block:\n");
ast_verbose(" Their SSRC: %u\n", report_block->source_ssrc);
ast_verbose(" Fraction lost: %d\n", report_block->lost_count.fraction);
ast_verbose(" Cumulative loss: %u\n", report_block->lost_count.packets);
ast_verbose(" Highest seq no: %u\n", report_block->highest_seq_no);
- ast_verbose(" IA jitter: %.4f\n", (double)report_block->ia_jitter / ast_rtp_get_rate(rtp->f.subclass.format));
+ ast_verbose(" IA jitter (samp): %u\n", report_block->ia_jitter);
+ ast_verbose(" IA jitter (secs): %.6f\n", ast_samp2sec(report_block->ia_jitter, rate));
ast_verbose(" Their last SR: %u\n", report_block->lsr);
ast_verbose(" DLSR: %4.4f (sec)\n\n", (double)(report_block->dlsr / 65536.0));
}
}
- message_blob = ast_json_pack("{s: s, s: s}",
+ message_blob = ast_json_pack("{s: s, s: s, s: f}",
"to", ast_sockaddr_stringify(&remote_address),
- "from", rtp->rtcp->local_addr_str);
+ "from", rtp->rtcp->local_addr_str,
+ "mes", rtp->rxmes);
+
ast_rtp_publish_rtcp_message(instance, ast_rtp_rtcp_sent_type(),
rtcp_report, message_blob);
}
} else {
if (rtp->rtcp && rtp->rtcp->schedid < 0) {
- ast_debug_rtcp(1, "(%p) RTCP starting transmission\n", instance);
+ ast_debug_rtcp(2, "(%s) RTCP starting transmission in %u ms\n",
+ ast_rtp_instance_get_channel_id(instance), ast_rtcp_calc_interval(rtp));
ao2_ref(instance, +1);
rtp->rtcp->schedid = ast_sched_add(rtp->sched, ast_rtcp_calc_interval(rtp), ast_rtcp_write, instance);
if (rtp->rtcp->schedid < 0) {
format = frame->subclass.format;
if (ast_format_cmp(rtp->lasttxformat, format) == AST_FORMAT_CMP_NOT_EQUAL) {
/* Oh dear, if the format changed we will have to set up a new smoother */
- ast_debug_rtp(1, "(%p) RTP ooh, format changed from %s to %s\n",
- instance, ast_format_get_name(rtp->lasttxformat),
+ ast_debug_rtp(1, "(%s) RTP ooh, format changed from %s to %s\n",
+ ast_rtp_instance_get_channel_id(instance),
+ ast_format_get_name(rtp->lasttxformat),
ast_format_get_name(frame->subclass.format));
ao2_replace(rtp->lasttxformat, format);
if (rtp->smoother) {
return 0;
}
-static void calc_rxstamp(struct timeval *tv, struct ast_rtp *rtp, unsigned int timestamp, int mark)
+static void calc_rxstamp_and_jitter(struct timeval *tv,
+ struct ast_rtp *rtp, unsigned int rx_rtp_ts,
+ int mark)
{
- struct timeval now;
- struct timeval tmp;
- double transit;
- double current_time;
- double d;
- double dtv;
- double prog;
int rate = ast_rtp_get_rate(rtp->f.subclass.format);
- if ((!rtp->rxcore.tv_sec && !rtp->rxcore.tv_usec) || mark) {
- gettimeofday(&rtp->rxcore, NULL);
- rtp->drxcore = (double) rtp->rxcore.tv_sec + (double) rtp->rxcore.tv_usec / 1000000;
- /* map timestamp to a real time */
- rtp->seedrxts = timestamp; /* Their RTP timestamp started with this */
- tmp = ast_samp2tv(timestamp, rate);
- rtp->rxcore = ast_tvsub(rtp->rxcore, tmp);
- /* Round to 0.1ms for nice, pretty timestamps */
- rtp->rxcore.tv_usec -= rtp->rxcore.tv_usec % 100;
- }
+ double estimated_elapsed;
+ double jitter = 0.0;
+ double prev_jitter = 0.0;
+ struct timeval now;
+ double rxnow;
+ double arrival_sec;
+ unsigned int arrival;
+ int transit;
+ int d;
gettimeofday(&now,NULL);
- /* rxcore is the mapping between the RTP timestamp and _our_ real time from gettimeofday() */
- tmp = ast_samp2tv(timestamp, rate);
- *tv = ast_tvadd(rtp->rxcore, tmp);
-
- prog = (double)((timestamp-rtp->seedrxts)/(float)(rate));
- dtv = (double)rtp->drxcore + (double)(prog);
- current_time = (double)now.tv_sec + (double)now.tv_usec/1000000;
- transit = current_time - dtv;
- d = transit - rtp->rxtransit;
- rtp->rxtransit = transit;
- if (d<0) {
- d=-d;
- }
- rtp->rxjitter += (1./16.) * (d - rtp->rxjitter);
+
+ if (rtp->rxcount == 1 || mark) {
+ rtp->rxstart = ast_tv2double(&now);
+ rtp->remote_seed_rx_rtp_ts = rx_rtp_ts;
+
+ *tv = ast_double2tv(rtp->rxstart);
+
+ ast_debug_rtcp(3, "%s: "
+ "Seed ts: %u current time: %f\n",
+ ast_rtp_instance_get_channel_id(rtp->owner)
+ , rx_rtp_ts
+ , rtp->rxstart
+ );
+
+ return;
+ }
+
+ estimated_elapsed = ast_samp2sec(rx_rtp_ts - rtp->remote_seed_rx_rtp_ts, rate);
+ *tv = ast_double2tv(rtp->rxstart + estimated_elapsed);
+
+ /*
+ * The first few packets are generally unstable so let's
+ * not use them in the calculations.
+ */
+ if (rtp->rxcount < RTP_IGNORE_FIRST_PACKETS_COUNT) {
+ ast_debug_rtcp(3, "%s: Packet %d < %d. Ignoring\n",
+ ast_rtp_instance_get_channel_id(rtp->owner)
+ , rtp->rxcount
+ , RTP_IGNORE_FIRST_PACKETS_COUNT
+ );
+
+ return;
+ }
+
+ /*
+ * First good packet. Capture the start time and timestamp
+ * but don't actually use this packet for calculation.
+ */
+ if (rtp->rxcount == RTP_IGNORE_FIRST_PACKETS_COUNT) {
+ rtp->rxstart_stable = ast_tv2double(&now);
+ rtp->remote_seed_rx_rtp_ts_stable = rx_rtp_ts;
+ rtp->last_transit_time_samples = -rx_rtp_ts;
+
+ ast_debug_rtcp(3, "%s: "
+ "pkt: %5u Stable Seed ts: %u current time: %f\n",
+ ast_rtp_instance_get_channel_id(rtp->owner)
+ , rtp->rxcount
+ , rx_rtp_ts
+ , rtp->rxstart_stable
+ );
+
+ return;
+ }
+
+ /*
+ * If the current packet isn't in sequence, don't
+ * use it in any calculations as remote_current_rx_rtp_ts
+ * is not going to be correct.
+ */
+ if (rtp->lastrxseqno != rtp->prevrxseqno + 1) {
+ ast_debug_rtcp(3, "%s: Current packet seq %d != last packet seq %d + 1. Ignoring\n",
+ ast_rtp_instance_get_channel_id(rtp->owner)
+ , rtp->lastrxseqno
+ , rtp->prevrxseqno
+ );
+
+ return;
+ }
+
+ /*
+ * The following calculations are taken from
+ * https://www.rfc-editor.org/rfc/rfc3550#appendix-A.8
+ *
+ * The received rtp timestamp is the random "seed"
+ * timestamp chosen by the sender when they sent the
+ * first packet, plus the number of samples since then.
+ *
+ * To get our arrival time in the same units, we
+ * calculate the time difference in seconds between
+ * when we received the first packet and when we
+ * received this packet and convert that to samples.
+ */
+ rxnow = ast_tv2double(&now);
+ arrival_sec = rxnow - rtp->rxstart_stable;
+ arrival = ast_sec2samp(arrival_sec, rate);
+
+ /*
+ * Now we can use the exact formula in
+ * https://www.rfc-editor.org/rfc/rfc3550#appendix-A.8 :
+ *
+ * int transit = arrival - r->ts;
+ * int d = transit - s->transit;
+ * s->transit = transit;
+ * if (d < 0) d = -d;
+ * s->jitter += (1./16.) * ((double)d - s->jitter);
+ *
+ * Our rx_rtp_ts is their r->ts.
+ * Our rtp->last_transit_time_samples is their s->transit.
+ * Our rtp->rxjitter is their s->jitter.
+ */
+ transit = arrival - rx_rtp_ts;
+ d = transit - rtp->last_transit_time_samples;
+
+ if (d < 0) {
+ d = -d;
+ }
+
+ prev_jitter = rtp->rxjitter_samples;
+ jitter = (1.0/16.0) * (((double)d) - prev_jitter);
+ rtp->rxjitter_samples = prev_jitter + jitter;
+
+ /*
+ * We need to hang on to jitter in both samples and seconds.
+ */
+ rtp->rxjitter = ast_samp2sec(rtp->rxjitter_samples, rate);
+
+ ast_debug_rtcp(3, "%s: pkt: %5u "
+ "Arrival sec: %7.3f Arrival ts: %10u RX ts: %10u "
+ "Transit samp: %6d Last transit samp: %6d d: %4d "
+ "Curr jitter: %7.0f(%7.3f) Prev Jitter: %7.0f(%7.3f) New Jitter: %7.0f(%7.3f)\n",
+ ast_rtp_instance_get_channel_id(rtp->owner)
+ , rtp->rxcount
+ , arrival_sec
+ , arrival
+ , rx_rtp_ts
+ , transit
+ , rtp->last_transit_time_samples
+ , d
+ , jitter
+ , ast_samp2sec(jitter, rate)
+ , prev_jitter
+ , ast_samp2sec(prev_jitter, rate)
+ , rtp->rxjitter_samples
+ , rtp->rxjitter
+ );
+
+ rtp->last_transit_time_samples = transit;
+
+ /*
+ * Update all the stats.
+ */
if (rtp->rtcp) {
if (rtp->rxjitter > rtp->rtcp->maxrxjitter)
rtp->rtcp->maxrxjitter = rtp->rxjitter;
if (rtp->rtcp && rtp->rxjitter < rtp->rtcp->minrxjitter)
rtp->rtcp->minrxjitter = rtp->rxjitter;
- calc_mean_and_standard_deviation(rtp->rxjitter, &rtp->rtcp->normdev_rxjitter,
- &rtp->rtcp->stdev_rxjitter, &rtp->rtcp->rxjitter_count);
+ calc_mean_and_standard_deviation(rtp->rxjitter,
+ &rtp->rtcp->normdev_rxjitter, &rtp->rtcp->stdev_rxjitter,
+ &rtp->rtcp->rxjitter_count);
}
+
+ return;
}
static struct ast_frame *create_dtmf_frame(struct ast_rtp_instance *instance, enum ast_frame_type type, int compensate)
*/
static void update_jitter_stats(struct ast_rtp *rtp, unsigned int ia_jitter)
{
- double reported_jitter;
+ int rate = ast_rtp_get_rate(rtp->f.subclass.format);
+
+ rtp->rtcp->reported_jitter = ast_samp2sec(ia_jitter, rate);
- rtp->rtcp->reported_jitter = ia_jitter;
- reported_jitter = (double) rtp->rtcp->reported_jitter;
if (rtp->rtcp->reported_jitter_count == 0) {
- rtp->rtcp->reported_minjitter = reported_jitter;
+ rtp->rtcp->reported_minjitter = rtp->rtcp->reported_jitter;
}
- if (reported_jitter < rtp->rtcp->reported_minjitter) {
- rtp->rtcp->reported_minjitter = reported_jitter;
+ if (rtp->rtcp->reported_jitter < rtp->rtcp->reported_minjitter) {
+ rtp->rtcp->reported_minjitter = rtp->rtcp->reported_jitter;
}
- if (reported_jitter > rtp->rtcp->reported_maxjitter) {
- rtp->rtcp->reported_maxjitter = reported_jitter;
+ if (rtp->rtcp->reported_jitter > rtp->rtcp->reported_maxjitter) {
+ rtp->rtcp->reported_maxjitter = rtp->rtcp->reported_jitter;
}
- calc_mean_and_standard_deviation(reported_jitter, &rtp->rtcp->reported_normdev_jitter,
- &rtp->rtcp->reported_stdev_jitter, &rtp->rtcp->reported_jitter_count);
+ calc_mean_and_standard_deviation(rtp->rtcp->reported_jitter,
+ &rtp->rtcp->reported_normdev_jitter, &rtp->rtcp->reported_stdev_jitter,
+ &rtp->rtcp->reported_jitter_count);
}
/*!
&rtp->rtcp->reported_stdev_lost, &rtp->rtcp->reported_lost_count);
}
+#define RESCALE(in, inmin, inmax, outmin, outmax) ((((in - inmin)/(inmax-inmin))*(outmax-outmin))+outmin)
+/*!
+ * \brief Calculate a "media experience score" based on given data
+ *
+ * Technically, a mean opinion score (MOS) cannot be calculated without the involvement
+ * of human eyes (video) and ears (audio). Thus instead we'll approximate an opinion
+ * using the given parameters, and call it a media experience score.
+ *
+ * The tallied score is based upon recommendations and formulas from ITU-T G.107,
+ * ITU-T G.109, ITU-T G.113, and other various internet sources.
+ *
+ * \param normdevrtt The average round trip time
+ * \param rxjitter The smoothed jitter
+ * \param stdev_rxjitter The jitter standard deviation value
+ * \param normdev_rxlost The average number of packets lost since last check
+ *
+ * \return A media experience score.
+ *
+ * \note The calculations in this function could probably be simplified
+ * but calculating a MOS using the information available publicly,
+ * then re-scaling it to 0.0 -> 100.0 makes the process clearer and
+ * easier to troubleshoot or change.
+ */
+static double calc_media_experience_score(struct ast_rtp_instance *instance,
+ double normdevrtt, double normdev_rxjitter, double stdev_rxjitter,
+ double normdev_rxlost)
+{
+ double r_value;
+ double pseudo_mos;
+ double mes = 0;
+
+ /*
+ * While the media itself might be okay, a significant enough delay could make
+ * for an unpleasant user experience.
+ *
+ * Calculate the effective latency by using the given round trip time, and adding
+ * jitter scaled according to its standard deviation. The scaling is done in order
+ * to increase jitter's weight since a higher deviation can result in poorer overall
+ * quality.
+ */
+ double effective_latency = (normdevrtt * 1000)
+ + ((normdev_rxjitter * 2) * (stdev_rxjitter / 3))
+ + 10;
+
+ /*
+ * Using the defaults for the standard transmission rating factor ("R" value)
+ * one arrives at 93.2 (see ITU-T G.107 for more details), so we'll use that
+ * as the starting value and subtract deficiencies that could affect quality.
+ *
+ * Calculate the impact of the effective latency. Influence increases with
+ * values over 160 as the significant "lag" can degrade user experience.
+ */
+ if (effective_latency < 160) {
+ r_value = 93.2 - (effective_latency / 40);
+ } else {
+ r_value = 93.2 - (effective_latency - 120) / 10;
+ }
+
+ /* Next evaluate the impact of lost packets */
+ r_value = r_value - (normdev_rxlost * 2.0);
+
+ /*
+ * Finally convert the "R" value into a opinion/quality score between 1 (really anything
+ * below 3 should be considered poor) and 4.5 (the highest achievable for VOIP).
+ */
+ if (r_value < 0) {
+ pseudo_mos = 1.0;
+ } else if (r_value > 100) {
+ pseudo_mos = 4.5;
+ } else {
+ pseudo_mos = 1 + (0.035 * r_value) + (r_value * (r_value - 60) * (100 - r_value) * 0.0000007);
+ }
+
+ /*
+ * We're going to rescale the 0.0->5.0 pseudo_mos to the 0.0->100.0 MES.
+ * For those ranges, we could actually just multiply the pseudo_mos
+ * by 20 but we may want to change the scale later.
+ */
+ mes = RESCALE(pseudo_mos, 0.0, 5.0, 0.0, 100.0);
+
+ return mes;
+}
+
+/*!
+ * \internal
+ * \brief Update MES stats based on info received in an SR or RR.
+ * This is RTP we sent and they received.
+ */
+static void update_reported_mes_stats(struct ast_rtp *rtp)
+{
+ double mes = calc_media_experience_score(rtp->owner,
+ rtp->rtcp->normdevrtt,
+ rtp->rtcp->reported_jitter,
+ rtp->rtcp->reported_stdev_jitter,
+ rtp->rtcp->reported_normdev_lost);
+
+ rtp->rtcp->reported_mes = mes;
+ if (rtp->rtcp->reported_mes_count == 0) {
+ rtp->rtcp->reported_minmes = mes;
+ }
+ if (mes < rtp->rtcp->reported_minmes) {
+ rtp->rtcp->reported_minmes = mes;
+ }
+ if (mes > rtp->rtcp->reported_maxmes) {
+ rtp->rtcp->reported_maxmes = mes;
+ }
+
+ calc_mean_and_standard_deviation(mes, &rtp->rtcp->reported_normdev_mes,
+ &rtp->rtcp->reported_stdev_mes, &rtp->rtcp->reported_mes_count);
+
+ ast_debug_rtcp(2, "%s: rtt: %.9f j: %.9f sjh: %.9f lost: %.9f mes: %4.1f\n",
+ ast_rtp_instance_get_channel_id(rtp->owner),
+ rtp->rtcp->normdevrtt,
+ rtp->rtcp->reported_jitter,
+ rtp->rtcp->reported_stdev_jitter,
+ rtp->rtcp->reported_normdev_lost, mes);
+}
+
+/*!
+ * \internal
+ * \brief Update MES stats based on info we will send in an SR or RR.
+ * This is RTP they sent and we received.
+ */
+static void update_local_mes_stats(struct ast_rtp *rtp)
+{
+ rtp->rxmes = calc_media_experience_score(rtp->owner,
+ rtp->rtcp->normdevrtt,
+ rtp->rxjitter,
+ rtp->rtcp->stdev_rxjitter,
+ rtp->rtcp->normdev_rxlost);
+
+ if (rtp->rtcp->rxmes_count == 0) {
+ rtp->rtcp->minrxmes = rtp->rxmes;
+ }
+ if (rtp->rxmes < rtp->rtcp->minrxmes) {
+ rtp->rtcp->minrxmes = rtp->rxmes;
+ }
+ if (rtp->rxmes > rtp->rtcp->maxrxmes) {
+ rtp->rtcp->maxrxmes = rtp->rxmes;
+ }
+
+ calc_mean_and_standard_deviation(rtp->rxmes, &rtp->rtcp->normdev_rxmes,
+ &rtp->rtcp->stdev_rxmes, &rtp->rtcp->rxmes_count);
+
+ ast_debug_rtcp(2, " %s: rtt: %.9f j: %.9f sjh: %.9f lost: %.9f mes: %4.1f\n",
+ ast_rtp_instance_get_channel_id(rtp->owner),
+ rtp->rtcp->normdevrtt,
+ rtp->rxjitter,
+ rtp->rtcp->stdev_rxjitter,
+ rtp->rtcp->normdev_rxlost, rtp->rxmes);
+}
+
/*! \pre instance is locked */
static struct ast_rtp_instance *__rtp_find_instance_by_ssrc(struct ast_rtp_instance *instance,
struct ast_rtp *rtp, unsigned int ssrc, int source)
packetwords = len / 4;
- ast_debug_rtcp(1, "(%p) RTCP got report of %d bytes from %s\n",
- instance, len, ast_sockaddr_stringify(addr));
+ ast_debug_rtcp(2, "(%s) RTCP got report of %d bytes from %s\n",
+ ast_rtp_instance_get_channel_id(instance),
+ len, ast_sockaddr_stringify(addr));
/*
* Validate the RTCP packet according to an adapted and slightly
* modified RFC3550 validation algorithm.
*/
if (packetwords < RTCP_HEADER_SSRC_LENGTH) {
- ast_debug_rtcp(1, "(%p) RTCP %p -- from %s: Frame size (%u words) is too short\n",
- instance, transport_rtp, ast_sockaddr_stringify(addr), packetwords);
+ ast_debug_rtcp(2, "(%s) RTCP %p -- from %s: Frame size (%u words) is too short\n",
+ ast_rtp_instance_get_channel_id(instance),
+ transport_rtp, ast_sockaddr_stringify(addr), packetwords);
return &ast_null_frame;
}
position = 0;
first_word = ntohl(rtcpheader[position]);
if ((first_word & RTCP_VALID_MASK) != RTCP_VALID_VALUE) {
- ast_debug_rtcp(1, "(%p) RTCP %p -- from %s: Failed first packet validity check\n",
- instance, transport_rtp, ast_sockaddr_stringify(addr));
+ ast_debug_rtcp(2, "(%s) RTCP %p -- from %s: Failed first packet validity check\n",
+ ast_rtp_instance_get_channel_id(instance),
+ transport_rtp, ast_sockaddr_stringify(addr));
return &ast_null_frame;
}
do {
first_word = ntohl(rtcpheader[position]);
} while ((first_word & RTCP_VERSION_MASK_SHIFTED) == RTCP_VERSION_SHIFTED);
if (position != packetwords) {
- ast_debug_rtcp(1, "(%p) RTCP %p -- from %s: Failed packet version or length check\n",
- instance, transport_rtp, ast_sockaddr_stringify(addr));
+ ast_debug_rtcp(2, "(%s) RTCP %p -- from %s: Failed packet version or length check\n",
+ ast_rtp_instance_get_channel_id(instance),
+ transport_rtp, ast_sockaddr_stringify(addr));
return &ast_null_frame;
}
report_block->ia_jitter = ntohl(rtcpheader[i + 3]);
report_block->lsr = ntohl(rtcpheader[i + 4]);
report_block->dlsr = ntohl(rtcpheader[i + 5]);
- if (report_block->lsr
- && update_rtt_stats(rtp, report_block->lsr, report_block->dlsr)
- && rtcp_debug_test_addr(addr)) {
- struct timeval now;
- unsigned int lsr_now, lsw, msw;
- gettimeofday(&now, NULL);
- timeval2ntp(now, &msw, &lsw);
- lsr_now = (((msw & 0xffff) << 16) | ((lsw & 0xffff0000) >> 16));
- ast_verbose("Internal RTCP NTP clock skew detected: "
- "lsr=%u, now=%u, dlsr=%u (%u:%03ums), "
+ if (report_block->lsr) {
+ int skewed = update_rtt_stats(rtp, report_block->lsr, report_block->dlsr);
+ if (skewed && rtcp_debug_test_addr(addr)) {
+ struct timeval now;
+ unsigned int lsr_now, lsw, msw;
+ gettimeofday(&now, NULL);
+ timeval2ntp(now, &msw, &lsw);
+ lsr_now = (((msw & 0xffff) << 16) | ((lsw & 0xffff0000) >> 16));
+ ast_verbose("Internal RTCP NTP clock skew detected: "
+ "lsr=%u, now=%u, dlsr=%u (%u:%03ums), "
"diff=%u\n",
report_block->lsr, lsr_now, report_block->dlsr, report_block->dlsr / 65536,
(report_block->dlsr % 65536) * 1000 / 65536,
report_block->dlsr - (lsr_now - report_block->lsr));
+ }
}
update_jitter_stats(rtp, report_block->ia_jitter);
update_lost_stats(rtp, report_block->lost_count.packets);
+ /*
+ * update_reported_mes_stats must be called AFTER
+ * update_rtt_stats, update_jitter_stats and
+ * update_lost_stats.
+ */
+ update_reported_mes_stats(rtp);
if (rtcp_debug_test_addr(addr)) {
+ int rate = ast_rtp_get_rate(rtp->f.subclass.format);
+
ast_verbose(" Fraction lost: %d\n", report_block->lost_count.fraction);
ast_verbose(" Packets lost so far: %u\n", report_block->lost_count.packets);
ast_verbose(" Highest sequence number: %u\n", report_block->highest_seq_no & 0x0000ffff);
ast_verbose(" Sequence number cycles: %u\n", report_block->highest_seq_no >> 16);
- ast_verbose(" Interarrival jitter: %u\n", report_block->ia_jitter);
+ ast_verbose(" Interarrival jitter (samp): %u\n", report_block->ia_jitter);
+ ast_verbose(" Interarrival jitter (secs): %.6f\n", ast_samp2sec(report_block->ia_jitter, rate));
ast_verbose(" Last SR(our NTP): %lu.%010lu\n",(unsigned long)(report_block->lsr) >> 16,((unsigned long)(report_block->lsr) << 16) * 4096);
ast_verbose(" DLSR: %4.4f (sec)\n",(double)report_block->dlsr / 65536.0);
ast_verbose(" RTT: %4.4f(sec)\n", rtp->rtcp->rtt);
+ ast_verbose(" MES: %4.1f\n", rtp->rtcp->reported_mes);
}
}
/* If and when we handle more than one report block, this should occur outside
* this loop.
*/
- message_blob = ast_json_pack("{s: s, s: s, s: f}",
+ message_blob = ast_json_pack("{s: s, s: s, s: f, s: f}",
"from", ast_sockaddr_stringify(addr),
"to", transport_rtp->rtcp->local_addr_str,
- "rtt", rtp->rtcp->rtt);
+ "rtt", rtp->rtcp->rtt,
+ "mes", rtp->rtcp->reported_mes);
ast_rtp_publish_rtcp_message(instance, ast_rtp_rtcp_received_type(),
rtcp_report,
message_blob);
struct ast_frame *f;
/* Update statistics for jitter so they are correct in RTCP */
- calc_rxstamp(&rxtime, rtp, timestamp, mark);
+ calc_rxstamp_and_jitter(&rxtime, rtp, timestamp, mark);
+
/* When doing P2P we don't need to raise any frames about SSRC change to the core */
while ((f = AST_LIST_REMOVE_HEAD(&frames, frame_list)) != NULL) {
if (ast_format_cache_is_slinear(rtp->f.subclass.format)) {
ast_frame_byteswap_be(&rtp->f);
}
- calc_rxstamp(&rtp->f.delivery, rtp, timestamp, mark);
+ calc_rxstamp_and_jitter(&rtp->f.delivery, rtp, timestamp, mark);
/* Add timing data to let ast_generic_bridge() put the frame into a jitterbuf */
ast_set_flag(&rtp->f, AST_FRFLAG_HAS_TIMING_INFO);
rtp->f.ts = timestamp / (ast_rtp_get_rate(rtp->f.subclass.format) / 1000);
/* Video -- samples is # of samples vs. 90000 */
if (!rtp->lastividtimestamp)
rtp->lastividtimestamp = timestamp;
- calc_rxstamp(&rtp->f.delivery, rtp, timestamp, mark);
+ calc_rxstamp_and_jitter(&rtp->f.delivery, rtp, timestamp, mark);
ast_set_flag(&rtp->f, AST_FRFLAG_HAS_TIMING_INFO);
rtp->f.ts = timestamp / (ast_rtp_get_rate(rtp->f.subclass.format) / 1000);
rtp->f.samples = timestamp - rtp->lastividtimestamp;
bundled = (child || AST_VECTOR_SIZE(&rtp->ssrc_mapping)) ? 1 : 0;
prev_seqno = rtp->lastrxseqno;
+ /* We need to save lastrxseqno for use by jitter before resetting it. */
+ rtp->prevrxseqno = rtp->lastrxseqno;
rtp->lastrxseqno = seqno;
if (!rtp->recv_buffer) {
#endif
}
- ast_debug_rtcp(1, "(%p) RTCP setup on RTP instance\n", instance);
+ ast_debug_rtcp(1, "(%s) RTCP setup on RTP instance\n",
+ ast_rtp_instance_get_channel_id(instance));
} else {
if (rtp->rtcp) {
if (rtp->rtcp->schedid > -1) {
ast_free(rtp->rtcp->local_addr_str);
ast_free(rtp->rtcp);
rtp->rtcp = NULL;
+ ast_debug_rtcp(1, "(%s) RTCP torn down on RTP instance\n",
+ ast_rtp_instance_get_channel_id(instance));
}
}
} else if (property == AST_RTP_PROPERTY_ASYMMETRIC_CODEC) {
AST_RTP_STAT_TERMINATOR(AST_RTP_INSTANCE_STAT_COMBINED_LOSS);
AST_RTP_STAT_SET(AST_RTP_INSTANCE_STAT_TXJITTER, AST_RTP_INSTANCE_STAT_COMBINED_JITTER, stats->txjitter, rtp->rxjitter);
- AST_RTP_STAT_SET(AST_RTP_INSTANCE_STAT_RXJITTER, AST_RTP_INSTANCE_STAT_COMBINED_JITTER, stats->rxjitter, rtp->rtcp->reported_jitter / (unsigned int) 65536.0);
+ AST_RTP_STAT_SET(AST_RTP_INSTANCE_STAT_RXJITTER, AST_RTP_INSTANCE_STAT_COMBINED_JITTER, stats->rxjitter, rtp->rtcp->reported_jitter);
AST_RTP_STAT_SET(AST_RTP_INSTANCE_STAT_REMOTE_MAXJITTER, AST_RTP_INSTANCE_STAT_COMBINED_JITTER, stats->remote_maxjitter, rtp->rtcp->reported_maxjitter);
AST_RTP_STAT_SET(AST_RTP_INSTANCE_STAT_REMOTE_MINJITTER, AST_RTP_INSTANCE_STAT_COMBINED_JITTER, stats->remote_minjitter, rtp->rtcp->reported_minjitter);
AST_RTP_STAT_SET(AST_RTP_INSTANCE_STAT_REMOTE_NORMDEVJITTER, AST_RTP_INSTANCE_STAT_COMBINED_JITTER, stats->remote_normdevjitter, rtp->rtcp->reported_normdev_jitter);
AST_RTP_STAT_SET(AST_RTP_INSTANCE_STAT_STDEVRTT, AST_RTP_INSTANCE_STAT_COMBINED_RTT, stats->stdevrtt, rtp->rtcp->stdevrtt);
AST_RTP_STAT_TERMINATOR(AST_RTP_INSTANCE_STAT_COMBINED_RTT);
+ AST_RTP_STAT_SET(AST_RTP_INSTANCE_STAT_TXMES, AST_RTP_INSTANCE_STAT_COMBINED_MES, stats->txmes, rtp->rxmes);
+ AST_RTP_STAT_SET(AST_RTP_INSTANCE_STAT_RXMES, AST_RTP_INSTANCE_STAT_COMBINED_MES, stats->rxmes, rtp->rtcp->reported_mes);
+ AST_RTP_STAT_SET(AST_RTP_INSTANCE_STAT_REMOTE_MAXMES, AST_RTP_INSTANCE_STAT_COMBINED_MES, stats->remote_maxmes, rtp->rtcp->reported_maxmes);
+ AST_RTP_STAT_SET(AST_RTP_INSTANCE_STAT_REMOTE_MINMES, AST_RTP_INSTANCE_STAT_COMBINED_MES, stats->remote_minmes, rtp->rtcp->reported_minmes);
+ AST_RTP_STAT_SET(AST_RTP_INSTANCE_STAT_REMOTE_NORMDEVMES, AST_RTP_INSTANCE_STAT_COMBINED_MES, stats->remote_normdevmes, rtp->rtcp->reported_normdev_mes);
+ AST_RTP_STAT_SET(AST_RTP_INSTANCE_STAT_REMOTE_STDEVMES, AST_RTP_INSTANCE_STAT_COMBINED_MES, stats->remote_stdevmes, rtp->rtcp->reported_stdev_mes);
+ AST_RTP_STAT_SET(AST_RTP_INSTANCE_STAT_LOCAL_MAXMES, AST_RTP_INSTANCE_STAT_COMBINED_MES, stats->local_maxmes, rtp->rtcp->maxrxmes);
+ AST_RTP_STAT_SET(AST_RTP_INSTANCE_STAT_LOCAL_MINMES, AST_RTP_INSTANCE_STAT_COMBINED_MES, stats->local_minmes, rtp->rtcp->minrxmes);
+ AST_RTP_STAT_SET(AST_RTP_INSTANCE_STAT_LOCAL_NORMDEVMES, AST_RTP_INSTANCE_STAT_COMBINED_MES, stats->local_normdevmes, rtp->rtcp->normdev_rxmes);
+ AST_RTP_STAT_SET(AST_RTP_INSTANCE_STAT_LOCAL_STDEVMES, AST_RTP_INSTANCE_STAT_COMBINED_MES, stats->local_stdevmes, rtp->rtcp->stdev_rxjitter);
+ AST_RTP_STAT_TERMINATOR(AST_RTP_INSTANCE_STAT_COMBINED_MES);
+
+
AST_RTP_STAT_SET(AST_RTP_INSTANCE_STAT_LOCAL_SSRC, -1, stats->local_ssrc, rtp->ssrc);
AST_RTP_STAT_SET(AST_RTP_INSTANCE_STAT_REMOTE_SSRC, -1, stats->remote_ssrc, rtp->themssrc);
AST_RTP_STAT_STRCPY(AST_RTP_INSTANCE_STAT_CHANNEL_UNIQUEID, -1, stats->channel_uniqueid, ast_rtp_instance_get_channel_id(instance));
}
ao2_lock(instance);
#endif
+ ast_debug_rtp(1, "(%s) RTP Stop\n",
+ ast_rtp_instance_get_channel_id(instance));
if (rtp->rtcp && rtp->rtcp->schedid > -1) {
ao2_unlock(instance);
#include "asterisk/rtp_engine.h"
#include "asterisk/data_buffer.h"
#include "asterisk/format_cache.h"
+#include <assert.h>
+#include <sched.h>
enum test_type {
TEST_TYPE_NONE = 0, /* No special setup required */
TEST_TYPE_NACK, /* Enable NACK */
TEST_TYPE_REMB, /* Enable REMB */
+ TEST_TYPE_STD_RTCP, /* Let the stack do RTCP */
};
static void ast_sched_context_destroy_wrapper(struct ast_sched_context *sched)
struct ast_rtp_instance **instance2, struct ast_sched_context *test_sched,
enum test_type type)
{
- struct ast_sockaddr addr;
+ struct ast_sockaddr addr1;
+ struct ast_sockaddr addr2;
+ enum ast_rtp_instance_rtcp rtcp_type = AST_RTP_INSTANCE_RTCP_MUX;
- ast_sockaddr_parse(&addr, "127.0.0.1", 0);
+ ast_sockaddr_parse(&addr1, "127.0.0.1", 0);
+ ast_sockaddr_parse(&addr2, "127.0.0.1", 0);
- *instance1 = ast_rtp_instance_new("asterisk", test_sched, &addr, NULL);
- *instance2 = ast_rtp_instance_new("asterisk", test_sched, &addr, NULL);
+ *instance1 = ast_rtp_instance_new("asterisk", test_sched, &addr1, "instance1");
+ *instance2 = ast_rtp_instance_new("asterisk", test_sched, &addr2, "instance2");
if (!instance1 || !instance2) {
return -1;
}
- ast_rtp_instance_set_prop(*instance1, AST_RTP_PROPERTY_RTCP, AST_RTP_INSTANCE_RTCP_MUX);
- ast_rtp_instance_set_prop(*instance2, AST_RTP_PROPERTY_RTCP, AST_RTP_INSTANCE_RTCP_MUX);
+ ast_rtp_instance_set_channel_id(*instance1, "instance1");
+ ast_rtp_instance_set_channel_id(*instance2, "instance2");
+
+ if (type == TEST_TYPE_STD_RTCP) {
+ rtcp_type = AST_RTP_INSTANCE_RTCP_STANDARD;
+ }
+
+ ast_rtp_instance_set_prop(*instance1,
+ AST_RTP_PROPERTY_RTCP, rtcp_type);
+ ast_rtp_instance_set_prop(*instance2,
+ AST_RTP_PROPERTY_RTCP, rtcp_type);
if (type == TEST_TYPE_NACK) {
ast_rtp_instance_set_prop(*instance1, AST_RTP_PROPERTY_RETRANS_RECV, 1);
ast_rtp_instance_set_prop(*instance2, AST_RTP_PROPERTY_REMB, 1);
}
- ast_rtp_instance_get_local_address(*instance1, &addr);
- ast_rtp_instance_set_remote_address(*instance2, &addr);
+ ast_rtp_instance_get_local_address(*instance1, &addr1);
+ ast_rtp_instance_set_remote_address(*instance2, &addr1);
- ast_rtp_instance_get_local_address(*instance2, &addr);
- ast_rtp_instance_set_remote_address(*instance1, &addr);
+ ast_rtp_instance_get_local_address(*instance2, &addr2);
+ ast_rtp_instance_set_remote_address(*instance1, &addr2);
ast_rtp_instance_reset_test_engine(*instance1);
test_read_frames(instance2, num);
}
+
+/*
+ * Unfortunately, we can't use usleep() to create
+ * packet spacing because there are signals in use
+ * which cause usleep to immediately return. Instead
+ * we have to spin. :(
+ */
+static void SLEEP_SPINNER(int ms)
+{
+ struct timeval a = ast_tvnow();
+
+ while(1) {
+ sched_yield();
+ if (ast_remaining_ms(a, ms) <= 0) {
+ break;
+ }
+ }
+}
+
+/*
+ * This function is NOT really a reliable implementation.
+ * Its purpose is only to aid in code development in res_rtp_asterisk.
+ */
+static void test_write_and_read_interleaved_frames(struct ast_rtp_instance *instance1,
+ struct ast_rtp_instance *instance2, int howlong, int rtcp_interval)
+{
+ char data[320] = "";
+ int pktinterval = 20;
+
+ struct ast_frame frame_out1 = {
+ .frametype = AST_FRAME_VOICE,
+ .subclass.format = ast_format_ulaw,
+ .seqno = 4556,
+ .data.ptr = data,
+ .datalen = 160,
+ .samples = 1,
+ .len = pktinterval,
+ .ts = 4622295,
+ };
+ struct ast_frame frame_out2 = {
+ .frametype = AST_FRAME_VOICE,
+ .subclass.format = ast_format_ulaw,
+ .seqno = 6554,
+ .data.ptr = data,
+ .datalen = 160,
+ .samples = 1,
+ .len = pktinterval,
+ .ts = 8622295,
+ };
+ struct ast_frame *frame_in1;
+ struct ast_frame *frame_in2;
+ int index;
+ int num;
+ int rtcpnum;
+ int reverse = 1;
+ int send_rtcp = 0;
+
+ num = howlong / pktinterval;
+
+ rtcpnum = rtcp_interval / pktinterval;
+
+ ast_set_flag(&frame_out1, AST_FRFLAG_HAS_SEQUENCE_NUMBER);
+ ast_set_flag(&frame_out1, AST_FRFLAG_HAS_TIMING_INFO);
+ ast_set_flag(&frame_out2, AST_FRFLAG_HAS_SEQUENCE_NUMBER);
+ ast_set_flag(&frame_out2, AST_FRFLAG_HAS_TIMING_INFO);
+
+ for (index = 0; index < num; index++) {
+ struct timeval start = ast_tvnow();
+ time_t ms;
+
+ if (index == 1) {
+ ast_clear_flag(&frame_out1, AST_FRFLAG_HAS_SEQUENCE_NUMBER);
+ ast_clear_flag(&frame_out1, AST_FRFLAG_HAS_TIMING_INFO);
+ ast_clear_flag(&frame_out2, AST_FRFLAG_HAS_SEQUENCE_NUMBER);
+ ast_clear_flag(&frame_out2, AST_FRFLAG_HAS_TIMING_INFO);
+ }
+ frame_out1.seqno += index;
+ frame_out1.delivery = start;
+ frame_out1.ts += frame_out1.len;
+ ast_rtp_instance_write(instance1, &frame_out1);
+
+ if (send_rtcp && index && (index % rtcpnum == 0)) {
+ ast_rtp_instance_queue_report(instance1);
+ }
+
+ frame_in2 = ast_rtp_instance_read(instance2, 0);
+ ast_frfree(frame_in2);
+ frame_in2 = ast_rtp_instance_read(instance2, 1);
+ ast_frfree(frame_in2);
+
+ if (reverse) {
+ frame_out2.seqno += index;
+ frame_out2.delivery = ast_tvnow();
+ frame_out2.ts += frame_out2.len;
+ ast_rtp_instance_write(instance2, &frame_out2);
+
+ if (send_rtcp && index && (index % rtcpnum == 0)) {
+ ast_rtp_instance_queue_report(instance2);
+ }
+
+ frame_in1 = ast_rtp_instance_read(instance1, 0);
+ ast_frfree(frame_in1);
+ frame_in1 = ast_rtp_instance_read(instance1, 1);
+ ast_frfree(frame_in1);
+
+ }
+
+ ms = frame_out1.len - ast_tvdiff_ms(ast_tvnow(),start);
+ ms += (index % 2 ? 5 : 12);
+ ms += (index % 3 ? 2 : 30);
+ SLEEP_SPINNER(ms);
+ }
+}
+
AST_TEST_DEFINE(nack_no_packet_loss)
{
RAII_VAR(struct ast_rtp_instance *, instance1, NULL, ast_rtp_instance_destroy);
return AST_TEST_PASS;
}
+/*
+ * This test should not normally be run. Its only purpose is to
+ * aid in code development.
+ */
+AST_TEST_DEFINE(mes)
+{
+ RAII_VAR(struct ast_rtp_instance *, instance1, NULL, ast_rtp_instance_destroy);
+ RAII_VAR(struct ast_rtp_instance *, instance2, NULL, ast_rtp_instance_destroy);
+ RAII_VAR(struct ast_sched_context *, test_sched, NULL, ast_sched_context_destroy_wrapper);
+
+ switch (cmd) {
+ case TEST_INIT:
+ info->name = "mes";
+ info->category = "/res/res_rtp/";
+ info->summary = "Media Experience Score";
+ info->description =
+ "Tests calculation of Media Experience Score (only run by explicit request)";
+ info->explicit_only = 1;
+ return AST_TEST_NOT_RUN;
+ case TEST_EXECUTE:
+ break;
+ }
+
+ test_sched = ast_sched_context_create();
+ ast_sched_start_thread(test_sched);
+
+ if ((test_init_rtp_instances(&instance1, &instance2,
+ test_sched, TEST_TYPE_NONE)) < 0) {
+ ast_log(LOG_ERROR, "Failed to initialize test!\n");
+ return AST_TEST_FAIL;
+ }
+
+ test_write_and_read_interleaved_frames(
+ instance1, instance2, 1000, 5000);
+
+ return AST_TEST_PASS;
+}
+
static int unload_module(void)
{
+ AST_TEST_UNREGISTER(mes);
AST_TEST_UNREGISTER(nack_no_packet_loss);
AST_TEST_UNREGISTER(nack_nominal);
AST_TEST_UNREGISTER(nack_overflow);
AST_TEST_REGISTER(remb_nominal);
AST_TEST_REGISTER(sr_rr_nominal);
AST_TEST_REGISTER(fir_nominal);
+ AST_TEST_REGISTER(mes);
return AST_MODULE_LOAD_SUCCESS;
}