Add `RTCRemoteOutboundRtpStreamStats` for audio streams

Changes:
- adding the `RTCRemoteOutboundRtpStreamStats` dictionary (see [1])
- collection of remote outbound stats (only for audio streams)
- adding `remote_id` to the inbound stats and set with the ID of the
  corresponding remote outbound stats only if the latter are available
- unit tests

[1] https://www.w3.org/TR/webrtc-stats/#dom-rtcremoteoutboundrtpstreamstats

Tested: verified from chrome://webrtc-internals during an appr.tc call

Bug: webrtc:12529
Change-Id: Ide91dc04a3c387ba439618a9c6b64a95994a1940
Reviewed-on: https://webrtc-review.googlesource.com/c/src/+/211042
Commit-Queue: Alessio Bazzica <alessiob@webrtc.org>
Reviewed-by: Björn Terelius <terelius@webrtc.org>
Reviewed-by: Sam Zackrisson <saza@webrtc.org>
Reviewed-by: Henrik Boström <hbos@webrtc.org>
Cr-Commit-Position: refs/heads/master@{#33545}
diff --git a/pc/rtc_stats_collector.cc b/pc/rtc_stats_collector.cc
index c14f414..36ee542 100644
--- a/pc/rtc_stats_collector.cc
+++ b/pc/rtc_stats_collector.cc
@@ -109,17 +109,23 @@
   return sb.str();
 }
 
-std::string RTCInboundRTPStreamStatsIDFromSSRC(bool audio, uint32_t ssrc) {
+std::string RTCInboundRTPStreamStatsIDFromSSRC(cricket::MediaType media_type,
+                                               uint32_t ssrc) {
   char buf[1024];
   rtc::SimpleStringBuilder sb(buf);
-  sb << "RTCInboundRTP" << (audio ? "Audio" : "Video") << "Stream_" << ssrc;
+  sb << "RTCInboundRTP"
+     << (media_type == cricket::MEDIA_TYPE_AUDIO ? "Audio" : "Video")
+     << "Stream_" << ssrc;
   return sb.str();
 }
 
-std::string RTCOutboundRTPStreamStatsIDFromSSRC(bool audio, uint32_t ssrc) {
+std::string RTCOutboundRTPStreamStatsIDFromSSRC(cricket::MediaType media_type,
+                                                uint32_t ssrc) {
   char buf[1024];
   rtc::SimpleStringBuilder sb(buf);
-  sb << "RTCOutboundRTP" << (audio ? "Audio" : "Video") << "Stream_" << ssrc;
+  sb << "RTCOutboundRTP"
+     << (media_type == cricket::MEDIA_TYPE_AUDIO ? "Audio" : "Video")
+     << "Stream_" << ssrc;
   return sb.str();
 }
 
@@ -134,6 +140,17 @@
   return sb.str();
 }
 
+std::string RTCRemoteOutboundRTPStreamStatsIDFromSSRC(
+    cricket::MediaType media_type,
+    uint32_t source_ssrc) {
+  char buf[1024];
+  rtc::SimpleStringBuilder sb(buf);
+  sb << "RTCRemoteOutboundRTP"
+     << (media_type == cricket::MEDIA_TYPE_AUDIO ? "Audio" : "Video")
+     << "Stream_" << source_ssrc;
+  return sb.str();
+}
+
 std::string RTCMediaSourceStatsIDFromKindAndAttachment(
     cricket::MediaType media_type,
     int attachment_id) {
@@ -309,17 +326,21 @@
       static_cast<int32_t>(media_receiver_info.packets_lost);
 }
 
-void SetInboundRTPStreamStatsFromVoiceReceiverInfo(
-    const std::string& mid,
+std::unique_ptr<RTCInboundRTPStreamStats> CreateInboundAudioStreamStats(
     const cricket::VoiceReceiverInfo& voice_receiver_info,
-    RTCInboundRTPStreamStats* inbound_audio) {
+    const std::string& mid,
+    int64_t timestamp_us) {
+  auto inbound_audio = std::make_unique<RTCInboundRTPStreamStats>(
+      /*id=*/RTCInboundRTPStreamStatsIDFromSSRC(cricket::MEDIA_TYPE_AUDIO,
+                                                voice_receiver_info.ssrc()),
+      timestamp_us);
   SetInboundRTPStreamStatsFromMediaReceiverInfo(voice_receiver_info,
-                                                inbound_audio);
+                                                inbound_audio.get());
   inbound_audio->media_type = "audio";
   inbound_audio->kind = "audio";
   if (voice_receiver_info.codec_payload_type) {
     inbound_audio->codec_id = RTCCodecStatsIDFromMidDirectionAndPayload(
-        mid, true, *voice_receiver_info.codec_payload_type);
+        mid, /*inbound=*/true, *voice_receiver_info.codec_payload_type);
   }
   inbound_audio->jitter = static_cast<double>(voice_receiver_info.jitter_ms) /
                           rtc::kNumMillisecsPerSec;
@@ -358,6 +379,51 @@
       voice_receiver_info.fec_packets_received;
   inbound_audio->fec_packets_discarded =
       voice_receiver_info.fec_packets_discarded;
+  return inbound_audio;
+}
+
+std::unique_ptr<RTCRemoteOutboundRtpStreamStats>
+CreateRemoteOutboundAudioStreamStats(
+    const cricket::VoiceReceiverInfo& voice_receiver_info,
+    const std::string& mid,
+    const std::string& inbound_audio_id,
+    const std::string& transport_id) {
+  if (!voice_receiver_info.last_sender_report_timestamp_ms.has_value()) {
+    // Cannot create `RTCRemoteOutboundRtpStreamStats` when the RTCP SR arrival
+    // timestamp is not available - i.e., until the first sender report is
+    // received.
+    return nullptr;
+  }
+  RTC_DCHECK_GT(voice_receiver_info.sender_reports_reports_count, 0);
+
+  // Create.
+  auto stats = std::make_unique<RTCRemoteOutboundRtpStreamStats>(
+      /*id=*/RTCRemoteOutboundRTPStreamStatsIDFromSSRC(
+          cricket::MEDIA_TYPE_AUDIO, voice_receiver_info.ssrc()),
+      /*timestamp_us=*/rtc::kNumMicrosecsPerMillisec *
+          voice_receiver_info.last_sender_report_timestamp_ms.value());
+
+  // Populate.
+  // - RTCRtpStreamStats.
+  stats->ssrc = voice_receiver_info.ssrc();
+  stats->kind = "audio";
+  stats->transport_id = transport_id;
+  stats->codec_id = RTCCodecStatsIDFromMidDirectionAndPayload(
+      mid,
+      /*inbound=*/true,  // Remote-outbound same as local-inbound.
+      *voice_receiver_info.codec_payload_type);
+  // - RTCSentRtpStreamStats.
+  stats->packets_sent = voice_receiver_info.sender_reports_packets_sent;
+  stats->bytes_sent = voice_receiver_info.sender_reports_bytes_sent;
+  // - RTCRemoteOutboundRtpStreamStats.
+  stats->local_id = inbound_audio_id;
+  RTC_DCHECK(
+      voice_receiver_info.last_sender_report_remote_timestamp_ms.has_value());
+  stats->remote_timestamp = static_cast<double>(
+      voice_receiver_info.last_sender_report_remote_timestamp_ms.value());
+  stats->reports_sent = voice_receiver_info.sender_reports_reports_count;
+
+  return stats;
 }
 
 void SetInboundRTPStreamStatsFromVideoReceiverInfo(
@@ -370,7 +436,7 @@
   inbound_video->kind = "video";
   if (video_receiver_info.codec_payload_type) {
     inbound_video->codec_id = RTCCodecStatsIDFromMidDirectionAndPayload(
-        mid, true, *video_receiver_info.codec_payload_type);
+        mid, /*inbound=*/true, *video_receiver_info.codec_payload_type);
   }
   inbound_video->jitter = static_cast<double>(video_receiver_info.jitter_ms) /
                           rtc::kNumMillisecsPerSec;
@@ -454,7 +520,7 @@
   outbound_audio->kind = "audio";
   if (voice_sender_info.codec_payload_type) {
     outbound_audio->codec_id = RTCCodecStatsIDFromMidDirectionAndPayload(
-        mid, false, *voice_sender_info.codec_payload_type);
+        mid, /*inbound=*/false, *voice_sender_info.codec_payload_type);
   }
   // |fir_count|, |pli_count| and |sli_count| are only valid for video and are
   // purposefully left undefined for audio.
@@ -470,7 +536,7 @@
   outbound_video->kind = "video";
   if (video_sender_info.codec_payload_type) {
     outbound_video->codec_id = RTCCodecStatsIDFromMidDirectionAndPayload(
-        mid, false, *video_sender_info.codec_payload_type);
+        mid, /*inbound=*/false, *video_sender_info.codec_payload_type);
   }
   outbound_video->fir_count =
       static_cast<uint32_t>(video_sender_info.firs_rcvd);
@@ -550,8 +616,8 @@
   remote_inbound->round_trip_time_measurements =
       report_block_data.num_rtts();
 
-  std::string local_id = RTCOutboundRTPStreamStatsIDFromSSRC(
-      media_type == cricket::MEDIA_TYPE_AUDIO, report_block.source_ssrc);
+  std::string local_id =
+      RTCOutboundRTPStreamStatsIDFromSSRC(media_type, report_block.source_ssrc);
   // Look up local stat from |outbound_rtps| where the pointers are non-const.
   auto local_id_it = outbound_rtps.find(local_id);
   if (local_id_it != outbound_rtps.end()) {
@@ -1678,16 +1744,16 @@
   std::string mid = *stats.mid;
   std::string transport_id = RTCTransportStatsIDFromTransportChannel(
       *stats.transport_name, cricket::ICE_CANDIDATE_COMPONENT_RTP);
-  // Inbound
+  // Inbound and remote-outbound.
+  // The remote-outbound stats are based on RTCP sender reports sent from the
+  // remote endpoint providing metrics about the remote outbound streams.
   for (const cricket::VoiceReceiverInfo& voice_receiver_info :
        track_media_info_map.voice_media_info()->receivers) {
     if (!voice_receiver_info.connected())
       continue;
-    auto inbound_audio = std::make_unique<RTCInboundRTPStreamStats>(
-        RTCInboundRTPStreamStatsIDFromSSRC(true, voice_receiver_info.ssrc()),
-        timestamp_us);
-    SetInboundRTPStreamStatsFromVoiceReceiverInfo(mid, voice_receiver_info,
-                                                  inbound_audio.get());
+    // Inbound.
+    auto inbound_audio =
+        CreateInboundAudioStreamStats(voice_receiver_info, mid, timestamp_us);
     // TODO(hta): This lookup should look for the sender, not the track.
     rtc::scoped_refptr<AudioTrackInterface> audio_track =
         track_media_info_map.GetAudioTrack(voice_receiver_info);
@@ -1698,16 +1764,27 @@
               track_media_info_map.GetAttachmentIdByTrack(audio_track).value());
     }
     inbound_audio->transport_id = transport_id;
+    // Remote-outbound.
+    auto remote_outbound_audio = CreateRemoteOutboundAudioStreamStats(
+        voice_receiver_info, mid, inbound_audio->id(), transport_id);
+    // Add stats.
+    if (remote_outbound_audio) {
+      // When the remote outbound stats are available, the remote ID for the
+      // local inbound stats is set.
+      inbound_audio->remote_id = remote_outbound_audio->id();
+      report->AddStats(std::move(remote_outbound_audio));
+    }
     report->AddStats(std::move(inbound_audio));
   }
-  // Outbound
+  // Outbound.
   std::map<std::string, RTCOutboundRTPStreamStats*> audio_outbound_rtps;
   for (const cricket::VoiceSenderInfo& voice_sender_info :
        track_media_info_map.voice_media_info()->senders) {
     if (!voice_sender_info.connected())
       continue;
     auto outbound_audio = std::make_unique<RTCOutboundRTPStreamStats>(
-        RTCOutboundRTPStreamStatsIDFromSSRC(true, voice_sender_info.ssrc()),
+        RTCOutboundRTPStreamStatsIDFromSSRC(cricket::MEDIA_TYPE_AUDIO,
+                                            voice_sender_info.ssrc()),
         timestamp_us);
     SetOutboundRTPStreamStatsFromVoiceSenderInfo(mid, voice_sender_info,
                                                  outbound_audio.get());
@@ -1728,7 +1805,7 @@
         std::make_pair(outbound_audio->id(), outbound_audio.get()));
     report->AddStats(std::move(outbound_audio));
   }
-  // Remote-inbound
+  // Remote-inbound.
   // These are Report Block-based, information sent from the remote endpoint,
   // providing metrics about our Outbound streams. We take advantage of the fact
   // that RTCOutboundRtpStreamStats, RTCCodecStats and RTCTransport have already
@@ -1765,7 +1842,8 @@
     if (!video_receiver_info.connected())
       continue;
     auto inbound_video = std::make_unique<RTCInboundRTPStreamStats>(
-        RTCInboundRTPStreamStatsIDFromSSRC(false, video_receiver_info.ssrc()),
+        RTCInboundRTPStreamStatsIDFromSSRC(cricket::MEDIA_TYPE_VIDEO,
+                                           video_receiver_info.ssrc()),
         timestamp_us);
     SetInboundRTPStreamStatsFromVideoReceiverInfo(mid, video_receiver_info,
                                                   inbound_video.get());
@@ -1779,6 +1857,7 @@
     }
     inbound_video->transport_id = transport_id;
     report->AddStats(std::move(inbound_video));
+    // TODO(crbug.com/webrtc/12529): Add remote-outbound stats.
   }
   // Outbound
   std::map<std::string, RTCOutboundRTPStreamStats*> video_outbound_rtps;
@@ -1787,7 +1866,8 @@
     if (!video_sender_info.connected())
       continue;
     auto outbound_video = std::make_unique<RTCOutboundRTPStreamStats>(
-        RTCOutboundRTPStreamStatsIDFromSSRC(false, video_sender_info.ssrc()),
+        RTCOutboundRTPStreamStatsIDFromSSRC(cricket::MEDIA_TYPE_VIDEO,
+                                            video_sender_info.ssrc()),
         timestamp_us);
     SetOutboundRTPStreamStatsFromVideoSenderInfo(mid, video_sender_info,
                                                  outbound_video.get());