Bug 1359775 - Part 1 - add RTCRtpContributingSourceStats;r?jib draft
authorNico Grunbaum
Wed, 26 Apr 2017 04:27:13 -0700
changeset 583830 03c2d2ee9aaba4d6cfa054c693e5aa91a2aa13aa
parent 582957 c18a711575f5559d41fbe7eb2eff9d1ffea02a24
child 630226 62417a4368f4284cd9ed6b21ea5906d8fb61bb60
push id60573
push userna-g@nostrum.com
push dateWed, 24 May 2017 18:15:58 +0000
reviewersjib
bugs1359775
milestone55.0a1
Bug 1359775 - Part 1 - add RTCRtpContributingSourceStats;r?jib 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)) {