Bug 1359775 - Part 1 - add RTCRtpContributingSourceStats;r?jib
Still left TODO:
* add an aboutWebrtc.js section
* write tests
MozReview-Commit-ID: DwFxq19KWeu
--- 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)) {