- improve packet loss reporting + initial DTMF RFC 2833 event reporting

This commit is contained in:
2026-02-25 10:16:24 +03:00
parent 03f662e5ce
commit 06b39dd629
9 changed files with 210 additions and 109 deletions

View File

@@ -173,6 +173,11 @@ int RtpHelper::findPayloadLength(const void* buffer, size_t length)
return static_cast<int>(payloadLen); return static_cast<int>(payloadLen);
} }
std::chrono::microseconds RtpHelper::toMicroseconds(const jrtplib::RTPTime& t)
{
return std::chrono::microseconds(uint64_t(t.GetDouble() * 1000000));
}
// --- RtpDump implementation --- // --- RtpDump implementation ---
std::shared_ptr<jrtplib::RTPPacket> RtpDump::parseRtpData(const uint8_t* data, size_t len) std::shared_ptr<jrtplib::RTPPacket> RtpDump::parseRtpData(const uint8_t* data, size_t len)

View File

@@ -43,6 +43,8 @@ public:
static unsigned findSsrc(const void* buffer, size_t length); static unsigned findSsrc(const void* buffer, size_t length);
static void setSsrc(void* buffer, size_t length, uint32_t ssrc); static void setSsrc(void* buffer, size_t length, uint32_t ssrc);
static int findPayloadLength(const void* buffer, size_t length); static int findPayloadLength(const void* buffer, size_t length);
static std::chrono::microseconds toMicroseconds(const jrtplib::RTPTime& t);
}; };
/** /**

View File

@@ -148,7 +148,7 @@ static AmrPayload parseAmrPayload(AmrPayloadInfo& input, size_t& cngCounter)
// if (input.mWideband && f.mMode == 0xFF /* CNG */) // if (input.mWideband && f.mMode == 0xFF /* CNG */)
// { // {
// int a = 1; // int a = 1;
// } // }`
if (input.mWideband && f.mFrameType == 15) if (input.mWideband && f.mFrameType == 15)
{ {

View File

@@ -11,6 +11,7 @@
#include "MT_AudioReceiver.h" #include "MT_AudioReceiver.h"
#include "MT_AudioCodec.h" #include "MT_AudioCodec.h"
#include "MT_CngHelper.h" #include "MT_CngHelper.h"
#include "MT_Dtmf.h"
#include "../helper/HL_Log.h" #include "../helper/HL_Log.h"
#include "../helper/HL_Time.h" #include "../helper/HL_Time.h"
#include "../audio/Audio_Interface.h" #include "../audio/Audio_Interface.h"
@@ -207,7 +208,8 @@ RtpBuffer::FetchResult RtpBuffer::fetch()
// Save it as last packet however - to not confuse loss packet counter // Save it as last packet however - to not confuse loss packet counter
mFetchedPacket = mPacketList.front(); mFetchedPacket = mPacketList.front();
mLastSeqno = mPacketList.front()->rtp()->GetExtendedSequenceNumber(); mLastSeqno = mFetchedPacket->rtp()->GetExtendedSequenceNumber();
mLastReceiveTime = mFetchedPacket->rtp()->GetReceiveTime();
// Erase from packet list // Erase from packet list
mPacketList.erase(mPacketList.begin()); mPacketList.erase(mPacketList.begin());
@@ -238,20 +240,27 @@ RtpBuffer::FetchResult RtpBuffer::fetch()
// Gap between new packet and previous on // Gap between new packet and previous on
int gap = (int64_t)seqno - (int64_t)*mLastSeqno - 1; int gap = (int64_t)seqno - (int64_t)*mLastSeqno - 1;
// gap = std::min(gap, 127);
if (gap > 0) if (gap > 0)
{ {
// std::cout << "Increase the packet loss for SSRC " << std::hex << mSsrc << std::endl; // std::cout << "Increase the packet loss for SSRC " << std::hex << mSsrc << std::endl;
mStat.mPacketLoss += gap; mStat.mPacketLoss += gap;
auto currentTimestamp = std::chrono::microseconds(uint64_t(packet.rtp()->GetReceiveTime().GetDouble() * 1000000));
// Report is the onetime; there is no many sequential 1-packet gap reports
if (mStat.mPacketLossTimeline.empty() || (mStat.mPacketLossTimeline.back().mEndSeqno != seqno)) if (mStat.mPacketLossTimeline.empty() || (mStat.mPacketLossTimeline.back().mEndSeqno != seqno))
mStat.mPacketLossTimeline.push_back({.mStartSeqno = *mLastSeqno, {
.mEndSeqno = seqno, auto gapStart = RtpHelper::toMicroseconds(*mLastReceiveTime);
.mGap = gap, auto gapEnd = RtpHelper::toMicroseconds(packet.rtp()->GetReceiveTime());
.mTimestamp = currentTimestamp}); mStat.mPacketLossTimeline.emplace_back(PacketLossEvent{.mStartSeqno = *mLastSeqno,
.mEndSeqno = seqno,
.mGap = gap,
.mTimestampStart = gapStart,
.mTimestampEnd = gapEnd});
}
// ToDo: here we should decide smth - 2-packet gap shoud report Status::Gap two times at least; but current implementation gives only one.
// It is not big problem - as gap is detected when we have smth to return usually
mLastSeqno = seqno; mLastSeqno = seqno;
mLastReceiveTime = packet.rtp()->GetReceiveTime();
result = {FetchResult::Status::Gap}; result = {FetchResult::Status::Gap};
} }
else else
@@ -261,6 +270,7 @@ RtpBuffer::FetchResult RtpBuffer::fetch()
// Save last returned normal packet // Save last returned normal packet
mFetchedPacket = result.mPacket; mFetchedPacket = result.mPacket;
mLastSeqno = result.mPacket->rtp()->GetExtendedSequenceNumber(); mLastSeqno = result.mPacket->rtp()->GetExtendedSequenceNumber();
mLastReceiveTime = result.mPacket->rtp()->GetReceiveTime();
// Remove returned packet from the list // Remove returned packet from the list
mPacketList.erase(mPacketList.begin()); mPacketList.erase(mPacketList.begin());
@@ -278,6 +288,7 @@ RtpBuffer::FetchResult RtpBuffer::fetch()
// Remember returned packet // Remember returned packet
mFetchedPacket = result.mPacket; mFetchedPacket = result.mPacket;
mLastSeqno = result.mPacket->rtp()->GetExtendedSequenceNumber(); mLastSeqno = result.mPacket->rtp()->GetExtendedSequenceNumber();
mLastReceiveTime = result.mPacket->rtp()->GetReceiveTime();
// Remove returned packet from buffer list // Remove returned packet from buffer list
mPacketList.erase(mPacketList.begin()); mPacketList.erase(mPacketList.begin());
@@ -955,5 +966,25 @@ DtmfReceiver::DtmfReceiver(Statistics& stat)
DtmfReceiver::~DtmfReceiver() DtmfReceiver::~DtmfReceiver()
{} {}
void DtmfReceiver::add(std::shared_ptr<RTPPacket> /*p*/) void DtmfReceiver::add(const std::shared_ptr<RTPPacket>& p)
{} {
// This receiver always work in context of single RTP stream; so there is no need to put SSRC map and so on
if (p->GetPayloadType() != 101)
return;
auto ev = DtmfBuilder::parseRfc2833({p->GetPayloadData(), p->GetPayloadLength()});
if (ev.mTone != mEvent || ev.mEnd != mEventEnded)
{
// New tone is here
if (mCallback)
mCallback(ev.mTone);
// Queue statistics item
mStat.mDtmf2833Timeline.emplace_back(Dtmf2833Event{.mTone = ev.mTone,
.mTimestamp = RtpHelper::toMicroseconds(p->GetReceiveTime())});
// Store to avoid triggering on the packet
mEvent = ev.mTone;
mEventEnded = ev.mEnd;
}
}

View File

@@ -120,6 +120,7 @@ protected:
jrtplib::RTPSourceStats mRtpStats; jrtplib::RTPSourceStats mRtpStats;
std::shared_ptr<Packet> mFetchedPacket; std::shared_ptr<Packet> mFetchedPacket;
std::optional<uint32_t> mLastSeqno; std::optional<uint32_t> mLastSeqno;
std::optional<jrtplib::RTPTime> mLastReceiveTime;
// To calculate average interval between packet add. It is close to jitter but more useful in debugging. // To calculate average interval between packet add. It is close to jitter but more useful in debugging.
float mLastAddTime = 0.0f; float mLastAddTime = 0.0f;
@@ -248,11 +249,18 @@ protected:
class DtmfReceiver: public Receiver class DtmfReceiver: public Receiver
{ {
private:
char mEvent = 0;
bool mEventEnded = false;
std::chrono::milliseconds mEventStart = 0ms;
std::function<void(char)> mCallback;
public: public:
DtmfReceiver(Statistics& stat); DtmfReceiver(Statistics& stat);
~DtmfReceiver(); ~DtmfReceiver();
void add(std::shared_ptr<RTPPacket> p); void add(const std::shared_ptr<RTPPacket>& p);
void setCallback(std::function<void(char tone)> callback);
}; };
} }

View File

@@ -16,38 +16,68 @@
using namespace MT; using namespace MT;
void DtmfBuilder::buildRfc2833(int tone, int duration, int volume, bool endOfEvent, void* output) void DtmfBuilder::buildRfc2833(const Rfc2833Event& ev, void* output)
{ {
assert(duration); assert(ev.mDuration != 0);
assert(output); assert(output);
assert(tone); assert(ev.mTone != 0);
unsigned char toneValue = 0; unsigned char toneValue = 0;
if (tone >= '0' && tone <='9') if (ev.mTone >= '0' && ev.mTone <='9')
toneValue = tone - '0'; toneValue = ev.mTone - '0';
else else
if (tone >= 'A' && tone <='D' ) if (ev.mTone >= 'A' && ev.mTone <='D' )
toneValue = tone - 'A' + 12; toneValue = ev.mTone - 'A' + 12;
else else
if (tone == '*') if (ev.mTone == '*')
toneValue = 10; toneValue = 10;
else else
if (tone == '#') if (ev.mTone == '#')
toneValue = 11; toneValue = 11;
char* packet = (char*)output; char* packet = (char*)output;
packet[0] = toneValue; packet[0] = toneValue;
packet[1] = 1 | (volume << 2); packet[1] = 1 | (ev.mVolume << 2);
if (endOfEvent) if (ev.mEnd)
packet[1] |= 128; packet[1] |= 128;
else else
packet[1] &= 127; packet[1] &= 127;
unsigned short durationValue = htons(duration); unsigned short durationValue = htons(ev.mDuration);
memcpy(packet + 2, &durationValue, 2); memcpy(packet + 2, &durationValue, 2);
} }
DtmfBuilder::Rfc2833Event DtmfBuilder::parseRfc2833(std::span<uint8_t> payload)
{
Rfc2833Event r;
if (payload.size_bytes() < 4)
return r;
uint8_t b0 = payload[0];
uint8_t b1 = payload[1];
if (b0 >=0 && b0 <= 9)
r.mTone = '0' + b0;
else
if (b0 >= 12 && b0 <= 17)
r.mTone = 'A' + b0;
else
if (b0 == 10)
r.mTone = '*';
else
if (b0 == 11)
r.mTone = '#';
r.mEnd = (b1 & 128);
r.mVolume = (b1 & 127) >> 2;
r.mDuration = ntohs(*(uint16_t*)payload.data()+2);
return r;
}
#pragma region Inband DTMF support #pragma region Inband DTMF support
#include <math.h> #include <math.h>
#ifndef TARGET_WIN #ifndef TARGET_WIN
@@ -302,24 +332,24 @@ bool DtmfContext::getRfc2833(int milliseconds, ByteBuffer& output, ByteBuffer& s
{ {
// Emit rfc2833 packet // Emit rfc2833 packet
output.resize(4); output.resize(4);
DtmfBuilder::buildRfc2833(d.mTone, milliseconds, d.mVolume, false, output.mutableData()); DtmfBuilder::buildRfc2833({.mTone = (char)d.mTone, .mDuration = milliseconds, .mVolume = d.mVolume, .mEnd = false}, output.mutableData());
d.mDuration -= milliseconds; d.mDuration -= milliseconds;
if(d.mDuration <= 0) if(d.mDuration <= 0)
d.mStopped = true; d.mStopped = true;
} }
else else
if (!d.mStopped) if (!d.mStopped)
{ {
output.resize(4); output.resize(4);
DtmfBuilder::buildRfc2833(d.mTone, milliseconds, d.mVolume, false, output.mutableData()); DtmfBuilder::buildRfc2833({.mTone = (char)d.mTone, .mDuration = milliseconds, .mVolume = d.mVolume, .mEnd = false}, output.mutableData());
} }
else else
output.clear(); output.clear();
if (d.mStopped) if (d.mStopped)
{ {
stopPacket.resize(4); stopPacket.resize(4);
DtmfBuilder::buildRfc2833(d.mTone, milliseconds, d.mVolume, true, stopPacket.mutableData()); DtmfBuilder::buildRfc2833({.mTone = (char)d.mTone, .mDuration = milliseconds, .mVolume = d.mVolume, .mEnd = true}, stopPacket.mutableData());
} }
else else
stopPacket.clear(); stopPacket.clear();
@@ -375,7 +405,7 @@ void zap_dtmf_detect_init(dtmf_detect_state_t *s);
int zap_dtmf_detect(dtmf_detect_state_t *s, int16_t amp[], int samples, int isradio); int zap_dtmf_detect(dtmf_detect_state_t *s, int16_t amp[], int samples, int isradio);
int zap_dtmf_get(dtmf_detect_state_t *s, char *buf, int max); int zap_dtmf_get(dtmf_detect_state_t *s, char *buf, int max);
DTMFDetector::DTMFDetector() InbandDtmfDetector::InbandDtmfDetector()
:mState(NULL) :mState(NULL)
{ {
mState = malloc(sizeof(dtmf_detect_state_t)); mState = malloc(sizeof(dtmf_detect_state_t));
@@ -384,13 +414,13 @@ DTMFDetector::DTMFDetector()
zap_dtmf_detect_init((dtmf_detect_state_t*)mState); zap_dtmf_detect_init((dtmf_detect_state_t*)mState);
} }
DTMFDetector::~DTMFDetector() InbandDtmfDetector::~InbandDtmfDetector()
{ {
if (mState) if (mState)
free(mState); free(mState);
} }
std::string DTMFDetector::streamPut(unsigned char* samples, unsigned int size) std::string InbandDtmfDetector::streamPut(unsigned char* samples, unsigned int size)
{ {
char buf[16]; buf[0] = 0; char buf[16]; buf[0] = 0;
if (zap_dtmf_detect((dtmf_detect_state_t*)mState, (int16_t*)samples, size/2, 0)) if (zap_dtmf_detect((dtmf_detect_state_t*)mState, (int16_t*)samples, size/2, 0))
@@ -398,7 +428,7 @@ std::string DTMFDetector::streamPut(unsigned char* samples, unsigned int size)
return buf; return buf;
} }
void DTMFDetector::resetState() void InbandDtmfDetector::resetState()
{ {
zap_dtmf_detect_init((dtmf_detect_state_t*)mState); zap_dtmf_detect_init((dtmf_detect_state_t*)mState);
} }

View File

@@ -15,38 +15,52 @@
namespace MT namespace MT
{ {
class DtmfBuilder class DtmfBuilder
{ {
public: public:
struct Rfc2833Event
{
char mTone = 0;
int mDuration = 0;
int mVolume = 0;
bool mEnd = false;
bool isValid() const {
return mTone != 0;
}
};
// Output should be 4 bytes length // Output should be 4 bytes length
static void buildRfc2833(int tone, int duration, int volume, bool endOfEvent, void* output); static void buildRfc2833(const Rfc2833Event& ev, void* output);
static Rfc2833Event parseRfc2833(std::span<uint8_t> payload);
// Buf receives PCM audio // Buf receives PCM audio
static void buildInband(int tone, int startTime, int finishTime, int rate, short* buf); static void buildInband(int tone, int startTime, int finishTime, int rate, short* buf);
}; };
struct Dtmf struct Dtmf
{ {
Dtmf(): mTone(0), mDuration(0), mVolume(0), mFinishCount(3), mCurrentTime(0), mStopped(false) {} Dtmf(): mTone(0), mDuration(0), mVolume(0), mFinishCount(3), mCurrentTime(0), mStopped(false) {}
Dtmf(int tone, int volume, int duration): mTone(tone), mVolume(volume), mDuration(duration), mFinishCount(3), mCurrentTime(0), mStopped(false) {} Dtmf(int tone, int volume, int duration): mTone(tone), mVolume(volume), mDuration(duration), mFinishCount(3), mCurrentTime(0), mStopped(false) {}
int mStopped; int mStopped;
int mTone; int mTone;
int mDuration; // It is zero for tones generated by startTone()..stopTone() calls. int mDuration; // It is zero for tones generated by startTone()..stopTone() calls.
int mVolume; int mVolume;
int mFinishCount; int mFinishCount;
int mCurrentTime; int mCurrentTime;
}; };
typedef std::vector<Dtmf> DtmfQueue; typedef std::vector<Dtmf> DtmfQueue;
class DtmfContext class DtmfContext
{ {
public: public:
enum Type enum Type
{ {
Dtmf_Inband, Dtmf_Inband,
Dtmf_Rfc2833 Dtmf_Rfc2833
}; };
DtmfContext(); DtmfContext();
@@ -65,33 +79,34 @@ namespace MT
bool getInband(int milliseconds, int rate, ByteBuffer& output); bool getInband(int milliseconds, int rate, ByteBuffer& output);
bool getRfc2833(int milliseconds, ByteBuffer& output, ByteBuffer& stopPacket); bool getRfc2833(int milliseconds, ByteBuffer& output, ByteBuffer& stopPacket);
protected: protected:
Mutex mGuard; Mutex mGuard;
Type mType; Type mType;
DtmfQueue mQueue; DtmfQueue mQueue;
}; };
class DTMFDetector class InbandDtmfDetector
{ {
public: public:
/*! The default constructor. Allocates space for detector context. */ /*! The default constructor. Allocates space for detector context. */
DTMFDetector(); InbandDtmfDetector();
/*! The destructor. Free the detector context's memory. */
~DTMFDetector();
/*! This method receives the input PCM 16-bit data and returns found DTMF event(s) in string representation. /*! The destructor. Free the detector context's memory. */
~InbandDtmfDetector();
/*! This method receives the input PCM 16-bit data and returns found DTMF event(s) in string representation.
* @param samples Input PCM buffer pointer. * @param samples Input PCM buffer pointer.
* @param size Size of input buffer in bytes * @param size Size of input buffer in bytes
* @return Found DTMF event(s) in string representation. The returned value has variable length. * @return Found DTMF event(s) in string representation. The returned value has variable length.
*/ */
std::string streamPut(unsigned char* samples, unsigned int size); std::string streamPut(unsigned char* samples, unsigned int size);
void resetState(); void resetState();
protected: protected:
void* mState; /// DTMF detector context void* mState; /// DTMF detector context
}; };
} }
#endif #endif

View File

@@ -74,13 +74,16 @@ Statistics::~Statistics()
void Statistics::calculateBurstr(double* burstr, double* lossr) const void Statistics::calculateBurstr(double* burstr, double* lossr) const
{ {
int lost = 0; int lost = 0; // Total packet lost
int bursts = 0; for (const auto& item: mPacketLossTimeline)
for (int i = 0; i < 128; i++) lost += item.mGap;
{ int bursts = mPacketLossTimeline.size(); // number of events
lost += i * mLoss[i];
bursts += mLoss[i]; // for (const auto& entry: mLoss)
} // {
// lost += entry.first * entry.second;
// bursts += entry.second;
// }
if (lost < 5) if (lost < 5)
{ {
@@ -109,14 +112,14 @@ void Statistics::calculateBurstr(double* burstr, double* lossr) const
double Statistics::calculateMos(double maximalMos) const double Statistics::calculateMos(double maximalMos) const
{ {
// calculate lossrate and burst rate // calculate lossrate and burst rate
double burstr, lossr; double burstr = 0, lossr = 0;
calculateBurstr(&burstr, &lossr); calculateBurstr(&burstr, &lossr);
double r; double r = 0.0;
double bpl = 8.47627; //mos = -4.23836 + 0.29873 * r - 0.00416744 * r * r + 0.0000209855 * r * r * r; double bpl = 8.47627; //mos = -4.23836 + 0.29873 * r - 0.00416744 * r * r + 0.0000209855 * r * r * r;
double mos; double mos = 0.0;
if (mReceivedRtp < 100) if (mReceivedRtp < 10)
return 0.0; return 0.0;
if (lossr == 0.0 || burstr == 0.0) if (lossr == 0.0 || burstr == 0.0)

View File

@@ -4,16 +4,15 @@
#include <chrono> #include <chrono>
#include <map> #include <map>
#include <optional> #include <optional>
#include <array>
#include "audio/Audio_DataWindow.h"
#include "helper/HL_Optional.hpp"
#include "helper/HL_Statistics.h" #include "helper/HL_Statistics.h"
#include "helper/HL_Types.h" #include "helper/HL_Types.h"
#include "helper/HL_InternetAddress.h"
#include "jrtplib/src/rtptimeutilities.h" #include "jrtplib/src/rtptimeutilities.h"
#include "jrtplib/src/rtppacket.h" #include "jrtplib/src/rtppacket.h"
using namespace std::chrono_literals;
namespace MT namespace MT
{ {
@@ -56,47 +55,55 @@ struct PacketLossEvent
uint32_t mStartSeqno = 0, uint32_t mStartSeqno = 0,
mEndSeqno = 0; mEndSeqno = 0;
int mGap = 0; int mGap = 0;
std::chrono::microseconds mTimestampStart = 0us,
mTimestampEnd = 0us;
};
struct Dtmf2833Event
{
char mTone;
std::chrono::microseconds mTimestamp; std::chrono::microseconds mTimestamp;
}; };
class Statistics class Statistics
{ {
public: public:
size_t mReceived = 0, // Received traffic in bytes size_t mReceived = 0, // Received traffic in bytes
mSent = 0, // Sent traffic in bytes mSent = 0, // Sent traffic in bytes
mReceivedRtp = 0, // Number of received rtp packets mReceivedRtp = 0, // Number of received rtp packets
mSentRtp = 0, // Number of sent rtp packets mSentRtp = 0, // Number of sent rtp packets
mReceivedRtcp = 0, // Number of received rtcp packets mReceivedRtcp = 0, // Number of received rtcp packets
mSentRtcp = 0, // Number of sent rtcp packets mSentRtcp = 0, // Number of sent rtcp packets
mDuplicatedRtp = 0, // Number of received duplicated rtp packets mDuplicatedRtp = 0, // Number of received duplicated rtp packets
mOldRtp = 0, // Number of late rtp packets mOldRtp = 0, // Number of late rtp packets
mPacketLoss = 0, // Number of lost packets mPacketLoss = 0, // Number of lost packets
mPacketDropped = 0, // Number of dropped packets (due to time unsync when playing)б mPacketDropped = 0, // Number of dropped packets (due to time unsync when playing)б
mIllegalRtp = 0; // Number of rtp packets with bad payload type mIllegalRtp = 0; // Number of rtp packets with bad payload type
TestResult<float> mDecodingInterval, // Average interval on call to packet decode TestResult<float> mDecodingInterval, // Average interval on call to packet decode
mDecodeRequested, // Average amount of requested audio frames to play mDecodeRequested, // Average amount of requested audio frames to play
mPacketInterval; // Average interval between packet adding to jitter buffer mPacketInterval; // Average interval between packet adding to jitter buffer
std::array<float, 128> mLoss = {0}; // Every item is number of loss of corresping length std::map<int,int> mLoss; // Every item is number of loss of corresping length
size_t mAudioTime = 0; // Decoded/found time in milliseconds size_t mAudioTime = 0; // Decoded/found time in milliseconds
size_t mDecodedSize = 0; // Number of decoded bytes size_t mDecodedSize = 0; // Number of decoded bytes
uint16_t mSsrc = 0; // Last known SSRC ID in a RTP stream uint16_t mSsrc = 0; // Last known SSRC ID in a RTP stream
ice::NetworkAddress mRemotePeer; // Last known remote RTP address ice::NetworkAddress mRemotePeer; // Last known remote RTP address
// AMR codec bitrate switch counter // AMR codec bitrate switch counter
int mBitrateSwitchCounter = 0; int mBitrateSwitchCounter = 0;
int mCng = 0; int mCng = 0;
std::string mCodecName; std::string mCodecName;
float mJitter = 0.0f; // Jitter float mJitter = 0.0f; // Jitter
TestResult<float> mRttDelay; // RTT delay TestResult<float> mRttDelay; // RTT delay
// Timestamp when first RTP packet has arrived // Timestamp when first RTP packet has arrived
std::optional<timepoint_t> mFirstRtpTime; std::optional<timepoint_t> mFirstRtpTime;
std::map<int, int> mCodecCount; // Stats on used codecs std::map<int, int> mCodecCount; // Stats on used codecs
std::vector<PacketLossEvent> mPacketLossTimeline; // Packet loss timeline std::vector<PacketLossEvent> mPacketLossTimeline; // Packet loss timeline
std::vector<Dtmf2833Event> mDtmf2833Timeline;
// It is to calculate network MOS // It is to calculate network MOS
void calculateBurstr(double* burstr, double* loss) const; void calculateBurstr(double* burstr, double* loss) const;