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",