Pivot generation of stats to iterate senders/receivers

This will allow stats to be generated when AddTrack() is used.
It also exposes a ClearStatsCache() call on the PC to allow enforcement
of cache lifetime restrictions.

Bug: webrtc:8616
Change-Id: If47b967ce9e40fa768303e6f5f54fe74db2cc7a4
Reviewed-on: https://webrtc-review.googlesource.com/34360
Commit-Queue: Harald Alvestrand <hta@webrtc.org>
Reviewed-by: Henrik Boström <hbos@webrtc.org>
Cr-Commit-Position: refs/heads/master@{#21468}
diff --git a/api/peerconnectioninterface.h b/api/peerconnectioninterface.h
index eb6685a..d3209f4 100644
--- a/api/peerconnectioninterface.h
+++ b/api/peerconnectioninterface.h
@@ -734,6 +734,10 @@
   // break third party projects. As soon as they have been updated this should
   // be changed to "= 0;".
   virtual void GetStats(RTCStatsCollectorCallback* callback) {}
+  // Clear cached stats in the rtcstatscollector.
+  // Exposed for testing while waiting for automatic cache clear to work.
+  // https://bugs.webrtc.org/8693
+  virtual void ClearStatsCache() {}
 
   // Create a data channel with the provided config, or default config if none
   // is provided. Note that an offer/answer negotiation is still necessary
diff --git a/pc/peerconnection.cc b/pc/peerconnection.cc
index b99df99..94f2db1 100644
--- a/pc/peerconnection.cc
+++ b/pc/peerconnection.cc
@@ -5518,4 +5518,10 @@
   }
 }
 
+void PeerConnection::ClearStatsCache() {
+  if (stats_collector_) {
+    stats_collector_->ClearCachedStatsReport();
+  }
+}
+
 }  // namespace webrtc
diff --git a/pc/peerconnection.h b/pc/peerconnection.h
index b9a86d8..66061f1 100644
--- a/pc/peerconnection.h
+++ b/pc/peerconnection.h
@@ -137,6 +137,7 @@
                 webrtc::MediaStreamTrackInterface* track,
                 StatsOutputLevel level) override;
   void GetStats(RTCStatsCollectorCallback* callback) override;
+  void ClearStatsCache() override;
 
   SignalingState signaling_state() override;
 
diff --git a/pc/peerconnectioninterface_unittest.cc b/pc/peerconnectioninterface_unittest.cc
index 6bff276..56c12e9 100644
--- a/pc/peerconnectioninterface_unittest.cc
+++ b/pc/peerconnectioninterface_unittest.cc
@@ -846,6 +846,15 @@
     return observer->called();
   }
 
+  // Call the standards-compliant GetStats function.
+  bool DoGetRTCStats() {
+    rtc::scoped_refptr<webrtc::MockRTCStatsCollectorCallback> callback(
+        new rtc::RefCountedObject<webrtc::MockRTCStatsCollectorCallback>());
+    pc_->GetStats(callback);
+    EXPECT_TRUE_WAIT(callback->called(), kTimeout);
+    return callback->called();
+  }
+
   void InitiateCall() {
     CreatePeerConnectionWithoutDtls();
     // Create a local stream with audio&video tracks.
@@ -1466,7 +1475,6 @@
 // expecting a random stream ID to be generated.
 TEST_F(PeerConnectionInterfaceTest, AddTrackWithoutStream) {
   CreatePeerConnectionWithoutDtls();
-  // Create a dummy stream, so tracks share a stream label.
   rtc::scoped_refptr<AudioTrackInterface> audio_track(
       pc_factory_->CreateAudioTrack("audio_track", nullptr));
   rtc::scoped_refptr<VideoTrackInterface> video_track(
@@ -1487,6 +1495,24 @@
   EXPECT_NE(video_sender->stream_ids(), audio_sender->stream_ids());
 }
 
+// Test that we can call GetStats() after AddTrack but before connecting
+// the PeerConnection to a peer.
+TEST_F(PeerConnectionInterfaceTest, AddTrackBeforeConnecting) {
+  CreatePeerConnectionWithoutDtls();
+  rtc::scoped_refptr<AudioTrackInterface> audio_track(
+      pc_factory_->CreateAudioTrack("audio_track", nullptr));
+  rtc::scoped_refptr<VideoTrackInterface> video_track(
+      pc_factory_->CreateVideoTrack(
+          "video_track", pc_factory_->CreateVideoSource(
+                             std::unique_ptr<cricket::VideoCapturer>(
+                                 new cricket::FakeVideoCapturer()))));
+  auto audio_sender =
+      pc_->AddTrack(audio_track, std::vector<MediaStreamInterface*>());
+  auto video_sender =
+      pc_->AddTrack(video_track, std::vector<MediaStreamInterface*>());
+  EXPECT_TRUE(DoGetStats(nullptr));
+}
+
 TEST_F(PeerConnectionInterfaceTest, CreateOfferReceiveAnswer) {
   InitiateCall();
   WaitAndVerifyOnAddStream(kStreamLabel1);
@@ -1735,6 +1761,19 @@
   EXPECT_FALSE(DoGetStats(unknown_audio_track));
 }
 
+TEST_F(PeerConnectionInterfaceTest, GetRTCStatsBeforeAndAfterCalling) {
+  CreatePeerConnectionWithoutDtls();
+  EXPECT_TRUE(DoGetRTCStats());
+  // Clearing stats cache is needed now, but should be temporary.
+  // https://bugs.chromium.org/p/webrtc/issues/detail?id=8693
+  pc_->ClearStatsCache();
+  AddAudioVideoStream(kStreamLabel1, "audio_track", "video_track");
+  EXPECT_TRUE(DoGetRTCStats());
+  pc_->ClearStatsCache();
+  CreateOfferReceiveAnswer();
+  EXPECT_TRUE(DoGetRTCStats());
+}
+
 // This test setup two RTP data channels in loop back.
 TEST_F(PeerConnectionInterfaceTest, TestDataChannel) {
   FakeConstraints constraints;
diff --git a/pc/rtcstatscollector.cc b/pc/rtcstatscollector.cc
index 24444f3..e777a26 100644
--- a/pc/rtcstatscollector.cc
+++ b/pc/rtcstatscollector.cc
@@ -207,7 +207,7 @@
     RTCInboundRTPStreamStats* inbound_stats) {
   RTC_DCHECK(inbound_stats);
   inbound_stats->ssrc = media_receiver_info.ssrc();
-  // TODO(hbos): Support the remote case. crbug.com/657855
+  // TODO(hbos): Support the remote case. https://crbug.com/657855
   inbound_stats->is_remote = false;
   inbound_stats->packets_received =
       static_cast<uint32_t>(media_receiver_info.packets_rcvd);
@@ -265,7 +265,7 @@
     RTCOutboundRTPStreamStats* outbound_stats) {
   RTC_DCHECK(outbound_stats);
   outbound_stats->ssrc = media_sender_info.ssrc();
-  // TODO(hbos): Support the remote case. crbug.com/657856
+  // TODO(hbos): Support the remote case. https://crbug.com/657856
   outbound_stats->is_remote = false;
   outbound_stats->packets_sent =
       static_cast<uint32_t>(media_sender_info.packets_sent);
@@ -460,7 +460,7 @@
   video_track_stats->frame_height = static_cast<uint32_t>(
       video_sender_info.send_frame_height);
   // TODO(hbos): Will reduce this by frames dropped due to congestion control
-  // when available. crbug.com/659137
+  // when available. https://crbug.com/659137
   video_track_stats->frames_sent = video_sender_info.frames_encoded;
   return video_track_stats;
 }
@@ -492,7 +492,7 @@
   // TODO(hbos): When we support receiving simulcast, this should be the total
   // number of frames correctly decoded, independent of which SSRC it was
   // received from. Since we don't support that, this is correct and is the same
-  // value as "RTCInboundRTPStreamStats.framesDecoded". crbug.com/659137
+  // value as "RTCInboundRTPStreamStats.framesDecoded". https://crbug.com/659137
   video_track_stats->frames_decoded = video_receiver_info.frames_decoded;
   RTC_DCHECK_GE(video_receiver_info.frames_received,
                 video_receiver_info.frames_rendered);
@@ -501,20 +501,99 @@
   return video_track_stats;
 }
 
-void ProduceMediaStreamAndTrackStats(
+void ProduceSenderMediaTrackStats(
+    int64_t timestamp_us,
+    const TrackMediaInfoMap& track_media_info_map,
+    std::vector<rtc::scoped_refptr<RtpSenderInterface>> senders,
+    RTCStatsReport* report) {
+  // This function iterates over the senders to generate outgoing track stats.
+
+  // TODO(hbos): Return stats of detached tracks. We have to perform stats
+  // gathering at the time of detachment to get accurate stats and timestamps.
+  // https://crbug.com/659137
+  for (auto const sender : senders) {
+    // Don't report on tracks before starting to send.
+    // https://bugs.webrtc.org/8673
+    if (sender->ssrc() == 0)
+      continue;
+    if (sender->media_type() == cricket::MEDIA_TYPE_AUDIO) {
+      AudioTrackInterface* track =
+          static_cast<AudioTrackInterface*>(sender->track().get());
+      if (!track)
+        continue;
+      auto voice_sender_info =
+          track_media_info_map.GetVoiceSenderInfoBySsrc(sender->ssrc());
+      RTC_CHECK(voice_sender_info)
+          << "No voice sender info for sender with ssrc " << sender->ssrc();
+      std::unique_ptr<RTCMediaStreamTrackStats> audio_track_stats =
+          ProduceMediaStreamTrackStatsFromVoiceSenderInfo(timestamp_us, *track,
+                                                          *voice_sender_info);
+      report->AddStats(std::move(audio_track_stats));
+    } else if (sender->media_type() == cricket::MEDIA_TYPE_VIDEO) {
+      VideoTrackInterface* track =
+          static_cast<VideoTrackInterface*>(sender->track().get());
+      if (!track)
+        continue;
+      auto video_sender_info =
+          track_media_info_map.GetVideoSenderInfoBySsrc(sender->ssrc());
+      RTC_CHECK(video_sender_info)
+          << "No video sender info for sender with ssrc " << sender->ssrc();
+      std::unique_ptr<RTCMediaStreamTrackStats> video_track_stats =
+          ProduceMediaStreamTrackStatsFromVideoSenderInfo(timestamp_us, *track,
+                                                          *video_sender_info);
+      report->AddStats(std::move(video_track_stats));
+    } else {
+      RTC_NOTREACHED();
+    }
+  }
+}
+
+void ProduceReceiverMediaTrackStats(
+    int64_t timestamp_us,
+    const TrackMediaInfoMap& track_media_info_map,
+    std::vector<rtc::scoped_refptr<RtpReceiverInterface>> receivers,
+    RTCStatsReport* report) {
+  // This function iterates over the receivers to find the remote tracks.
+  for (auto const receiver : receivers) {
+    if (receiver->media_type() == cricket::MEDIA_TYPE_AUDIO) {
+      AudioTrackInterface* track =
+          static_cast<AudioTrackInterface*>(receiver->track().get());
+      const cricket::VoiceReceiverInfo* voice_receiver_info =
+          track_media_info_map.GetVoiceReceiverInfo(*track);
+      if (!voice_receiver_info) {
+        continue;
+      }
+      std::unique_ptr<RTCMediaStreamTrackStats> audio_track_stats =
+          ProduceMediaStreamTrackStatsFromVoiceReceiverInfo(
+              timestamp_us, *track, *voice_receiver_info);
+      report->AddStats(std::move(audio_track_stats));
+    } else if (receiver->media_type() == cricket::MEDIA_TYPE_VIDEO) {
+      VideoTrackInterface* track =
+          static_cast<VideoTrackInterface*>(receiver->track().get());
+      const cricket::VideoReceiverInfo* video_receiver_info =
+          track_media_info_map.GetVideoReceiverInfo(*track);
+      if (!video_receiver_info) {
+        continue;
+      }
+      std::unique_ptr<RTCMediaStreamTrackStats> video_track_stats =
+          ProduceMediaStreamTrackStatsFromVideoReceiverInfo(
+              timestamp_us, *track, *video_receiver_info);
+      report->AddStats(std::move(video_track_stats));
+    } else {
+      RTC_NOTREACHED();
+    }
+  }
+}
+
+void ProduceMediaStreamStats(
     int64_t timestamp_us,
     const TrackMediaInfoMap& track_media_info_map,
     rtc::scoped_refptr<StreamCollectionInterface> streams,
     bool is_local,
     RTCStatsReport* report) {
-  // TODO(hbos): When "AddTrack" is implemented we should iterate tracks to
-  // find which streams exist, not iterate streams to find tracks.
-  // crbug.com/659137
-  // TODO(hbos): Return stats of detached tracks. We have to perform stats
-  // gathering at the time of detachment to get accurate stats and timestamps.
-  // crbug.com/659137
   if (!streams)
     return;
+  // Collect info about streams and which tracks in them are attached to PC.
   for (size_t i = 0; i < streams->count(); ++i) {
     MediaStreamInterface* stream = streams->at(i);
 
@@ -524,70 +603,64 @@
             stream->label(), timestamp_us));
     stream_stats->stream_identifier = stream->label();
     stream_stats->track_ids = std::vector<std::string>();
-    // The track stats are per-attachment to the connection. There can be one
-    // for receiving (remote) tracks and multiple attachments for sending
-    // (local) tracks.
+    // Record the IDs of tracks that are currently connected.
+    // Note: Iterating over streams may be iffy with AddTrack.
+    // TODO(hta): Revisit in conjunction with https://bugs.webrtc.org/8674
     if (is_local) {
-      // Local Audio Tracks
-      for (const rtc::scoped_refptr<AudioTrackInterface>& audio_track :
-           stream->GetAudioTracks()) {
-        const std::vector<cricket::VoiceSenderInfo*>* voice_sender_infos =
+      for (auto audio_track : stream->GetAudioTracks()) {
+        auto sender_infos =
             track_media_info_map.GetVoiceSenderInfos(*audio_track);
-        if (!voice_sender_infos) {
+        // There is no map entry on unconnected tracks.
+        // https://bugs.webrtc.org/8673
+        if (!sender_infos)
           continue;
-        }
-        for (const auto& voice_sender_info : *voice_sender_infos) {
-          std::unique_ptr<RTCMediaStreamTrackStats> audio_track_stats =
-              ProduceMediaStreamTrackStatsFromVoiceSenderInfo(
-                  timestamp_us, *audio_track, *voice_sender_info);
-          stream_stats->track_ids->push_back(audio_track_stats->id());
-          report->AddStats(std::move(audio_track_stats));
+        for (const auto& sender_info : *sender_infos) {
+          // In the WebRTC implementation, SSRC 0 means unconnected,
+          // and should not occur in the map.
+          // https://bugs.webrtc.org/8694
+          RTC_DCHECK_NE(0, sender_info->ssrc());
+          stream_stats->track_ids->push_back(
+              RTCMediaStreamTrackStatsIDFromTrackKindIDAndSsrc(
+                  is_local, MediaStreamTrackInterface::kAudioKind,
+                  audio_track->id(), sender_info->ssrc()));
         }
       }
-      // Local Video Tracks
-      for (const rtc::scoped_refptr<VideoTrackInterface>& video_track :
-           stream->GetVideoTracks()) {
-        const std::vector<cricket::VideoSenderInfo*>* video_sender_infos =
+      for (auto video_track : stream->GetVideoTracks()) {
+        auto sender_infos =
             track_media_info_map.GetVideoSenderInfos(*video_track);
-        if (!video_sender_infos) {
+        // There is no map entry on unconnected tracks.
+        // https://bugs.webrtc.org/8673
+        if (!sender_infos)
           continue;
-        }
-        for (const auto& video_sender_info : *video_sender_infos) {
-          std::unique_ptr<RTCMediaStreamTrackStats> video_track_stats =
-              ProduceMediaStreamTrackStatsFromVideoSenderInfo(
-                  timestamp_us, *video_track, *video_sender_info);
-          stream_stats->track_ids->push_back(video_track_stats->id());
-          report->AddStats(std::move(video_track_stats));
+        for (const auto& sender_info : *sender_infos) {
+          // SSRC must not be zero. https://bugs.webrtc.org/8694
+          RTC_DCHECK_NE(0, sender_info->ssrc());
+          stream_stats->track_ids->push_back(
+              RTCMediaStreamTrackStatsIDFromTrackKindIDAndSsrc(
+                  is_local, MediaStreamTrackInterface::kVideoKind,
+                  video_track->id(), sender_info->ssrc()));
         }
       }
     } else {
-      // Remote Audio Tracks
-      for (const rtc::scoped_refptr<AudioTrackInterface>& audio_track :
-           stream->GetAudioTracks()) {
-        const cricket::VoiceReceiverInfo* voice_receiver_info =
+      for (auto audio_track : stream->GetAudioTracks()) {
+        auto receiver_info =
             track_media_info_map.GetVoiceReceiverInfo(*audio_track);
-        if (!voice_receiver_info) {
-          continue;
+        if (receiver_info) {
+          stream_stats->track_ids->push_back(
+              RTCMediaStreamTrackStatsIDFromTrackKindIDAndSsrc(
+                  is_local, MediaStreamTrackInterface::kAudioKind,
+                  audio_track->id(), receiver_info->ssrc()));
         }
-        std::unique_ptr<RTCMediaStreamTrackStats> audio_track_stats =
-            ProduceMediaStreamTrackStatsFromVoiceReceiverInfo(
-                timestamp_us, *audio_track, *voice_receiver_info);
-        stream_stats->track_ids->push_back(audio_track_stats->id());
-        report->AddStats(std::move(audio_track_stats));
       }
-      // Remote Video Tracks
-      for (const rtc::scoped_refptr<VideoTrackInterface>& video_track :
-           stream->GetVideoTracks()) {
-        const cricket::VideoReceiverInfo* video_receiver_info =
+      for (auto video_track : stream->GetVideoTracks()) {
+        auto receiver_info =
             track_media_info_map.GetVideoReceiverInfo(*video_track);
-        if (!video_receiver_info) {
-          continue;
+        if (receiver_info) {
+          stream_stats->track_ids->push_back(
+              RTCMediaStreamTrackStatsIDFromTrackKindIDAndSsrc(
+                  is_local, MediaStreamTrackInterface::kVideoKind,
+                  video_track->id(), receiver_info->ssrc()));
         }
-        std::unique_ptr<RTCMediaStreamTrackStats> video_track_stats =
-            ProduceMediaStreamTrackStatsFromVideoReceiverInfo(
-                timestamp_us, *video_track, *video_receiver_info);
-        stream_stats->track_ids->push_back(video_track_stats->id());
-        report->AddStats(std::move(video_track_stats));
       }
     }
     report->AddStats(std::move(stream_stats));
@@ -897,7 +970,7 @@
         // TODO(hbos): There could be other candidates that are not paired with
         // anything. We don't have a complete list. Local candidates come from
         // Port objects, and prflx candidates (both local and remote) are only
-        // stored in candidate pairs. crbug.com/632723
+        // stored in candidate pairs. https://crbug.com/632723
         candidate_pair_stats->local_candidate_id = ProduceIceCandidateStats(
             timestamp_us, info.local_candidate, true, transport_id, report);
         candidate_pair_stats->remote_candidate_id = ProduceIceCandidateStats(
@@ -908,7 +981,7 @@
         candidate_pair_stats->nominated = info.nominated;
         // TODO(hbos): This writable is different than the spec. It goes to
         // false after a certain amount of time without a response passes.
-        // crbug.com/633550
+        // https://crbug.com/633550
         candidate_pair_stats->writable = info.writable;
         candidate_pair_stats->bytes_sent =
             static_cast<uint64_t>(info.sent_total_bytes);
@@ -960,16 +1033,15 @@
     int64_t timestamp_us, RTCStatsReport* report) const {
   RTC_DCHECK(signaling_thread_->IsCurrent());
   RTC_DCHECK(track_media_info_map_);
-  ProduceMediaStreamAndTrackStats(timestamp_us,
-                                  *track_media_info_map_,
-                                  pc_->local_streams(),
-                                  true,
-                                  report);
-  ProduceMediaStreamAndTrackStats(timestamp_us,
-                                  *track_media_info_map_,
-                                  pc_->remote_streams(),
-                                  false,
-                                  report);
+  // TODO(bugs.webrtc.org/8674): Use the stream list updated by AddTrack
+  ProduceMediaStreamStats(timestamp_us, *track_media_info_map_,
+                          pc_->local_streams(), true, report);
+  ProduceMediaStreamStats(timestamp_us, *track_media_info_map_,
+                          pc_->remote_streams(), false, report);
+  ProduceSenderMediaTrackStats(timestamp_us, *track_media_info_map_,
+                               pc_->GetSenders(), report);
+  ProduceReceiverMediaTrackStats(timestamp_us, *track_media_info_map_,
+                                 pc_->GetReceivers(), report);
 }
 
 void RTCStatsCollector::ProducePeerConnectionStats_s(
diff --git a/pc/rtcstatscollector_unittest.cc b/pc/rtcstatscollector_unittest.cc
index 7e4ad7f..2149695 100644
--- a/pc/rtcstatscollector_unittest.cc
+++ b/pc/rtcstatscollector_unittest.cc
@@ -286,6 +286,7 @@
             std::unique_ptr<cricket::MediaEngineInterface>(media_engine_))),
         pc_(pc_factory_) {
     // Default return values for mocks.
+    EXPECT_CALL(pc_, GetCallStats()).WillRepeatedly(Return(Call::Stats()));
     EXPECT_CALL(pc_, local_streams()).WillRepeatedly(Return(nullptr));
     EXPECT_CALL(pc_, remote_streams()).WillRepeatedly(Return(nullptr));
     EXPECT_CALL(pc_, GetSenders()).WillRepeatedly(Return(
@@ -320,25 +321,32 @@
 
   void SetupLocalTrackAndSender(cricket::MediaType media_type,
                                 const std::string& track_id,
-                                uint32_t ssrc) {
+                                uint32_t ssrc,
+                                bool add_stream) {
     rtc::scoped_refptr<StreamCollection> local_streams =
         StreamCollection::Create();
     EXPECT_CALL(pc_, local_streams())
         .WillRepeatedly(Return(local_streams));
 
-    rtc::scoped_refptr<MediaStream> local_stream =
-        MediaStream::Create("LocalStreamLabel");
-    local_streams->AddStream(local_stream);
+    rtc::scoped_refptr<MediaStream> local_stream;
+    if (add_stream) {
+      local_stream = MediaStream::Create("LocalStreamLabel");
+      local_streams->AddStream(local_stream);
+    }
 
     rtc::scoped_refptr<MediaStreamTrackInterface> track;
     if (media_type == cricket::MEDIA_TYPE_AUDIO) {
       track = CreateFakeTrack(media_type, track_id,
                               MediaStreamTrackInterface::kLive);
-      local_stream->AddTrack(static_cast<AudioTrackInterface*>(track.get()));
+      if (add_stream) {
+        local_stream->AddTrack(static_cast<AudioTrackInterface*>(track.get()));
+      }
     } else {
       track = CreateFakeTrack(media_type, track_id,
                               MediaStreamTrackInterface::kLive);
-      local_stream->AddTrack(static_cast<VideoTrackInterface*>(track.get()));
+      if (add_stream) {
+        local_stream->AddTrack(static_cast<VideoTrackInterface*>(track.get()));
+      }
     }
 
     rtc::scoped_refptr<MockRtpSender> sender = CreateMockSender(track, ssrc);
@@ -1987,8 +1995,8 @@
       kDefaultRtcpMuxRequired, kDefaultSrtpRequired);
   voice_channel.set_transport_name_for_testing("TransportName");
 
-  test_->SetupLocalTrackAndSender(
-      cricket::MEDIA_TYPE_AUDIO, "LocalAudioTrackID", 1);
+  test_->SetupLocalTrackAndSender(cricket::MEDIA_TYPE_AUDIO,
+                                  "LocalAudioTrackID", 1, true);
 
   cricket::VoiceMediaInfo voice_media_info;
 
@@ -2064,8 +2072,8 @@
       "VideoContentName", kDefaultRtcpMuxRequired, kDefaultSrtpRequired);
   video_channel.set_transport_name_for_testing("TransportName");
 
-  test_->SetupLocalTrackAndSender(
-      cricket::MEDIA_TYPE_VIDEO, "LocalVideoTrackID", 1);
+  test_->SetupLocalTrackAndSender(cricket::MEDIA_TYPE_VIDEO,
+                                  "LocalVideoTrackID", 1, true);
 
   cricket::VideoMediaInfo video_media_info;
 
@@ -2315,6 +2323,104 @@
       report->Get(expected_rtcp_transport.id())->cast_to<RTCTransportStats>());
 }
 
+TEST_F(RTCStatsCollectorTest, CollectNoStreamRTCOutboundRTPStreamStats_Audio) {
+  // Emulates the case where AddTrack is used without an associated MediaStream
+  auto* voice_media_channel = new MockVoiceMediaChannel();
+  cricket::VoiceChannel voice_channel(
+      test_->worker_thread(), test_->network_thread(),
+      test_->signaling_thread(), test_->media_engine(),
+      rtc::WrapUnique(voice_media_channel), "VoiceContentName",
+      kDefaultRtcpMuxRequired, kDefaultSrtpRequired);
+  voice_channel.set_transport_name_for_testing("TransportName");
+
+  test_->SetupLocalTrackAndSender(cricket::MEDIA_TYPE_AUDIO,
+                                  "LocalAudioTrackID", 1, false);
+
+  cricket::VoiceMediaInfo voice_media_info;
+
+  voice_media_info.senders.push_back(cricket::VoiceSenderInfo());
+  voice_media_info.senders[0].local_stats.push_back(cricket::SsrcSenderInfo());
+  voice_media_info.senders[0].local_stats[0].ssrc = 1;
+  voice_media_info.senders[0].packets_sent = 2;
+  voice_media_info.senders[0].bytes_sent = 3;
+  voice_media_info.senders[0].codec_payload_type = 42;
+
+  RtpCodecParameters codec_parameters;
+  codec_parameters.payload_type = 42;
+  codec_parameters.kind = cricket::MEDIA_TYPE_AUDIO;
+  codec_parameters.name = "dummy";
+  codec_parameters.clock_rate = 0;
+  voice_media_info.send_codecs.insert(
+      std::make_pair(codec_parameters.payload_type, codec_parameters));
+
+  EXPECT_CALL(*voice_media_channel, GetStats(_))
+      .WillOnce(DoAll(SetArgPointee<0>(voice_media_info), Return(true)));
+
+  SessionStats session_stats;
+  session_stats.transport_stats["TransportName"].transport_name =
+      "TransportName";
+
+  // Make sure the associated |RTCTransportStats| is created.
+  cricket::TransportChannelStats channel_stats;
+  channel_stats.component = cricket::ICE_CANDIDATE_COMPONENT_RTP;
+  session_stats.transport_stats["TransportName"].channel_stats.push_back(
+      channel_stats);
+
+  EXPECT_CALL(test_->pc(), GetSessionStats(_))
+      .WillRepeatedly(Invoke([&session_stats](const ChannelNamePairs&) {
+        return std::unique_ptr<SessionStats>(new SessionStats(session_stats));
+      }));
+  EXPECT_CALL(test_->pc(), voice_channel())
+      .WillRepeatedly(Return(&voice_channel));
+
+  rtc::scoped_refptr<const RTCStatsReport> report = GetStatsReport();
+
+  RTCOutboundRTPStreamStats expected_audio("RTCOutboundRTPAudioStream_1",
+                                           report->timestamp_us());
+  expected_audio.ssrc = 1;
+  expected_audio.is_remote = false;
+  expected_audio.media_type = "audio";
+  expected_audio.track_id =
+      "RTCMediaStreamTrack_local_audio_LocalAudioTrackID_1";
+  expected_audio.transport_id =
+      "RTCTransport_TransportName_" +
+      rtc::ToString<>(cricket::ICE_CANDIDATE_COMPONENT_RTP);
+  expected_audio.codec_id = "RTCCodec_OutboundAudio_42";
+  expected_audio.packets_sent = 2;
+  expected_audio.bytes_sent = 3;
+
+  ASSERT_TRUE(report->Get(expected_audio.id()));
+  EXPECT_EQ(
+      report->Get(expected_audio.id())->cast_to<RTCOutboundRTPStreamStats>(),
+      expected_audio);
+
+  ASSERT_TRUE(report->Get(expected_audio.id()));
+  EXPECT_EQ(
+      report->Get(expected_audio.id())->cast_to<RTCOutboundRTPStreamStats>(),
+      expected_audio);
+  EXPECT_TRUE(report->Get(*expected_audio.track_id));
+  EXPECT_TRUE(report->Get(*expected_audio.transport_id));
+  EXPECT_TRUE(report->Get(*expected_audio.codec_id));
+}
+
+// When the PC has not had SetLocalDescription done, tracks all have
+// SSRC 0, meaning "unconnected".
+// We do not report stats on those tracks. https://bugs.webrtc.org/8673
+TEST_F(RTCStatsCollectorTest, StatsNotReportedOnZeroSsrc) {
+  rtc::scoped_refptr<MediaStreamTrackInterface> track =
+      CreateFakeTrack(cricket::MEDIA_TYPE_AUDIO, "audioTrack",
+                      MediaStreamTrackInterface::kLive);
+  rtc::scoped_refptr<MockRtpSender> sender = CreateMockSender(track, 0);
+  EXPECT_CALL(test_->pc(), GetSenders())
+      .WillRepeatedly(
+          Return(std::vector<rtc::scoped_refptr<RtpSenderInterface>>(
+              {rtc::scoped_refptr<RtpSenderInterface>(sender.get())})));
+  rtc::scoped_refptr<const RTCStatsReport> report = GetStatsReport();
+  std::vector<const RTCMediaStreamTrackStats*> track_stats =
+      report->GetStatsOfType<RTCMediaStreamTrackStats>();
+  EXPECT_EQ(0, track_stats.size());
+}
+
 class RTCStatsCollectorTestWithFakeCollector : public testing::Test {
  public:
   RTCStatsCollectorTestWithFakeCollector()
diff --git a/pc/trackmediainfomap.cc b/pc/trackmediainfomap.cc
index 2a7fe9c..3fbe3fa 100644
--- a/pc/trackmediainfomap.cc
+++ b/pc/trackmediainfomap.cc
@@ -135,6 +135,11 @@
         audio_track_by_sender_info_[&sender_info] = associated_track;
         voice_infos_by_local_track_[associated_track].push_back(&sender_info);
       }
+      if (sender_info.ssrc() == 0)
+        continue;  // Unconnected SSRC. bugs.webrtc.org/8673
+      RTC_CHECK(voice_info_by_sender_ssrc_.count(sender_info.ssrc()) == 0)
+          << "Duplicate voice sender SSRC: " << sender_info.ssrc();
+      voice_info_by_sender_ssrc_[sender_info.ssrc()] = &sender_info;
     }
     for (auto& receiver_info : voice_media_info_->receivers) {
       AudioTrackInterface* associated_track =
@@ -150,6 +155,9 @@
         audio_track_by_receiver_info_[&receiver_info] = unsignaled_audio_track;
         voice_info_by_remote_track_[unsignaled_audio_track] = &receiver_info;
       }
+      RTC_CHECK(voice_info_by_receiver_ssrc_.count(receiver_info.ssrc()) == 0)
+          << "Duplicate voice receiver SSRC: " << receiver_info.ssrc();
+      voice_info_by_receiver_ssrc_[receiver_info.ssrc()] = &receiver_info;
     }
   }
   if (video_media_info_) {
@@ -162,6 +170,11 @@
         video_track_by_sender_info_[&sender_info] = associated_track;
         video_infos_by_local_track_[associated_track].push_back(&sender_info);
       }
+      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;
     }
     for (auto& receiver_info : video_media_info_->receivers) {
       VideoTrackInterface* associated_track =
@@ -177,6 +190,9 @@
         video_track_by_receiver_info_[&receiver_info] = unsignaled_video_track;
         video_info_by_remote_track_[unsignaled_video_track] = &receiver_info;
       }
+      RTC_DCHECK(video_info_by_receiver_ssrc_.count(receiver_info.ssrc()) == 0)
+          << "Duplicate video receiver SSRC: " << receiver_info.ssrc();
+      video_info_by_receiver_ssrc_[receiver_info.ssrc()] = &receiver_info;
     }
   }
 }
@@ -203,6 +219,25 @@
   return FindValueOrNull(video_info_by_remote_track_, &remote_video_track);
 }
 
+const cricket::VoiceSenderInfo* TrackMediaInfoMap::GetVoiceSenderInfoBySsrc(
+    uint32_t ssrc) const {
+  return FindValueOrNull(voice_info_by_sender_ssrc_, ssrc);
+}
+const cricket::VoiceReceiverInfo* TrackMediaInfoMap::GetVoiceReceiverInfoBySsrc(
+    uint32_t ssrc) const {
+  return FindValueOrNull(voice_info_by_receiver_ssrc_, ssrc);
+}
+
+const cricket::VideoSenderInfo* TrackMediaInfoMap::GetVideoSenderInfoBySsrc(
+    uint32_t ssrc) const {
+  return FindValueOrNull(video_info_by_sender_ssrc_, ssrc);
+}
+
+const cricket::VideoReceiverInfo* TrackMediaInfoMap::GetVideoReceiverInfoBySsrc(
+    uint32_t ssrc) const {
+  return FindValueOrNull(video_info_by_receiver_ssrc_, ssrc);
+}
+
 rtc::scoped_refptr<AudioTrackInterface> TrackMediaInfoMap::GetAudioTrack(
     const cricket::VoiceSenderInfo& voice_sender_info) const {
   return FindValueOrNull(audio_track_by_sender_info_, &voice_sender_info);
diff --git a/pc/trackmediainfomap.h b/pc/trackmediainfomap.h
index acc0b14..fd9a98e 100644
--- a/pc/trackmediainfomap.h
+++ b/pc/trackmediainfomap.h
@@ -58,6 +58,13 @@
   const cricket::VideoReceiverInfo* GetVideoReceiverInfo(
       const VideoTrackInterface& remote_video_track) const;
 
+  const cricket::VoiceSenderInfo* GetVoiceSenderInfoBySsrc(uint32_t ssrc) const;
+  const cricket::VoiceReceiverInfo* GetVoiceReceiverInfoBySsrc(
+      uint32_t ssrc) const;
+  const cricket::VideoSenderInfo* GetVideoSenderInfoBySsrc(uint32_t ssrc) const;
+  const cricket::VideoReceiverInfo* GetVideoReceiverInfoBySsrc(
+      uint32_t ssrc) const;
+
   rtc::scoped_refptr<AudioTrackInterface> GetAudioTrack(
       const cricket::VoiceSenderInfo& voice_sender_info) const;
   rtc::scoped_refptr<AudioTrackInterface> GetAudioTrack(
@@ -95,6 +102,11 @@
   std::map<const cricket::VideoReceiverInfo*,
            rtc::scoped_refptr<VideoTrackInterface>>
       video_track_by_receiver_info_;
+  // These maps map SSRCs to the corresponding voice or video info objects.
+  std::map<uint32_t, cricket::VoiceSenderInfo*> voice_info_by_sender_ssrc_;
+  std::map<uint32_t, cricket::VoiceReceiverInfo*> voice_info_by_receiver_ssrc_;
+  std::map<uint32_t, cricket::VideoSenderInfo*> video_info_by_sender_ssrc_;
+  std::map<uint32_t, cricket::VideoReceiverInfo*> video_info_by_receiver_ssrc_;
 };
 
 }  // namespace webrtc
diff --git a/pc/trackmediainfomap_unittest.cc b/pc/trackmediainfomap_unittest.cc
index 65e75c4..c05abe1 100644
--- a/pc/trackmediainfomap_unittest.cc
+++ b/pc/trackmediainfomap_unittest.cc
@@ -386,6 +386,20 @@
             remote_video_track_.get());
 }
 
+TEST_F(TrackMediaInfoMapTest, SsrcLookupFunction) {
+  AddRtpSenderWithSsrcs({1}, local_audio_track_);
+  AddRtpReceiverWithSsrcs({2}, remote_audio_track_);
+  AddRtpSenderWithSsrcs({3}, local_video_track_);
+  AddRtpReceiverWithSsrcs({4}, remote_video_track_);
+  CreateMap();
+  EXPECT_TRUE(map_->GetVoiceSenderInfoBySsrc(1));
+  EXPECT_TRUE(map_->GetVoiceReceiverInfoBySsrc(2));
+  EXPECT_TRUE(map_->GetVideoSenderInfoBySsrc(3));
+  EXPECT_TRUE(map_->GetVideoReceiverInfoBySsrc(4));
+  EXPECT_FALSE(map_->GetVoiceSenderInfoBySsrc(2));
+  EXPECT_FALSE(map_->GetVoiceSenderInfoBySsrc(1024));
+}
+
 // Death tests.
 // Disabled on Android because death tests misbehave on Android, see
 // base/test/gtest_util.h.