stats: implement RTX ssrc on inbound-rtp/outbound-rtp

spec change:
  https://github.com/w3c/webrtc-stats/pull/765

BUG=webrtc:15096

Change-Id: I7c72193c23460330b6bb612a9568641d187ee638
Reviewed-on: https://webrtc-review.googlesource.com/c/src/+/312362
Reviewed-by: Harald Alvestrand <hta@webrtc.org>
Commit-Queue: Philipp Hancke <phancke@microsoft.com>
Reviewed-by: Henrik Boström <hbos@webrtc.org>
Cr-Commit-Position: refs/heads/main@{#40510}
diff --git a/api/stats/rtcstats_objects.h b/api/stats/rtcstats_objects.h
index 0b5d8d2..4861f8a 100644
--- a/api/stats/rtcstats_objects.h
+++ b/api/stats/rtcstats_objects.h
@@ -256,6 +256,8 @@
   // possible to distinguish retransmissions.
   RTCStatsMember<uint64_t> retransmitted_packets_received;
   RTCStatsMember<uint64_t> retransmitted_bytes_received;
+  RTCStatsMember<uint32_t> rtx_ssrc;
+
   RTCStatsMember<double> last_packet_received_timestamp;
   RTCStatsMember<double> jitter_buffer_delay;
   RTCStatsMember<double> jitter_buffer_target_delay;
@@ -368,6 +370,9 @@
   // In JavaScript, this is only exposed if HW exposure is allowed.
   RTCStatsMember<bool> power_efficient_encoder;
   RTCStatsMember<std::string> scalability_mode;
+
+  // RTX ssrc. Only present if RTX is negotiated.
+  RTCStatsMember<uint32_t> rtx_ssrc;
 };
 
 // https://w3c.github.io/webrtc-stats/#remoteinboundrtpstats-dict*
diff --git a/pc/rtc_stats_collector.cc b/pc/rtc_stats_collector.cc
index 07e8771..0c4df20 100644
--- a/pc/rtc_stats_collector.cc
+++ b/pc/rtc_stats_collector.cc
@@ -677,6 +677,12 @@
     inbound_video->power_efficient_decoder =
         *video_receiver_info.power_efficient_decoder;
   }
+  for (const auto& ssrc_group : video_receiver_info.ssrc_groups) {
+    if (ssrc_group.semantics == cricket::kFidSsrcGroupSemantics &&
+        ssrc_group.ssrcs.size() == 2) {
+      inbound_video->rtx_ssrc = ssrc_group.ssrcs[1];
+    }
+  }
 
   return inbound_video;
 }
@@ -826,6 +832,12 @@
     outbound_video->scalability_mode = std::string(
         ScalabilityModeToString(*video_sender_info.scalability_mode));
   }
+  for (const auto& ssrc_group : video_sender_info.ssrc_groups) {
+    if (ssrc_group.semantics == cricket::kFidSsrcGroupSemantics &&
+        ssrc_group.ssrcs.size() == 2) {
+      outbound_video->rtx_ssrc = ssrc_group.ssrcs[1];
+    }
+  }
   return outbound_video;
 }
 
diff --git a/pc/rtc_stats_collector_unittest.cc b/pc/rtc_stats_collector_unittest.cc
index 8781b32..788c884 100644
--- a/pc/rtc_stats_collector_unittest.cc
+++ b/pc/rtc_stats_collector_unittest.cc
@@ -2368,6 +2368,8 @@
   video_media_info.receivers[0].retransmitted_bytes_received = 62;
   video_media_info.receivers[0].fec_packets_received = 32;
   video_media_info.receivers[0].fec_bytes_received = 54;
+  video_media_info.receivers[0].ssrc_groups.push_back(
+      {cricket::kFidSsrcGroupSemantics, {1, 4404}});
 
   // Note: these two values intentionally differ,
   // only the decoded one should show up.
@@ -2434,6 +2436,7 @@
   expected_video.retransmitted_bytes_received = 62;
   expected_video.fec_packets_received = 32;
   expected_video.fec_bytes_received = 54;
+  expected_video.rtx_ssrc = 4404;
 
   ASSERT_TRUE(report->Get(expected_video.id()));
   EXPECT_EQ(
@@ -2629,6 +2632,8 @@
   video_media_info.senders[0].huge_frames_sent = 2;
   video_media_info.senders[0].active = false;
   video_media_info.senders[0].scalability_mode = ScalabilityMode::kL3T3_KEY;
+  video_media_info.senders[0].ssrc_groups.push_back(
+      {cricket::kFidSsrcGroupSemantics, {1, 4404}});
   video_media_info.aggregated_senders.push_back(video_media_info.senders[0]);
   RtpCodecParameters codec_parameters;
   codec_parameters.payload_type = 42;
@@ -2684,6 +2689,7 @@
   expected_video.active = false;
   expected_video.power_efficient_encoder = false;
   expected_video.scalability_mode = "L3T3_KEY";
+  expected_video.rtx_ssrc = 4404;
   // `expected_video.content_type` should be undefined.
   // `expected_video.qp_sum` should be undefined.
   // `expected_video.encoder_implementation` should be undefined.
diff --git a/pc/rtc_stats_integrationtest.cc b/pc/rtc_stats_integrationtest.cc
index fd47865..3e767a7 100644
--- a/pc/rtc_stats_integrationtest.cc
+++ b/pc/rtc_stats_integrationtest.cc
@@ -693,11 +693,13 @@
           inbound_stream.retransmitted_packets_received);
       verifier.TestMemberIsNonNegative<uint64_t>(
           inbound_stream.retransmitted_bytes_received);
+      verifier.TestMemberIsNonNegative<uint32_t>(inbound_stream.rtx_ssrc);
     } else {
       verifier.TestMemberIsUndefined(
           inbound_stream.retransmitted_packets_received);
       verifier.TestMemberIsUndefined(
           inbound_stream.retransmitted_bytes_received);
+      verifier.TestMemberIsUndefined(inbound_stream.rtx_ssrc);
     }
 
     // Test runtime too short to get an estimate (at least two RTCP sender
@@ -849,6 +851,7 @@
           outbound_stream.huge_frames_sent);
       verifier.MarkMemberTested(outbound_stream.rid, true);
       verifier.TestMemberIsDefined(outbound_stream.scalability_mode);
+      verifier.TestMemberIsNonNegative<uint32_t>(outbound_stream.rtx_ssrc);
     } else {
       verifier.TestMemberIsUndefined(outbound_stream.frames_encoded);
       verifier.TestMemberIsUndefined(outbound_stream.key_frames_encoded);
@@ -871,6 +874,7 @@
       verifier.TestMemberIsUndefined(outbound_stream.frames_sent);
       verifier.TestMemberIsUndefined(outbound_stream.huge_frames_sent);
       verifier.TestMemberIsUndefined(outbound_stream.scalability_mode);
+      verifier.TestMemberIsUndefined(outbound_stream.rtx_ssrc);
     }
     return verifier.ExpectAllMembersSuccessfullyTested();
   }
diff --git a/stats/rtcstats_objects.cc b/stats/rtcstats_objects.cc
index 97786f9..fbd597c 100644
--- a/stats/rtcstats_objects.cc
+++ b/stats/rtcstats_objects.cc
@@ -313,6 +313,7 @@
     &header_bytes_received,
     &retransmitted_packets_received,
     &retransmitted_bytes_received,
+    &rtx_ssrc,
     &last_packet_received_timestamp,
     &jitter_buffer_delay,
     &jitter_buffer_target_delay,
@@ -378,6 +379,7 @@
       header_bytes_received("headerBytesReceived"),
       retransmitted_packets_received("retransmittedPacketsReceived"),
       retransmitted_bytes_received("retransmittedBytesReceived"),
+      rtx_ssrc("rtxSsrc"),
       last_packet_received_timestamp("lastPacketReceivedTimestamp"),
       jitter_buffer_delay("jitterBufferDelay"),
       jitter_buffer_target_delay("jitterBufferTargetDelay"),
@@ -462,7 +464,8 @@
     &qp_sum,
     &active,
     &power_efficient_encoder,
-    &scalability_mode)
+    &scalability_mode,
+    &rtx_ssrc)
 // clang-format on
 
 RTCOutboundRtpStreamStats::RTCOutboundRtpStreamStats(std::string id,
@@ -498,7 +501,8 @@
       qp_sum("qpSum"),
       active("active"),
       power_efficient_encoder("powerEfficientEncoder"),
-      scalability_mode("scalabilityMode") {}
+      scalability_mode("scalabilityMode"),
+      rtx_ssrc("rtxSsrc") {}
 
 RTCOutboundRtpStreamStats::RTCOutboundRtpStreamStats(
     const RTCOutboundRtpStreamStats& other) = default;