From 823c9b8e3604fcaa0db3c1d290728334d1535d82 Mon Sep 17 00:00:00 2001 From: "asapersson@webrtc.org" Date: Thu, 8 Jan 2015 07:50:56 +0000 Subject: [PATCH] Add histograms stats for sent/received fraction loss for a stream: - "WebRTC.Video.SentPacketsLostInPercent" - "WebRTC.Video.ReceivedPacketsLostInPercent" BUG=crbug/419657 R=mflodman@webrtc.org, stefan@webrtc.org Review URL: https://webrtc-codereview.appspot.com/37419004 git-svn-id: http://webrtc.googlecode.com/svn/trunk@8020 4adac7df-926f-26a2-2b94-8c16560cd09d --- webrtc/video_engine/BUILD.gn | 2 + webrtc/video_engine/report_block_stats.cc | 108 +++++++++++++ webrtc/video_engine/report_block_stats.h | 61 ++++++++ .../report_block_stats_unittest.cc | 146 ++++++++++++++++++ webrtc/video_engine/video_engine_core.gypi | 3 + webrtc/video_engine/vie_channel.cc | 69 ++------- webrtc/video_engine/vie_channel.h | 4 +- 7 files changed, 339 insertions(+), 54 deletions(-) create mode 100644 webrtc/video_engine/report_block_stats.cc create mode 100644 webrtc/video_engine/report_block_stats.h create mode 100644 webrtc/video_engine/report_block_stats_unittest.cc diff --git a/webrtc/video_engine/BUILD.gn b/webrtc/video_engine/BUILD.gn index debcd2f8db..90999a748f 100644 --- a/webrtc/video_engine/BUILD.gn +++ b/webrtc/video_engine/BUILD.gn @@ -29,6 +29,8 @@ source_set("video_engine_core") { "encoder_state_feedback.h", "overuse_frame_detector.cc", "overuse_frame_detector.h", + "report_block_stats.cc", + "report_block_stats.h", "stream_synchronization.cc", "stream_synchronization.h", "vie_base_impl.cc", diff --git a/webrtc/video_engine/report_block_stats.cc b/webrtc/video_engine/report_block_stats.cc new file mode 100644 index 0000000000..61167f167c --- /dev/null +++ b/webrtc/video_engine/report_block_stats.cc @@ -0,0 +1,108 @@ +/* + * Copyright (c) 2014 The WebRTC project authors. All Rights Reserved. + * + * Use of this source code is governed by a BSD-style license + * that can be found in the LICENSE file in the root of the source + * tree. An additional intellectual property rights grant can be found + * in the file PATENTS. All contributing project authors may + * be found in the AUTHORS file in the root of the source tree. + */ + +#include "webrtc/video_engine/report_block_stats.h" + +namespace webrtc { + +namespace { +int FractionLost(uint32_t num_lost_sequence_numbers, + uint32_t num_sequence_numbers) { + if (num_sequence_numbers == 0) { + return 0; + } + return ((num_lost_sequence_numbers * 255) + (num_sequence_numbers / 2)) / + num_sequence_numbers; +} +} // namespace + + +// Helper class for rtcp statistics. +ReportBlockStats::ReportBlockStats() + : num_sequence_numbers_(0), + num_lost_sequence_numbers_(0) { +} + +void ReportBlockStats::Store(const RtcpStatistics& rtcp_stats, + uint32_t remote_ssrc, + uint32_t source_ssrc) { + RTCPReportBlock block; + block.cumulativeLost = rtcp_stats.cumulative_lost; + block.fractionLost = rtcp_stats.fraction_lost; + block.extendedHighSeqNum = rtcp_stats.extended_max_sequence_number; + block.jitter = rtcp_stats.jitter; + block.remoteSSRC = remote_ssrc; + block.sourceSSRC = source_ssrc; + uint32_t num_sequence_numbers = 0; + uint32_t num_lost_sequence_numbers = 0; + StoreAndAddPacketIncrement( + block, &num_sequence_numbers, &num_lost_sequence_numbers); +} + +RTCPReportBlock ReportBlockStats::AggregateAndStore( + const ReportBlockVector& report_blocks) { + RTCPReportBlock aggregate; + if (report_blocks.empty()) { + return aggregate; + } + uint32_t num_sequence_numbers = 0; + uint32_t num_lost_sequence_numbers = 0; + ReportBlockVector::const_iterator report_block = report_blocks.begin(); + for (; report_block != report_blocks.end(); ++report_block) { + aggregate.cumulativeLost += report_block->cumulativeLost; + aggregate.jitter += report_block->jitter; + StoreAndAddPacketIncrement(*report_block, + &num_sequence_numbers, + &num_lost_sequence_numbers); + } + + if (report_blocks.size() == 1) { + // No aggregation needed. + return report_blocks[0]; + } + // Fraction lost since previous report block. + aggregate.fractionLost = + FractionLost(num_lost_sequence_numbers, num_sequence_numbers); + aggregate.jitter = + (aggregate.jitter + report_blocks.size() / 2) / report_blocks.size(); + return aggregate; +} + +void ReportBlockStats::StoreAndAddPacketIncrement( + const RTCPReportBlock& report_block, + uint32_t* num_sequence_numbers, + uint32_t* num_lost_sequence_numbers) { + // Get diff with previous report block. + ReportBlockMap::iterator prev_report_block = prev_report_blocks_.find( + report_block.sourceSSRC); + if (prev_report_block != prev_report_blocks_.end()) { + int seq_num_diff = report_block.extendedHighSeqNum - + prev_report_block->second.extendedHighSeqNum; + int cum_loss_diff = report_block.cumulativeLost - + prev_report_block->second.cumulativeLost; + if (seq_num_diff >= 0 && cum_loss_diff >= 0) { + *num_sequence_numbers += seq_num_diff; + *num_lost_sequence_numbers += cum_loss_diff; + // Update total number of packets/lost packets. + num_sequence_numbers_ += seq_num_diff; + num_lost_sequence_numbers_ += cum_loss_diff; + } + } + // Store current report block. + prev_report_blocks_[report_block.sourceSSRC] = report_block; +} + +int ReportBlockStats::FractionLostInPercent() const { + return FractionLost( + num_lost_sequence_numbers_, num_sequence_numbers_) * 100 / 255; +} + +} // namespace webrtc + diff --git a/webrtc/video_engine/report_block_stats.h b/webrtc/video_engine/report_block_stats.h new file mode 100644 index 0000000000..2d6b16e657 --- /dev/null +++ b/webrtc/video_engine/report_block_stats.h @@ -0,0 +1,61 @@ +/* + * Copyright (c) 2014 The WebRTC project authors. All Rights Reserved. + * + * Use of this source code is governed by a BSD-style license + * that can be found in the LICENSE file in the root of the source + * tree. An additional intellectual property rights grant can be found + * in the file PATENTS. All contributing project authors may + * be found in the AUTHORS file in the root of the source tree. + */ + +#ifndef WEBRTC_VIDEO_ENGINE_REPORT_BLOCK_STATS_H_ +#define WEBRTC_VIDEO_ENGINE_REPORT_BLOCK_STATS_H_ + +#include +#include + +#include "webrtc/common_types.h" +#include "webrtc/modules/rtp_rtcp/interface/rtp_rtcp_defines.h" + +namespace webrtc { + +// Helper class for rtcp statistics. +class ReportBlockStats { + public: + typedef std::map ReportBlockMap; + typedef std::vector ReportBlockVector; + ReportBlockStats(); + ~ReportBlockStats() {} + + // Updates stats and stores report blocks. + // Returns an aggregate of the |report_blocks|. + RTCPReportBlock AggregateAndStore(const ReportBlockVector& report_blocks); + + // Updates stats and stores report block. + void Store(const RtcpStatistics& rtcp_stats, + uint32_t remote_ssrc, + uint32_t source_ssrc); + + // Returns the total fraction of lost packets. + int FractionLostInPercent() const; + + private: + // Updates the total number of packets/lost packets. + // Stores the report block. + // Returns the number of packets/lost packets since previous report block. + void StoreAndAddPacketIncrement(const RTCPReportBlock& report_block, + uint32_t* num_sequence_numbers, + uint32_t* num_lost_sequence_numbers); + + // The total number of packets/lost packets. + uint32_t num_sequence_numbers_; + uint32_t num_lost_sequence_numbers_; + + // Map holding the last stored report block (mapped by the source SSRC). + ReportBlockMap prev_report_blocks_; +}; + +} // namespace webrtc + +#endif // WEBRTC_VIDEO_ENGINE_REPORT_BLOCK_STATS_H_ + diff --git a/webrtc/video_engine/report_block_stats_unittest.cc b/webrtc/video_engine/report_block_stats_unittest.cc new file mode 100644 index 0000000000..10c4104241 --- /dev/null +++ b/webrtc/video_engine/report_block_stats_unittest.cc @@ -0,0 +1,146 @@ +/* + * Copyright (c) 2014 The WebRTC project authors. All Rights Reserved. + * + * Use of this source code is governed by a BSD-style license + * that can be found in the LICENSE file in the root of the source + * tree. An additional intellectual property rights grant can be found + * in the file PATENTS. All contributing project authors may + * be found in the AUTHORS file in the root of the source tree. + */ + +#include "testing/gtest/include/gtest/gtest.h" + +#include "webrtc/video_engine/report_block_stats.h" + +namespace webrtc { + +class ReportBlockStatsTest : public ::testing::Test { + protected: + ReportBlockStatsTest() : kSsrc1(0x12345), kSsrc2(0x23456) {} + + virtual void SetUp() OVERRIDE { + // kSsrc1: block 1-3. + block1_1_.cumulativeLost = 10; + block1_1_.fractionLost = 123; + block1_1_.extendedHighSeqNum = 24000; + block1_1_.jitter = 777; + block1_1_.sourceSSRC = kSsrc1; + block1_2_.cumulativeLost = 15; + block1_2_.fractionLost = 0; + block1_2_.extendedHighSeqNum = 24100; + block1_2_.jitter = 222; + block1_2_.sourceSSRC = kSsrc1; + block1_3_.cumulativeLost = 50; + block1_3_.fractionLost = 0; + block1_3_.extendedHighSeqNum = 24200; + block1_3_.jitter = 333; + block1_3_.sourceSSRC = kSsrc1; + // kSsrc2: block 1,2. + block2_1_.cumulativeLost = 111; + block2_1_.fractionLost = 222; + block2_1_.extendedHighSeqNum = 8500; + block2_1_.jitter = 555; + block2_1_.sourceSSRC = kSsrc2; + block2_2_.cumulativeLost = 136; + block2_2_.fractionLost = 0; + block2_2_.extendedHighSeqNum = 8800; + block2_2_.jitter = 888; + block2_2_.sourceSSRC = kSsrc2; + + ssrc1block1_.push_back(block1_1_); + ssrc1block2_.push_back(block1_2_); + ssrc12block1_.push_back(block1_1_); + ssrc12block1_.push_back(block2_1_); + ssrc12block2_.push_back(block1_2_); + ssrc12block2_.push_back(block2_2_); + } + + RtcpStatistics RtcpReportBlockToRtcpStatistics( + const RTCPReportBlock& stats) { + RtcpStatistics block; + block.cumulative_lost = stats.cumulativeLost; + block.fraction_lost = stats.fractionLost; + block.extended_max_sequence_number = stats.extendedHighSeqNum; + block.jitter = stats.jitter; + return block; + } + + const uint32_t kSsrc1; + const uint32_t kSsrc2; + RTCPReportBlock block1_1_; + RTCPReportBlock block1_2_; + RTCPReportBlock block1_3_; + RTCPReportBlock block2_1_; + RTCPReportBlock block2_2_; + std::vector ssrc1block1_; + std::vector ssrc1block2_; + std::vector ssrc12block1_; + std::vector ssrc12block2_; +}; + +TEST_F(ReportBlockStatsTest, AggregateAndStore_NoSsrc) { + ReportBlockStats stats; + std::vector empty; + RTCPReportBlock aggregated = stats.AggregateAndStore(empty); + EXPECT_EQ(0U, aggregated.fractionLost); + EXPECT_EQ(0U, aggregated.cumulativeLost); + EXPECT_EQ(0U, aggregated.jitter); + EXPECT_EQ(0U, aggregated.extendedHighSeqNum); +} + +TEST_F(ReportBlockStatsTest, AggregateAndStore_OneSsrc) { + ReportBlockStats stats; + RTCPReportBlock aggregated = stats.AggregateAndStore(ssrc1block1_); + // One ssrc, no aggregation done. + EXPECT_EQ(123U, aggregated.fractionLost); + EXPECT_EQ(10U, aggregated.cumulativeLost); + EXPECT_EQ(777U, aggregated.jitter); + EXPECT_EQ(24000U, aggregated.extendedHighSeqNum); + + aggregated = stats.AggregateAndStore(ssrc1block2_); + EXPECT_EQ(0U, aggregated.fractionLost); + EXPECT_EQ(15U, aggregated.cumulativeLost); + EXPECT_EQ(222U, aggregated.jitter); + EXPECT_EQ(24100U, aggregated.extendedHighSeqNum); + + // fl: 100 * (15-10) / (24100-24000) = 5% + EXPECT_EQ(5, stats.FractionLostInPercent()); +} + +TEST_F(ReportBlockStatsTest, AggregateAndStore_TwoSsrcs) { + ReportBlockStats stats; + RTCPReportBlock aggregated = stats.AggregateAndStore(ssrc12block1_); + EXPECT_EQ(0U, aggregated.fractionLost); + EXPECT_EQ(10U + 111U, aggregated.cumulativeLost); + EXPECT_EQ((777U + 555U) / 2, aggregated.jitter); + EXPECT_EQ(0U, aggregated.extendedHighSeqNum); + + aggregated = stats.AggregateAndStore(ssrc12block2_); + // fl: 255 * ((15-10) + (136-111)) / ((24100-24000) + (8800-8500)) = 19 + EXPECT_EQ(19U, aggregated.fractionLost); + EXPECT_EQ(15U + 136U, aggregated.cumulativeLost); + EXPECT_EQ((222U + 888U) / 2, aggregated.jitter); + EXPECT_EQ(0U, aggregated.extendedHighSeqNum); + + // fl: 100 * ((15-10) + (136-111)) / ((24100-24000) + (8800-8500)) = 7% + EXPECT_EQ(7, stats.FractionLostInPercent()); +} + +TEST_F(ReportBlockStatsTest, StoreAndGetFractionLost) { + const uint32_t kRemoteSsrc = 1; + ReportBlockStats stats; + EXPECT_EQ(0, stats.FractionLostInPercent()); + + // First block => 0% + stats.Store(RtcpReportBlockToRtcpStatistics(block1_1_), kRemoteSsrc, kSsrc1); + EXPECT_EQ(0, stats.FractionLostInPercent()); + // fl: 100 * (15-10) / (24100-24000) = 5% + stats.Store(RtcpReportBlockToRtcpStatistics(block1_2_), kRemoteSsrc, kSsrc1); + EXPECT_EQ(5, stats.FractionLostInPercent()); + // fl: 100 * (50-10) / (24200-24000) = 20% + stats.Store(RtcpReportBlockToRtcpStatistics(block1_3_), kRemoteSsrc, kSsrc1); + EXPECT_EQ(20, stats.FractionLostInPercent()); +} + +} // namespace webrtc + diff --git a/webrtc/video_engine/video_engine_core.gypi b/webrtc/video_engine/video_engine_core.gypi index 2bfc6536d8..763d0bee4a 100644 --- a/webrtc/video_engine/video_engine_core.gypi +++ b/webrtc/video_engine/video_engine_core.gypi @@ -49,6 +49,7 @@ 'call_stats.h', 'encoder_state_feedback.h', 'overuse_frame_detector.h', + 'report_block_stats.h', 'stream_synchronization.h', 'vie_base_impl.h', 'vie_capture_impl.h', @@ -82,6 +83,7 @@ 'call_stats.cc', 'encoder_state_feedback.cc', 'overuse_frame_detector.cc', + 'report_block_stats.cc', 'stream_synchronization.cc', 'vie_base_impl.cc', 'vie_capture_impl.cc', @@ -132,6 +134,7 @@ 'call_stats_unittest.cc', 'encoder_state_feedback_unittest.cc', 'overuse_frame_detector_unittest.cc', + 'report_block_stats_unittest.cc', 'stream_synchronization_unittest.cc', 'vie_capturer_unittest.cc', 'vie_codec_unittest.cc', diff --git a/webrtc/video_engine/vie_channel.cc b/webrtc/video_engine/vie_channel.cc index 180b4559f9..79ebe7081f 100644 --- a/webrtc/video_engine/vie_channel.cc +++ b/webrtc/video_engine/vie_channel.cc @@ -34,6 +34,7 @@ #include "webrtc/video_engine/include/vie_errors.h" #include "webrtc/video_engine/include/vie_image_process.h" #include "webrtc/video_engine/include/vie_rtp_rtcp.h" +#include "webrtc/video_engine/report_block_stats.h" #include "webrtc/video_engine/vie_defines.h" namespace webrtc { @@ -43,52 +44,6 @@ const int kInvalidRtpExtensionId = 0; static const int kMaxTargetDelayMs = 10000; static const float kMaxIncompleteTimeMultiplier = 3.5f; -namespace { - -RTCPReportBlock AggregateReportBlocks( - const std::vector& report_blocks, - std::map* prev_report_blocks) { - int num_sequence_numbers = 0; - int num_lost_sequence_numbers = 0; - int jitter_sum = 0; - int number_of_report_blocks = 0; - RTCPReportBlock aggregate; - std::vector::const_iterator report_block = - report_blocks.begin(); - for (; report_block != report_blocks.end(); ++report_block) { - aggregate.cumulativeLost += report_block->cumulativeLost; - std::map::iterator prev_report_block = - prev_report_blocks->find(report_block->sourceSSRC); - if (prev_report_block != prev_report_blocks->end()) { - // Skip the first report block since we won't be able to get a correct - // weight for it. - int seq_num_diff = report_block->extendedHighSeqNum - - prev_report_block->second.extendedHighSeqNum; - int cum_loss_diff = report_block->cumulativeLost - - prev_report_block->second.cumulativeLost; - if (seq_num_diff >= 0 && cum_loss_diff >= 0) { - num_sequence_numbers += seq_num_diff; - num_lost_sequence_numbers += cum_loss_diff; - } - } - jitter_sum += report_block->jitter; - ++number_of_report_blocks; - (*prev_report_blocks)[report_block->sourceSSRC] = *report_block; - } - if (num_sequence_numbers > 0) { - aggregate.fractionLost = ((num_lost_sequence_numbers * 255) + - (num_sequence_numbers / 2)) / num_sequence_numbers; - } - if (number_of_report_blocks > 0) { - aggregate.jitter = - (jitter_sum + number_of_report_blocks / 2) / number_of_report_blocks; - } - // Not well defined for aggregated report blocks. - aggregate.extendedHighSeqNum = 0; - return aggregate; -} -} // namespace - // Helper class receiving statistics callbacks. class ChannelStatsObserver : public CallStatsObserver { public: @@ -150,7 +105,9 @@ ViEChannel::ViEChannel(int32_t channel_id, sender_(sender), nack_history_size_sender_(kSendSidePacketHistorySize), max_nack_reordering_threshold_(kMaxPacketAgeToNack), - pre_render_callback_(NULL) { + pre_render_callback_(NULL), + report_block_stats_sender_(new ReportBlockStats()), + report_block_stats_receiver_(new ReportBlockStats()) { RtpRtcp::Configuration configuration = CreateRtpRtcpConfiguration(); configuration.remote_bitrate_estimator = remote_bitrate_estimator; configuration.receive_statistics = vie_receiver_.GetReceiveStatistics(); @@ -255,6 +212,8 @@ void ViEChannel::UpdateHistograms() { "WebRTC.Video.UniqueNackRequestsReceivedInPercent", rtcp_received.UniqueNackRequestsInPercent()); } + RTC_HISTOGRAM_PERCENTAGE("WebRTC.Video.SentPacketsLostInPercent", + report_block_stats_sender_->FractionLostInPercent()); } } else if (vie_receiver_.GetRemoteSsrc() > 0) { // Get receive stats if we are receiving packets, i.e. there is a remote @@ -271,6 +230,8 @@ void ViEChannel::UpdateHistograms() { RTC_HISTOGRAM_PERCENTAGE("WebRTC.Video.UniqueNackRequestsSentInPercent", rtcp_sent.UniqueNackRequestsInPercent()); } + RTC_HISTOGRAM_PERCENTAGE("WebRTC.Video.ReceivedPacketsLostInPercent", + report_block_stats_receiver_->FractionLostInPercent()); } StreamDataCounters rtp; @@ -1076,12 +1037,10 @@ int32_t ViEChannel::GetSendRtcpStatistics(uint16_t* fraction_lost, remote_ssrc = report_blocks[0].remoteSSRC; } - RTCPReportBlock report; - if (report_blocks.size() > 1) - report = AggregateReportBlocks(report_blocks, &prev_report_blocks_); - else - report = report_blocks[0]; - + // TODO(asapersson): Change report_block_stats to not rely on + // GetSendRtcpStatistics to be called. + RTCPReportBlock report = + report_block_stats_sender_->AggregateAndStore(report_blocks); *fraction_lost = report.fractionLost; *cumulative_lost = report.cumulativeLost; *extended_max = report.extendedHighSeqNum; @@ -1128,6 +1087,10 @@ int32_t ViEChannel::GetReceivedRtcpStatistics(uint16_t* fraction_lost, *extended_max = receive_stats.extended_max_sequence_number; *jitter_samples = receive_stats.jitter; + // TODO(asapersson): Change report_block_stats to not rely on + // GetReceivedRtcpStatistics to be called. + report_block_stats_receiver_->Store(receive_stats, remote_ssrc, 0); + uint16_t dummy = 0; uint16_t rtt = 0; rtp_rtcp_->RTT(remote_ssrc, &rtt, &dummy, &dummy, &dummy); diff --git a/webrtc/video_engine/vie_channel.h b/webrtc/video_engine/vie_channel.h index 3bc6597202..7a46d1f75e 100644 --- a/webrtc/video_engine/vie_channel.h +++ b/webrtc/video_engine/vie_channel.h @@ -40,6 +40,7 @@ class I420FrameCallback; class PacedSender; class ProcessThread; class ReceiveStatisticsProxy; +class ReportBlockStats; class RtcpRttStats; class ThreadWrapper; class ViEDecoderObserver; @@ -509,7 +510,8 @@ class ViEChannel int max_nack_reordering_threshold_; I420FrameCallback* pre_render_callback_; - std::map prev_report_blocks_; + scoped_ptr report_block_stats_sender_; + scoped_ptr report_block_stats_receiver_; }; } // namespace webrtc