Adding two new network metrics to NetEQ

Clock-drift (in parts-per-million) and peaky-jitter mode status.
Both metrics are propagated to the VoE API. Tests are added
in the NetEQ unittests, and to some extent in ACM unittests
and VoE tests.

Introducing a proper translation between structs NetworkStatistics
and ACMNetworkStatistics.

Note: The file neteq_network_stats.dat in resources must be updated
for the unittests to pass.

Review URL: http://webrtc-codereview.appspot.com/337005

git-svn-id: http://webrtc.googlecode.com/svn/trunk@1328 4adac7df-926f-26a2-2b94-8c16560cd09d
This commit is contained in:
henrik.lundin@webrtc.org 2012-01-04 13:09:55 +00:00
parent 80d28b22b9
commit d439870473
12 changed files with 175 additions and 14 deletions

2
DEPS
View File

@ -8,7 +8,7 @@ vars = {
# External resources like video and audio files used for testing purposes.
# Downloaded on demand when needed.
"webrtc_resources_revision": "5",
"webrtc_resources_revision": "6",
}
# NOTE: Prefer revision numbers to tags for svn deps.

View File

@ -251,6 +251,8 @@ struct NetworkStatistics // NETEQ statistics
WebRtc_UWord16 currentBufferSize;
// preferred (optimal) buffer size in ms
WebRtc_UWord16 preferredBufferSize;
// adding extra delay due to "peaky jitter"
bool jitterPeaksFound;
// loss rate (network + late) in percent (in Q14)
WebRtc_UWord16 currentPacketLossRate;
// late loss rate in percent (in Q14)
@ -263,6 +265,8 @@ struct NetworkStatistics // NETEQ statistics
WebRtc_UWord16 currentPreemptiveRate;
// fraction of data removed through acceleration (in Q14)
WebRtc_UWord16 currentAccelerateRate;
// clock-drift in parts-per-million (negative or positive)
int32_t clockDriftPPM;
// average packet waiting time in the jitter buffer (ms)
int meanWaitingTimeMs;
// median packet waiting time in the jitter buffer (ms)

View File

@ -145,6 +145,8 @@ enum ACMAMRPackingFormat {
//
// -currentBufferSize : current jitter buffer size in ms
// -preferredBufferSize : preferred (optimal) buffer size in ms
// -jitterPeaksFound : indicate if peaky-jitter mode is engaged, that is,
// if severe but sparse network delays have occurred.
// -currentPacketLossRate : loss rate (network + late) (in Q14)
// -currentDiscardRate : late loss rate (in Q14)
// -currentExpandRate : fraction (of original stream) of synthesized
@ -153,17 +155,22 @@ enum ACMAMRPackingFormat {
// pre-emptive expansion (in Q14)
// -currentAccelerateRate : fraction of data removed through acceleration
// (in Q14)
// -clockDriftPPM : clock-drift between sender and receiver in parts-
// per-million. Positive means that receiver sample
// rate is higher than sender sample rate.
// -meanWaitingTimeMs : average packet waiting time in the buffer
// -medianWaitingTimeMs : median packet waiting time in the buffer
// -maxWaitingTimeMs : max packet waiting time in the buffer
typedef struct {
WebRtc_UWord16 currentBufferSize;
WebRtc_UWord16 preferredBufferSize;
bool jitterPeaksFound;
WebRtc_UWord16 currentPacketLossRate;
WebRtc_UWord16 currentDiscardRate;
WebRtc_UWord16 currentExpandRate;
WebRtc_UWord16 currentPreemptiveRate;
WebRtc_UWord16 currentAccelerateRate;
int32_t clockDriftPPM;
int meanWaitingTimeMs;
int medianWaitingTimeMs;
int maxWaitingTimeMs;

View File

@ -458,11 +458,13 @@ ACMNetEQ::NetworkStatistics(
{
statistics->currentAccelerateRate = stats.currentAccelerateRate;
statistics->currentBufferSize = stats.currentBufferSize;
statistics->jitterPeaksFound = (stats.jitterPeaksFound > 0);
statistics->currentDiscardRate = stats.currentDiscardRate;
statistics->currentExpandRate = stats.currentExpandRate;
statistics->currentPacketLossRate = stats.currentPacketLossRate;
statistics->currentPreemptiveRate = stats.currentPreemptiveRate;
statistics->preferredBufferSize = stats.preferredBufferSize;
statistics->clockDriftPPM = stats.clockDriftPPM;
}
else
{

View File

@ -103,6 +103,15 @@ TEST_F(AcmNetEqTest, NetworkStatistics) {
ACMNetworkStatistics stats;
ASSERT_EQ(0, neteq_.NetworkStatistics(&stats));
EXPECT_EQ(0, stats.currentBufferSize);
EXPECT_EQ(0, stats.preferredBufferSize);
EXPECT_FALSE(stats.jitterPeaksFound);
EXPECT_EQ(0, stats.currentPacketLossRate);
EXPECT_EQ(0, stats.currentDiscardRate);
EXPECT_EQ(0, stats.currentExpandRate);
EXPECT_EQ(0, stats.currentPreemptiveRate);
EXPECT_EQ(0, stats.currentAccelerateRate);
EXPECT_EQ(-916, stats.clockDriftPPM);
EXPECT_EQ(300, stats.maxWaitingTimeMs);
EXPECT_EQ(159, stats.meanWaitingTimeMs);
EXPECT_EQ(160, stats.medianWaitingTimeMs);

View File

@ -977,12 +977,17 @@ APITest::TestDelay(char side)
fprintf(stdout, "\n\nJitter Statistics at Side %c\n", side);
fprintf(stdout, "--------------------------------------\n");
fprintf(stdout, "buffer-size............. %d\n", networkStat.currentBufferSize);
fprintf(stdout, "Preferred buffer-size... %d\n", networkStat.preferredBufferSize);
fprintf(stdout, "Preferred buffer-size... %d\n", networkStat.preferredBufferSize);
fprintf(stdout, "Peaky jitter mode........%d\n", networkStat.jitterPeaksFound);
fprintf(stdout, "packet-size rate........ %d\n", networkStat.currentPacketLossRate);
fprintf(stdout, "discard rate............ %d\n", networkStat.currentDiscardRate);
fprintf(stdout, "expand rate............. %d\n", networkStat.currentExpandRate);
fprintf(stdout, "Preemptive rate......... %d\n", networkStat.currentPreemptiveRate);
fprintf(stdout, "Accelerate rate......... %d\n", networkStat.currentAccelerateRate);
fprintf(stdout, "Clock-drift............. %d\n", networkStat.clockDriftPPM);
fprintf(stdout, "Mean waiting time....... %d\n", networkStat.meanWaitingTimeMs);
fprintf(stdout, "Median waiting time..... %d\n", networkStat.medianWaitingTimeMs);
fprintf(stdout, "Max waiting time........ %d\n", networkStat.maxWaitingTimeMs);
}
CHECK_ERROR_MT(myACM->SetMinimumPlayoutDelay(*myMinDelay));

View File

@ -14,6 +14,8 @@
#include "automode.h"
#include <assert.h>
#include "signal_processing_library.h"
#include "neteq_defines.h"
@ -496,11 +498,12 @@ WebRtc_Word16 WebRtcNetEQ_CalcOptimalBufLvl(AutomodeInst_t *inst, WebRtc_Word32
* If not disabled (enough peaks have been observed) and
* time since last peak is less than two peak periods.
*/
inst->peakFound = 0;
if ((!inst->peakModeDisabled) && (inst->peakIatCountSamp
<= WEBRTC_SPL_LSHIFT_W32(inst->curPeakPeriod , 1)))
{
/* Engage peak mode */
inst->peakFound = 1;
/* Set optimal buffer level to curPeakHeight (if it's not already larger) */
Bopt = WEBRTC_SPL_MAX(Bopt, inst->curPeakHeight);
@ -688,7 +691,7 @@ int WebRtcNetEQ_ResetAutomode(AutomodeInst_t *inst, int maxBufLenPackets)
}
/*
* Calculate the optimal buffer level corresponing to the initial PDF.
* Calculate the optimal buffer level corresponding to the initial PDF.
* No need to call WebRtcNetEQ_CalcOptimalBufLvl() since we have just hard-coded
* all the variables that the buffer level depends on => we know the result
*/
@ -715,3 +718,19 @@ int WebRtcNetEQ_ResetAutomode(AutomodeInst_t *inst, int maxBufLenPackets)
return 0;
}
int32_t WebRtcNetEQ_AverageIAT(const AutomodeInst_t *inst) {
int i;
int32_t sum_q24 = 0;
assert(inst);
for (i = 0; i <= MAX_IAT; ++i) {
/* Shift 6 to fit worst case: 2^30 * 64. */
sum_q24 += (inst->iatProb[i] >> 6) * i;
}
/* Subtract the nominal inter-arrival time 1 = 2^24 in Q24. */
sum_q24 -= (1 << 24);
/*
* Multiply with 1000000 / 2^24 = 15625 / 2^18 to get the average.
* Shift 7 to Q17 first, then multiply with 15625 and shift another 11.
*/
return ((sum_q24 >> 7) * 15625) >> 11;
}

View File

@ -103,6 +103,8 @@ typedef struct
WebRtc_Word16 curPeakHeight; /* derived from peakHeightPkt vector;
used as optimal buffer level in peak mode */
WebRtc_Word16 peakModeDisabled; /* ==0 if peak mode can be engaged; >0 if not */
uint16_t peakFound; /* 1 if peaks are detected and extra delay is applied;
* 0 otherwise. */
/* Post-call statistics */
WebRtc_UWord32 countIAT500ms; /* number of times we got small network outage */
@ -240,4 +242,23 @@ int WebRtcNetEQ_SetPacketSpeechLen(AutomodeInst_t *inst, WebRtc_Word16 newLenSam
int WebRtcNetEQ_ResetAutomode(AutomodeInst_t *inst, int maxBufLenPackets);
/****************************************************************************
* WebRtcNetEQ_AverageIAT(...)
*
* Calculate the average inter-arrival time based on current statistics.
* The average is expressed in parts per million relative the nominal. That is,
* if the average inter-arrival time is equal to the nominal frame time,
* the return value is zero. A positive value corresponds to packet spacing
* being too large, while a negative value means that the packets arrive with
* less spacing than expected.
*
*
* Input:
* - inst : Automode instance.
*
* Return value : Average relative inter-arrival time in samples.
*/
int32_t WebRtcNetEQ_AverageIAT(const AutomodeInst_t *inst);
#endif /* AUTOMODE_H */

View File

@ -92,16 +92,21 @@ int WebRtcNetEQ_RecOutMasterSlave(void *inst, WebRtc_Word16 *pw16_outData,
typedef struct
{
WebRtc_UWord16 currentBufferSize; /* current jitter buffer size in ms */
WebRtc_UWord16 preferredBufferSize; /* preferred (optimal) buffer size in ms */
WebRtc_UWord16 currentPacketLossRate; /* loss rate (network + late) (in Q14) */
WebRtc_UWord16 currentDiscardRate; /* late loss rate (in Q14) */
WebRtc_UWord16 currentExpandRate; /* fraction (of original stream) of synthesized speech
* inserted through expansion (in Q14) */
WebRtc_UWord16 currentPreemptiveRate; /* fraction of synthesized speech inserted through
* pre-emptive expansion (in Q14) */
WebRtc_UWord16 currentAccelerateRate; /* fraction of data removed through acceleration
* (in Q14) */
uint16_t currentBufferSize; /* Current jitter buffer size in ms. */
uint16_t preferredBufferSize; /* Preferred buffer size in ms. */
uint16_t jitterPeaksFound; /* 1 if adding extra delay due to peaky
* jitter; 0 otherwise. */
uint16_t currentPacketLossRate; /* Loss rate (network + late) (Q14). */
uint16_t currentDiscardRate; /* Late loss rate (Q14). */
uint16_t currentExpandRate; /* Fraction (of original stream) of
* synthesized speech inserted through
* expansion (in Q14). */
uint16_t currentPreemptiveRate; /* Fraction of data inserted through
* pre-emptive expansion (in Q14). */
uint16_t currentAccelerateRate; /* Fraction of data removed through
* acceleration (in Q14). */
int32_t clockDriftPPM; /* Average clock-drift in parts-per-
* million (positive or negative). */
} WebRtcNetEQ_NetworkStatistics;
/*

View File

@ -1241,6 +1241,13 @@ int WebRtcNetEQ_GetNetworkStatistics(void *inst, WebRtcNetEQ_NetworkStatistics *
stats->preferredBufferSize = 0;
}
/***********************************/
/* Check if jitter peaks are found */
/***********************************/
stats->jitterPeaksFound =
NetEqMainInst->MCUinst.BufferStat_inst.Automode_inst.peakFound;
/***********************/
/* Calculate loss rate */
/***********************/
@ -1525,6 +1532,9 @@ int WebRtcNetEQ_GetNetworkStatistics(void *inst, WebRtcNetEQ_NetworkStatistics *
stats->currentPreemptiveRate = 1 << 14; /* 1 in Q14 */
}
stats->clockDriftPPM = WebRtcNetEQ_AverageIAT(
&NetEqMainInst->MCUinst.BufferStat_inst.Automode_inst);
/* reset counters */
WebRtcNetEQ_ResetMcuInCallStats(&(NetEqMainInst->MCUinst));
WebRtcNetEQ_ClearInCallStats(&(NetEqMainInst->DSPinst));

View File

@ -183,6 +183,9 @@ class NetEqDecodingTest : public ::testing::Test {
void DecodeAndCheckStats(const std::string &rtp_file,
const std::string &stat_ref_file,
const std::string &rtcp_ref_file);
static void PopulateRtpInfo(int frame_index,
int samples_per_frame,
WebRtcNetEQ_RTPInfo* rtp_info);
NETEQTEST_NetEQClass* neteq_inst_;
std::vector<NETEQTEST_Decoder*> dec_;
@ -333,6 +336,16 @@ void NetEqDecodingTest::DecodeAndCheckStats(const std::string &rtp_file,
}
}
void NetEqDecodingTest::PopulateRtpInfo(int frame_index,
int samples_per_frame,
WebRtcNetEQ_RTPInfo* rtp_info) {
rtp_info->sequenceNumber = frame_index;
rtp_info->timeStamp = frame_index * samples_per_frame;
rtp_info->SSRC = 0x1234; // Just an arbitrary SSRC.
rtp_info->payloadType = 94; // PCM16b WB codec.
rtp_info->markerBit = 0;
}
#if defined(WEBRTC_LINUX) && defined(WEBRTC_ARCH_64_BITS)
TEST_F(NetEqDecodingTest, TestBitExactness) {
const std::string kInputRtpFile = webrtc::test::ProjectRootPath() +
@ -419,4 +432,66 @@ TEST_F(NetEqDecodingTest, TestFrameWaitingTimeStatistics) {
EXPECT_EQ(100, len);
}
TEST_F(NetEqDecodingTest, TestAverageInterArrivalTimeNegative) {
const int kNumFrames = 3000; // Needed for convergence.
int frame_index = 0;
const int kSamples = 10 * 16;
const int kPayloadBytes = kSamples * 2;
while (frame_index < kNumFrames) {
// Insert one packet each time, except every 10th time where we insert two
// packets at once. This will create a negative clock-drift of approx. 10%.
int num_packets = (frame_index % 10 == 0 ? 2 : 1);
for (int n = 0; n < num_packets; ++n) {
uint8_t payload[kPayloadBytes] = {0};
WebRtcNetEQ_RTPInfo rtp_info;
PopulateRtpInfo(frame_index, kSamples, &rtp_info);
ASSERT_EQ(0,
WebRtcNetEQ_RecInRTPStruct(neteq_inst_->instance(),
&rtp_info,
payload,
kPayloadBytes, 0));
++frame_index;
}
// Pull out data once.
ASSERT_TRUE(kBlockSize16kHz == neteq_inst_->recOut(out_data_));
}
WebRtcNetEQ_NetworkStatistics network_stats;
ASSERT_EQ(0, WebRtcNetEQ_GetNetworkStatistics(neteq_inst_->instance(),
&network_stats));
EXPECT_EQ(-106911, network_stats.clockDriftPPM);
}
TEST_F(NetEqDecodingTest, TestAverageInterArrivalTimePositive) {
const int kNumFrames = 5000; // Needed for convergence.
int frame_index = 0;
const int kSamples = 10 * 16;
const int kPayloadBytes = kSamples * 2;
for (int i = 0; i < kNumFrames; ++i) {
// Insert one packet each time, except every 10th time where we don't insert
// any packet. This will create a positive clock-drift of approx. 11%.
int num_packets = (i % 10 == 9 ? 0 : 1);
for (int n = 0; n < num_packets; ++n) {
uint8_t payload[kPayloadBytes] = {0};
WebRtcNetEQ_RTPInfo rtp_info;
PopulateRtpInfo(frame_index, kSamples, &rtp_info);
ASSERT_EQ(0,
WebRtcNetEQ_RecInRTPStruct(neteq_inst_->instance(),
&rtp_info,
payload,
kPayloadBytes, 0));
++frame_index;
}
// Pull out data once.
ASSERT_TRUE(kBlockSize16kHz == neteq_inst_->recOut(out_data_));
}
WebRtcNetEQ_NetworkStatistics network_stats;
ASSERT_EQ(0, WebRtcNetEQ_GetNetworkStatistics(neteq_inst_->instance(),
&network_stats));
EXPECT_EQ(108352, network_stats.clockDriftPPM);
}
} // namespace

View File

@ -3081,6 +3081,10 @@ TEST_MUSTPASS(voe_codec_->SetSendCodec(0, ci));
nStats.currentPreemptiveRate);
TEST_LOG(" preferredBufferSize = %hu \n",
nStats.preferredBufferSize);
TEST_LOG(" jitterPeaksFound = %i \n",
nStats.jitterPeaksFound);
TEST_LOG(" clockDriftPPM = %i \n",
nStats.clockDriftPPM);
TEST_LOG(" meanWaitingTimeMs = %i \n",
nStats.meanWaitingTimeMs);
TEST_LOG(" medianWaitingTimeMs = %i \n",