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/api/stats/rtc_stats.h b/api/stats/rtc_stats.h
index bb23490..4361163 100644
--- a/api/stats/rtc_stats.h
+++ b/api/stats/rtc_stats.h
@@ -179,6 +179,24 @@
     return local_var_members_vec;                                              \
   }
 
+// A version of WEBRTC_RTCSTATS_IMPL() where "..." is omitted, used to avoid a
+// compile error on windows. This is used if the stats dictionary does not
+// declare any members of its own (but perhaps its parent dictionary does).
+#define WEBRTC_RTCSTATS_IMPL_NO_MEMBERS(this_class, parent_class, type_str) \
+  const char this_class::kType[] = type_str;                                \
+                                                                            \
+  std::unique_ptr<webrtc::RTCStats> this_class::copy() const {              \
+    return std::unique_ptr<webrtc::RTCStats>(new this_class(*this));        \
+  }                                                                         \
+                                                                            \
+  const char* this_class::type() const { return this_class::kType; }        \
+                                                                            \
+  std::vector<const webrtc::RTCStatsMemberInterface*>                       \
+  this_class::MembersOfThisObjectAndAncestors(                              \
+      size_t local_var_additional_capacity) const {                         \
+    return parent_class::MembersOfThisObjectAndAncestors(0);                \
+  }
+
 // Non-standard stats members can be exposed to the JavaScript API in Chrome
 // e.g. through origin trials. The group ID can be used by the blink layer to
 // determine if a stats member should be exposed or not. Multiple non-standard
diff --git a/api/stats/rtcstats_objects.h b/api/stats/rtcstats_objects.h
index 2e6cd3b..cbcc8fa 100644
--- a/api/stats/rtcstats_objects.h
+++ b/api/stats/rtcstats_objects.h
@@ -279,6 +279,7 @@
   ~RTCMediaStreamTrackStats() override;
 
   RTCStatsMember<std::string> track_identifier;
+  RTCStatsMember<std::string> media_source_id;
   RTCStatsMember<bool> remote_source;
   RTCStatsMember<bool> ended;
   // TODO(hbos): |RTCStatsCollector| does not return stats for detached tracks.
@@ -450,6 +451,7 @@
   RTCOutboundRTPStreamStats(const RTCOutboundRTPStreamStats& other);
   ~RTCOutboundRTPStreamStats() override;
 
+  RTCStatsMember<std::string> media_source_id;
   RTCStatsMember<uint32_t> packets_sent;
   RTCStatsMember<uint64_t> retransmitted_packets_sent;
   RTCStatsMember<uint64_t> bytes_sent;
@@ -466,6 +468,50 @@
   RTCStatsMember<std::string> content_type;
 };
 
+// https://w3c.github.io/webrtc-stats/#dom-rtcmediasourcestats
+class RTC_EXPORT RTCMediaSourceStats : public RTCStats {
+ public:
+  WEBRTC_RTCSTATS_DECL();
+
+  RTCMediaSourceStats(const RTCMediaSourceStats& other);
+  ~RTCMediaSourceStats() override;
+
+  RTCStatsMember<std::string> track_identifier;
+  RTCStatsMember<std::string> kind;
+
+ protected:
+  RTCMediaSourceStats(const std::string& id, int64_t timestamp_us);
+  RTCMediaSourceStats(std::string&& id, int64_t timestamp_us);
+};
+
+// https://w3c.github.io/webrtc-stats/#dom-rtcaudiosourcestats
+class RTC_EXPORT RTCAudioSourceStats final : public RTCMediaSourceStats {
+ public:
+  WEBRTC_RTCSTATS_DECL();
+
+  RTCAudioSourceStats(const std::string& id, int64_t timestamp_us);
+  RTCAudioSourceStats(std::string&& id, int64_t timestamp_us);
+  RTCAudioSourceStats(const RTCAudioSourceStats& other);
+  ~RTCAudioSourceStats() override;
+};
+
+// https://w3c.github.io/webrtc-stats/#dom-rtcvideosourcestats
+class RTC_EXPORT RTCVideoSourceStats final : public RTCMediaSourceStats {
+ public:
+  WEBRTC_RTCSTATS_DECL();
+
+  RTCVideoSourceStats(const std::string& id, int64_t timestamp_us);
+  RTCVideoSourceStats(std::string&& id, int64_t timestamp_us);
+  RTCVideoSourceStats(const RTCVideoSourceStats& other);
+  ~RTCVideoSourceStats() override;
+
+  RTCStatsMember<uint32_t> width;
+  RTCStatsMember<uint32_t> height;
+  // TODO(hbos): Implement this metric.
+  RTCStatsMember<uint32_t> frames;
+  RTCStatsMember<uint32_t> frames_per_second;
+};
+
 // https://w3c.github.io/webrtc-stats/#transportstats-dict*
 class RTC_EXPORT RTCTransportStats final : public RTCStats {
  public:
diff --git a/pc/rtc_stats_collector.cc b/pc/rtc_stats_collector.cc
index 3e11a92..9f82abe 100644
--- a/pc/rtc_stats_collector.cc
+++ b/pc/rtc_stats_collector.cc
@@ -34,6 +34,7 @@
 
 namespace {
 
+// TODO(https://crbug.com/webrtc/10656): Consider making IDs less predictable.
 std::string RTCCertificateIDFromFingerprint(const std::string& fingerprint) {
   return "RTCCertificate_" + fingerprint;
 }
@@ -91,6 +92,16 @@
   return sb.str();
 }
 
+std::string RTCMediaSourceStatsIDFromKindAndAttachment(
+    cricket::MediaType media_type,
+    int attachment_id) {
+  char buf[1024];
+  rtc::SimpleStringBuilder sb(buf);
+  sb << "RTC" << (media_type == cricket::MEDIA_TYPE_AUDIO ? "Audio" : "Video")
+     << "Source_" << attachment_id;
+  return sb.str();
+}
+
 const char* CandidateTypeToRTCIceCandidateType(const std::string& type) {
   if (type == cricket::LOCAL_PORT_TYPE)
     return RTCIceCandidateType::kHost;
@@ -439,6 +450,9 @@
           timestamp_us, RTCMediaStreamTrackKind::kAudio));
   SetMediaStreamTrackStatsFromMediaStreamTrackInterface(
       audio_track, audio_track_stats.get());
+  audio_track_stats->media_source_id =
+      RTCMediaSourceStatsIDFromKindAndAttachment(cricket::MEDIA_TYPE_AUDIO,
+                                                 attachment_id);
   audio_track_stats->remote_source = false;
   audio_track_stats->detached = false;
   if (voice_sender_info.audio_level >= 0) {
@@ -524,11 +538,13 @@
   std::unique_ptr<RTCMediaStreamTrackStats> video_track_stats(
       new RTCMediaStreamTrackStats(
           RTCMediaStreamTrackStatsIDFromDirectionAndAttachment(kSender,
-
                                                                attachment_id),
           timestamp_us, RTCMediaStreamTrackKind::kVideo));
   SetMediaStreamTrackStatsFromMediaStreamTrackInterface(
       video_track, video_track_stats.get());
+  video_track_stats->media_source_id =
+      RTCMediaSourceStatsIDFromKindAndAttachment(cricket::MEDIA_TYPE_VIDEO,
+                                                 attachment_id);
   video_track_stats->remote_source = false;
   video_track_stats->detached = false;
   video_track_stats->frame_width = static_cast<uint32_t>(
@@ -930,6 +946,7 @@
   ProduceDataChannelStats_s(timestamp_us, partial_report);
   ProduceMediaStreamStats_s(timestamp_us, partial_report);
   ProduceMediaStreamTrackStats_s(timestamp_us, partial_report);
+  ProduceMediaSourceStats_s(timestamp_us, partial_report);
   ProducePeerConnectionStats_s(timestamp_us, partial_report);
 }
 
@@ -1265,6 +1282,64 @@
   }
 }
 
+void RTCStatsCollector::ProduceMediaSourceStats_s(
+    int64_t timestamp_us,
+    RTCStatsReport* report) const {
+  RTC_DCHECK(signaling_thread_->IsCurrent());
+  for (const RtpTransceiverStatsInfo& transceiver_stats_info :
+       transceiver_stats_infos_) {
+    const auto& track_media_info_map =
+        transceiver_stats_info.track_media_info_map;
+    for (const auto& sender : transceiver_stats_info.transceiver->senders()) {
+      const auto& sender_internal = sender->internal();
+      const auto& track = sender_internal->track();
+      if (!track)
+        continue;
+      // TODO(hbos): The same track could be attached to multiple senders which
+      // should result in multiple senders referencing the same media source
+      // stats. When all media source related metrics are moved to the track's
+      // source (e.g. input frame rate is moved from cricket::VideoSenderInfo to
+      // VideoTrackSourceInterface::Stats), don't create separate media source
+      // stats objects on a per-attachment basis.
+      std::unique_ptr<RTCMediaSourceStats> media_source_stats;
+      if (track->kind() == MediaStreamTrackInterface::kAudioKind) {
+        media_source_stats = absl::make_unique<RTCAudioSourceStats>(
+            RTCMediaSourceStatsIDFromKindAndAttachment(
+                cricket::MEDIA_TYPE_AUDIO, sender_internal->AttachmentId()),
+            timestamp_us);
+      } else {
+        RTC_DCHECK_EQ(MediaStreamTrackInterface::kVideoKind, track->kind());
+        auto video_source_stats = absl::make_unique<RTCVideoSourceStats>(
+            RTCMediaSourceStatsIDFromKindAndAttachment(
+                cricket::MEDIA_TYPE_VIDEO, sender_internal->AttachmentId()),
+            timestamp_us);
+        auto* video_track = static_cast<VideoTrackInterface*>(track.get());
+        auto* video_source = video_track->GetSource();
+        VideoTrackSourceInterface::Stats source_stats;
+        if (video_source && video_source->GetStats(&source_stats)) {
+          video_source_stats->width = source_stats.input_width;
+          video_source_stats->height = source_stats.input_height;
+        }
+        // TODO(hbos): Source stats should not depend on whether or not we are
+        // connected/have an SSRC assigned. Related to
+        // https://crbug.com/webrtc/8694 (using ssrc 0 to indicate "none").
+        if (sender_internal->ssrc() != 0) {
+          auto* sender_info = track_media_info_map->GetVideoSenderInfoBySsrc(
+              sender_internal->ssrc());
+          if (sender_info) {
+            video_source_stats->frames_per_second =
+                sender_info->framerate_input;
+          }
+        }
+        media_source_stats = std::move(video_source_stats);
+      }
+      media_source_stats->track_identifier = track->id();
+      media_source_stats->kind = track->kind();
+      report->AddStats(std::move(media_source_stats));
+    }
+  }
+}
+
 void RTCStatsCollector::ProducePeerConnectionStats_s(
     int64_t timestamp_us, RTCStatsReport* report) const {
   RTC_DCHECK(signaling_thread_->IsCurrent());
@@ -1340,10 +1415,14 @@
     rtc::scoped_refptr<AudioTrackInterface> audio_track =
         track_media_info_map.GetAudioTrack(voice_sender_info);
     if (audio_track) {
+      int attachment_id =
+          track_media_info_map.GetAttachmentIdByTrack(audio_track).value();
       outbound_audio->track_id =
-          RTCMediaStreamTrackStatsIDFromDirectionAndAttachment(
-              kSender,
-              track_media_info_map.GetAttachmentIdByTrack(audio_track).value());
+          RTCMediaStreamTrackStatsIDFromDirectionAndAttachment(kSender,
+                                                               attachment_id);
+      outbound_audio->media_source_id =
+          RTCMediaSourceStatsIDFromKindAndAttachment(cricket::MEDIA_TYPE_AUDIO,
+                                                     attachment_id);
     }
     outbound_audio->transport_id = transport_id;
     report->AddStats(std::move(outbound_audio));
@@ -1397,10 +1476,14 @@
     rtc::scoped_refptr<VideoTrackInterface> video_track =
         track_media_info_map.GetVideoTrack(video_sender_info);
     if (video_track) {
+      int attachment_id =
+          track_media_info_map.GetAttachmentIdByTrack(video_track).value();
       outbound_video->track_id =
-          RTCMediaStreamTrackStatsIDFromDirectionAndAttachment(
-              kSender,
-              track_media_info_map.GetAttachmentIdByTrack(video_track).value());
+          RTCMediaStreamTrackStatsIDFromDirectionAndAttachment(kSender,
+                                                               attachment_id);
+      outbound_video->media_source_id =
+          RTCMediaSourceStatsIDFromKindAndAttachment(cricket::MEDIA_TYPE_VIDEO,
+                                                     attachment_id);
     }
     outbound_video->transport_id = transport_id;
     report->AddStats(std::move(outbound_video));
diff --git a/pc/rtc_stats_collector.h b/pc/rtc_stats_collector.h
index 4837fc0..82501db 100644
--- a/pc/rtc_stats_collector.h
+++ b/pc/rtc_stats_collector.h
@@ -182,6 +182,10 @@
   // Produces |RTCMediaStreamTrackStats|.
   void ProduceMediaStreamTrackStats_s(int64_t timestamp_us,
                                       RTCStatsReport* report) const;
+  // Produces RTCMediaSourceStats, including RTCAudioSourceStats and
+  // RTCVideoSourceStats.
+  void ProduceMediaSourceStats_s(int64_t timestamp_us,
+                                 RTCStatsReport* report) const;
   // Produces |RTCPeerConnectionStats|.
   void ProducePeerConnectionStats_s(int64_t timestamp_us,
                                     RTCStatsReport* report) const;
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.
diff --git a/pc/rtc_stats_integrationtest.cc b/pc/rtc_stats_integrationtest.cc
index 438b47b..10b17e5 100644
--- a/pc/rtc_stats_integrationtest.cc
+++ b/pc/rtc_stats_integrationtest.cc
@@ -317,8 +317,8 @@
     EXPECT_TRUE(valid_reference)
         << stats_->type() << "." << member.name()
         << " is not a reference to an "
-        << "existing dictionary of type " << expected_type << " ("
-        << member.ValueToString() << ").";
+        << "existing dictionary of type " << expected_type << " (value: "
+        << (member.is_defined() ? member.ValueToString() : "null") << ").";
     MarkMemberTested(member, valid_reference);
   }
 
@@ -394,6 +394,18 @@
       } else if (stats.type() == RTCOutboundRTPStreamStats::kType) {
         verify_successful &= VerifyRTCOutboundRTPStreamStats(
             stats.cast_to<RTCOutboundRTPStreamStats>());
+      } else if (stats.type() == RTCAudioSourceStats::kType) {
+        // RTCAudioSourceStats::kType and RTCVideoSourceStats::kType both have
+        // the value "media-source", but they are distinguishable with pointer
+        // equality (==). In JavaScript they would be distinguished with |kind|.
+        verify_successful &=
+            VerifyRTCAudioSourceStats(stats.cast_to<RTCAudioSourceStats>());
+      } else if (stats.type() == RTCVideoSourceStats::kType) {
+        // RTCAudioSourceStats::kType and RTCVideoSourceStats::kType both have
+        // the value "media-source", but they are distinguishable with pointer
+        // equality (==). In JavaScript they would be distinguished with |kind|.
+        verify_successful &=
+            VerifyRTCVideoSourceStats(stats.cast_to<RTCVideoSourceStats>());
       } else if (stats.type() == RTCTransportStats::kType) {
         verify_successful &=
             VerifyRTCTransportStats(stats.cast_to<RTCTransportStats>());
@@ -542,6 +554,15 @@
     verifier.TestMemberIsDefined(media_stream_track.kind);
     // Video or audio media stream track?
     if (*media_stream_track.kind == RTCMediaStreamTrackKind::kVideo) {
+      // The type of the referenced media source depends on kind.
+      if (media_stream_track.remote_source.is_defined() &&
+          !*media_stream_track.remote_source) {
+        verifier.TestMemberIsIDReference(media_stream_track.media_source_id,
+                                         RTCVideoSourceStats::kType);
+      } else {
+        // Remote tracks don't have media source stats.
+        verifier.TestMemberIsUndefined(media_stream_track.media_source_id);
+      }
       // Video-only members
       verifier.TestMemberIsNonNegative<uint32_t>(
           media_stream_track.frame_width);
@@ -600,6 +621,15 @@
       verifier.TestMemberIsUndefined(media_stream_track.total_samples_duration);
     } else {
       RTC_DCHECK_EQ(*media_stream_track.kind, RTCMediaStreamTrackKind::kAudio);
+      // The type of the referenced media source depends on kind.
+      if (media_stream_track.remote_source.is_defined() &&
+          !*media_stream_track.remote_source) {
+        verifier.TestMemberIsIDReference(media_stream_track.media_source_id,
+                                         RTCAudioSourceStats::kType);
+      } else {
+        // Remote tracks don't have media source stats.
+        verifier.TestMemberIsUndefined(media_stream_track.media_source_id);
+      }
       // Video-only members should be undefined
       verifier.TestMemberIsUndefined(media_stream_track.frame_width);
       verifier.TestMemberIsUndefined(media_stream_track.frame_height);
@@ -777,8 +807,12 @@
     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);
     } else {
+      verifier.TestMemberIsIDReference(outbound_stream.media_source_id,
+                                       RTCAudioSourceStats::kType);
       verifier.TestMemberIsUndefined(outbound_stream.qp_sum);
     }
     verifier.TestMemberIsNonNegative<uint32_t>(outbound_stream.packets_sent);
@@ -812,6 +846,40 @@
     return verifier.ExpectAllMembersSuccessfullyTested();
   }
 
+  void VerifyRTCMediaSourceStats(const RTCMediaSourceStats& media_source,
+                                 RTCStatsVerifier* verifier) {
+    verifier->TestMemberIsDefined(media_source.track_identifier);
+    verifier->TestMemberIsDefined(media_source.kind);
+    if (media_source.kind.is_defined()) {
+      EXPECT_TRUE((*media_source.kind == "audio" &&
+                   media_source.type() == RTCAudioSourceStats::kType) ||
+                  (*media_source.kind == "video" &&
+                   media_source.type() == RTCVideoSourceStats::kType));
+    }
+  }
+
+  bool VerifyRTCAudioSourceStats(const RTCAudioSourceStats& audio_source) {
+    RTCStatsVerifier verifier(report_, &audio_source);
+    VerifyRTCMediaSourceStats(audio_source, &verifier);
+    return verifier.ExpectAllMembersSuccessfullyTested();
+  }
+
+  bool VerifyRTCVideoSourceStats(const RTCVideoSourceStats& video_source) {
+    RTCStatsVerifier verifier(report_, &video_source);
+    VerifyRTCMediaSourceStats(video_source, &verifier);
+    // TODO(hbos): This integration test uses fakes that doesn't support
+    // VideoTrackSourceInterface::Stats. When this is fixed we should
+    // TestMemberIsNonNegative<uint32_t>() for |width| and |height| instead to
+    // reflect real code.
+    verifier.TestMemberIsUndefined(video_source.width);
+    verifier.TestMemberIsUndefined(video_source.height);
+    // TODO(hbos): When |frames| is implemented test that this member should be
+    // expected to be non-negative.
+    verifier.TestMemberIsUndefined(video_source.frames);
+    verifier.TestMemberIsNonNegative<uint32_t>(video_source.frames_per_second);
+    return verifier.ExpectAllMembersSuccessfullyTested();
+  }
+
   bool VerifyRTCTransportStats(const RTCTransportStats& transport) {
     RTCStatsVerifier verifier(report_, &transport);
     verifier.TestMemberIsNonNegative<uint64_t>(transport.bytes_sent);
diff --git a/pc/rtc_stats_traversal.cc b/pc/rtc_stats_traversal.cc
index e7af0e1..16a6c9d 100644
--- a/pc/rtc_stats_traversal.cc
+++ b/pc/rtc_stats_traversal.cc
@@ -94,7 +94,8 @@
     const auto& stream = static_cast<const RTCMediaStreamStats&>(stats);
     AddIdsIfDefined(stream.track_ids, &neighbor_ids);
   } else if (type == RTCMediaStreamTrackStats::kType) {
-    // RTCMediaStreamTrackStats does not have any neighbor references.
+    const auto& track = static_cast<const RTCMediaStreamTrackStats&>(stats);
+    AddIdIfDefined(track.media_source_id, &neighbor_ids);
   } else if (type == RTCPeerConnectionStats::kType) {
     // RTCPeerConnectionStats does not have any neighbor references.
   } else if (type == RTCInboundRTPStreamStats::kType ||
@@ -104,6 +105,14 @@
     AddIdIfDefined(rtp.track_id, &neighbor_ids);
     AddIdIfDefined(rtp.transport_id, &neighbor_ids);
     AddIdIfDefined(rtp.codec_id, &neighbor_ids);
+    if (type == RTCOutboundRTPStreamStats::kType) {
+      const auto& outbound_rtp =
+          static_cast<const RTCOutboundRTPStreamStats&>(stats);
+      AddIdIfDefined(outbound_rtp.media_source_id, &neighbor_ids);
+    }
+  } else if (type == RTCAudioSourceStats::kType ||
+             type == RTCVideoSourceStats::kType) {
+    // RTC[Audio/Video]SourceStats does not have any neighbor references.
   } else if (type == RTCTransportStats::kType) {
     // RTCTransportStats does not have any neighbor references.
     const auto& transport = static_cast<const RTCTransportStats&>(stats);
diff --git a/stats/rtcstats_objects.cc b/stats/rtcstats_objects.cc
index e6c96e0..f97e23f 100644
--- a/stats/rtcstats_objects.cc
+++ b/stats/rtcstats_objects.cc
@@ -361,6 +361,7 @@
 // clang-format off
 WEBRTC_RTCSTATS_IMPL(RTCMediaStreamTrackStats, RTCStats, "track",
                      &track_identifier,
+                     &media_source_id,
                      &remote_source,
                      &ended,
                      &detached,
@@ -409,6 +410,7 @@
                                                    const char* kind)
     : RTCStats(std::move(id), timestamp_us),
       track_identifier("trackIdentifier"),
+      media_source_id("mediaSourceId"),
       remote_source("remoteSource"),
       ended("ended"),
       detached("detached"),
@@ -463,6 +465,7 @@
     const RTCMediaStreamTrackStats& other)
     : RTCStats(other.id(), other.timestamp_us()),
       track_identifier(other.track_identifier),
+      media_source_id(other.media_source_id),
       remote_source(other.remote_source),
       ended(other.ended),
       detached(other.detached),
@@ -668,6 +671,7 @@
 // clang-format off
 WEBRTC_RTCSTATS_IMPL(
     RTCOutboundRTPStreamStats, RTCRTPStreamStats, "outbound-rtp",
+    &media_source_id,
     &packets_sent,
     &retransmitted_packets_sent,
     &bytes_sent,
@@ -687,6 +691,7 @@
 RTCOutboundRTPStreamStats::RTCOutboundRTPStreamStats(std::string&& id,
                                                      int64_t timestamp_us)
     : RTCRTPStreamStats(std::move(id), timestamp_us),
+      media_source_id("mediaSourceId"),
       packets_sent("packetsSent"),
       retransmitted_packets_sent("retransmittedPacketsSent"),
       bytes_sent("bytesSent"),
@@ -701,6 +706,7 @@
 RTCOutboundRTPStreamStats::RTCOutboundRTPStreamStats(
     const RTCOutboundRTPStreamStats& other)
     : RTCRTPStreamStats(other),
+      media_source_id(other.media_source_id),
       packets_sent(other.packets_sent),
       retransmitted_packets_sent(other.retransmitted_packets_sent),
       bytes_sent(other.bytes_sent),
@@ -715,6 +721,73 @@
 RTCOutboundRTPStreamStats::~RTCOutboundRTPStreamStats() {}
 
 // clang-format off
+WEBRTC_RTCSTATS_IMPL(RTCMediaSourceStats, RTCStats, "parent-media-source",
+    &track_identifier,
+    &kind)
+// clang-format on
+
+RTCMediaSourceStats::RTCMediaSourceStats(const std::string& id,
+                                         int64_t timestamp_us)
+    : RTCMediaSourceStats(std::string(id), timestamp_us) {}
+
+RTCMediaSourceStats::RTCMediaSourceStats(std::string&& id, int64_t timestamp_us)
+    : RTCStats(std::move(id), timestamp_us),
+      track_identifier("trackIdentifier"),
+      kind("kind") {}
+
+RTCMediaSourceStats::RTCMediaSourceStats(const RTCMediaSourceStats& other)
+    : RTCStats(other.id(), other.timestamp_us()),
+      track_identifier(other.track_identifier),
+      kind(other.kind) {}
+
+RTCMediaSourceStats::~RTCMediaSourceStats() {}
+
+// clang-format off
+WEBRTC_RTCSTATS_IMPL_NO_MEMBERS(
+    RTCAudioSourceStats, RTCMediaSourceStats, "media-source")
+// clang-format on
+
+RTCAudioSourceStats::RTCAudioSourceStats(const std::string& id,
+                                         int64_t timestamp_us)
+    : RTCAudioSourceStats(std::string(id), timestamp_us) {}
+
+RTCAudioSourceStats::RTCAudioSourceStats(std::string&& id, int64_t timestamp_us)
+    : RTCMediaSourceStats(std::move(id), timestamp_us) {}
+
+RTCAudioSourceStats::RTCAudioSourceStats(const RTCAudioSourceStats& other)
+    : RTCMediaSourceStats(other) {}
+
+RTCAudioSourceStats::~RTCAudioSourceStats() {}
+
+// clang-format off
+WEBRTC_RTCSTATS_IMPL(RTCVideoSourceStats, RTCMediaSourceStats, "media-source",
+    &width,
+    &height,
+    &frames,
+    &frames_per_second)
+// clang-format on
+
+RTCVideoSourceStats::RTCVideoSourceStats(const std::string& id,
+                                         int64_t timestamp_us)
+    : RTCVideoSourceStats(std::string(id), timestamp_us) {}
+
+RTCVideoSourceStats::RTCVideoSourceStats(std::string&& id, int64_t timestamp_us)
+    : RTCMediaSourceStats(std::move(id), timestamp_us),
+      width("width"),
+      height("height"),
+      frames("frames"),
+      frames_per_second("framesPerSecond") {}
+
+RTCVideoSourceStats::RTCVideoSourceStats(const RTCVideoSourceStats& other)
+    : RTCMediaSourceStats(other),
+      width(other.width),
+      height(other.height),
+      frames(other.frames),
+      frames_per_second(other.frames_per_second) {}
+
+RTCVideoSourceStats::~RTCVideoSourceStats() {}
+
+// clang-format off
 WEBRTC_RTCSTATS_IMPL(RTCTransportStats, RTCStats, "transport",
     &bytes_sent,
     &bytes_received,