res_rtp_asterisk: Asterisk Media Experience Score (MES)

-----------------

This commit reinstates MES with some casting fixes to the
functions in time.h that convert between doubles and timeval
structures.  The casting issues were causing incorrect
timestamps to be calculated which caused transcoding from/to
G722 to produce bad or no audio.

ASTERISK-30391

-----------------

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.

Change-Id: I4fce265965e68c3fdfeca55e614371ee69c65038
This commit is contained in:
George Joseph 2022-10-28 04:57:56 -06:00
parent 3a3d6c7dcb
commit 7dc8773178
8 changed files with 934 additions and 102 deletions

View File

@ -2513,6 +2513,15 @@ static int hangup(void *data)
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 @@ static void chan_pjsip_session_end(struct ast_sip_session *session)
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);

View File

@ -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 @@ static int channel_read_rtcp(struct ast_channel *chan, const char *type, const c
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 @@ static int channel_read_rtcp(struct ast_channel *chan, const char *type, const c
{ "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, },
};

View File

@ -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

View File

@ -174,6 +174,8 @@ enum ast_rtp_instance_stat_field {
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 @@ enum ast_rtp_instance_stat {
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 @@ struct ast_rtp_instance_stats {
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 @@ uintmax_t ast_debug_category_ice_id(void);
#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 @@ uintmax_t ast_debug_category_ice_id(void);
#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)

View File

@ -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).
@ -232,6 +234,41 @@ struct timeval ast_tv(ast_time_t sec, ast_suseconds_t usec),
}
)
/*!
* \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 (((double)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
@ -244,6 +281,57 @@ struct timeval ast_samp2tv(unsigned int _nsamp, unsigned int _rate),
}
)
/*!
* \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(unsigned 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.
*/

View File

@ -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 @@ static void instance_destructor(void *obj)
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 @@ char *ast_rtp_instance_get_quality(struct ast_rtp_instance *instance, enum ast_r
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 @@ char *ast_rtp_instance_get_quality(struct ast_rtp_instance *instance, enum ast_r
/* 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 @@ void ast_rtp_instance_set_stats_vars(struct ast_channel *chan, struct ast_rtp_in
}
}
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 @@ static struct ast_manager_event_blob *rtcp_report_to_ami(struct stasis_message *
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 @@ static struct ast_manager_event_blob *rtcp_report_to_ami(struct stasis_message *
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 @@ struct ast_json *ast_rtp_convert_stats_json(const struct ast_rtp_instance_stats
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;
}

View File

@ -194,6 +194,16 @@ enum strict_rtp_mode {
#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 @@ struct ast_rtp {
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 @@ struct ast_rtp {
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 @@ struct ast_rtp {
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 @@ struct ast_rtcp {
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 @@ struct ast_rtcp {
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_remote_ssrc(struct ast_rtp_instance *instance, unsigned
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 @@ static int ast_rtp_new(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);
@ -4135,6 +4175,7 @@ static int ast_rtp_destroy(struct ast_rtp_instance *instance)
AST_VECTOR_FREE(&rtp->missing_seqno);
/* Finally destroy ourselves */
rtp->owner = NULL;
ast_free(rtp);
return 0;
@ -4546,6 +4587,11 @@ static int ast_rtcp_generate_report(struct ast_rtp_instance *instance, unsigned
/* 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 @@ static int ast_rtcp_generate_report(struct ast_rtp_instance *instance, unsigned
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 @@ static int ast_rtcp_calculate_sr_rr_statistics(struct ast_rtp_instance *instance
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 @@ static int rtp_raw_write(struct ast_rtp_instance *instance, struct ast_frame *fr
}
} 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 @@ static int ast_rtp_write(struct ast_rtp_instance *instance, struct ast_frame *fr
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,166 @@ static int ast_rtp_write(struct ast_rtp_instance *instance, struct ast_frame *fr
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;
/* Round to 0.1ms for nice, pretty timestamps */
rtp->rxstart = floor( rtp->rxstart * 10000.0 ) / 10000.0;
*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 +5658,12 @@ static void calc_rxstamp(struct timeval *tv, struct ast_rtp *rtp, unsigned int t
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 +6029,23 @@ static int update_rtt_stats(struct ast_rtp *rtp, unsigned int lsr, unsigned int
*/
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);
}
/*!
@ -5893,6 +6072,158 @@ static void update_lost_stats(struct ast_rtp *rtp, unsigned int lost_packets)
&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 +6482,26 @@ static struct ast_frame *ast_rtcp_interpret(struct ast_rtp_instance *instance, s
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 +6512,9 @@ static struct ast_frame *ast_rtcp_interpret(struct ast_rtp_instance *instance, s
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 +6781,55 @@ static struct ast_frame *ast_rtcp_interpret(struct ast_rtp_instance *instance, s
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 +7713,8 @@ static struct ast_frame *ast_rtp_interpret(struct ast_rtp_instance *instance, st
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 +7865,7 @@ static struct ast_frame *ast_rtp_interpret(struct ast_rtp_instance *instance, st
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 +7874,7 @@ static struct ast_frame *ast_rtp_interpret(struct ast_rtp_instance *instance, st
/* 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 +8323,8 @@ static struct ast_frame *ast_rtp_read(struct ast_rtp_instance *instance, int rtc
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 +8788,8 @@ static void ast_rtp_prop_set(struct ast_rtp_instance *instance, enum ast_rtp_pro
#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 +8833,8 @@ static void ast_rtp_prop_set(struct ast_rtp_instance *instance, enum ast_rtp_pro
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 +9075,7 @@ static int ast_rtp_get_stat(struct ast_rtp_instance *instance, struct ast_rtp_in
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 +9093,19 @@ static int ast_rtp_get_stat(struct ast_rtp_instance *instance, struct ast_rtp_in
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 +9161,8 @@ static void ast_rtp_stop(struct ast_rtp_instance *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);

View File

@ -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 @@ static int test_init_rtp_instances(struct ast_rtp_instance **instance1,
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 @@ static int test_init_rtp_instances(struct ast_rtp_instance **instance1,
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 @@ static void test_write_and_read_frames(struct ast_rtp_instance *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);
@ -523,8 +652,47 @@ AST_TEST_DEFINE(fir_nominal)
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 @@ static int load_module(void)
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;
}