Add relative_packet_arrival_delay and jitter_buffer_packets_received statistics.

Bug: webrtc:10333
Change-Id: I415e2286b426cbca940fe3a187957531847272ec
Reviewed-on: https://webrtc-review.googlesource.com/c/src/+/124780
Commit-Queue: Jakob Ivarsson‎ <jakobi@webrtc.org>
Reviewed-by: Minyue Li <minyue@webrtc.org>
Cr-Commit-Position: refs/heads/master@{#26976}
diff --git a/modules/audio_coding/neteq/decision_logic_unittest.cc b/modules/audio_coding/neteq/decision_logic_unittest.cc
index 17840d1..14b5842 100644
--- a/modules/audio_coding/neteq/decision_logic_unittest.cc
+++ b/modules/audio_coding/neteq/decision_logic_unittest.cc
@@ -16,6 +16,7 @@
 #include "modules/audio_coding/neteq/delay_manager.h"
 #include "modules/audio_coding/neteq/delay_peak_detector.h"
 #include "modules/audio_coding/neteq/packet_buffer.h"
+#include "modules/audio_coding/neteq/statistics_calculator.h"
 #include "modules/audio_coding/neteq/tick_timer.h"
 #include "test/field_trial.h"
 #include "test/gtest.h"
@@ -29,10 +30,11 @@
   DecoderDatabase decoder_database(
       new rtc::RefCountedObject<MockAudioDecoderFactory>, absl::nullopt);
   TickTimer tick_timer;
+  StatisticsCalculator stats;
   PacketBuffer packet_buffer(10, &tick_timer);
   DelayPeakDetector delay_peak_detector(&tick_timer, false);
-  auto delay_manager =
-      DelayManager::Create(240, 0, false, &delay_peak_detector, &tick_timer);
+  auto delay_manager = DelayManager::Create(240, 0, false, &delay_peak_detector,
+                                            &tick_timer, &stats);
   BufferLevelFilter buffer_level_filter;
   DecisionLogic* logic = DecisionLogic::Create(
       fs_hz, output_size_samples, false, &decoder_database, packet_buffer,
diff --git a/modules/audio_coding/neteq/delay_manager.cc b/modules/audio_coding/neteq/delay_manager.cc
index 1c7ad19..ab2d48d 100644
--- a/modules/audio_coding/neteq/delay_manager.cc
+++ b/modules/audio_coding/neteq/delay_manager.cc
@@ -20,6 +20,7 @@
 #include "absl/memory/memory.h"
 #include "modules/audio_coding/neteq/delay_peak_detector.h"
 #include "modules/audio_coding/neteq/histogram.h"
+#include "modules/audio_coding/neteq/statistics_calculator.h"
 #include "modules/include/module_common_types_public.h"
 #include "rtc_base/checks.h"
 #include "rtc_base/logging.h"
@@ -114,6 +115,7 @@
                            bool enable_rtx_handling,
                            DelayPeakDetector* peak_detector,
                            const TickTimer* tick_timer,
+                           StatisticsCalculator* statistics,
                            std::unique_ptr<Histogram> histogram)
     : first_packet_received_(false),
       max_packets_in_buffer_(max_packets_in_buffer),
@@ -121,6 +123,7 @@
       histogram_quantile_(histogram_quantile),
       histogram_mode_(histogram_mode),
       tick_timer_(tick_timer),
+      statistics_(statistics),
       base_minimum_delay_ms_(base_minimum_delay_ms),
       effective_minimum_delay_ms_(base_minimum_delay_ms),
       base_target_level_(4),                   // In Q0 domain.
@@ -150,7 +153,8 @@
     int base_minimum_delay_ms,
     bool enable_rtx_handling,
     DelayPeakDetector* peak_detector,
-    const TickTimer* tick_timer) {
+    const TickTimer* tick_timer,
+    StatisticsCalculator* statistics) {
   int quantile;
   std::unique_ptr<Histogram> histogram;
   HistogramMode mode;
@@ -168,7 +172,8 @@
   }
   return absl::make_unique<DelayManager>(
       max_packets_in_buffer, base_minimum_delay_ms, quantile, mode,
-      enable_rtx_handling, peak_detector, tick_timer, std::move(histogram));
+      enable_rtx_handling, peak_detector, tick_timer, statistics,
+      std::move(histogram));
 }
 
 DelayManager::~DelayManager() {}
@@ -234,16 +239,18 @@
       reordered = true;
     }
 
+    int iat_delay = iat_ms - packet_len_ms;
+    int relative_delay;
+    if (reordered) {
+      relative_delay = std::max(iat_delay, 0);
+    } else {
+      UpdateDelayHistory(iat_delay);
+      relative_delay = CalculateRelativePacketArrivalDelay();
+    }
+    statistics_->RelativePacketArrivalDelay(relative_delay);
+
     switch (histogram_mode_) {
       case RELATIVE_ARRIVAL_DELAY: {
-        int iat_delay = iat_ms - packet_len_ms;
-        int relative_delay;
-        if (reordered) {
-          relative_delay = std::max(iat_delay, 0);
-        } else {
-          UpdateDelayHistory(iat_delay);
-          relative_delay = CalculateRelativePacketArrivalDelay();
-        }
         const int index = relative_delay / kBucketSizeMs;
         if (index < histogram_->NumBuckets()) {
           // Maximum delay to register is 2000 ms.
diff --git a/modules/audio_coding/neteq/delay_manager.h b/modules/audio_coding/neteq/delay_manager.h
index 11dfeb9..e54e950 100644
--- a/modules/audio_coding/neteq/delay_manager.h
+++ b/modules/audio_coding/neteq/delay_manager.h
@@ -18,6 +18,7 @@
 
 #include "absl/types/optional.h"
 #include "modules/audio_coding/neteq/histogram.h"
+#include "modules/audio_coding/neteq/statistics_calculator.h"
 #include "modules/audio_coding/neteq/tick_timer.h"
 #include "rtc_base/constructor_magic.h"
 
@@ -40,6 +41,7 @@
                bool enable_rtx_handling,
                DelayPeakDetector* peak_detector,
                const TickTimer* tick_timer,
+               StatisticsCalculator* statistics,
                std::unique_ptr<Histogram> histogram);
 
   // Create a DelayManager object. Notify the delay manager that the packet
@@ -51,7 +53,8 @@
                                               int base_minimum_delay_ms,
                                               bool enable_rtx_handling,
                                               DelayPeakDetector* peak_detector,
-                                              const TickTimer* tick_timer);
+                                              const TickTimer* tick_timer,
+                                              StatisticsCalculator* statistics);
 
   virtual ~DelayManager();
 
@@ -174,6 +177,7 @@
   const int histogram_quantile_;
   const HistogramMode histogram_mode_;
   const TickTimer* tick_timer_;
+  StatisticsCalculator* statistics_;
   int base_minimum_delay_ms_;
   // Provides delay which is used by LimitTargetLevel as lower bound on target
   // delay.
diff --git a/modules/audio_coding/neteq/delay_manager_unittest.cc b/modules/audio_coding/neteq/delay_manager_unittest.cc
index 7b57324..c57f074 100644
--- a/modules/audio_coding/neteq/delay_manager_unittest.cc
+++ b/modules/audio_coding/neteq/delay_manager_unittest.cc
@@ -18,6 +18,7 @@
 #include "modules/audio_coding/neteq/histogram.h"
 #include "modules/audio_coding/neteq/mock/mock_delay_peak_detector.h"
 #include "modules/audio_coding/neteq/mock/mock_histogram.h"
+#include "modules/audio_coding/neteq/mock/mock_statistics_calculator.h"
 #include "rtc_base/checks.h"
 #include "test/field_trial.h"
 #include "test/gmock.h"
@@ -53,6 +54,7 @@
 
   std::unique_ptr<DelayManager> dm_;
   TickTimer tick_timer_;
+  MockStatisticsCalculator stats_;
   MockDelayPeakDetector detector_;
   MockHistogram* mock_histogram_;
   uint16_t seq_no_;
@@ -81,10 +83,11 @@
     dm_ = absl::make_unique<DelayManager>(
         kMaxNumberOfPackets, kMinDelayMs, kDefaultHistogramQuantile,
         histogram_mode_, enable_rtx_handling_, &detector_, &tick_timer_,
-        std::move(histogram));
+        &stats_, std::move(histogram));
   } else {
     dm_ = DelayManager::Create(kMaxNumberOfPackets, kMinDelayMs,
-                               enable_rtx_handling_, &detector_, &tick_timer_);
+                               enable_rtx_handling_, &detector_, &tick_timer_,
+                               &stats_);
   }
 }
 
@@ -709,4 +712,17 @@
   EXPECT_EQ(0, dm_->Update(seq_no_, ts_, kFs));
 }
 
+TEST_F(DelayManagerTest, RelativeArrivalDelayStatistic) {
+  SetPacketAudioLength(kFrameSizeMs);
+  InsertNextPacket();
+
+  IncreaseTime(kFrameSizeMs);
+  EXPECT_CALL(stats_, RelativePacketArrivalDelay(0));
+  InsertNextPacket();
+
+  IncreaseTime(2 * kFrameSizeMs);
+  EXPECT_CALL(stats_, RelativePacketArrivalDelay(20));
+  InsertNextPacket();
+}
+
 }  // namespace webrtc
diff --git a/modules/audio_coding/neteq/include/neteq.h b/modules/audio_coding/neteq/include/neteq.h
index a1c0b52..57fd349 100644
--- a/modules/audio_coding/neteq/include/neteq.h
+++ b/modules/audio_coding/neteq/include/neteq.h
@@ -71,9 +71,18 @@
   uint64_t concealment_events = 0;
   uint64_t jitter_buffer_delay_ms = 0;
   uint64_t jitter_buffer_emitted_count = 0;
-  // Below stat is not part of the spec.
+  // Below stats are not part of the spec.
   uint64_t voice_concealed_samples = 0;
   uint64_t delayed_packet_outage_samples = 0;
+  // This is sum of relative packet arrival delays of received packets so far.
+  // Since end-to-end delay of a packet is difficult to measure and is not
+  // necessarily useful for measuring jitter buffer performance, we report a
+  // relative packet arrival delay. The relative packet arrival delay of a
+  // packet is defined as the arrival delay compared to the first packet
+  // received, given that it had zero delay. To avoid clock drift, the "first"
+  // packet can be made dynamic.
+  uint64_t relative_packet_arrival_delay_ms = 0;
+  uint64_t jitter_buffer_packets_received = 0;
 };
 
 // Metrics that describe the operations performed in NetEq, and the internal
diff --git a/modules/audio_coding/neteq/mock/mock_delay_manager.h b/modules/audio_coding/neteq/mock/mock_delay_manager.h
index 63dd575..3a128ce 100644
--- a/modules/audio_coding/neteq/mock/mock_delay_manager.h
+++ b/modules/audio_coding/neteq/mock/mock_delay_manager.h
@@ -15,6 +15,7 @@
 
 #include "modules/audio_coding/neteq/delay_manager.h"
 #include "modules/audio_coding/neteq/histogram.h"
+#include "modules/audio_coding/neteq/statistics_calculator.h"
 #include "test/gmock.h"
 
 namespace webrtc {
@@ -28,6 +29,7 @@
                    bool enable_rtx_handling,
                    DelayPeakDetector* peak_detector,
                    const TickTimer* tick_timer,
+                   StatisticsCalculator* stats,
                    std::unique_ptr<Histogram> histogram)
       : DelayManager(max_packets_in_buffer,
                      base_min_target_delay_ms,
@@ -36,6 +38,7 @@
                      enable_rtx_handling,
                      peak_detector,
                      tick_timer,
+                     stats,
                      std::move(histogram)) {}
   virtual ~MockDelayManager() { Die(); }
   MOCK_METHOD0(Die, void());
diff --git a/modules/audio_coding/neteq/mock/mock_statistics_calculator.h b/modules/audio_coding/neteq/mock/mock_statistics_calculator.h
index 85f2620..aedb1df 100644
--- a/modules/audio_coding/neteq/mock/mock_statistics_calculator.h
+++ b/modules/audio_coding/neteq/mock/mock_statistics_calculator.h
@@ -21,6 +21,7 @@
  public:
   MOCK_METHOD1(PacketsDiscarded, void(size_t num_packets));
   MOCK_METHOD1(SecondaryPacketsDiscarded, void(size_t num_packets));
+  MOCK_METHOD1(RelativePacketArrivalDelay, void(size_t delay_ms));
 };
 
 }  // namespace webrtc
diff --git a/modules/audio_coding/neteq/neteq_impl.cc b/modules/audio_coding/neteq/neteq_impl.cc
index 5e6b8bc..f1e8527 100644
--- a/modules/audio_coding/neteq/neteq_impl.cc
+++ b/modules/audio_coding/neteq/neteq_impl.cc
@@ -41,6 +41,7 @@
 #include "modules/audio_coding/neteq/post_decode_vad.h"
 #include "modules/audio_coding/neteq/preemptive_expand.h"
 #include "modules/audio_coding/neteq/red_payload_splitter.h"
+#include "modules/audio_coding/neteq/statistics_calculator.h"
 #include "modules/audio_coding/neteq/sync_buffer.h"
 #include "modules/audio_coding/neteq/tick_timer.h"
 #include "modules/audio_coding/neteq/time_stretch.h"
@@ -58,6 +59,7 @@
     const NetEq::Config& config,
     const rtc::scoped_refptr<AudioDecoderFactory>& decoder_factory)
     : tick_timer(new TickTimer),
+      stats(new StatisticsCalculator),
       buffer_level_filter(new BufferLevelFilter),
       decoder_database(
           new DecoderDatabase(decoder_factory, config.codec_pair_id)),
@@ -67,7 +69,8 @@
                                          config.min_delay_ms,
                                          config.enable_rtx_handling,
                                          delay_peak_detector.get(),
-                                         tick_timer.get())),
+                                         tick_timer.get(),
+                                         stats.get())),
       dtmf_buffer(new DtmfBuffer(config.sample_rate_hz)),
       dtmf_tone_generator(new DtmfToneGenerator),
       packet_buffer(
@@ -97,6 +100,7 @@
       expand_factory_(std::move(deps.expand_factory)),
       accelerate_factory_(std::move(deps.accelerate_factory)),
       preemptive_expand_factory_(std::move(deps.preemptive_expand_factory)),
+      stats_(std::move(deps.stats)),
       last_mode_(kModeNormal),
       decoded_buffer_length_(kMaxFrameSize),
       decoded_buffer_(new int16_t[decoded_buffer_length_]),
@@ -233,7 +237,7 @@
   const std::vector<int> changed_payload_types =
       decoder_database_->SetCodecs(codecs);
   for (const int pt : changed_payload_types) {
-    packet_buffer_->DiscardPacketsWithPayloadType(pt, &stats_);
+    packet_buffer_->DiscardPacketsWithPayloadType(pt, stats_.get());
   }
 }
 
@@ -251,7 +255,8 @@
   rtc::CritScope lock(&crit_sect_);
   int ret = decoder_database_->Remove(rtp_payload_type);
   if (ret == DecoderDatabase::kOK || ret == DecoderDatabase::kDecoderNotFound) {
-    packet_buffer_->DiscardPacketsWithPayloadType(rtp_payload_type, &stats_);
+    packet_buffer_->DiscardPacketsWithPayloadType(rtp_payload_type,
+                                                  stats_.get());
     return kOK;
   }
   return kFail;
@@ -329,20 +334,21 @@
   assert(decision_logic_.get());
   const int ms_per_packet = rtc::dchecked_cast<int>(
       decision_logic_->packet_length_samples() / (fs_hz_ / 1000));
-  stats_.PopulateDelayManagerStats(ms_per_packet, *delay_manager_.get(), stats);
-  stats_.GetNetworkStatistics(fs_hz_, total_samples_in_buffers,
-                              decoder_frame_length_, stats);
+  stats_->PopulateDelayManagerStats(ms_per_packet, *delay_manager_.get(),
+                                    stats);
+  stats_->GetNetworkStatistics(fs_hz_, total_samples_in_buffers,
+                               decoder_frame_length_, stats);
   return 0;
 }
 
 NetEqLifetimeStatistics NetEqImpl::GetLifetimeStatistics() const {
   rtc::CritScope lock(&crit_sect_);
-  return stats_.GetLifetimeStatistics();
+  return stats_->GetLifetimeStatistics();
 }
 
 NetEqOperationsAndState NetEqImpl::GetOperationsAndState() const {
   rtc::CritScope lock(&crit_sect_);
-  auto result = stats_.GetOperationsAndState();
+  auto result = stats_->GetOperationsAndState();
   result.current_buffer_size_ms =
       (packet_buffer_->NumSamplesInBuffer(decoder_frame_length_) +
        sync_buffer_->FutureLength()) *
@@ -469,6 +475,7 @@
     RTC_LOG_F(LS_ERROR) << "payload is empty";
     return kInvalidPointer;
   }
+  stats_->ReceivedPacket();
 
   PacketList packet_list;
   // Insert packet in a packet list.
@@ -654,7 +661,7 @@
   // Insert packets in buffer.
   const int ret = packet_buffer_->InsertPacketList(
       &parsed_packet_list, *decoder_database_, &current_rtp_payload_type_,
-      &current_cng_rtp_payload_type_, &stats_);
+      &current_cng_rtp_payload_type_, stats_.get());
   if (ret == PacketBuffer::kFlushed) {
     // Reset DSP timestamp etc. if packet buffer flushed.
     new_codec_ = true;
@@ -751,8 +758,8 @@
   *muted = false;
   last_decoded_timestamps_.clear();
   tick_timer_->Increment();
-  stats_.IncreaseCounter(output_size_samples_, fs_hz_);
-  const auto lifetime_stats = stats_.GetLifetimeStatistics();
+  stats_->IncreaseCounter(output_size_samples_, fs_hz_);
+  const auto lifetime_stats = stats_->GetLifetimeStatistics();
   expand_uma_logger_.UpdateSampleCounter(lifetime_stats.concealed_samples,
                                          fs_hz_);
   speech_expand_uma_logger_.UpdateSampleCounter(
@@ -772,7 +779,7 @@
             : timestamp_scaler_->ToExternal(playout_timestamp_) -
                   static_cast<uint32_t>(audio_frame->samples_per_channel_);
     audio_frame->num_channels_ = sync_buffer_->Channels();
-    stats_.ExpandedNoiseSamples(output_size_samples_, false);
+    stats_->ExpandedNoiseSamples(output_size_samples_, false);
     *muted = true;
     return 0;
   }
@@ -981,7 +988,7 @@
   if (!new_codec_) {
     const uint32_t five_seconds_samples = 5 * fs_hz_;
     packet_buffer_->DiscardOldPackets(end_timestamp, five_seconds_samples,
-                                      &stats_);
+                                      stats_.get());
   }
   const Packet* packet = packet_buffer_->PeekNextPacket();
 
@@ -1001,12 +1008,14 @@
            (end_timestamp >= packet->timestamp ||
             end_timestamp + generated_noise_samples > packet->timestamp)) {
       // Don't use this packet, discard it.
-      if (packet_buffer_->DiscardNextPacket(&stats_) != PacketBuffer::kOK) {
+      if (packet_buffer_->DiscardNextPacket(stats_.get()) !=
+          PacketBuffer::kOK) {
         assert(false);  // Must be ok by design.
       }
       // Check buffer again.
       if (!new_codec_) {
-        packet_buffer_->DiscardOldPackets(end_timestamp, 5 * fs_hz_, &stats_);
+        packet_buffer_->DiscardOldPackets(end_timestamp, 5 * fs_hz_,
+                                          stats_.get());
       }
       packet = packet_buffer_->PeekNextPacket();
     }
@@ -1088,7 +1097,7 @@
     decision_logic_->SoftReset();
     buffer_level_filter_->Reset();
     delay_manager_->Reset();
-    stats_.ResetMcu();
+    stats_->ResetMcu();
   }
 
   size_t required_samples = output_size_samples_;
@@ -1193,7 +1202,7 @@
       // if comfort noise is not played. If comfort noise was just played,
       // this adjustment of timestamp is only done to get back in sync with the
       // stream timestamp; no loss to report.
-      stats_.LostSamples(packet->timestamp - end_timestamp);
+      stats_->LostSamples(packet->timestamp - end_timestamp);
     }
 
     if (*operation != kRfc3389Cng) {
@@ -1460,10 +1469,10 @@
   // Update in-call and post-call statistics.
   if (expand_->MuteFactor(0) == 0) {
     // Expand generates only noise.
-    stats_.ExpandedNoiseSamplesCorrection(expand_length_correction);
+    stats_->ExpandedNoiseSamplesCorrection(expand_length_correction);
   } else {
     // Expansion generates more than only noise.
-    stats_.ExpandedVoiceSamplesCorrection(expand_length_correction);
+    stats_->ExpandedVoiceSamplesCorrection(expand_length_correction);
   }
 
   last_mode_ = kModeMerge;
@@ -1504,12 +1513,12 @@
   if (std::all_of(concealment_audio_.cbegin(), concealment_audio_.cend(),
                   [](int16_t i) { return i == 0; })) {
     // Expand operation generates only noise.
-    stats_.ExpandedNoiseSamples(concealed_samples_per_channel,
-                                is_new_concealment_event);
+    stats_->ExpandedNoiseSamples(concealed_samples_per_channel,
+                                 is_new_concealment_event);
   } else {
     // Expand operation generates more than only noise.
-    stats_.ExpandedVoiceSamples(concealed_samples_per_channel,
-                                is_new_concealment_event);
+    stats_->ExpandedVoiceSamples(concealed_samples_per_channel,
+                                 is_new_concealment_event);
   }
   last_mode_ = kModeCodecPlc;
   if (!generated_noise_stopwatch_) {
@@ -1530,10 +1539,10 @@
     // Update in-call and post-call statistics.
     if (expand_->MuteFactor(0) == 0) {
       // Expand operation generates only noise.
-      stats_.ExpandedNoiseSamples(length, is_new_concealment_event);
+      stats_->ExpandedNoiseSamples(length, is_new_concealment_event);
     } else {
       // Expand operation generates more than only noise.
-      stats_.ExpandedVoiceSamples(length, is_new_concealment_event);
+      stats_->ExpandedVoiceSamples(length, is_new_concealment_event);
     }
 
     last_mode_ = kModeExpand;
@@ -1582,7 +1591,7 @@
   Accelerate::ReturnCodes return_code =
       accelerate_->Process(decoded_buffer, decoded_length, fast_accelerate,
                            algorithm_buffer_.get(), &samples_removed);
-  stats_.AcceleratedSamples(samples_removed);
+  stats_->AcceleratedSamples(samples_removed);
   switch (return_code) {
     case Accelerate::kSuccess:
       last_mode_ = kModeAccelerateSuccess;
@@ -1660,7 +1669,7 @@
   PreemptiveExpand::ReturnCodes return_code = preemptive_expand_->Process(
       decoded_buffer, decoded_length, old_borrowed_samples_per_channel,
       algorithm_buffer_.get(), &samples_added);
-  stats_.PreemptiveExpandedSamples(samples_added);
+  stats_->PreemptiveExpandedSamples(samples_added);
   switch (return_code) {
     case PreemptiveExpand::kSuccess:
       last_mode_ = kModePreemptiveExpandSuccess;
@@ -1875,7 +1884,7 @@
       return -1;
     }
     const uint64_t waiting_time_ms = packet->waiting_time->ElapsedMs();
-    stats_.StoreWaitingTime(waiting_time_ms);
+    stats_->StoreWaitingTime(waiting_time_ms);
     RTC_DCHECK(!packet->empty());
 
     if (first_packet) {
@@ -1899,7 +1908,7 @@
       packet_duration = packet->frame->Duration();
       // TODO(ossu): Is this the correct way to track Opus FEC packets?
       if (packet->priority.codec_level > 0) {
-        stats_.SecondaryDecodedSamples(
+        stats_->SecondaryDecodedSamples(
             rtc::dchecked_cast<int>(packet_duration));
       }
     } else if (!has_cng_packet) {
@@ -1915,7 +1924,7 @@
     }
     extracted_samples = packet->timestamp - first_timestamp + packet_duration;
 
-    stats_.JitterBufferDelay(packet_duration, waiting_time_ms);
+    stats_->JitterBufferDelay(packet_duration, waiting_time_ms);
 
     packet_list->push_back(std::move(*packet));  // Store packet in list.
     packet = absl::nullopt;  // Ensure it's never used after the move.
@@ -1943,7 +1952,7 @@
     // we could end up in the situation where we never decode anything, since
     // all incoming packets are considered too old but the buffer will also
     // never be flooded and flushed.
-    packet_buffer_->DiscardAllOldPackets(timestamp_, &stats_);
+    packet_buffer_->DiscardAllOldPackets(timestamp_, stats_.get());
   }
 
   return rtc::dchecked_cast<int>(extracted_samples);
@@ -1953,7 +1962,7 @@
   // Delete objects and create new ones.
   expand_.reset(expand_factory_->Create(background_noise_.get(),
                                         sync_buffer_.get(), &random_vector_,
-                                        &stats_, fs_hz, channels));
+                                        stats_.get(), fs_hz, channels));
   merge_.reset(new Merge(fs_hz, channels, expand_.get(), sync_buffer_.get()));
 }
 
diff --git a/modules/audio_coding/neteq/neteq_impl.h b/modules/audio_coding/neteq/neteq_impl.h
index 23b63eb..34a5c71 100644
--- a/modules/audio_coding/neteq/neteq_impl.h
+++ b/modules/audio_coding/neteq/neteq_impl.h
@@ -99,6 +99,7 @@
     ~Dependencies();
 
     std::unique_ptr<TickTimer> tick_timer;
+    std::unique_ptr<StatisticsCalculator> stats;
     std::unique_ptr<BufferLevelFilter> buffer_level_filter;
     std::unique_ptr<DecoderDatabase> decoder_database;
     std::unique_ptr<DelayPeakDetector> delay_peak_detector;
@@ -361,6 +362,7 @@
       RTC_GUARDED_BY(crit_sect_);
   const std::unique_ptr<PreemptiveExpandFactory> preemptive_expand_factory_
       RTC_GUARDED_BY(crit_sect_);
+  const std::unique_ptr<StatisticsCalculator> stats_ RTC_GUARDED_BY(crit_sect_);
 
   std::unique_ptr<BackgroundNoise> background_noise_ RTC_GUARDED_BY(crit_sect_);
   std::unique_ptr<DecisionLogic> decision_logic_ RTC_GUARDED_BY(crit_sect_);
@@ -375,7 +377,6 @@
       RTC_GUARDED_BY(crit_sect_);
   RandomVector random_vector_ RTC_GUARDED_BY(crit_sect_);
   std::unique_ptr<ComfortNoise> comfort_noise_ RTC_GUARDED_BY(crit_sect_);
-  StatisticsCalculator stats_ RTC_GUARDED_BY(crit_sect_);
   int fs_hz_ RTC_GUARDED_BY(crit_sect_);
   int fs_mult_ RTC_GUARDED_BY(crit_sect_);
   int last_output_sample_rate_hz_ RTC_GUARDED_BY(crit_sect_);
diff --git a/modules/audio_coding/neteq/neteq_impl_unittest.cc b/modules/audio_coding/neteq/neteq_impl_unittest.cc
index 86fbe9c..5875493 100644
--- a/modules/audio_coding/neteq/neteq_impl_unittest.cc
+++ b/modules/audio_coding/neteq/neteq_impl_unittest.cc
@@ -26,6 +26,7 @@
 #include "modules/audio_coding/neteq/mock/mock_red_payload_splitter.h"
 #include "modules/audio_coding/neteq/neteq_impl.h"
 #include "modules/audio_coding/neteq/preemptive_expand.h"
+#include "modules/audio_coding/neteq/statistics_calculator.h"
 #include "modules/audio_coding/neteq/sync_buffer.h"
 #include "modules/audio_coding/neteq/timestamp_scaler.h"
 #include "rtc_base/numerics/safe_conversions.h"
@@ -100,7 +101,7 @@
           config_.max_packets_in_buffer, config_.min_delay_ms, 1020054733,
           DelayManager::HistogramMode::INTER_ARRIVAL_TIME,
           config_.enable_rtx_handling, delay_peak_detector_, tick_timer_,
-          absl::make_unique<Histogram>(50, 32745)));
+          deps.stats.get(), absl::make_unique<Histogram>(50, 32745)));
       mock_delay_manager_ = mock.get();
       EXPECT_CALL(*mock_delay_manager_, set_streaming_mode(false)).Times(1);
       deps.delay_manager = std::move(mock);
diff --git a/modules/audio_coding/neteq/statistics_calculator.cc b/modules/audio_coding/neteq/statistics_calculator.cc
index 7ad1a28..a0e9bca 100644
--- a/modules/audio_coding/neteq/statistics_calculator.cc
+++ b/modules/audio_coding/neteq/statistics_calculator.cc
@@ -258,6 +258,14 @@
   buffer_full_counter_.RegisterSample();
 }
 
+void StatisticsCalculator::ReceivedPacket() {
+  ++lifetime_stats_.jitter_buffer_packets_received;
+}
+
+void StatisticsCalculator::RelativePacketArrivalDelay(size_t delay_ms) {
+  lifetime_stats_.relative_packet_arrival_delay_ms += delay_ms;
+}
+
 void StatisticsCalculator::LogDelayedPacketOutageEvent(int num_samples,
                                                        int fs_hz) {
   int outage_duration_ms = num_samples / (fs_hz / 1000);
diff --git a/modules/audio_coding/neteq/statistics_calculator.h b/modules/audio_coding/neteq/statistics_calculator.h
index 1dee643..cb92f37 100644
--- a/modules/audio_coding/neteq/statistics_calculator.h
+++ b/modules/audio_coding/neteq/statistics_calculator.h
@@ -83,9 +83,15 @@
   // Reports that |num_samples| samples were decoded from secondary packets.
   void SecondaryDecodedSamples(int num_samples);
 
-  // Rerport that the packet buffer was flushed.
+  // Reports that the packet buffer was flushed.
   void FlushedPacketBuffer();
 
+  // Reports that the jitter buffer received a packet.
+  void ReceivedPacket();
+
+  // Reports that a received packet was delayed by |delay_ms| milliseconds.
+  virtual void RelativePacketArrivalDelay(size_t delay_ms);
+
   // Logs a delayed packet outage event of |num_samples| expanded at a sample
   // rate of |fs_hz|. A delayed packet outage event is defined as an expand
   // period caused not by an actual packet loss, but by a delayed packet.
diff --git a/modules/audio_coding/neteq/statistics_calculator_unittest.cc b/modules/audio_coding/neteq/statistics_calculator_unittest.cc
index 0a4901d..1fb8e1c 100644
--- a/modules/audio_coding/neteq/statistics_calculator_unittest.cc
+++ b/modules/audio_coding/neteq/statistics_calculator_unittest.cc
@@ -104,4 +104,28 @@
   EXPECT_EQ((50u << 14) / k10MsSamples, stats_output.speech_expand_rate);
 }
 
+TEST(StatisticsCalculator, RelativePacketArrivalDelay) {
+  StatisticsCalculator stats;
+
+  stats.RelativePacketArrivalDelay(50);
+  NetEqLifetimeStatistics stats_output = stats.GetLifetimeStatistics();
+  EXPECT_EQ(50u, stats_output.relative_packet_arrival_delay_ms);
+
+  stats.RelativePacketArrivalDelay(20);
+  stats_output = stats.GetLifetimeStatistics();
+  EXPECT_EQ(70u, stats_output.relative_packet_arrival_delay_ms);
+}
+
+TEST(StatisticsCalculator, ReceivedPacket) {
+  StatisticsCalculator stats;
+
+  stats.ReceivedPacket();
+  NetEqLifetimeStatistics stats_output = stats.GetLifetimeStatistics();
+  EXPECT_EQ(1u, stats_output.jitter_buffer_packets_received);
+
+  stats.ReceivedPacket();
+  stats_output = stats.GetLifetimeStatistics();
+  EXPECT_EQ(2u, stats_output.jitter_buffer_packets_received);
+}
+
 }  // namespace webrtc