diff options
-rw-r--r-- | pjmedia/include/pjmedia/rtcp.h | 86 | ||||
-rw-r--r-- | pjmedia/include/pjmedia/rtp.h | 75 | ||||
-rw-r--r-- | pjmedia/include/pjmedia/session.h | 2 | ||||
-rw-r--r-- | pjmedia/include/pjmedia/stream.h | 24 | ||||
-rw-r--r-- | pjmedia/src/pjmedia/rtcp.c | 315 | ||||
-rw-r--r-- | pjmedia/src/pjmedia/rtp.c | 135 | ||||
-rw-r--r-- | pjmedia/src/pjmedia/session.c | 2 | ||||
-rw-r--r-- | pjmedia/src/pjmedia/stream.c | 142 | ||||
-rw-r--r-- | pjsip-apps/src/samples/siprtp.c | 227 | ||||
-rw-r--r-- | pjsip/src/pjsua-lib/pjsua_settings.c | 140 |
10 files changed, 757 insertions, 391 deletions
diff --git a/pjmedia/include/pjmedia/rtcp.h b/pjmedia/include/pjmedia/rtcp.h index 78498a80..d327d4f6 100644 --- a/pjmedia/include/pjmedia/rtcp.h +++ b/pjmedia/include/pjmedia/rtcp.h @@ -146,6 +146,73 @@ typedef struct pjmedia_rtcp_ntp_rec pjmedia_rtcp_ntp_rec; /** + * Unidirectional RTP stream statistics. + */ +struct pjmedia_rtcp_stream_stat +{ + pj_time_val update; /**< Time of last update. */ + unsigned update_cnt; /**< Number of updates (to calculate avg) */ + pj_uint32_t pkt; /**< Total number of packets */ + pj_uint32_t bytes; /**< Total number of payload/bytes */ + unsigned discard; /**< Number of discarded packets. */ + unsigned loss; /**< Number of packets lost */ + unsigned reorder; /**< Number of out of order packets */ + unsigned dup; /**< Number of duplicates packets */ + + struct { + unsigned min; /**< Minimum loss period (in usec) */ + unsigned avg; /**< Average loss period (in usec) */ + unsigned max; /**< Maximum loss period (in usec) */ + unsigned last; /**< Last loss period (in usec) */ + } loss_period; /**< Lost period history. */ + + struct { + unsigned burst:1; /**< Burst/sequential packet lost detected */ + unsigned random:1; /**< Random packet lost detected. */ + } loss_type; /**< Types of loss detected. */ + + struct { + unsigned min; /**< Minimum jitter (in usec) */ + unsigned avg; /**< Average jitter (in usec) */ + unsigned max; /**< Maximum jitter (in usec) */ + unsigned last; /**< Last jitter (in usec) */ + } jitter; /**< Jitter history. */ +}; + + +/** + * @see pjmedia_rtcp_stream_stat + */ +typedef struct pjmedia_rtcp_stream_stat pjmedia_rtcp_stream_stat; + + + +/** + * Bidirectional RTP stream statistics. + */ +struct pjmedia_rtcp_stat +{ + pjmedia_rtcp_stream_stat tx; /**< Encoder stream statistics. */ + pjmedia_rtcp_stream_stat rx; /**< Decoder stream statistics. */ + + struct { + unsigned min; /**< Minimum round-trip delay (in usec) */ + unsigned avg; /**< Average round-trip delay (in usec) */ + unsigned max; /**< Maximum round-trip delay (in usec) */ + unsigned last; /**< Last round-trip delay (in usec) */ + } rtt; /**< Round trip delay history. */ + + unsigned rtt_update_cnt; /**< Nb of times rtt is updated. */ +}; + + +/** + * @see pjmedia_rtcp_stat + */ +typedef struct pjmedia_rtcp_stat pjmedia_rtcp_stat; + + +/** * RTCP session is used to monitor the RTP session of one endpoint. There * should only be one RTCP session for a bidirectional RTP streams. */ @@ -156,6 +223,7 @@ struct pjmedia_rtcp_session pjmedia_rtp_seq_session seq_ctrl; /**< RTCP sequence number control. */ unsigned clock_rate; /**< Clock rate of the stream */ + unsigned pkt_size; /**< Avg pkt size, in samples. */ pj_uint32_t received; /**< # pkt received */ pj_uint32_t exp_prior; /**< # pkt expected at last interval*/ pj_uint32_t rx_prior; /**< # pkt received at last interval*/ @@ -166,7 +234,8 @@ struct pjmedia_rtcp_session pj_uint32_t rx_lsr; /**< NTP ts in last SR received */ pj_timestamp rx_lsr_time;/**< Time when last SR is received */ pj_uint32_t peer_ssrc; /**< Peer SSRC */ - unsigned rtt_us; /**< End-to-end delay, in usec. */ + + pjmedia_rtcp_stat stat; /**< Bidirectional stream stat. */ }; /** @@ -178,11 +247,14 @@ typedef struct pjmedia_rtcp_session pjmedia_rtcp_session; /** * Initialize RTCP session. * - * @param session The session - * @param ssrc The SSRC used in to identify the session. + * @param session The session + * @param clock_rate Codec clock rate in samples per second. + * @param samples_per_frame Average number of samples per frame. + * @param ssrc The SSRC used in to identify the session. */ PJ_DECL(void) pjmedia_rtcp_init( pjmedia_rtcp_session *session, unsigned clock_rate, + unsigned samples_per_frame, pj_uint32_t ssrc ); @@ -201,10 +273,12 @@ PJ_DECL(void) pjmedia_rtcp_fini( pjmedia_rtcp_session *session); * @param session The session. * @param seq The RTP packet sequence number, in host byte order. * @param ts The RTP packet timestamp, in host byte order. + * @param payload Size of the payload. */ PJ_DECL(void) pjmedia_rtcp_rx_rtp( pjmedia_rtcp_session *session, - pj_uint16_t seq, - pj_uint32_t ts ); + unsigned seq, + unsigned ts, + unsigned payload); /** @@ -216,7 +290,7 @@ PJ_DECL(void) pjmedia_rtcp_rx_rtp( pjmedia_rtcp_session *session, * RTP header) in bytes. */ PJ_DECL(void) pjmedia_rtcp_tx_rtp( pjmedia_rtcp_session *session, - pj_uint16_t ptsize ); + unsigned ptsize ); /** diff --git a/pjmedia/include/pjmedia/rtp.h b/pjmedia/include/pjmedia/rtp.h index 34c576bd..87a73c81 100644 --- a/pjmedia/include/pjmedia/rtp.h +++ b/pjmedia/include/pjmedia/rtp.h @@ -180,6 +180,48 @@ typedef struct pjmedia_rtp_session pjmedia_rtp_session; /** + * This structure is used to receive additional information about the + * state of incoming RTP packet. + */ +struct pjmedia_rtp_status +{ + union { + struct flag { + int bad:1; /**< General flag to indicate that sequence is + bad, and application should not process + this packet. More information will be given + in other flags. */ + int badpt:1; /**< Bad payload type. */ + int dup:1; /**< Indicates duplicate packet */ + int outorder:1; /**< Indicates out of order packet */ + int probation:1;/**< Indicates that session is in probation + until more packets are received. */ + int restart:1; /**< Indicates that sequence number has made + a large jump, and internal base sequence + number has been adjusted. */ + } flag; /**< Status flags. */ + + pj_uint16_t value; /**< Status value, to conveniently address all + flags. */ + + } status; /**< Status information union. */ + + pj_uint16_t diff; /**< Sequence number difference from previous + packet. Normally the value should be 1. + Value greater than one may indicate packet + loss. If packet with lower sequence is + received, the value will be set to zero. + If base sequence has been restarted, the + value will be one. */ +}; + +/** + * @see pjmedia_rtp_status + */ +typedef struct pjmedia_rtp_status pjmedia_rtp_status; + + +/** * This function will initialize the RTP session according to given parameters. * * @param ses The session. @@ -241,12 +283,12 @@ PJ_DECL(pj_status_t) pjmedia_rtp_decode_rtp( pjmedia_rtp_session *ses, * * @param ses The session. * @param hdr The RTP header of the incoming packet. - * - * @return PJ_SUCCESS if the packet is valid and can be processed, - * otherwise will return the appropriate status code. + * @param seq_st Optional structure to receive the status of the RTP packet + * processing. */ -PJ_DECL(pj_status_t) pjmedia_rtp_session_update( pjmedia_rtp_session *ses, - const pjmedia_rtp_hdr *hdr); +PJ_DECL(void) pjmedia_rtp_session_update( pjmedia_rtp_session *ses, + const pjmedia_rtp_hdr *hdr, + pjmedia_rtp_status *seq_st); /* @@ -265,25 +307,16 @@ void pjmedia_rtp_seq_init(pjmedia_rtp_seq_session *seq_ctrl, /** - * Internal function to restart the sequence number control, shared by RTCP - * implementation. - * - * @param seq_ctrl The sequence control instance. - * @param seq Sequence number to restart. - */ -void pjmedia_rtp_seq_restart(pjmedia_rtp_seq_session *seq_ctrl, - pj_uint16_t seq); - -/** * Internal function update sequence control, shared by RTCP implementation. * - * @param seq_ctrl The sequence control instance. - * @param seq Sequence number to update. - * - * @return PJ_SUCCESS if the sequence number can be accepted. + * @param seq_ctrl The sequence control instance. + * @param seq Sequence number to update. + * @param seq_status Optional structure to receive additional information + * about the packet. */ -pj_status_t pjmedia_rtp_seq_update(pjmedia_rtp_seq_session *seq_ctrl, - pj_uint16_t seq); +void pjmedia_rtp_seq_update( pjmedia_rtp_seq_session *seq_ctrl, + pj_uint16_t seq, + pjmedia_rtp_status *seq_status); /** * @} diff --git a/pjmedia/include/pjmedia/session.h b/pjmedia/include/pjmedia/session.h index 898744d4..762f9b8d 100644 --- a/pjmedia/include/pjmedia/session.h +++ b/pjmedia/include/pjmedia/session.h @@ -217,7 +217,7 @@ PJ_DECL(pj_status_t) pjmedia_session_get_port( pjmedia_session *session, */ PJ_DECL(pj_status_t) pjmedia_session_get_stream_stat(pjmedia_session *session, unsigned index, - pjmedia_stream_stat *sta); + pjmedia_rtcp_stat *stat); /** * Dial DTMF digit to the stream, using RFC 2833 mechanism. diff --git a/pjmedia/include/pjmedia/stream.h b/pjmedia/include/pjmedia/stream.h index 91f50dc3..90d2a2f6 100644 --- a/pjmedia/include/pjmedia/stream.h +++ b/pjmedia/include/pjmedia/stream.h @@ -29,6 +29,7 @@ #include <pjmedia/codec.h> #include <pjmedia/endpoint.h> #include <pjmedia/port.h> +#include <pjmedia/rtcp.h> #include <pj/sock.h> PJ_BEGIN_DECL @@ -82,27 +83,6 @@ struct pjmedia_stream_info /** - * Individual channel statistic. - */ -struct pjmedia_channel_stat -{ - pj_uint32_t pkt; /**< Total number of packets. */ - pj_uint32_t bytes; /**< Total number of bytes, including RTP hdr. */ - pj_uint32_t lost; /**< Total number of packet lost */ -}; - -/** - * Stream statistic. - */ -struct pjmedia_stream_stat -{ - pjmedia_channel_stat enc; /**< Encoder statistics. */ - pjmedia_channel_stat dec; /**< Decoder statistics. */ -}; - - - -/** * Create a media stream based on the specified stream parameter. * All channels in the stream initially will be inactive. * @@ -164,7 +144,7 @@ PJ_DECL(pj_status_t) pjmedia_stream_start(pjmedia_stream *stream); * @return PJ_SUCCESS on success. */ PJ_DECL(pj_status_t) pjmedia_stream_get_stat( const pjmedia_stream *stream, - pjmedia_stream_stat *stat); + pjmedia_rtcp_stat *stat); /** * Pause the individual channel in the stream. diff --git a/pjmedia/src/pjmedia/rtcp.c b/pjmedia/src/pjmedia/rtcp.c index 6fc3a752..8755af52 100644 --- a/pjmedia/src/pjmedia/rtcp.c +++ b/pjmedia/src/pjmedia/rtcp.c @@ -43,7 +43,7 @@ /* * Get NTP time. */ -static void rtcp_get_ntp_time(const pjmedia_rtcp_session *s, +static void rtcp_get_ntp_time(const pjmedia_rtcp_session *sess, struct pjmedia_rtcp_ntp_rec *ntp) { pj_time_val tv; @@ -56,28 +56,30 @@ static void rtcp_get_ntp_time(const pjmedia_rtcp_session *s, ntp->hi = tv.sec; /* Calculate second fractions */ - ts.u64 %= s->ts_freq.u64; - ts.u64 = (ts.u64 << 32) / s->ts_freq.u64; + ts.u64 %= sess->ts_freq.u64; + ts.u64 = (ts.u64 << 32) / sess->ts_freq.u64; /* Fill up the low 32bit part */ ntp->lo = ts.u32.lo; } -PJ_DEF(void) pjmedia_rtcp_init(pjmedia_rtcp_session *s, +PJ_DEF(void) pjmedia_rtcp_init(pjmedia_rtcp_session *sess, unsigned clock_rate, + unsigned samples_per_frame, pj_uint32_t ssrc) { - pjmedia_rtcp_pkt *rtcp_pkt = &s->rtcp_pkt; + pjmedia_rtcp_pkt *rtcp_pkt = &sess->rtcp_pkt; pj_memset(rtcp_pkt, 0, sizeof(pjmedia_rtcp_pkt)); /* Set clock rate */ - s->clock_rate = clock_rate; + sess->clock_rate = clock_rate; + sess->pkt_size = samples_per_frame; /* Init time */ - s->rx_lsr = 0; - s->rx_lsr_time.u64 = 0; + sess->rx_lsr = 0; + sess->rx_lsr_time.u64 = 0; /* Init common RTCP header */ rtcp_pkt->common.version = 2; @@ -89,49 +91,62 @@ PJ_DEF(void) pjmedia_rtcp_init(pjmedia_rtcp_session *s, rtcp_pkt->sr.ssrc = pj_htonl(ssrc); /* Get timestamp frequency */ - pj_get_timestamp_freq(&s->ts_freq); + pj_get_timestamp_freq(&sess->ts_freq); /* RR will be initialized on receipt of the first RTP packet. */ } -PJ_DEF(void) pjmedia_rtcp_fini(pjmedia_rtcp_session *session) +PJ_DEF(void) pjmedia_rtcp_fini(pjmedia_rtcp_session *sess) { /* Nothing to do. */ - PJ_UNUSED_ARG(session); + PJ_UNUSED_ARG(sess); } -static void rtcp_init_seq(pjmedia_rtcp_session *s, pj_uint16_t seq) +static void rtcp_init_seq(pjmedia_rtcp_session *sess) { - s->received = 0; - s->exp_prior = 0; - s->rx_prior = 0; - s->transit = 0; - s->jitter = 0; - - pjmedia_rtp_seq_restart(&s->seq_ctrl, seq); + sess->received = 0; + sess->exp_prior = 0; + sess->rx_prior = 0; + sess->transit = 0; + sess->jitter = 0; } -PJ_DEF(void) pjmedia_rtcp_rx_rtp(pjmedia_rtcp_session *s, - pj_uint16_t seq, - pj_uint32_t rtp_ts) +PJ_DEF(void) pjmedia_rtcp_rx_rtp(pjmedia_rtcp_session *sess, + unsigned seq, + unsigned rtp_ts, + unsigned payload) { pj_timestamp ts; pj_uint32_t arrival; pj_int32_t transit; - int status; + pjmedia_rtp_status seq_st; + unsigned last_seq; + + sess->stat.rx.pkt++; + sess->stat.rx.bytes += payload; - /* Update sequence numbers (received, lost, etc). */ - status = pjmedia_rtp_seq_update(&s->seq_ctrl, seq); - if (status == PJMEDIA_RTP_ESESSRESTART) { - rtcp_init_seq(s, seq); - status = 0; + /* Update sequence numbers. */ + last_seq = sess->seq_ctrl.max_seq; + pjmedia_rtp_seq_update(&sess->seq_ctrl, (pj_uint16_t)seq, &seq_st); + if (seq_st.status.flag.restart) { + rtcp_init_seq(sess); } - if (status != 0) + if (seq_st.status.flag.dup) + sess->stat.rx.dup++; + if (seq_st.status.flag.outorder) + sess->stat.rx.reorder++; + + if (seq_st.status.flag.bad) { + sess->stat.rx.discard++; return; + } + + + /* Only mark "good" packets */ + ++sess->received; - ++s->received; /* * Calculate jitter (see RFC 3550 section A.8) @@ -139,41 +154,55 @@ PJ_DEF(void) pjmedia_rtcp_rx_rtp(pjmedia_rtcp_session *s, /* Get arrival time and convert timestamp to samples */ pj_get_timestamp(&ts); - ts.u64 = ts.u64 * s->clock_rate / s->ts_freq.u64; + ts.u64 = ts.u64 * sess->clock_rate / sess->ts_freq.u64; arrival = ts.u32.lo; transit = arrival - rtp_ts; - if (s->transit == 0) { - s->transit = transit; + /* Ignore the first N packets as they normally have bad jitter + * due to other threads working to establish the call + */ + if (sess->transit == 0 || sess->received < 25 ) { + sess->transit = transit; + sess->stat.rx.jitter.min = 2000; } else { pj_int32_t d; + pj_uint32_t jitter; - d = transit - s->transit; - s->transit = transit; + d = transit - sess->transit; + sess->transit = transit; if (d < 0) d = -d; - s->jitter += d - ((s->jitter + 8) >> 4); + sess->jitter += d - ((sess->jitter + 8) >> 4); + + /* Get jitter in usec */ + if (d < 4294) + jitter = d * 1000000 / sess->clock_rate; + else { + jitter = d * 1000 / sess->clock_rate; + jitter *= 1000; + } + + /* Update jitter stat */ + if (jitter < sess->stat.rx.jitter.min) + sess->stat.rx.jitter.min = jitter; + if (jitter > sess->stat.rx.jitter.max) + sess->stat.rx.jitter.max = jitter; + sess->stat.rx.jitter.last = jitter; } } -PJ_DEF(void) pjmedia_rtcp_tx_rtp(pjmedia_rtcp_session *s, - pj_uint16_t bytes_payload_size) +PJ_DEF(void) pjmedia_rtcp_tx_rtp(pjmedia_rtcp_session *sess, + unsigned bytes_payload_size) { - pjmedia_rtcp_pkt *rtcp_pkt = &s->rtcp_pkt; - - /* Update number of packets */ - rtcp_pkt->sr.sender_pcount = - pj_htonl( pj_ntohl(rtcp_pkt->sr.sender_pcount) + 1); - - /* Update number of bytes */ - rtcp_pkt->sr.sender_bcount = - pj_htonl( pj_ntohl(rtcp_pkt->sr.sender_bcount) + bytes_payload_size ); + /* Update statistics */ + sess->stat.tx.pkt++; + sess->stat.tx.bytes += bytes_payload_size; } -PJ_DEF(void) pjmedia_rtcp_rx_rtcp( pjmedia_rtcp_session *session, +PJ_DEF(void) pjmedia_rtcp_rx_rtcp( pjmedia_rtcp_session *sess, const void *pkt, pj_size_t size) { @@ -183,19 +212,77 @@ PJ_DEF(void) pjmedia_rtcp_rx_rtcp( pjmedia_rtcp_session *session, pj_assert(size >= sizeof(pjmedia_rtcp_common)+sizeof(pjmedia_rtcp_sr)); /* Save LSR from NTP timestamp of RTCP packet */ - session->rx_lsr = ((pj_ntohl(rtcp->sr.ntp_sec) & 0x0000FFFF) << 16) | - ((pj_ntohl(rtcp->sr.ntp_frac) >> 16) & 0xFFFF); + sess->rx_lsr = ((pj_ntohl(rtcp->sr.ntp_sec) & 0x0000FFFF) << 16) | + ((pj_ntohl(rtcp->sr.ntp_frac) >> 16) & 0xFFFF); /* Calculate SR arrival time for DLSR */ - pj_get_timestamp(&session->rx_lsr_time); + pj_get_timestamp(&sess->rx_lsr_time); TRACE_((THIS_FILE, "Rx RTCP SR: ntp-ts=%p, time=%p", - session->rx_lsr, - (pj_uint32_t)(session->rx_lsr_time.u64*65536/session->ts_freq.u64))); + sess->rx_lsr, + (pj_uint32_t)(sess->rx_lsr_time.u64*65536/sess->ts_freq.u64))); /* Calculate RTT if it has RR */ if (size >= sizeof(pjmedia_rtcp_pkt)) { + pj_uint32_t last_loss, jitter_samp, jitter; + + last_loss = sess->stat.tx.loss; + + /* Get packet loss */ + sess->stat.tx.loss = (rtcp->rr.total_lost_2 << 16) + + (rtcp->rr.total_lost_1 << 8) + + rtcp->rr.total_lost_0; + + /* We can't calculate the exact loss period for TX, so just give the + * best estimation. + */ + if (sess->stat.tx.loss > last_loss) { + unsigned period; + + /* Loss period in msec */ + period = (sess->stat.tx.loss - last_loss) * sess->pkt_size * + 1000 / sess->clock_rate; + + /* Loss period in usec */ + period *= 1000; + + if (sess->stat.tx.update_cnt==0||sess->stat.tx.loss_period.min==0) + sess->stat.tx.loss_period.min = period; + if (period < sess->stat.tx.loss_period.min) + sess->stat.tx.loss_period.min = period; + if (period > sess->stat.tx.loss_period.max) + sess->stat.tx.loss_period.max = period; + + sess->stat.tx.loss_period.avg = + (sess->stat.tx.loss_period.avg*sess->stat.tx.update_cnt+period) + / (sess->stat.tx.update_cnt + 1); + sess->stat.tx.loss_period.last = period; + } + + /* Get jitter value in usec */ + jitter_samp = pj_ntohl(rtcp->rr.jitter); + /* Calculate jitter in usec, avoiding overflows */ + if (jitter_samp <= 4294) + jitter = jitter_samp * 1000000 / sess->clock_rate; + else { + jitter = jitter_samp * 1000 / sess->clock_rate; + jitter *= 1000; + } + + /* Update jitter statistics */ + if (sess->stat.tx.update_cnt == 0) + sess->stat.tx.jitter.min = jitter; + if (jitter < sess->stat.tx.jitter.min && jitter) + sess->stat.tx.jitter.min = jitter; + if (jitter > sess->stat.tx.jitter.max) + sess->stat.tx.jitter.max = jitter; + sess->stat.tx.jitter.avg = + (sess->stat.tx.jitter.avg * sess->stat.tx.update_cnt + jitter) / + (sess->stat.tx.update_cnt + 1); + sess->stat.tx.jitter.last = jitter; + + /* Can only calculate if LSR and DLSR is present in RR */ if (rtcp->rr.lsr && rtcp->rr.dlsr) { pj_uint32_t lsr, now, dlsr; @@ -211,7 +298,7 @@ PJ_DEF(void) pjmedia_rtcp_rx_rtcp( pjmedia_rtcp_session *session, dlsr = pj_ntohl(rtcp->rr.dlsr); /* Get current time, and convert to 1/65536 resolution */ - rtcp_get_ntp_time(session, &ntp); + rtcp_get_ntp_time(sess, &ntp); now = ((ntp.hi & 0xFFFF) << 16) + (ntp.lo >> 16); @@ -220,7 +307,7 @@ PJ_DEF(void) pjmedia_rtcp_rx_rtcp( pjmedia_rtcp_session *session, /* Convert end to end delay to usec (keeping the calculation in * 64bit space):: - * session->ee_delay = (eedelay * 1000) / 65536; + * sess->ee_delay = (eedelay * 1000) / 65536; */ eedelay = (eedelay * 1000000) >> 16; @@ -233,48 +320,90 @@ PJ_DEF(void) pjmedia_rtcp_rx_rtcp( pjmedia_rtcp_session *session, * otherwise rtt will be invalid */ if (now-dlsr >= lsr) { - session->rtt_us = (pj_uint32_t)eedelay; + unsigned rtt = (pj_uint32_t)eedelay; + + if (sess->stat.rtt_update_cnt == 0) + sess->stat.rtt.min = rtt; + + if (rtt < sess->stat.rtt.min && rtt) + sess->stat.rtt.min = rtt; + if (rtt > sess->stat.rtt.max) + sess->stat.rtt.max = rtt; + + sess->stat.rtt.avg = + (sess->stat.rtt.avg * sess->stat.rtt_update_cnt + rtt) / + (sess->stat.rtt_update_cnt + 1); + + sess->stat.rtt.last = rtt; + sess->stat.rtt_update_cnt++; + } else { - PJ_LOG(3, (THIS_FILE, "Internal NTP clock skew detected")); + PJ_LOG(3, (THIS_FILE, "Internal NTP clock skew detected: " + "lsr=%p, now=%p, dlsr=%p (%d:%03dms)", + lsr, now, dlsr, dlsr/65536, + (dlsr%65536)*1000/65536)); } } + + pj_gettimeofday(&sess->stat.tx.update); + sess->stat.tx.update_cnt++; } } -static void rtcp_build_rtcp(pjmedia_rtcp_session *s, - pj_uint32_t receiver_ssrc) -{ - pj_uint32_t expected; - pj_uint32_t u32; - pj_uint32_t expected_interval, received_interval, lost_interval; - pjmedia_rtcp_pkt *rtcp_pkt = &s->rtcp_pkt; +PJ_DEF(void) pjmedia_rtcp_build_rtcp(pjmedia_rtcp_session *sess, + pjmedia_rtcp_pkt **ret_p_pkt, + int *len) +{ + pj_uint32_t expected, expected_interval, received_interval, lost_interval; + pj_uint32_t jitter_samp, jitter; + pjmedia_rtcp_pkt *rtcp_pkt = &sess->rtcp_pkt; + pjmedia_rtcp_ntp_rec ntp; + + /* Packet count */ + rtcp_pkt->sr.sender_pcount = pj_htonl(sess->stat.tx.pkt); + + /* Octets count */ + rtcp_pkt->sr.sender_bcount = pj_htonl(sess->stat.tx.bytes); /* SSRC and last_seq */ - rtcp_pkt->rr.ssrc = pj_htonl(receiver_ssrc); - rtcp_pkt->rr.last_seq = (s->seq_ctrl.cycles & 0xFFFF0000L); - rtcp_pkt->rr.last_seq += s->seq_ctrl.max_seq; + rtcp_pkt->rr.ssrc = pj_htonl(sess->peer_ssrc); + rtcp_pkt->rr.last_seq = (sess->seq_ctrl.cycles & 0xFFFF0000L); + rtcp_pkt->rr.last_seq += sess->seq_ctrl.max_seq; rtcp_pkt->rr.last_seq = pj_htonl(rtcp_pkt->rr.last_seq); + /* Jitter */ - rtcp_pkt->rr.jitter = pj_htonl(s->jitter >> 4); + jitter_samp = (sess->jitter >> 4); + rtcp_pkt->rr.jitter = pj_htonl(jitter_samp); + + /* Calculate jitter in usec, avoiding overflows */ + if (jitter_samp <= 4294) + jitter = jitter_samp * 1000000 / sess->clock_rate; + else { + jitter = jitter_samp * 1000 / sess->clock_rate; + jitter *= 1000; + } + + /* Update jitter statistics */ + sess->stat.rx.jitter.avg = + (sess->stat.rx.jitter.avg * sess->stat.rx.update_cnt + jitter) / + (sess->stat.rx.update_cnt + 1); /* Total lost. */ - expected = pj_ntohl(rtcp_pkt->rr.last_seq) - s->seq_ctrl.base_seq; - if (expected >= s->received) - u32 = expected - s->received; - else - u32 = 0; - rtcp_pkt->rr.total_lost_2 = (u32 >> 16) & 0x00FF; - rtcp_pkt->rr.total_lost_1 = (u32 >> 8) & 0x00FF; - rtcp_pkt->rr.total_lost_0 = u32 & 0x00FF; + expected = pj_ntohl(rtcp_pkt->rr.last_seq) - sess->seq_ctrl.base_seq; + if (expected >= sess->received) + sess->stat.rx.loss = expected - sess->received; + rtcp_pkt->rr.total_lost_2 = (sess->stat.rx.loss >> 16) & 0xFF; + rtcp_pkt->rr.total_lost_1 = (sess->stat.rx.loss >> 8) & 0xFF; + rtcp_pkt->rr.total_lost_0 = (sess->stat.rx.loss & 0xFF); /* Fraction lost calculation */ - expected_interval = expected - s->exp_prior; - s->exp_prior = expected; + expected_interval = expected - sess->exp_prior; + sess->exp_prior = expected; - received_interval = s->received - s->rx_prior; - s->rx_prior = s->received; + received_interval = sess->received - sess->rx_prior; + sess->rx_prior = sess->received; lost_interval = expected_interval - received_interval; @@ -283,35 +412,25 @@ static void rtcp_build_rtcp(pjmedia_rtcp_session *s, } else { rtcp_pkt->rr.fract_lost = (lost_interval << 8) / expected_interval; } -} - -PJ_DEF(void) pjmedia_rtcp_build_rtcp(pjmedia_rtcp_session *session, - pjmedia_rtcp_pkt **ret_p_pkt, - int *len) -{ - pjmedia_rtcp_pkt *rtcp_pkt = &session->rtcp_pkt; - pjmedia_rtcp_ntp_rec ntp; - - rtcp_build_rtcp(session, session->peer_ssrc); /* Get current NTP time. */ - rtcp_get_ntp_time(session, &ntp); + rtcp_get_ntp_time(sess, &ntp); /* Fill in NTP timestamp in SR. */ rtcp_pkt->sr.ntp_sec = pj_htonl(ntp.hi); rtcp_pkt->sr.ntp_frac = pj_htonl(ntp.lo); - if (session->rx_lsr_time.u64 == 0 || session->rx_lsr == 0) { + if (sess->rx_lsr_time.u64 == 0 || sess->rx_lsr == 0) { rtcp_pkt->rr.lsr = 0; rtcp_pkt->rr.dlsr = 0; } else { pj_timestamp ts; - pj_uint32_t lsr = session->rx_lsr; - pj_uint64_t lsr_time = session->rx_lsr_time.u64; + pj_uint32_t lsr = sess->rx_lsr; + pj_uint64_t lsr_time = sess->rx_lsr_time.u64; pj_uint32_t dlsr; /* Convert LSR time to 1/65536 seconds resolution */ - lsr_time = (lsr_time << 16) / session->ts_freq.u64; + lsr_time = (lsr_time << 16) / sess->ts_freq.u64; /* Fill in LSR. LSR is the middle 32bit of the last SR NTP time received. @@ -324,7 +443,7 @@ PJ_DEF(void) pjmedia_rtcp_build_rtcp(pjmedia_rtcp_session *session, pj_get_timestamp(&ts); /* Convert interval to 1/65536 seconds value */ - ts.u64 = (ts.u64 << 16) / session->ts_freq.u64; + ts.u64 = (ts.u64 << 16) / sess->ts_freq.u64; /* Get DLSR */ dlsr = (pj_uint32_t)(ts.u64 - lsr_time); @@ -340,6 +459,10 @@ PJ_DEF(void) pjmedia_rtcp_build_rtcp(pjmedia_rtcp_session *session, (dlsr%65536)*1000/65536 )); } + /* Update counter */ + pj_gettimeofday(&sess->stat.rx.update); + sess->stat.rx.update_cnt++; + /* Return pointer. */ *ret_p_pkt = rtcp_pkt; diff --git a/pjmedia/src/pjmedia/rtp.c b/pjmedia/src/pjmedia/rtp.c index 17b4e712..5103c5ac 100644 --- a/pjmedia/src/pjmedia/rtp.c +++ b/pjmedia/src/pjmedia/rtp.c @@ -34,6 +34,9 @@ #define MAX_MISORDER ((pj_int16_t)100) #define MIN_SEQUENTIAL ((pj_int16_t)2) +static void pjmedia_rtp_seq_restart(pjmedia_rtp_seq_session *seq_ctrl, + pj_uint16_t seq); + PJ_DEF(pj_status_t) pjmedia_rtp_session_init( pjmedia_rtp_session *ses, int default_pt, pj_uint32_t sender_ssrc ) @@ -161,9 +164,11 @@ PJ_DEF(pj_status_t) pjmedia_rtp_decode_rtp( pjmedia_rtp_session *ses, } -PJ_DEF(pj_status_t) pjmedia_rtp_session_update( pjmedia_rtp_session *ses, const pjmedia_rtp_hdr *hdr) +PJ_DEF(void) pjmedia_rtp_session_update( pjmedia_rtp_session *ses, + const pjmedia_rtp_hdr *hdr, + pjmedia_rtp_status *p_seq_st) { - int status; + pjmedia_rtp_status seq_st; /* Check SSRC. */ if (ses->peer_ssrc == 0) ses->peer_ssrc = pj_ntohl(hdr->ssrc); @@ -175,11 +180,21 @@ PJ_DEF(pj_status_t) pjmedia_rtp_session_update( pjmedia_rtp_session *ses, const } */ + /* Init status */ + seq_st.status.value = 0; + seq_st.diff = 0; + /* Check payload type. */ if (hdr->pt != ses->out_pt) { - PJ_LOG(4, (THIS_FILE, "pjmedia_rtp_session_update: ses=%p, invalid payload type %d (!=%d)", + PJ_LOG(4, (THIS_FILE, + "pjmedia_rtp_session_update: ses=%p, invalid payload " + "type %d (expecting %d)", ses, hdr->pt, ses->out_pt)); - return PJMEDIA_RTP_EINPT; + if (p_seq_st) { + p_seq_st->status.flag.bad = 1; + p_seq_st->status.flag.badpt = 1; + } + return; } /* Initialize sequence number on first packet received. */ @@ -187,87 +202,125 @@ PJ_DEF(pj_status_t) pjmedia_rtp_session_update( pjmedia_rtp_session *ses, const pjmedia_rtp_seq_init( &ses->seq_ctrl, pj_ntohs(hdr->seq) ); /* Check sequence number to see if remote session has been restarted. */ - status = pjmedia_rtp_seq_update( &ses->seq_ctrl, pj_ntohs(hdr->seq)); - if (status == PJMEDIA_RTP_ESESSRESTART) { - pjmedia_rtp_seq_restart( &ses->seq_ctrl, pj_ntohs(hdr->seq)); + pjmedia_rtp_seq_update( &ses->seq_ctrl, pj_ntohs(hdr->seq), &seq_st); + if (seq_st.status.flag.restart) { ++ses->received; - } else if (status == 0 || status == PJMEDIA_RTP_ESESSPROBATION) { + + } else if (!seq_st.status.flag.bad) { ++ses->received; } - - return status; + if (p_seq_st) { + p_seq_st->status.value = seq_st.status.value; + p_seq_st->diff = seq_st.diff; + } } -void pjmedia_rtp_seq_restart(pjmedia_rtp_seq_session *sctrl, pj_uint16_t seq) +void pjmedia_rtp_seq_restart(pjmedia_rtp_seq_session *sess, pj_uint16_t seq) { - sctrl->base_seq = seq; - sctrl->max_seq = seq; - sctrl->bad_seq = RTP_SEQ_MOD + 1; - sctrl->cycles = 0; + sess->base_seq = seq; + sess->max_seq = seq; + sess->bad_seq = RTP_SEQ_MOD + 1; + sess->cycles = 0; } -void pjmedia_rtp_seq_init(pjmedia_rtp_seq_session *sctrl, pj_uint16_t seq) +void pjmedia_rtp_seq_init(pjmedia_rtp_seq_session *sess, pj_uint16_t seq) { - pjmedia_rtp_seq_restart(sctrl, seq); + pjmedia_rtp_seq_restart(sess, seq); - sctrl->max_seq = (pj_uint16_t) (seq - 1); - sctrl->probation = MIN_SEQUENTIAL; + sess->max_seq = (pj_uint16_t) (seq - 1); + sess->probation = MIN_SEQUENTIAL; } -pj_status_t pjmedia_rtp_seq_update(pjmedia_rtp_seq_session *sctrl, - pj_uint16_t seq) +void pjmedia_rtp_seq_update( pjmedia_rtp_seq_session *sess, + pj_uint16_t seq, + pjmedia_rtp_status *seq_status) { - pj_uint16_t udelta = (pj_uint16_t) (seq - sctrl->max_seq); + pj_uint16_t udelta = (pj_uint16_t) (seq - sess->max_seq); + pjmedia_rtp_status st; + /* Init status */ + st.status.value = 0; + st.diff = 0; + /* * Source is not valid until MIN_SEQUENTIAL packets with * sequential sequence numbers have been received. */ - if (sctrl->probation) { - /* packet is in sequence */ - if (seq == sctrl->max_seq+ 1) { - sctrl->probation--; - sctrl->max_seq = seq; - if (sctrl->probation == 0) { - return PJMEDIA_RTP_ESESSRESTART; + if (sess->probation) { + + st.status.flag.probation = 1; + + if (seq == sess->max_seq+ 1) { + /* packet is in sequence */ + st.diff = 1; + sess->probation--; + sess->max_seq = seq; + if (sess->probation == 0) { + st.status.flag.probation = 0; } } else { - sctrl->probation = MIN_SEQUENTIAL - 1; - sctrl->max_seq = seq; + + st.diff = 0; + + st.status.flag.bad = 1; + if (seq == sess->max_seq) + st.status.flag.dup = 1; + else + st.status.flag.outorder = 1; + + sess->probation = MIN_SEQUENTIAL - 1; + sess->max_seq = seq; } - return PJMEDIA_RTP_ESESSPROBATION; + + + } else if (udelta == 0) { + + st.status.flag.dup = 1; } else if (udelta < MAX_DROPOUT) { /* in order, with permissible gap */ - if (seq < sctrl->max_seq) { + if (seq < sess->max_seq) { /* Sequence number wrapped - count another 64K cycle. */ - sctrl->cycles += RTP_SEQ_MOD; + sess->cycles += RTP_SEQ_MOD; } - sctrl->max_seq = seq; + sess->max_seq = seq; + + st.diff = udelta; } else if (udelta <= (RTP_SEQ_MOD - MAX_MISORDER)) { /* the sequence number made a very large jump */ - if (seq == sctrl->bad_seq) { + if (seq == sess->bad_seq) { /* * Two sequential packets -- assume that the other side * restarted without telling us so just re-sync * (i.e., pretend this was the first packet). */ - return PJMEDIA_RTP_ESESSRESTART; + pjmedia_rtp_seq_restart(sess, seq); + st.status.flag.restart = 1; + st.status.flag.probation = 1; + st.diff = 1; } else { - sctrl->bad_seq = (seq + 1) & (RTP_SEQ_MOD-1); - return PJMEDIA_RTP_EBADSEQ; + sess->bad_seq = (seq + 1) & (RTP_SEQ_MOD-1); + st.status.flag.bad = 1; + st.status.flag.outorder = 1; } } else { - /* duplicate or reordered packet */ + /* old duplicate or reordered packet. + * Not necessarily bad packet (?) + */ + st.status.flag.outorder = 1; } - return PJ_SUCCESS; + + if (seq_status) { + seq_status->diff = st.diff; + seq_status->status.value = st.status.value; + } } diff --git a/pjmedia/src/pjmedia/session.c b/pjmedia/src/pjmedia/session.c index 5f00df53..2b50485e 100644 --- a/pjmedia/src/pjmedia/session.c +++ b/pjmedia/src/pjmedia/session.c @@ -525,7 +525,7 @@ PJ_DEF(pj_status_t) pjmedia_session_get_port( pjmedia_session *session, */ PJ_DEF(pj_status_t) pjmedia_session_get_stream_stat( pjmedia_session *session, unsigned index, - pjmedia_stream_stat *stat) + pjmedia_rtcp_stat *stat) { PJ_ASSERT_RETURN(session && stat && index < session->stream_cnt, PJ_EINVAL); diff --git a/pjmedia/src/pjmedia/stream.c b/pjmedia/src/pjmedia/stream.c index 6f0d4881..378d3670 100644 --- a/pjmedia/src/pjmedia/stream.c +++ b/pjmedia/src/pjmedia/stream.c @@ -37,12 +37,13 @@ #define THIS_FILE "stream.c" #define ERRLEVEL 1 #define TRACE_(expr) stream_perror expr - +#define TRC_(expr) PJ_LOG(4,expr) #define PJMEDIA_MAX_FRAME_DURATION_MS 200 #define PJMEDIA_MAX_BUFFER_SIZE_MS 2000 #define PJMEDIA_MAX_MTU 1500 #define PJMEDIA_DTMF_DURATION 1600 /* in timestamp */ #define PJMEDIA_RTP_NAT_PROBATION_CNT 10 +#define PJMEDIA_RTCP_INTERVAL 5 /* seconds */ /** @@ -87,7 +88,6 @@ struct pjmedia_stream pjmedia_channel *dec; /**< Decoding channel. */ pjmedia_dir dir; /**< Stream direction. */ - pjmedia_stream_stat stat; /**< Stream statistics. */ void *user_data; /**< User data. */ pjmedia_codec *codec; /**< Codec instance being used. */ @@ -110,7 +110,10 @@ struct pjmedia_stream pj_ioqueue_key_t *rtcp_key; /**< RTCP ioqueue key. */ pj_ioqueue_op_key_t rtcp_op_key; /**< The pending read op key. */ - + pj_size_t rtcp_pkt_size; /**< Size of RTCP packet buf. */ + char rtcp_pkt[512]; /**< RTCP packet buffer. */ + pj_uint32_t rtcp_tx_time; /**< RTCP tx time in timestamp */ + int rtcp_addrlen; /**< Address length. */ /* RFC 2833 DTMF transmission queue: */ int tx_event_pt; /**< Outgoing pt for dtmf. */ @@ -269,6 +272,7 @@ static pj_status_t put_frame( pjmedia_port *port, pj_status_t status = 0; struct pjmedia_frame frame_out; int ts_len; + pj_bool_t has_tx; void *rtphdr; int rtphdrlen; pj_ssize_t sent; @@ -279,11 +283,15 @@ static pj_status_t put_frame( pjmedia_port *port, /* Init frame_out buffer. */ frame_out.buf = ((char*)channel->out_pkt) + sizeof(pjmedia_rtp_hdr); + /* Make compiler happy */ + frame_out.size = 0; + /* If we have DTMF digits in the queue, transmit the digits. * Otherwise encode the PCM buffer. */ if (stream->tx_dtmf_count) { + has_tx = PJ_TRUE; create_dtmf_payload(stream, &frame_out); /* Encapsulate. */ @@ -296,6 +304,7 @@ static pj_status_t put_frame( pjmedia_port *port, } else if (frame->type != PJMEDIA_FRAME_TYPE_NONE) { unsigned max_size; + has_tx = PJ_TRUE; max_size = channel->out_pkt_size - sizeof(pjmedia_rtp_hdr); status = stream->codec->op->encode( stream->codec, frame, max_size, @@ -316,39 +325,67 @@ static pj_status_t put_frame( pjmedia_port *port, } else { /* Just update RTP session's timestamp. */ + has_tx = PJ_FALSE; status = pjmedia_rtp_encode_rtp( &channel->rtp, 0, 0, 0, ts_len, (const void**)&rtphdr, &rtphdrlen); - return PJ_SUCCESS; } - if (status != 0) { + if (status != PJ_SUCCESS) { TRACE_((THIS_FILE, "RTP encode_rtp() error", status)); return status; } + /* Check if this is the time to transmit RTCP packet */ + if (stream->rtcp_tx_time == 0) { + stream->rtcp_tx_time = pj_ntohl(channel->rtp.out_hdr.ts) + + PJMEDIA_RTCP_INTERVAL * + stream->port.info.sample_rate; + } else if (pj_ntohl(channel->rtp.out_hdr.ts) >= stream->rtcp_tx_time) { + + pjmedia_rtcp_pkt *rtcp_pkt; + pj_ssize_t size; + int len; + + pjmedia_rtcp_build_rtcp(&stream->rtcp, &rtcp_pkt, &len); + size = len; + status = pj_sock_sendto(stream->skinfo.rtcp_sock, rtcp_pkt, &size, 0, + &stream->rem_rtcp_addr, + sizeof(stream->rem_rtcp_addr)); + if (status != PJ_SUCCESS) { + ; + } + + stream->rtcp_tx_time = pj_ntohl(channel->rtp.out_hdr.ts) + + PJMEDIA_RTCP_INTERVAL * + stream->port.info.sample_rate; + } + + /* Do nothing if we have nothing to transmit */ + if (!has_tx) + return PJ_SUCCESS; + if (rtphdrlen != sizeof(pjmedia_rtp_hdr)) { /* We don't support RTP with extended header yet. */ PJ_TODO(SUPPORT_SENDING_RTP_WITH_EXTENDED_HEADER); - //TRACE_((THIS_FILE, "Unsupported extended RTP header for transmission")); - return 0; + return PJ_SUCCESS; } pj_memcpy(channel->out_pkt, rtphdr, sizeof(pjmedia_rtp_hdr)); /* Send. */ sent = frame_out.size+sizeof(pjmedia_rtp_hdr); - status = pj_sock_sendto(stream->skinfo.rtp_sock, channel->out_pkt, &sent, 0, - &stream->rem_rtp_addr, sizeof(stream->rem_rtp_addr)); + status = pj_sock_sendto(stream->skinfo.rtp_sock, channel->out_pkt, + &sent, 0, &stream->rem_rtp_addr, + sizeof(stream->rem_rtp_addr)); if (status != PJ_SUCCESS) return status; /* Update stat */ - stream->stat.enc.pkt++; - stream->stat.enc.bytes += frame_out.size+sizeof(pjmedia_rtp_hdr); + pjmedia_rtcp_tx_rtp(&stream->rtcp, frame_out.size); return PJ_SUCCESS; } @@ -459,6 +496,7 @@ static void on_rx_rtp( pj_ioqueue_key_t *key, const pjmedia_rtp_hdr *hdr; const void *payload; unsigned payloadlen; + pjmedia_rtp_status seq_st; /* Go straight to read next packet if bytes_read == 0. */ @@ -476,6 +514,10 @@ static void on_rx_rtp( pj_ioqueue_key_t *key, } + /* Inform RTCP session */ + pjmedia_rtcp_rx_rtp(&stream->rtcp, pj_ntohs(hdr->seq), + pj_ntohl(hdr->ts), payloadlen); + /* Handle incoming DTMF. */ if (hdr->pt == stream->rx_event_pt) { handle_incoming_dtmf(stream, payload, payloadlen); @@ -486,29 +528,20 @@ static void on_rx_rtp( pj_ioqueue_key_t *key, /* Update RTP session (also checks if RTP session can accept * the incoming packet. */ - status = pjmedia_rtp_session_update(&channel->rtp, hdr); - if (status != 0 && - status != PJMEDIA_RTP_ESESSPROBATION && - status != PJMEDIA_RTP_ESESSRESTART) - { - TRACE_((THIS_FILE, "RTP session_update error (details follows)", - status)); - PJ_LOG(4,(THIS_FILE,"RTP packet detail: pt=%d, seq=%d", - hdr->pt, pj_ntohs(hdr->seq))); + pjmedia_rtp_session_update(&channel->rtp, hdr, &seq_st); + if (seq_st.status.flag.bad) { + TRC_ ((THIS_FILE, + "RTP session_update error: badpt=%d, dup=%d, outorder=%d, " + "probation=%d, restart=%d", + seq_st.status.flag.badpt, + seq_st.status.flag.dup, + seq_st.status.flag.outorder, + seq_st.status.flag.probation, + seq_st.status.flag.restart)); goto read_next_packet; } - /* Update the RTCP session. */ - pjmedia_rtcp_rx_rtp(&stream->rtcp, pj_ntohs(hdr->seq), - pj_ntohl(hdr->ts)); - - - /* Update stat */ - stream->stat.dec.pkt++; - stream->stat.dec.bytes += bytes_read; - - /* See if source address of RTP packet is different than the * configured address. */ @@ -571,9 +604,36 @@ static void on_rx_rtcp( pj_ioqueue_key_t *key, pj_ioqueue_op_key_t *op_key, pj_ssize_t bytes_read) { - PJ_UNUSED_ARG(key); + pjmedia_stream *stream = pj_ioqueue_get_user_data(key); + pj_status_t status; + PJ_UNUSED_ARG(op_key); - PJ_UNUSED_ARG(bytes_read); + + do { + if (bytes_read > 0) { + pjmedia_rtcp_rx_rtcp(&stream->rtcp, stream->rtcp_pkt, + bytes_read); + } + + bytes_read = stream->rtcp_pkt_size; + stream->rtcp_addrlen = sizeof(stream->rem_rtcp_addr); + status = pj_ioqueue_recvfrom( stream->rtcp_key, + &stream->rtcp_op_key, + stream->rtcp_pkt, + &bytes_read, 0, + &stream->rem_rtcp_addr, + &stream->rtcp_addrlen); + + } while (status == PJ_SUCCESS); + + if (status != PJ_SUCCESS && status != PJ_EPENDING) { + char errmsg[PJ_ERR_MSG_SIZE]; + + pj_strerror(status, errmsg, sizeof(errmsg)); + PJ_LOG(4,(THIS_FILE, "Error reading RTCP packet: %s [status=%d]", + errmsg, status)); + } + } @@ -658,6 +718,7 @@ PJ_DEF(pj_status_t) pjmedia_stream_create( pjmedia_endpt *endpt, pjmedia_stream *stream; pjmedia_codec_param codec_param; pj_ioqueue_callback ioqueue_cb; + pj_uint16_t rtcp_port; pj_status_t status; PJ_ASSERT_RETURN(pool && info && p_stream, PJ_EINVAL); @@ -690,13 +751,13 @@ PJ_DEF(pj_status_t) pjmedia_stream_create( pjmedia_endpt *endpt, stream->user_data = user_data; stream->skinfo = info->sock_info; stream->rem_rtp_addr = info->rem_addr; + rtcp_port = (pj_uint16_t) (pj_ntohs(info->rem_addr.sin_port)+1); + stream->rem_rtcp_addr = stream->rem_rtp_addr; + stream->rem_rtcp_addr.sin_port = pj_htons(rtcp_port); stream->tx_event_pt = info->tx_event_pt; stream->rx_event_pt = info->rx_event_pt; stream->last_dtmf = -1; - - PJ_TODO(INITIALIZE_RTCP_REMOTE_ADDRESS); - /* Create mutex to protect jitter buffer: */ status = pj_mutex_create_simple(pool, NULL, &stream->jb_mutex); @@ -738,7 +799,9 @@ PJ_DEF(pj_status_t) pjmedia_stream_create( pjmedia_endpt *endpt, /* Init RTCP session: */ - pjmedia_rtcp_init(&stream->rtcp, info->fmt.sample_rate, info->ssrc); + pjmedia_rtcp_init(&stream->rtcp, info->fmt.sample_rate, + stream->port.info.samples_per_frame, + info->ssrc); /* Create jitter buffer: */ @@ -799,6 +862,8 @@ PJ_DEF(pj_status_t) pjmedia_stream_create( pjmedia_endpt *endpt, /* Init pending operation key. */ pj_ioqueue_op_key_init(&stream->rtcp_op_key, sizeof(stream->rtcp_op_key)); + stream->rtcp_pkt_size = sizeof(stream->rtcp_pkt); + /* Bootstrap the first recvfrom() operation. */ on_rx_rtcp( stream->rtcp_key, &stream->rtcp_op_key, 0); @@ -900,12 +965,11 @@ PJ_DEF(pj_status_t) pjmedia_stream_start(pjmedia_stream *stream) * Get stream statistics. */ PJ_DEF(pj_status_t) pjmedia_stream_get_stat( const pjmedia_stream *stream, - pjmedia_stream_stat *stat) + pjmedia_rtcp_stat *stat) { PJ_ASSERT_RETURN(stream && stat, PJ_EINVAL); - pj_memcpy(stat, &stream->stat, sizeof(pjmedia_stream_stat)); - + pj_memcpy(stat, &stream->rtcp.stat, sizeof(pjmedia_rtcp_stat)); return PJ_SUCCESS; } diff --git a/pjsip-apps/src/samples/siprtp.c b/pjsip-apps/src/samples/siprtp.c index d8d18f1d..108d0ca5 100644 --- a/pjsip-apps/src/samples/siprtp.c +++ b/pjsip-apps/src/samples/siprtp.c @@ -51,18 +51,6 @@ struct codec }; -/* Unidirectional media stat: */ -struct stream_stat -{ - pj_uint32_t pkt, payload; - pj_uint32_t discard, reorder; - unsigned loss_min, loss_avg, loss_max; - char *loss_type; - unsigned jitter_min_us, jitter_avg_us, jitter_max_us; - unsigned rtcp_cnt; -}; - - /* A bidirectional media stream */ struct media_stream { @@ -87,11 +75,6 @@ struct media_stream /* RTCP stats: */ pjmedia_rtcp_session rtcp; /* incoming RTCP session. */ - pjmedia_rtcp_pkt rem_rtcp; /* received RTCP stat. */ - - /* More stats: */ - struct stream_stat rx_stat; /* incoming stream stat */ - struct stream_stat tx_stat; /* outgoing stream stat. */ /* Thread: */ pj_bool_t thread_quit_flag; /* worker thread quit flag */ @@ -1073,8 +1056,6 @@ static int media_thread(void *arg) continue; } - ++strm->rx_stat.pkt; - strm->rx_stat.payload += (size - 12); /* Decode RTP packet. */ status = pjmedia_rtp_decode_rtp(&strm->in_sess, @@ -1083,27 +1064,15 @@ static int media_thread(void *arg) &payload, &payload_len); if (status != PJ_SUCCESS) { app_perror(THIS_FILE, "RTP decode error", status); - strm->rx_stat.discard++; - continue; - } - - /* Update RTP session */ - status = pjmedia_rtp_session_update(&strm->in_sess, hdr); - if (status != PJ_SUCCESS && - status != PJMEDIA_RTP_ESESSPROBATION && - status != PJMEDIA_RTP_ESESSRESTART) - { - app_perror(THIS_FILE, "RTP update error", status); - PJ_LOG(3,(THIS_FILE,"RTP packet detail: pt=%d, seq=%d", - hdr->pt, pj_ntohs(hdr->seq))); - strm->rx_stat.discard++; continue; } /* Update the RTCP session. */ pjmedia_rtcp_rx_rtp(&strm->rtcp, pj_ntohs(hdr->seq), - pj_ntohl(hdr->ts)); + pj_ntohl(hdr->ts), payload_len); + /* Update RTP session */ + pjmedia_rtp_session_update(&strm->in_sess, hdr, NULL); } if (rc > 0 && PJ_FD_ISSET(strm->rtcp_sock, &set)) { @@ -1118,37 +1087,8 @@ static int media_thread(void *arg) status = pj_sock_recv( strm->rtcp_sock, packet, &size, 0); if (status != PJ_SUCCESS) app_perror(THIS_FILE, "Error receiving RTCP packet", status); - else { - if (size != sizeof(strm->rem_rtcp)) { - PJ_LOG(3,(THIS_FILE, "Error: RTCP packet size mismatch " - "(recv %d bytes, expecting %d)", - size, sizeof(strm->rem_rtcp))); - status = -1; - } else { - pj_memcpy(&strm->rem_rtcp, packet, size); - status = PJ_SUCCESS; - - /* Report receipt of RTCP to RTCP session */ - pjmedia_rtcp_rx_rtcp(&strm->rtcp, packet, size); - } - } - - if (status == PJ_SUCCESS) { - /* Process RTCP stats */ - unsigned jitter; - - jitter = (unsigned)(pj_ntohl(strm->rem_rtcp.rr.jitter) * - 1000000.0 / strm->clock_rate); - if (jitter < strm->tx_stat.jitter_min_us) - strm->tx_stat.jitter_min_us = jitter; - if (jitter > strm->tx_stat.jitter_max_us) - strm->tx_stat.jitter_max_us = jitter; - strm->tx_stat.jitter_avg_us = - (strm->tx_stat.jitter_avg_us * strm->tx_stat.rtcp_cnt + - jitter) / (strm->tx_stat.rtcp_cnt + 1); - - strm->tx_stat.rtcp_cnt++; - } + else + pjmedia_rtcp_rx_rtcp(&strm->rtcp, packet, size); } @@ -1193,10 +1133,6 @@ static int media_thread(void *arg) /* Schedule next send */ next_rtp.u64 += (msec_interval * freq.u64 / 1000); - - /* Update stats */ - strm->tx_stat.pkt++; - strm->tx_stat.payload += strm->bytes_per_frame; } @@ -1228,24 +1164,7 @@ static int media_thread(void *arg) app_perror(THIS_FILE, "Error sending RTCP packet", status); } - - /* Process RTCP stats */ - { - unsigned jitter; - - jitter = (unsigned) (pj_ntohl(rtcp_pkt->rr.jitter) * - 1000000.0 / strm->clock_rate); - if (jitter < strm->rx_stat.jitter_min_us) - strm->rx_stat.jitter_min_us = jitter; - if (jitter > strm->rx_stat.jitter_max_us) - strm->rx_stat.jitter_max_us = jitter; - strm->rx_stat.jitter_avg_us = - (strm->rx_stat.jitter_avg_us * strm->rx_stat.rtcp_cnt + - jitter) / (strm->rx_stat.rtcp_cnt + 1); - - strm->rx_stat.rtcp_cnt++; - } - + /* Schedule next send */ next_rtcp.u64 += (freq.u64 * RTCP_INTERVAL); } } @@ -1318,12 +1237,9 @@ static void call_on_media_update( pjsip_inv_session *inv, pjmedia_rtp_session_init(&audio->out_sess, audio->si.tx_pt, pj_rand()); pjmedia_rtp_session_init(&audio->in_sess, audio->si.fmt.pt, 0); - pjmedia_rtcp_init(&audio->rtcp, audio->clock_rate, 0); - + pjmedia_rtcp_init(&audio->rtcp, audio->clock_rate, + audio->samples_per_frame, 0); - /* Clear media statistics */ - pj_memset(&audio->rx_stat, 0, sizeof(audio->rx_stat)); - pj_memset(&audio->tx_stat, 0, sizeof(audio->tx_stat)); /* Start media thread. */ @@ -1410,16 +1326,15 @@ static void print_call(int call_index) pjsip_dialog *dlg = inv->dlg; struct media_stream *audio = &call->media[0]; char userinfo[128]; - char duration[80]; + char duration[80], last_update[80]; char bps[16], ipbps[16], packets[16], bytes[16], ipbytes[16]; - pj_uint32_t total_loss; + pj_time_val now; + pj_gettimeofday(&now); /* Print duration */ if (inv->state >= PJSIP_INV_STATE_CONFIRMED) { - pj_time_val now; - pj_gettimeofday(&now); PJ_TIME_VAL_SUB(now, call->connect_time); sprintf(duration, " [duration: %02ld:%02ld:%02ld.%03ld]", @@ -1488,63 +1403,97 @@ static void print_call(int call_index) good_number(bps, audio->bytes_per_frame * audio->clock_rate / audio->samples_per_frame), good_number(ipbps, (audio->bytes_per_frame+32) * audio->clock_rate / audio->samples_per_frame)); - total_loss = (audio->rtcp.rtcp_pkt.rr.total_lost_2 << 16) + - (audio->rtcp.rtcp_pkt.rr.total_lost_1 << 8) + - audio->rtcp.rtcp_pkt.rr.total_lost_0; - - printf(" RX total %s packets %sB received (%sB +IP hdr)%s\n" - " pkt discards=%d (%3.1f%%), loss=%d (%3.1f%%), reorder=%d (%3.1f%%)%s\n" - " loss period min=%dms, avg=%dms, max=%dms%s\n" - " jitter min=%5.3fms, avg=%5.3fms, max=%5.3fms, curr=%5.3f ms%s\n", - good_number(packets, audio->rx_stat.pkt), - good_number(bytes, audio->rx_stat.payload), - good_number(ipbytes, audio->rx_stat.payload + audio->rx_stat.pkt * 32), + if (audio->rtcp.stat.rx.update_cnt == 0) + strcpy(last_update, "never"); + else { + pj_gettimeofday(&now); + PJ_TIME_VAL_SUB(now, audio->rtcp.stat.rx.update); + sprintf(last_update, "%02dh:%02dm:%02d.%03ds ago", + now.sec / 3600, + (now.sec % 3600) / 60, + now.sec % 60, + now.msec); + } + + printf(" RX stat last update: %s\n" + " total %s packets %sB received (%sB +IP hdr)%s\n" + " pkt loss=%d (%3.1f%%), dup=%d (%3.1f%%), reorder=%d (%3.1f%%)%s\n" + " loss period min=%5.3fms, avg=%5.3fms, max=%5.3fms, last=%5.3f%s\n" + " jitter min=%5.3fms, avg=%5.3fms, max=%5.3fms, last=%5.3fms%s\n", + last_update, + good_number(packets, audio->rtcp.stat.rx.pkt), + good_number(bytes, audio->rtcp.stat.rx.bytes), + good_number(ipbytes, audio->rtcp.stat.rx.bytes + audio->rtcp.stat.rx.pkt * 32), "", - audio->rx_stat.discard, - audio->rx_stat.discard * 100.0 / audio->rx_stat.pkt, - total_loss, - total_loss * 100.0 / audio->rx_stat.pkt, - 0, 0.0, + audio->rtcp.stat.rx.loss, + audio->rtcp.stat.rx.loss * 100.0 / audio->rtcp.stat.rx.pkt, + audio->rtcp.stat.rx.dup, + audio->rtcp.stat.rx.dup * 100.0 / audio->rtcp.stat.rx.pkt, + audio->rtcp.stat.rx.reorder, + audio->rtcp.stat.rx.reorder * 100.0 / audio->rtcp.stat.rx.pkt, "", - -1, -1, -1, + audio->rtcp.stat.rx.loss_period.min / 1000.0, + audio->rtcp.stat.rx.loss_period.avg / 1000.0, + audio->rtcp.stat.rx.loss_period.max / 1000.0, + audio->rtcp.stat.rx.loss_period.last / 1000.0, "", - (audio->rx_stat.rtcp_cnt? audio->rx_stat.jitter_min_us/1000.0 : -1.), - (audio->rx_stat.rtcp_cnt? audio->rx_stat.jitter_avg_us/1000.0 : -1.), - (audio->rx_stat.rtcp_cnt? audio->rx_stat.jitter_max_us/1000.0 : -1.), - (audio->rx_stat.rtcp_cnt? pj_ntohl(audio->rtcp.rtcp_pkt.rr.jitter)*1000.0/audio->clock_rate : -1.), + audio->rtcp.stat.rx.jitter.min / 1000.0, + audio->rtcp.stat.rx.jitter.avg / 1000.0, + audio->rtcp.stat.rx.jitter.max / 1000.0, + audio->rtcp.stat.rx.jitter.last / 1000.0, "" ); - total_loss = (audio->rem_rtcp.rr.total_lost_2 << 16) + - (audio->rem_rtcp.rr.total_lost_1 << 8) + - audio->rem_rtcp.rr.total_lost_0; + if (audio->rtcp.stat.tx.update_cnt == 0) + strcpy(last_update, "never"); + else { + pj_gettimeofday(&now); + PJ_TIME_VAL_SUB(now, audio->rtcp.stat.tx.update); + sprintf(last_update, "%02dh:%02dm:%02d.%03ds ago", + now.sec / 3600, + (now.sec % 3600) / 60, + now.sec % 60, + now.msec); + } - printf(" TX total %s packets %sB sent (%sB +IP hdr)%s\n" - " pkt discards=%d (%3.1f%%), loss=%d (%3.1f%%), reorder=%d (%3.1f%%)%s\n" - " loss period min=%dms, avg=%dms, max=%dms%s\n" - " jitter min=%5.3fms, avg=%5.3fms, max=%5.3fms, curr=%5.3f ms%s\n", - good_number(packets, audio->tx_stat.pkt), - good_number(bytes, audio->tx_stat.payload), - good_number(ipbytes, audio->tx_stat.payload + audio->tx_stat.pkt * 32), + printf(" TX stat last update: %s\n" + " total %s packets %sB received (%sB +IP hdr)%s\n" + " pkt loss=%d (%3.1f%%), dup=%d (%3.1f%%), reorder=%d (%3.1f%%)%s\n" + " loss period min=%5.3fms, avg=%5.3fms, max=%5.3fms, last=%5.3f%s\n" + " jitter min=%5.3fms, avg=%5.3fms, max=%5.3fms, last=%5.3fms%s\n", + last_update, + good_number(packets, audio->rtcp.stat.tx.pkt), + good_number(bytes, audio->rtcp.stat.tx.bytes), + good_number(ipbytes, audio->rtcp.stat.tx.bytes + audio->rtcp.stat.tx.pkt * 32), "", - audio->tx_stat.discard, - audio->tx_stat.discard * 100.0 / audio->tx_stat.pkt, - total_loss, - total_loss * 100.0 / audio->tx_stat.pkt, - 0, 0.0, + audio->rtcp.stat.tx.loss, + audio->rtcp.stat.tx.loss * 100.0 / audio->rtcp.stat.tx.pkt, + audio->rtcp.stat.tx.dup, + audio->rtcp.stat.tx.dup * 100.0 / audio->rtcp.stat.tx.pkt, + audio->rtcp.stat.tx.reorder, + audio->rtcp.stat.tx.reorder * 100.0 / audio->rtcp.stat.tx.pkt, "", - -1, -1, -1, + audio->rtcp.stat.tx.loss_period.min / 1000.0, + audio->rtcp.stat.tx.loss_period.avg / 1000.0, + audio->rtcp.stat.tx.loss_period.max / 1000.0, + audio->rtcp.stat.tx.loss_period.last / 1000.0, "", - (audio->tx_stat.rtcp_cnt? audio->tx_stat.jitter_min_us/1000.0 : -1.), - (audio->tx_stat.rtcp_cnt? audio->tx_stat.jitter_avg_us/1000.0 : -1.), - (audio->tx_stat.rtcp_cnt? audio->tx_stat.jitter_max_us/1000.0 : -1.), - (audio->tx_stat.rtcp_cnt? pj_ntohl(audio->rem_rtcp.rr.jitter)*1000.0/audio->clock_rate : -1.), + audio->rtcp.stat.tx.jitter.min / 1000.0, + audio->rtcp.stat.tx.jitter.avg / 1000.0, + audio->rtcp.stat.tx.jitter.max / 1000.0, + audio->rtcp.stat.tx.jitter.last / 1000.0, "" ); - printf(" End to end delay: %5.3f ms\n", - audio->rtcp.rtt_us / 1000.0); + + printf(" RTT min=%5.3fms, avg=%5.3fms, max=%5.3fms, last=%5.3fms%s\n", + audio->rtcp.stat.rtt.min / 1000.0, + audio->rtcp.stat.rtt.avg / 1000.0, + audio->rtcp.stat.rtt.max / 1000.0, + audio->rtcp.stat.rtt.last / 1000.0, + "" + ); } diff --git a/pjsip/src/pjsua-lib/pjsua_settings.c b/pjsip/src/pjsua-lib/pjsua_settings.c index 0c4f081a..e24e9741 100644 --- a/pjsip/src/pjsua-lib/pjsua_settings.c +++ b/pjsip/src/pjsua-lib/pjsua_settings.c @@ -647,13 +647,15 @@ static void dump_media_session(pjmedia_session *session) pjmedia_session_get_info(session, &info); for (i=0; i<info.stream_cnt; ++i) { - pjmedia_stream_stat strm_stat; + pjmedia_rtcp_stat stat; const char *rem_addr; int rem_port; const char *dir; - char stxpkt[10], stxoct[10], srxpkt[10], srxoct[10]; + char last_update[40]; + char packets[16], bytes[16], ipbytes[16]; + pj_time_val now; - pjmedia_session_get_stream_stat(session, i, &strm_stat); + pjmedia_session_get_stream_stat(session, i, &stat); rem_addr = pj_inet_ntoa(info.stream_info[i].rem_addr.sin_addr); rem_port = pj_ntohs(info.stream_info[i].rem_addr.sin_port); @@ -676,15 +678,105 @@ static void dump_media_session(pjmedia_session *session) info.stream_info[i].fmt.sample_rate / 1000, dir, rem_addr, rem_port)); + + if (stat.rx.update_cnt == 0) + strcpy(last_update, "never"); + else { + pj_gettimeofday(&now); + PJ_TIME_VAL_SUB(now, stat.rx.update); + sprintf(last_update, "%02dh:%02dm:%02d.%03ds ago", + now.sec / 3600, + (now.sec % 3600) / 60, + now.sec % 60, + now.msec); + } + PJ_LOG(3,(THIS_FILE, - "%s tx{pt=%d,pkt=%s,oct=%s} rx{pt=%d,pkt=%s,oct=%s}", - " ", - info.stream_info[i].tx_pt, - good_number(stxpkt, strm_stat.enc.pkt), - good_number(stxoct, strm_stat.enc.bytes), - info.stream_info[i].fmt.pt, - good_number(srxpkt, strm_stat.dec.pkt), - good_number(srxoct, strm_stat.dec.bytes))); + " RX pt=%d, stat last update: %s\n" + " total %s packets %sB received (%sB +IP hdr)%s\n" + " pkt loss=%d (%3.1f%%), dup=%d (%3.1f%%), reorder=%d (%3.1f%%)%s\n" + " (msec) min avg max last\n" + " loss period: %7.3f %7.3f %7.3f %7.3f%s\n" + " jitter : %7.3f %7.3f %7.3f %7.3f%s", + info.stream_info[i].fmt.pt, + last_update, + good_number(packets, stat.rx.pkt), + good_number(bytes, stat.rx.bytes), + good_number(ipbytes, stat.rx.bytes + stat.rx.pkt * 32), + "", + stat.rx.loss, + stat.rx.loss * 100.0 / stat.rx.pkt, + stat.rx.dup, + stat.rx.dup * 100.0 / stat.rx.pkt, + stat.rx.reorder, + stat.rx.reorder * 100.0 / stat.rx.pkt, + "", + stat.rx.loss_period.min / 1000.0, + stat.rx.loss_period.avg / 1000.0, + stat.rx.loss_period.max / 1000.0, + stat.rx.loss_period.last / 1000.0, + "", + stat.rx.jitter.min / 1000.0, + stat.rx.jitter.avg / 1000.0, + stat.rx.jitter.max / 1000.0, + stat.rx.jitter.last / 1000.0, + "" + )); + + + if (stat.tx.update_cnt == 0) + strcpy(last_update, "never"); + else { + pj_gettimeofday(&now); + PJ_TIME_VAL_SUB(now, stat.tx.update); + sprintf(last_update, "%02dh:%02dm:%02d.%03ds ago", + now.sec / 3600, + (now.sec % 3600) / 60, + now.sec % 60, + now.msec); + } + + PJ_LOG(3,(THIS_FILE, + " TX pt=%d, stat last update: %s\n" + " total %s packets %sB received (%sB +IP hdr)%s\n" + " pkt loss=%d (%3.1f%%), dup=%d (%3.1f%%), reorder=%d (%3.1f%%)%s\n" + " (msec) min avg max last\n" + " loss period: %7.3f %7.3f %7.3f %7.3f%s\n" + " jitter : %7.3f %7.3f %7.3f %7.3f%s", + info.stream_info[i].tx_pt, + last_update, + good_number(packets, stat.tx.pkt), + good_number(bytes, stat.tx.bytes), + good_number(ipbytes, stat.tx.bytes + stat.tx.pkt * 32), + "", + stat.tx.loss, + stat.tx.loss * 100.0 / stat.tx.pkt, + stat.tx.dup, + stat.tx.dup * 100.0 / stat.tx.pkt, + stat.tx.reorder, + stat.tx.reorder * 100.0 / stat.tx.pkt, + "", + stat.tx.loss_period.min / 1000.0, + stat.tx.loss_period.avg / 1000.0, + stat.tx.loss_period.max / 1000.0, + stat.tx.loss_period.last / 1000.0, + "", + stat.tx.jitter.min / 1000.0, + stat.tx.jitter.avg / 1000.0, + stat.tx.jitter.max / 1000.0, + stat.tx.jitter.last / 1000.0, + "" + )); + + + PJ_LOG(3,(THIS_FILE, + " RTT msec : %7.3f %7.3f %7.3f %7.3f%s", + stat.rtt.min / 1000.0, + stat.rtt.avg / 1000.0, + stat.rtt.max / 1000.0, + stat.rtt.last / 1000.0, + "" + )); } } @@ -712,27 +804,25 @@ void pjsua_dump(pj_bool_t detail) /* Dump all invite sessions: */ - if (detail) { - PJ_LOG(3,(THIS_FILE, "Dumping invite sessions:")); + PJ_LOG(3,(THIS_FILE, "Dumping invite sessions:")); - if (pjsua.call_cnt == 0) { + if (pjsua.call_cnt == 0) { - PJ_LOG(3,(THIS_FILE, " - no sessions -")); + PJ_LOG(3,(THIS_FILE, " - no sessions -")); - } else { - int i; + } else { + int i; - for (i=0; i<pjsua.max_calls; ++i) { + for (i=0; i<pjsua.max_calls; ++i) { - if (pjsua.calls[i].inv == NULL) - continue; + if (pjsua.calls[i].inv == NULL) + continue; - print_call(" ", i, buf, sizeof(buf)); - PJ_LOG(3,(THIS_FILE, "%s", buf)); + print_call(" ", i, buf, sizeof(buf)); + PJ_LOG(3,(THIS_FILE, "%s", buf)); - if (pjsua.calls[i].session) - dump_media_session(pjsua.calls[i].session); - } + if (pjsua.calls[i].session) + dump_media_session(pjsua.calls[i].session); } } |