Fixed RTP dejitter code, use it automatically in client mode.

git-svn-id: http://yate.null.ro/svn/yate/trunk@4961 acf43c95-373e-0410-b603-e72c3f656dc1
This commit is contained in:
paulc 2012-03-13 09:43:47 +00:00
parent 6d3767365f
commit fa6153dd82
6 changed files with 177 additions and 114 deletions

View File

@ -36,6 +36,14 @@
; drillhole: bool: Attempt to drill a hole through a firewall or NAT ; drillhole: bool: Attempt to drill a hole through a firewall or NAT
;drillhole=disable in server mode, enable in client mode ;drillhole=disable in server mode, enable in client mode
; minjitter: int: Amount to attempt to keep in the dejitter buffer in msec
; Valid values 5 to maxjitter-30, negative disables dejitter buffer
;minjitter=50
; maxjitter: int: Maximum dejitter buffer size in msec
; Valid values 50 to 1000, 0 disables dejitter buffer
;maxjitter=120 in client mode, 0 in server mode
; thread: keyword: Default priority of the data service threads ; thread: keyword: Default priority of the data service threads
; Can be one of: lowest, low, normal, high, highest ; Can be one of: lowest, low, normal, high, highest
; It is a bad idea to set a low priority for anything but testing ; It is a bad idea to set a low priority for anything but testing

View File

@ -25,112 +25,132 @@
using namespace TelEngine; using namespace TelEngine;
namespace { // anonymous
class RTPDelayedData : public DataBlock class RTPDelayedData : public DataBlock
{ {
public: public:
inline RTPDelayedData(u_int64_t when, bool mark, unsigned int tstamp, const void* data, int len) inline RTPDelayedData(u_int64_t when, bool mark, int payload,
: DataBlock(const_cast<void*>(data),len), m_scheduled(when), m_marker(mark), m_timestamp(tstamp) unsigned int tstamp, const void* data, int len)
: DataBlock(const_cast<void*>(data),len), m_scheduled(when),
m_marker(mark), m_payload(payload), m_timestamp(tstamp)
{ } { }
inline u_int64_t scheduled() const inline u_int64_t scheduled() const
{ return m_scheduled; } { return m_scheduled; }
inline bool marker() const inline bool marker() const
{ return m_marker; } { return m_marker; }
int payload() const
{ return m_payload; }
inline unsigned int timestamp() const inline unsigned int timestamp() const
{ return m_timestamp; } { return m_timestamp; }
inline void schedule(u_int64_t when)
{ m_scheduled = when; }
private: private:
u_int64_t m_scheduled; u_int64_t m_scheduled;
bool m_marker; bool m_marker;
int m_payload;
unsigned int m_timestamp; unsigned int m_timestamp;
}; };
}; // anonymous namespace
RTPDejitter::RTPDejitter(RTPReceiver* receiver, unsigned int mindelay, unsigned int maxdelay) RTPDejitter::RTPDejitter(RTPReceiver* receiver, unsigned int mindelay, unsigned int maxdelay)
: m_receiver(receiver), m_mindelay(mindelay), m_maxdelay(maxdelay), : m_receiver(receiver), m_minDelay(mindelay), m_maxDelay(maxdelay),
m_headStamp(0), m_tailStamp(0), m_headTime(0), m_tailTime(0) m_headStamp(0), m_tailStamp(0), m_headTime(0), m_sampRate(125000), m_fastRate(10)
{ {
if (m_maxdelay > 2000000) if (m_maxDelay > 1000000)
m_maxdelay = 2000000; m_maxDelay = 1000000;
if (m_maxdelay < 50000) if (m_maxDelay < 50000)
m_maxdelay = 50000; m_maxDelay = 50000;
if (m_mindelay < 5000) if (m_minDelay < 5000)
m_mindelay = 5000; m_minDelay = 5000;
if (m_mindelay > m_maxdelay - 20000) if (m_minDelay > m_maxDelay - 30000)
m_mindelay = m_maxdelay - 20000; m_minDelay = m_maxDelay - 30000;
} }
RTPDejitter::~RTPDejitter() RTPDejitter::~RTPDejitter()
{ {
DDebug(DebugMild,"Dejitter destroyed with %u packets [%p]",m_packets.count(),this); DDebug(DebugInfo,"Dejitter destroyed with %u packets [%p]",m_packets.count(),this);
} }
bool RTPDejitter::rtpRecvData(bool marker, unsigned int timestamp, const void* data, int len) void RTPDejitter::clear()
{
m_packets.clear();
m_headStamp = m_tailStamp = 0;
}
bool RTPDejitter::rtpRecv(bool marker, int payload, unsigned int timestamp, const void* data, int len)
{ {
u_int64_t when = 0; u_int64_t when = 0;
bool insert = false; bool insert = false;
if (m_headStamp && (m_tailStamp != m_headStamp)) { if (m_headStamp) {
// at least one packet got out of the queue and another is waiting // at least one packet got out of the queue
int dTs = timestamp - m_headStamp; int dTs = timestamp - m_headStamp;
if (dTs < 0) { if (dTs == 0)
DDebug(DebugMild,"Dejitter got TS %u while last delivered was %u [%p]",timestamp,m_headStamp,this); return true;
else if (dTs < 0) {
DDebug(DebugNote,"Dejitter dropping TS %u, last delivered was %u [%p]",
timestamp,m_headStamp,this);
return false; return false;
} }
u_int64_t bufTime = m_tailTime - m_headTime; u_int64_t now = Time::now();
int bufStamp = m_tailStamp - m_headStamp; int64_t rate = 1000 * (now - m_headTime) / dTs;
if (bufStamp <= 0) if (rate > 0) {
Debug(DebugWarn,"Oops! %d [%p]",bufStamp,this); if (m_sampRate) {
// interpolate or extrapolate the delivery time for the packet if (m_fastRate) {
// rounding down is ok - the buffer will slowly shrink as expected m_fastRate--;
when = dTs * bufTime / bufStamp; rate = (7 * m_sampRate + rate) >> 3;
DDebug(DebugMild,"Dejitter when=" FMT64U " dTs=%d bufTime=" FMT64U " bufSTamp=%d [%p]", }
when,dTs,bufTime,bufStamp,this); else
when += m_headTime; rate = (31 * m_sampRate + rate) >> 5;
if (dTs > bufStamp) {
bufTime = when - m_headTime;
if (bufTime > m_maxdelay) {
// buffer has lagged behind so we must drop some old packets
// and also reschedule the others
DDebug(DebugMild,"Dejitter grew to " FMT64U " [%p]",bufTime,this);
// when = m_headTime -
} }
if (rate > 150000)
rate = 150000; // 6.67 kHz
else if (rate < 20000)
rate = 20000; // 50 kHz
m_sampRate = rate;
XDebug(DebugAll,"Time per sample " FMT64, rate);
} }
else else
// timestamp falls inside buffer so we must insert the packet rate = m_sampRate;
// between the already scheduled ones if (rate > 0)
insert = true; when = m_headTime + (dTs * rate / 1000) + m_minDelay;
} else
else { when = now + m_minDelay;
if (m_tailStamp) { if (m_tailStamp) {
int dTs = timestamp - m_tailStamp; if (timestamp == m_tailStamp)
if (dTs < 0) { return true;
// until we get some statistics don't attempt to reorder packets if (((int)(timestamp - m_tailStamp)) < 0)
DDebug(DebugMild,"Dejitter got TS %u while last queued was %u [%p]",timestamp,m_tailStamp,this); insert = true;
else if (when > now + m_maxDelay) {
DDebug(DebugNote,"Packet with TS %u falls after max buffer [%p]",timestamp,this);
return false; return false;
} }
} }
}
else {
if (m_tailStamp && ((int)(timestamp - m_tailStamp)) < 0) {
// until we get some statistics don't attempt to reorder packets
DDebug(DebugNote,"Dejitter got TS %u while last queued was %u [%p]",timestamp,m_tailStamp,this);
return false;
}
// we got no packets out yet so use a fixed interval // we got no packets out yet so use a fixed interval
when = Time::now() + m_mindelay; when = Time::now() + m_minDelay;
} }
if (when > m_tailTime) {
// remember the latest in the queue
m_tailStamp = timestamp;
m_tailTime = when;
}
RTPDelayedData* packet = new RTPDelayedData(when,marker,timestamp,data,len);
if (insert) { if (insert) {
for (ObjList* l = m_packets.skipNull();l;l = l->skipNext()) { for (ObjList* l = m_packets.skipNull(); l; l = l->skipNext()) {
RTPDelayedData* pkt = static_cast<RTPDelayedData*>(l->get()); RTPDelayedData* pkt = static_cast<RTPDelayedData*>(l->get());
if (pkt->scheduled() > when) { if (pkt->timestamp() == timestamp)
DDebug(DebugMild,"Dejitter inserting packet %p before %p [%p]",packet,pkt,this); return true;
l->insert(packet); if (pkt->timestamp() > timestamp && pkt->scheduled() > when) {
l->insert(new RTPDelayedData(when,marker,payload,timestamp,data,len));
return true; return true;
} }
} }
} }
m_packets.append(packet); m_tailStamp = timestamp;
m_packets.append(new RTPDelayedData(when,marker,payload,timestamp,data,len));
return true; return true;
} }
@ -138,18 +158,33 @@ void RTPDejitter::timerTick(const Time& when)
{ {
RTPDelayedData* packet = static_cast<RTPDelayedData*>(m_packets.get()); RTPDelayedData* packet = static_cast<RTPDelayedData*>(m_packets.get());
if (!packet) { if (!packet) {
// queue is empty - reset timestamps m_tailStamp = 0;
m_headStamp = m_tailStamp = 0; if (m_headStamp && (m_headTime + m_maxDelay < when))
m_headStamp = 0;
return; return;
} }
if (packet->scheduled() <= when) { if (packet->scheduled() > when)
// remember the last delivered return;
m_headStamp = packet->timestamp(); m_packets.remove(packet,false);
m_headTime = when; // remember the last delivered
if (m_receiver) m_headStamp = packet->timestamp();
m_receiver->rtpRecvData(packet->marker(),packet->timestamp(),packet->data(),packet->length()); m_headTime = packet->scheduled();
m_packets.remove(packet); if (m_receiver)
m_receiver->rtpRecv(packet->marker(),packet->payload(),
packet->timestamp(),packet->data(),packet->length());
TelEngine::destruct(packet);
unsigned int count = 0;
while ((packet = static_cast<RTPDelayedData*>(m_packets.get()))) {
long delayed = when - packet->scheduled();
if (delayed <= 0 || delayed <= m_minDelay)
break;
// we are too delayed - probably rtpRecv() took too long to complete...
m_packets.remove(packet,true);
count++;
} }
if (count)
Debug((count > 1) ? DebugMild : DebugNote,
"Dropped %u delayed packet%s from buffer [%p]",count,((count > 1) ? "s" : ""),this);
} }
/* vi: set ts=8 sw=4 sts=4 noet: */ /* vi: set ts=8 sw=4 sts=4 noet: */

View File

@ -244,9 +244,9 @@ bool RTPSecure::deriveKey(Cipher& cipher, DataBlock& key, unsigned int len, unsi
bool RTPSecure::rtpDecipher(unsigned char* data, int len, const void* secData, u_int32_t ssrc, u_int64_t seq) bool RTPSecure::rtpDecipher(unsigned char* data, int len, const void* secData, u_int32_t ssrc, u_int64_t seq)
{ {
if (!m_rtpEncrypted) if (!(m_rtpEncrypted && data))
return true; return true;
if (!(len && data && m_rtpCipher)) if (!(len && m_rtpCipher))
return false; return false;
DataBlock iv(m_cipherSalt); DataBlock iv(m_cipherSalt);
int i; int i;

View File

@ -164,6 +164,8 @@ void RTPReceiver::rtpData(const void* data, int len)
} }
if (len < 0) if (len < 0)
return; return;
if (!len)
pc = 0;
// grab some data at the first packet received or resync // grab some data at the first packet received or resync
if (m_ssrcInit) { if (m_ssrcInit) {
@ -173,6 +175,8 @@ void RTPReceiver::rtpData(const void* data, int len)
m_seq = seq-1; m_seq = seq-1;
m_seqCount = 0; m_seqCount = 0;
m_warn = true; m_warn = true;
if (m_dejitter)
m_dejitter->clear();
} }
if (ss != m_ssrc) { if (ss != m_ssrc) {
@ -191,19 +195,35 @@ void RTPReceiver::rtpData(const void* data, int len)
m_seq = seq; m_seq = seq;
m_ts = ts - m_tsLast; m_ts = ts - m_tsLast;
m_seqCount = 0; m_seqCount = 0;
if (m_dejitter)
m_dejitter->clear();
// drop this packet, next packet will come in correctly // drop this packet, next packet will come in correctly
return; return;
} }
// substraction with overflow u_int32_t rollover = m_rollover;
// compare unsigned to detect rollovers
if (seq < m_seq)
rollover++;
u_int64_t seq48 = rollover;
seq48 = (seq48 << 16) | seq;
// if some security data is present authenticate the packet now
if (secPtr && !rtpCheckIntegrity((const unsigned char*)data,len + padding + 12,secPtr + m_mkiLen,ss,seq48))
return;
// substraction with overflow to compute sequence difference
int16_t ds = seq - m_seq; int16_t ds = seq - m_seq;
if (ds != 1) if (ds != 1)
m_seqLost++; m_seqLost++;
// check if we received duplicate or delayed packet if (ds == 0)
return;
// check if we received a packet too much out of sequence
// be much more tolerant when authenticating as we cannot resync // be much more tolerant when authenticating as we cannot resync
if ((ds <= 0) || ((ds > SEQ_DESYNC_COUNT) && !secPtr)) { if ((ds <= -SEQ_DESYNC_COUNT) || ((ds > SEQ_DESYNC_COUNT) && !secPtr)) {
m_ioLostPkt++; m_ioLostPkt++;
if (ds && !secPtr) { if (!secPtr) {
// try to resync sequence unless we need to authenticate // try to resync sequence unless we need to authenticate
if (m_seqCount++) { if (m_seqCount++) {
if (seq == ++m_seqSync) { if (seq == ++m_seqSync) {
@ -216,6 +236,8 @@ void RTPReceiver::rtpData(const void* data, int len)
m_seqCount = 0; m_seqCount = 0;
m_warn = true; m_warn = true;
m_syncLost++; m_syncLost++;
if (m_dejitter)
m_dejitter->clear();
// drop this packet, next packet will come in correctly // drop this packet, next packet will come in correctly
return; return;
} }
@ -226,38 +248,32 @@ void RTPReceiver::rtpData(const void* data, int len)
else else
m_seqSync = seq; m_seqSync = seq;
} }
if (m_warn && ds) { if (m_warn) {
m_warn = false; m_warn = false;
Debug(DebugWarn,"RTP received SEQ %u while current is %u [%p]",seq,m_seq,this); Debug(DebugWarn,"RTP received SEQ %u while current is %u [%p]",seq,m_seq,this);
} }
return; return;
} }
if (ds > 1) if (!rtpDecipher(const_cast<unsigned char*>(pc),len + padding,secPtr,ss,seq48))
m_ioLostPkt += (ds - 1);
u_int32_t rollover = m_rollover;
// this time compare unsigned to detect rollovers
if (seq < m_seq)
rollover++;
u_int64_t seq48 = rollover;
seq48 = (seq48 << 16) | seq;
// if some security data is present authenticate the packet now
if (secPtr && !rtpCheckIntegrity((const unsigned char*)data,len + padding + 12,secPtr + m_mkiLen,ss,seq48))
return; return;
// keep track of the last valid sequence number and timestamp we have seen
m_seq = seq;
m_rollover = rollover;
m_tsLast = ts - m_ts; m_tsLast = ts - m_ts;
m_seqCount = 0; m_seqCount = 0;
m_ioPackets++; m_ioPackets++;
m_ioOctets += len; m_ioOctets += len;
// keep track of the last valid sequence number and timestamp we have seen
m_seq = seq;
m_rollover = rollover;
if (!len) if (m_dejitter) {
pc = 0; if (!m_dejitter->rtpRecv(marker,typ,m_tsLast,pc,len))
if (rtpDecipher(const_cast<unsigned char*>(pc),len + padding,secPtr,ss,seq48)) m_ioLostPkt++;
return;
}
if (ds > 1)
m_ioLostPkt += (ds - 1);
if (ds >= 1)
rtpRecv(marker,typ,m_tsLast,pc,len); rtpRecv(marker,typ,m_tsLast,pc,len);
} }
@ -274,15 +290,8 @@ bool RTPReceiver::rtpRecv(bool marker, int payload, unsigned int timestamp, cons
if (payload == silencePayload()) if (payload == silencePayload())
return decodeSilence(marker,timestamp,data,len); return decodeSilence(marker,timestamp,data,len);
finishEvent(timestamp); finishEvent(timestamp);
if (payload == dataPayload()) { if (payload == dataPayload())
#if 0 return rtpRecvData(marker,timestamp,data,len);
// dejitter is broken - don't use it
if (m_dejitter)
return m_dejitter->rtpRecvData(marker,timestamp,data,len);
else
#endif
return rtpRecvData(marker,timestamp,data,len);
}
return false; return false;
} }

View File

@ -360,14 +360,20 @@ public:
/** /**
* Process and store one RTP data packet * Process and store one RTP data packet
* @param marker True if the marker bit is set in data packet * @param marker True if the marker bit is set in data packet
* @param payload Payload number
* @param timestamp Sampling instant of the packet data * @param timestamp Sampling instant of the packet data
* @param data Pointer to data block to process * @param data Pointer to data block to process
* @param len Length of the data block in bytes * @param len Length of the data block in bytes
* @return True if data was handled * @return True if the data packet was queued
*/ */
virtual bool rtpRecvData(bool marker, unsigned int timestamp, virtual bool rtpRecv(bool marker, int payload, unsigned int timestamp,
const void* data, int len); const void* data, int len);
/**
* Clear the delayed packets queue and all variables
*/
void clear();
protected: protected:
/** /**
* Method called periodically to keep the data flowing * Method called periodically to keep the data flowing
@ -378,12 +384,13 @@ protected:
private: private:
ObjList m_packets; ObjList m_packets;
RTPReceiver* m_receiver; RTPReceiver* m_receiver;
unsigned int m_mindelay; unsigned int m_minDelay;
unsigned int m_maxdelay; unsigned int m_maxDelay;
unsigned int m_headStamp; unsigned int m_headStamp;
unsigned int m_tailStamp; unsigned int m_tailStamp;
u_int64_t m_headTime; u_int64_t m_headTime;
u_int64_t m_tailTime; u_int64_t m_sampRate;
unsigned char m_fastRate;
}; };
/** /**
@ -586,6 +593,7 @@ private:
class YRTP_API RTPReceiver : public RTPBaseIO class YRTP_API RTPReceiver : public RTPBaseIO
{ {
friend class RTPSession; friend class RTPSession;
friend class RTPDejitter;
public: public:
/** /**
* Constructor * Constructor

View File

@ -106,8 +106,8 @@ static int s_interval= 0;
static int s_timeout = 0; static int s_timeout = 0;
static int s_udptlTimeout = 0; static int s_udptlTimeout = 0;
static int s_minjitter = 0; static int s_minJitter = 0;
static int s_maxjitter = 0; static int s_maxJitter = 0;
class YRTPSource; class YRTPSource;
class YRTPConsumer; class YRTPConsumer;
@ -659,8 +659,6 @@ bool YRTPWrapper::startRTP(const char* raddr, unsigned int rport, Message& msg)
} }
Debug(&splugin,DebugInfo,"RTP starting format '%s' payload %d [%p]",format,payload,this); Debug(&splugin,DebugInfo,"RTP starting format '%s' payload %d [%p]",format,payload,this);
int minJitter = msg.getIntValue(YSTRING("minjitter"),s_minjitter);
int maxJitter = msg.getIntValue(YSTRING("maxjitter"),s_maxjitter);
if (!setRemote(raddr,rport,msg)) if (!setRemote(raddr,rport,msg))
return false; return false;
@ -726,8 +724,13 @@ bool YRTPWrapper::startRTP(const char* raddr, unsigned int rport, Message& msg)
} }
setTimeout(msg,s_timeout); setTimeout(msg,s_timeout);
m_rtp->setReports(msg.getIntValue(YSTRING("rtcp_interval"),s_interval)); m_rtp->setReports(msg.getIntValue(YSTRING("rtcp_interval"),s_interval));
// if (maxJitter > 0) // dejittering is only meaningful for audio
// m_rtp->setDejitter(minJitter*1000,maxJitter*1000); if (isAudio()){
int minJitter = msg.getIntValue(YSTRING("minjitter"),s_minJitter);
int maxJitter = msg.getIntValue(YSTRING("maxjitter"),s_maxJitter);
if (minJitter >= 0 && maxJitter > 0)
m_rtp->setDejitter(minJitter*1000,maxJitter*1000);
}
m_bufsize = s_bufsize; m_bufsize = s_bufsize;
return true; return true;
} }
@ -1830,8 +1833,8 @@ void YRTPPlugin::initialize()
s_minport = cfg.getIntValue("general","minport",MIN_PORT); s_minport = cfg.getIntValue("general","minport",MIN_PORT);
s_maxport = cfg.getIntValue("general","maxport",MAX_PORT); s_maxport = cfg.getIntValue("general","maxport",MAX_PORT);
s_bufsize = cfg.getIntValue("general","buffer",BUF_SIZE); s_bufsize = cfg.getIntValue("general","buffer",BUF_SIZE);
s_minjitter = cfg.getIntValue("general","minjitter"); s_minJitter = cfg.getIntValue("general","minjitter",50);
s_maxjitter = cfg.getIntValue("general","maxjitter"); s_maxJitter = cfg.getIntValue("general","maxjitter",Engine::clientMode() ? 120 : 0);
s_tos = cfg.getIntValue("general","tos",dict_tos); s_tos = cfg.getIntValue("general","tos",dict_tos);
s_localip = cfg.getValue("general","localip"); s_localip = cfg.getValue("general","localip");
s_autoaddr = cfg.getBoolValue("general","autoaddr",true); s_autoaddr = cfg.getBoolValue("general","autoaddr",true);