Bug 1393095 - remote audio receiver stats missing;r?dminor draft
authorNico Grunbaum
Wed, 13 Sep 2017 01:38:35 -0700
changeset 665622 2fc5069dfb86f53c6bc0b9e664dc7385ed77111a
parent 665088 8e818b5e9b6bef0fc1a5c527ecf30b0d56a02f14
child 731838 6384d69f0e955f7b93a2a84c9ae9b51411fa494c
push id80125
push userna-g@nostrum.com
push dateFri, 15 Sep 2017 19:40:47 +0000
reviewersdminor
bugs1393095
milestone57.0a1
Bug 1393095 - remote audio receiver stats missing;r?dminor MozReview-Commit-ID: ykg2rqiiPG
media/webrtc/signaling/src/media-conduit/AudioConduit.cpp
media/webrtc/trunk/webrtc/voice_engine/channel.cc
media/webrtc/trunk/webrtc/voice_engine/channel.h
media/webrtc/trunk/webrtc/voice_engine/channel_proxy.cc
media/webrtc/trunk/webrtc/voice_engine/channel_proxy.h
--- 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(&timestampTmp,
+                                                      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;