[Asterisk-code-review] res_rtp_asterisk: Asterisk Media Experience Score (MES) (asterisk[18])

George Joseph asteriskteam at digium.com
Tue Jan 3 07:54:58 CST 2023


George Joseph has submitted this change. ( https://gerrit.asterisk.org/c/asterisk/+/19479 )

Change subject: res_rtp_asterisk: Asterisk Media Experience Score (MES)
......................................................................

res_rtp_asterisk: Asterisk Media Experience Score (MES)

This module has been updated to provide additional
quality statistics in the form of an Asterisk
Media Experience Score.  The score is avilable using
the same mechanisms you'd use to retrieve jitter, loss,
and rtt statistics.  For more information about the
score and how to retrieve it, see
https://wiki.asterisk.org/wiki/display/AST/Media+Experience+Score

* Updated chan_pjsip to set quality channel variables when a
  call ends.
* Updated channels/pjsip/dialplan_functions.c to add the ability
  to retrieve the MES along with the existing rtcp stats when
  using the CHANNEL dialplan function.
* Added the ast_debug_rtp_is_allowed and ast_debug_rtcp_is_allowed
  checks for debugging purposes.
* Added several function to time.h for manipulating time-in-samples
  and times represented as double seconds.
* Updated rtp_engine.c to pass through the MES when stats are
  requested.  Also debug output that dumps the stats when an
  rtp instance is destroyed.
* Updated res_rtp_asterisk.c to implement the calculation of the
  MES.  In the process, also had to update the calculation of
  jitter.  Many debugging statements were also changed to be
  more informative.
* Added a unit test for internal testing.  The test should not be
  run during normal operation and is disabled by default.

ASTERISK-30280

Change-Id: I458cb9a311e8e5dc1db769b8babbcf2e093f107a
---
M channels/chan_pjsip.c
M channels/pjsip/dialplan_functions.c
A doc/CHANGES-staging/res_rtp_asterisk.txt
M include/asterisk/rtp_engine.h
M include/asterisk/time.h
M main/rtp_engine.c
M res/res_rtp_asterisk.c
M tests/test_res_rtp.c
8 files changed, 970 insertions(+), 102 deletions(-)

Approvals:
  Joshua Colp: Looks good to me, but someone else must approve
  George Joseph: Looks good to me, approved; Approved for Submit




diff --git a/channels/chan_pjsip.c b/channels/chan_pjsip.c
index 3e8abd2..22ed5ec 100644
--- a/channels/chan_pjsip.c
+++ b/channels/chan_pjsip.c
@@ -2513,6 +2513,15 @@
 		if (session) {
 			int cause = h_data->cause;
 
+			if (channel->session->active_media_state &&
+				channel->session->active_media_state->default_session[AST_MEDIA_TYPE_AUDIO]) {
+				struct ast_sip_session_media *media =
+					channel->session->active_media_state->default_session[AST_MEDIA_TYPE_AUDIO];
+				if (media->rtp) {
+					ast_rtp_instance_set_stats_vars(ast, media->rtp);
+				}
+			}
+
 			/*
 	 		* It's possible that session_terminate might cause the session to be destroyed
 	 		* immediately so we need to keep a reference to it so we can NULL session->channel
@@ -2993,6 +3002,16 @@
 		SCOPE_EXIT_RTN("No channel\n");
 	}
 
+
+	if (session->active_media_state &&
+		session->active_media_state->default_session[AST_MEDIA_TYPE_AUDIO]) {
+		struct ast_sip_session_media *media =
+			session->active_media_state->default_session[AST_MEDIA_TYPE_AUDIO];
+		if (media->rtp) {
+			ast_rtp_instance_set_stats_vars(session->channel, media->rtp);
+		}
+	}
+
 	chan_pjsip_remove_hold(ast_channel_uniqueid(session->channel));
 
 	ast_set_hangupsource(session->channel, ast_channel_name(session->channel), 0);
diff --git a/channels/pjsip/dialplan_functions.c b/channels/pjsip/dialplan_functions.c
index a433d07..0496f04 100644
--- a/channels/pjsip/dialplan_functions.c
+++ b/channels/pjsip/dialplan_functions.c
@@ -304,6 +304,12 @@
 							<enum name="rtt">
 								<para>Round trip time</para>
 							</enum>
+							<enum name="txmes">
+								<para>Transmitted Media Experience Score</para>
+							</enum>
+							<enum name="rxmes">
+								<para>Received Media Experience Score</para>
+							</enum>
 						</enumlist>
 					</enum>
 					<enum name="all_jitter">
@@ -387,6 +393,37 @@
 							</enum>
 						</enumlist>
 					</enum>
+					<enum name="all_mes">
+						<para>Retrieve a summary of all RTCP Media Experience Score information.</para>
+						<para>The following data items are returned in a semi-colon
+						delineated list:</para>
+						<enumlist>
+							<enum name="minmes">
+								<para>Minimum MES based on us analysing received packets.</para>
+							</enum>
+							<enum name="maxmes">
+								<para>Maximum MES based on us analysing received packets.</para>
+							</enum>
+							<enum name="avgmes">
+								<para>Average MES based on us analysing received packets.</para>
+							</enum>
+							<enum name="stdevmes">
+								<para>Standard deviation MES based on us analysing received packets.</para>
+							</enum>
+							<enum name="reported_minmes">
+								<para>Minimum MES based on data we get in Sender and Receiver Reports sent by the remote end</para>
+							</enum>
+							<enum name="reported_maxmes">
+								<para>Maximum MES based on data we get in Sender and Receiver Reports sent by the remote end</para>
+							</enum>
+							<enum name="reported_avgmes">
+								<para>Average MES based on data we get in Sender and Receiver Reports sent by the remote end</para>
+							</enum>
+							<enum name="reported_stdevmes">
+								<para>Standard deviation MES based on data we get in Sender and Receiver Reports sent by the remote end</para>
+							</enum>
+						</enumlist>
+					</enum>
 					<enum name="txcount"><para>Transmitted packet count</para></enum>
 					<enum name="rxcount"><para>Received packet count</para></enum>
 					<enum name="txjitter"><para>Transmitted packet jitter</para></enum>
@@ -416,6 +453,24 @@
 					<enum name="stdevrtt"><para>Standard deviation round trip time</para></enum>
 					<enum name="local_ssrc"><para>Our Synchronization Source identifier</para></enum>
 					<enum name="remote_ssrc"><para>Their Synchronization Source identifier</para></enum>
+					<enum name="txmes"><para>
+					Current MES based on us analyzing rtt, jitter and loss
+					in the actual received RTP stream received from the remote end.
+					I.E.  This is the MES for the incoming audio stream.
+					</para></enum>
+					<enum name="rxmes"><para>
+					Current MES based on rtt and the jitter and loss values in
+					RTCP sender and receiver reports we receive from the
+					remote end. I.E.  This is the MES for the outgoing audio stream.
+					</para></enum>
+					<enum name="remote_maxmes"><para>Max MES based on data we get in Sender and Receiver Reports sent by the remote end</para></enum>
+					<enum name="remote_minmes"><para>Min MES based on data we get in Sender and Receiver Reports sent by the remote end</para></enum>
+					<enum name="remote_normdevmes"><para>Average MES based on data we get in Sender and Receiver Reports sent by the remote end</para></enum>
+					<enum name="remote_stdevmes"><para>Standard deviation MES based on data we get in Sender and Receiver Reports sent by the remote end</para></enum>
+					<enum name="local_maxmes"><para>Max MES based on us analyzing the received RTP stream</para></enum>
+					<enum name="local_minmes"><para>Min MES based on us analyzing the received RTP stream</para></enum>
+					<enum name="local_normdevmes"><para>Average MES based on us analyzing the received RTP stream</para></enum>
+					<enum name="local_stdevmes"><para>Standard deviation MES based on us analyzing the received RTP stream</para></enum>
 				</enumlist>
 			</parameter>
 			<parameter name="media_type" required="false">
@@ -678,6 +733,8 @@
 			stat_field = AST_RTP_INSTANCE_STAT_FIELD_QUALITY_RTT;
 		} else if (!strcasecmp(type, "all_loss")) {
 			stat_field = AST_RTP_INSTANCE_STAT_FIELD_QUALITY_LOSS;
+		} else if (!strcasecmp(type, "all_mes")) {
+			stat_field = AST_RTP_INSTANCE_STAT_FIELD_QUALITY_MES;
 		}
 
 		if (!ast_rtp_instance_get_quality(media->rtp, stat_field, buf, buflen)) {
@@ -724,6 +781,16 @@
 			{ "stdevrtt",              DBL, { .d8 = &stats.stdevrtt, }, },
 			{ "local_ssrc",            INT, { .i4 = &stats.local_ssrc, }, },
 			{ "remote_ssrc",           INT, { .i4 = &stats.remote_ssrc, }, },
+			{ "txmes",                 DBL, { .d8 = &stats.txmes, }, },
+			{ "rxmes",                 DBL, { .d8 = &stats.rxmes, }, },
+			{ "remote_maxmes",         DBL, { .d8 = &stats.remote_maxmes, }, },
+			{ "remote_minmes",         DBL, { .d8 = &stats.remote_minmes, }, },
+			{ "remote_normdevmes",     DBL, { .d8 = &stats.remote_normdevmes, }, },
+			{ "remote_stdevmes",       DBL, { .d8 = &stats.remote_stdevmes, }, },
+			{ "local_maxmes",          DBL, { .d8 = &stats.local_maxmes, }, },
+			{ "local_minmes",          DBL, { .d8 = &stats.local_minmes, }, },
+			{ "local_normdevmes",      DBL, { .d8 = &stats.local_normdevmes, }, },
+			{ "local_stdevmes",        DBL, { .d8 = &stats.local_stdevmes, }, },
 			{ NULL, },
 		};
 
diff --git a/doc/CHANGES-staging/res_rtp_asterisk.txt b/doc/CHANGES-staging/res_rtp_asterisk.txt
new file mode 100644
index 0000000..9c8e05f
--- /dev/null
+++ b/doc/CHANGES-staging/res_rtp_asterisk.txt
@@ -0,0 +1,9 @@
+Subject: res_rtp_asterisk
+
+This module has been updated to provide additional
+quality statistics in the form of an Asterisk
+Media Experience Score.  The score is available using
+the same mechanisms you'd use to retrieve jitter, loss,
+and rtt statistics.  For more information about the
+score and how to retrieve it, see
+https://wiki.asterisk.org/wiki/display/AST/Media+Experience+Score
diff --git a/include/asterisk/rtp_engine.h b/include/asterisk/rtp_engine.h
index 50e2a7b..7834acd 100644
--- a/include/asterisk/rtp_engine.h
+++ b/include/asterisk/rtp_engine.h
@@ -174,6 +174,8 @@
 	AST_RTP_INSTANCE_STAT_FIELD_QUALITY_LOSS,
 	/*! Retrieve quality information about round trip time */
 	AST_RTP_INSTANCE_STAT_FIELD_QUALITY_RTT,
+	/*! Retrieve quality information about Media Experience Score */
+	AST_RTP_INSTANCE_STAT_FIELD_QUALITY_MES,
 };
 
 /*! Statistics that can be retrieved from an RTP instance */
@@ -250,6 +252,29 @@
 	AST_RTP_INSTANCE_STAT_TXOCTETCOUNT,
 	/*! Retrieve number of octets received */
 	AST_RTP_INSTANCE_STAT_RXOCTETCOUNT,
+
+	/*! Retrieve ALL statistics relating to Media Experience Score */
+	AST_RTP_INSTANCE_STAT_COMBINED_MES,
+	/*! Retrieve MES on transmitted packets */
+	AST_RTP_INSTANCE_STAT_TXMES,
+	/*! Retrieve MES on received packets */
+	AST_RTP_INSTANCE_STAT_RXMES,
+	/*! Retrieve maximum MES on remote side */
+	AST_RTP_INSTANCE_STAT_REMOTE_MAXMES,
+	/*! Retrieve minimum MES on remote side */
+	AST_RTP_INSTANCE_STAT_REMOTE_MINMES,
+	/*! Retrieve average MES on remote side */
+	AST_RTP_INSTANCE_STAT_REMOTE_NORMDEVMES,
+	/*! Retrieve standard deviation MES on remote side */
+	AST_RTP_INSTANCE_STAT_REMOTE_STDEVMES,
+	/*! Retrieve maximum MES on local side */
+	AST_RTP_INSTANCE_STAT_LOCAL_MAXMES,
+	/*! Retrieve minimum MES on local side */
+	AST_RTP_INSTANCE_STAT_LOCAL_MINMES,
+	/*! Retrieve average MES on local side */
+	AST_RTP_INSTANCE_STAT_LOCAL_NORMDEVMES,
+	/*! Retrieve standard deviation MES on local side */
+	AST_RTP_INSTANCE_STAT_LOCAL_STDEVMES,
 };
 
 enum ast_rtp_instance_rtcp {
@@ -428,6 +453,27 @@
 	unsigned int txoctetcount;
 	/*! Number of octets received */
 	unsigned int rxoctetcount;
+
+	/*! Media Experience Score on transmitted packets */
+	double txmes;
+	/*! Media Experience Score on received packets */
+	double rxmes;
+	/*! Maximum MES on remote side */
+	double remote_maxmes;
+	/*! Minimum MES on remote side */
+	double remote_minmes;
+	/*! Average MES on remote side */
+	double remote_normdevmes;
+	/*! Standard deviation MES on remote side */
+	double remote_stdevmes;
+	/*! Maximum MES on local side */
+	double local_maxmes;
+	/*! Minimum MES on local side */
+	double local_minmes;
+	/*! Average MES on local side */
+	double local_normdevmes;
+	/*! Standard deviation MES on local side */
+	double local_stdevmes;
 };
 
 #define AST_RTP_STAT_SET(current_stat, combined, placement, value) \
@@ -2860,6 +2906,10 @@
 #define ast_debug_rtp(sublevel, ...) \
 	ast_debug_category(sublevel, AST_DEBUG_CATEGORY_RTP,  __VA_ARGS__)
 
+/* Allow logging of RTP? */
+#define ast_debug_rtp_is_allowed \
+	ast_debug_category_is_allowed(AST_LOG_CATEGORY_ENABLED, AST_DEBUG_CATEGORY_RTP)
+
 /* Allow logging of RTP packets? */
 #define ast_debug_rtp_packet_is_allowed \
 	ast_debug_category_is_allowed(AST_LOG_CATEGORY_ENABLED, AST_DEBUG_CATEGORY_RTP_PACKET)
@@ -2873,6 +2923,10 @@
 #define ast_debug_rtcp(sublevel, ...) \
 	ast_debug_category(sublevel, AST_DEBUG_CATEGORY_RTCP, __VA_ARGS__)
 
+/* Allow logging of RTCP? */
+#define ast_debug_rtcp_is_allowed \
+	ast_debug_category_is_allowed(AST_LOG_CATEGORY_ENABLED, AST_DEBUG_CATEGORY_RTCP)
+
 /* Allow logging of RTCP packets? */
 #define ast_debug_rtcp_packet_is_allowed \
 	ast_debug_category_is_allowed(AST_LOG_CATEGORY_ENABLED, AST_DEBUG_CATEGORY_RTCP_PACKET)
diff --git a/include/asterisk/time.h b/include/asterisk/time.h
index 3e0c064..098f460 100644
--- a/include/asterisk/time.h
+++ b/include/asterisk/time.h
@@ -33,6 +33,8 @@
 #include <unistd.h>
 #endif
 
+#include <math.h>
+
 #include "asterisk/inline_api.h"
 
 /* A time_t can be represented as an unsigned long long (or uint64_t).
@@ -233,6 +235,41 @@
 )
 
 /*!
+ * \brief Returns a timeval structure corresponding to the
+ * number of seconds in the double _td.
+ *
+ * \param _td The number of seconds.
+ * \returns A timeval structure containing the number of seconds.
+ *
+ * This is the inverse of ast_tv2double().
+ */
+AST_INLINE_API(
+struct timeval ast_double2tv(double _td),
+{
+	struct timeval t;
+	t.tv_sec = (typeof(t.tv_sec))floor(_td);
+	t.tv_usec = (typeof(t.tv_usec))(_td - t.tv_sec) / 1000000.0;
+	return t;
+}
+)
+
+/*!
+ * \brief Returns a double corresponding to the number of seconds
+ * in the timeval _tv.
+ *
+ * \param _tv A pointer to a timeval structure.
+ * \returns A double containing the number of seconds.
+ *
+ * This is the inverse of ast_double2tv().
+ */
+AST_INLINE_API(
+double ast_tv2double(const struct timeval *tv),
+{
+	return tv->tv_sec + ((double)tv->tv_usec) / 1000000.0;
+}
+)
+
+/*!
  * \brief Returns a timeval corresponding to the duration of n samples at rate r.
  * Useful to convert samples to timevals, or even milliseconds to timevals
  * in the form ast_samp2tv(milliseconds, 1000)
@@ -245,6 +282,57 @@
 )
 
 /*!
+ * \brief Returns the number of samples at rate _rate in the
+ * duration specified by _tv.
+ *
+ * \param _tv A pointer to a timeval structure.
+ * \param _rate The sample rate in Hz.
+ * \returns A time_t containing the number of samples.
+ *
+ * This is the inverse of ast_samp2tv().
+ */
+AST_INLINE_API(
+time_t ast_tv2samp(const struct timeval *_tv, int _rate),
+{
+	return (time_t)(ast_tv2double(_tv) * (double)_rate);
+}
+)
+
+/*!
+ * \brief Returns the duration in seconds of _nsamp samples
+ * at rate _rate.
+ *
+ * \param _nsamp The number of samples
+ * \param _rate The sample rate in Hz.
+ * \returns A double containing the number of seconds.
+ *
+ * This is the inverse of ast_sec2samp().
+ */
+AST_INLINE_API(
+double ast_samp2sec(int _nsamp, unsigned int _rate),
+{
+	return ((double)_nsamp) / ((double)_rate);
+}
+)
+
+/*!
+ * \brief Returns the number of samples at _rate in the duration
+ * in _seconds.
+ *
+ * \param _seconds The time interval in seconds.
+ * \param _rate The sample rate in Hz.
+ * \returns The number of samples.
+ *
+ * This is the inverse of ast_samp2sec().
+ */
+AST_INLINE_API(
+unsigned int ast_sec2samp(double _seconds, int _rate),
+{
+	return (unsigned int)(_seconds * _rate);
+}
+)
+
+/*!
  * \brief Time units enumeration.
  */
 enum TIME_UNIT {
diff --git a/main/rtp_engine.c b/main/rtp_engine.c
index eac3a02..d36f70c 100644
--- a/main/rtp_engine.c
+++ b/main/rtp_engine.c
@@ -143,7 +143,6 @@
 
 #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 */
@@ -457,6 +456,28 @@
 
 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;
@@ -2463,6 +2484,8 @@
 		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;
 	}
@@ -2474,16 +2497,25 @@
 
 	/* 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;
@@ -2540,6 +2572,15 @@
 		}
 	}
 
+	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) {
@@ -3312,6 +3353,7 @@
 		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));
 		}
@@ -3321,6 +3363,9 @@
 		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);
@@ -4006,6 +4051,19 @@
 	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;
 }
 
diff --git a/res/res_rtp_asterisk.c b/res/res_rtp_asterisk.c
index 0700cbd..41eb636 100644
--- a/res/res_rtp_asterisk.c
+++ b/res/res_rtp_asterisk.c
@@ -194,6 +194,16 @@
 #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;
 
@@ -391,22 +401,32 @@
 	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 */
@@ -422,9 +442,8 @@
 	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. */
@@ -433,6 +452,12 @@
 	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 */
@@ -526,7 +551,7 @@
 	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 */
@@ -560,6 +585,19 @@
 	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;
 
@@ -630,6 +668,8 @@
 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);
@@ -4024,16 +4064,16 @@
 	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);
 
@@ -4135,6 +4175,7 @@
 	AST_VECTOR_FREE(&rtp->missing_seqno);
 
 	/* Finally destroy ourselves */
+	rtp->owner = NULL;
 	ast_free(rtp);
 
 	return 0;
@@ -4546,6 +4587,11 @@
 
 	/* 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;
@@ -4569,7 +4615,7 @@
 		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)) {
@@ -4646,20 +4692,24 @@
 			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);
 
@@ -5116,7 +5166,8 @@
 			}
 		} 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) {
@@ -5374,8 +5425,9 @@
 	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) {
@@ -5438,43 +5490,164 @@
 	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;
+	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;
 	}
-	rtp->rxjitter += (1./16.) * (d - rtp->rxjitter);
+
+	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;
@@ -5483,9 +5656,12 @@
 		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)
@@ -5851,22 +6027,23 @@
  */
 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 = ia_jitter;
-	reported_jitter = (double) rtp->rtcp->reported_jitter;
+	rtp->rtcp->reported_jitter = ast_samp2sec(ia_jitter, rate);
+
 	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);
 }
 
 /*!
@@ -5893,6 +6070,158 @@
 		&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)
@@ -6151,23 +6480,26 @@
 
 	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 {
@@ -6178,8 +6510,9 @@
 		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;
 	}
 
@@ -6446,43 +6779,55 @@
 				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);
@@ -7366,7 +7711,8 @@
 		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) {
@@ -7517,7 +7863,7 @@
 		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);
@@ -7526,7 +7872,7 @@
 		/* 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;
@@ -7975,6 +8321,8 @@
 	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) {
@@ -8438,7 +8786,8 @@
 #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) {
@@ -8482,6 +8831,8 @@
 				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) {
@@ -8722,7 +9073,7 @@
 	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);
@@ -8740,6 +9091,19 @@
 	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));
@@ -8795,6 +9159,8 @@
 	}
 	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);
diff --git a/tests/test_res_rtp.c b/tests/test_res_rtp.c
index 1d36116..2ecf383 100644
--- a/tests/test_res_rtp.c
+++ b/tests/test_res_rtp.c
@@ -36,11 +36,14 @@
 #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)
@@ -54,18 +57,30 @@
 	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);
@@ -77,11 +92,11 @@
 		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);
 
@@ -130,6 +145,120 @@
 	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);
@@ -523,8 +652,47 @@
 	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);
@@ -544,6 +712,7 @@
 	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;
 }
 

-- 
To view, visit https://gerrit.asterisk.org/c/asterisk/+/19479
To unsubscribe, or for help writing mail filters, visit https://gerrit.asterisk.org/settings

Gerrit-Project: asterisk
Gerrit-Branch: 18
Gerrit-Change-Id: I458cb9a311e8e5dc1db769b8babbcf2e093f107a
Gerrit-Change-Number: 19479
Gerrit-PatchSet: 8
Gerrit-Owner: George Joseph <gjoseph at digium.com>
Gerrit-Reviewer: George Joseph <gjoseph at digium.com>
Gerrit-Reviewer: Joshua Colp <jcolp at sangoma.com>
Gerrit-Reviewer: Kevin Harwell <default.enum at gmail.com>
Gerrit-Reviewer: N A <asterisk at phreaknet.org>
Gerrit-MessageType: merged
-------------- next part --------------
An HTML attachment was scrubbed...
URL: <http://lists.digium.com/pipermail/asterisk-code-review/attachments/20230103/a3e0cf0e/attachment-0001.html>


More information about the asterisk-code-review mailing list