RTCMediaStream[Track]Stats added.

Not all members are collected by RTCStatsCollector and detached tracks
are not visible in the returned stats. This needs to be addressed before
closing crbug.com/660827 and crbug.com/659137

BUG=chromium:660827, chromium:659137, chromium:627816

Review-Url: https://codereview.webrtc.org/2467873005
Cr-Commit-Position: refs/heads/master@{#14978}
diff --git a/webrtc/api/rtcstatscollector.cc b/webrtc/api/rtcstatscollector.cc
index b14b396..ce3176b 100644
--- a/webrtc/api/rtcstatscollector.cc
+++ b/webrtc/api/rtcstatscollector.cc
@@ -15,6 +15,8 @@
 #include <vector>
 
 #include "webrtc/api/peerconnection.h"
+#include "webrtc/api/peerconnectioninterface.h"
+#include "webrtc/api/mediastreaminterface.h"
 #include "webrtc/api/webrtcsession.h"
 #include "webrtc/base/checks.h"
 #include "webrtc/base/timeutils.h"
@@ -37,6 +39,11 @@
       info.remote_candidate.id();
 }
 
+std::string RTCMediaStreamTrackStatsIDFromMediaStreamTrackInterface(
+    const MediaStreamTrackInterface& track) {
+  return "RTCMediaStreamTrack_" + track.id();
+}
+
 std::string RTCTransportStatsIDFromTransportChannel(
     const std::string& transport_name, int channel_component) {
   return "RTCTransport_" + transport_name + "_" +
@@ -93,6 +100,13 @@
   }
 }
 
+void SetMediaStreamTrackStatsFromMediaStreamTrackInterface(
+    const MediaStreamTrackInterface& track,
+    RTCMediaStreamTrackStats* track_stats) {
+  track_stats->track_identifier = track.id();
+  track_stats->ended = (track.state() == MediaStreamTrackInterface::kEnded);
+}
+
 void SetInboundRTPStreamStatsFromMediaReceiverInfo(
     const cricket::MediaReceiverInfo& media_receiver_info,
     RTCInboundRTPStreamStats* inbound_stats) {
@@ -214,6 +228,93 @@
   return stats->id();
 }
 
+void ProduceMediaStreamAndTrackStats(
+    int64_t timestamp_us,
+    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;
+  for (size_t i = 0; i < streams->count(); ++i) {
+    MediaStreamInterface* stream = streams->at(i);
+
+    std::unique_ptr<RTCMediaStreamStats> stream_stats(
+        new RTCMediaStreamStats("RTCMediaStream_" + stream->label(),
+                                timestamp_us));
+    stream_stats->stream_identifier = stream->label();
+    stream_stats->track_ids = std::vector<std::string>();
+    // Audio Tracks
+    for (rtc::scoped_refptr<AudioTrackInterface> audio_track :
+         stream->GetAudioTracks()) {
+      std::string id = RTCMediaStreamTrackStatsIDFromMediaStreamTrackInterface(
+        *audio_track.get());
+      if (report->Get(id)) {
+        // Skip track, stats already exist for it.
+        continue;
+      }
+      std::unique_ptr<RTCMediaStreamTrackStats> audio_track_stats(
+          new RTCMediaStreamTrackStats(id, timestamp_us));
+      stream_stats->track_ids->push_back(audio_track_stats->id());
+      SetMediaStreamTrackStatsFromMediaStreamTrackInterface(
+          *audio_track.get(),
+          audio_track_stats.get());
+      audio_track_stats->remote_source = !is_local;
+      audio_track_stats->detached = false;
+      int signal_level;
+      if (audio_track->GetSignalLevel(&signal_level)) {
+        // Convert signal level from [0,32767] int to [0,1] double.
+        RTC_DCHECK_GE(signal_level, 0);
+        RTC_DCHECK_LE(signal_level, 32767);
+        audio_track_stats->audio_level = signal_level / 32767.0;
+      }
+      if (audio_track->GetAudioProcessor()) {
+        AudioProcessorInterface::AudioProcessorStats audio_processor_stats;
+        audio_track->GetAudioProcessor()->GetStats(&audio_processor_stats);
+        audio_track_stats->echo_return_loss = static_cast<double>(
+            audio_processor_stats.echo_return_loss);
+        audio_track_stats->echo_return_loss_enhancement = static_cast<double>(
+            audio_processor_stats.echo_return_loss_enhancement);
+      }
+      report->AddStats(std::move(audio_track_stats));
+    }
+    // Video Tracks
+    for (rtc::scoped_refptr<VideoTrackInterface> video_track :
+         stream->GetVideoTracks()) {
+      std::string id = RTCMediaStreamTrackStatsIDFromMediaStreamTrackInterface(
+          *video_track.get());
+      if (report->Get(id)) {
+        // Skip track, stats already exist for it.
+        continue;
+      }
+      std::unique_ptr<RTCMediaStreamTrackStats> video_track_stats(
+          new RTCMediaStreamTrackStats(id, timestamp_us));
+      stream_stats->track_ids->push_back(video_track_stats->id());
+      SetMediaStreamTrackStatsFromMediaStreamTrackInterface(
+          *video_track.get(),
+          video_track_stats.get());
+      video_track_stats->remote_source = !is_local;
+      video_track_stats->detached = false;
+      if (video_track->GetSource()) {
+        VideoTrackSourceInterface::Stats video_track_source_stats;
+        if (video_track->GetSource()->GetStats(&video_track_source_stats)) {
+          video_track_stats->frame_width = static_cast<uint32_t>(
+              video_track_source_stats.input_width);
+          video_track_stats->frame_height = static_cast<uint32_t>(
+              video_track_source_stats.input_height);
+        }
+      }
+      report->AddStats(std::move(video_track_stats));
+    }
+    report->AddStats(std::move(stream_stats));
+  }
+}
+
 }  // namespace
 
 rtc::scoped_refptr<RTCStatsCollector> RTCStatsCollector::Create(
@@ -301,6 +402,7 @@
         timestamp_us, session_stats, transport_cert_stats, report.get());
   }
   ProduceDataChannelStats_s(timestamp_us, report.get());
+  ProduceMediaStreamAndTrackStats_s(timestamp_us, report.get());
   ProducePeerConnectionStats_s(timestamp_us, report.get());
 
   AddPartialResults(report);
@@ -467,6 +569,15 @@
   }
 }
 
+void RTCStatsCollector::ProduceMediaStreamAndTrackStats_s(
+    int64_t timestamp_us, RTCStatsReport* report) const {
+  RTC_DCHECK(signaling_thread_->IsCurrent());
+  ProduceMediaStreamAndTrackStats(
+      timestamp_us, pc_->local_streams(), true, report);
+  ProduceMediaStreamAndTrackStats(
+      timestamp_us, pc_->remote_streams(), false, report);
+}
+
 void RTCStatsCollector::ProducePeerConnectionStats_s(
     int64_t timestamp_us, RTCStatsReport* report) const {
   RTC_DCHECK(signaling_thread_->IsCurrent());
diff --git a/webrtc/api/rtcstatscollector.h b/webrtc/api/rtcstatscollector.h
index 2c160ee..ae4ba19 100644
--- a/webrtc/api/rtcstatscollector.h
+++ b/webrtc/api/rtcstatscollector.h
@@ -98,6 +98,9 @@
   void ProduceIceCandidateAndPairStats_s(
       int64_t timestamp_us, const SessionStats& session_stats,
       RTCStatsReport* report) const;
+  // Produces |RTCMediaStreamStats| and |RTCMediaStreamTrackStats|.
+  void ProduceMediaStreamAndTrackStats_s(
+      int64_t timestamp_us, RTCStatsReport* report) const;
   // Produces |RTCPeerConnectionStats|.
   void ProducePeerConnectionStats_s(
       int64_t timestamp_us, RTCStatsReport* report) const;
diff --git a/webrtc/api/rtcstatscollector_unittest.cc b/webrtc/api/rtcstatscollector_unittest.cc
index 808668b..adc1520 100644
--- a/webrtc/api/rtcstatscollector_unittest.cc
+++ b/webrtc/api/rtcstatscollector_unittest.cc
@@ -15,6 +15,8 @@
 #include <string>
 #include <vector>
 
+#include "webrtc/api/mediastream.h"
+#include "webrtc/api/mediastreamtrack.h"
 #include "webrtc/api/jsepsessiondescription.h"
 #include "webrtc/api/stats/rtcstats_objects.h"
 #include "webrtc/api/stats/rtcstatsreport.h"
@@ -70,6 +72,14 @@
   *os << stats.ToString();
 }
 
+void PrintTo(const RTCMediaStreamStats& stats, ::std::ostream* os) {
+  *os << stats.ToString();
+}
+
+void PrintTo(const RTCMediaStreamTrackStats& stats, ::std::ostream* os) {
+  *os << stats.ToString();
+}
+
 void PrintTo(const RTCInboundRTPStreamStats& stats, ::std::ostream* os) {
   *os << stats.ToString();
 }
@@ -150,6 +160,126 @@
   return candidate;
 }
 
+class FakeAudioProcessorForStats
+    : public rtc::RefCountedObject<AudioProcessorInterface> {
+ public:
+  FakeAudioProcessorForStats(
+      AudioProcessorInterface::AudioProcessorStats stats)
+      : stats_(stats) {
+  }
+
+  void GetStats(AudioProcessorInterface::AudioProcessorStats* stats) override {
+    *stats = stats_;
+  }
+
+ private:
+  AudioProcessorInterface::AudioProcessorStats stats_;
+};
+
+class FakeAudioTrackForStats
+    : public MediaStreamTrack<AudioTrackInterface> {
+ public:
+  static rtc::scoped_refptr<FakeAudioTrackForStats> Create(
+      const std::string& id,
+      MediaStreamTrackInterface::TrackState state,
+      int signal_level,
+      rtc::scoped_refptr<FakeAudioProcessorForStats> processor) {
+    rtc::scoped_refptr<FakeAudioTrackForStats> audio_track_stats(
+        new rtc::RefCountedObject<FakeAudioTrackForStats>(
+            id, signal_level, processor));
+    audio_track_stats->set_state(state);
+    return audio_track_stats;
+  }
+
+  FakeAudioTrackForStats(
+      const std::string& id,
+      int signal_level,
+      rtc::scoped_refptr<FakeAudioProcessorForStats> processor)
+      : MediaStreamTrack<AudioTrackInterface>(id),
+        signal_level_(signal_level),
+        processor_(processor) {
+  }
+
+  std::string kind() const override {
+    return MediaStreamTrackInterface::kAudioKind;
+  }
+  webrtc::AudioSourceInterface* GetSource() const override { return nullptr; }
+  void AddSink(webrtc::AudioTrackSinkInterface* sink) override {}
+  void RemoveSink(webrtc::AudioTrackSinkInterface* sink) override {}
+  bool GetSignalLevel(int* level) override {
+    *level = signal_level_;
+    return true;
+  }
+  rtc::scoped_refptr<AudioProcessorInterface> GetAudioProcessor() override {
+    return processor_;
+  }
+
+ private:
+  int signal_level_;
+  rtc::scoped_refptr<FakeAudioProcessorForStats> processor_;
+};
+
+class FakeVideoTrackSourceForStats
+    : public rtc::RefCountedObject<VideoTrackSourceInterface> {
+ public:
+  FakeVideoTrackSourceForStats(VideoTrackSourceInterface::Stats stats)
+      : stats_(stats) {
+  }
+
+  MediaSourceInterface::SourceState state() const override {
+    return MediaSourceInterface::kLive;
+  }
+  bool remote() const override { return false; }
+  void RegisterObserver(ObserverInterface* observer) override {}
+  void UnregisterObserver(ObserverInterface* observer) override {}
+  void AddOrUpdateSink(rtc::VideoSinkInterface<cricket::VideoFrame>* sink,
+                       const rtc::VideoSinkWants& wants) override {}
+  void RemoveSink(rtc::VideoSinkInterface<cricket::VideoFrame>* sink) override {
+  }
+  bool is_screencast() const override { return false; }
+  rtc::Optional<bool> needs_denoising() const override {
+    return rtc::Optional<bool>();
+  }
+  bool GetStats(VideoTrackSourceInterface::Stats* stats) override {
+    *stats = stats_;
+    return true;
+  }
+
+ private:
+  VideoTrackSourceInterface::Stats stats_;
+};
+
+class FakeVideoTrackForStats
+    : public MediaStreamTrack<VideoTrackInterface> {
+ public:
+  static rtc::scoped_refptr<FakeVideoTrackForStats> Create(
+      const std::string& id,
+      MediaStreamTrackInterface::TrackState state,
+      rtc::scoped_refptr<VideoTrackSourceInterface> source) {
+    rtc::scoped_refptr<FakeVideoTrackForStats> video_track(
+        new rtc::RefCountedObject<FakeVideoTrackForStats>(id, source));
+    video_track->set_state(state);
+    return video_track;
+  }
+
+  FakeVideoTrackForStats(
+      const std::string& id,
+      rtc::scoped_refptr<VideoTrackSourceInterface> source)
+      : MediaStreamTrack<VideoTrackInterface>(id),
+        source_(source) {
+  }
+
+  std::string kind() const override {
+    return MediaStreamTrackInterface::kVideoKind;
+  }
+  VideoTrackSourceInterface* GetSource() const override {
+    return source_;
+  }
+
+ private:
+  rtc::scoped_refptr<VideoTrackSourceInterface> source_;
+};
+
 class RTCStatsCollectorTestHelper : public SetSessionDescriptionObserver {
  public:
   RTCStatsCollectorTestHelper()
@@ -168,6 +298,8 @@
         session_(media_controller_.get()),
         pc_() {
     // Default return values for mocks.
+    EXPECT_CALL(pc_, local_streams()).WillRepeatedly(Return(nullptr));
+    EXPECT_CALL(pc_, remote_streams()).WillRepeatedly(Return(nullptr));
     EXPECT_CALL(pc_, session()).WillRepeatedly(Return(&session_));
     EXPECT_CALL(pc_, sctp_data_channels()).WillRepeatedly(
         ReturnRef(data_channels_));
@@ -198,12 +330,12 @@
 
  private:
   rtc::ScopedFakeClock fake_clock_;
-  webrtc::RtcEventLogNullImpl event_log_;
+  RtcEventLogNullImpl event_log_;
   rtc::Thread* const worker_thread_;
   rtc::Thread* const network_thread_;
   cricket::FakeMediaEngine* media_engine_;
   std::unique_ptr<cricket::ChannelManager> channel_manager_;
-  std::unique_ptr<webrtc::MediaControllerInterface> media_controller_;
+  std::unique_ptr<MediaControllerInterface> media_controller_;
   MockWebRtcSession session_;
   MockPeerConnection pc_;
 
@@ -979,6 +1111,196 @@
   }
 }
 
+TEST_F(RTCStatsCollectorTest,
+       CollectRTCMediaStreamStatsAndRTCMediaStreamTrackStats_Audio) {
+  rtc::scoped_refptr<StreamCollection> local_streams =
+      StreamCollection::Create();
+  rtc::scoped_refptr<StreamCollection> remote_streams =
+      StreamCollection::Create();
+  EXPECT_CALL(test_->pc(), local_streams())
+      .WillRepeatedly(Return(local_streams));
+  EXPECT_CALL(test_->pc(), remote_streams())
+      .WillRepeatedly(Return(remote_streams));
+
+  rtc::scoped_refptr<MediaStream> local_stream =
+      MediaStream::Create("LocalStreamLabel");
+  local_streams->AddStream(local_stream);
+  rtc::scoped_refptr<MediaStream> remote_stream =
+      MediaStream::Create("RemoteStreamLabel");
+  remote_streams->AddStream(remote_stream);
+
+  // Local audio track
+  AudioProcessorInterface::AudioProcessorStats local_audio_processor_stats;
+  local_audio_processor_stats.echo_return_loss = 42;
+  local_audio_processor_stats.echo_return_loss_enhancement = 52;
+  rtc::scoped_refptr<FakeAudioTrackForStats> local_audio_track =
+      FakeAudioTrackForStats::Create(
+          "LocalAudioTrackID",
+          MediaStreamTrackInterface::TrackState::kEnded,
+          32767,
+          new FakeAudioProcessorForStats(local_audio_processor_stats));
+  local_stream->AddTrack(local_audio_track);
+
+  // Remote audio track
+  AudioProcessorInterface::AudioProcessorStats remote_audio_processor_stats;
+  remote_audio_processor_stats.echo_return_loss = 13;
+  remote_audio_processor_stats.echo_return_loss_enhancement = 37;
+  rtc::scoped_refptr<FakeAudioTrackForStats> remote_audio_track =
+      FakeAudioTrackForStats::Create(
+          "RemoteAudioTrackID",
+          MediaStreamTrackInterface::TrackState::kLive,
+          0,
+          new FakeAudioProcessorForStats(remote_audio_processor_stats));
+  remote_stream->AddTrack(remote_audio_track);
+
+  rtc::scoped_refptr<const RTCStatsReport> report = GetStatsReport();
+
+  RTCMediaStreamStats expected_local_stream(
+      "RTCMediaStream_LocalStreamLabel", report->timestamp_us());
+  expected_local_stream.stream_identifier = local_stream->label();
+  expected_local_stream.track_ids = std::vector<std::string>();
+  expected_local_stream.track_ids->push_back(
+      "RTCMediaStreamTrack_LocalAudioTrackID");
+  EXPECT_TRUE(report->Get(expected_local_stream.id()));
+  EXPECT_EQ(expected_local_stream,
+            report->Get(expected_local_stream.id())->cast_to<
+                RTCMediaStreamStats>());
+
+  RTCMediaStreamStats expected_remote_stream(
+      "RTCMediaStream_RemoteStreamLabel", report->timestamp_us());
+  expected_remote_stream.stream_identifier = remote_stream->label();
+  expected_remote_stream.track_ids = std::vector<std::string>();
+  expected_remote_stream.track_ids->push_back(
+      "RTCMediaStreamTrack_RemoteAudioTrackID");
+  EXPECT_TRUE(report->Get(expected_remote_stream.id()));
+  EXPECT_EQ(expected_remote_stream,
+            report->Get(expected_remote_stream.id())->cast_to<
+                RTCMediaStreamStats>());
+
+  RTCMediaStreamTrackStats expected_local_audio_track(
+      "RTCMediaStreamTrack_LocalAudioTrackID", report->timestamp_us());
+  expected_local_audio_track.track_identifier = local_audio_track->id();
+  expected_local_audio_track.remote_source = false;
+  expected_local_audio_track.ended = true;
+  expected_local_audio_track.detached = false;
+  expected_local_audio_track.audio_level = 1.0;
+  expected_local_audio_track.echo_return_loss = 42.0;
+  expected_local_audio_track.echo_return_loss_enhancement = 52.0;
+  EXPECT_TRUE(report->Get(expected_local_audio_track.id()));
+  EXPECT_EQ(expected_local_audio_track,
+            report->Get(expected_local_audio_track.id())->cast_to<
+                RTCMediaStreamTrackStats>());
+
+  RTCMediaStreamTrackStats expected_remote_audio_track(
+      "RTCMediaStreamTrack_RemoteAudioTrackID", report->timestamp_us());
+  expected_remote_audio_track.track_identifier = remote_audio_track->id();
+  expected_remote_audio_track.remote_source = true;
+  expected_remote_audio_track.ended = false;
+  expected_remote_audio_track.detached = false;
+  expected_remote_audio_track.audio_level = 0.0;
+  expected_remote_audio_track.echo_return_loss = 13.0;
+  expected_remote_audio_track.echo_return_loss_enhancement = 37.0;
+  EXPECT_TRUE(report->Get(expected_remote_audio_track.id()));
+  EXPECT_EQ(expected_remote_audio_track,
+            report->Get(expected_remote_audio_track.id())->cast_to<
+                RTCMediaStreamTrackStats>());
+}
+
+TEST_F(RTCStatsCollectorTest,
+       CollectRTCMediaStreamStatsAndRTCMediaStreamTrackStats_Video) {
+  rtc::scoped_refptr<StreamCollection> local_streams =
+      StreamCollection::Create();
+  rtc::scoped_refptr<StreamCollection> remote_streams =
+      StreamCollection::Create();
+  EXPECT_CALL(test_->pc(), local_streams())
+      .WillRepeatedly(Return(local_streams));
+  EXPECT_CALL(test_->pc(), remote_streams())
+      .WillRepeatedly(Return(remote_streams));
+
+  rtc::scoped_refptr<MediaStream> local_stream =
+      MediaStream::Create("LocalStreamLabel");
+  local_streams->AddStream(local_stream);
+  rtc::scoped_refptr<MediaStream> remote_stream =
+      MediaStream::Create("RemoteStreamLabel");
+  remote_streams->AddStream(remote_stream);
+
+  // Local video track
+  VideoTrackSourceInterface::Stats local_video_track_source_stats;
+  local_video_track_source_stats.input_width = 1234;
+  local_video_track_source_stats.input_height = 4321;
+  rtc::scoped_refptr<FakeVideoTrackSourceForStats> local_video_track_source =
+      new FakeVideoTrackSourceForStats(local_video_track_source_stats);
+  rtc::scoped_refptr<FakeVideoTrackForStats> local_video_track =
+      FakeVideoTrackForStats::Create(
+          "LocalVideoTrackID",
+          MediaStreamTrackInterface::TrackState::kLive,
+          local_video_track_source);
+  local_stream->AddTrack(local_video_track);
+
+  // Remote video track
+  VideoTrackSourceInterface::Stats remote_video_track_source_stats;
+  remote_video_track_source_stats.input_width = 1234;
+  remote_video_track_source_stats.input_height = 4321;
+  rtc::scoped_refptr<FakeVideoTrackSourceForStats> remote_video_track_source =
+      new FakeVideoTrackSourceForStats(remote_video_track_source_stats);
+  rtc::scoped_refptr<FakeVideoTrackForStats> remote_video_track =
+      FakeVideoTrackForStats::Create(
+          "RemoteVideoTrackID",
+          MediaStreamTrackInterface::TrackState::kEnded,
+          remote_video_track_source);
+  remote_stream->AddTrack(remote_video_track);
+
+  rtc::scoped_refptr<const RTCStatsReport> report = GetStatsReport();
+
+  RTCMediaStreamStats expected_local_stream(
+      "RTCMediaStream_LocalStreamLabel", report->timestamp_us());
+  expected_local_stream.stream_identifier = local_stream->label();
+  expected_local_stream.track_ids = std::vector<std::string>();
+  expected_local_stream.track_ids->push_back(
+      "RTCMediaStreamTrack_LocalVideoTrackID");
+  EXPECT_TRUE(report->Get(expected_local_stream.id()));
+  EXPECT_EQ(expected_local_stream,
+            report->Get(expected_local_stream.id())->cast_to<
+                RTCMediaStreamStats>());
+
+  RTCMediaStreamStats expected_remote_stream(
+      "RTCMediaStream_RemoteStreamLabel", report->timestamp_us());
+  expected_remote_stream.stream_identifier = remote_stream->label();
+  expected_remote_stream.track_ids = std::vector<std::string>();
+  expected_remote_stream.track_ids->push_back(
+      "RTCMediaStreamTrack_RemoteVideoTrackID");
+  EXPECT_TRUE(report->Get(expected_remote_stream.id()));
+  EXPECT_EQ(expected_remote_stream,
+            report->Get(expected_remote_stream.id())->cast_to<
+                RTCMediaStreamStats>());
+
+  RTCMediaStreamTrackStats expected_local_video_track(
+      "RTCMediaStreamTrack_LocalVideoTrackID", report->timestamp_us());
+  expected_local_video_track.track_identifier = local_video_track->id();
+  expected_local_video_track.remote_source = false;
+  expected_local_video_track.ended = false;
+  expected_local_video_track.detached = false;
+  expected_local_video_track.frame_width = 1234;
+  expected_local_video_track.frame_height = 4321;
+  EXPECT_TRUE(report->Get(expected_local_video_track.id()));
+  EXPECT_EQ(expected_local_video_track,
+            report->Get(expected_local_video_track.id())->cast_to<
+                RTCMediaStreamTrackStats>());
+
+  RTCMediaStreamTrackStats expected_remote_video_track(
+      "RTCMediaStreamTrack_RemoteVideoTrackID", report->timestamp_us());
+  expected_remote_video_track.track_identifier = remote_video_track->id();
+  expected_remote_video_track.remote_source = true;
+  expected_remote_video_track.ended = true;
+  expected_remote_video_track.detached = false;
+  expected_remote_video_track.frame_width = 1234;
+  expected_remote_video_track.frame_height = 4321;
+  EXPECT_TRUE(report->Get(expected_remote_video_track.id()));
+  EXPECT_EQ(expected_remote_video_track,
+            report->Get(expected_remote_video_track.id())->cast_to<
+                RTCMediaStreamTrackStats>());
+}
+
 TEST_F(RTCStatsCollectorTest, CollectRTCInboundRTPStreamStats_Audio) {
   MockVoiceMediaChannel* voice_media_channel = new MockVoiceMediaChannel();
   cricket::VoiceChannel voice_channel(
diff --git a/webrtc/api/stats/rtcstats_objects.h b/webrtc/api/stats/rtcstats_objects.h
index 4788a14..3263379 100644
--- a/webrtc/api/stats/rtcstats_objects.h
+++ b/webrtc/api/stats/rtcstats_objects.h
@@ -183,6 +183,65 @@
   const char* type() const override;
 };
 
+// https://w3c.github.io/webrtc-stats/#msstats-dict*
+// TODO(hbos): Finish implementation. Tracking bug crbug.com/660827
+class RTCMediaStreamStats final : public RTCStats {
+ public:
+  WEBRTC_RTCSTATS_DECL();
+
+  RTCMediaStreamStats(const std::string& id, int64_t timestamp_us);
+  RTCMediaStreamStats(std::string&& id, int64_t timestamp_us);
+  RTCMediaStreamStats(const RTCMediaStreamStats& other);
+  ~RTCMediaStreamStats() override;
+
+  RTCStatsMember<std::string> stream_identifier;
+  RTCStatsMember<std::vector<std::string>> track_ids;
+};
+
+// https://w3c.github.io/webrtc-stats/#mststats-dict*
+// TODO(hbos): Finish implementation. Tracking bug crbug.com/659137
+class RTCMediaStreamTrackStats final : public RTCStats {
+ public:
+  WEBRTC_RTCSTATS_DECL();
+
+  RTCMediaStreamTrackStats(const std::string& id, int64_t timestamp_us);
+  RTCMediaStreamTrackStats(std::string&& id, int64_t timestamp_us);
+  RTCMediaStreamTrackStats(const RTCMediaStreamTrackStats& other);
+  ~RTCMediaStreamTrackStats() override;
+
+  RTCStatsMember<std::string> track_identifier;
+  RTCStatsMember<bool> remote_source;
+  RTCStatsMember<bool> ended;
+  // TODO(hbos): |RTCStatsCollector| does not return stats for detached tracks.
+  // crbug.com/659137
+  RTCStatsMember<bool> detached;
+  // TODO(hbos): Not collected by |RTCStatsCollector|. crbug.com/659137
+  RTCStatsMember<std::vector<std::string>> ssrc_ids;
+  // Video-only members
+  RTCStatsMember<uint32_t> frame_width;
+  RTCStatsMember<uint32_t> frame_height;
+  // TODO(hbos): Not collected by |RTCStatsCollector|. crbug.com/659137
+  RTCStatsMember<double> frames_per_second;
+  // TODO(hbos): Not collected by |RTCStatsCollector|. crbug.com/659137
+  RTCStatsMember<uint32_t> frames_sent;
+  // TODO(hbos): Not collected by |RTCStatsCollector|. crbug.com/659137
+  RTCStatsMember<uint32_t> frames_received;
+  // TODO(hbos): Not collected by |RTCStatsCollector|. crbug.com/659137
+  RTCStatsMember<uint32_t> frames_decoded;
+  // TODO(hbos): Not collected by |RTCStatsCollector|. crbug.com/659137
+  RTCStatsMember<uint32_t> frames_dropped;
+  // TODO(hbos): Not collected by |RTCStatsCollector|. crbug.com/659137
+  RTCStatsMember<uint32_t> frames_corrupted;
+  // TODO(hbos): Not collected by |RTCStatsCollector|. crbug.com/659137
+  RTCStatsMember<uint32_t> partial_frames_lost;
+  // TODO(hbos): Not collected by |RTCStatsCollector|. crbug.com/659137
+  RTCStatsMember<uint32_t> full_frames_lost;
+  // Audio-only members
+  RTCStatsMember<double> audio_level;
+  RTCStatsMember<double> echo_return_loss;
+  RTCStatsMember<double> echo_return_loss_enhancement;
+};
+
 // https://w3c.github.io/webrtc-stats/#pcstats-dict*
 // TODO(hbos): Finish implementation. Tracking bug crbug.com/636818
 class RTCPeerConnectionStats final : public RTCStats {
diff --git a/webrtc/api/test/mock_peerconnection.h b/webrtc/api/test/mock_peerconnection.h
index 50e6618..7cc2b0d 100644
--- a/webrtc/api/test/mock_peerconnection.h
+++ b/webrtc/api/test/mock_peerconnection.h
@@ -28,6 +28,10 @@
   MockPeerConnection()
       : rtc::RefCountedObject<webrtc::PeerConnection>(
             new FakePeerConnectionFactory()) {}
+  MOCK_METHOD0(local_streams,
+               rtc::scoped_refptr<StreamCollectionInterface>());
+  MOCK_METHOD0(remote_streams,
+               rtc::scoped_refptr<StreamCollectionInterface>());
   MOCK_METHOD0(session, WebRtcSession*());
   MOCK_CONST_METHOD0(sctp_data_channels,
                      const std::vector<rtc::scoped_refptr<DataChannel>>&());