[Stats] Explicit RTP-RTX and RTP-FEC mappings. Unblocks simulcast stats.

--- Background ---
The webrtc::VideoSendStream::StreamStats are converted into
VideoSenderInfo objects which turn into "outbound-rtp" stats objects in
getStats() (or "ssrc" objects in legacy getStats()).

StreamStats are created for each type of substream: RTP media streams,
RTX streams and FlexFEC streams - each with individual packet counters.

The RTX stream is responsible for retransmissions of a referenced media
stream and the FlexFEC stream is responsible for FEC of a referenced
media stream. RTX/FEC streams do not show up as separate objects in
getStats(). Only the media streams become "outbound-rtp" objects, but
their packet and byte counters have to include the RTX and FEC counters.

--- Overview of this CL ---
This CL adds MergeInfoAboutOutboundRtpSubstreams(). It takes
StreamStats of all kinds as input, and outputs media-only StreamStats
- incorporating the RTX and FEC counters into the relevant media
StreamStats.

The merged StreamStats objects is a smaller set of objects than the
non-merged counterparts, but when aggregating all packet counters
together we end up with exact same packet and count as before.

Because WebRtcVideoSendStream::GetVideoSenderInfo() currently aggregates
the StreamStats into a single VideoSenderInfo (single "outbound-rtp"),
this CL should not have any observable side-effects. Prior to this CL:
aggregate StreamStats. After this CL: merge StreamStats and then
aggregate them.

However, when simulcast stats are implemented (WIP CL:
https://webrtc-review.googlesource.com/c/src/+/168120) each RTP media
stream should turn into an individual "outbound-rtp" object. We will
then no longer aggregate all StreamStats into a single "info". This CL
unblocks simulcast stats by providing StreamStats objects that could be
turned into individual VideoSenderInfos.

--- The Changes ---
1. Methods added to RtpConfig to be able to easily tell the relationship
   between RTP, RTX and FEC ssrcs.
2. StreamStats gets a StreamType (kMedia, kRtx or kFlexfec) that
   replaces the booleans (is_rtx, is_flexfec).
3. "referenced_media_ssrc" is added to StreamStats, making it possible
   to tell which kRtx/kFlexFec stream stats need to be merged with which
   kMedia StreamStats.
4. MergeInfoAboutOutboundRtpSubstreams() added and used.

Bug: webrtc:11439
Change-Id: Iaf9002041169a054ddfd32c7ea06bd1dc36c6bca
Reviewed-on: https://webrtc-review.googlesource.com/c/src/+/170826
Commit-Queue: Henrik Boström <hbos@webrtc.org>
Reviewed-by: Ilya Nikolaevskiy <ilnik@webrtc.org>
Reviewed-by: Harald Alvestrand <hta@webrtc.org>
Reviewed-by: Stefan Holmer <stefan@webrtc.org>
Cr-Commit-Position: refs/heads/master@{#30869}
diff --git a/call/BUILD.gn b/call/BUILD.gn
index a2e8c05..a33df15 100644
--- a/call/BUILD.gn
+++ b/call/BUILD.gn
@@ -97,7 +97,9 @@
     "../api/transport:bitrate_settings",
     "../api/units:timestamp",
     "../modules/rtp_rtcp:rtp_rtcp_format",
+    "../rtc_base:checks",
     "../rtc_base:rtc_base_approved",
+    "//third_party/abseil-cpp/absl/algorithm:container",
     "//third_party/abseil-cpp/absl/types:optional",
   ]
 }
diff --git a/call/rtp_config.cc b/call/rtp_config.cc
index a23351b..d1db867 100644
--- a/call/rtp_config.cc
+++ b/call/rtp_config.cc
@@ -12,11 +12,29 @@
 
 #include <cstdint>
 
+#include "absl/algorithm/container.h"
 #include "api/array_view.h"
+#include "rtc_base/checks.h"
 #include "rtc_base/strings/string_builder.h"
 
 namespace webrtc {
 
+namespace {
+
+uint32_t FindAssociatedSsrc(uint32_t ssrc,
+                            const std::vector<uint32_t>& ssrcs,
+                            const std::vector<uint32_t>& associated_ssrcs) {
+  RTC_DCHECK_EQ(ssrcs.size(), associated_ssrcs.size());
+  for (size_t i = 0; i < ssrcs.size(); ++i) {
+    if (ssrcs[i] == ssrc)
+      return associated_ssrcs[i];
+  }
+  RTC_NOTREACHED();
+  return 0;
+}
+
+}  // namespace
+
 std::string LntfConfig::ToString() const {
   return enabled ? "{enabled: true}" : "{enabled: false}";
 }
@@ -124,4 +142,51 @@
   ss << '}';
   return ss.str();
 }
+
+bool RtpConfig::IsMediaSsrc(uint32_t ssrc) const {
+  return absl::c_linear_search(ssrcs, ssrc);
+}
+
+bool RtpConfig::IsRtxSsrc(uint32_t ssrc) const {
+  return absl::c_linear_search(rtx.ssrcs, ssrc);
+}
+
+bool RtpConfig::IsFlexfecSsrc(uint32_t ssrc) const {
+  return flexfec.payload_type != -1 && ssrc == flexfec.ssrc;
+}
+
+absl::optional<uint32_t> RtpConfig::GetRtxSsrcAssociatedWithMediaSsrc(
+    uint32_t media_ssrc) const {
+  RTC_DCHECK(IsMediaSsrc(media_ssrc));
+  // If we don't use RTX there is no association.
+  if (rtx.ssrcs.empty())
+    return absl::nullopt;
+  // If we use RTX there MUST be an association ssrcs[i] <-> rtx.ssrcs[i].
+  RTC_DCHECK_EQ(ssrcs.size(), rtx.ssrcs.size());
+  return FindAssociatedSsrc(media_ssrc, ssrcs, rtx.ssrcs);
+}
+
+uint32_t RtpConfig::GetMediaSsrcAssociatedWithRtxSsrc(uint32_t rtx_ssrc) const {
+  RTC_DCHECK(IsRtxSsrc(rtx_ssrc));
+  // If we use RTX there MUST be an association ssrcs[i] <-> rtx.ssrcs[i].
+  RTC_DCHECK_EQ(ssrcs.size(), rtx.ssrcs.size());
+  return FindAssociatedSsrc(rtx_ssrc, rtx.ssrcs, ssrcs);
+}
+
+uint32_t RtpConfig::GetMediaSsrcAssociatedWithFlexfecSsrc(
+    uint32_t flexfec_ssrc) const {
+  RTC_DCHECK(IsFlexfecSsrc(flexfec_ssrc));
+  // If we use FlexFEC there MUST be an associated media ssrc.
+  //
+  // TODO(brandtr/hbos): The current implementation only supports an association
+  // with a single media ssrc. If multiple ssrcs are to be supported in the
+  // future, in order not to break GetStats()'s packet and byte counters, we
+  // must be able to tell how many packets and bytes have contributed to which
+  // SSRC.
+  RTC_DCHECK_EQ(1u, flexfec.protected_media_ssrcs.size());
+  uint32_t media_ssrc = flexfec.protected_media_ssrcs[0];
+  RTC_DCHECK(IsMediaSsrc(media_ssrc));
+  return media_ssrc;
+}
+
 }  // namespace webrtc
diff --git a/call/rtp_config.h b/call/rtp_config.h
index a0596a8..d9caeb0 100644
--- a/call/rtp_config.h
+++ b/call/rtp_config.h
@@ -17,6 +17,7 @@
 #include <string>
 #include <vector>
 
+#include "absl/types/optional.h"
 #include "api/rtp_headers.h"
 #include "api/rtp_parameters.h"
 
@@ -157,6 +158,14 @@
 
   // RTCP CNAME, see RFC 3550.
   std::string c_name;
+
+  bool IsMediaSsrc(uint32_t ssrc) const;
+  bool IsRtxSsrc(uint32_t ssrc) const;
+  bool IsFlexfecSsrc(uint32_t ssrc) const;
+  absl::optional<uint32_t> GetRtxSsrcAssociatedWithMediaSsrc(
+      uint32_t media_ssrc) const;
+  uint32_t GetMediaSsrcAssociatedWithRtxSsrc(uint32_t rtx_ssrc) const;
+  uint32_t GetMediaSsrcAssociatedWithFlexfecSsrc(uint32_t flexfec_ssrc) const;
 };
 }  // namespace webrtc
 #endif  // CALL_RTP_CONFIG_H_
diff --git a/call/rtp_video_sender.cc b/call/rtp_video_sender.cc
index cf9af9f..cb32085 100644
--- a/call/rtp_video_sender.cc
+++ b/call/rtp_video_sender.cc
@@ -231,7 +231,7 @@
   std::vector<RtpStreamSender> rtp_streams;
 
   RTC_DCHECK(rtp_config.rtx.ssrcs.empty() ||
-             rtp_config.rtx.ssrcs.size() == rtp_config.rtx.ssrcs.size());
+             rtp_config.rtx.ssrcs.size() == rtp_config.ssrcs.size());
   for (size_t i = 0; i < rtp_config.ssrcs.size(); ++i) {
     RTPSenderVideo::Config video_config;
     configuration.local_media_ssrc = rtp_config.ssrcs[i];
@@ -241,9 +241,10 @@
     configuration.fec_generator = fec_generator.get();
     video_config.fec_generator = fec_generator.get();
 
-    if (rtp_config.rtx.ssrcs.size() > i) {
-      configuration.rtx_send_ssrc = rtp_config.rtx.ssrcs[i];
-    }
+    configuration.rtx_send_ssrc =
+        rtp_config.GetRtxSsrcAssociatedWithMediaSsrc(rtp_config.ssrcs[i]);
+    RTC_DCHECK_EQ(configuration.rtx_send_ssrc.has_value(),
+                  !rtp_config.rtx.ssrcs.empty());
 
     configuration.need_rtp_packet_infos = rtp_config.lntf.enabled;
 
diff --git a/call/video_send_stream.cc b/call/video_send_stream.cc
index f495d08..a4b6744 100644
--- a/call/video_send_stream.cc
+++ b/call/video_send_stream.cc
@@ -17,12 +17,31 @@
 
 namespace webrtc {
 
+namespace {
+
+const char* StreamTypeToString(VideoSendStream::StreamStats::StreamType type) {
+  switch (type) {
+    case VideoSendStream::StreamStats::StreamType::kMedia:
+      return "media";
+    case VideoSendStream::StreamStats::StreamType::kRtx:
+      return "rtx";
+    case VideoSendStream::StreamStats::StreamType::kFlexfec:
+      return "flexfec";
+  }
+}
+
+}  // namespace
+
 VideoSendStream::StreamStats::StreamStats() = default;
 VideoSendStream::StreamStats::~StreamStats() = default;
 
 std::string VideoSendStream::StreamStats::ToString() const {
   char buf[1024];
   rtc::SimpleStringBuilder ss(buf);
+  ss << "type: " << StreamTypeToString(type);
+  if (referenced_media_ssrc.has_value())
+    ss << " (for: " << referenced_media_ssrc.value() << ")";
+  ss << ", ";
   ss << "width: " << width << ", ";
   ss << "height: " << height << ", ";
   ss << "key: " << frame_counts.key_frames << ", ";
@@ -64,7 +83,8 @@
   ss << "#quality_adaptations: " << number_of_quality_adapt_changes;
   ss << '}';
   for (const auto& substream : substreams) {
-    if (!substream.second.is_rtx && !substream.second.is_flexfec) {
+    if (substream.second.type ==
+        VideoSendStream::StreamStats::StreamType::kMedia) {
       ss << " {ssrc: " << substream.first << ", ";
       ss << substream.second.ToString();
       ss << '}';
diff --git a/call/video_send_stream.h b/call/video_send_stream.h
index 962578d..08806ec 100644
--- a/call/video_send_stream.h
+++ b/call/video_send_stream.h
@@ -40,15 +40,35 @@
 
 class VideoSendStream {
  public:
+  // Multiple StreamStats objects are present if simulcast is used (multiple
+  // kMedia streams) or if RTX or FlexFEC is negotiated. Multiple SVC layers, on
+  // the other hand, does not cause additional StreamStats.
   struct StreamStats {
+    enum class StreamType {
+      // A media stream is an RTP stream for audio or video. Retransmissions and
+      // FEC is either sent over the same SSRC or negotiated to be sent over
+      // separate SSRCs, in which case separate StreamStats objects exist with
+      // references to this media stream's SSRC.
+      kMedia,
+      // RTX streams are streams dedicated to retransmissions. They have a
+      // dependency on a single kMedia stream: |referenced_media_ssrc|.
+      kRtx,
+      // FlexFEC streams are streams dedicated to FlexFEC. They have a
+      // dependency on a single kMedia stream: |referenced_media_ssrc|.
+      kFlexfec,
+    };
+
     StreamStats();
     ~StreamStats();
 
     std::string ToString() const;
 
+    StreamType type = StreamType::kMedia;
+    // If |type| is kRtx or kFlexfec this value is present. The referenced SSRC
+    // is the kMedia stream that this stream is performing retransmissions or
+    // FEC for. If |type| is kMedia, this value is null.
+    absl::optional<uint32_t> referenced_media_ssrc;
     FrameCounts frame_counts;
-    bool is_rtx = false;
-    bool is_flexfec = false;
     int width = 0;
     int height = 0;
     // TODO(holmer): Move bitrate_bps out to the webrtc::Call layer.
@@ -63,6 +83,13 @@
     // A snapshot of the most recent Report Block with additional data of
     // interest to statistics. Used to implement RTCRemoteInboundRtpStreamStats.
     absl::optional<ReportBlockData> report_block_data;
+
+    // These booleans are redundant; this information is already exposed in
+    // |type|.
+    // TODO(hbos): Update downstream projects to use |type| instead and delete
+    // these members.
+    bool is_flexfec = false;
+    bool is_rtx = false;
   };
 
   struct Stats {