Implement RTCMediaSourceStats and friends in standard getStats().

This implements RTCAudioSourceStats and RTCVideoSourceStats, both
inheriting from abstract dictionary RTCMediaSourceStats:
https://w3c.github.io/webrtc-stats/#dom-rtcmediasourcestats

All members are implemented except for the total "frames" counter:
- trackIdentifier
- kind
- width
- height
- framesPerSecond

This means to make googFrameWidthInput, googFrameHeightInput and
googFrameRateInput obsolete.

Implemented using the same code path as the goog stats, there are
some minor bugs that should be fixed in the future, but not this CL:
1. We create media-source objects on a per-track attachment basis.
   If the same track is attached multiple times this results in
   multiple media-source objects, but the spec says it should be on a
   per-source basis.
2. framesPerSecond is only calculated after connecting (when we have a
   sender with SSRC), but if collected on a per-source basis the source
   should be able to tell us the FPS whether or not we are sending it.

Bug: webrtc:10453
Change-Id: I23705a79f15075dca2536275934af1904a7f0d39
Reviewed-on: https://webrtc-review.googlesource.com/c/src/+/137804
Commit-Queue: Henrik Boström <hbos@webrtc.org>
Reviewed-by: Harald Alvestrand <hta@webrtc.org>
Cr-Commit-Position: refs/heads/master@{#28028}
diff --git a/pc/rtc_stats_collector_unittest.cc b/pc/rtc_stats_collector_unittest.cc
index 78530df..6cd6319 100644
--- a/pc/rtc_stats_collector_unittest.cc
+++ b/pc/rtc_stats_collector_unittest.cc
@@ -89,6 +89,14 @@
   *os << stats.ToJson();
 }
 
+void PrintTo(const RTCAudioSourceStats& stats, ::std::ostream* os) {
+  *os << stats.ToJson();
+}
+
+void PrintTo(const RTCVideoSourceStats& stats, ::std::ostream* os) {
+  *os << stats.ToJson();
+}
+
 void PrintTo(const RTCTransportStats& stats, ::std::ostream* os) {
   *os << stats.ToJson();
 }
@@ -195,19 +203,62 @@
   }
 };
 
+class FakeVideoTrackSourceForStats : public VideoTrackSourceInterface {
+ public:
+  static rtc::scoped_refptr<FakeVideoTrackSourceForStats> Create(
+      int input_width,
+      int input_height) {
+    return rtc::scoped_refptr<FakeVideoTrackSourceForStats>(
+        new rtc::RefCountedObject<FakeVideoTrackSourceForStats>(input_width,
+                                                                input_height));
+  }
+
+  FakeVideoTrackSourceForStats(int input_width, int input_height)
+      : input_width_(input_width), input_height_(input_height) {}
+  ~FakeVideoTrackSourceForStats() override {}
+
+  // VideoTrackSourceInterface
+  bool is_screencast() const override { return false; }
+  absl::optional<bool> needs_denoising() const override { return false; }
+  bool GetStats(VideoTrackSourceInterface::Stats* stats) override {
+    stats->input_width = input_width_;
+    stats->input_height = input_height_;
+    return true;
+  }
+  // MediaSourceInterface (part of VideoTrackSourceInterface)
+  MediaSourceInterface::SourceState state() const override {
+    return MediaSourceInterface::SourceState::kLive;
+  }
+  bool remote() const override { return false; }
+  // NotifierInterface (part of MediaSourceInterface)
+  void RegisterObserver(ObserverInterface* observer) override {}
+  void UnregisterObserver(ObserverInterface* observer) override {}
+  // rtc::VideoSourceInterface<VideoFrame> (part of VideoTrackSourceInterface)
+  void AddOrUpdateSink(rtc::VideoSinkInterface<VideoFrame>* sink,
+                       const rtc::VideoSinkWants& wants) override {}
+  void RemoveSink(rtc::VideoSinkInterface<VideoFrame>* sink) override {}
+
+ private:
+  int input_width_;
+  int input_height_;
+};
+
 class FakeVideoTrackForStats : public MediaStreamTrack<VideoTrackInterface> {
  public:
   static rtc::scoped_refptr<FakeVideoTrackForStats> Create(
       const std::string& id,
-      MediaStreamTrackInterface::TrackState state) {
+      MediaStreamTrackInterface::TrackState state,
+      rtc::scoped_refptr<VideoTrackSourceInterface> source) {
     rtc::scoped_refptr<FakeVideoTrackForStats> video_track(
-        new rtc::RefCountedObject<FakeVideoTrackForStats>(id));
+        new rtc::RefCountedObject<FakeVideoTrackForStats>(id,
+                                                          std::move(source)));
     video_track->set_state(state);
     return video_track;
   }
 
-  explicit FakeVideoTrackForStats(const std::string& id)
-      : MediaStreamTrack<VideoTrackInterface>(id) {}
+  FakeVideoTrackForStats(const std::string& id,
+                         rtc::scoped_refptr<VideoTrackSourceInterface> source)
+      : MediaStreamTrack<VideoTrackInterface>(id), source_(source) {}
 
   std::string kind() const override {
     return MediaStreamTrackInterface::kVideoKind;
@@ -217,7 +268,12 @@
                        const rtc::VideoSinkWants& wants) override {}
   void RemoveSink(rtc::VideoSinkInterface<VideoFrame>* sink) override {}
 
-  VideoTrackSourceInterface* GetSource() const override { return nullptr; }
+  VideoTrackSourceInterface* GetSource() const override {
+    return source_.get();
+  }
+
+ private:
+  rtc::scoped_refptr<VideoTrackSourceInterface> source_;
 };
 
 rtc::scoped_refptr<MediaStreamTrackInterface> CreateFakeTrack(
@@ -228,24 +284,26 @@
     return FakeAudioTrackForStats::Create(track_id, track_state);
   } else {
     RTC_DCHECK_EQ(media_type, cricket::MEDIA_TYPE_VIDEO);
-    return FakeVideoTrackForStats::Create(track_id, track_state);
+    return FakeVideoTrackForStats::Create(track_id, track_state, nullptr);
   }
 }
 
 rtc::scoped_refptr<MockRtpSenderInternal> CreateMockSender(
-    const rtc::scoped_refptr<MediaStreamTrackInterface>& track,
+    cricket::MediaType media_type,
+    rtc::scoped_refptr<MediaStreamTrackInterface> track,
     uint32_t ssrc,
     int attachment_id,
     std::vector<std::string> local_stream_ids) {
+  RTC_DCHECK(!track ||
+             (track->kind() == MediaStreamTrackInterface::kAudioKind &&
+              media_type == cricket::MEDIA_TYPE_AUDIO) ||
+             (track->kind() == MediaStreamTrackInterface::kVideoKind &&
+              media_type == cricket::MEDIA_TYPE_VIDEO));
   rtc::scoped_refptr<MockRtpSenderInternal> sender(
       new rtc::RefCountedObject<MockRtpSenderInternal>());
   EXPECT_CALL(*sender, track()).WillRepeatedly(Return(track));
   EXPECT_CALL(*sender, ssrc()).WillRepeatedly(Return(ssrc));
-  EXPECT_CALL(*sender, media_type())
-      .WillRepeatedly(
-          Return(track->kind() == MediaStreamTrackInterface::kAudioKind
-                     ? cricket::MEDIA_TYPE_AUDIO
-                     : cricket::MEDIA_TYPE_VIDEO));
+  EXPECT_CALL(*sender, media_type()).WillRepeatedly(Return(media_type));
   EXPECT_CALL(*sender, GetParameters()).WillRepeatedly(Invoke([ssrc]() {
     RtpParameters params;
     params.encodings.push_back(RtpEncodingParameters());
@@ -325,7 +383,8 @@
       cricket::MediaType media_type,
       const std::string& track_id,
       uint32_t ssrc,
-      bool add_stream) {
+      bool add_stream,
+      int attachment_id) {
     rtc::scoped_refptr<MediaStream> local_stream;
     if (add_stream) {
       local_stream = MediaStream::Create("LocalStreamId");
@@ -348,7 +407,7 @@
     }
 
     rtc::scoped_refptr<MockRtpSenderInternal> sender =
-        CreateMockSender(track, ssrc, 50, {});
+        CreateMockSender(media_type, track, ssrc, attachment_id, {});
     pc_->AddSender(sender);
     return sender;
   }
@@ -388,6 +447,7 @@
   // |[Voice/Video][Sender/Receiver]Info| and their SSRCs. Local tracks can be
   // associated with multiple |[Voice/Video]SenderInfo|s, remote tracks can only
   // be associated with one |[Voice/Video]ReceiverInfo|.
+  // Senders get assigned attachment ID "ssrc + 10".
   void CreateMockRtpSendersReceiversAndChannels(
       std::initializer_list<
           std::pair<MediaStreamTrackInterface*, cricket::VoiceSenderInfo>>
@@ -407,7 +467,6 @@
     cricket::VideoMediaInfo video_media_info;
 
     // Local audio tracks and voice sender infos
-    int attachment_id = 147;
     for (auto& pair : local_audio_track_info_pairs) {
       MediaStreamTrackInterface* local_audio_track = pair.first;
       const cricket::VoiceSenderInfo& voice_sender_info = pair.second;
@@ -416,14 +475,14 @@
 
       voice_media_info.senders.push_back(voice_sender_info);
       rtc::scoped_refptr<MockRtpSenderInternal> rtp_sender = CreateMockSender(
+          cricket::MEDIA_TYPE_AUDIO,
           rtc::scoped_refptr<MediaStreamTrackInterface>(local_audio_track),
-          voice_sender_info.local_stats[0].ssrc, attachment_id++,
-          local_stream_ids);
+          voice_sender_info.local_stats[0].ssrc,
+          voice_sender_info.local_stats[0].ssrc + 10, local_stream_ids);
       pc_->AddSender(rtp_sender);
     }
 
     // Remote audio tracks and voice receiver infos
-    attachment_id = 181;
     for (auto& pair : remote_audio_track_info_pairs) {
       MediaStreamTrackInterface* remote_audio_track = pair.first;
       const cricket::VoiceReceiverInfo& voice_receiver_info = pair.second;
@@ -434,14 +493,14 @@
       rtc::scoped_refptr<MockRtpReceiverInternal> rtp_receiver =
           CreateMockReceiver(
               rtc::scoped_refptr<MediaStreamTrackInterface>(remote_audio_track),
-              voice_receiver_info.local_stats[0].ssrc, attachment_id++);
+              voice_receiver_info.local_stats[0].ssrc,
+              voice_receiver_info.local_stats[0].ssrc + 10);
       EXPECT_CALL(*rtp_receiver, streams())
           .WillRepeatedly(Return(remote_streams));
       pc_->AddReceiver(rtp_receiver);
     }
 
     // Local video tracks and video sender infos
-    attachment_id = 151;
     for (auto& pair : local_video_track_info_pairs) {
       MediaStreamTrackInterface* local_video_track = pair.first;
       const cricket::VideoSenderInfo& video_sender_info = pair.second;
@@ -450,14 +509,14 @@
 
       video_media_info.senders.push_back(video_sender_info);
       rtc::scoped_refptr<MockRtpSenderInternal> rtp_sender = CreateMockSender(
+          cricket::MEDIA_TYPE_VIDEO,
           rtc::scoped_refptr<MediaStreamTrackInterface>(local_video_track),
-          video_sender_info.local_stats[0].ssrc, attachment_id++,
-          local_stream_ids);
+          video_sender_info.local_stats[0].ssrc,
+          video_sender_info.local_stats[0].ssrc + 10, local_stream_ids);
       pc_->AddSender(rtp_sender);
     }
 
     // Remote video tracks and video receiver infos
-    attachment_id = 191;
     for (auto& pair : remote_video_track_info_pairs) {
       MediaStreamTrackInterface* remote_video_track = pair.first;
       const cricket::VideoReceiverInfo& video_receiver_info = pair.second;
@@ -468,7 +527,8 @@
       rtc::scoped_refptr<MockRtpReceiverInternal> rtp_receiver =
           CreateMockReceiver(
               rtc::scoped_refptr<MediaStreamTrackInterface>(remote_video_track),
-              video_receiver_info.local_stats[0].ssrc, attachment_id++);
+              video_receiver_info.local_stats[0].ssrc,
+              video_receiver_info.local_stats[0].ssrc + 10);
       EXPECT_CALL(*rtp_receiver, streams())
           .WillRepeatedly(Return(remote_streams));
       pc_->AddReceiver(rtp_receiver);
@@ -536,6 +596,7 @@
     std::string receiver_track_id;
     std::string remote_stream_id;
     std::string peer_connection_id;
+    std::string media_source_id;
   };
 
   // Sets up the example stats graph (see ASCII art below) used for testing the
@@ -583,7 +644,7 @@
     video_media_channel->SetStats(video_media_info);
     // track (sender)
     graph.sender = stats_->SetupLocalTrackAndSender(
-        cricket::MEDIA_TYPE_VIDEO, "LocalVideoTrackID", 3, false);
+        cricket::MEDIA_TYPE_VIDEO, "LocalVideoTrackID", 3, false, 50);
     graph.sender_track_id = "RTCMediaStreamTrack_sender_" +
                             rtc::ToString(graph.sender->AttachmentId());
     // track (receiver) and stream (remote stream)
@@ -594,20 +655,25 @@
     graph.remote_stream_id = "RTCMediaStream_RemoteStreamId";
     // peer-connection
     graph.peer_connection_id = "RTCPeerConnection";
+    // media-source (kind: video)
+    graph.media_source_id =
+        "RTCVideoSource_" + rtc::ToString(graph.sender->AttachmentId());
 
     // Expected stats graph:
     //
-    // track (sender)      stream (remote stream) ---> track (receiver)
-    //          ^                                        ^
-    //          |                                        |
-    //         outbound-rtp   inbound-rtp ---------------+
-    //          |        |     |       |
-    //          v        v     v       v
-    // codec (send)     transport     codec (recv)     peer-connection
+    //  +--- track (sender)      stream (remote stream) ---> track (receiver)
+    //  |             ^                                        ^
+    //  |             |                                        |
+    //  | +--------- outbound-rtp   inbound-rtp ---------------+
+    //  | |           |        |     |       |
+    //  | |           v        v     v       v
+    //  | |  codec (send)     transport     codec (recv)     peer-connection
+    //  v v
+    //  media-source
 
     // Verify the stats graph is set up correctly.
     graph.full_report = stats_->GetStatsReport();
-    EXPECT_EQ(graph.full_report->size(), 9u);
+    EXPECT_EQ(graph.full_report->size(), 10u);
     EXPECT_TRUE(graph.full_report->Get(graph.send_codec_id));
     EXPECT_TRUE(graph.full_report->Get(graph.recv_codec_id));
     EXPECT_TRUE(graph.full_report->Get(graph.outbound_rtp_id));
@@ -617,8 +683,13 @@
     EXPECT_TRUE(graph.full_report->Get(graph.receiver_track_id));
     EXPECT_TRUE(graph.full_report->Get(graph.remote_stream_id));
     EXPECT_TRUE(graph.full_report->Get(graph.peer_connection_id));
+    EXPECT_TRUE(graph.full_report->Get(graph.media_source_id));
+    const auto& sender_track = graph.full_report->Get(graph.sender_track_id)
+                                   ->cast_to<RTCMediaStreamTrackStats>();
+    EXPECT_EQ(*sender_track.media_source_id, graph.media_source_id);
     const auto& outbound_rtp = graph.full_report->Get(graph.outbound_rtp_id)
                                    ->cast_to<RTCOutboundRTPStreamStats>();
+    EXPECT_EQ(*outbound_rtp.media_source_id, graph.media_source_id);
     EXPECT_EQ(*outbound_rtp.codec_id, graph.send_codec_id);
     EXPECT_EQ(*outbound_rtp.track_id, graph.sender_track_id);
     EXPECT_EQ(*outbound_rtp.transport_id, graph.transport_id);
@@ -1386,6 +1457,8 @@
       IdForType<RTCMediaStreamTrackStats>(report), report->timestamp_us(),
       RTCMediaStreamTrackKind::kAudio);
   expected_local_audio_track_ssrc1.track_identifier = local_audio_track->id();
+  expected_local_audio_track_ssrc1.media_source_id =
+      "RTCAudioSource_11";  // Attachment ID = SSRC + 10
   expected_local_audio_track_ssrc1.remote_source = false;
   expected_local_audio_track_ssrc1.ended = true;
   expected_local_audio_track_ssrc1.detached = false;
@@ -1457,6 +1530,8 @@
       IdForType<RTCMediaStreamTrackStats>(report), report->timestamp_us(),
       RTCMediaStreamTrackKind::kAudio);
   expected_remote_audio_track.track_identifier = remote_audio_track->id();
+  // |expected_remote_audio_track.media_source_id| should be undefined
+  // because the track is remote.
   expected_remote_audio_track.remote_source = true;
   expected_remote_audio_track.ended = false;
   expected_remote_audio_track.detached = false;
@@ -1530,6 +1605,8 @@
       stats_of_track_type[0]->id(), report->timestamp_us(),
       RTCMediaStreamTrackKind::kVideo);
   expected_local_video_track_ssrc1.track_identifier = local_video_track->id();
+  expected_local_video_track_ssrc1.media_source_id =
+      "RTCVideoSource_11";  // Attachment ID = SSRC + 10
   expected_local_video_track_ssrc1.remote_source = false;
   expected_local_video_track_ssrc1.ended = false;
   expected_local_video_track_ssrc1.detached = false;
@@ -1601,6 +1678,8 @@
       RTCMediaStreamTrackKind::kVideo);
   expected_remote_video_track_ssrc3.track_identifier =
       remote_video_track_ssrc3->id();
+  // |expected_remote_video_track_ssrc3.media_source_id| should be undefined
+  // because the track is remote.
   expected_remote_video_track_ssrc3.remote_source = true;
   expected_remote_video_track_ssrc3.ended = true;
   expected_remote_video_track_ssrc3.detached = false;
@@ -1801,12 +1880,14 @@
   auto* voice_media_channel = pc_->AddVoiceChannel("AudioMid", "TransportName");
   voice_media_channel->SetStats(voice_media_info);
   stats_->SetupLocalTrackAndSender(cricket::MEDIA_TYPE_AUDIO,
-                                   "LocalAudioTrackID", 1, true);
+                                   "LocalAudioTrackID", 1, true,
+                                   /*attachment_id=*/50);
 
   rtc::scoped_refptr<const RTCStatsReport> report = stats_->GetStatsReport();
 
   RTCOutboundRTPStreamStats expected_audio("RTCOutboundRTPAudioStream_1",
                                            report->timestamp_us());
+  expected_audio.media_source_id = "RTCAudioSource_50";
   expected_audio.ssrc = 1;
   expected_audio.is_remote = false;
   expected_audio.media_type = "audio";
@@ -1865,7 +1946,8 @@
   auto* video_media_channel = pc_->AddVideoChannel("VideoMid", "TransportName");
   video_media_channel->SetStats(video_media_info);
   stats_->SetupLocalTrackAndSender(cricket::MEDIA_TYPE_VIDEO,
-                                   "LocalVideoTrackID", 1, true);
+                                   "LocalVideoTrackID", 1, true,
+                                   /*attachment_id=*/50);
 
   rtc::scoped_refptr<const RTCStatsReport> report = stats_->GetStatsReport();
 
@@ -1876,6 +1958,7 @@
 
   RTCOutboundRTPStreamStats expected_video(stats_of_my_type[0]->id(),
                                            report->timestamp_us());
+  expected_video.media_source_id = "RTCVideoSource_50";
   expected_video.ssrc = 1;
   expected_video.is_remote = false;
   expected_video.media_type = "video";
@@ -2081,12 +2164,14 @@
   auto* voice_media_channel = pc_->AddVoiceChannel("AudioMid", "TransportName");
   voice_media_channel->SetStats(voice_media_info);
   stats_->SetupLocalTrackAndSender(cricket::MEDIA_TYPE_AUDIO,
-                                   "LocalAudioTrackID", 1, false);
+                                   "LocalAudioTrackID", 1, false,
+                                   /*attachment_id=*/50);
 
   rtc::scoped_refptr<const RTCStatsReport> report = stats_->GetStatsReport();
 
   RTCOutboundRTPStreamStats expected_audio("RTCOutboundRTPAudioStream_1",
                                            report->timestamp_us());
+  expected_audio.media_source_id = "RTCAudioSource_50";
   expected_audio.ssrc = 1;
   expected_audio.is_remote = false;
   expected_audio.media_type = "audio";
@@ -2108,22 +2193,194 @@
   EXPECT_TRUE(report->Get(*expected_audio.codec_id));
 }
 
+TEST_F(RTCStatsCollectorTest, RTCAudioSourceStatsCollectedForSenderWithTrack) {
+  const uint32_t kSsrc = 4;
+  const int kAttachmentId = 42;
+
+  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 = kSsrc;
+  auto* voice_media_channel = pc_->AddVoiceChannel("AudioMid", "TransportName");
+  voice_media_channel->SetStats(voice_media_info);
+  stats_->SetupLocalTrackAndSender(cricket::MEDIA_TYPE_AUDIO,
+                                   "LocalAudioTrackID", kSsrc, false,
+                                   kAttachmentId);
+
+  rtc::scoped_refptr<const RTCStatsReport> report = stats_->GetStatsReport();
+
+  RTCAudioSourceStats expected_audio("RTCAudioSource_42",
+                                     report->timestamp_us());
+  expected_audio.track_identifier = "LocalAudioTrackID";
+  expected_audio.kind = "audio";
+
+  ASSERT_TRUE(report->Get(expected_audio.id()));
+  EXPECT_EQ(report->Get(expected_audio.id())->cast_to<RTCAudioSourceStats>(),
+            expected_audio);
+}
+
+TEST_F(RTCStatsCollectorTest, RTCVideoSourceStatsCollectedForSenderWithTrack) {
+  const uint32_t kSsrc = 4;
+  const int kAttachmentId = 42;
+  const int kVideoSourceWidth = 12;
+  const int kVideoSourceHeight = 34;
+
+  cricket::VideoMediaInfo video_media_info;
+  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;
+  auto* video_media_channel = pc_->AddVideoChannel("VideoMid", "TransportName");
+  video_media_channel->SetStats(video_media_info);
+
+  auto video_source = FakeVideoTrackSourceForStats::Create(kVideoSourceWidth,
+                                                           kVideoSourceHeight);
+  auto video_track = FakeVideoTrackForStats::Create(
+      "LocalVideoTrackID", MediaStreamTrackInterface::kLive, video_source);
+  rtc::scoped_refptr<MockRtpSenderInternal> sender = CreateMockSender(
+      cricket::MEDIA_TYPE_VIDEO, video_track, kSsrc, kAttachmentId, {});
+  pc_->AddSender(sender);
+
+  rtc::scoped_refptr<const RTCStatsReport> report = stats_->GetStatsReport();
+
+  RTCVideoSourceStats expected_video("RTCVideoSource_42",
+                                     report->timestamp_us());
+  expected_video.track_identifier = "LocalVideoTrackID";
+  expected_video.kind = "video";
+  expected_video.width = kVideoSourceWidth;
+  expected_video.height = kVideoSourceHeight;
+  // |expected_video.frames| is expected to be undefined because it is not set.
+  // TODO(hbos): When implemented, set its expected value here.
+  expected_video.frames_per_second = 29;
+
+  ASSERT_TRUE(report->Get(expected_video.id()));
+  EXPECT_EQ(report->Get(expected_video.id())->cast_to<RTCVideoSourceStats>(),
+            expected_video);
+}
+
+// This test exercises the current behavior and code path, but the correct
+// behavior is to report frame rate even if we have no SSRC.
+// TODO(hbos): When we know the frame rate even if we have no SSRC, update the
+// expectations of this test.
+TEST_F(RTCStatsCollectorTest,
+       RTCVideoSourceStatsMissingFrameRateWhenSenderHasNoSsrc) {
+  // TODO(https://crbug.com/webrtc/8694): When 0 is no longer a magic value for
+  // "none", update this test.
+  const uint32_t kNoSsrc = 0;
+  const int kAttachmentId = 42;
+  const int kVideoSourceWidth = 12;
+  const int kVideoSourceHeight = 34;
+
+  cricket::VideoMediaInfo video_media_info;
+  video_media_info.senders.push_back(cricket::VideoSenderInfo());
+  video_media_info.senders[0].local_stats.push_back(cricket::SsrcSenderInfo());
+  video_media_info.senders[0].framerate_input = 29;
+  auto* video_media_channel = pc_->AddVideoChannel("VideoMid", "TransportName");
+  video_media_channel->SetStats(video_media_info);
+
+  auto video_source = FakeVideoTrackSourceForStats::Create(kVideoSourceWidth,
+                                                           kVideoSourceHeight);
+  auto video_track = FakeVideoTrackForStats::Create(
+      "LocalVideoTrackID", MediaStreamTrackInterface::kLive, video_source);
+  rtc::scoped_refptr<MockRtpSenderInternal> sender = CreateMockSender(
+      cricket::MEDIA_TYPE_VIDEO, video_track, kNoSsrc, kAttachmentId, {});
+  pc_->AddSender(sender);
+
+  rtc::scoped_refptr<const RTCStatsReport> report = stats_->GetStatsReport();
+  ASSERT_TRUE(report->Get("RTCVideoSource_42"));
+  auto video_stats =
+      report->Get("RTCVideoSource_42")->cast_to<RTCVideoSourceStats>();
+  EXPECT_FALSE(video_stats.frames_per_second.is_defined());
+}
+
+// The track not having a source is not expected to be true in practise, but
+// this is true in some tests relying on fakes. This test covers that code path.
+TEST_F(RTCStatsCollectorTest,
+       RTCVideoSourceStatsMissingResolutionWhenTrackHasNoSource) {
+  const uint32_t kSsrc = 4;
+  const int kAttachmentId = 42;
+
+  cricket::VideoMediaInfo video_media_info;
+  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;
+  auto* video_media_channel = pc_->AddVideoChannel("VideoMid", "TransportName");
+  video_media_channel->SetStats(video_media_info);
+
+  auto video_track = FakeVideoTrackForStats::Create(
+      "LocalVideoTrackID", MediaStreamTrackInterface::kLive,
+      /*source=*/nullptr);
+  rtc::scoped_refptr<MockRtpSenderInternal> sender = CreateMockSender(
+      cricket::MEDIA_TYPE_VIDEO, video_track, kSsrc, kAttachmentId, {});
+  pc_->AddSender(sender);
+
+  rtc::scoped_refptr<const RTCStatsReport> report = stats_->GetStatsReport();
+  ASSERT_TRUE(report->Get("RTCVideoSource_42"));
+  auto video_stats =
+      report->Get("RTCVideoSource_42")->cast_to<RTCVideoSourceStats>();
+  EXPECT_FALSE(video_stats.width.is_defined());
+  EXPECT_FALSE(video_stats.height.is_defined());
+}
+
+TEST_F(RTCStatsCollectorTest,
+       RTCAudioSourceStatsNotCollectedForSenderWithoutTrack) {
+  const uint32_t kSsrc = 4;
+  const int kAttachmentId = 42;
+
+  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 = kSsrc;
+  auto* voice_media_channel = pc_->AddVoiceChannel("AudioMid", "TransportName");
+  voice_media_channel->SetStats(voice_media_info);
+  rtc::scoped_refptr<MockRtpSenderInternal> sender = CreateMockSender(
+      cricket::MEDIA_TYPE_AUDIO, /*track=*/nullptr, kSsrc, kAttachmentId, {});
+  pc_->AddSender(sender);
+
+  rtc::scoped_refptr<const RTCStatsReport> report = stats_->GetStatsReport();
+  EXPECT_FALSE(report->Get("RTCAudioSource_42"));
+}
+
+TEST_F(RTCStatsCollectorTest,
+       RTCVideoSourceStatsNotCollectedForSenderWithoutTrack) {
+  const uint32_t kSsrc = 4;
+  const int kAttachmentId = 42;
+
+  cricket::VideoMediaInfo video_media_info;
+  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;
+  auto* video_media_channel = pc_->AddVideoChannel("VideoMid", "TransportName");
+  video_media_channel->SetStats(video_media_info);
+
+  rtc::scoped_refptr<MockRtpSenderInternal> sender = CreateMockSender(
+      cricket::MEDIA_TYPE_VIDEO, /*track=*/nullptr, kSsrc, kAttachmentId, {});
+  pc_->AddSender(sender);
+
+  rtc::scoped_refptr<const RTCStatsReport> report = stats_->GetStatsReport();
+  EXPECT_FALSE(report->Get("RTCVideoSource_42"));
+}
+
 TEST_F(RTCStatsCollectorTest, GetStatsWithSenderSelector) {
   ExampleStatsGraph graph = SetupExampleStatsGraphForSelectorTests();
   // Expected stats graph when filtered by sender:
   //
-  // track (sender)
-  //          ^
-  //          |
-  //         outbound-rtp
-  //          |        |
-  //          v        v
-  // codec (send)     transport
+  //  +--- track (sender)
+  //  |             ^
+  //  |             |
+  //  | +--------- outbound-rtp
+  //  | |           |        |
+  //  | |           v        v
+  //  | |  codec (send)     transport
+  //  v v
+  //  media-source
   rtc::scoped_refptr<const RTCStatsReport> sender_report =
       stats_->GetStatsReportWithSenderSelector(graph.sender);
   EXPECT_TRUE(sender_report);
   EXPECT_EQ(sender_report->timestamp_us(), graph.full_report->timestamp_us());
-  EXPECT_EQ(sender_report->size(), 4u);
+  EXPECT_EQ(sender_report->size(), 5u);
   EXPECT_TRUE(sender_report->Get(graph.send_codec_id));
   EXPECT_FALSE(sender_report->Get(graph.recv_codec_id));
   EXPECT_TRUE(sender_report->Get(graph.outbound_rtp_id));
@@ -2133,19 +2390,20 @@
   EXPECT_FALSE(sender_report->Get(graph.receiver_track_id));
   EXPECT_FALSE(sender_report->Get(graph.remote_stream_id));
   EXPECT_FALSE(sender_report->Get(graph.peer_connection_id));
+  EXPECT_TRUE(sender_report->Get(graph.media_source_id));
 }
 
 TEST_F(RTCStatsCollectorTest, GetStatsWithReceiverSelector) {
   ExampleStatsGraph graph = SetupExampleStatsGraphForSelectorTests();
   // Expected stats graph when filtered by receiver:
   //
-  //                                                 track (receiver)
-  //                                                   ^
-  //                                                   |
-  //                        inbound-rtp ---------------+
-  //                         |       |
-  //                         v       v
-  //                  transport     codec (recv)
+  //                                                       track (receiver)
+  //                                                         ^
+  //                                                         |
+  //                              inbound-rtp ---------------+
+  //                               |       |
+  //                               v       v
+  //                        transport     codec (recv)
   rtc::scoped_refptr<const RTCStatsReport> receiver_report =
       stats_->GetStatsReportWithReceiverSelector(graph.receiver);
   EXPECT_TRUE(receiver_report);
@@ -2160,6 +2418,7 @@
   EXPECT_TRUE(receiver_report->Get(graph.receiver_track_id));
   EXPECT_FALSE(receiver_report->Get(graph.remote_stream_id));
   EXPECT_FALSE(receiver_report->Get(graph.peer_connection_id));
+  EXPECT_FALSE(receiver_report->Get(graph.media_source_id));
 }
 
 TEST_F(RTCStatsCollectorTest, GetStatsWithNullSenderSelector) {
@@ -2188,7 +2447,7 @@
       CreateFakeTrack(cricket::MEDIA_TYPE_AUDIO, "audioTrack",
                       MediaStreamTrackInterface::kLive);
   rtc::scoped_refptr<MockRtpSenderInternal> sender =
-      CreateMockSender(track, 0, 49, {});
+      CreateMockSender(cricket::MEDIA_TYPE_AUDIO, track, 0, 49, {});
   pc_->AddSender(sender);
 
   rtc::scoped_refptr<const RTCStatsReport> report = stats_->GetStatsReport();
@@ -2207,7 +2466,7 @@
       CreateFakeTrack(cricket::MEDIA_TYPE_AUDIO, "audioTrack",
                       MediaStreamTrackInterface::kLive);
   rtc::scoped_refptr<MockRtpSenderInternal> sender =
-      CreateMockSender(track, 4711, 49, {});
+      CreateMockSender(cricket::MEDIA_TYPE_AUDIO, track, 4711, 49, {});
   pc_->AddSender(sender);
 
   // We do not generate any matching voice_sender_info stats.