stats: Implement receive RTX stats

* retransmittedBytesReceived
* retransmittedPacketsReceived
added to the specification in
  https://github.com/w3c/webrtc-stats/pull/735

BUG=webrtc:15096

Change-Id: I6770e5d8d09ac1c2693c918fd943b0ab257ec7ba
Reviewed-on: https://webrtc-review.googlesource.com/c/src/+/295260
Reviewed-by: Henrik Boström <hbos@webrtc.org>
Reviewed-by: Harald Alvestrand <hta@webrtc.org>
Commit-Queue: Philipp Hancke <phancke@microsoft.com>
Cr-Commit-Position: refs/heads/main@{#39959}
diff --git a/api/stats/rtcstats_objects.h b/api/stats/rtcstats_objects.h
index 4428ab5..15b0158 100644
--- a/api/stats/rtcstats_objects.h
+++ b/api/stats/rtcstats_objects.h
@@ -425,6 +425,10 @@
   RTCStatsMember<uint64_t> fec_packets_discarded;
   RTCStatsMember<uint64_t> bytes_received;
   RTCStatsMember<uint64_t> header_bytes_received;
+  // Inbound RTX stats. Only defined when RTX is used and it is therefore
+  // possible to distinguish retransmissions.
+  RTCStatsMember<uint64_t> retransmitted_packets_received;
+  RTCStatsMember<uint64_t> retransmitted_bytes_received;
   RTCStatsMember<double> last_packet_received_timestamp;
   RTCStatsMember<double> jitter_buffer_delay;
   RTCStatsMember<double> jitter_buffer_target_delay;
diff --git a/call/video_receive_stream.h b/call/video_receive_stream.h
index bde8c8b..4d6b36b 100644
--- a/call/video_receive_stream.h
+++ b/call/video_receive_stream.h
@@ -145,6 +145,7 @@
     std::string c_name;
     RtpReceiveStats rtp_stats;
     RtcpPacketTypeCounter rtcp_packet_type_counts;
+    absl::optional<RtpReceiveStats> rtx_rtp_stats;
 
     // Timing frame info: all important timestamps for a full lifetime of a
     // single 'timing frame'.
diff --git a/media/base/media_channel.h b/media/base/media_channel.h
index 378042f..52aedec 100644
--- a/media/base/media_channel.h
+++ b/media/base/media_channel.h
@@ -443,6 +443,9 @@
   int64_t header_and_padding_bytes_received = 0;
   int packets_received = 0;
   int packets_lost = 0;
+
+  absl::optional<uint64_t> retransmitted_bytes_received;
+  absl::optional<uint64_t> retransmitted_packets_received;
   absl::optional<uint32_t> nacks_sent;
   // Jitter (network-related) latency (cumulative).
   // https://w3c.github.io/webrtc-stats/#dom-rtcinboundrtpstreamstats-jitterbufferdelay
diff --git a/media/engine/webrtc_video_engine.cc b/media/engine/webrtc_video_engine.cc
index 83581bf..af97dfa 100644
--- a/media/engine/webrtc_video_engine.cc
+++ b/media/engine/webrtc_video_engine.cc
@@ -3364,6 +3364,20 @@
 
   info.timing_frame_info = stats.timing_frame_info;
 
+  if (stats.rtx_rtp_stats.has_value()) {
+    info.retransmitted_packets_received =
+        stats.rtx_rtp_stats->packet_counter.packets;
+    info.retransmitted_bytes_received =
+        stats.rtx_rtp_stats->packet_counter.payload_bytes;
+    // RTX information gets added to primary counters.
+    info.payload_bytes_received +=
+        stats.rtx_rtp_stats->packet_counter.payload_bytes;
+    info.header_and_padding_bytes_received +=
+        stats.rtx_rtp_stats->packet_counter.header_bytes +
+        stats.rtx_rtp_stats->packet_counter.padding_bytes;
+    info.packets_received += stats.rtx_rtp_stats->packet_counter.packets;
+  }
+
   if (log_stats)
     RTC_LOG(LS_INFO) << stats.ToString(rtc::TimeMillis());
 
diff --git a/media/engine/webrtc_video_engine_unittest.cc b/media/engine/webrtc_video_engine_unittest.cc
index 99d36b7..bbabddf 100644
--- a/media/engine/webrtc_video_engine_unittest.cc
+++ b/media/engine/webrtc_video_engine_unittest.cc
@@ -6548,6 +6548,10 @@
   stats.total_assembly_time = webrtc::TimeDelta::Millis(4);
   stats.frames_assembled_from_multiple_packets = 2;
   stats.power_efficient_decoder = true;
+  webrtc::RtpReceiveStats rtx_stats;
+  rtx_stats.packet_counter.packets = 5;
+  rtx_stats.packet_counter.payload_bytes = 23;
+  stats.rtx_rtp_stats = rtx_stats;
   stream->SetStats(stats);
 
   cricket::VideoMediaSendInfo send_info;
@@ -6586,6 +6590,10 @@
   EXPECT_EQ(stats.frames_assembled_from_multiple_packets,
             receive_info.receivers[0].frames_assembled_from_multiple_packets);
   EXPECT_TRUE(receive_info.receivers[0].power_efficient_decoder);
+  EXPECT_EQ(stats.rtx_rtp_stats->packet_counter.packets,
+            receive_info.receivers[0].retransmitted_packets_received);
+  EXPECT_EQ(stats.rtx_rtp_stats->packet_counter.payload_bytes,
+            receive_info.receivers[0].retransmitted_bytes_received);
 }
 
 TEST_F(WebRtcVideoChannelTest,
diff --git a/pc/rtc_stats_collector.cc b/pc/rtc_stats_collector.cc
index 9b9f5ba..4535597 100644
--- a/pc/rtc_stats_collector.cc
+++ b/pc/rtc_stats_collector.cc
@@ -421,6 +421,14 @@
       static_cast<uint64_t>(media_receiver_info.payload_bytes_received);
   inbound_stats->header_bytes_received = static_cast<uint64_t>(
       media_receiver_info.header_and_padding_bytes_received);
+  if (media_receiver_info.retransmitted_bytes_received.has_value()) {
+    inbound_stats->retransmitted_bytes_received =
+        *media_receiver_info.retransmitted_bytes_received;
+  }
+  if (media_receiver_info.retransmitted_packets_received.has_value()) {
+    inbound_stats->retransmitted_packets_received =
+        *media_receiver_info.retransmitted_packets_received;
+  }
   inbound_stats->packets_lost =
       static_cast<int32_t>(media_receiver_info.packets_lost);
   inbound_stats->jitter_buffer_delay =
diff --git a/pc/rtc_stats_collector_unittest.cc b/pc/rtc_stats_collector_unittest.cc
index 9b0466d..9db5b3f 100644
--- a/pc/rtc_stats_collector_unittest.cc
+++ b/pc/rtc_stats_collector_unittest.cc
@@ -2667,7 +2667,6 @@
   video_media_info.receivers[0].jitter_buffer_target_delay_seconds = 1.1;
   video_media_info.receivers[0].jitter_buffer_minimum_delay_seconds = 0.999;
   video_media_info.receivers[0].jitter_buffer_emitted_count = 13;
-
   video_media_info.receivers[0].last_packet_received_timestamp_ms =
       absl::nullopt;
   video_media_info.receivers[0].content_type = VideoContentType::UNSPECIFIED;
@@ -2676,6 +2675,8 @@
   video_media_info.receivers[0].decoder_implementation_name = "";
   video_media_info.receivers[0].min_playout_delay_ms = 50;
   video_media_info.receivers[0].power_efficient_decoder = false;
+  video_media_info.receivers[0].retransmitted_packets_received = 17;
+  video_media_info.receivers[0].retransmitted_bytes_received = 62;
 
   // Note: these two values intentionally differ,
   // only the decoded one should show up.
@@ -2741,6 +2742,8 @@
   expected_video.min_playout_delay = 0.05;
   expected_video.frames_per_second = 5;
   expected_video.power_efficient_decoder = false;
+  expected_video.retransmitted_packets_received = 17;
+  expected_video.retransmitted_bytes_received = 62;
 
   ASSERT_TRUE(report->Get(expected_video.id()));
   EXPECT_EQ(
diff --git a/pc/rtc_stats_integrationtest.cc b/pc/rtc_stats_integrationtest.cc
index 42a7769..3f67042 100644
--- a/pc/rtc_stats_integrationtest.cc
+++ b/pc/rtc_stats_integrationtest.cc
@@ -843,6 +843,20 @@
           inbound_stream.total_samples_duration);
       verifier.TestMemberIsUndefined(inbound_stream.frames_received);
     }
+
+    // RTX stats are typically only defined for video where RTX is negotiated.
+    if (inbound_stream.kind.is_defined() && *inbound_stream.kind == "video") {
+      verifier.TestMemberIsNonNegative<uint64_t>(
+          inbound_stream.retransmitted_packets_received);
+      verifier.TestMemberIsNonNegative<uint64_t>(
+          inbound_stream.retransmitted_bytes_received);
+    } else {
+      verifier.TestMemberIsUndefined(
+          inbound_stream.retransmitted_packets_received);
+      verifier.TestMemberIsUndefined(
+          inbound_stream.retransmitted_bytes_received);
+    }
+
     // Test runtime too short to get an estimate (at least two RTCP sender
     // reports need to be received).
     verifier.MarkMemberTested(inbound_stream.estimated_playout_timestamp, true);
diff --git a/stats/rtcstats_objects.cc b/stats/rtcstats_objects.cc
index 997764b..5aef7f9 100644
--- a/stats/rtcstats_objects.cc
+++ b/stats/rtcstats_objects.cc
@@ -466,6 +466,8 @@
     &fec_packets_discarded,
     &bytes_received,
     &header_bytes_received,
+    &retransmitted_packets_received,
+    &retransmitted_bytes_received,
     &last_packet_received_timestamp,
     &jitter_buffer_delay,
     &jitter_buffer_target_delay,
@@ -528,6 +530,8 @@
       fec_packets_discarded("fecPacketsDiscarded"),
       bytes_received("bytesReceived"),
       header_bytes_received("headerBytesReceived"),
+      retransmitted_packets_received("retransmittedPacketsReceived"),
+      retransmitted_bytes_received("retransmittedBytesReceived"),
       last_packet_received_timestamp("lastPacketReceivedTimestamp"),
       jitter_buffer_delay("jitterBufferDelay"),
       jitter_buffer_target_delay("jitterBufferTargetDelay"),
diff --git a/video/video_receive_stream2.cc b/video/video_receive_stream2.cc
index 9cc78c7..d4ca5e8 100644
--- a/video/video_receive_stream2.cc
+++ b/video/video_receive_stream2.cc
@@ -566,8 +566,10 @@
   if (rtx_ssrc()) {
     StreamStatistician* rtx_statistician =
         rtp_receive_statistics_->GetStatistician(rtx_ssrc());
-    if (rtx_statistician)
+    if (rtx_statistician) {
       stats.total_bitrate_bps += rtx_statistician->BitrateReceived();
+      stats.rtx_rtp_stats = rtx_statistician->GetStats();
+    }
   }
   return stats;
 }