/** * @file rtp/sess.c Real-time Transport Control Protocol Session * * Copyright (C) 2010 Creytiv.com */ #ifdef HAVE_SYS_TIME_H #include #endif #include #ifdef WIN32 #include #endif #include #include #include #include #include #include #include #include #include #include #include #include "rtcp.h" #define DEBUG_MODULE "rtcp_sess" #define DEBUG_LEVEL 5 #include enum { RTCP_INTERVAL = 5000, /**< Interval in [ms] between sending reports */ MAX_MEMBERS = 8, }; /** RTP Transmit stats */ struct txstat { uint32_t psent; /**< Total number of RTP packets sent */ uint32_t osent; /**< Total number of RTP octets sent */ uint32_t ts_offset; /**< RTP Timestamp offset (transmit) */ time_t start; /**< When session started [s] */ }; /** RTCP Session */ struct rtcp_sess { struct rtp_sock *rs; /**< RTP Socket */ struct hash *members; /**< Member table */ struct tmr tmr; /**< Event sender timer */ char *cname; /**< Canonical Name */ uint32_t memberc; /**< Number of members */ uint32_t senderc; /**< Number of senders */ uint32_t srate_tx; /**< Transmit sampling rate */ uint32_t srate_rx; /**< Receive sampling rate */ /* stats */ struct lock *lock; /**< Lock for txstat */ struct txstat txstat; /**< Local transmit statistics */ }; /* Prototypes */ static void schedule(struct rtcp_sess *sess); static int send_bye_packet(struct rtcp_sess *sess); static void sess_destructor(void *data) { struct rtcp_sess *sess = data; if (sess->cname) (void)send_bye_packet(sess); tmr_cancel(&sess->tmr); mem_deref(sess->cname); hash_flush(sess->members); mem_deref(sess->members); mem_deref(sess->lock); } static struct rtp_member *get_member(struct rtcp_sess *sess, uint32_t src) { struct rtp_member *mbr; mbr = member_find(sess->members, src); if (mbr) return mbr; if (sess->memberc >= MAX_MEMBERS) return NULL; mbr = member_add(sess->members, src); if (!mbr) return NULL; ++sess->memberc; return mbr; } /** Calculate Round-Trip Time in [microseconds] */ static void calc_rtt(uint32_t *rtt, uint32_t lsr, uint32_t dlsr) { struct ntp_time ntp_time; uint64_t a_us, lsr_us, dlsr_us; int err; err = ntp_time_get(&ntp_time); if (err) return; a_us = ntp_compact2us(ntp_compact(&ntp_time)); lsr_us = ntp_compact2us(lsr); dlsr_us = 1000000ULL * dlsr / 65536; /* RTT delay is (A - LSR - DLSR) */ *rtt = MAX((int)(a_us - lsr_us - dlsr_us), 0); } /** Decode Reception Report block */ static void handle_rr_block(struct rtcp_sess *sess, struct rtp_member *mbr, const struct rtcp_rr *rr) { /* Lost */ mbr->cum_lost = rr->lost; /* Interarrival jitter */ if (sess->srate_tx) mbr->jit = 1000000 * rr->jitter / sess->srate_tx; /* round-trip propagation delay as (A - LSR - DLSR) */ if (rr->lsr && rr->dlsr) calc_rtt(&mbr->rtt, rr->lsr, rr->dlsr); } /** Handle incoming RR (Receiver Report) packet */ static void handle_incoming_rr(struct rtcp_sess *sess, const struct rtcp_msg *msg) { struct rtp_member *mbr; uint32_t i; mbr = get_member(sess, msg->r.rr.ssrc); if (!mbr) return; for (i=0; ihdr.count; i++) handle_rr_block(sess, mbr, &msg->r.rr.rrv[i]); } /** Handle incoming SR (Sender Report) packet */ static void handle_incoming_sr(struct rtcp_sess *sess, const struct rtcp_msg *msg) { struct rtp_member *mbr; uint32_t i; mbr = get_member(sess, msg->r.sr.ssrc); if (!mbr) { DEBUG_WARNING("0x%08x: could not add member\n", msg->r.sr.ssrc); return; } if (mbr->s) { /* Save time when SR was received */ mbr->s->sr_recv = tmr_jiffies(); /* Save NTP timestamp from SR */ mbr->s->last_sr.hi = msg->r.sr.ntp_sec; mbr->s->last_sr.lo = msg->r.sr.ntp_frac; mbr->s->rtp_ts = msg->r.sr.rtp_ts; mbr->s->psent = msg->r.sr.psent; mbr->s->osent = msg->r.sr.osent; } for (i=0; ihdr.count; i++) handle_rr_block(sess, mbr, &msg->r.sr.rrv[i]); } static void handle_incoming_bye(struct rtcp_sess *sess, const struct rtcp_msg *msg) { uint32_t i; for (i=0; ihdr.count; i++) { struct rtp_member *mbr; mbr = member_find(sess->members, msg->r.bye.srcv[i]); if (mbr) { if (mbr->s) --sess->senderc; --sess->memberc; mem_deref(mbr); } } } void rtcp_handler(struct rtcp_sess *sess, struct rtcp_msg *msg) { if (!sess || !msg) return; switch (msg->hdr.pt) { case RTCP_SR: handle_incoming_sr(sess, msg); break; case RTCP_RR: handle_incoming_rr(sess, msg); break; case RTCP_BYE: handle_incoming_bye(sess, msg); break; default: break; } } int rtcp_sess_alloc(struct rtcp_sess **sessp, struct rtp_sock *rs) { struct rtcp_sess *sess; int err; if (!sessp) return EINVAL; sess = mem_zalloc(sizeof(*sess), sess_destructor); if (!sess) return ENOMEM; sess->rs = rs; tmr_init(&sess->tmr); err = lock_alloc(&sess->lock); if (err) goto out; err = hash_alloc(&sess->members, MAX_MEMBERS); if (err) goto out; out: if (err) mem_deref(sess); else *sessp = sess; return err; } /** * Set the Sampling-rate on an RTCP Session * * @param rs RTP Socket * @param srate_tx Transmit samplerate * @param srate_rx Receive samplerate */ void rtcp_set_srate(struct rtp_sock *rs, uint32_t srate_tx, uint32_t srate_rx) { struct rtcp_sess *sess = rtp_rtcp_sess(rs); if (!sess) return; sess->srate_tx = srate_tx; sess->srate_rx = srate_rx; } /** * Set the transmit Sampling-rate on an RTCP Session * * @param rs RTP Socket * @param srate_tx Transmit samplerate */ void rtcp_set_srate_tx(struct rtp_sock *rs, uint32_t srate_tx) { struct rtcp_sess *sess = rtp_rtcp_sess(rs); if (!sess) return; sess->srate_tx = srate_tx; } /** * Set the receive Sampling-rate on an RTCP Session * * @param rs RTP Socket * @param srate_rx Receive samplerate */ void rtcp_set_srate_rx(struct rtp_sock *rs, uint32_t srate_rx) { struct rtcp_sess *sess = rtp_rtcp_sess(rs); if (!sess) return; sess->srate_rx = srate_rx; } int rtcp_enable(struct rtcp_sess *sess, bool enabled, const char *cname) { int err; if (!sess) return EINVAL; sess->cname = mem_deref(sess->cname); err = str_dup(&sess->cname, cname); if (err) return err; if (enabled) schedule(sess); else tmr_cancel(&sess->tmr); return 0; } /** Calculate LSR (middle 32 bits out of 64 in the NTP timestamp) */ static uint32_t calc_lsr(const struct ntp_time *last_sr) { return last_sr->hi ? ntp_compact(last_sr) : 0; } static uint32_t calc_dlsr(uint64_t sr_recv) { if (sr_recv) { const uint64_t diff = tmr_jiffies() - sr_recv; return (uint32_t)((65536 * diff) / 1000); } else { return 0; } } static bool sender_apply_handler(struct le *le, void *arg) { struct rtp_member *mbr = le->data; struct rtp_source *s = mbr->s; struct mbuf *mb = arg; struct rtcp_rr rr; if (!s) return false; /* Initialise the members */ rr.ssrc = mbr->src; rr.fraction = source_calc_fraction_lost(s); rr.lost = source_calc_lost(s); rr.last_seq = s->cycles | s->max_seq; rr.jitter = s->jitter >> 4; rr.lsr = calc_lsr(&s->last_sr); rr.dlsr = calc_dlsr(s->sr_recv); return 0 != rtcp_rr_encode(mb, &rr); } static int encode_handler(struct mbuf *mb, void *arg) { struct hash *members = arg; /* copy all report blocks */ if (hash_apply(members, sender_apply_handler, mb)) return ENOMEM; return 0; } /** Create a Sender Report */ static int mk_sr(struct rtcp_sess *sess, struct mbuf *mb) { struct ntp_time ntp = {0, 0}; struct txstat txstat; uint32_t dur = 0, rtp_ts = 0; int err; err = ntp_time_get(&ntp); if (err) return err; lock_read_get(sess->lock); txstat = sess->txstat; lock_rel(sess->lock); if (txstat.start) { dur = (uint32_t)(time(NULL) - txstat.start); rtp_ts = txstat.ts_offset + dur * sess->srate_tx; } err = rtcp_encode(mb, RTCP_SR, sess->senderc, rtp_sess_ssrc(sess->rs), ntp.hi, ntp.lo, rtp_ts, txstat.psent, txstat.osent, encode_handler, sess->members); if (err) return err; return err; } static int sdes_encode_handler(struct mbuf *mb, void *arg) { struct rtcp_sess *sess = arg; return rtcp_sdes_encode(mb, rtp_sess_ssrc(sess->rs), 1, RTCP_SDES_CNAME, sess->cname); } static int mk_sdes(struct rtcp_sess *sess, struct mbuf *mb) { return rtcp_encode(mb, RTCP_SDES, 1, sdes_encode_handler, sess); } static int send_rtcp_report(struct rtcp_sess *sess) { struct mbuf *mb; int err; mb = mbuf_alloc(512); if (!mb) return ENOMEM; mb->pos = RTCP_HEADROOM; err = mk_sr(sess, mb); err |= mk_sdes(sess, mb); if (err) goto out; mb->pos = RTCP_HEADROOM; err = rtcp_send(sess->rs, mb); out: mem_deref(mb); return err; } static int send_bye_packet(struct rtcp_sess *sess) { const uint32_t ssrc = rtp_sess_ssrc(sess->rs); struct mbuf *mb; int err; mb = mbuf_alloc(512); if (!mb) return ENOMEM; mb->pos = RTCP_HEADROOM; err = rtcp_encode(mb, RTCP_BYE, 1, &ssrc, "Adjo"); err |= mk_sdes(sess, mb); if (err) goto out; mb->pos = RTCP_HEADROOM; err = rtcp_send(sess->rs, mb); out: mem_deref(mb); return err; } static void timeout(void *arg) { struct rtcp_sess *sess = arg; int err; err = send_rtcp_report(sess); if (err) { DEBUG_WARNING("Send RTCP report failed: %s\n", strerror(err)); } schedule(sess); } static void schedule(struct rtcp_sess *sess) { tmr_start(&sess->tmr, RTCP_INTERVAL, timeout, sess); } void rtcp_sess_tx_rtp(struct rtcp_sess *sess, uint32_t ts, size_t payload_size) { if (!sess) return; lock_write_get(sess->lock); sess->txstat.osent += (uint32_t)payload_size; sess->txstat.psent += 1; if (sess->txstat.start == 0) { sess->txstat.ts_offset = ts; sess->txstat.start = time(NULL); } lock_rel(sess->lock); } void rtcp_sess_rx_rtp(struct rtcp_sess *sess, uint16_t seq, uint32_t ts, uint32_t ssrc, size_t payload_size, const struct sa *peer) { struct rtp_member *mbr; if (!sess) return; mbr = get_member(sess, ssrc); if (!mbr) { DEBUG_NOTICE("could not add member: 0x%08x\n", ssrc); return; } if (!mbr->s) { mbr->s = mem_zalloc(sizeof(*mbr->s), NULL); if (!mbr->s) { DEBUG_NOTICE("could not add sender: 0x%08x\n", ssrc); return; } /* first packet - init sequence number */ source_init_seq(mbr->s, seq); mbr->s->max_seq = seq - 1; /* probation not used */ sa_cpy(&mbr->s->rtp_peer, peer); ++sess->senderc; } if (!source_update_seq(mbr->s, seq)) { DEBUG_WARNING("rtp_update_seq() returned 0\n"); } if (sess->srate_rx) { uint32_t ts_arrive; /* Convert from wall-clock time to timestamp units */ ts_arrive = (uint32_t)(tmr_jiffies()) * sess->srate_rx / 1000; source_calc_jitter(mbr->s, ts, ts_arrive); } mbr->s->rtp_rx_bytes += payload_size; } /** * Get the RTCP Statistics for a source * * @param rs RTP Socket * @param ssrc Synchronization source * @param stats RTCP Statistics, set on return * * @return 0 if success, otherwise errorcode */ int rtcp_stats(struct rtp_sock *rs, uint32_t ssrc, struct rtcp_stats *stats) { const struct rtcp_sess *sess = rtp_rtcp_sess(rs); struct rtp_member *mbr; if (!sess || !stats) return EINVAL; mbr = member_find(sess->members, ssrc); if (!mbr) return ENOENT; lock_read_get(sess->lock); stats->tx.sent = sess->txstat.psent; lock_rel(sess->lock); stats->tx.lost = mbr->cum_lost; stats->tx.jit = mbr->jit; if (!mbr->s) { memset(&stats->rx, 0, sizeof(stats->rx)); return 0; } stats->rx.sent = mbr->s->received; stats->rx.lost = source_calc_lost(mbr->s); stats->rx.jit = sess->srate_rx ? 1000000 * (mbr->s->jitter>>4) / sess->srate_rx : 0; return 0; } static bool debug_handler(struct le *le, void *arg) { const struct rtp_member *mbr = le->data; struct re_printf *pf = arg; int err; err = re_hprintf(pf, " member 0x%08x: lost=%d Jitter=%.1fms" " RTT=%.1fms\n", mbr->src, mbr->cum_lost, (double)mbr->jit/1000, (double)mbr->rtt/1000); if (mbr->s) { err |= re_hprintf(pf, " IP=%J psent=%u rcvd=%u\n", &mbr->s->rtp_peer, mbr->s->psent, mbr->s->received); } return err != 0; } /** * RTCP Debug handler, use with fmt %H * * @param pf Print function * @param rs RTP Socket * * @return 0 if success, otherwise errorcode */ int rtcp_debug(struct re_printf *pf, const struct rtp_sock *rs) { const struct rtcp_sess *sess = rtp_rtcp_sess(rs); int err = 0; if (!sess) return 0; err |= re_hprintf(pf, "----- RTCP Session: -----\n"); err |= re_hprintf(pf, " cname=%s SSRC=0x%08x/%u rx=%uHz\n", sess->cname, rtp_sess_ssrc(sess->rs), rtp_sess_ssrc(sess->rs), sess->srate_rx); hash_apply(sess->members, debug_handler, pf); lock_read_get(sess->lock); err |= re_hprintf(pf, " TX: packets=%u, octets=%u\n", sess->txstat.psent, sess->txstat.osent); lock_rel(sess->lock); return err; }