Implement nack_count metric for outbound audio rtp streams.

Bug: webrtc:12510
Change-Id: Ia035885bced3c3d202bb9ffeb88c2556d4830e92
Reviewed-on: https://webrtc-review.googlesource.com/c/src/+/225021
Reviewed-by: Sam Zackrisson <saza@webrtc.org>
Reviewed-by: Erik Språng <sprang@webrtc.org>
Reviewed-by: Henrik Boström <hbos@webrtc.org>
Commit-Queue: Jakob Ivarsson <jakobi@webrtc.org>
Cr-Commit-Position: refs/heads/master@{#34444}
diff --git a/api/stats/rtcstats_objects.h b/api/stats/rtcstats_objects.h
index ebc6aff..2030380 100644
--- a/api/stats/rtcstats_objects.h
+++ b/api/stats/rtcstats_objects.h
@@ -553,8 +553,6 @@
   // FIR and PLI counts are only defined for |media_type == "video"|.
   RTCStatsMember<uint32_t> fir_count;
   RTCStatsMember<uint32_t> pli_count;
-  // TODO(hbos): NACK count should be collected by |RTCStatsCollector| for both
-  // audio and video but is only defined in the "video" case. crbug.com/657856
   RTCStatsMember<uint32_t> nack_count;
   RTCStatsMember<uint64_t> qp_sum;
 };
diff --git a/audio/audio_send_stream.cc b/audio/audio_send_stream.cc
index 5d7bc71..62dd53d 100644
--- a/audio/audio_send_stream.cc
+++ b/audio/audio_send_stream.cc
@@ -498,6 +498,8 @@
 
   stats.report_block_datas = std::move(call_stats.report_block_datas);
 
+  stats.nacks_rcvd = call_stats.nacks_rcvd;
+
   return stats;
 }
 
diff --git a/audio/channel_send.cc b/audio/channel_send.cc
index 52dd528..06e9238 100644
--- a/audio/channel_send.cc
+++ b/audio/channel_send.cc
@@ -60,8 +60,9 @@
 class VoERtcpObserver;
 
 class ChannelSend : public ChannelSendInterface,
-                    public AudioPacketizationCallback {  // receive encoded
-                                                         // packets from the ACM
+                    public AudioPacketizationCallback,  // receive encoded
+                                                        // packets from the ACM
+                    public RtcpPacketTypeCounterObserver {
  public:
   // TODO(nisse): Make OnUplinkPacketLossRate public, and delete friend
   // declaration.
@@ -150,6 +151,11 @@
       rtc::scoped_refptr<webrtc::FrameTransformerInterface> frame_transformer)
       override;
 
+  // RtcpPacketTypeCounterObserver.
+  void RtcpPacketTypesCounterUpdated(
+      uint32_t ssrc,
+      const RtcpPacketTypeCounter& packet_counter) override;
+
  private:
   // From AudioPacketizationCallback in the ACM
   int32_t SendData(AudioFrameType frameType,
@@ -187,6 +193,7 @@
 
   mutable Mutex volume_settings_mutex_;
 
+  const uint32_t ssrc_;
   bool sending_ RTC_GUARDED_BY(&worker_thread_checker_) = false;
 
   RtcEventLog* const event_log_;
@@ -239,6 +246,10 @@
   rtc::TaskQueue encoder_queue_;
 
   const bool fixing_timestamp_stall_;
+
+  mutable Mutex rtcp_counter_mutex_;
+  RtcpPacketTypeCounter rtcp_packet_type_counter_
+      RTC_GUARDED_BY(rtcp_counter_mutex_);
 };
 
 const int kTelephoneEventAttenuationdB = 10;
@@ -452,7 +463,8 @@
     uint32_t ssrc,
     rtc::scoped_refptr<FrameTransformerInterface> frame_transformer,
     TransportFeedbackObserver* feedback_observer)
-    : event_log_(rtc_event_log),
+    : ssrc_(ssrc),
+      event_log_(rtc_event_log),
       _timeStamp(0),  // This is just an offset, RTP module will add it's own
                       // random offset
       input_mute_(false),
@@ -487,6 +499,7 @@
       retransmission_rate_limiter_.get();
   configuration.extmap_allow_mixed = extmap_allow_mixed;
   configuration.rtcp_report_interval_ms = rtcp_report_interval_ms;
+  configuration.rtcp_packet_type_counter_observer = this;
 
   configuration.local_media_ssrc = ssrc;
 
@@ -777,9 +790,24 @@
   stats.retransmitted_packets_sent = rtp_stats.retransmitted.packets;
   stats.report_block_datas = rtp_rtcp_->GetLatestReportBlockData();
 
+  {
+    MutexLock lock(&rtcp_counter_mutex_);
+    stats.nacks_rcvd = rtcp_packet_type_counter_.nack_packets;
+  }
+
   return stats;
 }
 
+void ChannelSend::RtcpPacketTypesCounterUpdated(
+    uint32_t ssrc,
+    const RtcpPacketTypeCounter& packet_counter) {
+  if (ssrc != ssrc_) {
+    return;
+  }
+  MutexLock lock(&rtcp_counter_mutex_);
+  rtcp_packet_type_counter_ = packet_counter;
+}
+
 void ChannelSend::ProcessAndEncodeAudio(
     std::unique_ptr<AudioFrame> audio_frame) {
   RTC_DCHECK_RUNS_SERIALIZED(&audio_thread_race_checker_);
diff --git a/audio/channel_send.h b/audio/channel_send.h
index cbdb3ee..67391af 100644
--- a/audio/channel_send.h
+++ b/audio/channel_send.h
@@ -45,6 +45,7 @@
   // ReportBlockData represents the latest Report Block that was received for
   // that pair.
   std::vector<ReportBlockData> report_block_datas;
+  uint32_t nacks_rcvd;
 };
 
 // See section 6.4.2 in http://www.ietf.org/rfc/rfc3550.txt for details.
diff --git a/audio/test/nack_test.cc b/audio/test/nack_test.cc
index 714a877..13cfe74 100644
--- a/audio/test/nack_test.cc
+++ b/audio/test/nack_test.cc
@@ -20,7 +20,7 @@
 TEST_F(NackTest, ShouldNackInLossyNetwork) {
   class NackTest : public AudioEndToEndTest {
    public:
-    const int kTestDurationMs = 1000;
+    const int kTestDurationMs = 2000;
     const int64_t kRttMs = 30;
     const int64_t kLossPercent = 30;
     const int kNackHistoryMs = 1000;
@@ -46,6 +46,9 @@
       AudioReceiveStream::Stats recv_stats =
           receive_stream()->GetStats(/*get_and_clear_legacy_stats=*/true);
       EXPECT_GT(recv_stats.nacks_sent, 0U);
+      AudioSendStream::Stats send_stats = send_stream()->GetStats();
+      EXPECT_GT(send_stats.retransmitted_packets_sent, 0U);
+      EXPECT_GT(send_stats.nacks_rcvd, 0U);
     }
   } test;
 
diff --git a/call/audio_send_stream.h b/call/audio_send_stream.h
index d21dff4..e084d42 100644
--- a/call/audio_send_stream.h
+++ b/call/audio_send_stream.h
@@ -70,6 +70,7 @@
     // per-pair the ReportBlockData represents the latest Report Block that was
     // received for that pair.
     std::vector<ReportBlockData> report_block_datas;
+    uint32_t nacks_rcvd = 0;
   };
 
   struct Config {
diff --git a/media/base/media_channel.h b/media/base/media_channel.h
index 979a5c1..7b9a6f1 100644
--- a/media/base/media_channel.h
+++ b/media/base/media_channel.h
@@ -372,6 +372,8 @@
   int packets_sent = 0;
   // https://w3c.github.io/webrtc-stats/#dom-rtcoutboundrtpstreamstats-retransmittedpacketssent
   uint64_t retransmitted_packets_sent = 0;
+  // https://w3c.github.io/webrtc-stats/#dom-rtcoutboundrtpstreamstats-nackcount
+  uint32_t nacks_rcvd = 0;
   int packets_lost = 0;
   float fraction_lost = 0.0f;
   int64_t rtt_ms = 0;
@@ -535,7 +537,6 @@
   std::string encoder_implementation_name;
   int firs_rcvd = 0;
   int plis_rcvd = 0;
-  int nacks_rcvd = 0;
   int send_frame_width = 0;
   int send_frame_height = 0;
   int frames = 0;
diff --git a/media/engine/webrtc_video_engine_unittest.cc b/media/engine/webrtc_video_engine_unittest.cc
index e2d8312..1cad35a 100644
--- a/media/engine/webrtc_video_engine_unittest.cc
+++ b/media/engine/webrtc_video_engine_unittest.cc
@@ -1894,7 +1894,7 @@
   EXPECT_EQ(DefaultCodec().id, *info.senders[0].codec_payload_type);
   EXPECT_EQ(0, info.senders[0].firs_rcvd);
   EXPECT_EQ(0, info.senders[0].plis_rcvd);
-  EXPECT_EQ(0, info.senders[0].nacks_rcvd);
+  EXPECT_EQ(0u, info.senders[0].nacks_rcvd);
   EXPECT_EQ(kVideoWidth, info.senders[0].send_frame_width);
   EXPECT_EQ(kVideoHeight, info.senders[0].send_frame_height);
   EXPECT_GT(info.senders[0].framerate_input, 0);
@@ -5555,7 +5555,7 @@
   // Comes from substream only.
   EXPECT_EQ(sender.firs_rcvd, 0);
   EXPECT_EQ(sender.plis_rcvd, 0);
-  EXPECT_EQ(sender.nacks_rcvd, 0);
+  EXPECT_EQ(sender.nacks_rcvd, 0u);
   EXPECT_EQ(sender.send_frame_width, 0);
   EXPECT_EQ(sender.send_frame_height, 0);
 
@@ -5679,9 +5679,8 @@
   EXPECT_EQ(
       sender.plis_rcvd,
       static_cast<int>(2 * substream.rtcp_packet_type_counts.pli_packets));
-  EXPECT_EQ(
-      sender.nacks_rcvd,
-      static_cast<int>(2 * substream.rtcp_packet_type_counts.nack_packets));
+  EXPECT_EQ(sender.nacks_rcvd,
+            2 * substream.rtcp_packet_type_counts.nack_packets);
   EXPECT_EQ(sender.send_frame_width, substream.width);
   EXPECT_EQ(sender.send_frame_height, substream.height);
 
@@ -5800,8 +5799,7 @@
             static_cast<int>(substream.rtcp_packet_type_counts.fir_packets));
   EXPECT_EQ(sender.plis_rcvd,
             static_cast<int>(substream.rtcp_packet_type_counts.pli_packets));
-  EXPECT_EQ(sender.nacks_rcvd,
-            static_cast<int>(substream.rtcp_packet_type_counts.nack_packets));
+  EXPECT_EQ(sender.nacks_rcvd, substream.rtcp_packet_type_counts.nack_packets);
   EXPECT_EQ(sender.send_frame_width, substream.width);
   EXPECT_EQ(sender.send_frame_height, substream.height);
 
@@ -6122,15 +6120,15 @@
   cricket::VideoMediaInfo info;
   ASSERT_TRUE(channel_->GetStats(&info));
   EXPECT_EQ(2, info.senders[0].firs_rcvd);
-  EXPECT_EQ(3, info.senders[0].nacks_rcvd);
+  EXPECT_EQ(3u, info.senders[0].nacks_rcvd);
   EXPECT_EQ(4, info.senders[0].plis_rcvd);
 
   EXPECT_EQ(5, info.senders[1].firs_rcvd);
-  EXPECT_EQ(7, info.senders[1].nacks_rcvd);
+  EXPECT_EQ(7u, info.senders[1].nacks_rcvd);
   EXPECT_EQ(9, info.senders[1].plis_rcvd);
 
   EXPECT_EQ(7, info.aggregated_senders[0].firs_rcvd);
-  EXPECT_EQ(10, info.aggregated_senders[0].nacks_rcvd);
+  EXPECT_EQ(10u, info.aggregated_senders[0].nacks_rcvd);
   EXPECT_EQ(13, info.aggregated_senders[0].plis_rcvd);
 }
 
diff --git a/media/engine/webrtc_voice_engine.cc b/media/engine/webrtc_voice_engine.cc
index f2783f6..aa80c87 100644
--- a/media/engine/webrtc_voice_engine.cc
+++ b/media/engine/webrtc_voice_engine.cc
@@ -2320,6 +2320,7 @@
     sinfo.retransmitted_packets_sent = stats.retransmitted_packets_sent;
     sinfo.packets_lost = stats.packets_lost;
     sinfo.fraction_lost = stats.fraction_lost;
+    sinfo.nacks_rcvd = stats.nacks_rcvd;
     sinfo.codec_name = stats.codec_name;
     sinfo.codec_payload_type = stats.codec_payload_type;
     sinfo.jitter_ms = stats.jitter_ms;
diff --git a/pc/rtc_stats_collector.cc b/pc/rtc_stats_collector.cc
index 070400c..6599d0e 100644
--- a/pc/rtc_stats_collector.cc
+++ b/pc/rtc_stats_collector.cc
@@ -516,6 +516,7 @@
       static_cast<uint64_t>(media_sender_info.header_and_padding_bytes_sent);
   outbound_stats->retransmitted_bytes_sent =
       media_sender_info.retransmitted_bytes_sent;
+  outbound_stats->nack_count = media_sender_info.nacks_rcvd;
 }
 
 void SetOutboundRTPStreamStatsFromVoiceSenderInfo(
@@ -550,8 +551,6 @@
       static_cast<uint32_t>(video_sender_info.firs_rcvd);
   outbound_video->pli_count =
       static_cast<uint32_t>(video_sender_info.plis_rcvd);
-  outbound_video->nack_count =
-      static_cast<uint32_t>(video_sender_info.nacks_rcvd);
   if (video_sender_info.qp_sum)
     outbound_video->qp_sum = *video_sender_info.qp_sum;
   outbound_video->frames_encoded = video_sender_info.frames_encoded;
diff --git a/pc/rtc_stats_collector_unittest.cc b/pc/rtc_stats_collector_unittest.cc
index ca4f48e..2ac0737 100644
--- a/pc/rtc_stats_collector_unittest.cc
+++ b/pc/rtc_stats_collector_unittest.cc
@@ -2165,6 +2165,7 @@
   voice_media_info.senders[0].payload_bytes_sent = 3;
   voice_media_info.senders[0].header_and_padding_bytes_sent = 12;
   voice_media_info.senders[0].retransmitted_bytes_sent = 30;
+  voice_media_info.senders[0].nacks_rcvd = 31;
   voice_media_info.senders[0].codec_payload_type = 42;
 
   RtpCodecParameters codec_parameters;
@@ -2198,6 +2199,7 @@
   expected_audio.bytes_sent = 3;
   expected_audio.header_bytes_sent = 12;
   expected_audio.retransmitted_bytes_sent = 30;
+  expected_audio.nack_count = 31;
 
   ASSERT_TRUE(report->Get(expected_audio.id()));
   EXPECT_EQ(
@@ -2562,6 +2564,7 @@
   voice_media_info.senders[0].payload_bytes_sent = 3;
   voice_media_info.senders[0].header_and_padding_bytes_sent = 4;
   voice_media_info.senders[0].retransmitted_bytes_sent = 30;
+  voice_media_info.senders[0].nacks_rcvd = 31;
   voice_media_info.senders[0].codec_payload_type = 42;
 
   RtpCodecParameters codec_parameters;
@@ -2595,6 +2598,7 @@
   expected_audio.bytes_sent = 3;
   expected_audio.header_bytes_sent = 4;
   expected_audio.retransmitted_bytes_sent = 30;
+  expected_audio.nack_count = 31;
 
   ASSERT_TRUE(report->Get(expected_audio.id()));
   EXPECT_EQ(
diff --git a/pc/rtc_stats_integrationtest.cc b/pc/rtc_stats_integrationtest.cc
index c15ffbf..2dfe1b5 100644
--- a/pc/rtc_stats_integrationtest.cc
+++ b/pc/rtc_stats_integrationtest.cc
@@ -934,7 +934,6 @@
                                        RTCVideoSourceStats::kType);
       verifier.TestMemberIsNonNegative<uint32_t>(outbound_stream.fir_count);
       verifier.TestMemberIsNonNegative<uint32_t>(outbound_stream.pli_count);
-      verifier.TestMemberIsNonNegative<uint32_t>(outbound_stream.nack_count);
       if (*outbound_stream.frames_encoded > 0) {
         verifier.TestMemberIsNonNegative<uint64_t>(outbound_stream.qp_sum);
       } else {
@@ -943,11 +942,11 @@
     } else {
       verifier.TestMemberIsUndefined(outbound_stream.fir_count);
       verifier.TestMemberIsUndefined(outbound_stream.pli_count);
-      verifier.TestMemberIsUndefined(outbound_stream.nack_count);
       verifier.TestMemberIsIDReference(outbound_stream.media_source_id,
                                        RTCAudioSourceStats::kType);
       verifier.TestMemberIsUndefined(outbound_stream.qp_sum);
     }
+    verifier.TestMemberIsNonNegative<uint32_t>(outbound_stream.nack_count);
     verifier.TestMemberIsOptionalIDReference(
         outbound_stream.remote_id, RTCRemoteInboundRtpStreamStats::kType);
     verifier.TestMemberIsNonNegative<uint32_t>(outbound_stream.packets_sent);