Reland "Improve outbound-rtp statistics for simulcast"

This reverts commit 9a925c9ce33a6ccdd11b545b11ba68e985c2a65d.

Reason for revert: The original CL is updated in PS #2 to
fix the googRtt issue which was that when the legacy sender
stats were put in "aggregated_senders" we forgot to update
rtt_ms the same way that we do it for "senders".

Original change's description:
> Revert "Improve outbound-rtp statistics for simulcast"
>
> This reverts commit da6cda839dac7d9d18eba8d365188fa94831e0b1.
>
> Reason for revert: Breaks googRtt in legacy getStats API
>
> Original change's description:
> > Improve outbound-rtp statistics for simulcast
> >
> > Bug: webrtc:9547
> > Change-Id: Iec4eb976aa11ee743805425bedb77dcea7c2c9be
> > Reviewed-on: https://webrtc-review.googlesource.com/c/src/+/168120
> > Reviewed-by: Sebastian Jansson <srte@webrtc.org>
> > Reviewed-by: Erik Språng <sprang@webrtc.org>
> > Reviewed-by: Henrik Boström <hbos@webrtc.org>
> > Reviewed-by: Harald Alvestrand <hta@webrtc.org>
> > Commit-Queue: Eldar Rello <elrello@microsoft.com>
> > Cr-Commit-Position: refs/heads/master@{#31097}
>
> TBR=hbos@webrtc.org,sprang@webrtc.org,stefan@webrtc.org,srte@webrtc.org,hta@webrtc.org,elrello@microsoft.com
>
> # Not skipping CQ checks because original CL landed > 1 day ago.
>
> Bug: webrtc:9547
> Change-Id: I06673328c2a5293a7eef03b3aaf2ded9d13df1b3
> Reviewed-on: https://webrtc-review.googlesource.com/c/src/+/174443
> Reviewed-by: Henrik Boström <hbos@webrtc.org>
> Commit-Queue: Henrik Boström <hbos@webrtc.org>
> Cr-Commit-Position: refs/heads/master@{#31165}

TBR=hbos@webrtc.org,sprang@webrtc.org,stefan@webrtc.org,srte@webrtc.org,hta@webrtc.org,elrello@microsoft.com

# Not skipping CQ checks because this is a reland.

Bug: webrtc:9547
Change-Id: I723744c496c3c65f95ab6a8940862c8b9f544338
Reviewed-on: https://webrtc-review.googlesource.com/c/src/+/174480
Reviewed-by: Harald Alvestrand <hta@webrtc.org>
Reviewed-by: Henrik Boström <hbos@webrtc.org>
Commit-Queue: Henrik Boström <hbos@webrtc.org>
Cr-Commit-Position: refs/heads/master@{#31169}
diff --git a/api/peer_connection_interface.h b/api/peer_connection_interface.h
index 0ae47b2..1d81de7 100644
--- a/api/peer_connection_interface.h
+++ b/api/peer_connection_interface.h
@@ -666,6 +666,8 @@
     // Whether network condition based codec switching is allowed.
     absl::optional<bool> allow_codec_switching;
 
+    bool enable_simulcast_stats = true;
+
     //
     // Don't forget to update operator== if adding something.
     //
diff --git a/api/stats/rtcstats_objects.h b/api/stats/rtcstats_objects.h
index e83c83d..28d841d 100644
--- a/api/stats/rtcstats_objects.h
+++ b/api/stats/rtcstats_objects.h
@@ -469,6 +469,7 @@
 
   RTCStatsMember<std::string> media_source_id;
   RTCStatsMember<std::string> remote_id;
+  RTCStatsMember<std::string> rid;
   RTCStatsMember<uint32_t> packets_sent;
   RTCStatsMember<uint64_t> retransmitted_packets_sent;
   RTCStatsMember<uint64_t> bytes_sent;
@@ -480,6 +481,11 @@
   RTCStatsMember<uint32_t> key_frames_encoded;
   RTCStatsMember<double> total_encode_time;
   RTCStatsMember<uint64_t> total_encoded_bytes_target;
+  RTCStatsMember<uint32_t> frame_width;
+  RTCStatsMember<uint32_t> frame_height;
+  RTCStatsMember<double> frames_per_second;
+  RTCStatsMember<uint32_t> frames_sent;
+  RTCStatsMember<uint32_t> huge_frames_sent;
   // TODO(https://crbug.com/webrtc/10635): This is only implemented for video;
   // implement it for audio as well.
   RTCStatsMember<double> total_packet_send_delay;
diff --git a/call/rtp_config.cc b/call/rtp_config.cc
index d1db867..c84a63e 100644
--- a/call/rtp_config.cc
+++ b/call/rtp_config.cc
@@ -189,4 +189,15 @@
   return media_ssrc;
 }
 
+absl::optional<std::string> RtpConfig::GetRidForSsrc(uint32_t ssrc) const {
+  auto it = std::find(ssrcs.begin(), ssrcs.end(), ssrc);
+  if (it != ssrcs.end()) {
+    size_t ssrc_index = std::distance(ssrcs.begin(), it);
+    if (ssrc_index < rids.size()) {
+      return rids[ssrc_index];
+    }
+  }
+  return absl::nullopt;
+}
+
 }  // namespace webrtc
diff --git a/call/rtp_config.h b/call/rtp_config.h
index d9caeb0..2986449 100644
--- a/call/rtp_config.h
+++ b/call/rtp_config.h
@@ -166,6 +166,7 @@
       uint32_t media_ssrc) const;
   uint32_t GetMediaSsrcAssociatedWithRtxSsrc(uint32_t rtx_ssrc) const;
   uint32_t GetMediaSsrcAssociatedWithFlexfecSsrc(uint32_t flexfec_ssrc) const;
+  absl::optional<std::string> GetRidForSsrc(uint32_t ssrc) const;
 };
 }  // namespace webrtc
 #endif  // CALL_RTP_CONFIG_H_
diff --git a/call/video_send_stream.h b/call/video_send_stream.h
index 86f3102..392c955 100644
--- a/call/video_send_stream.h
+++ b/call/video_send_stream.h
@@ -83,6 +83,12 @@
     // 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;
+    double encode_frame_rate = 0.0;
+    int frames_encoded = 0;
+    absl::optional<uint64_t> qp_sum;
+    uint64_t total_encode_time_ms = 0;
+    uint64_t total_encoded_bytes_target = 0;
+    uint32_t huge_frames_sent = 0;
   };
 
   struct Stats {
@@ -104,7 +110,6 @@
     uint32_t frames_dropped_by_rate_limiter = 0;
     uint32_t frames_dropped_by_congestion_window = 0;
     uint32_t frames_dropped_by_encoder = 0;
-    absl::optional<uint64_t> qp_sum;
     // Bitrate the encoder is currently configured to use due to bandwidth
     // limitations.
     int target_media_bitrate_bps = 0;
@@ -130,6 +135,7 @@
     std::map<uint32_t, StreamStats> substreams;
     webrtc::VideoContentType content_type =
         webrtc::VideoContentType::UNSPECIFIED;
+    uint32_t frames_sent = 0;
     uint32_t huge_frames_sent = 0;
   };
 
diff --git a/media/base/media_channel.h b/media/base/media_channel.h
index 4758cf5..d71ec91 100644
--- a/media/base/media_channel.h
+++ b/media/base/media_channel.h
@@ -569,6 +569,7 @@
   int send_frame_height = 0;
   int framerate_input = 0;
   int framerate_sent = 0;
+  int aggregated_framerate_sent = 0;
   int nominal_bitrate = 0;
   int adapt_reason = 0;
   int adapt_changes = 0;
@@ -592,8 +593,11 @@
   bool has_entered_low_resolution = false;
   absl::optional<uint64_t> qp_sum;
   webrtc::VideoContentType content_type = webrtc::VideoContentType::UNSPECIFIED;
+  uint32_t frames_sent = 0;
   // https://w3c.github.io/webrtc-stats/#dom-rtcvideosenderstats-hugeframessent
   uint32_t huge_frames_sent = 0;
+  uint32_t aggregated_huge_frames_sent = 0;
+  absl::optional<std::string> rid;
 };
 
 struct VideoReceiverInfo : public MediaReceiverInfo {
@@ -713,11 +717,20 @@
   ~VideoMediaInfo();
   void Clear() {
     senders.clear();
+    aggregated_senders.clear();
     receivers.clear();
     send_codecs.clear();
     receive_codecs.clear();
   }
+  // Each sender info represents one "outbound-rtp" stream.In non - simulcast,
+  // this means one info per RtpSender but if simulcast is used this means
+  // one info per simulcast layer.
   std::vector<VideoSenderInfo> senders;
+  // Used for legacy getStats() API's "ssrc" stats and modern getStats() API's
+  // "track" stats. If simulcast is used, instead of having one sender info per
+  // simulcast layer, the metrics of all layers of an RtpSender are aggregated
+  // into a single sender info per RtpSender.
+  std::vector<VideoSenderInfo> aggregated_senders;
   std::vector<VideoReceiverInfo> receivers;
   RtpCodecParametersMap send_codecs;
   RtpCodecParametersMap receive_codecs;
diff --git a/media/engine/webrtc_video_engine.cc b/media/engine/webrtc_video_engine.cc
index 092fb67..71a0939 100644
--- a/media/engine/webrtc_video_engine.cc
+++ b/media/engine/webrtc_video_engine.cc
@@ -1570,6 +1570,9 @@
     for (size_t i = 0; i < info->senders.size(); ++i) {
       info->senders[i].rtt_ms = stats.rtt_ms;
     }
+    for (size_t i = 0; i < info->aggregated_senders.size(); ++i) {
+      info->aggregated_senders[i].rtt_ms = stats.rtt_ms;
+    }
   }
 
   if (log_stats)
@@ -1583,8 +1586,12 @@
   for (std::map<uint32_t, WebRtcVideoSendStream*>::iterator it =
            send_streams_.begin();
        it != send_streams_.end(); ++it) {
-    video_media_info->senders.push_back(
-        it->second->GetVideoSenderInfo(log_stats));
+    auto infos = it->second->GetPerLayerVideoSenderInfos(log_stats);
+    video_media_info->aggregated_senders.push_back(
+        it->second->GetAggregatedVideoSenderInfo(infos));
+    for (auto&& info : infos) {
+      video_media_info->senders.push_back(info);
+    }
   }
 }
 
@@ -2474,108 +2481,161 @@
         });
   }
 }
-
-VideoSenderInfo WebRtcVideoChannel::WebRtcVideoSendStream::GetVideoSenderInfo(
+std::vector<VideoSenderInfo>
+WebRtcVideoChannel::WebRtcVideoSendStream::GetPerLayerVideoSenderInfos(
     bool log_stats) {
-  VideoSenderInfo info;
   RTC_DCHECK_RUN_ON(&thread_checker_);
-  for (uint32_t ssrc : parameters_.config.rtp.ssrcs)
-    info.add_ssrc(ssrc);
-
+  VideoSenderInfo common_info;
   if (parameters_.codec_settings) {
-    info.codec_name = parameters_.codec_settings->codec.name;
-    info.codec_payload_type = parameters_.codec_settings->codec.id;
+    common_info.codec_name = parameters_.codec_settings->codec.name;
+    common_info.codec_payload_type = parameters_.codec_settings->codec.id;
   }
+  std::vector<VideoSenderInfo> infos;
+  webrtc::VideoSendStream::Stats stats;
+  if (stream_ == nullptr) {
+    for (uint32_t ssrc : parameters_.config.rtp.ssrcs) {
+      common_info.add_ssrc(ssrc);
+    }
+    infos.push_back(common_info);
+    return infos;
+  } else {
+    stats = stream_->GetStats();
+    if (log_stats)
+      RTC_LOG(LS_INFO) << stats.ToString(rtc::TimeMillis());
 
-  if (stream_ == NULL)
-    return info;
+    // Metrics that are in common for all substreams.
+    common_info.adapt_changes = stats.number_of_cpu_adapt_changes;
+    common_info.adapt_reason =
+        stats.cpu_limited_resolution ? ADAPTREASON_CPU : ADAPTREASON_NONE;
+    common_info.has_entered_low_resolution = stats.has_entered_low_resolution;
 
-  webrtc::VideoSendStream::Stats stats = stream_->GetStats();
+    // Get bandwidth limitation info from stream_->GetStats().
+    // Input resolution (output from video_adapter) can be further scaled down
+    // or higher video layer(s) can be dropped due to bitrate constraints.
+    // Note, adapt_changes only include changes from the video_adapter.
+    if (stats.bw_limited_resolution)
+      common_info.adapt_reason |= ADAPTREASON_BANDWIDTH;
 
-  if (log_stats)
-    RTC_LOG(LS_INFO) << stats.ToString(rtc::TimeMillis());
+    common_info.quality_limitation_reason = stats.quality_limitation_reason;
+    common_info.quality_limitation_durations_ms =
+        stats.quality_limitation_durations_ms;
+    common_info.quality_limitation_resolution_changes =
+        stats.quality_limitation_resolution_changes;
+    common_info.encoder_implementation_name = stats.encoder_implementation_name;
+    common_info.ssrc_groups = ssrc_groups_;
+    common_info.framerate_input = stats.input_frame_rate;
+    common_info.avg_encode_ms = stats.avg_encode_time_ms;
+    common_info.encode_usage_percent = stats.encode_usage_percent;
+    common_info.nominal_bitrate = stats.media_bitrate_bps;
+    common_info.content_type = stats.content_type;
+    common_info.aggregated_framerate_sent = stats.encode_frame_rate;
+    common_info.aggregated_huge_frames_sent = stats.huge_frames_sent;
 
-  info.adapt_changes = stats.number_of_cpu_adapt_changes;
-  info.adapt_reason =
-      stats.cpu_limited_resolution ? ADAPTREASON_CPU : ADAPTREASON_NONE;
-  info.has_entered_low_resolution = stats.has_entered_low_resolution;
-
-  // Get bandwidth limitation info from stream_->GetStats().
-  // Input resolution (output from video_adapter) can be further scaled down or
-  // higher video layer(s) can be dropped due to bitrate constraints.
-  // Note, adapt_changes only include changes from the video_adapter.
-  if (stats.bw_limited_resolution)
-    info.adapt_reason |= ADAPTREASON_BANDWIDTH;
-
-  info.quality_limitation_reason = stats.quality_limitation_reason;
-  info.quality_limitation_durations_ms = stats.quality_limitation_durations_ms;
-  info.quality_limitation_resolution_changes =
-      stats.quality_limitation_resolution_changes;
-  info.encoder_implementation_name = stats.encoder_implementation_name;
-  info.ssrc_groups = ssrc_groups_;
-  info.framerate_input = stats.input_frame_rate;
-  info.framerate_sent = stats.encode_frame_rate;
-  info.avg_encode_ms = stats.avg_encode_time_ms;
-  info.encode_usage_percent = stats.encode_usage_percent;
-  info.frames_encoded = stats.frames_encoded;
-  // TODO(bugs.webrtc.org/9547): Populate individual outbound-rtp stats objects
-  // for each simulcast stream, instead of accumulating all keyframes encoded
-  // over all simulcast streams in the same outbound-rtp stats object.
-  info.key_frames_encoded = 0;
-  for (const auto& kv : stats.substreams) {
-    info.key_frames_encoded += kv.second.frame_counts.key_frames;
+    // If we don't have any substreams, get the remaining metrics from |stats|.
+    // Otherwise, these values are obtained from |sub_stream| below.
+    if (stats.substreams.empty()) {
+      for (uint32_t ssrc : parameters_.config.rtp.ssrcs) {
+        common_info.add_ssrc(ssrc);
+      }
+      common_info.framerate_sent = stats.encode_frame_rate;
+      common_info.frames_encoded = stats.frames_encoded;
+      common_info.total_encode_time_ms = stats.total_encode_time_ms;
+      common_info.total_encoded_bytes_target = stats.total_encoded_bytes_target;
+      common_info.frames_sent = stats.frames_encoded;
+      common_info.huge_frames_sent = stats.huge_frames_sent;
+      infos.push_back(common_info);
+      return infos;
+    }
   }
-  info.total_encode_time_ms = stats.total_encode_time_ms;
-  info.total_encoded_bytes_target = stats.total_encoded_bytes_target;
-  info.qp_sum = stats.qp_sum;
-
-  info.nominal_bitrate = stats.media_bitrate_bps;
-
-  info.content_type = stats.content_type;
-  info.huge_frames_sent = stats.huge_frames_sent;
-
-  info.send_frame_width = 0;
-  info.send_frame_height = 0;
-  info.total_packet_send_delay_ms = 0;
-  std::map<uint32_t, webrtc::VideoSendStream::StreamStats>
-      outbound_rtp_substreams =
-          MergeInfoAboutOutboundRtpSubstreams(stats.substreams);
+  auto outbound_rtp_substreams =
+      MergeInfoAboutOutboundRtpSubstreams(stats.substreams);
   for (const auto& pair : outbound_rtp_substreams) {
-    // TODO(pbos): Wire up additional stats, such as padding bytes.
-    const webrtc::VideoSendStream::StreamStats& stream_stats = pair.second;
+    auto info = common_info;
+    info.add_ssrc(pair.first);
+    info.rid = parameters_.config.rtp.GetRidForSsrc(pair.first);
+    auto stream_stats = pair.second;
     RTC_DCHECK_EQ(stream_stats.type,
                   webrtc::VideoSendStream::StreamStats::StreamType::kMedia);
-    info.payload_bytes_sent += stream_stats.rtp_stats.transmitted.payload_bytes;
-    info.header_and_padding_bytes_sent +=
+    info.payload_bytes_sent = stream_stats.rtp_stats.transmitted.payload_bytes;
+    info.header_and_padding_bytes_sent =
         stream_stats.rtp_stats.transmitted.header_bytes +
         stream_stats.rtp_stats.transmitted.padding_bytes;
-    info.packets_sent += stream_stats.rtp_stats.transmitted.packets;
+    info.packets_sent = stream_stats.rtp_stats.transmitted.packets;
     info.total_packet_send_delay_ms += stream_stats.total_packet_send_delay_ms;
-    info.retransmitted_bytes_sent +=
+    info.send_frame_width = stream_stats.width;
+    info.send_frame_height = stream_stats.height;
+    info.key_frames_encoded = stream_stats.frame_counts.key_frames;
+    info.framerate_sent = stream_stats.encode_frame_rate;
+    info.frames_encoded = stream_stats.frames_encoded;
+    info.frames_sent = stream_stats.frames_encoded;
+    info.retransmitted_bytes_sent =
         stream_stats.rtp_stats.retransmitted.payload_bytes;
-    info.retransmitted_packets_sent +=
+    info.retransmitted_packets_sent =
         stream_stats.rtp_stats.retransmitted.packets;
-    info.packets_lost += stream_stats.rtcp_stats.packets_lost;
-    if (stream_stats.width > info.send_frame_width)
-      info.send_frame_width = stream_stats.width;
-    if (stream_stats.height > info.send_frame_height)
-      info.send_frame_height = stream_stats.height;
-    info.firs_rcvd += stream_stats.rtcp_packet_type_counts.fir_packets;
-    info.nacks_rcvd += stream_stats.rtcp_packet_type_counts.nack_packets;
-    info.plis_rcvd += stream_stats.rtcp_packet_type_counts.pli_packets;
+    info.packets_lost = stream_stats.rtcp_stats.packets_lost;
+    info.firs_rcvd = stream_stats.rtcp_packet_type_counts.fir_packets;
+    info.nacks_rcvd = stream_stats.rtcp_packet_type_counts.nack_packets;
+    info.plis_rcvd = stream_stats.rtcp_packet_type_counts.pli_packets;
     if (stream_stats.report_block_data.has_value()) {
       info.report_block_datas.push_back(stream_stats.report_block_data.value());
     }
-  }
-  if (!stats.substreams.empty()) {
-    // TODO(pbos): Report fraction lost per SSRC.
-    webrtc::VideoSendStream::StreamStats first_stream_stats =
-        stats.substreams.begin()->second;
     info.fraction_lost =
-        static_cast<float>(first_stream_stats.rtcp_stats.fraction_lost) /
-        (1 << 8);
+        static_cast<float>(stream_stats.rtcp_stats.fraction_lost) / (1 << 8);
+    info.qp_sum = stream_stats.qp_sum;
+    info.total_encode_time_ms = stream_stats.total_encode_time_ms;
+    info.total_encoded_bytes_target = stream_stats.total_encoded_bytes_target;
+    info.huge_frames_sent = stream_stats.huge_frames_sent;
+    infos.push_back(info);
   }
+  return infos;
+}
 
+VideoSenderInfo
+WebRtcVideoChannel::WebRtcVideoSendStream::GetAggregatedVideoSenderInfo(
+    const std::vector<VideoSenderInfo>& infos) const {
+  RTC_DCHECK_RUN_ON(&thread_checker_);
+  RTC_DCHECK(!infos.empty());
+  if (infos.size() == 1) {
+    return infos[0];
+  }
+  VideoSenderInfo info = infos[0];
+  info.local_stats.clear();
+  for (uint32_t ssrc : parameters_.config.rtp.ssrcs) {
+    info.add_ssrc(ssrc);
+  }
+  info.framerate_sent = info.aggregated_framerate_sent;
+  info.huge_frames_sent = info.aggregated_huge_frames_sent;
+
+  for (size_t i = 1; i < infos.size(); i++) {
+    info.key_frames_encoded += infos[i].key_frames_encoded;
+    info.payload_bytes_sent += infos[i].payload_bytes_sent;
+    info.header_and_padding_bytes_sent +=
+        infos[i].header_and_padding_bytes_sent;
+    info.packets_sent += infos[i].packets_sent;
+    info.total_packet_send_delay_ms += infos[i].total_packet_send_delay_ms;
+    info.retransmitted_bytes_sent += infos[i].retransmitted_bytes_sent;
+    info.retransmitted_packets_sent += infos[i].retransmitted_packets_sent;
+    info.packets_lost += infos[i].packets_lost;
+    if (infos[i].send_frame_width > info.send_frame_width)
+      info.send_frame_width = infos[i].send_frame_width;
+    if (infos[i].send_frame_height > info.send_frame_height)
+      info.send_frame_height = infos[i].send_frame_height;
+    info.firs_rcvd += infos[i].firs_rcvd;
+    info.nacks_rcvd += infos[i].nacks_rcvd;
+    info.plis_rcvd += infos[i].plis_rcvd;
+    if (infos[i].report_block_datas.size())
+      info.report_block_datas.push_back(infos[i].report_block_datas[0]);
+    if (infos[i].qp_sum) {
+      if (!info.qp_sum) {
+        info.qp_sum = 0;
+      }
+      info.qp_sum = *info.qp_sum + *infos[i].qp_sum;
+    }
+    info.frames_encoded += infos[i].frames_encoded;
+    info.frames_sent += infos[i].frames_sent;
+    info.total_encode_time_ms += infos[i].total_encode_time_ms;
+    info.total_encoded_bytes_target += infos[i].total_encoded_bytes_target;
+  }
   return info;
 }
 
diff --git a/media/engine/webrtc_video_engine.h b/media/engine/webrtc_video_engine.h
index 6ed556e..00d2495 100644
--- a/media/engine/webrtc_video_engine.h
+++ b/media/engine/webrtc_video_engine.h
@@ -357,7 +357,12 @@
     void SetSend(bool send);
 
     const std::vector<uint32_t>& GetSsrcs() const;
-    VideoSenderInfo GetVideoSenderInfo(bool log_stats);
+    // Returns per ssrc VideoSenderInfos. Useful for simulcast scenario.
+    std::vector<VideoSenderInfo> GetPerLayerVideoSenderInfos(bool log_stats);
+    // Aggregates per ssrc VideoSenderInfos to single VideoSenderInfo for
+    // legacy reasons. Used in old GetStats API and track stats.
+    VideoSenderInfo GetAggregatedVideoSenderInfo(
+        const std::vector<VideoSenderInfo>& infos) const;
     void FillBitrateInfo(BandwidthEstimationInfo* bwe_info);
 
     void SetEncoderToPacketizerFrameTransformer(
diff --git a/media/engine/webrtc_video_engine_unittest.cc b/media/engine/webrtc_video_engine_unittest.cc
index 27206db..4a33c51 100644
--- a/media/engine/webrtc_video_engine_unittest.cc
+++ b/media/engine/webrtc_video_engine_unittest.cc
@@ -2425,6 +2425,18 @@
     ASSERT_TRUE(channel_->SetSendParameters(send_parameters_));
   }
 
+  cricket::VideoCodec GetEngineCodec(const std::string& name) {
+    for (const cricket::VideoCodec& engine_codec : engine_.send_codecs()) {
+      if (absl::EqualsIgnoreCase(name, engine_codec.name))
+        return engine_codec;
+    }
+    // This point should never be reached.
+    ADD_FAILURE() << "Unrecognized codec name: " << name;
+    return cricket::VideoCodec();
+  }
+
+  cricket::VideoCodec DefaultCodec() { return GetEngineCodec("VP8"); }
+
  protected:
   FakeVideoSendStream* AddSendStream() {
     return AddSendStream(StreamParams::CreateLegacy(++last_ssrc_));
@@ -5218,21 +5230,369 @@
 
   cricket::VideoMediaInfo info;
   ASSERT_TRUE(channel_->GetStats(&info));
-  // TODO(bugs.webrtc.org/9547): Populate individual outbound-rtp stats objects
-  // for each simulcast stream, instead of accumulating all keyframes encoded
-  // over all simulcast streams in the same outbound-rtp stats object.
-  EXPECT_EQ(97u, info.senders[0].key_frames_encoded);
+  EXPECT_EQ(info.senders.size(), 2u);
+  EXPECT_EQ(10u, info.senders[0].key_frames_encoded);
+  EXPECT_EQ(87u, info.senders[1].key_frames_encoded);
+  EXPECT_EQ(97u, info.aggregated_senders[0].key_frames_encoded);
 }
 
-TEST_F(WebRtcVideoChannelTest, GetStatsReportsQpSum) {
+TEST_F(WebRtcVideoChannelTest, GetStatsReportsPerLayerQpSum) {
   FakeVideoSendStream* stream = AddSendStream();
   webrtc::VideoSendStream::Stats stats;
-  stats.qp_sum = 13;
+  stats.substreams[123].qp_sum = 15;
+  stats.substreams[456].qp_sum = 11;
   stream->SetStats(stats);
 
   cricket::VideoMediaInfo info;
   ASSERT_TRUE(channel_->GetStats(&info));
-  EXPECT_EQ(stats.qp_sum, info.senders[0].qp_sum);
+  EXPECT_EQ(info.senders.size(), 2u);
+  EXPECT_EQ(stats.substreams[123].qp_sum, info.senders[0].qp_sum);
+  EXPECT_EQ(stats.substreams[456].qp_sum, info.senders[1].qp_sum);
+  EXPECT_EQ(*info.aggregated_senders[0].qp_sum, 26u);
+}
+
+webrtc::VideoSendStream::Stats GetInitialisedStats() {
+  webrtc::VideoSendStream::Stats stats;
+  stats.encoder_implementation_name = "vp";
+  stats.input_frame_rate = 1;
+  stats.encode_frame_rate = 2;
+  stats.avg_encode_time_ms = 3;
+  stats.encode_usage_percent = 4;
+  stats.frames_encoded = 5;
+  stats.total_encode_time_ms = 6;
+  stats.frames_dropped_by_capturer = 7;
+  stats.frames_dropped_by_encoder_queue = 8;
+  stats.frames_dropped_by_rate_limiter = 9;
+  stats.frames_dropped_by_congestion_window = 10;
+  stats.frames_dropped_by_encoder = 11;
+  stats.target_media_bitrate_bps = 13;
+  stats.media_bitrate_bps = 14;
+  stats.suspended = true;
+  stats.bw_limited_resolution = true;
+  stats.cpu_limited_resolution = true;
+  // Not wired.
+  stats.bw_limited_framerate = true;
+  // Not wired.
+  stats.cpu_limited_framerate = true;
+  stats.quality_limitation_reason = webrtc::QualityLimitationReason::kCpu;
+  stats.quality_limitation_durations_ms[webrtc::QualityLimitationReason::kCpu] =
+      15;
+  stats.quality_limitation_resolution_changes = 16;
+  stats.number_of_cpu_adapt_changes = 17;
+  stats.number_of_quality_adapt_changes = 18;
+  stats.has_entered_low_resolution = true;
+  stats.content_type = webrtc::VideoContentType::SCREENSHARE;
+  stats.frames_sent = 19;
+  stats.huge_frames_sent = 20;
+
+  return stats;
+}
+
+TEST_F(WebRtcVideoChannelTest, GetAggregatedStatsReportWithoutSubStreams) {
+  FakeVideoSendStream* stream = AddSendStream();
+  auto stats = GetInitialisedStats();
+  stream->SetStats(stats);
+  cricket::VideoMediaInfo video_media_info;
+  ASSERT_TRUE(channel_->GetStats(&video_media_info));
+  EXPECT_EQ(video_media_info.aggregated_senders.size(), 1u);
+  auto& sender = video_media_info.aggregated_senders[0];
+
+  // MediaSenderInfo
+
+  EXPECT_EQ(sender.payload_bytes_sent, 0);
+  EXPECT_EQ(sender.header_and_padding_bytes_sent, 0);
+  EXPECT_EQ(sender.retransmitted_bytes_sent, 0u);
+  EXPECT_EQ(sender.packets_sent, 0);
+  EXPECT_EQ(sender.retransmitted_packets_sent, 0u);
+  EXPECT_EQ(sender.packets_lost, 0);
+  EXPECT_EQ(sender.fraction_lost, 0.0f);
+  EXPECT_EQ(sender.rtt_ms, 0);
+  EXPECT_EQ(sender.codec_name, DefaultCodec().name);
+  EXPECT_EQ(sender.codec_payload_type, DefaultCodec().id);
+  EXPECT_EQ(sender.local_stats.size(), 1u);
+  EXPECT_EQ(sender.local_stats[0].ssrc, last_ssrc_);
+  EXPECT_EQ(sender.local_stats[0].timestamp, 0.0f);
+  EXPECT_EQ(sender.remote_stats.size(), 0u);
+  EXPECT_EQ(sender.report_block_datas.size(), 0u);
+
+  // VideoSenderInfo
+
+  EXPECT_EQ(sender.ssrc_groups.size(), 0u);
+  EXPECT_EQ(sender.encoder_implementation_name,
+            stats.encoder_implementation_name);
+  // 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.send_frame_width, 0);
+  EXPECT_EQ(sender.send_frame_height, 0);
+
+  EXPECT_EQ(sender.framerate_input, stats.input_frame_rate);
+  EXPECT_EQ(sender.framerate_sent, stats.encode_frame_rate);
+  EXPECT_EQ(sender.nominal_bitrate, stats.media_bitrate_bps);
+  EXPECT_NE(sender.adapt_reason & WebRtcVideoChannel::ADAPTREASON_CPU, 0);
+  EXPECT_NE(sender.adapt_reason & WebRtcVideoChannel::ADAPTREASON_BANDWIDTH, 0);
+  EXPECT_EQ(sender.adapt_changes, stats.number_of_cpu_adapt_changes);
+  EXPECT_EQ(sender.quality_limitation_reason, stats.quality_limitation_reason);
+  EXPECT_EQ(sender.quality_limitation_durations_ms,
+            stats.quality_limitation_durations_ms);
+  EXPECT_EQ(sender.quality_limitation_resolution_changes,
+            stats.quality_limitation_resolution_changes);
+  EXPECT_EQ(sender.avg_encode_ms, stats.avg_encode_time_ms);
+  EXPECT_EQ(sender.encode_usage_percent, stats.encode_usage_percent);
+  EXPECT_EQ(sender.frames_encoded, stats.frames_encoded);
+  // Comes from substream only.
+  EXPECT_EQ(sender.key_frames_encoded, 0u);
+
+  EXPECT_EQ(sender.total_encode_time_ms, stats.total_encode_time_ms);
+  EXPECT_EQ(sender.total_encoded_bytes_target,
+            stats.total_encoded_bytes_target);
+  // Comes from substream only.
+  EXPECT_EQ(sender.total_packet_send_delay_ms, 0u);
+  EXPECT_EQ(sender.qp_sum, absl::nullopt);
+
+  EXPECT_EQ(sender.has_entered_low_resolution,
+            stats.has_entered_low_resolution);
+  EXPECT_EQ(sender.content_type, webrtc::VideoContentType::SCREENSHARE);
+  EXPECT_EQ(sender.frames_sent, stats.frames_encoded);
+  EXPECT_EQ(sender.huge_frames_sent, stats.huge_frames_sent);
+  EXPECT_EQ(sender.rid, absl::nullopt);
+}
+
+TEST_F(WebRtcVideoChannelTest, GetAggregatedStatsReportForSubStreams) {
+  FakeVideoSendStream* stream = AddSendStream();
+  auto stats = GetInitialisedStats();
+
+  const uint32_t ssrc_1 = 123u;
+  const uint32_t ssrc_2 = 456u;
+
+  auto& substream = stats.substreams[ssrc_1];
+  substream.frame_counts.key_frames = 1;
+  substream.frame_counts.delta_frames = 2;
+  substream.width = 3;
+  substream.height = 4;
+  substream.total_bitrate_bps = 5;
+  substream.retransmit_bitrate_bps = 6;
+  substream.avg_delay_ms = 7;
+  substream.max_delay_ms = 8;
+  substream.total_packet_send_delay_ms = 9;
+  substream.rtp_stats.transmitted.header_bytes = 10;
+  substream.rtp_stats.transmitted.padding_bytes = 11;
+  substream.rtp_stats.retransmitted.payload_bytes = 12;
+  substream.rtp_stats.retransmitted.packets = 13;
+  substream.rtcp_packet_type_counts.fir_packets = 14;
+  substream.rtcp_packet_type_counts.nack_packets = 15;
+  substream.rtcp_packet_type_counts.pli_packets = 16;
+  substream.rtcp_stats.packets_lost = 17;
+  substream.rtcp_stats.fraction_lost = 18;
+  webrtc::ReportBlockData report_block_data;
+  report_block_data.AddRoundTripTimeSample(19);
+  substream.report_block_data = report_block_data;
+  substream.encode_frame_rate = 20.0;
+  substream.frames_encoded = 21;
+  substream.qp_sum = 22;
+  substream.total_encode_time_ms = 23;
+  substream.total_encoded_bytes_target = 24;
+  substream.huge_frames_sent = 25;
+
+  stats.substreams[ssrc_2] = substream;
+
+  stream->SetStats(stats);
+
+  cricket::VideoMediaInfo video_media_info;
+  ASSERT_TRUE(channel_->GetStats(&video_media_info));
+  EXPECT_EQ(video_media_info.aggregated_senders.size(), 1u);
+  auto& sender = video_media_info.aggregated_senders[0];
+
+  // MediaSenderInfo
+
+  EXPECT_EQ(
+      sender.payload_bytes_sent,
+      static_cast<int64_t>(2u * substream.rtp_stats.transmitted.payload_bytes));
+  EXPECT_EQ(sender.header_and_padding_bytes_sent,
+            static_cast<int64_t>(
+                2u * (substream.rtp_stats.transmitted.header_bytes +
+                      substream.rtp_stats.transmitted.padding_bytes)));
+  EXPECT_EQ(sender.retransmitted_bytes_sent,
+            2u * substream.rtp_stats.retransmitted.payload_bytes);
+  EXPECT_EQ(sender.packets_sent,
+            static_cast<int>(2 * substream.rtp_stats.transmitted.packets));
+  EXPECT_EQ(sender.retransmitted_packets_sent,
+            2u * substream.rtp_stats.retransmitted.packets);
+  EXPECT_EQ(sender.packets_lost, 2 * substream.rtcp_stats.packets_lost);
+  EXPECT_EQ(sender.fraction_lost,
+            static_cast<float>(substream.rtcp_stats.fraction_lost) / (1 << 8));
+  EXPECT_EQ(sender.rtt_ms, 0);
+  EXPECT_EQ(sender.codec_name, DefaultCodec().name);
+  EXPECT_EQ(sender.codec_payload_type, DefaultCodec().id);
+  EXPECT_EQ(sender.local_stats.size(), 1u);
+  EXPECT_EQ(sender.local_stats[0].ssrc, last_ssrc_);
+  EXPECT_EQ(sender.local_stats[0].timestamp, 0.0f);
+  EXPECT_EQ(sender.remote_stats.size(), 0u);
+  EXPECT_EQ(sender.report_block_datas.size(), 2u * 1);
+
+  // VideoSenderInfo
+
+  EXPECT_EQ(sender.ssrc_groups.size(), 0u);
+  EXPECT_EQ(sender.encoder_implementation_name,
+            stats.encoder_implementation_name);
+  EXPECT_EQ(
+      sender.firs_rcvd,
+      static_cast<int>(2 * substream.rtcp_packet_type_counts.fir_packets));
+  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.send_frame_width, substream.width);
+  EXPECT_EQ(sender.send_frame_height, substream.height);
+
+  EXPECT_EQ(sender.framerate_input, stats.input_frame_rate);
+  EXPECT_EQ(sender.framerate_sent, stats.encode_frame_rate);
+  EXPECT_EQ(sender.nominal_bitrate, stats.media_bitrate_bps);
+  EXPECT_NE(sender.adapt_reason & WebRtcVideoChannel::ADAPTREASON_CPU, 0);
+  EXPECT_NE(sender.adapt_reason & WebRtcVideoChannel::ADAPTREASON_BANDWIDTH, 0);
+  EXPECT_EQ(sender.adapt_changes, stats.number_of_cpu_adapt_changes);
+  EXPECT_EQ(sender.quality_limitation_reason, stats.quality_limitation_reason);
+  EXPECT_EQ(sender.quality_limitation_durations_ms,
+            stats.quality_limitation_durations_ms);
+  EXPECT_EQ(sender.quality_limitation_resolution_changes,
+            stats.quality_limitation_resolution_changes);
+  EXPECT_EQ(sender.avg_encode_ms, stats.avg_encode_time_ms);
+  EXPECT_EQ(sender.encode_usage_percent, stats.encode_usage_percent);
+  EXPECT_EQ(sender.frames_encoded, 2u * substream.frames_encoded);
+  EXPECT_EQ(sender.key_frames_encoded, 2u * substream.frame_counts.key_frames);
+  EXPECT_EQ(sender.total_encode_time_ms, 2u * substream.total_encode_time_ms);
+  EXPECT_EQ(sender.total_encoded_bytes_target,
+            2u * substream.total_encoded_bytes_target);
+  EXPECT_EQ(sender.total_packet_send_delay_ms,
+            2u * substream.total_packet_send_delay_ms);
+  EXPECT_EQ(sender.has_entered_low_resolution,
+            stats.has_entered_low_resolution);
+  EXPECT_EQ(sender.qp_sum, 2u * *substream.qp_sum);
+  EXPECT_EQ(sender.content_type, webrtc::VideoContentType::SCREENSHARE);
+  EXPECT_EQ(sender.frames_sent, 2u * substream.frames_encoded);
+  EXPECT_EQ(sender.huge_frames_sent, stats.huge_frames_sent);
+  EXPECT_EQ(sender.rid, absl::nullopt);
+}
+
+TEST_F(WebRtcVideoChannelTest, GetPerLayerStatsReportForSubStreams) {
+  FakeVideoSendStream* stream = AddSendStream();
+  auto stats = GetInitialisedStats();
+
+  const uint32_t ssrc_1 = 123u;
+  const uint32_t ssrc_2 = 456u;
+
+  auto& substream = stats.substreams[ssrc_1];
+  substream.frame_counts.key_frames = 1;
+  substream.frame_counts.delta_frames = 2;
+  substream.width = 3;
+  substream.height = 4;
+  substream.total_bitrate_bps = 5;
+  substream.retransmit_bitrate_bps = 6;
+  substream.avg_delay_ms = 7;
+  substream.max_delay_ms = 8;
+  substream.total_packet_send_delay_ms = 9;
+  substream.rtp_stats.transmitted.header_bytes = 10;
+  substream.rtp_stats.transmitted.padding_bytes = 11;
+  substream.rtp_stats.retransmitted.payload_bytes = 12;
+  substream.rtp_stats.retransmitted.packets = 13;
+  substream.rtcp_packet_type_counts.fir_packets = 14;
+  substream.rtcp_packet_type_counts.nack_packets = 15;
+  substream.rtcp_packet_type_counts.pli_packets = 16;
+  substream.rtcp_stats.packets_lost = 17;
+  substream.rtcp_stats.fraction_lost = 18;
+  webrtc::ReportBlockData report_block_data;
+  report_block_data.AddRoundTripTimeSample(19);
+  substream.report_block_data = report_block_data;
+  substream.encode_frame_rate = 20.0;
+  substream.frames_encoded = 21;
+  substream.qp_sum = 22;
+  substream.total_encode_time_ms = 23;
+  substream.total_encoded_bytes_target = 24;
+  substream.huge_frames_sent = 25;
+
+  stats.substreams[ssrc_2] = substream;
+
+  stream->SetStats(stats);
+
+  cricket::VideoMediaInfo video_media_info;
+  ASSERT_TRUE(channel_->GetStats(&video_media_info));
+  EXPECT_EQ(video_media_info.senders.size(), 2u);
+  auto& sender = video_media_info.senders[0];
+
+  // MediaSenderInfo
+
+  EXPECT_EQ(
+      sender.payload_bytes_sent,
+      static_cast<int64_t>(substream.rtp_stats.transmitted.payload_bytes));
+  EXPECT_EQ(
+      sender.header_and_padding_bytes_sent,
+      static_cast<int64_t>(substream.rtp_stats.transmitted.header_bytes +
+                           substream.rtp_stats.transmitted.padding_bytes));
+  EXPECT_EQ(sender.retransmitted_bytes_sent,
+            substream.rtp_stats.retransmitted.payload_bytes);
+  EXPECT_EQ(sender.packets_sent,
+            static_cast<int>(substream.rtp_stats.transmitted.packets));
+  EXPECT_EQ(sender.retransmitted_packets_sent,
+            substream.rtp_stats.retransmitted.packets);
+  EXPECT_EQ(sender.packets_lost, substream.rtcp_stats.packets_lost);
+  EXPECT_EQ(sender.fraction_lost,
+            static_cast<float>(substream.rtcp_stats.fraction_lost) / (1 << 8));
+  EXPECT_EQ(sender.rtt_ms, 0);
+  EXPECT_EQ(sender.codec_name, DefaultCodec().name);
+  EXPECT_EQ(sender.codec_payload_type, DefaultCodec().id);
+  EXPECT_EQ(sender.local_stats.size(), 1u);
+  EXPECT_EQ(sender.local_stats[0].ssrc, ssrc_1);
+  EXPECT_EQ(sender.local_stats[0].timestamp, 0.0f);
+  EXPECT_EQ(sender.remote_stats.size(), 0u);
+  EXPECT_EQ(sender.report_block_datas.size(), 1u);
+
+  // VideoSenderInfo
+
+  EXPECT_EQ(sender.ssrc_groups.size(), 0u);
+  EXPECT_EQ(sender.encoder_implementation_name,
+            stats.encoder_implementation_name);
+  EXPECT_EQ(sender.firs_rcvd,
+            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.send_frame_width, substream.width);
+  EXPECT_EQ(sender.send_frame_height, substream.height);
+
+  EXPECT_EQ(sender.framerate_input, stats.input_frame_rate);
+  EXPECT_EQ(sender.framerate_sent, substream.encode_frame_rate);
+  EXPECT_EQ(sender.nominal_bitrate, stats.media_bitrate_bps);
+  EXPECT_NE(sender.adapt_reason & WebRtcVideoChannel::ADAPTREASON_CPU, 0);
+  EXPECT_NE(sender.adapt_reason & WebRtcVideoChannel::ADAPTREASON_BANDWIDTH, 0);
+  EXPECT_EQ(sender.adapt_changes, stats.number_of_cpu_adapt_changes);
+  EXPECT_EQ(sender.quality_limitation_reason, stats.quality_limitation_reason);
+  EXPECT_EQ(sender.quality_limitation_durations_ms,
+            stats.quality_limitation_durations_ms);
+  EXPECT_EQ(sender.quality_limitation_resolution_changes,
+            stats.quality_limitation_resolution_changes);
+  EXPECT_EQ(sender.avg_encode_ms, stats.avg_encode_time_ms);
+  EXPECT_EQ(sender.encode_usage_percent, stats.encode_usage_percent);
+  EXPECT_EQ(sender.frames_encoded,
+            static_cast<uint32_t>(substream.frames_encoded));
+  EXPECT_EQ(sender.key_frames_encoded,
+            static_cast<uint32_t>(substream.frame_counts.key_frames));
+  EXPECT_EQ(sender.total_encode_time_ms, substream.total_encode_time_ms);
+  EXPECT_EQ(sender.total_encoded_bytes_target,
+            substream.total_encoded_bytes_target);
+  EXPECT_EQ(sender.total_packet_send_delay_ms,
+            substream.total_packet_send_delay_ms);
+  EXPECT_EQ(sender.has_entered_low_resolution,
+            stats.has_entered_low_resolution);
+  EXPECT_EQ(sender.qp_sum, *substream.qp_sum);
+  EXPECT_EQ(sender.content_type, webrtc::VideoContentType::SCREENSHARE);
+  EXPECT_EQ(sender.frames_sent,
+            static_cast<uint32_t>(substream.frames_encoded));
+  EXPECT_EQ(sender.huge_frames_sent, substream.huge_frames_sent);
+  EXPECT_EQ(sender.rid, absl::nullopt);
 }
 
 TEST_F(WebRtcVideoChannelTest, GetStatsReportsUpperResolution) {
@@ -5248,9 +5608,16 @@
 
   cricket::VideoMediaInfo info;
   ASSERT_TRUE(channel_->GetStats(&info));
-  ASSERT_EQ(1u, info.senders.size());
-  EXPECT_EQ(123, info.senders[0].send_frame_width);
+  ASSERT_EQ(1u, info.aggregated_senders.size());
+  ASSERT_EQ(3u, info.senders.size());
+  EXPECT_EQ(123, info.senders[1].send_frame_width);
+  EXPECT_EQ(40, info.senders[1].send_frame_height);
+  EXPECT_EQ(80, info.senders[2].send_frame_width);
+  EXPECT_EQ(31, info.senders[2].send_frame_height);
+  EXPECT_EQ(20, info.senders[0].send_frame_width);
   EXPECT_EQ(90, info.senders[0].send_frame_height);
+  EXPECT_EQ(123, info.aggregated_senders[0].send_frame_width);
+  EXPECT_EQ(90, info.aggregated_senders[0].send_frame_height);
 }
 
 TEST_F(WebRtcVideoChannelTest, GetStatsReportsCpuAdaptationStats) {
@@ -5448,19 +5815,18 @@
 
   cricket::VideoMediaInfo info;
   ASSERT_TRUE(channel_->GetStats(&info));
-  // TODO(https://crbug.com/webrtc/9547): Populate individual VideoSenderInfo
-  // objects for each simulcast stream, instead of accumulating all layers into
-  // a single VideoSenderInfo. When this is fixed, this test should expect that
-  // there are two VideoSenderInfo, where the first info accounts for the first
-  // RTX and the second info accounts for the second RTX. In order for the test
-  // to be set up correctly, it may need to be updated such that the
-  // relationship between RTP and RTX streams are known. See also
-  // https://crbug.com/webrtc/11439.
-  EXPECT_EQ(60u, info.senders[0].header_and_padding_bytes_sent);
-  EXPECT_EQ(107u, info.senders[0].payload_bytes_sent);
-  EXPECT_EQ(20, info.senders[0].packets_sent);
-  EXPECT_EQ(30u, info.senders[0].retransmitted_bytes_sent);
-  EXPECT_EQ(5u, info.senders[0].retransmitted_packets_sent);
+  EXPECT_EQ(info.senders.size(), 2u);
+  EXPECT_EQ(15u, info.senders[0].header_and_padding_bytes_sent);
+  EXPECT_EQ(30u, info.senders[0].payload_bytes_sent);
+  EXPECT_EQ(4, info.senders[0].packets_sent);
+  EXPECT_EQ(10u, info.senders[0].retransmitted_bytes_sent);
+  EXPECT_EQ(1u, info.senders[0].retransmitted_packets_sent);
+
+  EXPECT_EQ(45u, info.senders[1].header_and_padding_bytes_sent);
+  EXPECT_EQ(77u, info.senders[1].payload_bytes_sent);
+  EXPECT_EQ(16, info.senders[1].packets_sent);
+  EXPECT_EQ(20u, info.senders[1].retransmitted_bytes_sent);
+  EXPECT_EQ(4u, info.senders[1].retransmitted_packets_sent);
 }
 
 TEST_F(WebRtcVideoChannelTest,
@@ -5492,9 +5858,17 @@
 
   cricket::VideoMediaInfo info;
   ASSERT_TRUE(channel_->GetStats(&info));
-  EXPECT_EQ(7, info.senders[0].firs_rcvd);
-  EXPECT_EQ(10, info.senders[0].nacks_rcvd);
-  EXPECT_EQ(13, info.senders[0].plis_rcvd);
+  EXPECT_EQ(2, info.senders[0].firs_rcvd);
+  EXPECT_EQ(3, 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(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(13, info.aggregated_senders[0].plis_rcvd);
 }
 
 TEST_F(WebRtcVideoChannelTest,
@@ -5639,13 +6013,16 @@
 
   cricket::VideoMediaInfo info;
   ASSERT_TRUE(channel_->GetStats(&info));
-  ASSERT_EQ(2u, info.senders.size());
+  ASSERT_EQ(2u, info.aggregated_senders.size());
+  ASSERT_EQ(4u, info.senders.size());
   BandwidthEstimationInfo bwe_info;
   channel_->FillBitrateInfo(&bwe_info);
   // Assuming stream and stream2 corresponds to senders[0] and [1] respectively
   // is OK as std::maps are sorted and AddSendStream() gives increasing SSRCs.
-  EXPECT_EQ(stats.media_bitrate_bps, info.senders[0].nominal_bitrate);
-  EXPECT_EQ(stats2.media_bitrate_bps, info.senders[1].nominal_bitrate);
+  EXPECT_EQ(stats.media_bitrate_bps,
+            info.aggregated_senders[0].nominal_bitrate);
+  EXPECT_EQ(stats2.media_bitrate_bps,
+            info.aggregated_senders[1].nominal_bitrate);
   EXPECT_EQ(stats.target_media_bitrate_bps + stats2.target_media_bitrate_bps,
             bwe_info.target_enc_bitrate);
   EXPECT_EQ(stats.media_bitrate_bps + stats2.media_bitrate_bps,
diff --git a/pc/peer_connection.cc b/pc/peer_connection.cc
index 14281eb..05e7b95 100644
--- a/pc/peer_connection.cc
+++ b/pc/peer_connection.cc
@@ -973,7 +973,8 @@
          offer_extmap_allow_mixed == o.offer_extmap_allow_mixed &&
          turn_logging_id == o.turn_logging_id &&
          enable_implicit_rollback == o.enable_implicit_rollback &&
-         allow_codec_switching == o.allow_codec_switching;
+         allow_codec_switching == o.allow_codec_switching &&
+         enable_simulcast_stats == o.enable_simulcast_stats;
 }
 
 bool PeerConnectionInterface::RTCConfiguration::operator!=(
diff --git a/pc/peer_connection_integrationtest.cc b/pc/peer_connection_integrationtest.cc
index 063c24f..f3b4f28 100644
--- a/pc/peer_connection_integrationtest.cc
+++ b/pc/peer_connection_integrationtest.cc
@@ -3059,7 +3059,7 @@
   ASSERT_TRUE(caller_report);
   auto outbound_stream_stats =
       caller_report->GetStatsOfType<webrtc::RTCOutboundRTPStreamStats>();
-  ASSERT_EQ(4u, outbound_stream_stats.size());
+  ASSERT_EQ(outbound_stream_stats.size(), 4u);
   std::vector<std::string> outbound_track_ids;
   for (const auto& stat : outbound_stream_stats) {
     ASSERT_TRUE(stat->bytes_sent.is_defined());
diff --git a/pc/rtc_stats_collector.cc b/pc/rtc_stats_collector.cc
index 01799b4..0e2f170 100644
--- a/pc/rtc_stats_collector.cc
+++ b/pc/rtc_stats_collector.cc
@@ -397,6 +397,7 @@
 void SetOutboundRTPStreamStatsFromVideoSenderInfo(
     const std::string& mid,
     const cricket::VideoSenderInfo& video_sender_info,
+    bool enable_simulcast_stats,
     RTCOutboundRTPStreamStats* outbound_video) {
   SetOutboundRTPStreamStatsFromMediaSenderInfo(video_sender_info,
                                                outbound_video);
@@ -421,6 +422,21 @@
       rtc::kNumMillisecsPerSec;
   outbound_video->total_encoded_bytes_target =
       video_sender_info.total_encoded_bytes_target;
+  if (enable_simulcast_stats) {
+    if (video_sender_info.send_frame_width > 0) {
+      outbound_video->frame_width =
+          static_cast<uint32_t>(video_sender_info.send_frame_width);
+    }
+    if (video_sender_info.send_frame_height > 0) {
+      outbound_video->frame_height =
+          static_cast<uint32_t>(video_sender_info.send_frame_height);
+    }
+    if (video_sender_info.framerate_sent > 0) {
+      outbound_video->frames_per_second = video_sender_info.framerate_sent;
+    }
+    outbound_video->frames_sent = video_sender_info.frames_sent;
+    outbound_video->huge_frames_sent = video_sender_info.huge_frames_sent;
+  }
   outbound_video->total_packet_send_delay =
       static_cast<double>(video_sender_info.total_packet_send_delay_ms) /
       rtc::kNumMillisecsPerSec;
@@ -437,6 +453,9 @@
     outbound_video->encoder_implementation =
         video_sender_info.encoder_implementation_name;
   }
+  if (video_sender_info.rid) {
+    outbound_video->rid = *video_sender_info.rid;
+  }
 }
 
 std::unique_ptr<RTCRemoteInboundRtpStreamStats>
@@ -968,6 +987,7 @@
   RTC_DCHECK_GE(cache_lifetime_us_, 0);
   pc_->SignalDataChannelCreated().connect(
       this, &RTCStatsCollector::OnDataChannelCreated);
+  enable_simulcast_stats_ = pc_->GetConfiguration().enable_simulcast_stats;
 }
 
 RTCStatsCollector::~RTCStatsCollector() {
@@ -1643,14 +1663,16 @@
   // Outbound
   std::map<std::string, RTCOutboundRTPStreamStats*> video_outbound_rtps;
   for (const cricket::VideoSenderInfo& video_sender_info :
-       track_media_info_map.video_media_info()->senders) {
+       enable_simulcast_stats_
+           ? track_media_info_map.video_media_info()->senders
+           : track_media_info_map.video_media_info()->aggregated_senders) {
     if (!video_sender_info.connected())
       continue;
     auto outbound_video = std::make_unique<RTCOutboundRTPStreamStats>(
         RTCOutboundRTPStreamStatsIDFromSSRC(false, video_sender_info.ssrc()),
         timestamp_us);
-    SetOutboundRTPStreamStatsFromVideoSenderInfo(mid, video_sender_info,
-                                                 outbound_video.get());
+    SetOutboundRTPStreamStatsFromVideoSenderInfo(
+        mid, video_sender_info, enable_simulcast_stats_, outbound_video.get());
     rtc::scoped_refptr<VideoTrackInterface> video_track =
         track_media_info_map.GetVideoTrack(video_sender_info);
     if (video_track) {
diff --git a/pc/rtc_stats_collector.h b/pc/rtc_stats_collector.h
index cd5ec21..7c85a35 100644
--- a/pc/rtc_stats_collector.h
+++ b/pc/rtc_stats_collector.h
@@ -288,6 +288,7 @@
     std::set<uintptr_t> opened_data_channels;
   };
   InternalRecord internal_record_;
+  bool enable_simulcast_stats_ = false;
 };
 
 const char* CandidateTypeToRTCIceCandidateTypeForTesting(
diff --git a/pc/rtc_stats_collector_unittest.cc b/pc/rtc_stats_collector_unittest.cc
index 12f6059..d3114f3 100644
--- a/pc/rtc_stats_collector_unittest.cc
+++ b/pc/rtc_stats_collector_unittest.cc
@@ -519,6 +519,7 @@
                     MediaStreamTrackInterface::kVideoKind);
 
       video_media_info.senders.push_back(video_sender_info);
+      video_media_info.aggregated_senders.push_back(video_sender_info);
       rtc::scoped_refptr<MockRtpSenderInternal> rtp_sender = CreateMockSender(
           cricket::MEDIA_TYPE_VIDEO,
           rtc::scoped_refptr<MediaStreamTrackInterface>(local_video_track),
@@ -641,6 +642,7 @@
         cricket::SsrcSenderInfo());
     video_media_info.senders[0].local_stats[0].ssrc = 3;
     video_media_info.senders[0].codec_payload_type = send_codec.payload_type;
+    video_media_info.aggregated_senders.push_back(video_media_info.senders[0]);
     // inbound-rtp
     graph.inbound_rtp_id = "RTCInboundRTPVideoStream_4";
     video_media_info.receivers.push_back(cricket::VideoReceiverInfo());
@@ -2014,7 +2016,12 @@
   video_media_info.senders[0].qp_sum = absl::nullopt;
   video_media_info.senders[0].content_type = VideoContentType::UNSPECIFIED;
   video_media_info.senders[0].encoder_implementation_name = "";
-
+  video_media_info.senders[0].send_frame_width = 200;
+  video_media_info.senders[0].send_frame_height = 100;
+  video_media_info.senders[0].framerate_sent = 10;
+  video_media_info.senders[0].frames_sent = 5;
+  video_media_info.senders[0].huge_frames_sent = 2;
+  video_media_info.aggregated_senders.push_back(video_media_info.senders[0]);
   RtpCodecParameters codec_parameters;
   codec_parameters.payload_type = 42;
   codec_parameters.kind = cricket::MEDIA_TYPE_AUDIO;
@@ -2062,6 +2069,13 @@
   expected_video.total_packet_send_delay = 10.0;
   expected_video.quality_limitation_reason = "bandwidth";
   expected_video.quality_limitation_resolution_changes = 56u;
+  if (pc_->GetConfiguration().enable_simulcast_stats) {
+    expected_video.frame_width = 200u;
+    expected_video.frame_height = 100u;
+    expected_video.frames_per_second = 10.0;
+    expected_video.frames_sent = 5;
+    expected_video.huge_frames_sent = 2;
+  }
   // |expected_video.content_type| should be undefined.
   // |expected_video.qp_sum| should be undefined.
   // |expected_video.encoder_implementation| should be undefined.
@@ -2077,6 +2091,7 @@
   video_media_info.senders[0].content_type = VideoContentType::SCREENSHARE;
   expected_video.content_type = "screenshare";
   video_media_info.senders[0].encoder_implementation_name = "libfooencoder";
+  video_media_info.aggregated_senders[0] = video_media_info.senders[0];
   expected_video.encoder_implementation = "libfooencoder";
   video_media_channel->SetStats(video_media_info);
 
@@ -2390,10 +2405,15 @@
   const int kVideoSourceHeight = 34;
 
   cricket::VideoMediaInfo video_media_info;
+  video_media_info.aggregated_senders.push_back(cricket::VideoSenderInfo());
   video_media_info.senders.push_back(cricket::VideoSenderInfo());
   video_media_info.senders[0].local_stats.push_back(cricket::SsrcSenderInfo());
   video_media_info.senders[0].local_stats[0].ssrc = kSsrc;
   video_media_info.senders[0].framerate_input = 29;
+  video_media_info.aggregated_senders[0].local_stats.push_back(
+      cricket::SsrcSenderInfo());
+  video_media_info.aggregated_senders[0].local_stats[0].ssrc = kSsrc;
+  video_media_info.aggregated_senders[0].framerate_input = 29;
   auto* video_media_channel = pc_->AddVideoChannel("VideoMid", "TransportName");
   video_media_channel->SetStats(video_media_info);
 
@@ -2572,6 +2592,8 @@
         }
         video_media_info.senders[0].report_block_datas.push_back(
             report_block_data);
+        video_media_info.aggregated_senders.push_back(
+            video_media_info.senders[0]);
         auto* video_media_channel = pc_->AddVideoChannel("mid", transport_name);
         video_media_channel->SetStats(video_media_info);
         return;
diff --git a/pc/rtc_stats_integrationtest.cc b/pc/rtc_stats_integrationtest.cc
index fa7d56a..d6d5c6f 100644
--- a/pc/rtc_stats_integrationtest.cc
+++ b/pc/rtc_stats_integrationtest.cc
@@ -352,7 +352,8 @@
   explicit RTCStatsReportVerifier(const RTCStatsReport* report)
       : report_(report) {}
 
-  void VerifyReport(std::vector<const char*> allowed_missing_stats) {
+  void VerifyReport(std::vector<const char*> allowed_missing_stats,
+                    bool enable_simulcast_stats) {
     std::set<const char*> missing_stats = StatsTypes();
     bool verify_successful = true;
     std::vector<const RTCTransportStats*> transport_stats =
@@ -395,7 +396,7 @@
             stats.cast_to<RTCInboundRTPStreamStats>());
       } else if (stats.type() == RTCOutboundRTPStreamStats::kType) {
         verify_successful &= VerifyRTCOutboundRTPStreamStats(
-            stats.cast_to<RTCOutboundRTPStreamStats>());
+            stats.cast_to<RTCOutboundRTPStreamStats>(), enable_simulcast_stats);
       } else if (stats.type() == RTCRemoteInboundRtpStreamStats::kType) {
         verify_successful &= VerifyRTCRemoteInboundRtpStreamStats(
             stats.cast_to<RTCRemoteInboundRtpStreamStats>());
@@ -865,14 +866,19 @@
   }
 
   bool VerifyRTCOutboundRTPStreamStats(
-      const RTCOutboundRTPStreamStats& outbound_stream) {
+      const RTCOutboundRTPStreamStats& outbound_stream,
+      bool enable_simulcast_stats) {
     RTCStatsVerifier verifier(report_, &outbound_stream);
     VerifyRTCRTPStreamStats(outbound_stream, &verifier);
     if (outbound_stream.media_type.is_defined() &&
         *outbound_stream.media_type == "video") {
       verifier.TestMemberIsIDReference(outbound_stream.media_source_id,
                                        RTCVideoSourceStats::kType);
-      verifier.TestMemberIsNonNegative<uint64_t>(outbound_stream.qp_sum);
+      if (*outbound_stream.frames_encoded > 0) {
+        verifier.TestMemberIsNonNegative<uint64_t>(outbound_stream.qp_sum);
+      } else {
+        verifier.TestMemberIsUndefined(outbound_stream.qp_sum);
+      }
     } else {
       verifier.TestMemberIsIDReference(outbound_stream.media_source_id,
                                        RTCAudioSourceStats::kType);
@@ -906,6 +912,24 @@
       // this to be present.
       verifier.MarkMemberTested(outbound_stream.content_type, true);
       verifier.TestMemberIsDefined(outbound_stream.encoder_implementation);
+      if (enable_simulcast_stats) {
+        verifier.TestMemberIsNonNegative<double>(
+            outbound_stream.frames_per_second);
+        verifier.TestMemberIsNonNegative<uint32_t>(
+            outbound_stream.frame_height);
+        verifier.TestMemberIsNonNegative<uint32_t>(outbound_stream.frame_width);
+        verifier.TestMemberIsNonNegative<uint32_t>(outbound_stream.frames_sent);
+        verifier.TestMemberIsNonNegative<uint32_t>(
+            outbound_stream.huge_frames_sent);
+        verifier.MarkMemberTested(outbound_stream.rid, true);
+      } else {
+        verifier.TestMemberIsUndefined(outbound_stream.frames_per_second);
+        verifier.TestMemberIsUndefined(outbound_stream.frame_height);
+        verifier.TestMemberIsUndefined(outbound_stream.frame_width);
+        verifier.TestMemberIsUndefined(outbound_stream.frames_sent);
+        verifier.TestMemberIsUndefined(outbound_stream.huge_frames_sent);
+        verifier.TestMemberIsUndefined(outbound_stream.rid);
+      }
     } else {
       verifier.TestMemberIsUndefined(outbound_stream.frames_encoded);
       verifier.TestMemberIsUndefined(outbound_stream.key_frames_encoded);
@@ -920,6 +944,12 @@
       verifier.TestMemberIsUndefined(outbound_stream.content_type);
       // TODO(hbos): Implement for audio as well.
       verifier.TestMemberIsUndefined(outbound_stream.encoder_implementation);
+      verifier.TestMemberIsUndefined(outbound_stream.rid);
+      verifier.TestMemberIsUndefined(outbound_stream.frames_per_second);
+      verifier.TestMemberIsUndefined(outbound_stream.frame_height);
+      verifier.TestMemberIsUndefined(outbound_stream.frame_width);
+      verifier.TestMemberIsUndefined(outbound_stream.frames_sent);
+      verifier.TestMemberIsUndefined(outbound_stream.huge_frames_sent);
     }
     return verifier.ExpectAllMembersSuccessfullyTested();
   }
@@ -1014,9 +1044,11 @@
   StartCall();
 
   rtc::scoped_refptr<const RTCStatsReport> report = GetStatsFromCaller();
-  RTCStatsReportVerifier(report.get()).VerifyReport({});
+  RTCStatsReportVerifier(report.get())
+      .VerifyReport({},
+                    caller_->pc()->GetConfiguration().enable_simulcast_stats);
 
-  #if RTC_TRACE_EVENTS_ENABLED
+#if RTC_TRACE_EVENTS_ENABLED
   EXPECT_EQ(report->ToJson(), RTCStatsReportTraceListener::last_trace());
   #endif
 }
@@ -1025,9 +1057,11 @@
   StartCall();
 
   rtc::scoped_refptr<const RTCStatsReport> report = GetStatsFromCallee();
-  RTCStatsReportVerifier(report.get()).VerifyReport({});
+  RTCStatsReportVerifier(report.get())
+      .VerifyReport({},
+                    caller_->pc()->GetConfiguration().enable_simulcast_stats);
 
-  #if RTC_TRACE_EVENTS_ENABLED
+#if RTC_TRACE_EVENTS_ENABLED
   EXPECT_EQ(report->ToJson(), RTCStatsReportTraceListener::last_trace());
   #endif
 }
@@ -1049,7 +1083,9 @@
       RTCMediaStreamStats::kType,
       RTCDataChannelStats::kType,
   };
-  RTCStatsReportVerifier(report.get()).VerifyReport(allowed_missing_stats);
+  RTCStatsReportVerifier(report.get())
+      .VerifyReport(allowed_missing_stats,
+                    caller_->pc()->GetConfiguration().enable_simulcast_stats);
   EXPECT_TRUE(report->size());
 }
 
@@ -1068,7 +1104,9 @@
       RTCMediaStreamStats::kType,
       RTCDataChannelStats::kType,
   };
-  RTCStatsReportVerifier(report.get()).VerifyReport(allowed_missing_stats);
+  RTCStatsReportVerifier(report.get())
+      .VerifyReport(allowed_missing_stats,
+                    caller_->pc()->GetConfiguration().enable_simulcast_stats);
   EXPECT_TRUE(report->size());
 }
 
diff --git a/pc/stats_collector.cc b/pc/stats_collector.cc
index b447b8f..0509c6d 100644
--- a/pc/stats_collector.cc
+++ b/pc/stats_collector.cc
@@ -1028,7 +1028,7 @@
 
   void ExtractStats(StatsCollector* collector) const override {
     ExtractSenderReceiverStats(collector, video_media_info.receivers,
-                               video_media_info.senders);
+                               video_media_info.aggregated_senders);
   }
 
   bool HasRemoteAudio() const override { return false; }
diff --git a/pc/stats_collector_unittest.cc b/pc/stats_collector_unittest.cc
index 471f382..ab46972 100644
--- a/pc/stats_collector_unittest.cc
+++ b/pc/stats_collector_unittest.cc
@@ -912,7 +912,7 @@
   video_sender_info.payload_bytes_sent = kBytesSent;
   video_sender_info.header_and_padding_bytes_sent = 0;
   VideoMediaInfo video_info;
-  video_info.senders.push_back(video_sender_info);
+  video_info.aggregated_senders.push_back(video_sender_info);
 
   auto* video_media_channel = pc->AddVideoChannel("video", "transport");
   video_media_channel->SetStats(video_info);
@@ -995,7 +995,7 @@
   video_sender_info.header_and_padding_bytes_sent = 12;
 
   VideoMediaInfo video_info;
-  video_info.senders.push_back(video_sender_info);
+  video_info.aggregated_senders.push_back(video_sender_info);
 
   auto* video_media_channel = pc->AddVideoChannel("video", "transport");
   video_media_channel->SetStats(video_info);
@@ -1093,7 +1093,7 @@
   video_sender_info.payload_bytes_sent = kBytesSent - 12;
   video_sender_info.header_and_padding_bytes_sent = 12;
   VideoMediaInfo video_info;
-  video_info.senders.push_back(video_sender_info);
+  video_info.aggregated_senders.push_back(video_sender_info);
 
   auto* video_media_channel = pc->AddVideoChannel("video", "transport");
   video_media_channel->SetStats(video_info);
@@ -1148,7 +1148,7 @@
   video_sender_info.payload_bytes_sent = kBytesSent - 12;
   video_sender_info.header_and_padding_bytes_sent = 12;
   VideoMediaInfo video_info;
-  video_info.senders.push_back(video_sender_info);
+  video_info.aggregated_senders.push_back(video_sender_info);
 
   auto* video_media_channel = pc->AddVideoChannel("video", "transport");
   video_media_channel->SetStats(video_info);
@@ -1211,7 +1211,7 @@
   video_sender_info.add_ssrc(kSsrcOfTrack);
   video_sender_info.remote_stats.push_back(remote_ssrc_stats);
   VideoMediaInfo video_info;
-  video_info.senders.push_back(video_sender_info);
+  video_info.aggregated_senders.push_back(video_sender_info);
 
   auto* video_media_channel = pc->AddVideoChannel("video", "transport");
   video_media_channel->SetStats(video_info);
@@ -1853,7 +1853,7 @@
   video_sender_info.frames_encoded = 10;
   video_sender_info.qp_sum = 11;
   VideoMediaInfo video_info;
-  video_info.senders.push_back(video_sender_info);
+  video_info.aggregated_senders.push_back(video_sender_info);
 
   auto* video_media_channel = pc->AddVideoChannel("video", "transport");
   video_media_channel->SetStats(video_info);
diff --git a/pc/track_media_info_map.cc b/pc/track_media_info_map.cc
index 781737a..ca923a0 100644
--- a/pc/track_media_info_map.cc
+++ b/pc/track_media_info_map.cc
@@ -10,6 +10,7 @@
 
 #include "pc/track_media_info_map.h"
 
+#include <set>
 #include <string>
 #include <utility>
 
@@ -170,19 +171,36 @@
   }
   if (video_media_info_) {
     for (auto& sender_info : video_media_info_->senders) {
-      VideoTrackInterface* associated_track =
-          FindValueOrNull(local_video_track_by_ssrc, sender_info.ssrc());
-      if (associated_track) {
-        // One sender is associated with at most one track.
-        // One track may be associated with multiple senders.
-        video_track_by_sender_info_[&sender_info] = associated_track;
-        video_infos_by_local_track_[associated_track].push_back(&sender_info);
+      std::set<uint32_t> ssrcs;
+      ssrcs.insert(sender_info.ssrc());
+      for (auto& ssrc_group : sender_info.ssrc_groups) {
+        for (auto ssrc : ssrc_group.ssrcs) {
+          ssrcs.insert(ssrc);
+        }
       }
+      for (auto ssrc : ssrcs) {
+        VideoTrackInterface* associated_track =
+            FindValueOrNull(local_video_track_by_ssrc, ssrc);
+        if (associated_track) {
+          // One sender is associated with at most one track.
+          // One track may be associated with multiple senders.
+          video_track_by_sender_info_[&sender_info] = associated_track;
+          video_infos_by_local_track_[associated_track].push_back(&sender_info);
+          break;
+        }
+      }
+    }
+    for (auto& sender_info : video_media_info_->aggregated_senders) {
       if (sender_info.ssrc() == 0)
         continue;  // Unconnected SSRC. bugs.webrtc.org/8673
       RTC_DCHECK(video_info_by_sender_ssrc_.count(sender_info.ssrc()) == 0)
           << "Duplicate video sender SSRC: " << sender_info.ssrc();
       video_info_by_sender_ssrc_[sender_info.ssrc()] = &sender_info;
+      VideoTrackInterface* associated_track =
+          FindValueOrNull(local_video_track_by_ssrc, sender_info.ssrc());
+      if (associated_track) {
+        video_track_by_sender_info_[&sender_info] = associated_track;
+      }
     }
     for (auto& receiver_info : video_media_info_->receivers) {
       VideoTrackInterface* associated_track =
diff --git a/pc/track_media_info_map_unittest.cc b/pc/track_media_info_map_unittest.cc
index 4fa8a4a..c487ab8 100644
--- a/pc/track_media_info_map_unittest.cc
+++ b/pc/track_media_info_map_unittest.cc
@@ -131,6 +131,7 @@
         video_sender_info.local_stats[i++].ssrc = ssrc;
       }
       video_media_info_->senders.push_back(video_sender_info);
+      video_media_info_->aggregated_senders.push_back(video_sender_info);
     }
   }
 
diff --git a/stats/rtcstats_objects.cc b/stats/rtcstats_objects.cc
index d99c9e7..453acce 100644
--- a/stats/rtcstats_objects.cc
+++ b/stats/rtcstats_objects.cc
@@ -698,6 +698,7 @@
     RTCOutboundRTPStreamStats, RTCRTPStreamStats, "outbound-rtp",
     &media_source_id,
     &remote_id,
+    &rid,
     &packets_sent,
     &retransmitted_packets_sent,
     &bytes_sent,
@@ -708,6 +709,11 @@
     &key_frames_encoded,
     &total_encode_time,
     &total_encoded_bytes_target,
+    &frame_width,
+    &frame_height,
+    &frames_per_second,
+    &frames_sent,
+    &huge_frames_sent,
     &total_packet_send_delay,
     &quality_limitation_reason,
     &quality_limitation_resolution_changes,
@@ -724,6 +730,7 @@
     : RTCRTPStreamStats(std::move(id), timestamp_us),
       media_source_id("mediaSourceId"),
       remote_id("remoteId"),
+      rid("rid"),
       packets_sent("packetsSent"),
       retransmitted_packets_sent("retransmittedPacketsSent"),
       bytes_sent("bytesSent"),
@@ -734,6 +741,11 @@
       key_frames_encoded("keyFramesEncoded"),
       total_encode_time("totalEncodeTime"),
       total_encoded_bytes_target("totalEncodedBytesTarget"),
+      frame_width("frameWidth"),
+      frame_height("frameHeight"),
+      frames_per_second("framesPerSecond"),
+      frames_sent("framesSent"),
+      huge_frames_sent("hugeFramesSent"),
       total_packet_send_delay("totalPacketSendDelay"),
       quality_limitation_reason("qualityLimitationReason"),
       quality_limitation_resolution_changes(
@@ -746,6 +758,7 @@
     : RTCRTPStreamStats(other),
       media_source_id(other.media_source_id),
       remote_id(other.remote_id),
+      rid(other.rid),
       packets_sent(other.packets_sent),
       retransmitted_packets_sent(other.retransmitted_packets_sent),
       bytes_sent(other.bytes_sent),
@@ -756,6 +769,11 @@
       key_frames_encoded(other.key_frames_encoded),
       total_encode_time(other.total_encode_time),
       total_encoded_bytes_target(other.total_encoded_bytes_target),
+      frame_width(other.frame_width),
+      frame_height(other.frame_height),
+      frames_per_second(other.frames_per_second),
+      frames_sent(other.frames_sent),
+      huge_frames_sent(other.huge_frames_sent),
       total_packet_send_delay(other.total_packet_send_delay),
       quality_limitation_reason(other.quality_limitation_reason),
       quality_limitation_resolution_changes(
diff --git a/video/send_statistics_proxy.cc b/video/send_statistics_proxy.cc
index 1cb059d..f8d768f 100644
--- a/video/send_statistics_proxy.cc
+++ b/video/send_statistics_proxy.cc
@@ -958,7 +958,15 @@
   VideoSendStream::StreamStats* stats = GetStatsEntry(ssrc);
   if (!stats)
     return;
-
+  if (encoded_frame_rate_trackers_.count(simulcast_idx) == 0) {
+    encoded_frame_rate_trackers_[simulcast_idx] =
+        std::make_unique<rtc::RateTracker>(kBucketSizeMs, kBucketCount);
+  }
+  stats->encode_frame_rate =
+      encoded_frame_rate_trackers_[simulcast_idx]->ComputeRate();
+  stats->frames_encoded++;
+  stats->total_encode_time_ms += encoded_image.timing_.encode_finish_ms -
+                                 encoded_image.timing_.encode_start_ms;
   // Report resolution of top spatial layer in case of VP9 SVC.
   bool is_svc_low_spatial_layer =
       (codec_info && codec_info->codecType == kVideoCodecVP9)
@@ -975,9 +983,9 @@
                                          VideoFrameType::kVideoFrameKey);
 
   if (encoded_image.qp_ != -1) {
-    if (!stats_.qp_sum)
-      stats_.qp_sum = 0;
-    *stats_.qp_sum += encoded_image.qp_;
+    if (!stats->qp_sum)
+      stats->qp_sum = 0;
+    *stats->qp_sum += encoded_image.qp_;
 
     if (codec_info) {
       if (codec_info->codecType == kVideoCodecVP8) {
@@ -997,6 +1005,7 @@
   // as a single difficult input frame.
   // https://w3c.github.io/webrtc-stats/#dom-rtcvideosenderstats-hugeframessent
   if (encoded_image.timing_.flags & VideoSendTiming::kTriggeredBySize) {
+    ++stats->huge_frames_sent;
     if (!last_outlier_timestamp_ ||
         *last_outlier_timestamp_ < encoded_image.capture_time_ms_) {
       last_outlier_timestamp_.emplace(encoded_image.capture_time_ms_);
@@ -1007,6 +1016,7 @@
   media_byte_rate_tracker_.AddSamples(encoded_image.size());
 
   if (uma_container_->InsertEncodedFrame(encoded_image, simulcast_idx)) {
+    encoded_frame_rate_trackers_[simulcast_idx]->AddSamples(1);
     encoded_frame_rate_tracker_.AddSamples(1);
   }
 
diff --git a/video/send_statistics_proxy.h b/video/send_statistics_proxy.h
index 08717ca..1d2fd21 100644
--- a/video/send_statistics_proxy.h
+++ b/video/send_statistics_proxy.h
@@ -285,6 +285,8 @@
       RTC_GUARDED_BY(crit_);
   rtc::RateTracker media_byte_rate_tracker_ RTC_GUARDED_BY(crit_);
   rtc::RateTracker encoded_frame_rate_tracker_ RTC_GUARDED_BY(crit_);
+  std::map<uint32_t, std::unique_ptr<rtc::RateTracker>>
+      encoded_frame_rate_trackers_ RTC_GUARDED_BY(crit_);
 
   absl::optional<int64_t> last_outlier_timestamp_ RTC_GUARDED_BY(crit_);
 
diff --git a/video/send_statistics_proxy_unittest.cc b/video/send_statistics_proxy_unittest.cc
index b69dfad..ab5b491 100644
--- a/video/send_statistics_proxy_unittest.cc
+++ b/video/send_statistics_proxy_unittest.cc
@@ -372,22 +372,27 @@
 TEST_F(SendStatisticsProxyTest, OnSendEncodedImageIncreasesQpSum) {
   EncodedImage encoded_image;
   CodecSpecificInfo codec_info;
-  EXPECT_EQ(absl::nullopt, statistics_proxy_->GetStats().qp_sum);
+  auto ssrc = config_.rtp.ssrcs[0];
+  EXPECT_EQ(absl::nullopt,
+            statistics_proxy_->GetStats().substreams[ssrc].qp_sum);
   encoded_image.qp_ = 3;
   statistics_proxy_->OnSendEncodedImage(encoded_image, &codec_info);
-  EXPECT_EQ(3u, statistics_proxy_->GetStats().qp_sum);
+  EXPECT_EQ(3u, statistics_proxy_->GetStats().substreams[ssrc].qp_sum);
   encoded_image.qp_ = 127;
   statistics_proxy_->OnSendEncodedImage(encoded_image, &codec_info);
-  EXPECT_EQ(130u, statistics_proxy_->GetStats().qp_sum);
+  EXPECT_EQ(130u, statistics_proxy_->GetStats().substreams[ssrc].qp_sum);
 }
 
 TEST_F(SendStatisticsProxyTest, OnSendEncodedImageWithoutQpQpSumWontExist) {
   EncodedImage encoded_image;
   CodecSpecificInfo codec_info;
+  auto ssrc = config_.rtp.ssrcs[0];
   encoded_image.qp_ = -1;
-  EXPECT_EQ(absl::nullopt, statistics_proxy_->GetStats().qp_sum);
+  EXPECT_EQ(absl::nullopt,
+            statistics_proxy_->GetStats().substreams[ssrc].qp_sum);
   statistics_proxy_->OnSendEncodedImage(encoded_image, &codec_info);
-  EXPECT_EQ(absl::nullopt, statistics_proxy_->GetStats().qp_sum);
+  EXPECT_EQ(absl::nullopt,
+            statistics_proxy_->GetStats().substreams[ssrc].qp_sum);
 }
 
 TEST_F(SendStatisticsProxyTest, TotalEncodedBytesTargetFirstFrame) {
@@ -442,6 +447,29 @@
   EXPECT_EQ(kTargetBytesPerSecond / 10, delta_encoded_bytes_target);
 }
 
+TEST_F(SendStatisticsProxyTest, EncodeFrameRateInSubStream) {
+  const int kInterframeDelayMs = 100;
+  auto ssrc = config_.rtp.ssrcs[0];
+  rtc::ScopedFakeClock fake_global_clock;
+  fake_global_clock.SetTime(
+      Timestamp::Millis(fake_clock_.TimeInMilliseconds()));
+
+  EncodedImage encoded_image;
+
+  // First frame
+  statistics_proxy_->OnSendEncodedImage(encoded_image, nullptr);
+  // Second frame
+  fake_clock_.AdvanceTimeMilliseconds(kInterframeDelayMs);
+  fake_global_clock.SetTime(
+      Timestamp::Millis(fake_clock_.TimeInMilliseconds()));
+  encoded_image.SetTimestamp(encoded_image.Timestamp() +
+                             90 * kInterframeDelayMs);
+  statistics_proxy_->OnSendEncodedImage(encoded_image, nullptr);
+
+  auto stats = statistics_proxy_->GetStats();
+  EXPECT_EQ(stats.substreams[ssrc].encode_frame_rate, 10);
+}
+
 TEST_F(SendStatisticsProxyTest, GetCpuAdaptationStats) {
   VideoAdaptationCounters cpu_counts;
   VideoAdaptationCounters quality_counts;