diff --git a/conf.d/yrtpchan.conf.sample b/conf.d/yrtpchan.conf.sample index ee7427ff..1b7b8a8d 100644 --- a/conf.d/yrtpchan.conf.sample +++ b/conf.d/yrtpchan.conf.sample @@ -30,6 +30,9 @@ ; rtcp: bool: Allocate socket for the RTCP protocol by default ;rtcp=enabled +; rtcp_interval: int: RTCP report interval in ms (500-60000), zero disables +;rtcp_interval=4500 + ; drillhole: bool: Attempt to drill a hole through a firewall or NAT ;drillhole=disable in server mode, enable in client mode diff --git a/libs/yrtp/session.cpp b/libs/yrtp/session.cpp index 4a9752be..c07fb19a 100644 --- a/libs/yrtp/session.cpp +++ b/libs/yrtp/session.cpp @@ -379,7 +379,7 @@ bool RTPReceiver::rtpCheckIntegrity(const unsigned char* data, int len, const vo RTPSender::RTPSender(RTPSession* session, bool randomTs) - : RTPBaseIO(session), m_evTime(0), m_tsLast(0), m_padding(0) + : RTPBaseIO(session), m_evTime(0), m_padding(0) { if (randomTs) { m_ts = ::random() & ~1; @@ -635,7 +635,8 @@ void UDPSession::setTimeout(int interval) RTPSession::RTPSession() : m_direction(FullStop), - m_send(0), m_recv(0), m_secure(0) + m_send(0), m_recv(0), m_secure(0), + m_reportTime(0), m_reportInterval(0) { DDebug(DebugInfo,"RTPSession::RTPSession() [%p]",this); } @@ -668,6 +669,12 @@ void RTPSession::timerTick(const Time& when) else m_timeoutTime = when + m_timeoutInterval; } + if (m_reportInterval) { + if (when >= m_reportTime) { + m_reportTime = when + m_reportInterval; + sendRtcpReport(when); + } + } } void RTPSession::rtpData(const void* data, int len) @@ -738,6 +745,8 @@ bool RTPSession::checkCipher(const String& name) void RTPSession::transport(RTPTransport* trans) { + if (!trans) + sendRtcpBye(); UDPSession::transport(trans); if (!m_transport) m_direction = FullStop; @@ -748,6 +757,7 @@ void RTPSession::sender(RTPSender* send) DDebug(DebugInfo,"RTPSession::sender(%p) old=%p [%p]",send,m_send,this); if (send == m_send) return; + sendRtcpBye(); RTPSender* tmp = m_send; m_send = send; if (tmp) @@ -848,6 +858,93 @@ void RTPSession::getStats(String& stats) const } } +void RTPSession::setReports(int interval) +{ + if (interval > 0 && m_transport && m_transport->rtcpSock()->valid()) { + if (interval < 500) + interval = 500; + else if (interval > 60000) + interval = 60000; + m_reportInterval = interval * (u_int64_t)1000 + (::random() % 20000); + } + else + m_reportInterval = 0; + m_reportTime = 0; +} + +static void store32(unsigned char* buf, unsigned int& len, u_int32_t val) +{ + buf[len++] = (unsigned char)(val >> 24); + buf[len++] = (unsigned char)(val >> 16); + buf[len++] = (unsigned char)(val >> 8); + buf[len++] = (unsigned char)(val & 0xff); +} + +void RTPSession::sendRtcpReport(const Time& when) +{ + if (!((m_send || m_recv) && m_transport && m_transport->rtcpSock()->valid())) + return; + unsigned char buf[52]; + buf[0] = 0x80; // RC=0 + buf[1] = 0xc9; // RR + buf[2] = 0; + unsigned int len = 8; + if (m_send && m_send->ioPackets()) { + // Include a sender report + buf[1] = 0xc8; // SR + // NTP timestamp + store32(buf,len,2208988800 + (when.usec() / 1000000)); + store32(buf,len,((when.usec() % 1000000) << 32) / 1000000); + // RTP timestamp + store32(buf,len,m_send->tsLast()); + // Packet and octet counters + store32(buf,len,m_send->ioPackets()); + store32(buf,len,m_send->ioOctets()); + } + if (m_recv && m_recv->ioPackets()) { + // Add a single receiver report + buf[0] |= 0x01; // RC=1 + store32(buf,len,m_recv->ssrc()); + u_int32_t lost = m_recv->ioPacketsLost(); + u_int32_t lostf = 0xff & (lost * 255 / (lost + m_recv->ioPackets())); + store32(buf,len,(lost & 0xffffff) | (lostf << 24)); + store32(buf,len,m_recv->fullSeq()); + // TODO: Compute and store Jitter, LSR and DLSR + store32(buf,len,0); + store32(buf,len,0); + store32(buf,len,0); + } + // Don't send a RR with no receiver report blocks... + if (len <= 8) + return; + DDebug(DebugInfo,"RTPSession sending RTCP Report [%p]",this); + unsigned int lptr = 4; + store32(buf,lptr,(m_send ? m_send->ssrcInit() : 0)); + buf[3] = (len - 1) / 4; // same as ((len + 3) / 4) - 1 + static_cast(m_transport)->rtcpData(buf,len); +} + +void RTPSession::sendRtcpBye() +{ + if (!(m_send && m_transport && m_transport->rtcpSock()->valid())) + return; + u_int32_t ssrc = m_send->ssrc(); + if (!ssrc) + return; + DDebug(DebugInfo,"RTPSession sending RTCP Bye [%p]",this); + // SSRC was initialized if we sent at least one RTP or RTCP packet + unsigned char buf[8]; + buf[0] = 0x81; + buf[1] = 0xcb; + buf[2] = 0; + buf[3] = 1; // len = 2 x 32bit + buf[4] = (unsigned char)(ssrc >> 24); + buf[5] = (unsigned char)(ssrc >> 16); + buf[6] = (unsigned char)(ssrc >> 8); + buf[7] = (unsigned char)(0xff & ssrc); + static_cast(m_transport)->rtcpData(buf,8); +} + UDPTLSession::UDPTLSession(u_int16_t maxLen, u_int8_t maxSec) : m_rxSeq(0xffff), m_txSeq(0xffff), diff --git a/libs/yrtp/yatertp.h b/libs/yrtp/yatertp.h index 92ccaad7..a72920d7 100644 --- a/libs/yrtp/yatertp.h +++ b/libs/yrtp/yatertp.h @@ -271,6 +271,13 @@ public: inline Socket* rtpSock() { return &m_rtpSock; } + /** + * Get the RTCP socket used by this transport + * @return Pointer to the RTCP socket + */ + inline Socket* rtcpSock() + { return &m_rtcpSock; } + /** * Drill a hole in a firewall or NAT for the RTP and RTCP sockets * @return True if at least a packet was sent for the RTP socket @@ -378,7 +385,7 @@ public: m_ssrcInit(true), m_ssrc(0), m_ts(0), m_seq(0), m_rollover(0), m_secLen(0), m_mkiLen(0), m_evTs(0), m_evNum(-1), m_evVol(-1), - m_ioPackets(), m_ioOctets(0), + m_ioPackets(), m_ioOctets(0), m_tsLast(0), m_dataType(-1), m_eventType(-1), m_silenceType(-1) { } @@ -490,6 +497,13 @@ public: inline u_int32_t ioOctets() const { return m_ioOctets; } + /** + * Get the timestamp of the last packet as transmitted over the wire + * @return Timestamp of last packet sent or received + */ + inline unsigned int tsLast() const + { return m_ts + m_tsLast; } + /** * Get the session this object belongs to * @return Pointer to RTP session or NULL @@ -539,6 +553,7 @@ protected: int m_evVol; u_int32_t m_ioPackets; u_int32_t m_ioOctets; + unsigned int m_tsLast; private: int m_dataType; @@ -559,7 +574,7 @@ public: */ inline RTPReceiver(RTPSession* session = 0) : RTPBaseIO(session), - m_ioLostPkt(0), m_dejitter(0), m_tsLast(0), + m_ioLostPkt(0), m_dejitter(0), m_seqSync(0), m_seqCount(0), m_warn(true) { } @@ -685,7 +700,6 @@ private: void finishEvent(unsigned int timestamp); bool pushEvent(int event, int duration, int volume, unsigned int timestamp); RTPDejitter* m_dejitter; - unsigned int m_tsLast; u_int16_t m_seqSync; u_int16_t m_seqCount; bool m_warn; @@ -796,7 +810,6 @@ protected: private: int m_evTime; - unsigned int m_tsLast; unsigned char m_padding; bool sendEventData(unsigned int timestamp); }; @@ -1196,6 +1209,12 @@ public: */ void security(RTPSecure* secure); + /** + * Set the RTCP report interval + * @param interval Average interval between reports in msec, zero to disable + */ + void setReports(int interval); + protected: /** * Method called periodically to push any asynchronous data or statistics @@ -1203,11 +1222,24 @@ protected: */ virtual void timerTick(const Time& when); + /** + * Send a RTCP report + * @param when Time to use as base for timestamps + */ + void sendRtcpReport(const Time& when); + + /** + * Send a RTCP BYE when the sender is stopped or replaced + */ + void sendRtcpBye(); + private: Direction m_direction; RTPSender* m_send; RTPReceiver* m_recv; RTPSecure* m_secure; + u_int64_t m_reportTime; + u_int64_t m_reportInterval; }; /** diff --git a/modules/yrtpchan.cpp b/modules/yrtpchan.cpp index 5cd77fa2..000f5575 100644 --- a/modules/yrtpchan.cpp +++ b/modules/yrtpchan.cpp @@ -99,7 +99,9 @@ static bool s_drill = false; static Thread::Priority s_priority = Thread::Normal; static int s_tos = 0; static int s_sleep = 5; +static int s_interval= 0; static int s_timeout = 0; + static int s_minjitter = 0; static int s_maxjitter = 0; @@ -700,6 +702,7 @@ bool YRTPWrapper::startRTP(const char* raddr, unsigned int rport, Message& msg) (ok ? "opened" : "failed to open"),this); } setTimeout(msg,s_timeout); + m_rtp->setReports(msg.getIntValue("rtcp_interval",s_interval)); // if (maxJitter > 0) // m_rtp->setDejitter(minJitter*1000,maxJitter*1000); m_bufsize = s_bufsize; @@ -1737,6 +1740,7 @@ void YRTPPlugin::initialize() s_anyssrc = cfg.getBoolValue("general","anyssrc",false); s_padding = cfg.getIntValue("general","padding",0); s_rtcp = cfg.getBoolValue("general","rtcp",true); + s_interval = cfg.getIntValue("general","rtcp_interval",4500); s_drill = cfg.getBoolValue("general","drillhole",Engine::clientMode()); s_sleep = cfg.getIntValue("general","defsleep",5); RTPGroup::setMinSleep(cfg.getIntValue("general","minsleep"));