From a5385c2f4746cff24d4158ec9b743fb9cea6c441 Mon Sep 17 00:00:00 2001 From: marian Date: Mon, 3 Jun 2013 07:50:14 +0000 Subject: [PATCH] Fixed timestamps for sent media: send timestamps using media source timestamp, sync audio timestamps with transaction timestamp. git-svn-id: http://yate.null.ro/svn/yate/trunk@5511 acf43c95-373e-0410-b603-e72c3f656dc1 --- conf.d/yiaxchan.conf.sample | 33 +++++ libs/yiax/engine.cpp | 85 +++++++++++++ libs/yiax/frame.cpp | 37 +++++- libs/yiax/transaction.cpp | 236 ++++++++++++++++++++++++++---------- libs/yiax/yateiax.h | 149 ++++++++++++++++++++--- modules/yiaxchan.cpp | 9 +- 6 files changed, 458 insertions(+), 91 deletions(-) diff --git a/conf.d/yiaxchan.conf.sample b/conf.d/yiaxchan.conf.sample index 3fe40d32..f7f7f202 100644 --- a/conf.d/yiaxchan.conf.sample +++ b/conf.d/yiaxchan.conf.sample @@ -30,6 +30,39 @@ ; Defaults to yes ;calltoken_rejectmissing=yes +; adjust_ts_out_threshold: integer: The difference, in milliseconds, between sent audio data +; timestamp and transaction timestamp at which audio data timestamp will be adjusted +; Its value will be rouded up to a multiple of 10 +; This value is applied on reload for new calls only +; It can be overridden from routing +; Defaults to 120 +; Allowed interval: 20 .. 300 +;adjust_ts_out_threshold=120 + +; adjust_ts_out_over: integer: Interval, in milliseconds, to adjust sent audio data +; timestamp on data overrun (the sender transmits data on a rate greater then expected) +; When applied the packets will be dropped until data timestamp will be at least at the +; value of last sent packet timestamp +; NOTE: Choose values greater the packet time to drop more packets at a time. +; Lower values will drop less packets in a row but this will happen more frequently +; Its value will be rouded up to a multiple of 10 +; This value is applied on reload for new calls only +; It can be overridden from routing +; It can't be greater then adjust_ts_out_threshold +; Defaults to 120 +; Allowed interval: 10 .. adjust_ts_out_threshold +;adjust_ts_out_over=120 + +; adjust_ts_out_under: integer: Interval, in milliseconds, to adjust sent audio data +; timestamp on data underrun (the sender transmits data on a rate less then expected) +; Its value will be rouded up to a multiple of 10 +; This value is applied on reload for new calls only +; It can be overridden from routing +; It can't be greater then 2 * adjust_ts_out_threshold - 1 +; Defaults to 60 +; Allowed interval: 10 .. 2 * adjust_ts_out_threshold - 1 +;adjust_ts_out_under=60 + ; tos: keyword: Type Of Service to set in outgoing UDP packets ; numeric TOS value or: lowdelay, throughput, reliability, mincost ;tos=0 diff --git a/libs/yiax/engine.cpp b/libs/yiax/engine.cpp index 1114df1c..73a524e2 100644 --- a/libs/yiax/engine.cpp +++ b/libs/yiax/engine.cpp @@ -37,6 +37,12 @@ using namespace TelEngine; // Minimum value for local call numbers #define IAX2_MIN_CALLNO 2 +// Outgoing data adjust timestamp defaults +#define IAX2_ADJUSTTSOUT_THRES 120 +#define IAX2_ADJUSTTSOUT_OVER 120 +#define IAX2_ADJUSTTSOUT_UNDER 60 + + // Build an MD5 digest from secret, address, integer value and engine run id // MD5(addr.host() + secret + addr.port() + t) static void buildSecretDigest(String& buf, const String& secret, unsigned int t, @@ -70,6 +76,9 @@ IAXEngine::IAXEngine(const char* iface, int port, u_int16_t transListCount, u_in m_format(format), m_formatVideo(0), m_capability(capab), + m_adjustTsOutThreshold(IAX2_ADJUSTTSOUT_THRES), + m_adjustTsOutOverrun(IAX2_ADJUSTTSOUT_OVER), + m_adjustTsOutUnderrun(IAX2_ADJUSTTSOUT_UNDER), m_mutexTrunk(true,"IAXEngine::Trunk"), m_trunkSendInterval(trunkSendInterval) { @@ -312,6 +321,81 @@ bool IAXEngine::process() return ok; } +static inline void roundUp10(unsigned int& value) +{ + unsigned int rest = value % 10; + if (rest) + value += 10 - rest; +} + +// Initialize outgoing data timestamp adjust values +void IAXEngine::initOutDataAdjust(const NamedList& params, IAXTransaction* tr) +{ + const String* thresS = 0; + const String* overS = 0; + const String* underS = 0; + NamedIterator iter(params); + for (const NamedString* ns = 0; 0 != (ns = iter.get());) { + if (ns->name() == YSTRING("adjust_ts_out_threshold")) + thresS = ns; + else if (ns->name() == YSTRING("adjust_ts_out_over")) + overS = ns; + else if (ns->name() == YSTRING("adjust_ts_out_under")) + underS = ns; + } + // No need to set transaction's data if no parameter found + if (tr && !(thresS || overS || underS)) + return; + Lock lck(tr ? (Mutex*)tr : (Mutex*)this); + unsigned int thresDef = IAX2_ADJUSTTSOUT_THRES; + unsigned int overDef = IAX2_ADJUSTTSOUT_OVER; + unsigned int underDef = IAX2_ADJUSTTSOUT_UNDER; + if (tr) { + thresDef = tr->m_adjustTsOutThreshold; + overDef = tr->m_adjustTsOutOverrun; + underDef = tr->m_adjustTsOutUnderrun; + } + unsigned int thres = thresS ? thresS->toInteger(thresDef,0,20,300) : thresDef; + unsigned int over = overS ? overS->toInteger(overDef,0,10) : overDef; + unsigned int under = underS ? underS->toInteger(underDef,0,10) : underDef; + bool adjusted = false; + // Round down to multiple of 10 + roundUp10(thres); + roundUp10(over); + roundUp10(under); + // Overrun must not be greater then threshold + if (over > thres) { + over = thres; + adjusted = true; + } + // Underrun must be less then 2 * threshold + unsigned int doubleThres = 2 * thres; + if (under >= doubleThres) { + under = doubleThres - 10; + adjusted = true; + } + if (tr) { + tr->m_adjustTsOutThreshold = thres; + tr->m_adjustTsOutOverrun = over; + tr->m_adjustTsOutUnderrun = under; + Debug(this,DebugAll, + "Transaction(%u,%u) adjust ts out set to thres=%u over=%u under=%u [%p]", + tr->localCallNo(),tr->remoteCallNo(),thres,over,under,tr); + return; + } + m_adjustTsOutThreshold = thres; + m_adjustTsOutOverrun = over; + m_adjustTsOutUnderrun = under; + if (adjusted) + Debug(this,DebugConf, + "Adjust ts out set to thres=%u over=%u under=%u from thres=%s over=%s under=%s", + thres,over,under,TelEngine::c_safe(thresS), + TelEngine::c_safe(overS),TelEngine::c_safe(underS)); + else + Debug(this,DebugAll,"Adjust ts out set to thres=%u over=%u under=%u", + thres,over,under); +} + // (Re)Initialize the engine void IAXEngine::initialize(const NamedList& params) { @@ -324,6 +408,7 @@ void IAXEngine::initialize(const NamedList& params) m_showCallTokenFailures = params.getBoolValue("calltoken_printfailure"); m_rejectMissingCallToken = params.getBoolValue("calltoken_rejectmissing",true); m_printMsg = params.getBoolValue("printmsg",true); + initOutDataAdjust(params); } void IAXEngine::readSocket(SocketAddr& addr) diff --git a/libs/yiax/frame.cpp b/libs/yiax/frame.cpp index f60e5ca5..47a1bcfe 100644 --- a/libs/yiax/frame.cpp +++ b/libs/yiax/frame.cpp @@ -640,6 +640,37 @@ void IAXAuthMethod::authList(String& dest, u_int16_t auth, char sep) } } + +/* + * IAXFormatDesc + */ +// Set the format +void IAXFormatDesc::setFormat(u_int32_t fmt, int type) +{ + m_format = IAXFormat::mask(fmt,type); + if (!m_format) { + m_multiplier = 1; + return; + } + if (type == IAXFormat::Audio) { + switch (m_format) { + case IAXFormat::G722: + // 16kHz samplig rate + m_multiplier = 16; + break; + default: + // Assume 8kHz sampling rate + m_multiplier = 8; + } + } + else if (type == IAXFormat::Video) + // Assume 90kHz sampling rate for video + m_multiplier = 90; + else + m_multiplier = 1; +} + + /* * IAXFormat */ @@ -680,11 +711,11 @@ const String IAXFormat::s_typesList[IAXFormat::TypeCount] = { "audio", "video", void IAXFormat::set(u_int32_t* fmt, u_int32_t* fmtIn, u_int32_t* fmtOut) { if (fmt) - m_format = mask(*fmt,m_type); + m_format.setFormat(*fmt,m_type); if (fmtIn) - m_formatIn = mask(*fmtIn,m_type); + m_formatIn.setFormat(*fmtIn,m_type); if (fmtOut) - m_formatOut = mask(*fmtOut,m_type); + m_formatOut.setFormat(*fmtOut,m_type); } void IAXFormat::formatList(String& dest, u_int32_t formats, const TokenDict* dict, diff --git a/libs/yiax/transaction.cpp b/libs/yiax/transaction.cpp index 8de37b1c..22d9c237 100644 --- a/libs/yiax/transaction.cpp +++ b/libs/yiax/transaction.cpp @@ -38,10 +38,11 @@ unsigned char IAXTransaction::m_maxInFrames = 100; // Print statistics void IAXMediaData::print(String& buf) { - Lock lck(this); + Lock2 lck(m_inMutex,m_outMutex); buf << "PS=" << m_sent << ",OS=" << m_sentBytes; buf << ",PR=" << m_recv << ",OR=" << m_recvBytes; buf << ",PL=" << m_ooPackets << ",OL=" << m_ooBytes; + buf << ",PD=" << m_dropOut << ",OD=" << m_dropOutBytes; } @@ -76,6 +77,9 @@ IAXTransaction::IAXTransaction(IAXEngine* engine, IAXFullFrame* frame, u_int16_t m_expire(60), m_format(IAXFormat::Audio), m_formatVideo(IAXFormat::Video), m_capability(0), m_callToken(false), + m_adjustTsOutThreshold(0), + m_adjustTsOutOverrun(0), + m_adjustTsOutUnderrun(0), m_trunkFrame(0), m_trunkInOffsetTimeMs(0), m_trunkInLastTs(0), m_warnTrunkInTimestamp(true) { @@ -103,6 +107,8 @@ IAXTransaction::IAXTransaction(IAXEngine* engine, IAXFullFrame* frame, u_int16_t localCallNo(),remoteCallNo(),frame->subclass(),this); return; } + engine->getOutDataAdjust(m_adjustTsOutThreshold,m_adjustTsOutOverrun, + m_adjustTsOutUnderrun); // Append frame to incoming list Lock lock(this); m_inFrames.append(frame); @@ -140,6 +146,9 @@ IAXTransaction::IAXTransaction(IAXEngine* engine, Type type, u_int16_t lcallno, m_expire(60), m_format(IAXFormat::Audio), m_formatVideo(IAXFormat::Video), m_capability(0), m_callToken(false), + m_adjustTsOutThreshold(0), + m_adjustTsOutOverrun(0), + m_adjustTsOutUnderrun(0), m_trunkFrame(0), m_trunkInOffsetTimeMs(0), m_trunkInLastTs(0), m_warnTrunkInTimestamp(true) { @@ -189,6 +198,8 @@ IAXTransaction::IAXTransaction(IAXEngine* engine, Type type, u_int16_t lcallno, m_type = Incorrect; return; } + engine->getOutDataAdjust(m_adjustTsOutThreshold,m_adjustTsOutOverrun, + m_adjustTsOutUnderrun); postFrameIes(IAXFrame::IAX,frametype,ies); changeState(NewLocalInvite); } @@ -219,26 +230,6 @@ IAXTransaction* IAXTransaction::factoryOut(IAXEngine* engine, Type type, u_int16 return 0; } -// Retrieve the media of a given type -IAXFormat* IAXTransaction::getFormat(int type) -{ - if (type == IAXFormat::Audio) - return &m_format; - if (type == IAXFormat::Video) - return &m_formatVideo; - return 0; -} - -// Retrieve the media data for a given type -IAXMediaData* IAXTransaction::getData(int type) -{ - if (type == IAXFormat::Audio) - return &m_dataAudio; - if (type == IAXFormat::Video) - return &m_dataVideo; - return 0; -} - IAXTransaction* IAXTransaction::processFrame(IAXFrame* frame) { if (!frame) @@ -325,8 +316,9 @@ IAXTransaction* IAXTransaction::processMedia(DataBlock& data, u_int32_t tStamp, IAXFormat::typeName(type)); return 0; } - Lock lck(d); - if (!fmt->in()) { + Lock lck(d->m_inMutex); + const IAXFormatDesc& desc = fmt->formatDesc(true); + if (!desc.format()) { if (d->m_showInNoFmt) { Debug(m_engine,DebugInfo, "Transaction(%u,%u) received %s data without format [%p]", @@ -335,6 +327,11 @@ IAXTransaction* IAXTransaction::processMedia(DataBlock& data, u_int32_t tStamp, } return 0; } + if (!d->m_startedIn) { + d->m_startedIn = true; + Debug(m_engine,DebugAll,"Transaction(%u,%u) started incoming media '%s' [%p]", + localCallNo(),remoteCallNo(),fmt->typeName(),this); + } d->m_showInNoFmt = true; d->m_recv++; d->m_recvBytes += data.length(); @@ -379,7 +376,7 @@ IAXTransaction* IAXTransaction::processMedia(DataBlock& data, u_int32_t tStamp, "Transaction(%u,%u) forwarding %u %s data mark=%u ts=%u [%p]", localCallNo(),remoteCallNo(),data.length(),fmt->typeName(), mark,tStamp,this); - m_engine->processMedia(this,data,tStamp,type,mark); + m_engine->processMedia(this,data,tStamp * desc.multiplier(),type,mark); return 0; } d->m_ooPackets++; @@ -391,8 +388,8 @@ IAXTransaction* IAXTransaction::processMedia(DataBlock& data, u_int32_t tStamp, return 0; } -unsigned int IAXTransaction::sendMedia(const DataBlock& data, u_int32_t format, - int type, bool mark) +unsigned int IAXTransaction::sendMedia(const DataBlock& data, unsigned int tStamp, + u_int32_t format, int type, bool mark) { if (!data.length()) return 0; @@ -406,15 +403,94 @@ unsigned int IAXTransaction::sendMedia(const DataBlock& data, u_int32_t format, IAXFormat::typeName(type)); return 0; } - d->m_sent++; - d->m_sentBytes += data.length(); - u_int32_t ts = (u_int32_t)timeStamp(); - // Avoid sending the same timestamp twice for non video - if (type != IAXFormat::Video && d->m_lastOut && ts == d->m_lastOut) - ts++; + Lock lck(d->m_outMutex); + u_int64_t msecNow = Time::msecNow(); + u_int32_t transTs = (u_int32_t)(msecNow - m_timeStamp); + // Check format change + bool fmtChanged = (fmt->out() != format); + if (fmtChanged) { + Debug(m_engine,DebugNote, + "Transaction(%u,%u). Outgoing %s format changed %u --> %u [%p]", + localCallNo(),remoteCallNo(),fmt->typeName(),fmt->out(),format,this); + fmt->set(0,0,&format); + } + const IAXFormatDesc& desc = fmt->formatDesc(false); + u_int32_t ts = 0; + unsigned int delta = 0; + if (d->m_startedOut) { + if (desc.multiplier() > 1) { + if (d->m_outFirstSrcTs > tStamp) { + if (d->m_showOutOldTs) { + Debug(m_engine,DebugNote, + "Transaction(%u,%u) dropping outgoing %s %u bytes with old tStamp=%u (first=%u) [%p]", + localCallNo(),remoteCallNo(),fmt->typeName(),data.length(), + tStamp,d->m_outFirstSrcTs,this); + d->m_showOutOldTs = false; + } + d->dropOut(data.length()); + return 0; + } + d->m_showOutOldTs = true; + unsigned int srcTsDelta = (tStamp - d->m_outFirstSrcTs) / desc.multiplier(); + ts = d->m_outStartTransTs + srcTsDelta; + // Audio + if (type == IAXFormat::Audio) { + if (ts > transTs) { + // Voice timestamp is past transaction timestamp + // Packets arrived on intervals shorter then expected + // Data overrun: decrease timestamp + delta = ts - transTs; + if (delta >= m_adjustTsOutThreshold) { + d->dropOut(data.length()); + d->m_outStartTransTs -= m_adjustTsOutOverrun; + DDebug(m_engine,DebugNote, + "Transaction(%u,%u) voice overrun ts=%u transTs=%u [%p]", + localCallNo(),remoteCallNo(),ts,transTs,this); + return 0; + } + } + else if (ts < transTs) { + // Voice timestamp is behind transaction timestamp + // Packets arrived on intervals longer then expected + // Data underrun: increase timestamp + delta = transTs - ts; + if (delta >= m_adjustTsOutThreshold) { + d->m_outStartTransTs += m_adjustTsOutUnderrun; + DDebug(m_engine,DebugInfo, + "Transaction(%u,%u) voice underrun ts=%u transTs=%u [%p]", + localCallNo(),remoteCallNo(),ts,transTs,this); + } + } + // Avoid sending the same timestamp twice + if (ts == d->m_lastOut) + ts++; + } + } + else { + ts = transTs; + // Audio: avoid sending the same timestamp twice + if (type == IAXFormat::Audio && ts == d->m_lastOut) + ts++; + } + } + else { + d->m_startedOut = true; + d->m_outStartTransTs = transTs; + d->m_outFirstSrcTs = tStamp; + ts = d->m_outStartTransTs; + Debug(m_engine,DebugAll,"Transaction(%u,%u) started outgoing media '%s' [%p]", + localCallNo(),remoteCallNo(),fmt->typeName(),this); + } + if (ts < d->m_lastOut) { + d->dropOut(data.length()); + DDebug(m_engine,DebugNote, + "Transaction(%u,%u) %s ts %u less then last sent %u [%p]", + localCallNo(),remoteCallNo(),fmt->typeName(),ts,d->m_lastOut,this); + return 0; + } // Format changed or timestamp wrapped around // Send a full frame - bool fullFrame = (fmt->out() != format) || !d->m_lastOut; + bool fullFrame = fmtChanged || !d->m_lastOut; if (!fullFrame) { // Voice: timestamp is lowest 16 bits // Video: timestamp is lowest 15 bits @@ -425,20 +501,12 @@ unsigned int IAXTransaction::sendMedia(const DataBlock& data, u_int32_t format, // we had a media gap greater then mask fullFrame = ((ts & mask) < (d->m_lastOut & mask)) || ((ts - d->m_lastOut) > mask); } - if (fullFrame) { - if (fmt->out() != format) { - Debug(m_engine,DebugNote, - "Transaction(%u,%u). Outgoing %s format changed %u --> %u [%p]", - localCallNo(),remoteCallNo(),fmt->typeName(),fmt->out(),format,this); - fmt->set(0,0,&format); - } #ifdef DEBUG - else - Debug(m_engine,DebugInfo, - "Transaction(%u,%u). Sending full frame for media '%s': ts=%u last=%u [%p]", - localCallNo(),remoteCallNo(),fmt->typeName(),ts,d->m_lastOut,this); + if (fullFrame && !fmtChanged) + Debug(m_engine,DebugInfo, + "Transaction(%u,%u). Sending full frame for media '%s': ts=%u last=%u [%p]", + localCallNo(),remoteCallNo(),fmt->typeName(),ts,d->m_lastOut,this); #endif - } d->m_lastOut = ts; unsigned int sent = 0; if (type == IAXFormat::Audio) { @@ -483,10 +551,12 @@ unsigned int IAXTransaction::sendMedia(const DataBlock& data, u_int32_t format, else Debug(m_engine,DebugStub, "IAXTransaction::sendMedia() not implemented for type '%s'",fmt->typeName()); + d->m_sent++; + d->m_sentBytes += sent; XDebug(m_engine,sent == data.length() ? DebugAll : DebugNote, - "Transaction(%u,%u) sent %u/%u media=%s mark=%u ts=%u [%p]", + "Transaction(%u,%u) sent %u/%u media=%s mark=%u ts=%u tStamp=%u transTs=%u [%p]", localCallNo(),remoteCallNo(),sent,data.length(), - fmt->typeName(),mark,ts,this); + fmt->typeName(),mark,ts,tStamp,transTs,this); return sent; } @@ -515,7 +585,7 @@ IAXEvent* IAXTransaction::getEvent(u_int64_t time) } // Time to Ping remote peer ? if (time > m_timeToNextPing && state() != Terminating) { - postFrame(IAXFrame::IAX,IAXControl::Ping,0,0,(u_int32_t)timeStamp(),false); + postFrame(IAXFrame::IAX,IAXControl::Ping,0,0,0,false); m_timeToNextPing = time + m_pingInterval; } // Process outgoing frames @@ -884,16 +954,10 @@ bool IAXTransaction::updateTrunkRecvTs(u_int32_t& frameTs, u_int32_t ts, u_int64 void IAXTransaction::print(bool printStats, bool printFrames, const char* location) { if (m_engine && !m_engine->debugAt(DebugAll)) - return; - String stats; - if (printStats && m_type == New) { - stats << " audio: "; - m_dataAudio.print(stats); - stats << " video: "; - m_dataVideo.print(stats); - } + printFrames = false; String buf; if (printFrames) { + buf << "\r\n-----"; SocketAddr addr; ObjList* l; buf << "\r\nOutgoing frames: " << m_outFrames.count(); @@ -906,12 +970,44 @@ void IAXTransaction::print(bool printStats, bool printFrames, const char* locati IAXFullFrame* frame = static_cast(l->get()); frame->toString(buf,addr,remoteAddr(),true); } + buf << "\r\n-----"; } - Debug(m_engine,DebugAll, - "Transaction(%u,%u) %s remote=%s:%u type=%u state=%u timestamp=" FMT64U "%s [%p]%s%s%s", + if (m_type != New) { + Debug(m_engine,DebugAll, + "Transaction(%u,%u) %s remote=%s:%d type=%u state=%u timestamp=" FMT64U " [%p]%s", + localCallNo(),remoteCallNo(),location,remoteAddr().host().c_str(),remoteAddr().port(), + type(),state(),(u_int64_t)timeStamp(),this,buf.safe()); + return; + } + String stats; + int level = DebugAll; + if (printStats) { + stats << " audio: "; + m_dataAudio.print(stats); + if (m_formatVideo.format()) { + stats << " video: "; + m_dataVideo.print(stats); + } + } + if (m_dataAudio.m_dropOut) { + Lock lck(m_dataAudio.m_outMutex); + unsigned int total = m_dataAudio.m_dropOut + m_dataAudio.m_sent; + float percent = (float)m_dataAudio.m_dropOut / (float)total * 100; + if (percent > 0.5) { + if (percent < 3) + level = DebugInfo; + else if (percent < 5) + level = DebugNote; + else + level = DebugMild; + } + if (!printStats) + stats << " dropped audio packets=" << m_dataAudio.m_dropOut << "/" << total; + } + Debug(m_engine,level, + "Transaction(%u,%u) %s remote=%s:%d type=%u state=%u timestamp=" FMT64U "%s [%p]%s", localCallNo(),remoteCallNo(),location,remoteAddr().host().c_str(),remoteAddr().port(), - type(),state(),(u_int64_t)timeStamp(),stats.safe(),this, - buf ? "\r\n-----" : "",buf.safe(),buf ? "\r\n-----" : ""); + type(),state(),(u_int64_t)timeStamp(),stats.safe(),this,buf.safe()); } // Cleanup @@ -1056,7 +1152,8 @@ void IAXTransaction::postFrame(IAXFrame::Type type, u_int32_t subclass, void* da if (state() == Terminated) return; // Pong and LagRp don't need timestamp to be adjusted - if (type != IAXFrame::IAX || + // Don't adjust for video + if ((type != IAXFrame::IAX && type == IAXFrame::Video) || (subclass != IAXControl::Pong && subclass != IAXControl::LagRp)) adjustTStamp(tStamp); IAXFrameOut* frame = new IAXFrameOut(type,subclass,m_lCallNo,m_rCallNo,m_oSeqNo,m_iSeqNo,tStamp, @@ -1790,14 +1887,19 @@ void IAXTransaction::eventTerminated(IAXEvent* event) void IAXTransaction::adjustTStamp(u_int32_t& tStamp) { - if (!tStamp) + if (!tStamp) { tStamp = (u_int32_t)timeStamp(); - if (m_lastFullFrameOut) { - // adjust timestamp to be different from the last sent - int32_t delta = tStamp - m_lastFullFrameOut; - if (delta <= 0) - tStamp = m_lastFullFrameOut + 1; + // Make sure we don't send old timestamp + IAXMediaData* d = getData(IAXFormat::Audio); + if (d) { + Lock lck(d->m_outMutex); + if (tStamp <= d->m_lastOut) + tStamp = d->m_lastOut + 1; + } } + // Adjust timestamp to be different from the last sent + if (tStamp <= m_lastFullFrameOut) + tStamp = m_lastFullFrameOut + 1; m_lastFullFrameOut = tStamp; } diff --git a/libs/yiax/yateiax.h b/libs/yiax/yateiax.h index 7bbf2315..92e1d5a5 100644 --- a/libs/yiax/yateiax.h +++ b/libs/yiax/yateiax.h @@ -541,6 +541,47 @@ public: static TokenDict s_texts[]; }; + +/** + * This class holds IAX format description + * @short IAX format description + */ +class YIAX_API IAXFormatDesc +{ +public: + /** + * Constructor + */ + inline IAXFormatDesc() + : m_format(0), m_multiplier(1) + {} + + /** + * Get the format + * @return The format + */ + inline u_int32_t format() const + { return m_format; } + + /** + * Get the format multiplier used to translate timestamps + * @return The format multiplier (always greater then 0) + */ + inline unsigned int multiplier() const + { return m_multiplier; } + + /** + * Set the format + * @param fmt The format + * @param type Format type as IAXFormat::Media enumeration + */ + void setFormat(u_int32_t fmt, int type); + +protected: + u_int32_t m_format; // The format + unsigned int m_multiplier; // Format multiplier derived from sampling rate +}; + /** * This class holds the enumeration values for audio and video formats * @short Wrapper class for audio and video formats @@ -593,7 +634,7 @@ public: * @param type Media type */ inline IAXFormat(int type = Audio) - : m_type(type), m_format(0), m_formatIn(0), m_formatOut(0) + : m_type(type) {} /** @@ -608,28 +649,36 @@ public: * @return The format */ inline u_int32_t format() const - { return m_format; } + { return m_format.format(); } /** * Get the incoming format * @return The incoming format */ inline u_int32_t in() const - { return m_formatIn; } + { return m_formatIn.format(); } /** * Get the outgoing format * @return The outgoing format */ inline u_int32_t out() const - { return m_formatOut; } + { return m_formatOut.format(); } /** + * Get the incoming or outgoing format description + * @param in True to retrieve the incoming format, false to retrieve the outgoing one + * @return Requested format desc + */ + inline const IAXFormatDesc& formatDesc(bool in) const + { return in ? m_formatIn : m_formatOut; } + + /** * Get the text associated with the format * @return Format name */ inline const char* formatName() const - { return formatName(m_format); } + { return formatName(format()); } /** * Get the text associated with the media type @@ -746,9 +795,9 @@ public: protected: int m_type; - u_int32_t m_format; - u_int32_t m_formatIn; - u_int32_t m_formatOut; + IAXFormatDesc m_format; + IAXFormatDesc m_formatIn; + IAXFormatDesc m_formatOut; }; /** @@ -1346,10 +1395,10 @@ private: /** * This class holds data used by transaction to sync media. - * The mutex is not reentrant + * The mutexes are not reentrant * @short IAX2 transaction media data */ -class YIAX_API IAXMediaData : public Mutex +class YIAX_API IAXMediaData { friend class IAXTransaction; public: @@ -1357,12 +1406,27 @@ public: * Constructor */ inline IAXMediaData() - : Mutex(true,"IAXTransaction::InMedia"), + : m_inMutex(false,"IAXTransaction::InMedia"), + m_outMutex(false,"IAXTransaction::OutMedia"), + m_startedIn(false), m_startedOut(false), + m_outStartTransTs(0), m_outFirstSrcTs(0), m_lastOut(0), m_lastIn(0), m_sent(0), m_sentBytes(0), m_recv(0), m_recvBytes(0), m_ooPackets(0), m_ooBytes(0), - m_showInNoFmt(true) + m_showInNoFmt(true), m_showOutOldTs(true), + m_dropOut(0), m_dropOutBytes(0) {} + /** + * Increase drop out data + * @param len The number of dropped bytes + */ + inline void dropOut(unsigned int len) { + if (len) { + m_dropOut++; + m_dropOutBytes += len; + } + } + /** * Print statistics * @param buf Destination buffer @@ -1370,6 +1434,12 @@ public: void print(String& buf); protected: + Mutex m_inMutex; + Mutex m_outMutex; + bool m_startedIn; // Incoming media started + bool m_startedOut; // Outgoing media started + int m_outStartTransTs; // Transaction timestamp where media send started + unsigned int m_outFirstSrcTs; // First outgoing source packet timestamp as received from source u_int32_t m_lastOut; // Last transmitted mini timestamp u_int32_t m_lastIn; // Last received timestamp unsigned int m_sent; // Packets sent @@ -1379,6 +1449,9 @@ protected: unsigned int m_ooPackets; // Dropped received out of order packets unsigned int m_ooBytes; // Dropped received out of order bytes bool m_showInNoFmt; // Show incoming media arrival without format debug + bool m_showOutOldTs; // Show dropped media out debug message + unsigned int m_dropOut; // The number of dropped outgoing packets + unsigned int m_dropOutBytes; // The number of dropped outgoing bytes }; /** @@ -1565,14 +1638,26 @@ public: * @param type Media type to retrieve * @return IAXFormat pointer or 0 for invalid type */ - IAXFormat* getFormat(int type); + inline IAXFormat* getFormat(int type) { + if (type == IAXFormat::Audio) + return &m_format; + if (type == IAXFormat::Video) + return &m_formatVideo; + return 0; + } /** * Retrieve the media data for a given type * @param type Media type to retrieve * @return IAXMediaData pointer or 0 for invalid type */ - IAXMediaData* getData(int type); + inline IAXMediaData* getData(int type) { + if (type == IAXFormat::Audio) + return &m_dataAudio; + if (type == IAXFormat::Video) + return &m_dataVideo; + return 0; + } /** * Retrieve the media format used during initialization @@ -1636,7 +1721,7 @@ public: /** * Process received media data * @param data Received data - * @param tStamp Mini frame timestamp + * @param tStamp Mini frame timestamp multiplied by format multiplier * @param type Media type * @param full True if received in a full frame * @param mark Mark flag @@ -1648,12 +1733,13 @@ public: /** * Send media data to remote peer. Update the outgoing media format if changed * @param data Data to send + * @param tStamp Data timestamp * @param format Data format * @param type Media type * @param mark Mark flag * @return The number of bytes sent */ - unsigned int sendMedia(const DataBlock& data, u_int32_t format, + unsigned int sendMedia(const DataBlock& data, unsigned int tStamp, u_int32_t format, int type = IAXFormat::Audio, bool mark = false); /** @@ -2243,6 +2329,11 @@ private: IAXFormat m_formatVideo; // Video format u_int32_t m_capability; // Media capability of this transaction bool m_callToken; // Call token supported/expected + unsigned int m_adjustTsOutThreshold; // Adjust outgoing data timestamp threshold + unsigned int m_adjustTsOutOverrun; // Value used to adjust outgoing data timestamp on data + // overrun (incoming data with rate greater then expected) + unsigned int m_adjustTsOutUnderrun; // Value used to adjust outgoing data timestamp on data + // underrun (incoming data with rate less then expected) // Meta trunking IAXMetaTrunkFrame* m_trunkFrame; // Reference to a trunk frame if trunking is enabled for this transaction int64_t m_trunkInOffsetTimeMs; // Offset between transaction start and trunk start @@ -2530,6 +2621,27 @@ public: inline u_int32_t capability() const { return m_capability; } + /** + * Retrieve outgoing data timestamp adjust values + * @param thres Adjust outgoing data timestamp threshold + * @param over Value used to adjust outgoing data timestamp on data overrun + * @param under Value used to adjust outgoing data timestamp on data underrun + */ + inline void getOutDataAdjust(unsigned int& thres, unsigned int& over, + unsigned int& under) const { + thres = m_adjustTsOutThreshold; + over = m_adjustTsOutOverrun; + under = m_adjustTsOutUnderrun; + } + + /** + * Initialize outgoing data timestamp adjust values. + * This method is thread safe + * @param params Parameters list + * @param tr Optional transaction to init, initialize the engine's data if 0 + */ + void initOutDataAdjust(const NamedList& params, IAXTransaction* tr = 0); + /** * (Re)Initialize the engine * @param params Parameter list @@ -2785,6 +2897,11 @@ private: u_int32_t m_format; // The default media format u_int32_t m_formatVideo; // Default video format u_int32_t m_capability; // The media capability + unsigned int m_adjustTsOutThreshold; // Adjust outgoing data timestamp threshold + unsigned int m_adjustTsOutOverrun; // Value used to adjust outgoing data timestamp on data + // overrun (incoming data with rate greater then expected) + unsigned int m_adjustTsOutUnderrun; // Value used to adjust outgoing data timestamp on data + // underrun (incoming data with rate less then expected) // Trunking Mutex m_mutexTrunk; // Mutex for trunk operations ObjList m_trunkList; // Trunk frames list diff --git a/modules/yiaxchan.cpp b/modules/yiaxchan.cpp index 5895d9f3..d8ae2383 100644 --- a/modules/yiaxchan.cpp +++ b/modules/yiaxchan.cpp @@ -1014,13 +1014,10 @@ void YIAXEngine::processMedia(IAXTransaction* transaction, DataBlock& data, if (conn) { DataSource* src = conn->getSourceMedia(type); if (src) { - unsigned long ts = tStamp; - if (IAXFormat::Audio == type) - ts *= 8; // assume 8kHz sampling unsigned long flags = 0; if (mark) flags = DataNode::DataMark; - src->Forward(data,ts,flags); + src->Forward(data,tStamp,flags); } else DDebug(this,DebugAll,"processMedia. No media source"); @@ -1474,6 +1471,7 @@ bool YIAXDriver::msgExecute(Message& msg, String& dest) IAXTransaction* tr = m_iaxEngine->call(addr,params); if (!tr) return false; + tr->getEngine()->initOutDataAdjust(msg,tr); YIAXConnection* conn = new YIAXConnection(m_iaxEngine,tr,&msg,¶ms); conn->initChan(); tr->setUserData(conn); @@ -1660,7 +1658,7 @@ unsigned long YIAXConsumer::Consume(const DataBlock& data, unsigned long tStamp, m_total += data.length(); if (m_connection->transaction()) { bool mark = (flags & DataMark) != 0; - sent = m_connection->transaction()->sendMedia(data,format(),m_type,mark); + sent = m_connection->transaction()->sendMedia(data,tStamp,format(),m_type,mark); } } return sent; @@ -1734,6 +1732,7 @@ void YIAXConnection::callAccept(Message& msg) DDebug(this,DebugCall,"callAccept [%p]",this); m_mutexTrans.lock(); if (m_transaction) { + m_transaction->getEngine()->initOutDataAdjust(msg,m_transaction); u_int32_t codecs = iplugin.getEngine()->capability(); if (msg.getValue("formats")) { u_int32_t ca = IAXFormat::mask(codecs,IAXFormat::Audio);