Bug 1393095 - remote audio receiver stats missing;r?dminor, jesup
MozReview-Commit-ID: 9izPPOqybcK
--- a/media/webrtc/signaling/src/media-conduit/AudioConduit.cpp
+++ b/media/webrtc/signaling/src/media-conduit/AudioConduit.cpp
@@ -208,38 +208,29 @@ NTPtoDOMHighResTimeStamp(uint32_t ntpHig
}
bool WebrtcAudioConduit::GetRTCPReceiverReport(DOMHighResTimeStamp* timestamp,
uint32_t* jitterMs,
uint32_t* packetsReceived,
uint64_t* bytesReceived,
uint32_t* cumulativeLost,
int32_t* rttMs) {
-
- // We get called on STS thread... the proxy thread-checks to MainThread
- // I removed the check, since GetRTCPStatistics ends up going down to
- // methods (rtp_receiver_->SSRC() and rtp_receive_statistics_->GetStatistician()
- // and GetStatistics that internally lock, so we're ok here without a thread-check.
- webrtc::CallStatistics call_stats = mChannelProxy->GetRTCPStatistics();
- *bytesReceived = call_stats.bytesReceived;
- *packetsReceived = call_stats.packetsReceived;
- *cumulativeLost = call_stats.cumulativeLost;
- *rttMs = call_stats.rttMs;
-
- unsigned int averageJitterMs;
- unsigned int maxJitterMs;
- unsigned int discardedPackets;
- unsigned int cumulative;
- mChannelProxy->GetRTPStatistics(averageJitterMs, maxJitterMs, discardedPackets, cumulative);
- *jitterMs = averageJitterMs;
-
- // XXX Note: timestamp is not correct per the spec... should be time the
- // rtcp was received (remote) or sent (local)
- *timestamp = webrtc::Clock::GetRealTimeClock()->TimeInMilliseconds();
- return true;
+ double fractionLost;
+ int64_t timestampTmp;
+ int64_t rttMsTmp;
+ bool res = mChannelProxy->GetRTCPReceiverStatistics(×tampTmp,
+ jitterMs,
+ cumulativeLost,
+ packetsReceived,
+ bytesReceived,
+ &fractionLost,
+ &rttMsTmp);
+ *timestamp = static_cast<double>(timestampTmp);
+ *rttMs = static_cast<uint32_t>(rttMsTmp);
+ return res;
}
bool WebrtcAudioConduit::GetRTCPSenderReport(DOMHighResTimeStamp* timestamp,
unsigned int* packetsSent,
uint64_t* bytesSent) {
webrtc::RTCPSenderInfo senderInfo;
webrtc::RtpRtcp * rtpRtcpModule;
webrtc::RtpReceiver * rtp_receiver;
--- a/media/webrtc/trunk/webrtc/voice_engine/channel.cc
+++ b/media/webrtc/trunk/webrtc/voice_engine/channel.cc
@@ -308,45 +308,193 @@ class StatisticsProxy : public RtcpStati
}
}
void CNameChanged(const char* cname, uint32_t ssrc) override {}
void SetSSRC(uint32_t ssrc) {
rtc::CritScope cs(&stats_lock_);
ssrc_ = ssrc;
+ mReceiverReportDerivedStats.clear();
+ mInitialSequenceNumber.reset();
}
ChannelStatistics GetStats() {
rtc::CritScope cs(&stats_lock_);
return stats_;
}
+ // These can be created before reports are received so that information
+ // needed to derive certain stats (e.g. PacketsReceived) can be stored.
+ class ReceiverReportDerivedStats {
+ public:
+ // Event handler for incoming RTCP Receiver Reports
+ void UpdateWithReceiverReport(const RTCPReportBlock& aReceiverReport,
+ rtc::Optional<uint32_t> initialSequenceNum,
+ int64_t aRoundTripTime,
+ uint32_t aEncoderFrequencyHz,
+ int64_t aReceptionTime)
+ {
+ if (!mFirstExtendedSequenceNumber && initialSequenceNum) {
+ mFirstExtendedSequenceNumber = *initialSequenceNum;
+ }
+ // No initial sequence number available!
+ if (!mFirstExtendedSequenceNumber) {
+ WEBRTC_TRACE(kTraceWarning, kTraceVoice, -1,
+ "ReceiverReportDerivedStats::UpdateWithReceiverReport()"
+ " called before a first sequence number is known to the"
+ " StatisticsProxy");
+ // This is as good a guess as we can get if the initial
+ // sequence number is not known
+ mFirstExtendedSequenceNumber = static_cast<uint32_t>(
+ std::max<int64_t>(0, aReceiverReport.extendedHighSeqNum -
+ aReceiverReport.cumulativeLost));
+ }
+ mReceiverSsrc = aReceiverReport.remoteSSRC;
+ mSenderSsrc = aReceiverReport.sourceSSRC;
+ mLatestHighExtendedSequenceNumber = aReceiverReport.extendedHighSeqNum;
+ mLatestReceiverReportReceptionTime = aReceptionTime;
+ mFractionOfPacketsLostInQ8 = aReceiverReport.fractionLost;
+ mJitterInSamples = aReceiverReport.jitter;
+ mEncoderFrequencyHz = aEncoderFrequencyHz;
+ mCumulativePacketsLost = aReceiverReport.cumulativeLost;
+ mLastSenderReportTimestamp = aReceiverReport.lastSR;
+ mDelaySinceLastSenderReport = aReceiverReport.delaySinceLastSR;
+ mRoundTripTime = aRoundTripTime;
+ }
+ bool HasReceivedReport() { return mFirstReceiverReportReceptionTime; }
+ // This is the SSRC of the entity sending the RTCP Receiver Reports
+ // That is it is the SSRC of the RTP receiver
+ uint32_t mReceiverSsrc = 0;
+ // This is the SSRC of the entity receiving the RTCP Receiver Reports
+ // That is it is the SSRC of the RTP sender
+ uint32_t mSenderSsrc = 0;
+ // Reception time for the RTCP packet containing this data
+ // Only available if an receiver report has been received
+ int64_t mLatestReceiverReportReceptionTime = 0;
+ // Reception time for the first RTCP packet contianing a
+ // Receiver Report with match mReceiverSsrc.
+ int64_t mFirstReceiverReportReceptionTime = 0;
+ // The total number of packets know to be lost
+ uint32_t mCumulativePacketsLost = 0;
+ // The RTP sender must record the first sequence number used
+ // so that number of packets received can be calculated from ...
+ uint32_t mFirstExtendedSequenceNumber = 0;
+ // The most recent sequence number seen by the receiver at the time
+ // Receiver Report was generated
+ uint32_t mLatestHighExtendedSequenceNumber = 0;
+ int64_t mRoundTripTime = 0;
+ // The amount of jitter measured in MS, derived from the
+ // RTCP reported jitter (measured in frames), and the
+ // effective playout frequency.
+ double JitterMs() const {
+ if (!mEncoderFrequencyHz) {
+ if (!mHasWarnedAboutNoFrequency) {
+ mHasWarnedAboutNoFrequency = true;
+ WEBRTC_TRACE(kTraceWarning, kTraceVoice, -1,
+ "ReceiverReportDerivedStats::JitterMs() called before"
+ " the playout frequency is known.");
+ }
+ return 0;
+ }
+ return (mJitterInSamples * 1000) / mEncoderFrequencyHz;
+ }
+ // Fraction of packets lost
+ double FractionOfPacketsLost() const {
+ return (double) mFractionOfPacketsLostInQ8 / 256;
+ }
+ uint32_t PacketsReceived() const {
+ return static_cast<uint32_t>(std::max<int64_t>(0,
+ (int64_t) mLatestHighExtendedSequenceNumber -
+ (mFirstExtendedSequenceNumber + mCumulativePacketsLost)));
+ }
+ private:
+ // The ratio of packets lost to total packets sent expressed
+ // as the dividend in X / 256.
+ uint8_t mFractionOfPacketsLostInQ8 = 0;
+ // Jitter in the RTCP packet is in Time Units,
+ // which is the sample rate of the audio.
+ uint32_t mJitterInSamples = 0;
+ // Use to calculate the jitter
+ uint32_t mEncoderFrequencyHz = 0;
+ // Used to calculate the RTT
+ uint32_t mLastSenderReportTimestamp = 0;
+ // Used to calculate the RTT
+ uint32_t mDelaySinceLastSenderReport = 0;
+ // Only warn about jitter calculation once per instance
+ mutable bool mHasWarnedAboutNoFrequency = false;
+ };
+
void RtcpPacketTypesCounterUpdated(uint32_t ssrc,
const RtcpPacketTypeCounter& packet_counter) override {
rtc::CritScope cs(&stats_lock_);
if (ssrc != ssrc_) {
return;
}
packet_counter_ = packet_counter;
};
- void GetPacketTypeCounter(RtcpPacketTypeCounter& aPacketTypeCounter) {
+ // Called when we receive RTCP receiver reports
+ void OnIncomingReceiverReports(const ReportBlockList & mReceiverReports,
+ const int64_t aRoundTripTime,
+ const int64_t aReceptionTime) {
+ if (!mReceiverReports.empty()) { // Don't lock if we have nothing to do.
+ rtc::CritScope cs(&stats_lock_);
+ for(const auto& report : mReceiverReports) {
+ // Creates a new report if necessary before updating
+ ReceiverReportDerivedStats newStats;
+ mReceiverReportDerivedStats.emplace(report.sourceSSRC, newStats)
+ .first->second.UpdateWithReceiverReport(report,
+ mInitialSequenceNumber,
+ aRoundTripTime,
+ mPlayoutFrequency,
+ aReceptionTime);
+ }
+ }
+ }
+
+ void OnSendCodecFrequencyChanged(uint32_t aFrequency) {
+ rtc::CritScope cs(&stats_lock_);
+ mPlayoutFrequency = aFrequency;
+ }
+
+ void OnInitialSequenceNumberSet(uint32_t aSequenceNumber) {
+ rtc::CritScope cs(&stats_lock_);
+ mInitialSequenceNumber.emplace(aSequenceNumber);
+ mReceiverReportDerivedStats.clear();
+ }
+
+ const rtc::Optional<ReceiverReportDerivedStats>
+ GetReceiverReportDerivedStats(const uint32_t receiverSsrc) const {
+ rtc::CritScope cs(&stats_lock_);
+ const auto& it = mReceiverReportDerivedStats.find(receiverSsrc);
+ if (it != mReceiverReportDerivedStats.end()) {
+ return rtc::Optional<ReceiverReportDerivedStats>(it->second);
+ }
+ return rtc::Optional<ReceiverReportDerivedStats>();
+ }
+ void GetPacketTypeCounter(RtcpPacketTypeCounter& aPacketTypeCounter) {
rtc::CritScope cs(&stats_lock_);
aPacketTypeCounter = packet_counter_;
}
private:
// StatisticsUpdated calls are triggered from threads in the RTP module,
// while GetStats calls can be triggered from the public voice engine API,
// hence synchronization is needed.
rtc::CriticalSection stats_lock_;
uint32_t ssrc_;
ChannelStatistics stats_;
RtcpPacketTypeCounter packet_counter_;
+
+ // receiver report handling, maps ssrc -> stats
+ std::map<uint32_t, ReceiverReportDerivedStats> mReceiverReportDerivedStats;
+ // store initial sender sequence number
+ rtc::Optional<uint32_t> mInitialSequenceNumber;
+ uint32_t mPlayoutFrequency;
};
class VoERtcpObserver : public RtcpBandwidthObserver {
public:
explicit VoERtcpObserver(Channel* owner) : owner_(owner) {}
virtual ~VoERtcpObserver() {}
void OnReceivedEstimatedBitrate(uint32_t bitrate) override {
@@ -386,24 +534,73 @@ class VoERtcpObserver : public RtcpBandw
}
int weighted_fraction_lost = 0;
if (total_number_of_packets > 0) {
weighted_fraction_lost =
(fraction_lost_aggregate + total_number_of_packets / 2) /
total_number_of_packets;
}
owner_->OnIncomingFractionLoss(weighted_fraction_lost);
+ owner_->OnIncomingReceiverReports(report_blocks, rtt, now_ms);
}
private:
Channel* owner_;
// Maps remote side ssrc to extended highest sequence number received.
std::map<uint32_t, uint32_t> extended_max_sequence_number_;
};
+void Channel::OnIncomingReceiverReports(const ReportBlockList& aReportBlocks,
+ const int64_t aRoundTripTime,
+ const int64_t aReceptionTime) {
+ statistics_proxy_->OnIncomingReceiverReports(aReportBlocks,
+ aRoundTripTime,
+ aReceptionTime);
+}
+
+bool Channel::GetRTCPReceiverStatistics(int64_t* timestamp,
+ uint32_t* jitterMs,
+ uint32_t* cumulativeLost,
+ uint32_t* packetsReceived,
+ uint64_t* bytesReceived,
+ double* packetsFractionLost,
+ int64_t* rtt) const {
+ uint32_t ssrc = _rtpRtcpModule->SSRC();
+ const auto& stats = statistics_proxy_->GetReceiverReportDerivedStats(ssrc);
+ if (!stats || !stats->PacketsReceived()) {
+ return false;
+ }
+ *timestamp = stats->mLatestReceiverReportReceptionTime;
+ *jitterMs = stats->JitterMs();
+ *cumulativeLost = stats->mCumulativePacketsLost;
+ *packetsReceived = stats->PacketsReceived();
+ *packetsFractionLost = stats->FractionOfPacketsLost();
+ *rtt = stats->mRoundTripTime;
+
+ // bytesReceived is only an estimate, which we derive from the locally
+ // generated RTCP sender reports, and the remotely genderated receiver
+ // reports.
+ // There is an open issue in the spec as to if this should be included
+ // here where it is only a guess.
+ // https://github.com/w3c/webrtc-stats/issues/241
+ *bytesReceived = 0;
+ if (*packetsReceived) {
+ // GetDataCounters has internal CS lock within RtpSender
+ StreamDataCounters rtpCounters;
+ StreamDataCounters rtxCounters; // unused
+ _rtpRtcpModule->GetSendStreamDataCounters(&rtpCounters, &rtxCounters);
+ uint64_t sentPackets = rtpCounters.transmitted.packets;
+ if (sentPackets) {
+ uint64_t sentBytes = rtpCounters.MediaPayloadBytes();
+ *bytesReceived = sentBytes * (*packetsReceived) / sentPackets;
+ }
+ }
+ return true;
+}
+
int32_t Channel::SendData(FrameType frameType,
uint8_t payloadType,
uint32_t timeStamp,
const uint8_t* payloadData,
size_t payloadSize,
const RTPFragmentationHeader* fragmentation) {
WEBRTC_TRACE(kTraceStream, kTraceVoice, VoEId(_instanceId, _channelId),
"Channel::SendData(frameType=%u, payloadType=%u, timeStamp=%u,"
@@ -972,25 +1169,24 @@ Channel::Channel(int32_t channelId,
seq_num_allocator_proxy_.get();
configuration.transport_feedback_callback = feedback_observer_proxy_.get();
}
configuration.event_log = &(*event_log_proxy_);
configuration.rtt_stats = &(*rtcp_rtt_stats_proxy_);
configuration.retransmission_rate_limiter =
retransmission_rate_limiter_.get();
- configuration.rtcp_packet_type_counter_observer = statistics_proxy_.get();
-
_rtpRtcpModule.reset(RtpRtcp::CreateRtpRtcp(configuration));
_rtpRtcpModule->SetSendingMediaStatus(false);
statistics_proxy_.reset(new StatisticsProxy(_rtpRtcpModule->SSRC()));
rtp_receive_statistics_->RegisterRtcpStatisticsCallback(
- statistics_proxy_.get());
-}
+ statistics_proxy_.get());
+ configuration.rtcp_packet_type_counter_observer = statistics_proxy_.get();
+ }
Channel::~Channel() {
rtp_receive_statistics_->RegisterRtcpStatisticsCallback(NULL);
WEBRTC_TRACE(kTraceMemory, kTraceVoice, VoEId(_instanceId, _channelId),
"Channel::~Channel() - dtor");
if (_outputExternalMedia) {
DeRegisterExternalMediaProcessing(kPlaybackPerChannel);
@@ -1343,17 +1539,17 @@ int32_t Channel::SetSendCodec(const Code
_rtpRtcpModule->DeRegisterSendPayload(codec.pltype);
if (_rtpRtcpModule->RegisterSendPayload(codec) != 0) {
WEBRTC_TRACE(kTraceError, kTraceVoice, VoEId(_instanceId, _channelId),
"SetSendCodec() failed to register codec to"
" RTP/RTCP module");
return -1;
}
}
-
+ statistics_proxy_->OnSendCodecFrequencyChanged(codec.plfreq);
return 0;
}
void Channel::SetBitRate(int bitrate_bps, int64_t probing_interval_ms) {
WEBRTC_TRACE(kTraceInfo, kTraceVoice, VoEId(_instanceId, _channelId),
"Channel::SetBitRate(bitrate_bps=%d)", bitrate_bps);
audio_coding_->ModifyEncoder([&](std::unique_ptr<AudioEncoder>* encoder) {
if (*encoder) {
@@ -3077,16 +3273,17 @@ int Channel::SetInitSequenceNumber(short
WEBRTC_TRACE(kTraceInfo, kTraceVoice, VoEId(_instanceId, _channelId),
"Channel::SetInitSequenceNumber()");
if (channel_state_.Get().sending) {
_engineStatisticsPtr->SetLastError(
VE_SENDING, kTraceError, "SetInitSequenceNumber() already sending");
return -1;
}
_rtpRtcpModule->SetSequenceNumber(sequenceNumber);
+ statistics_proxy_->OnInitialSequenceNumberSet(sequenceNumber);
return 0;
}
int Channel::GetRtpRtcp(RtpRtcp** rtpRtcpModule,
RtpReceiver** rtp_receiver) const {
*rtpRtcpModule = _rtpRtcpModule.get();
*rtp_receiver = rtp_receiver_.get();
return 0;
--- a/media/webrtc/trunk/webrtc/voice_engine/channel.h
+++ b/media/webrtc/trunk/webrtc/voice_engine/channel.h
@@ -418,18 +418,28 @@ class Channel
void SetRtcEventLog(RtcEventLog* event_log);
void SetRtcpRttStats(RtcpRttStats* rtcp_rtt_stats);
void SetTransportOverhead(size_t transport_overhead_per_packet);
// From OverheadObserver in the RTP/RTCP module
void OnOverheadChanged(size_t overhead_bytes_per_packet) override;
+ bool GetRTCPReceiverStatistics(int64_t* timestamp,
+ uint32_t* jitterMs,
+ uint32_t* cumulativeLost,
+ uint32_t* packetsReceived,
+ uint64_t* bytesReceived,
+ double* packetsFractionLost,
+ int64_t* rtt) const;
protected:
void OnIncomingFractionLoss(int fraction_lost);
+ void OnIncomingReceiverReports(const ReportBlockList& aReportBlocks,
+ const int64_t aRoundTripTime,
+ const int64_t aReceptionTime);
private:
bool ReceivePacket(const uint8_t* packet,
size_t packet_length,
const RTPHeader& header,
bool in_order);
bool HandleRtxPacket(const uint8_t* packet,
size_t packet_length,
--- a/media/webrtc/trunk/webrtc/voice_engine/channel_proxy.cc
+++ b/media/webrtc/trunk/webrtc/voice_engine/channel_proxy.cc
@@ -87,16 +87,33 @@ void ChannelProxy::RegisterReceiverConge
channel()->RegisterReceiverCongestionControlObjects(packet_router);
}
void ChannelProxy::ResetCongestionControlObjects() {
RTC_DCHECK(thread_checker_.CalledOnValidThread());
channel()->ResetCongestionControlObjects();
}
+bool ChannelProxy::GetRTCPReceiverStatistics(int64_t* timestamp,
+ uint32_t* jitterMs,
+ uint32_t* cumulativeLost,
+ uint32_t* packetsReceived,
+ uint64_t* bytesReceived,
+ double* packetsFractionLost,
+ int64_t* rtt) const {
+ // No thread check necessary, we are synchronizing on the lock in StatsProxy
+ return channel()->GetRTCPReceiverStatistics(timestamp,
+ jitterMs,
+ cumulativeLost,
+ packetsReceived,
+ bytesReceived,
+ packetsFractionLost,
+ rtt);
+}
+
CallStatistics ChannelProxy::GetRTCPStatistics() const {
// Since we (Mozilla) need to collect stats on STS, we can't
// use the thread-checker (which will want to be called on MainThread)
// without refactor of ExecuteStatsQuery_s().
// However, GetRTPStatistics internally locks in the SSRC()
// and statistician methods.
// RTC_DCHECK(thread_checker_.CalledOnValidThread());
--- a/media/webrtc/trunk/webrtc/voice_engine/channel_proxy.h
+++ b/media/webrtc/trunk/webrtc/voice_engine/channel_proxy.h
@@ -59,16 +59,23 @@ class ChannelProxy {
virtual void EnableReceiveTransportSequenceNumber(int id);
virtual void RegisterSenderCongestionControlObjects(
RtpPacketSender* rtp_packet_sender,
TransportFeedbackObserver* transport_feedback_observer,
PacketRouter* packet_router);
virtual void RegisterReceiverCongestionControlObjects(
PacketRouter* packet_router);
virtual void ResetCongestionControlObjects();
+ virtual bool GetRTCPReceiverStatistics(int64_t* timestamp,
+ uint32_t* jitterMs,
+ uint32_t* cumulativeLost,
+ uint32_t* packetsReceived,
+ uint64_t* bytesReceived,
+ double* packetsFractionLost,
+ int64_t* rtt) const;
virtual CallStatistics GetRTCPStatistics() const;
virtual int GetRTPStatistics(unsigned int& averageJitterMs,
unsigned int& maxJitterMs,
unsigned int& discardedPackets,
unsigned int& cumulativeLost) const;
virtual std::vector<ReportBlock> GetRemoteRTCPReportBlocks() const;
virtual NetworkStatistics GetNetworkStatistics() const;
virtual AudioDecodingCallStats GetDecodingCallStatistics() const;