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
diff --git a/DEPS b/DEPS
index 8878f06..942ab8f 100644
--- a/DEPS
+++ b/DEPS
@@ -8,7 +8,7 @@
# 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.
diff --git a/src/common_types.h b/src/common_types.h
index 74b7db7..02d712e 100644
--- a/src/common_types.h
+++ b/src/common_types.h
@@ -251,6 +251,8 @@
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 @@
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)
diff --git a/src/modules/audio_coding/main/interface/audio_coding_module_typedefs.h b/src/modules/audio_coding/main/interface/audio_coding_module_typedefs.h
index a6ccdd6..527ea94 100644
--- a/src/modules/audio_coding/main/interface/audio_coding_module_typedefs.h
+++ b/src/modules/audio_coding/main/interface/audio_coding_module_typedefs.h
@@ -145,6 +145,8 @@
//
// -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 @@
// 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;
diff --git a/src/modules/audio_coding/main/source/acm_neteq.cc b/src/modules/audio_coding/main/source/acm_neteq.cc
index 7c0cfe2..f41d4df 100644
--- a/src/modules/audio_coding/main/source/acm_neteq.cc
+++ b/src/modules/audio_coding/main/source/acm_neteq.cc
@@ -458,11 +458,13 @@
{
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
{
diff --git a/src/modules/audio_coding/main/source/acm_neteq_unittest.cc b/src/modules/audio_coding/main/source/acm_neteq_unittest.cc
index 72f6f71..dd05e84 100644
--- a/src/modules/audio_coding/main/source/acm_neteq_unittest.cc
+++ b/src/modules/audio_coding/main/source/acm_neteq_unittest.cc
@@ -103,6 +103,15 @@
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);
diff --git a/src/modules/audio_coding/main/test/APITest.cpp b/src/modules/audio_coding/main/test/APITest.cpp
index 558fb7b..c25bfc0 100644
--- a/src/modules/audio_coding/main/test/APITest.cpp
+++ b/src/modules/audio_coding/main/test/APITest.cpp
@@ -977,12 +977,17 @@
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));
diff --git a/src/modules/audio_coding/neteq/automode.c b/src/modules/audio_coding/neteq/automode.c
index be5758d..a38afac 100644
--- a/src/modules/audio_coding/neteq/automode.c
+++ b/src/modules/audio_coding/neteq/automode.c
@@ -14,6 +14,8 @@
#include "automode.h"
+#include <assert.h>
+
#include "signal_processing_library.h"
#include "neteq_defines.h"
@@ -496,11 +498,12 @@
* 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 @@
}
/*
- * 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 @@
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;
+}
diff --git a/src/modules/audio_coding/neteq/automode.h b/src/modules/audio_coding/neteq/automode.h
index 672098a..dbd09cf 100644
--- a/src/modules/audio_coding/neteq/automode.h
+++ b/src/modules/audio_coding/neteq/automode.h
@@ -103,6 +103,8 @@
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_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 */
diff --git a/src/modules/audio_coding/neteq/interface/webrtc_neteq_internal.h b/src/modules/audio_coding/neteq/interface/webrtc_neteq_internal.h
index 59134d3..e7d09cb 100644
--- a/src/modules/audio_coding/neteq/interface/webrtc_neteq_internal.h
+++ b/src/modules/audio_coding/neteq/interface/webrtc_neteq_internal.h
@@ -92,16 +92,21 @@
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;
/*
diff --git a/src/modules/audio_coding/neteq/webrtc_neteq.c b/src/modules/audio_coding/neteq/webrtc_neteq.c
index 4a0f98f..bf3ab20 100644
--- a/src/modules/audio_coding/neteq/webrtc_neteq.c
+++ b/src/modules/audio_coding/neteq/webrtc_neteq.c
@@ -1241,6 +1241,13 @@
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 @@
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));
diff --git a/src/modules/audio_coding/neteq/webrtc_neteq_unittest.cc b/src/modules/audio_coding/neteq/webrtc_neteq_unittest.cc
index 94aa6b9..2fabcea 100644
--- a/src/modules/audio_coding/neteq/webrtc_neteq_unittest.cc
+++ b/src/modules/audio_coding/neteq/webrtc_neteq_unittest.cc
@@ -183,6 +183,9 @@
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::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 @@
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
diff --git a/src/voice_engine/main/test/auto_test/voe_standard_test.cc b/src/voice_engine/main/test/auto_test/voe_standard_test.cc
index abe0660..6491b73 100644
--- a/src/voice_engine/main/test/auto_test/voe_standard_test.cc
+++ b/src/voice_engine/main/test/auto_test/voe_standard_test.cc
@@ -3081,6 +3081,10 @@
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",