Add stats totalSamplesReceived and concealedSamples

Adds two new stats to RTCMediaStreamTrackStats:
* totalSamplesReceived is the total number of samples received on
      the audio channel and includes real and synthetic samples.
* concealedSamples is the total number of synthetic samples
      received on the audio channel used to conceal packet loss.

Bug: webrtc:8076
Change-Id: I36e9828525ec341490cf3310a972b56a8b443667
Reviewed-on: https://chromium-review.googlesource.com/615902
Commit-Queue: Steve Anton <steveanton@webrtc.org>
Reviewed-by: Tommi <tommi@webrtc.org>
Reviewed-by: Taylor Brandstetter <deadbeef@webrtc.org>
Reviewed-by: Henrik Boström <hbos@webrtc.org>
Reviewed-by: Henrik Lundin <henrik.lundin@webrtc.org>
Cr-Original-Commit-Position: refs/heads/master@{#19506}
Cr-Mirrored-From: https://chromium.googlesource.com/external/webrtc
Cr-Mirrored-Commit: 2dbc69fa64a7ea183e090dbfaa68ab16d4bcef7a
diff --git a/api/stats/rtcstats_objects.h b/api/stats/rtcstats_objects.h
index 071a7fb..0cbea7c 100644
--- a/api/stats/rtcstats_objects.h
+++ b/api/stats/rtcstats_objects.h
@@ -273,9 +273,11 @@
   // Audio-only members
   RTCStatsMember<double> audio_level;
   RTCStatsMember<double> total_audio_energy;
-  RTCStatsMember<double> total_samples_duration;
   RTCStatsMember<double> echo_return_loss;
   RTCStatsMember<double> echo_return_loss_enhancement;
+  RTCStatsMember<uint64_t> total_samples_received;
+  RTCStatsMember<double> total_samples_duration;
+  RTCStatsMember<uint64_t> concealed_samples;
 };
 
 // https://w3c.github.io/webrtc-stats/#pcstats-dict*
diff --git a/api/statstypes.cc b/api/statstypes.cc
index 777e9f8..fb9e1e5 100644
--- a/api/statstypes.cc
+++ b/api/statstypes.cc
@@ -371,6 +371,8 @@
       return "audioInputLevel";
     case kStatsValueNameBytesSent:
       return "bytesSent";
+    case kStatsValueNameConcealedSamples:
+      return "concealedSamples";
     case kStatsValueNamePacketsSent:
       return "packetsSent";
     case kStatsValueNameBytesReceived:
@@ -383,6 +385,8 @@
       return "packetsLost";
     case kStatsValueNameProtocol:
       return "protocol";
+    case kStatsValueNameTotalSamplesReceived:
+      return "totalSamplesReceived";
     case kStatsValueNameTransportId:
       return "transportId";
     case kStatsValueNameSelectedCandidatePairId:
diff --git a/api/statstypes.h b/api/statstypes.h
index 57fe82d..819bfe0 100644
--- a/api/statstypes.h
+++ b/api/statstypes.h
@@ -104,6 +104,7 @@
     kStatsValueNameBytesReceived,
     kStatsValueNameBytesSent,
     kStatsValueNameCodecImplementationName,
+    kStatsValueNameConcealedSamples,
     kStatsValueNameDataChannelId,
     kStatsValueNameFramesDecoded,
     kStatsValueNameFramesEncoded,
@@ -120,6 +121,7 @@
     kStatsValueNameState,
     kStatsValueNameTotalAudioEnergy,
     kStatsValueNameTotalSamplesDuration,
+    kStatsValueNameTotalSamplesReceived,
     kStatsValueNameTransportId,
     kStatsValueNameSentPingRequestsTotal,
     kStatsValueNameSentPingRequestsBeforeFirstResponse,
diff --git a/audio/audio_receive_stream.cc b/audio/audio_receive_stream.cc
index be5c18a..d2200f4 100644
--- a/audio/audio_receive_stream.cc
+++ b/audio/audio_receive_stream.cc
@@ -194,6 +194,8 @@
   auto ns = channel_proxy_->GetNetworkStatistics();
   stats.jitter_buffer_ms = ns.currentBufferSize;
   stats.jitter_buffer_preferred_ms = ns.preferredBufferSize;
+  stats.total_samples_received = ns.totalSamplesReceived;
+  stats.concealed_samples = ns.concealedSamples;
   stats.expand_rate = Q14ToFloat(ns.currentExpandRate);
   stats.speech_expand_rate = Q14ToFloat(ns.currentSpeechExpandRate);
   stats.secondary_decoded_rate = Q14ToFloat(ns.currentSecondaryDecodedRate);
diff --git a/audio/audio_receive_stream_unittest.cc b/audio/audio_receive_stream_unittest.cc
index 0b1d47a..9723ee65 100644
--- a/audio/audio_receive_stream_unittest.cc
+++ b/audio/audio_receive_stream_unittest.cc
@@ -64,8 +64,9 @@
     345,  678,  901, 234, -12, 3456, 7890, 567, 890, 123};
 const CodecInst kCodecInst = {
     123, "codec_name_recv", 96000, -187, 0, -103};
-const NetworkStatistics kNetworkStats = {
-    123, 456, false, 0, {}, 789, 12, 345, 678, 901, 0, -1, -1, -1, -1, -1, 0};
+const NetworkStatistics kNetworkStats = {123, 456, false, 789012, 3456, 0, {},
+                                         789, 12,  345,   678,    901,  0, -1,
+                                         -1,  -1,  -1,    -1,     0};
 const AudioDecodingCallStats kAudioDecodeStats = MakeAudioDecodeStatsForTest();
 
 struct ConfigHelper {
@@ -318,7 +319,9 @@
             stats.delay_estimate_ms);
   EXPECT_EQ(static_cast<int32_t>(kSpeechOutputLevel), stats.audio_level);
   EXPECT_EQ(kTotalOutputEnergy, stats.total_output_energy);
+  EXPECT_EQ(kNetworkStats.totalSamplesReceived, stats.total_samples_received);
   EXPECT_EQ(kTotalOutputDuration, stats.total_output_duration);
+  EXPECT_EQ(kNetworkStats.concealedSamples, stats.concealed_samples);
   EXPECT_EQ(Q14ToFloat(kNetworkStats.currentExpandRate), stats.expand_rate);
   EXPECT_EQ(Q14ToFloat(kNetworkStats.currentSpeechExpandRate),
             stats.speech_expand_rate);
diff --git a/call/audio_receive_stream.h b/call/audio_receive_stream.h
index d692425..78f1bff 100644
--- a/call/audio_receive_stream.h
+++ b/call/audio_receive_stream.h
@@ -52,7 +52,15 @@
     // See description of "totalAudioEnergy" in the WebRTC stats spec:
     // https://w3c.github.io/webrtc-stats/#dom-rtcmediastreamtrackstats-totalaudioenergy
     double total_output_energy = 0.0;
+    // See description of "totalSamplesReceived" in the WebRTC stats spec:
+    // https://w3c.github.io/webrtc-stats/#dom-rtcmediastreamtrackstats-totalsamplesreceived
+    uint64_t total_samples_received = 0;
+    // See description of "totalSamplesDuration" in the WebRTC stats spec:
+    // https://w3c.github.io/webrtc-stats/#dom-rtcmediastreamtrackstats-totalsamplesduration
     double total_output_duration = 0.0;
+    // See description of "concealedSamples" in the WebRTC stats spec:
+    // https://w3c.github.io/webrtc-stats/#dom-rtcmediastreamtrackstats-concealedsamples
+    uint64_t concealed_samples = 0;
     float expand_rate = 0.0f;
     float speech_expand_rate = 0.0f;
     float secondary_decoded_rate = 0.0f;
diff --git a/common_types.h b/common_types.h
index 4034d54..c4b5ce8 100644
--- a/common_types.h
+++ b/common_types.h
@@ -367,6 +367,13 @@
   uint16_t preferredBufferSize;
   // adding extra delay due to "peaky jitter"
   bool jitterPeaksFound;
+  // Total number of audio samples received, including synthesized samples.
+  // https://w3c.github.io/webrtc-stats/#dom-rtcmediastreamtrackstats-totalsamplesreceived
+  uint64_t totalSamplesReceived;
+  // Total number of inbound audio samples that are based on synthesized data to
+  // conceal packet loss.
+  // https://w3c.github.io/webrtc-stats/#dom-rtcmediastreamtrackstats-concealedsamples
+  uint64_t concealedSamples;
   // Loss rate (network + late); fraction between 0 and 1, scaled to Q14.
   uint16_t currentPacketLossRate;
   // Late loss rate; fraction between 0 and 1, scaled to Q14.
diff --git a/media/base/mediachannel.h b/media/base/mediachannel.h
index baa8c3a..76328ef 100644
--- a/media/base/mediachannel.h
+++ b/media/base/mediachannel.h
@@ -652,7 +652,9 @@
         delay_estimate_ms(0),
         audio_level(0),
         total_output_energy(0.0),
+        total_samples_received(0),
         total_output_duration(0.0),
+        concealed_samples(0),
         expand_rate(0),
         speech_expand_rate(0),
         secondary_decoded_rate(0),
@@ -676,7 +678,15 @@
   // See description of "totalAudioEnergy" in the WebRTC stats spec:
   // https://w3c.github.io/webrtc-stats/#dom-rtcmediastreamtrackstats-totalaudioenergy
   double total_output_energy;
+  // See description of "totalSamplesReceived" in the WebRTC stats spec:
+  // https://w3c.github.io/webrtc-stats/#dom-rtcmediastreamtrackstats-totalsamplesreceived
+  uint64_t total_samples_received;
+  // See description of "totalSamplesDuration" in the WebRTC stats spec:
+  // https://w3c.github.io/webrtc-stats/#dom-rtcmediastreamtrackstats-totalsamplesduration
   double total_output_duration;
+  // See description of "concealedSamples" in the WebRTC stats spec:
+  // https://w3c.github.io/webrtc-stats/#dom-rtcmediastreamtrackstats-concealedsamples
+  uint64_t concealed_samples;
   // fraction of synthesized audio inserted through expansion.
   float expand_rate;
   // fraction of synthesized speech inserted through expansion.
diff --git a/media/engine/webrtcvoiceengine.cc b/media/engine/webrtcvoiceengine.cc
index b6a84da..fdbb5c7 100644
--- a/media/engine/webrtcvoiceengine.cc
+++ b/media/engine/webrtcvoiceengine.cc
@@ -2282,7 +2282,9 @@
     rinfo.delay_estimate_ms = stats.delay_estimate_ms;
     rinfo.audio_level = stats.audio_level;
     rinfo.total_output_energy = stats.total_output_energy;
+    rinfo.total_samples_received = stats.total_samples_received;
     rinfo.total_output_duration = stats.total_output_duration;
+    rinfo.concealed_samples = stats.concealed_samples;
     rinfo.expand_rate = stats.expand_rate;
     rinfo.speech_expand_rate = stats.speech_expand_rate;
     rinfo.secondary_decoded_rate = stats.secondary_decoded_rate;
diff --git a/media/engine/webrtcvoiceengine_unittest.cc b/media/engine/webrtcvoiceengine_unittest.cc
index d5743ca..28d09e8 100644
--- a/media/engine/webrtcvoiceengine_unittest.cc
+++ b/media/engine/webrtcvoiceengine_unittest.cc
@@ -596,6 +596,8 @@
     stats.jitter_buffer_preferred_ms = 567;
     stats.delay_estimate_ms = 890;
     stats.audio_level = 1234;
+    stats.total_samples_received = 5678901;
+    stats.concealed_samples = 234;
     stats.expand_rate = 5.67f;
     stats.speech_expand_rate = 8.90f;
     stats.secondary_decoded_rate = 1.23f;
@@ -632,6 +634,8 @@
               stats.jitter_buffer_preferred_ms);
     EXPECT_EQ(info.delay_estimate_ms, stats.delay_estimate_ms);
     EXPECT_EQ(info.audio_level, stats.audio_level);
+    EXPECT_EQ(info.total_samples_received, stats.total_samples_received);
+    EXPECT_EQ(info.concealed_samples, stats.concealed_samples);
     EXPECT_EQ(info.expand_rate, stats.expand_rate);
     EXPECT_EQ(info.speech_expand_rate, stats.speech_expand_rate);
     EXPECT_EQ(info.secondary_decoded_rate, stats.secondary_decoded_rate);
diff --git a/modules/audio_coding/acm2/acm_receiver.cc b/modules/audio_coding/acm2/acm_receiver.cc
index b512f84..8912eaa 100644
--- a/modules/audio_coding/acm2/acm_receiver.cc
+++ b/modules/audio_coding/acm2/acm_receiver.cc
@@ -332,6 +332,10 @@
   acm_stat->medianWaitingTimeMs = neteq_stat.median_waiting_time_ms;
   acm_stat->minWaitingTimeMs = neteq_stat.min_waiting_time_ms;
   acm_stat->maxWaitingTimeMs = neteq_stat.max_waiting_time_ms;
+
+  NetEqLifetimeStatistics neteq_lifetime_stat = neteq_->GetLifetimeStatistics();
+  acm_stat->totalSamplesReceived = neteq_lifetime_stat.total_samples_received;
+  acm_stat->concealedSamples = neteq_lifetime_stat.concealed_samples;
 }
 
 int AcmReceiver::DecoderByPayloadType(uint8_t payload_type,
diff --git a/modules/audio_coding/neteq/include/neteq.h b/modules/audio_coding/neteq/include/neteq.h
index d05b76e..56c30e5 100644
--- a/modules/audio_coding/neteq/include/neteq.h
+++ b/modules/audio_coding/neteq/include/neteq.h
@@ -58,6 +58,18 @@
   int max_waiting_time_ms;
 };
 
+// NetEq statistics that persist over the lifetime of the class.
+// These metrics are never reset.
+struct NetEqLifetimeStatistics {
+  // Total number of audio samples received, including synthesized samples.
+  // https://w3c.github.io/webrtc-stats/#dom-rtcmediastreamtrackstats-totalsamplesreceived
+  uint64_t total_samples_received = 0;
+  // Total number of inbound audio samples that are based on synthesized data to
+  // conceal packet loss.
+  // https://w3c.github.io/webrtc-stats/#dom-rtcmediastreamtrackstats-concealedsamples
+  uint64_t concealed_samples = 0;
+};
+
 enum NetEqPlayoutMode {
   kPlayoutOn,
   kPlayoutOff,
@@ -220,6 +232,10 @@
   // after the call.
   virtual int NetworkStatistics(NetEqNetworkStatistics* stats) = 0;
 
+  // Returns a copy of this class's lifetime statistics. These statistics are
+  // never reset.
+  virtual NetEqLifetimeStatistics GetLifetimeStatistics() const = 0;
+
   // Writes the current RTCP statistics to |stats|. The statistics are reset
   // and a new report period is started with the call.
   virtual void GetRtcpStatistics(RtcpStatistics* stats) = 0;
diff --git a/modules/audio_coding/neteq/neteq_impl.cc b/modules/audio_coding/neteq/neteq_impl.cc
index 4b95253..7858e84 100644
--- a/modules/audio_coding/neteq/neteq_impl.cc
+++ b/modules/audio_coding/neteq/neteq_impl.cc
@@ -380,6 +380,11 @@
   return 0;
 }
 
+NetEqLifetimeStatistics NetEqImpl::GetLifetimeStatistics() const {
+  rtc::CritScope lock(&crit_sect_);
+  return stats_.GetLifetimeStatistics();
+}
+
 void NetEqImpl::GetRtcpStatistics(RtcpStatistics* stats) {
   rtc::CritScope lock(&crit_sect_);
   if (stats) {
diff --git a/modules/audio_coding/neteq/neteq_impl.h b/modules/audio_coding/neteq/neteq_impl.h
index 0eeff2e..f4b014a 100644
--- a/modules/audio_coding/neteq/neteq_impl.h
+++ b/modules/audio_coding/neteq/neteq_impl.h
@@ -185,6 +185,8 @@
   // and a new report period is started with the call.
   void GetRtcpStatistics(RtcpStatistics* stats) override;
 
+  NetEqLifetimeStatistics GetLifetimeStatistics() const override;
+
   // Same as RtcpStatistics(), but does not reset anything.
   void GetRtcpStatisticsNoReset(RtcpStatistics* stats) override;
 
diff --git a/modules/audio_coding/neteq/statistics_calculator.cc b/modules/audio_coding/neteq/statistics_calculator.cc
index 3faed62..d7d1644 100644
--- a/modules/audio_coding/neteq/statistics_calculator.cc
+++ b/modules/audio_coding/neteq/statistics_calculator.cc
@@ -153,24 +153,29 @@
 
 void StatisticsCalculator::ExpandedVoiceSamples(size_t num_samples) {
   expanded_speech_samples_ += num_samples;
+  lifetime_stats_.concealed_samples += num_samples;
 }
 
 void StatisticsCalculator::ExpandedNoiseSamples(size_t num_samples) {
   expanded_noise_samples_ += num_samples;
+  lifetime_stats_.concealed_samples += num_samples;
 }
 
 void StatisticsCalculator::ExpandedVoiceSamplesCorrection(int num_samples) {
   expanded_speech_samples_ =
       AddIntToSizeTWithLowerCap(num_samples, expanded_speech_samples_);
+  lifetime_stats_.concealed_samples += num_samples;
 }
 
 void StatisticsCalculator::ExpandedNoiseSamplesCorrection(int num_samples) {
   expanded_noise_samples_ =
       AddIntToSizeTWithLowerCap(num_samples, expanded_noise_samples_);
+  lifetime_stats_.concealed_samples += num_samples;
 }
 
 void StatisticsCalculator::PreemptiveExpandedSamples(size_t num_samples) {
   preemptive_samples_ += num_samples;
+  lifetime_stats_.concealed_samples += num_samples;
 }
 
 void StatisticsCalculator::AcceleratedSamples(size_t num_samples) {
@@ -205,6 +210,7 @@
     timestamps_since_last_report_ = 0;
     discarded_packets_ = 0;
   }
+  lifetime_stats_.total_samples_received += num_samples;
 }
 
 void StatisticsCalculator::SecondaryDecodedSamples(int num_samples) {
@@ -307,6 +313,10 @@
   Reset();
 }
 
+NetEqLifetimeStatistics StatisticsCalculator::GetLifetimeStatistics() const {
+  return lifetime_stats_;
+}
+
 uint16_t StatisticsCalculator::CalculateQ14Ratio(size_t numerator,
                                                  uint32_t denominator) {
   if (numerator == 0) {
diff --git a/modules/audio_coding/neteq/statistics_calculator.h b/modules/audio_coding/neteq/statistics_calculator.h
index 2877a16..f261a66 100644
--- a/modules/audio_coding/neteq/statistics_calculator.h
+++ b/modules/audio_coding/neteq/statistics_calculator.h
@@ -99,6 +99,10 @@
                             const DecisionLogic& decision_logic,
                             NetEqNetworkStatistics *stats);
 
+  // Returns a copy of this class's lifetime statistics. These statistics are
+  // never reset.
+  NetEqLifetimeStatistics GetLifetimeStatistics() const;
+
  private:
   static const int kMaxReportPeriod = 60;  // Seconds before auto-reset.
   static const size_t kLenWaitingTimes = 100;
@@ -158,6 +162,8 @@
   // Calculates numerator / denominator, and returns the value in Q14.
   static uint16_t CalculateQ14Ratio(size_t numerator, uint32_t denominator);
 
+  // TODO(steveanton): Add unit tests for the lifetime stats.
+  NetEqLifetimeStatistics lifetime_stats_;
   size_t preemptive_samples_;
   size_t accelerate_samples_;
   size_t added_zero_samples_;
diff --git a/pc/rtcstats_integrationtest.cc b/pc/rtcstats_integrationtest.cc
index 56a4ef1..b886bcc 100644
--- a/pc/rtcstats_integrationtest.cc
+++ b/pc/rtcstats_integrationtest.cc
@@ -525,6 +525,18 @@
       verifier.MarkMemberTested(
           media_stream_track.echo_return_loss_enhancement, true);
     }
+    // totalSamplesReceived and concealedSamples are only present on inbound
+    // audio tracks.
+    if (*media_stream_track.kind == RTCMediaStreamTrackKind::kAudio &&
+        *media_stream_track.remote_source) {
+      verifier.TestMemberIsNonNegative<uint64_t>(
+          media_stream_track.total_samples_received);
+      verifier.TestMemberIsNonNegative<uint64_t>(
+          media_stream_track.concealed_samples);
+    } else {
+      verifier.TestMemberIsUndefined(media_stream_track.total_samples_received);
+      verifier.TestMemberIsUndefined(media_stream_track.concealed_samples);
+    }
     return verifier.ExpectAllMembersSuccessfullyTested();
   }
 
diff --git a/pc/rtcstatscollector.cc b/pc/rtcstatscollector.cc
index 4952012..5c506e4 100644
--- a/pc/rtcstatscollector.cc
+++ b/pc/rtcstatscollector.cc
@@ -424,8 +424,11 @@
   }
   audio_track_stats->total_audio_energy =
       voice_receiver_info.total_output_energy;
+  audio_track_stats->total_samples_received =
+      voice_receiver_info.total_samples_received;
   audio_track_stats->total_samples_duration =
       voice_receiver_info.total_output_duration;
+  audio_track_stats->concealed_samples = voice_receiver_info.concealed_samples;
   return audio_track_stats;
 }
 
diff --git a/pc/rtcstatscollector_unittest.cc b/pc/rtcstatscollector_unittest.cc
index 4f3b35f..cc258ed 100644
--- a/pc/rtcstatscollector_unittest.cc
+++ b/pc/rtcstatscollector_unittest.cc
@@ -1554,7 +1554,9 @@
   voice_receiver_info.local_stats[0].ssrc = 3;
   voice_receiver_info.audio_level = 16383;
   voice_receiver_info.total_output_energy = 0.125;
+  voice_receiver_info.total_samples_received = 4567;
   voice_receiver_info.total_output_duration = 0.25;
+  voice_receiver_info.concealed_samples = 123;
 
   test_->CreateMockRtpSendersReceiversAndChannels(
       { std::make_pair(local_audio_track.get(), voice_sender_info_ssrc1),
@@ -1628,7 +1630,9 @@
   expected_remote_audio_track.detached = false;
   expected_remote_audio_track.audio_level = 16383.0 / 32767.0;
   expected_remote_audio_track.total_audio_energy = 0.125;
+  expected_remote_audio_track.total_samples_received = 4567;
   expected_remote_audio_track.total_samples_duration = 0.25;
+  expected_remote_audio_track.concealed_samples = 123;
   ASSERT_TRUE(report->Get(expected_remote_audio_track.id()));
   EXPECT_EQ(expected_remote_audio_track,
             report->Get(expected_remote_audio_track.id())->cast_to<
diff --git a/stats/rtcstats_objects.cc b/stats/rtcstats_objects.cc
index 19e94b3..284dfe0 100644
--- a/stats/rtcstats_objects.cc
+++ b/stats/rtcstats_objects.cc
@@ -379,9 +379,11 @@
                      &full_frames_lost,
                      &audio_level,
                      &total_audio_energy,
-                     &total_samples_duration,
                      &echo_return_loss,
-                     &echo_return_loss_enhancement);
+                     &echo_return_loss_enhancement,
+                     &total_samples_received,
+                     &total_samples_duration,
+                     &concealed_samples);
 // clang-format on
 
 RTCMediaStreamTrackStats::RTCMediaStreamTrackStats(
@@ -410,9 +412,11 @@
       full_frames_lost("fullFramesLost"),
       audio_level("audioLevel"),
       total_audio_energy("totalAudioEnergy"),
-      total_samples_duration("totalSamplesDuration"),
       echo_return_loss("echoReturnLoss"),
-      echo_return_loss_enhancement("echoReturnLossEnhancement") {
+      echo_return_loss_enhancement("echoReturnLossEnhancement"),
+      total_samples_received("totalSamplesReceived"),
+      total_samples_duration("totalSamplesDuration"),
+      concealed_samples("concealedSamples") {
   RTC_DCHECK(kind == RTCMediaStreamTrackKind::kAudio ||
              kind == RTCMediaStreamTrackKind::kVideo);
 }
@@ -437,9 +441,11 @@
       full_frames_lost(other.full_frames_lost),
       audio_level(other.audio_level),
       total_audio_energy(other.total_audio_energy),
-      total_samples_duration(other.total_samples_duration),
       echo_return_loss(other.echo_return_loss),
-      echo_return_loss_enhancement(other.echo_return_loss_enhancement) {}
+      echo_return_loss_enhancement(other.echo_return_loss_enhancement),
+      total_samples_received(other.total_samples_received),
+      total_samples_duration(other.total_samples_duration),
+      concealed_samples(other.concealed_samples) {}
 
 RTCMediaStreamTrackStats::~RTCMediaStreamTrackStats() {
 }