diff --git a/webrtc/modules/rtp_rtcp/interface/rtp_rtcp_defines.h b/webrtc/modules/rtp_rtcp/interface/rtp_rtcp_defines.h index 6dbf52c931..0bdbec0b46 100644 --- a/webrtc/modules/rtp_rtcp/interface/rtp_rtcp_defines.h +++ b/webrtc/modules/rtp_rtcp/interface/rtp_rtcp_defines.h @@ -245,6 +245,13 @@ class RtcpBandwidthObserver { virtual ~RtcpBandwidthObserver() {} }; +class RtcpRttObserver { + public: + virtual void OnRttUpdate(uint32_t rtt) = 0; + + virtual ~RtcpRttObserver() {}; +}; + // A clock interface that allows reading of absolute and relative // timestamps in an RTP/RTCP module. class RtpRtcpClock { diff --git a/webrtc/video_engine/call_stats.cc b/webrtc/video_engine/call_stats.cc new file mode 100644 index 0000000000..d9f2243ff1 --- /dev/null +++ b/webrtc/video_engine/call_stats.cc @@ -0,0 +1,119 @@ +/* + * Copyright (c) 2012 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/call_stats.h" + +#include + +#include "webrtc/modules/rtp_rtcp/interface/rtp_rtcp_defines.h" +#include "webrtc/system_wrappers/interface/critical_section_wrapper.h" +#include "webrtc/system_wrappers/interface/tick_util.h" +#include "webrtc/system_wrappers/interface/trace.h" + +namespace webrtc { + +// A rtt report is considered valid for this long. +const int kRttTimeoutMs = 1500; +// Time interval for updating the observers. +const int kUpdateIntervalMs = 1000; + +class RtcpObserver : public RtcpRttObserver { + public: + explicit RtcpObserver(CallStats* owner) : owner_(owner) {} + virtual ~RtcpObserver() {} + + virtual void OnRttUpdate(uint32_t rtt) { + owner_->OnRttUpdate(rtt); + } + + private: + CallStats* owner_; + + DISALLOW_COPY_AND_ASSIGN(RtcpObserver); +}; + +CallStats::CallStats() + : crit_(CriticalSectionWrapper::CreateCriticalSection()), + rtcp_rtt_observer_(new RtcpObserver(this)), + last_process_time_(TickTime::MillisecondTimestamp()) { +} + +CallStats::~CallStats() { + assert(observers_.empty()); +} + +int32_t CallStats::TimeUntilNextProcess() { + return last_process_time_ + kUpdateIntervalMs - + TickTime::MillisecondTimestamp(); +} + +int32_t CallStats::Process() { + CriticalSectionScoped cs(crit_.get()); + if (TickTime::MillisecondTimestamp() < last_process_time_ + kUpdateIntervalMs) + return 0; + + // Remove invalid, as in too old, rtt values. + int64_t time_now = TickTime::MillisecondTimestamp(); + while (!reports_.empty() && reports_.front().time + kRttTimeoutMs < + time_now) { + reports_.pop_front(); + } + + // Find the max stored RTT. + uint32_t max_rtt = 0; + for (std::list::const_iterator it = reports_.begin(); + it != reports_.end(); ++it) { + if (it->rtt > max_rtt) + max_rtt = it->rtt; + } + + // If there is a valid rtt, update all observers. + if (max_rtt > 0) { + for (std::list::iterator it = observers_.begin(); + it != observers_.end(); ++it) { + (*it)->OnRttUpdate(max_rtt); + } + } + last_process_time_ = time_now; + return 0; +} + +RtcpRttObserver* CallStats::rtcp_rtt_observer() const { + return rtcp_rtt_observer_.get(); +} + +void CallStats::RegisterStatsObserver(StatsObserver* observer) { + CriticalSectionScoped cs(crit_.get()); + for (std::list::iterator it = observers_.begin(); + it != observers_.end(); ++it) { + if (*it == observer) + return; + } + observers_.push_back(observer); +} + +void CallStats::DeregisterStatsObserver(StatsObserver* observer) { + CriticalSectionScoped cs(crit_.get()); + for (std::list::iterator it = observers_.begin(); + it != observers_.end(); ++it) { + if (*it == observer) { + observers_.erase(it); + return; + } + } +} + +void CallStats::OnRttUpdate(uint32_t rtt) { + CriticalSectionScoped cs(crit_.get()); + int64_t time_now = TickTime::MillisecondTimestamp(); + reports_.push_back(RttTime(rtt, time_now)); +} + +} // namespace webrtc diff --git a/webrtc/video_engine/call_stats.h b/webrtc/video_engine/call_stats.h new file mode 100644 index 0000000000..14619f1e6d --- /dev/null +++ b/webrtc/video_engine/call_stats.h @@ -0,0 +1,84 @@ +/* + * Copyright (c) 2012 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_CALL_STATS_H_ +#define WEBRTC_VIDEO_ENGINE_CALL_STATS_H_ + +#include + +#include "webrtc/modules/interface/module.h" +#include "webrtc/system_wrappers/interface/constructor_magic.h" +#include "webrtc/system_wrappers/interface/scoped_ptr.h" + +namespace webrtc { + +class CriticalSectionWrapper; +class RtcpRttObserver; + +// Interface used to distribute call statistics. Callbacks will be triggered as +// soon as the class has been registered using RegisterStatsObserver. +class StatsObserver { + public: + virtual void OnRttUpdate(uint32_t rtt) = 0; + + virtual ~StatsObserver() {} +}; + +// CallStats keeps track of statistics for a call. +class CallStats : public Module { + public: + friend class RtcpObserver; + + CallStats(); + ~CallStats(); + + // Implements Module, to use the process thread. + virtual int32_t TimeUntilNextProcess(); + virtual int32_t Process(); + + // Returns a RtcpRttObserver to register at a statistics provider. The object + // has the same lifetime as the CallStats instance. + RtcpRttObserver* rtcp_rtt_observer() const; + + // Registers/deregisters a new observer to receive statistics updates. + void RegisterStatsObserver(StatsObserver* observer); + void DeregisterStatsObserver(StatsObserver* observer); + + protected: + void OnRttUpdate(uint32_t rtt); + + private: + // Helper struct keeping track of the time a rtt value is reported. + struct RttTime { + RttTime(uint32_t new_rtt, int64_t rtt_time) + : rtt(new_rtt), time(rtt_time) {} + const uint32_t rtt; + const int64_t time; + }; + + // Protecting all members. + scoped_ptr crit_; + // Observer receiving statistics updates. + scoped_ptr rtcp_rtt_observer_; + // The last time 'Process' resulted in statistic update. + int64_t last_process_time_; + + // All Rtt reports within valid time interval, oldest first. + std::list reports_; + + // Observers getting stats reports. + std::list observers_; + + DISALLOW_COPY_AND_ASSIGN(CallStats); +}; + +} // namespace webrtc + +#endif // WEBRTC_VIDEO_ENGINE_CALL_STATS_H_ diff --git a/webrtc/video_engine/call_stats_unittest.cc b/webrtc/video_engine/call_stats_unittest.cc new file mode 100644 index 0000000000..a6c8720312 --- /dev/null +++ b/webrtc/video_engine/call_stats_unittest.cc @@ -0,0 +1,179 @@ +/* + * Copyright (c) 2012 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 +#include + +#include "webrtc/modules/rtp_rtcp/interface/rtp_rtcp_defines.h" +#include "webrtc/system_wrappers/interface/scoped_ptr.h" +#include "webrtc/system_wrappers/interface/tick_util.h" +#include "webrtc/video_engine/call_stats.h" + +using ::testing::_; +using ::testing::AnyNumber; +using ::testing::Return; + +namespace webrtc { + +class MockStatsObserver : public StatsObserver { + public: + MockStatsObserver() {} + virtual ~MockStatsObserver() {} + + MOCK_METHOD1(OnRttUpdate, void(uint32_t)); +}; + +class CallStatsTest : public ::testing::Test { + protected: + virtual void SetUp() { + TickTime::UseFakeClock(12345); + call_stats_.reset(new CallStats()); + } + scoped_ptr call_stats_; +}; + +TEST_F(CallStatsTest, AddAndTriggerCallback) { + MockStatsObserver stats_observer; + RtcpRttObserver* rtcp_observer = call_stats_->rtcp_rtt_observer(); + call_stats_->RegisterStatsObserver(&stats_observer); + TickTime::AdvanceFakeClock(1000); + + uint32_t rtt = 25; + rtcp_observer->OnRttUpdate(rtt); + EXPECT_CALL(stats_observer, OnRttUpdate(rtt)) + .Times(1); + call_stats_->Process(); + + call_stats_->DeregisterStatsObserver(&stats_observer); +} + +TEST_F(CallStatsTest, ProcessTime) { + MockStatsObserver stats_observer; + call_stats_->RegisterStatsObserver(&stats_observer); + RtcpRttObserver* rtcp_observer = call_stats_->rtcp_rtt_observer(); + rtcp_observer->OnRttUpdate(100); + + // Time isn't updated yet. + EXPECT_CALL(stats_observer, OnRttUpdate(_)) + .Times(0); + call_stats_->Process(); + + // Advance clock and verify we get an update. + TickTime::AdvanceFakeClock(1000); + EXPECT_CALL(stats_observer, OnRttUpdate(_)) + .Times(1); + call_stats_->Process(); + + // Advance clock just too little to get an update. + TickTime::AdvanceFakeClock(999); + rtcp_observer->OnRttUpdate(100); + EXPECT_CALL(stats_observer, OnRttUpdate(_)) + .Times(0); + call_stats_->Process(); + + // Advance enough to trigger a new update. + TickTime::AdvanceFakeClock(1); + EXPECT_CALL(stats_observer, OnRttUpdate(_)) + .Times(1); + call_stats_->Process(); + + call_stats_->DeregisterStatsObserver(&stats_observer); +} + +// Verify all observers get correct estimates and observers can be added and +// removed. +TEST_F(CallStatsTest, MultipleObservers) { + MockStatsObserver stats_observer_1; + call_stats_->RegisterStatsObserver(&stats_observer_1); + // Add the secondobserver twice, there should still be only one report to the + // observer. + MockStatsObserver stats_observer_2; + call_stats_->RegisterStatsObserver(&stats_observer_2); + call_stats_->RegisterStatsObserver(&stats_observer_2); + + RtcpRttObserver* rtcp_observer = call_stats_->rtcp_rtt_observer(); + uint32_t rtt = 100; + rtcp_observer->OnRttUpdate(rtt); + + // Verify both observers are updated. + TickTime::AdvanceFakeClock(1000); + EXPECT_CALL(stats_observer_1, OnRttUpdate(rtt)) + .Times(1); + EXPECT_CALL(stats_observer_2, OnRttUpdate(rtt)) + .Times(1); + call_stats_->Process(); + + // Deregister the second observer and verify update is only sent to the first + // observer. + call_stats_->DeregisterStatsObserver(&stats_observer_2); + rtcp_observer->OnRttUpdate(rtt); + TickTime::AdvanceFakeClock(1000); + EXPECT_CALL(stats_observer_1, OnRttUpdate(rtt)) + .Times(1); + EXPECT_CALL(stats_observer_2, OnRttUpdate(rtt)) + .Times(0); + call_stats_->Process(); + + // Deregister the first observer. + call_stats_->DeregisterStatsObserver(&stats_observer_1); + rtcp_observer->OnRttUpdate(rtt); + TickTime::AdvanceFakeClock(1000); + EXPECT_CALL(stats_observer_1, OnRttUpdate(rtt)) + .Times(0); + EXPECT_CALL(stats_observer_2, OnRttUpdate(rtt)) + .Times(0); + call_stats_->Process(); +} + +// Verify increasing and decreasing rtt triggers callbacks with correct values. +TEST_F(CallStatsTest, ChangeRtt) { + MockStatsObserver stats_observer; + call_stats_->RegisterStatsObserver(&stats_observer); + RtcpRttObserver* rtcp_observer = call_stats_->rtcp_rtt_observer(); + + // Advance clock to be ready for an update. + TickTime::AdvanceFakeClock(1000); + + // Set a first value and verify the callback is triggered. + const uint32_t first_rtt = 100; + rtcp_observer->OnRttUpdate(first_rtt); + EXPECT_CALL(stats_observer, OnRttUpdate(first_rtt)) + .Times(1); + call_stats_->Process(); + + // Increase rtt and verify the new value is reported. + TickTime::AdvanceFakeClock(1000); + const uint32_t high_rtt = first_rtt + 20; + rtcp_observer->OnRttUpdate(high_rtt); + EXPECT_CALL(stats_observer, OnRttUpdate(high_rtt)) + .Times(1); + call_stats_->Process(); + + // Increase time enough for a new update, but not too much to make the + // rtt invalid. Report a lower rtt and verify the old/high value still is sent + // in the callback. + TickTime::AdvanceFakeClock(1000); + const uint32_t low_rtt = first_rtt - 20; + rtcp_observer->OnRttUpdate(low_rtt); + EXPECT_CALL(stats_observer, OnRttUpdate(high_rtt)) + .Times(1); + call_stats_->Process(); + + // Advance time to make the high report invalid, the lower rtt should no be in + // the callback. + TickTime::AdvanceFakeClock(1000); + EXPECT_CALL(stats_observer, OnRttUpdate(low_rtt)) + .Times(1); + call_stats_->Process(); + + call_stats_->DeregisterStatsObserver(&stats_observer); +} + +} // namespace webrtc diff --git a/webrtc/video_engine/video_engine_core.gypi b/webrtc/video_engine/video_engine_core.gypi index 5606c29553..db287f4665 100644 --- a/webrtc/video_engine/video_engine_core.gypi +++ b/webrtc/video_engine/video_engine_core.gypi @@ -67,6 +67,7 @@ 'include/vie_rtp_rtcp.h', # headers + 'call_stats.h', 'encoder_state_feedback.h', 'stream_synchronization.h', 'vie_base_impl.h', @@ -102,6 +103,7 @@ 'vie_sync_module.h', # ViE + 'call_stats.cc', 'encoder_state_feedback.cc', 'stream_synchronization.cc', 'vie_base_impl.cc', @@ -155,6 +157,7 @@ '../modules/rtp_rtcp/interface', ], 'sources': [ + 'call_stats_unittest.cc', 'encoder_state_feedback_unittest.cc', 'stream_synchronization_unittest.cc', 'vie_remb_unittest.cc',