diff --git a/channels/chan_pjsip.c b/channels/chan_pjsip.c index 3e8abd2f32..22ed5ecf3f 100644 --- a/channels/chan_pjsip.c +++ b/channels/chan_pjsip.c @@ -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); diff --git a/channels/pjsip/dialplan_functions.c b/channels/pjsip/dialplan_functions.c index a433d0734c..0496f048e3 100644 --- a/channels/pjsip/dialplan_functions.c +++ b/channels/pjsip/dialplan_functions.c @@ -304,6 +304,12 @@ Round trip time + + Transmitted Media Experience Score + + + Received Media Experience Score + @@ -387,6 +393,37 @@ + + Retrieve a summary of all RTCP Media Experience Score information. + The following data items are returned in a semi-colon + delineated list: + + + Minimum MES based on us analysing received packets. + + + Maximum MES based on us analysing received packets. + + + Average MES based on us analysing received packets. + + + Standard deviation MES based on us analysing received packets. + + + Minimum MES based on data we get in Sender and Receiver Reports sent by the remote end + + + Maximum MES based on data we get in Sender and Receiver Reports sent by the remote end + + + Average MES based on data we get in Sender and Receiver Reports sent by the remote end + + + Standard deviation MES based on data we get in Sender and Receiver Reports sent by the remote end + + + Transmitted packet count Received packet count Transmitted packet jitter @@ -416,6 +453,24 @@ Standard deviation round trip time Our Synchronization Source identifier Their Synchronization Source identifier + + 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. + + + 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. + + Max MES based on data we get in Sender and Receiver Reports sent by the remote end + Min MES based on data we get in Sender and Receiver Reports sent by the remote end + Average MES based on data we get in Sender and Receiver Reports sent by the remote end + Standard deviation MES based on data we get in Sender and Receiver Reports sent by the remote end + Max MES based on us analyzing the received RTP stream + Min MES based on us analyzing the received RTP stream + Average MES based on us analyzing the received RTP stream + Standard deviation MES based on us analyzing the received RTP stream @@ -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, }, }; diff --git a/doc/CHANGES-staging/res_rtp_asterisk.txt b/doc/CHANGES-staging/res_rtp_asterisk.txt new file mode 100644 index 0000000000..9c8e05f0b6 --- /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 50e2a7b623..7834acd033 100644 --- a/include/asterisk/rtp_engine.h +++ b/include/asterisk/rtp_engine.h @@ -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) diff --git a/include/asterisk/time.h b/include/asterisk/time.h index 3e0c064fc7..23ba18ff4d 100644 --- a/include/asterisk/time.h +++ b/include/asterisk/time.h @@ -33,6 +33,8 @@ #include #endif +#include + #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. */ diff --git a/main/rtp_engine.c b/main/rtp_engine.c index eac3a02ed4..d36f70ce05 100644 --- a/main/rtp_engine.c +++ b/main/rtp_engine.c @@ -143,7 +143,6 @@ #include "asterisk.h" -#include /* for sqrt, MAX */ #include /* for sched_yield */ #include /* for timeval */ #include /* 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; } diff --git a/res/res_rtp_asterisk.c b/res/res_rtp_asterisk.c index 0700cbd6e3..da0a3b97ac 100644 --- a/res/res_rtp_asterisk.c +++ b/res/res_rtp_asterisk.c @@ -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); diff --git a/tests/test_res_rtp.c b/tests/test_res_rtp.c index 1d36116745..2ecf383363 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 +#include 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; }