Bug 1359775 - Part 1 - add RTCRtpContributingSourceStats;r=jib,smaug
authorNico Grunbaum
Wed, 26 Apr 2017 04:27:13 -0700
changeset 409060 aaf62f94cb345c8f5d391842e63e2cf197a770d7
parent 409059 12875bf75bd1b09b4d358ffe055283fb28a09e6f
child 409061 bf1c34370ad8cbc8b8ac78eebc293266109012c6
push id7391
push usermtabara@mozilla.com
push dateMon, 12 Jun 2017 13:08:53 +0000
treeherdermozilla-beta@2191d7f87e2e [default view] [failures only]
perfherder[talos] [build metrics] [platform microbench] (compared to previous push)
reviewersjib, smaug
bugs1359775
milestone55.0a1
first release with
nightly linux32
nightly linux64
nightly mac
nightly win32
nightly win64
last release without
nightly linux32
nightly linux64
nightly mac
nightly win32
nightly win64
Bug 1359775 - Part 1 - add RTCRtpContributingSourceStats;r=jib,smaug Still left TODO: * add an aboutWebrtc.js section * write tests MozReview-Commit-ID: DwFxq19KWeu
dom/media/RTCStatsReport.jsm
dom/media/tests/mochitest/test_peerConnection_stats.html
dom/media/webrtc/WebrtcGlobal.h
dom/webidl/RTCStatsReport.webidl
media/webrtc/signaling/src/mediapipeline/MediaPipeline.cpp
media/webrtc/signaling/src/mediapipeline/MediaPipeline.h
media/webrtc/signaling/src/peerconnection/PeerConnectionImpl.cpp
--- a/dom/media/RTCStatsReport.jsm
+++ b/dom/media/RTCStatsReport.jsm
@@ -10,16 +10,17 @@ function convertToRTCStatsReport(dict) {
   function appendStats(stats, report) {
     stats.forEach(function(stat) {
         report[stat.id] = stat;
       });
   }
   let report = {};
   appendStats(dict.inboundRTPStreamStats, report);
   appendStats(dict.outboundRTPStreamStats, report);
+  appendStats(dict.rtpContributingSourceStats, report);
   appendStats(dict.mediaStreamTrackStats, report);
   appendStats(dict.mediaStreamStats, report);
   appendStats(dict.transportStats, report);
   appendStats(dict.iceComponentStats, report);
   appendStats(dict.iceCandidatePairStats, report);
   appendStats(dict.iceCandidateStats, report);
   appendStats(dict.codecStats, report);
   return report;
--- a/dom/media/tests/mochitest/test_peerConnection_stats.html
+++ b/dom/media/tests/mochitest/test_peerConnection_stats.html
@@ -30,16 +30,17 @@ var statsExpectedByType = {
     optional: ["remoteId", "nackCount",],
     localVideoOnly: ["droppedFrames", "bitrateMean", "bitrateStdDev",
       "framerateMean", "framerateStdDev", "framesEncoded", "firCount",
       "pliCount",],
     unimplemented: ["mediaTrackId", "transportId", "codecId",
       "sliCount", "qpSum", "targetBitrate",],
     deprecated: [],
   },
+  "csrc": { skip: true },
   "codec": { skip: true },
   "peer-connection": { skip: true },
   "data-channel": { skip: true },
   "track": { skip: true },
   "transport": { skip: true },
   "candidate-pair": { skip : true },
   "local-candidate": { skip: true },
   "remote-candidate": { skip: true },
--- a/dom/media/webrtc/WebrtcGlobal.h
+++ b/dom/media/webrtc/WebrtcGlobal.h
@@ -110,16 +110,17 @@ struct ParamTraits<mozilla::dom::RTCStat
     WriteParam(aMsg, aParam.mMediaStreamTrackStats);
     WriteParam(aMsg, aParam.mOutboundRTPStreamStats);
     WriteParam(aMsg, aParam.mPcid);
     WriteParam(aMsg, aParam.mRemoteSdp);
     WriteParam(aMsg, aParam.mTimestamp);
     WriteParam(aMsg, aParam.mIceRestarts);
     WriteParam(aMsg, aParam.mIceRollbacks);
     WriteParam(aMsg, aParam.mTransportStats);
+    WriteParam(aMsg, aParam.mRtpContributingSourceStats);
   }
 
   static bool Read(const Message* aMsg, PickleIterator* aIter, paramType* aResult)
   {
     if (!ReadParam(aMsg, aIter, &(aResult->mClosed)) ||
         !ReadParam(aMsg, aIter, &(aResult->mCodecStats)) ||
         !ReadParam(aMsg, aIter, &(aResult->mIceCandidatePairStats)) ||
         !ReadParam(aMsg, aIter, &(aResult->mIceCandidateStats)) ||
@@ -129,17 +130,18 @@ struct ParamTraits<mozilla::dom::RTCStat
         !ReadParam(aMsg, aIter, &(aResult->mMediaStreamStats)) ||
         !ReadParam(aMsg, aIter, &(aResult->mMediaStreamTrackStats)) ||
         !ReadParam(aMsg, aIter, &(aResult->mOutboundRTPStreamStats)) ||
         !ReadParam(aMsg, aIter, &(aResult->mPcid)) ||
         !ReadParam(aMsg, aIter, &(aResult->mRemoteSdp)) ||
         !ReadParam(aMsg, aIter, &(aResult->mTimestamp)) ||
         !ReadParam(aMsg, aIter, &(aResult->mIceRestarts)) ||
         !ReadParam(aMsg, aIter, &(aResult->mIceRollbacks)) ||
-        !ReadParam(aMsg, aIter, &(aResult->mTransportStats))) {
+        !ReadParam(aMsg, aIter, &(aResult->mTransportStats)) ||
+        !ReadParam(aMsg, aIter, &(aResult->mRtpContributingSourceStats))) {
       return false;
     }
 
     return true;
   }
 };
 
 typedef mozilla::dom::RTCStats RTCStats;
@@ -499,11 +501,34 @@ struct ParamTraits<mozilla::dom::RTCMedi
         !ReadRTCStats(aMsg, aIter, aResult)) {
       return false;
     }
 
     return true;
   }
 };
 
+template<>
+struct ParamTraits<mozilla::dom::RTCRTPContributingSourceStats>
+{
+  typedef mozilla::dom::RTCRTPContributingSourceStats paramType;
+
+  static void Write(Message* aMsg, const paramType& aParam)
+  {
+    WriteParam(aMsg, aParam.mContributorSsrc);
+    WriteParam(aMsg, aParam.mInboundRtpStreamId);
+    WriteRTCStats(aMsg, aParam);
+  }
+
+  static bool Read(const Message* aMsg, PickleIterator* aIter, paramType* aResult)
+  {
+    if (!ReadParam(aMsg, aIter, &(aResult->mContributorSsrc)) ||
+        !ReadParam(aMsg, aIter, &(aResult->mInboundRtpStreamId)) ||
+        !ReadRTCStats(aMsg, aIter, aResult)) {
+      return false;
+    }
+    return true;
+  }
+};
+
 } // namespace ipc
 
 #endif  // _WEBRTC_GLOBAL_H_
--- a/dom/webidl/RTCStatsReport.webidl
+++ b/dom/webidl/RTCStatsReport.webidl
@@ -6,16 +6,17 @@
  * The origin of this IDL file is
  * http://dev.w3.org/2011/webrtc/editor/webrtc.html#rtcstatsreport-object
  * http://www.w3.org/2011/04/webrtc/wiki/Stats
  */
 
 enum RTCStatsType {
   "inbound-rtp",
   "outbound-rtp",
+  "csrc",
   "session",
   "track",
   "transport",
   "candidate-pair",
   "local-candidate",
   "remote-candidate"
 };
 
@@ -90,16 +91,21 @@ dictionary RTCMediaStreamTrackStats : RT
   double echoReturnLossEnhancement; // as above, section 3.15
 };
 
 dictionary RTCMediaStreamStats : RTCStats {
   DOMString streamIdentifier;     // stream.id property
   sequence<DOMString> trackIds;   // Note: stats object ids, not track.id
 };
 
+dictionary RTCRTPContributingSourceStats : RTCStats {
+  unsigned long contributorSsrc;
+  DOMString     inboundRtpStreamId;
+};
+
 dictionary RTCTransportStats: RTCStats {
   unsigned long bytesSent;
   unsigned long bytesReceived;
 };
 
 dictionary RTCIceComponentStats : RTCStats {
   DOMString transportId;
   long component;
@@ -152,32 +158,33 @@ dictionary RTCCodecStats : RTCStats {
   unsigned long channels;          // 2=stereo, missing for most other cases.
   DOMString parameters;            // From SDP description line
 };
 
 // This is the internal representation of the report in this implementation
 // to be received from c++
 
 dictionary RTCStatsReportInternal {
-  DOMString                           pcid = "";
-  sequence<RTCInboundRTPStreamStats>  inboundRTPStreamStats;
-  sequence<RTCOutboundRTPStreamStats> outboundRTPStreamStats;
-  sequence<RTCMediaStreamTrackStats>  mediaStreamTrackStats;
-  sequence<RTCMediaStreamStats>       mediaStreamStats;
-  sequence<RTCTransportStats>         transportStats;
-  sequence<RTCIceComponentStats>      iceComponentStats;
-  sequence<RTCIceCandidatePairStats>  iceCandidatePairStats;
-  sequence<RTCIceCandidateStats>      iceCandidateStats;
-  sequence<RTCCodecStats>             codecStats;
-  DOMString                           localSdp;
-  DOMString                           remoteSdp;
-  DOMHighResTimeStamp                 timestamp;
-  unsigned long                       iceRestarts;
-  unsigned long                       iceRollbacks;
-  boolean                             closed; // Is the PC now closed
+  DOMString                               pcid = "";
+  sequence<RTCInboundRTPStreamStats>      inboundRTPStreamStats;
+  sequence<RTCOutboundRTPStreamStats>     outboundRTPStreamStats;
+  sequence<RTCRTPContributingSourceStats> rtpContributingSourceStats;
+  sequence<RTCMediaStreamTrackStats>      mediaStreamTrackStats;
+  sequence<RTCMediaStreamStats>           mediaStreamStats;
+  sequence<RTCTransportStats>             transportStats;
+  sequence<RTCIceComponentStats>          iceComponentStats;
+  sequence<RTCIceCandidatePairStats>      iceCandidatePairStats;
+  sequence<RTCIceCandidateStats>          iceCandidateStats;
+  sequence<RTCCodecStats>                 codecStats;
+  DOMString                               localSdp;
+  DOMString                               remoteSdp;
+  DOMHighResTimeStamp                     timestamp;
+  unsigned long                           iceRestarts;
+  unsigned long                           iceRollbacks;
+  boolean                                 closed; // Is the PC now closed
 };
 
 [Pref="media.peerconnection.enabled",
 // TODO: Use MapClass here once it's available (Bug 928114)
 // MapClass(DOMString, object)
  JSImplementation="@mozilla.org/dom/rtcstatsreport;1"]
 interface RTCStatsReport {
   readonly maplike<DOMString, object>;
--- a/media/webrtc/signaling/src/mediapipeline/MediaPipeline.cpp
+++ b/media/webrtc/signaling/src/mediapipeline/MediaPipeline.cpp
@@ -37,30 +37,33 @@
 #include "RtpLogger.h"
 #include "databuffer.h"
 #include "transportflow.h"
 #include "transportlayer.h"
 #include "transportlayerdtls.h"
 #include "transportlayerice.h"
 #include "runnable_utils.h"
 #include "libyuv/convert.h"
+#include "mozilla/dom/RTCStatsReportBinding.h"
 #include "mozilla/SharedThreadPool.h"
 #include "mozilla/PeerIdentity.h"
 #include "mozilla/Preferences.h"
 #include "mozilla/TaskQueue.h"
 #include "mozilla/gfx/Point.h"
 #include "mozilla/gfx/Types.h"
 #include "mozilla/UniquePtr.h"
 #include "mozilla/UniquePtrExtensions.h"
 #include "mozilla/Sprintf.h"
 #include "mozilla/SizePrintfMacros.h"
 
 #include "webrtc/common_types.h"
 #include "webrtc/common_video/libyuv/include/webrtc_libyuv.h"
 
+#include "nsThreadUtils.h"
+
 #include "logging.h"
 
 // Max size given stereo is 480*2*2 = 1920 (10ms of 16-bits stereo audio at
 // 48KHz)
 #define AUDIO_SAMPLE_BUFFER_MAX 480*2*2
 static_assert((WEBRTC_DEFAULT_SAMPLE_RATE/100)*sizeof(uint16_t) * 2
                <= AUDIO_SAMPLE_BUFFER_MAX,
                "AUDIO_SAMPLE_BUFFER_MAX is not large enough");
@@ -604,17 +607,17 @@ MediaPipeline::MediaPipeline(const std::
     rtcp_packets_sent_(0),
     rtp_packets_received_(0),
     rtcp_packets_received_(0),
     rtp_bytes_sent_(0),
     rtp_bytes_received_(0),
     pc_(pc),
     description_(),
     filter_(filter),
-    rtp_parser_(webrtc::RtpHeaderParser::Create()) {
+    rtp_parser_(webrtc::RtpHeaderParser::Create()){
   // To indicate rtcp-mux rtcp_transport should be nullptr.
   // Therefore it's an error to send in the same flow for
   // both rtp and rtcp.
   MOZ_ASSERT(rtp_transport != rtcp_transport);
 
   // PipelineTransport() will access this->sts_thread_; moved here for safety
   transport_ = new PipelineTransport(this);
 }
@@ -764,16 +767,32 @@ MediaPipeline::AddRIDFilter_m(const std:
 
 void
 MediaPipeline::AddRIDFilter_s(const std::string& rid)
 {
   filter_ = new MediaPipelineFilter;
   filter_->AddRemoteRtpStreamId(rid);
 }
 
+void
+MediaPipeline::GetContributingSourceStats(
+    const nsString& aInboundRtpStreamId,
+    FallibleTArray<dom::RTCRTPContributingSourceStats>& aArr) const
+{
+  // Get the expiry from now
+  DOMHighResTimeStamp expiry = RtpCSRCStats::GetExpiryFromTime(GetNow());
+  for (auto info : csrc_stats_) {
+    if (!info.second.Expired(expiry)) {
+      RTCRTPContributingSourceStats stats;
+      info.second.GetWebidlInstance(stats, aInboundRtpStreamId);
+      aArr.AppendElement(stats, fallible);
+    }
+  }
+}
+
 void MediaPipeline::StateChange(TransportFlow *flow, TransportLayer::State state) {
   TransportInfo* info = GetTransportInfo_s(flow);
   MOZ_ASSERT(info);
 
   if (state == TransportLayer::TS_OPEN) {
     MOZ_MTLOG(ML_INFO, "Flow is ready");
     TransportReady_s(*info);
   } else if (state == TransportLayer::TS_CLOSED ||
@@ -1058,16 +1077,54 @@ void MediaPipeline::RtpPacketReceived(Tr
   if (!rtp_parser_->Parse(data, len, &header)) {
     return;
   }
 
   if (filter_ && !filter_->Filter(header)) {
     return;
   }
 
+  // Make sure to only get the time once, and only if we need it by
+  // using getTimestamp() for access
+  DOMHighResTimeStamp now = 0.0;
+  bool hasTime = false;
+
+  // Remove expired RtpCSRCStats
+  if (!csrc_stats_.empty()) {
+    if (!hasTime) {
+      now = GetNow();
+      hasTime = true;
+    }
+    auto expiry = RtpCSRCStats::GetExpiryFromTime(now);
+    for (auto p = csrc_stats_.begin(); p != csrc_stats_.end();) {
+      if (p->second.Expired(expiry)) {
+        p = csrc_stats_.erase(p);
+        continue;
+      }
+      p++;
+    }
+  }
+
+  // Add new RtpCSRCStats
+  if (header.numCSRCs) {
+    for (auto i = 0; i < header.numCSRCs; i++) {
+      if (!hasTime) {
+        now = GetNow();
+        hasTime = true;
+      }
+      auto csrcInfo = csrc_stats_.find(header.arrOfCSRCs[i]);
+      if (csrcInfo == csrc_stats_.end()) {
+        csrc_stats_.insert(std::make_pair(header.arrOfCSRCs[i],
+            RtpCSRCStats(header.arrOfCSRCs[i],now)));
+      } else {
+        csrcInfo->second.SetTimestamp(now);
+      }
+    }
+  }
+
   // Make a copy rather than cast away constness
   auto inner_data = MakeUnique<unsigned char[]>(len);
   memcpy(inner_data.get(), data, len);
   int out_len = 0;
   nsresult res = rtp_.recv_srtp_->UnprotectRtp(inner_data.get(),
                                                len, len, &out_len);
   if (!NS_SUCCEEDED(res)) {
     char tmp[16];
@@ -2317,9 +2374,40 @@ nsresult MediaPipelineReceiveVideo::Init
   return MediaPipelineReceive::Init();
 }
 
 void MediaPipelineReceiveVideo::SetPrincipalHandle_m(const PrincipalHandle& principal_handle)
 {
   listener_->SetPrincipalHandle_m(principal_handle);
 }
 
+DOMHighResTimeStamp MediaPipeline::GetNow() {
+  return webrtc::Clock::GetRealTimeClock()->TimeInMilliseconds();
+}
+
+DOMHighResTimeStamp
+MediaPipeline::RtpCSRCStats::GetExpiryFromTime(
+    const DOMHighResTimeStamp aTime) {
+  // DOMHighResTimeStamp is a unit measured in ms
+  return aTime - EXPIRY_TIME_MILLISECONDS;
+}
+
+MediaPipeline::RtpCSRCStats::RtpCSRCStats(const uint32_t aCsrc,
+                                          const DOMHighResTimeStamp aTime)
+  : mCsrc(aCsrc)
+  , mTimestamp(aTime) {}
+
+void
+MediaPipeline::RtpCSRCStats::GetWebidlInstance(
+    dom::RTCRTPContributingSourceStats& aWebidlObj,
+    const nsString &aInboundRtpStreamId) const
+{
+  nsString statId = NS_LITERAL_STRING("csrc_") + aInboundRtpStreamId;
+  statId.AppendLiteral("_");
+  statId.AppendInt(mCsrc);
+  aWebidlObj.mId.Construct(statId);
+  aWebidlObj.mType.Construct(RTCStatsType::Csrc);
+  aWebidlObj.mTimestamp.Construct(mTimestamp);
+  aWebidlObj.mContributorSsrc.Construct(mCsrc);
+  aWebidlObj.mInboundRtpStreamId.Construct(aInboundRtpStreamId);
+}
+
 }  // end namespace
--- a/media/webrtc/signaling/src/mediapipeline/MediaPipeline.h
+++ b/media/webrtc/signaling/src/mediapipeline/MediaPipeline.h
@@ -3,16 +3,18 @@
  * License, v. 2.0. If a copy of the MPL was not distributed with this file,
  * You can obtain one at http://mozilla.org/MPL/2.0/. */
 
 // Original author: ekr@rtfm.com
 
 #ifndef mediapipeline_h__
 #define mediapipeline_h__
 
+#include <map>
+
 #include "sigslot.h"
 
 #include "MediaConduitInterface.h"
 #include "mozilla/ReentrantMonitor.h"
 #include "mozilla/Atomics.h"
 #include "SrtpFlow.h"
 #include "databuffer.h"
 #include "runnable_utils.h"
@@ -31,16 +33,17 @@ class nsIPrincipal;
 namespace mozilla {
 class MediaPipelineFilter;
 class PeerIdentity;
 class AudioProxyThread;
 class VideoFrameConverter;
 
 namespace dom {
   class MediaStreamTrack;
+  struct RTCRTPContributingSourceStats;
 } // namespace dom
 
 class SourceMediaStream;
 
 // A class that represents the pipeline of audio and video
 // The dataflow looks like:
 //
 // TRANSMIT
@@ -127,16 +130,55 @@ class MediaPipeline : public sigslot::ha
   virtual const std::string& trackid() const { return track_id_; }
   virtual int level() const { return level_; }
   virtual bool IsVideo() const = 0;
 
   bool IsDoingRtcpMux() const {
     return (rtp_.type_ == MUX);
   }
 
+  class RtpCSRCStats {
+  public:
+    // Gets an expiration time for CRC info given a reference time,
+    //   this reference time would normally be the time of calling.
+    //   This value can then be used to check if a RtpCSRCStats
+    //   has expired via Expired(...)
+    static DOMHighResTimeStamp
+    GetExpiryFromTime(const DOMHighResTimeStamp aTime);
+
+    RtpCSRCStats(const uint32_t aCsrc,
+                 const DOMHighResTimeStamp aTime);
+    ~RtpCSRCStats() {};
+    // Initialize a webidl representation suitable for adding to a report.
+    //   This assumes that the webidl object is empty.
+    // @param aWebidlObj the webidl binding object to popluate
+    // @param aRtpInboundStreamId the associated RTCInboundRTPStreamStats.id
+    void
+    GetWebidlInstance(dom::RTCRTPContributingSourceStats& aWebidlObj,
+                             const nsString &aInboundRtpStreamId) const;
+    void SetTimestamp(const DOMHighResTimeStamp aTime) { mTimestamp = aTime; }
+    // Check if the RtpCSRCStats has expired, checks against a
+    //   given expiration time.
+    bool Expired(const DOMHighResTimeStamp aExpiry) const {
+      return mTimestamp < aExpiry;
+    }
+  private:
+    static const double constexpr EXPIRY_TIME_MILLISECONDS = 10 * 1000;
+    uint32_t mCsrc;
+    DOMHighResTimeStamp mTimestamp;
+  };
+
+  // Gets the gathered contributing source stats for the last expiration period.
+  // @param aId the stream id to use for populating inboundRtpStreamId field
+  // @param aArr the array to append the stats objects to
+  void
+  GetContributingSourceStats(
+      const nsString& aInboundStreamId,
+      FallibleTArray<dom::RTCRTPContributingSourceStats>& aArr) const;
+
   int32_t rtp_packets_sent() const { return rtp_packets_sent_; }
   int64_t rtp_bytes_sent() const { return rtp_bytes_sent_; }
   int32_t rtcp_packets_sent() const { return rtcp_packets_sent_; }
   int32_t rtp_packets_received() const { return rtp_packets_received_; }
   int64_t rtp_bytes_received() const { return rtp_bytes_received_; }
   int32_t rtcp_packets_received() const { return rtcp_packets_received_; }
 
   MediaSessionConduit *Conduit() const { return conduit_; }
@@ -264,25 +306,30 @@ class MediaPipeline : public sigslot::ha
   // Build into TransportInfo?
   int32_t rtp_packets_sent_;
   int32_t rtcp_packets_sent_;
   int32_t rtp_packets_received_;
   int32_t rtcp_packets_received_;
   int64_t rtp_bytes_sent_;
   int64_t rtp_bytes_received_;
 
+  // Only safe to access from STS thread.
+  std::map<uint32_t, RtpCSRCStats> csrc_stats_;
+
   // Written on Init. Read on STS thread.
   std::string pc_;
   std::string description_;
 
   // Written on Init, all following accesses are on the STS thread.
   nsAutoPtr<MediaPipelineFilter> filter_;
   nsAutoPtr<webrtc::RtpHeaderParser> rtp_parser_;
 
  private:
+  // Gets the current time as a DOMHighResTimeStamp
+  static DOMHighResTimeStamp GetNow();
   nsresult Init_s();
 
   bool IsRtp(const unsigned char *data, size_t len);
 };
 
 class ConduitDeleteEvent: public Runnable
 {
 public:
--- a/media/webrtc/signaling/src/peerconnection/PeerConnectionImpl.cpp
+++ b/media/webrtc/signaling/src/peerconnection/PeerConnectionImpl.cpp
@@ -2085,16 +2085,17 @@ PeerConnectionImpl::GetTimeSinceEpoch(DO
   *result = perf->Now() + perf->Timing()->NavigationStart();
   return NS_OK;
 }
 
 class RTCStatsReportInternalConstruct : public RTCStatsReportInternal {
 public:
   RTCStatsReportInternalConstruct(const nsString &pcid, DOMHighResTimeStamp now) {
     mPcid = pcid;
+    mRtpContributingSourceStats.Construct();
     mInboundRTPStreamStats.Construct();
     mOutboundRTPStreamStats.Construct();
     mMediaStreamTrackStats.Construct();
     mMediaStreamStats.Construct();
     mTransportStats.Construct();
     mIceComponentStats.Construct();
     mIceCandidatePairStats.Construct();
     mIceCandidateStats.Construct();
@@ -3813,16 +3814,19 @@ PeerConnectionImpl::ExecuteStatsQuery_s(
             s.mFramerateStdDev.Construct(framerateStdDev);
             s.mBitrateMean.Construct(bitrateMean);
             s.mBitrateStdDev.Construct(bitrateStdDev);
             s.mDiscardedPackets.Construct(discardedPackets);
           }
         }
         query->report->mInboundRTPStreamStats.Value().AppendElement(s,
                                                                     fallible);
+        // Fill in Contributing Source statistics
+        mp.GetContributingSourceStats(localId,
+            query->report->mRtpContributingSourceStats.Value());
         break;
       }
     }
 
     if (!query->grabAllLevels) {
       // If we're grabbing all levels, that means we want datachannels too,
       // which don't have pipelines.
       if (query->iceCtx->GetStream(p)) {