diff --git a/BUILD.gn b/BUILD.gn index 3ad9c97c31..461138a40d 100644 --- a/BUILD.gn +++ b/BUILD.gn @@ -638,7 +638,6 @@ if (rtc_include_tests && !build_with_chromium) { "test:test_common", "test:test_main", "test:video_test_common", - "video:video_legacy_tests", "video:video_tests", "video/adaptation:video_adaptation_tests", ] diff --git a/video/BUILD.gn b/video/BUILD.gn index 381cad0b76..35e12e45ac 100644 --- a/video/BUILD.gn +++ b/video/BUILD.gn @@ -166,81 +166,6 @@ rtc_library("video") { } } -rtc_source_set("video_legacy") { - sources = [ - "call_stats.cc", - "call_stats.h", - "receive_statistics_proxy.cc", - "receive_statistics_proxy.h", - "video_quality_observer.cc", - "video_quality_observer.h", - ] - deps = [ - ":frame_dumping_decoder", - ":unique_timestamp_counter", - ":video", - "../api:array_view", - "../api:field_trials_view", - "../api:scoped_refptr", - "../api:sequence_checker", - "../api/crypto:frame_decryptor_interface", - "../api/task_queue", - "../api/transport:field_trial_based_config", - "../api/units:timestamp", - "../api/video:encoded_image", - "../api/video:recordable_encoded_frame", - "../api/video:video_frame", - "../api/video:video_rtp_headers", - "../call:call_interfaces", - "../call:rtp_interfaces", - "../call:rtp_receiver", # For RtxReceiveStream. - "../call:video_stream_api", - "../common_video", - "../modules:module_api", - "../modules/pacing", - "../modules/remote_bitrate_estimator", - "../modules/rtp_rtcp", - "../modules/rtp_rtcp:rtp_rtcp_format", - "../modules/rtp_rtcp:rtp_rtcp_legacy", - "../modules/rtp_rtcp:rtp_video_header", - "../modules/utility", - "../modules/video_coding", - "../modules/video_coding:packet_buffer", - "../modules/video_coding:video_codec_interface", - "../modules/video_coding:video_coding_utility", - "../modules/video_coding/deprecated:nack_module", - "../rtc_base:checks", - "../rtc_base:histogram_percentile_counter", - "../rtc_base:location", - "../rtc_base:logging", - "../rtc_base:macromagic", - "../rtc_base:moving_max_counter", - "../rtc_base:platform_thread", - "../rtc_base:rate_statistics", - "../rtc_base:rate_tracker", - "../rtc_base:rtc_numerics", - "../rtc_base:rtc_task_queue", - "../rtc_base:sample_counter", - "../rtc_base:stringutils", - "../rtc_base:timeutils", - "../rtc_base/experiments:field_trial_parser", - "../rtc_base/experiments:keyframe_interval_settings_experiment", - "../rtc_base/synchronization:mutex", - "../rtc_base/system:no_unique_address", - "../rtc_base/task_utils:to_queued_task", - "../system_wrappers", - "../system_wrappers:field_trial", - "../system_wrappers:metrics", - ] - if (!build_with_mozilla) { - deps += [ "../media:rtc_media_base" ] - } - absl_deps = [ - "//third_party/abseil-cpp/absl/algorithm:container", - "//third_party/abseil-cpp/absl/types:optional", - ] -} - rtc_library("video_stream_decoder_impl") { visibility = [ "*" ] @@ -1001,40 +926,4 @@ if (rtc_include_tests) { deps += [ "../media:rtc_media_base" ] } } - rtc_library("video_legacy_tests") { - testonly = true - sources = [ - "call_stats_unittest.cc", - "receive_statistics_proxy_unittest.cc", - ] - deps = [ - ":video_legacy", - "../api:scoped_refptr", - "../api/video:video_frame", - "../api/video:video_frame_type", - "../api/video:video_rtp_headers", - "../call:mock_rtp_interfaces", - "../common_video", - "../media:rtc_media_base", - "../modules/rtp_rtcp", - "../modules/rtp_rtcp:rtp_rtcp_format", - "../modules/utility", - "../modules/video_coding", - "../modules/video_coding:video_codec_interface", - "../rtc_base:byte_buffer", - "../rtc_base:location", - "../rtc_base:logging", - "../rtc_base:rtc_event", - "../rtc_base/task_utils:to_queued_task", - "../system_wrappers", - "../system_wrappers:field_trial", - "../system_wrappers:metrics", - "../test:field_trial", - "../test:mock_frame_transformer", - "../test:mock_transport", - "../test:scoped_key_value_config", - "../test:test_support", - ] - absl_deps = [ "//third_party/abseil-cpp/absl/types:optional" ] - } } diff --git a/video/call_stats.cc b/video/call_stats.cc deleted file mode 100644 index 9fd6802c44..0000000000 --- a/video/call_stats.cc +++ /dev/null @@ -1,228 +0,0 @@ -/* - * 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 "video/call_stats.h" - -#include -#include - -#include "absl/algorithm/container.h" -#include "modules/utility/include/process_thread.h" -#include "rtc_base/checks.h" -#include "rtc_base/location.h" -#include "rtc_base/task_utils/to_queued_task.h" -#include "system_wrappers/include/metrics.h" - -namespace webrtc { -namespace { - -void RemoveOldReports(int64_t now, std::list* reports) { - static constexpr const int64_t kRttTimeoutMs = 1500; - reports->remove_if( - [&now](CallStats::RttTime& r) { return now - r.time > kRttTimeoutMs; }); -} - -int64_t GetMaxRttMs(const std::list& reports) { - int64_t max_rtt_ms = -1; - for (const CallStats::RttTime& rtt_time : reports) - max_rtt_ms = std::max(rtt_time.rtt, max_rtt_ms); - return max_rtt_ms; -} - -int64_t GetAvgRttMs(const std::list& reports) { - RTC_DCHECK(!reports.empty()); - int64_t sum = 0; - for (std::list::const_iterator it = reports.begin(); - it != reports.end(); ++it) { - sum += it->rtt; - } - return sum / reports.size(); -} - -int64_t GetNewAvgRttMs(const std::list& reports, - int64_t prev_avg_rtt) { - if (reports.empty()) - return -1; // Reset (invalid average). - - int64_t cur_rtt_ms = GetAvgRttMs(reports); - if (prev_avg_rtt == -1) - return cur_rtt_ms; // New initial average value. - - // Weight factor to apply to the average rtt. - // We weigh the old average at 70% against the new average (30%). - constexpr const float kWeightFactor = 0.3f; - return prev_avg_rtt * (1.0f - kWeightFactor) + cur_rtt_ms * kWeightFactor; -} - -// This class is used to de-register a Module from a ProcessThread to satisfy -// threading requirements of the Module (CallStats). -// The guarantee offered by TemporaryDeregistration is that while its in scope, -// no calls to `TimeUntilNextProcess` or `Process()` will occur and therefore -// synchronization with those methods, is not necessary. -class TemporaryDeregistration { - public: - TemporaryDeregistration(Module* module, - ProcessThread* process_thread, - bool thread_running) - : module_(module), - process_thread_(process_thread), - deregistered_(thread_running) { - if (thread_running) - process_thread_->DeRegisterModule(module_); - } - ~TemporaryDeregistration() { - if (deregistered_) - process_thread_->RegisterModule(module_, RTC_FROM_HERE); - } - - private: - Module* const module_; - ProcessThread* const process_thread_; - const bool deregistered_; -}; - -} // namespace - -CallStats::CallStats(Clock* clock, ProcessThread* process_thread) - : clock_(clock), - last_process_time_(clock_->TimeInMilliseconds()), - max_rtt_ms_(-1), - avg_rtt_ms_(-1), - sum_avg_rtt_ms_(0), - num_avg_rtt_(0), - time_of_first_rtt_ms_(-1), - process_thread_(process_thread), - process_thread_running_(false) { - RTC_DCHECK(process_thread_); - process_thread_checker_.Detach(); -} - -CallStats::~CallStats() { - RTC_DCHECK_RUN_ON(&construction_thread_checker_); - RTC_DCHECK(!process_thread_running_); - RTC_DCHECK(observers_.empty()); - - UpdateHistograms(); -} - -int64_t CallStats::TimeUntilNextProcess() { - RTC_DCHECK_RUN_ON(&process_thread_checker_); - return last_process_time_ + kUpdateIntervalMs - clock_->TimeInMilliseconds(); -} - -void CallStats::Process() { - RTC_DCHECK_RUN_ON(&process_thread_checker_); - int64_t now = clock_->TimeInMilliseconds(); - last_process_time_ = now; - - // `avg_rtt_ms_` is allowed to be read on the process thread since that's the - // only thread that modifies the value. - int64_t avg_rtt_ms = avg_rtt_ms_; - RemoveOldReports(now, &reports_); - max_rtt_ms_ = GetMaxRttMs(reports_); - avg_rtt_ms = GetNewAvgRttMs(reports_, avg_rtt_ms); - { - MutexLock lock(&avg_rtt_ms_lock_); - avg_rtt_ms_ = avg_rtt_ms; - } - - // If there is a valid rtt, update all observers with the max rtt. - if (max_rtt_ms_ >= 0) { - RTC_DCHECK_GE(avg_rtt_ms, 0); - for (CallStatsObserver* observer : observers_) - observer->OnRttUpdate(avg_rtt_ms, max_rtt_ms_); - // Sum for Histogram of average RTT reported over the entire call. - sum_avg_rtt_ms_ += avg_rtt_ms; - ++num_avg_rtt_; - } -} - -void CallStats::ProcessThreadAttached(ProcessThread* process_thread) { - RTC_DCHECK_RUN_ON(&construction_thread_checker_); - RTC_DCHECK(!process_thread || process_thread_ == process_thread); - process_thread_running_ = process_thread != nullptr; - - // Whether we just got attached or detached, we clear the - // `process_thread_checker_` so that it can be used to protect variables - // in either the process thread when it starts again, or UpdateHistograms() - // (mutually exclusive). - process_thread_checker_.Detach(); -} - -void CallStats::RegisterStatsObserver(CallStatsObserver* observer) { - RTC_DCHECK_RUN_ON(&construction_thread_checker_); - TemporaryDeregistration deregister(this, process_thread_, - process_thread_running_); - - if (!absl::c_linear_search(observers_, observer)) - observers_.push_back(observer); -} - -void CallStats::DeregisterStatsObserver(CallStatsObserver* observer) { - RTC_DCHECK_RUN_ON(&construction_thread_checker_); - TemporaryDeregistration deregister(this, process_thread_, - process_thread_running_); - observers_.remove(observer); -} - -int64_t CallStats::LastProcessedRtt() const { - // TODO(tommi): This currently gets called from the construction thread of - // Call as well as from the process thread. Look into restricting this to - // allow only reading this from the process thread (or TQ once we get there) - // so that the lock isn't necessary. - - MutexLock lock(&avg_rtt_ms_lock_); - return avg_rtt_ms_; -} - -void CallStats::OnRttUpdate(int64_t rtt) { - RTC_DCHECK_RUN_ON(&process_thread_checker_); - - int64_t now_ms = clock_->TimeInMilliseconds(); - reports_.push_back(RttTime(rtt, now_ms)); - if (time_of_first_rtt_ms_ == -1) - time_of_first_rtt_ms_ = now_ms; - - // Make sure Process() will be called and deliver the updates asynchronously. - last_process_time_ -= kUpdateIntervalMs; - process_thread_->WakeUp(this); -} - -void CallStats::UpdateHistograms() { - RTC_DCHECK_RUN_ON(&construction_thread_checker_); - RTC_DCHECK(!process_thread_running_); - - // The extra scope is because we have two 'dcheck run on' thread checkers. - // This is a special case since it's safe to access variables on the current - // thread that normally are only touched on the process thread. - // Since we're not attached to the process thread and/or the process thread - // isn't running, it's OK to touch these variables here. - { - // This method is called on the ctor thread (usually from the dtor, unless - // a test calls it). It's a requirement that the function be called when - // the process thread is not running (a condition that's met at destruction - // time), and thanks to that, we don't need a lock to synchronize against - // it. - RTC_DCHECK_RUN_ON(&process_thread_checker_); - - if (time_of_first_rtt_ms_ == -1 || num_avg_rtt_ < 1) - return; - - int64_t elapsed_sec = - (clock_->TimeInMilliseconds() - time_of_first_rtt_ms_) / 1000; - if (elapsed_sec >= metrics::kMinRunTimeInSeconds) { - int64_t avg_rtt_ms = (sum_avg_rtt_ms_ + num_avg_rtt_ / 2) / num_avg_rtt_; - RTC_HISTOGRAM_COUNTS_10000( - "WebRTC.Video.AverageRoundTripTimeInMilliseconds", avg_rtt_ms); - } - } -} - -} // namespace webrtc diff --git a/video/call_stats.h b/video/call_stats.h deleted file mode 100644 index d198223a9d..0000000000 --- a/video/call_stats.h +++ /dev/null @@ -1,123 +0,0 @@ -/* - * 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 VIDEO_CALL_STATS_H_ -#define VIDEO_CALL_STATS_H_ - -#include -#include - -#include "api/sequence_checker.h" -#include "modules/include/module.h" -#include "modules/include/module_common_types.h" -#include "modules/rtp_rtcp/include/rtp_rtcp_defines.h" -#include "rtc_base/synchronization/mutex.h" -#include "system_wrappers/include/clock.h" - -namespace webrtc { - -// CallStats keeps track of statistics for a call. -// TODO(webrtc:11489): Make call_stats_ not depend on ProcessThread and -// make callbacks on the worker thread (TQ). -class CallStats : public Module, public RtcpRttStats { - public: - // Time interval for updating the observers. - static constexpr int64_t kUpdateIntervalMs = 1000; - - CallStats(Clock* clock, ProcessThread* process_thread); - ~CallStats() override; - - CallStats(const CallStats&) = delete; - CallStats& operator=(const CallStats&) = delete; - - // Registers/deregisters a new observer to receive statistics updates. - // Must be called from the construction thread. - void RegisterStatsObserver(CallStatsObserver* observer); - void DeregisterStatsObserver(CallStatsObserver* observer); - - // Expose `LastProcessedRtt()` from RtcpRttStats to the public interface, as - // it is the part of the API that is needed by direct users of CallStats. - // TODO(tommi): Threading or lifetime guarantees are not explicit in how - // CallStats is used as RtcpRttStats or how pointers are cached in a - // few different places (distributed via Call). It would be good to clarify - // from what thread/TQ calls to OnRttUpdate and LastProcessedRtt need to be - // allowed. - int64_t LastProcessedRtt() const override; - - // Exposed for tests to test histogram support. - void UpdateHistogramsForTest() { UpdateHistograms(); } - - // Helper struct keeping track of the time a rtt value is reported. - struct RttTime { - RttTime(int64_t new_rtt, int64_t rtt_time) : rtt(new_rtt), time(rtt_time) {} - const int64_t rtt; - const int64_t time; - }; - - private: - // RtcpRttStats implementation. - void OnRttUpdate(int64_t rtt) override; - - // Implements Module, to use the process thread. - int64_t TimeUntilNextProcess() override; - void Process() override; - - // TODO(tommi): Use this to know when we're attached to the process thread? - // Alternatively, inject that pointer via the ctor since the call_stats - // test code, isn't using a processthread atm. - void ProcessThreadAttached(ProcessThread* process_thread) override; - - // This method must only be called when the process thread is not - // running, and from the construction thread. - void UpdateHistograms(); - - Clock* const clock_; - - // The last time 'Process' resulted in statistic update. - int64_t last_process_time_ RTC_GUARDED_BY(process_thread_checker_); - // The last RTT in the statistics update (zero if there is no valid estimate). - int64_t max_rtt_ms_ RTC_GUARDED_BY(process_thread_checker_); - - // Accessed from random threads (seemingly). Consider atomic. - // `avg_rtt_ms_` is allowed to be read on the process thread without a lock. - // `avg_rtt_ms_lock_` must be held elsewhere for reading. - // `avg_rtt_ms_lock_` must be held on the process thread for writing. - int64_t avg_rtt_ms_; - - // Protects `avg_rtt_ms_`. - mutable Mutex avg_rtt_ms_lock_; - - // `sum_avg_rtt_ms_`, `num_avg_rtt_` and `time_of_first_rtt_ms_` are only used - // on the ProcessThread when running. When the Process Thread is not running, - // (and only then) they can be used in UpdateHistograms(), usually called from - // the dtor. - int64_t sum_avg_rtt_ms_ RTC_GUARDED_BY(process_thread_checker_); - int64_t num_avg_rtt_ RTC_GUARDED_BY(process_thread_checker_); - int64_t time_of_first_rtt_ms_ RTC_GUARDED_BY(process_thread_checker_); - - // All Rtt reports within valid time interval, oldest first. - std::list reports_ RTC_GUARDED_BY(process_thread_checker_); - - // Observers getting stats reports. - // When attached to ProcessThread, this is read-only. In order to allow - // modification, we detach from the process thread while the observer - // list is updated, to avoid races. This allows us to not require a lock - // for the observers_ list, which makes the most common case lock free. - std::list observers_; - - SequenceChecker construction_thread_checker_; - SequenceChecker process_thread_checker_; - ProcessThread* const process_thread_; - bool process_thread_running_ RTC_GUARDED_BY(construction_thread_checker_); -}; - -} // namespace webrtc - -#endif // VIDEO_CALL_STATS_H_ diff --git a/video/call_stats_unittest.cc b/video/call_stats_unittest.cc deleted file mode 100644 index e85c4f8c54..0000000000 --- a/video/call_stats_unittest.cc +++ /dev/null @@ -1,325 +0,0 @@ -/* - * 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 "video/call_stats.h" - -#include - -#include "modules/rtp_rtcp/include/rtp_rtcp_defines.h" -#include "modules/utility/include/process_thread.h" -#include "rtc_base/event.h" -#include "rtc_base/location.h" -#include "rtc_base/task_utils/to_queued_task.h" -#include "system_wrappers/include/metrics.h" -#include "test/gmock.h" -#include "test/gtest.h" - -using ::testing::AnyNumber; -using ::testing::InvokeWithoutArgs; -using ::testing::Return; - -namespace webrtc { - -class MockStatsObserver : public CallStatsObserver { - public: - MockStatsObserver() {} - virtual ~MockStatsObserver() {} - - MOCK_METHOD(void, OnRttUpdate, (int64_t, int64_t), (override)); -}; - -class CallStatsTest : public ::testing::Test { - public: - CallStatsTest() { - process_thread_->RegisterModule(&call_stats_, RTC_FROM_HERE); - process_thread_->Start(); - } - ~CallStatsTest() override { - process_thread_->Stop(); - process_thread_->DeRegisterModule(&call_stats_); - } - - // Queues an rtt update call on the process thread. - void AsyncSimulateRttUpdate(int64_t rtt) { - RtcpRttStats* rtcp_rtt_stats = &call_stats_; - process_thread_->PostTask(ToQueuedTask( - [rtcp_rtt_stats, rtt] { rtcp_rtt_stats->OnRttUpdate(rtt); })); - } - - protected: - std::unique_ptr process_thread_{ - ProcessThread::Create("CallStats")}; - SimulatedClock fake_clock_{12345}; - CallStats call_stats_{&fake_clock_, process_thread_.get()}; -}; - -TEST_F(CallStatsTest, AddAndTriggerCallback) { - rtc::Event event; - - static constexpr const int64_t kRtt = 25; - - MockStatsObserver stats_observer; - EXPECT_CALL(stats_observer, OnRttUpdate(kRtt, kRtt)) - .Times(1) - .WillOnce(InvokeWithoutArgs([&event] { event.Set(); })); - - RtcpRttStats* rtcp_rtt_stats = &call_stats_; - call_stats_.RegisterStatsObserver(&stats_observer); - EXPECT_EQ(-1, rtcp_rtt_stats->LastProcessedRtt()); - - AsyncSimulateRttUpdate(kRtt); - - EXPECT_TRUE(event.Wait(1000)); - - EXPECT_EQ(kRtt, rtcp_rtt_stats->LastProcessedRtt()); - - call_stats_.DeregisterStatsObserver(&stats_observer); -} - -TEST_F(CallStatsTest, ProcessTime) { - rtc::Event event; - - static constexpr const int64_t kRtt = 100; - static constexpr const int64_t kRtt2 = 80; - - RtcpRttStats* rtcp_rtt_stats = &call_stats_; - - MockStatsObserver stats_observer; - - EXPECT_CALL(stats_observer, OnRttUpdate(kRtt, kRtt)) - .Times(2) - .WillOnce(InvokeWithoutArgs([this] { - // Advance clock and verify we get an update. - fake_clock_.AdvanceTimeMilliseconds(CallStats::kUpdateIntervalMs); - })) - .WillRepeatedly(InvokeWithoutArgs([this, rtcp_rtt_stats] { - rtcp_rtt_stats->OnRttUpdate(kRtt2); - // Advance clock just too little to get an update. - fake_clock_.AdvanceTimeMilliseconds(CallStats::kUpdateIntervalMs - 1); - })); - - // In case you're reading this and wondering how this number is arrived at, - // please see comments in the ChangeRtt test that go into some detail. - static constexpr const int64_t kLastAvg = 94; - EXPECT_CALL(stats_observer, OnRttUpdate(kLastAvg, kRtt2)) - .Times(1) - .WillOnce(InvokeWithoutArgs([&event] { event.Set(); })); - - call_stats_.RegisterStatsObserver(&stats_observer); - - AsyncSimulateRttUpdate(kRtt); - EXPECT_TRUE(event.Wait(1000)); - - 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 second observer 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); - - static constexpr const int64_t kRtt = 100; - - // Verify both observers are updated. - rtc::Event ev1; - rtc::Event ev2; - EXPECT_CALL(stats_observer_1, OnRttUpdate(kRtt, kRtt)) - .Times(AnyNumber()) - .WillOnce(InvokeWithoutArgs([&ev1] { ev1.Set(); })) - .WillRepeatedly(Return()); - EXPECT_CALL(stats_observer_2, OnRttUpdate(kRtt, kRtt)) - .Times(AnyNumber()) - .WillOnce(InvokeWithoutArgs([&ev2] { ev2.Set(); })) - .WillRepeatedly(Return()); - AsyncSimulateRttUpdate(kRtt); - ASSERT_TRUE(ev1.Wait(100)); - ASSERT_TRUE(ev2.Wait(100)); - - // Deregister the second observer and verify update is only sent to the first - // observer. - call_stats_.DeregisterStatsObserver(&stats_observer_2); - - EXPECT_CALL(stats_observer_1, OnRttUpdate(kRtt, kRtt)) - .Times(AnyNumber()) - .WillOnce(InvokeWithoutArgs([&ev1] { ev1.Set(); })) - .WillRepeatedly(Return()); - EXPECT_CALL(stats_observer_2, OnRttUpdate(kRtt, kRtt)).Times(0); - AsyncSimulateRttUpdate(kRtt); - ASSERT_TRUE(ev1.Wait(100)); - - // Deregister the first observer. - call_stats_.DeregisterStatsObserver(&stats_observer_1); - - // Now make sure we don't get any callbacks. - EXPECT_CALL(stats_observer_1, OnRttUpdate(kRtt, kRtt)).Times(0); - EXPECT_CALL(stats_observer_2, OnRttUpdate(kRtt, kRtt)).Times(0); - AsyncSimulateRttUpdate(kRtt); - - // Force a call to Process(). - process_thread_->WakeUp(&call_stats_); - - // Flush the queue on the process thread to make sure we return after - // Process() has been called. - rtc::Event event; - process_thread_->PostTask(ToQueuedTask([&event] { event.Set(); })); - event.Wait(rtc::Event::kForever); -} - -// Verify increasing and decreasing rtt triggers callbacks with correct values. -TEST_F(CallStatsTest, ChangeRtt) { - // TODO(tommi): This test assumes things about how old reports are removed - // inside of call_stats.cc. The threshold ms value is 1500ms, but it's not - // clear here that how the clock is advanced, affects that algorithm and - // subsequently the average reported rtt. - - MockStatsObserver stats_observer; - call_stats_.RegisterStatsObserver(&stats_observer); - RtcpRttStats* rtcp_rtt_stats = &call_stats_; - - rtc::Event event; - - static constexpr const int64_t kFirstRtt = 100; - static constexpr const int64_t kLowRtt = kFirstRtt - 20; - static constexpr const int64_t kHighRtt = kFirstRtt + 20; - - EXPECT_CALL(stats_observer, OnRttUpdate(kFirstRtt, kFirstRtt)) - .Times(1) - .WillOnce(InvokeWithoutArgs([&rtcp_rtt_stats, this] { - fake_clock_.AdvanceTimeMilliseconds(1000); - rtcp_rtt_stats->OnRttUpdate(kHighRtt); // Reported at T1 (1000ms). - })); - - // TODO(tommi): This relies on the internal algorithms of call_stats.cc. - // There's a weight factor there (0.3), that weighs the previous average to - // the new one by 70%, so the number 103 in this case is arrived at like so: - // (100) / 1 * 0.7 + (100+120)/2 * 0.3 = 103 - static constexpr const int64_t kAvgRtt1 = 103; - EXPECT_CALL(stats_observer, OnRttUpdate(kAvgRtt1, kHighRtt)) - .Times(1) - .WillOnce(InvokeWithoutArgs([&rtcp_rtt_stats, this] { - // This interacts with an internal implementation detail in call_stats - // that decays the oldest rtt value. See more below. - fake_clock_.AdvanceTimeMilliseconds(1000); - rtcp_rtt_stats->OnRttUpdate(kLowRtt); // Reported at T2 (2000ms). - })); - - // 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. - - // Here, enough time must have passed in order to remove exactly the first - // report and nothing else (>1500ms has passed since the first rtt). - // So, this value is arrived by doing: - // (kAvgRtt1)/1 * 0.7 + (kHighRtt+kLowRtt)/2 * 0.3 = 102.1 - static constexpr const int64_t kAvgRtt2 = 102; - EXPECT_CALL(stats_observer, OnRttUpdate(kAvgRtt2, kHighRtt)) - .Times(1) - .WillOnce(InvokeWithoutArgs([this] { - // Advance time to make the high report invalid, the lower rtt should - // now be in the callback. - fake_clock_.AdvanceTimeMilliseconds(1000); - })); - - static constexpr const int64_t kAvgRtt3 = 95; - EXPECT_CALL(stats_observer, OnRttUpdate(kAvgRtt3, kLowRtt)) - .Times(1) - .WillOnce(InvokeWithoutArgs([&event] { event.Set(); })); - - // Trigger the first rtt value and set off the chain of callbacks. - AsyncSimulateRttUpdate(kFirstRtt); // Reported at T0 (0ms). - EXPECT_TRUE(event.Wait(1000)); - - call_stats_.DeregisterStatsObserver(&stats_observer); -} - -TEST_F(CallStatsTest, LastProcessedRtt) { - rtc::Event event; - MockStatsObserver stats_observer; - call_stats_.RegisterStatsObserver(&stats_observer); - RtcpRttStats* rtcp_rtt_stats = &call_stats_; - - static constexpr const int64_t kRttLow = 10; - static constexpr const int64_t kRttHigh = 30; - // The following two average numbers dependend on average + weight - // calculations in call_stats.cc. - static constexpr const int64_t kAvgRtt1 = 13; - static constexpr const int64_t kAvgRtt2 = 15; - - EXPECT_CALL(stats_observer, OnRttUpdate(kRttLow, kRttLow)) - .Times(1) - .WillOnce(InvokeWithoutArgs([rtcp_rtt_stats] { - EXPECT_EQ(kRttLow, rtcp_rtt_stats->LastProcessedRtt()); - // Don't advance the clock to make sure that low and high rtt values - // are associated with the same time stamp. - rtcp_rtt_stats->OnRttUpdate(kRttHigh); - })); - - EXPECT_CALL(stats_observer, OnRttUpdate(kAvgRtt1, kRttHigh)) - .Times(1) - .WillOnce(InvokeWithoutArgs([rtcp_rtt_stats, this] { - EXPECT_EQ(kAvgRtt1, rtcp_rtt_stats->LastProcessedRtt()); - fake_clock_.AdvanceTimeMilliseconds(CallStats::kUpdateIntervalMs); - rtcp_rtt_stats->OnRttUpdate(kRttLow); - rtcp_rtt_stats->OnRttUpdate(kRttHigh); - })); - - EXPECT_CALL(stats_observer, OnRttUpdate(kAvgRtt2, kRttHigh)) - .Times(1) - .WillOnce(InvokeWithoutArgs([rtcp_rtt_stats, &event] { - EXPECT_EQ(kAvgRtt2, rtcp_rtt_stats->LastProcessedRtt()); - event.Set(); - })); - - // Set a first values and verify that LastProcessedRtt initially returns the - // average rtt. - fake_clock_.AdvanceTimeMilliseconds(CallStats::kUpdateIntervalMs); - AsyncSimulateRttUpdate(kRttLow); - EXPECT_TRUE(event.Wait(1000)); - EXPECT_EQ(kAvgRtt2, rtcp_rtt_stats->LastProcessedRtt()); - - call_stats_.DeregisterStatsObserver(&stats_observer); -} - -TEST_F(CallStatsTest, ProducesHistogramMetrics) { - metrics::Reset(); - rtc::Event event; - static constexpr const int64_t kRtt = 123; - MockStatsObserver stats_observer; - call_stats_.RegisterStatsObserver(&stats_observer); - EXPECT_CALL(stats_observer, OnRttUpdate(kRtt, kRtt)) - .Times(AnyNumber()) - .WillRepeatedly(InvokeWithoutArgs([&event] { event.Set(); })); - - AsyncSimulateRttUpdate(kRtt); - EXPECT_TRUE(event.Wait(1000)); - fake_clock_.AdvanceTimeMilliseconds(metrics::kMinRunTimeInSeconds * - CallStats::kUpdateIntervalMs); - AsyncSimulateRttUpdate(kRtt); - EXPECT_TRUE(event.Wait(1000)); - - call_stats_.DeregisterStatsObserver(&stats_observer); - - process_thread_->Stop(); - call_stats_.UpdateHistogramsForTest(); - - EXPECT_METRIC_EQ(1, metrics::NumSamples( - "WebRTC.Video.AverageRoundTripTimeInMilliseconds")); - EXPECT_METRIC_EQ( - 1, metrics::NumEvents("WebRTC.Video.AverageRoundTripTimeInMilliseconds", - kRtt)); -} - -} // namespace webrtc diff --git a/video/receive_statistics_proxy.cc b/video/receive_statistics_proxy.cc deleted file mode 100644 index 06873d8aef..0000000000 --- a/video/receive_statistics_proxy.cc +++ /dev/null @@ -1,945 +0,0 @@ -/* - * Copyright (c) 2013 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 "video/receive_statistics_proxy.h" - -#include -#include -#include - -#include "modules/video_coding/include/video_codec_interface.h" -#include "rtc_base/checks.h" -#include "rtc_base/logging.h" -#include "rtc_base/strings/string_builder.h" -#include "rtc_base/time_utils.h" -#include "system_wrappers/include/clock.h" -#include "system_wrappers/include/metrics.h" - -namespace webrtc { -namespace { -// Periodic time interval for processing samples for `freq_offset_counter_`. -const int64_t kFreqOffsetProcessIntervalMs = 40000; - -// Configuration for bad call detection. -const int kBadCallMinRequiredSamples = 10; -const int kMinSampleLengthMs = 990; -const int kNumMeasurements = 10; -const int kNumMeasurementsVariance = kNumMeasurements * 1.5; -const float kBadFraction = 0.8f; -// For fps: -// Low means low enough to be bad, high means high enough to be good -const int kLowFpsThreshold = 12; -const int kHighFpsThreshold = 14; -// For qp and fps variance: -// Low means low enough to be good, high means high enough to be bad -const int kLowQpThresholdVp8 = 60; -const int kHighQpThresholdVp8 = 70; -const int kLowVarianceThreshold = 1; -const int kHighVarianceThreshold = 2; - -// Some metrics are reported as a maximum over this period. -// This should be synchronized with a typical getStats polling interval in -// the clients. -const int kMovingMaxWindowMs = 1000; - -// How large window we use to calculate the framerate/bitrate. -const int kRateStatisticsWindowSizeMs = 1000; - -// Some sane ballpark estimate for maximum common value of inter-frame delay. -// Values below that will be stored explicitly in the array, -// values above - in the map. -const int kMaxCommonInterframeDelayMs = 500; - -const char* UmaPrefixForContentType(VideoContentType content_type) { - if (videocontenttypehelpers::IsScreenshare(content_type)) - return "WebRTC.Video.Screenshare"; - return "WebRTC.Video"; -} - -std::string UmaSuffixForContentType(VideoContentType content_type) { - char ss_buf[1024]; - rtc::SimpleStringBuilder ss(ss_buf); - int simulcast_id = videocontenttypehelpers::GetSimulcastId(content_type); - if (simulcast_id > 0) { - ss << ".S" << simulcast_id - 1; - } - int experiment_id = videocontenttypehelpers::GetExperimentId(content_type); - if (experiment_id > 0) { - ss << ".ExperimentGroup" << experiment_id - 1; - } - return ss.str(); -} - -bool EnableDecodeTimeHistogram(const FieldTrialsView* field_trials) { - if (field_trials == nullptr) { - return true; - } - return !field_trials->IsEnabled("WebRTC-DecodeTimeHistogramsKillSwitch"); -} - -} // namespace - -ReceiveStatisticsProxy::ReceiveStatisticsProxy( - uint32_t remote_ssrc, - Clock* clock, - const FieldTrialsView* field_trials) - : clock_(clock), - start_ms_(clock->TimeInMilliseconds()), - enable_decode_time_histograms_(EnableDecodeTimeHistogram(field_trials)), - last_sample_time_(clock->TimeInMilliseconds()), - fps_threshold_(kLowFpsThreshold, - kHighFpsThreshold, - kBadFraction, - kNumMeasurements), - qp_threshold_(kLowQpThresholdVp8, - kHighQpThresholdVp8, - kBadFraction, - kNumMeasurements), - variance_threshold_(kLowVarianceThreshold, - kHighVarianceThreshold, - kBadFraction, - kNumMeasurementsVariance), - num_bad_states_(0), - num_certain_states_(0), - // 1000ms window, scale 1000 for ms to s. - decode_fps_estimator_(1000, 1000), - renders_fps_estimator_(1000, 1000), - render_fps_tracker_(100, 10u), - render_pixel_tracker_(100, 10u), - video_quality_observer_( - new VideoQualityObserver(VideoContentType::UNSPECIFIED)), - interframe_delay_max_moving_(kMovingMaxWindowMs), - freq_offset_counter_(clock, nullptr, kFreqOffsetProcessIntervalMs), - avg_rtt_ms_(0), - last_content_type_(VideoContentType::UNSPECIFIED), - last_codec_type_(kVideoCodecVP8), - num_delayed_frames_rendered_(0), - sum_missed_render_deadline_ms_(0), - timing_frame_info_counter_(kMovingMaxWindowMs) { - decode_thread_.Detach(); - network_thread_.Detach(); - stats_.ssrc = remote_ssrc; -} - -void ReceiveStatisticsProxy::UpdateHistograms( - absl::optional fraction_lost, - const StreamDataCounters& rtp_stats, - const StreamDataCounters* rtx_stats) { - // Not actually running on the decoder thread, but must be called after - // DecoderThreadStopped, which detaches the thread checker. It is therefore - // safe to access `qp_counters_`, which were updated on the decode thread - // earlier. - RTC_DCHECK_RUN_ON(&decode_thread_); - - MutexLock lock(&mutex_); - - char log_stream_buf[8 * 1024]; - rtc::SimpleStringBuilder log_stream(log_stream_buf); - int stream_duration_sec = (clock_->TimeInMilliseconds() - start_ms_) / 1000; - if (stats_.frame_counts.key_frames > 0 || - stats_.frame_counts.delta_frames > 0) { - RTC_HISTOGRAM_COUNTS_100000("WebRTC.Video.ReceiveStreamLifetimeInSeconds", - stream_duration_sec); - log_stream << "WebRTC.Video.ReceiveStreamLifetimeInSeconds " - << stream_duration_sec << '\n'; - } - - log_stream << "Frames decoded " << stats_.frames_decoded << '\n'; - - if (num_unique_frames_) { - int num_dropped_frames = *num_unique_frames_ - stats_.frames_decoded; - RTC_HISTOGRAM_COUNTS_1000("WebRTC.Video.DroppedFrames.Receiver", - num_dropped_frames); - log_stream << "WebRTC.Video.DroppedFrames.Receiver " << num_dropped_frames - << '\n'; - } - - if (fraction_lost && stream_duration_sec >= metrics::kMinRunTimeInSeconds) { - RTC_HISTOGRAM_PERCENTAGE("WebRTC.Video.ReceivedPacketsLostInPercent", - *fraction_lost); - log_stream << "WebRTC.Video.ReceivedPacketsLostInPercent " << *fraction_lost - << '\n'; - } - - if (first_decoded_frame_time_ms_) { - const int64_t elapsed_ms = - (clock_->TimeInMilliseconds() - *first_decoded_frame_time_ms_); - if (elapsed_ms >= - metrics::kMinRunTimeInSeconds * rtc::kNumMillisecsPerSec) { - int decoded_fps = static_cast( - (stats_.frames_decoded * 1000.0f / elapsed_ms) + 0.5f); - RTC_HISTOGRAM_COUNTS_100("WebRTC.Video.DecodedFramesPerSecond", - decoded_fps); - log_stream << "WebRTC.Video.DecodedFramesPerSecond " << decoded_fps - << '\n'; - - const uint32_t frames_rendered = stats_.frames_rendered; - if (frames_rendered > 0) { - RTC_HISTOGRAM_PERCENTAGE("WebRTC.Video.DelayedFramesToRenderer", - static_cast(num_delayed_frames_rendered_ * - 100 / frames_rendered)); - if (num_delayed_frames_rendered_ > 0) { - RTC_HISTOGRAM_COUNTS_1000( - "WebRTC.Video.DelayedFramesToRenderer_AvgDelayInMs", - static_cast(sum_missed_render_deadline_ms_ / - num_delayed_frames_rendered_)); - } - } - } - } - - const int kMinRequiredSamples = 200; - int samples = static_cast(render_fps_tracker_.TotalSampleCount()); - if (samples >= kMinRequiredSamples) { - int rendered_fps = round(render_fps_tracker_.ComputeTotalRate()); - RTC_HISTOGRAM_COUNTS_100("WebRTC.Video.RenderFramesPerSecond", - rendered_fps); - log_stream << "WebRTC.Video.RenderFramesPerSecond " << rendered_fps << '\n'; - RTC_HISTOGRAM_COUNTS_100000( - "WebRTC.Video.RenderSqrtPixelsPerSecond", - round(render_pixel_tracker_.ComputeTotalRate())); - } - - absl::optional sync_offset_ms = - sync_offset_counter_.Avg(kMinRequiredSamples); - if (sync_offset_ms) { - RTC_HISTOGRAM_COUNTS_10000("WebRTC.Video.AVSyncOffsetInMs", - *sync_offset_ms); - log_stream << "WebRTC.Video.AVSyncOffsetInMs " << *sync_offset_ms << '\n'; - } - AggregatedStats freq_offset_stats = freq_offset_counter_.GetStats(); - if (freq_offset_stats.num_samples > 0) { - RTC_HISTOGRAM_COUNTS_10000("WebRTC.Video.RtpToNtpFreqOffsetInKhz", - freq_offset_stats.average); - log_stream << "WebRTC.Video.RtpToNtpFreqOffsetInKhz " - << freq_offset_stats.ToString() << '\n'; - } - - int num_total_frames = - stats_.frame_counts.key_frames + stats_.frame_counts.delta_frames; - if (num_total_frames >= kMinRequiredSamples) { - int num_key_frames = stats_.frame_counts.key_frames; - int key_frames_permille = - (num_key_frames * 1000 + num_total_frames / 2) / num_total_frames; - RTC_HISTOGRAM_COUNTS_1000("WebRTC.Video.KeyFramesReceivedInPermille", - key_frames_permille); - log_stream << "WebRTC.Video.KeyFramesReceivedInPermille " - << key_frames_permille << '\n'; - } - - absl::optional qp = qp_counters_.vp8.Avg(kMinRequiredSamples); - if (qp) { - RTC_HISTOGRAM_COUNTS_200("WebRTC.Video.Decoded.Vp8.Qp", *qp); - log_stream << "WebRTC.Video.Decoded.Vp8.Qp " << *qp << '\n'; - } - absl::optional decode_ms = decode_time_counter_.Avg(kMinRequiredSamples); - if (decode_ms) { - RTC_HISTOGRAM_COUNTS_1000("WebRTC.Video.DecodeTimeInMs", *decode_ms); - log_stream << "WebRTC.Video.DecodeTimeInMs " << *decode_ms << '\n'; - } - absl::optional jb_delay_ms = - jitter_buffer_delay_counter_.Avg(kMinRequiredSamples); - if (jb_delay_ms) { - RTC_HISTOGRAM_COUNTS_10000("WebRTC.Video.JitterBufferDelayInMs", - *jb_delay_ms); - log_stream << "WebRTC.Video.JitterBufferDelayInMs " << *jb_delay_ms << '\n'; - } - - absl::optional target_delay_ms = - target_delay_counter_.Avg(kMinRequiredSamples); - if (target_delay_ms) { - RTC_HISTOGRAM_COUNTS_10000("WebRTC.Video.TargetDelayInMs", - *target_delay_ms); - log_stream << "WebRTC.Video.TargetDelayInMs " << *target_delay_ms << '\n'; - } - absl::optional current_delay_ms = - current_delay_counter_.Avg(kMinRequiredSamples); - if (current_delay_ms) { - RTC_HISTOGRAM_COUNTS_10000("WebRTC.Video.CurrentDelayInMs", - *current_delay_ms); - log_stream << "WebRTC.Video.CurrentDelayInMs " << *current_delay_ms << '\n'; - } - absl::optional delay_ms = delay_counter_.Avg(kMinRequiredSamples); - if (delay_ms) - RTC_HISTOGRAM_COUNTS_10000("WebRTC.Video.OnewayDelayInMs", *delay_ms); - - // Aggregate content_specific_stats_ by removing experiment or simulcast - // information; - std::map aggregated_stats; - for (const auto& it : content_specific_stats_) { - // Calculate simulcast specific metrics (".S0" ... ".S2" suffixes). - VideoContentType content_type = it.first; - if (videocontenttypehelpers::GetSimulcastId(content_type) > 0) { - // Aggregate on experiment id. - videocontenttypehelpers::SetExperimentId(&content_type, 0); - aggregated_stats[content_type].Add(it.second); - } - // Calculate experiment specific metrics (".ExperimentGroup[0-7]" suffixes). - content_type = it.first; - if (videocontenttypehelpers::GetExperimentId(content_type) > 0) { - // Aggregate on simulcast id. - videocontenttypehelpers::SetSimulcastId(&content_type, 0); - aggregated_stats[content_type].Add(it.second); - } - // Calculate aggregated metrics (no suffixes. Aggregated on everything). - content_type = it.first; - videocontenttypehelpers::SetSimulcastId(&content_type, 0); - videocontenttypehelpers::SetExperimentId(&content_type, 0); - aggregated_stats[content_type].Add(it.second); - } - - for (const auto& it : aggregated_stats) { - // For the metric Foo we report the following slices: - // WebRTC.Video.Foo, - // WebRTC.Video.Screenshare.Foo, - // WebRTC.Video.Foo.S[0-3], - // WebRTC.Video.Foo.ExperimentGroup[0-7], - // WebRTC.Video.Screenshare.Foo.S[0-3], - // WebRTC.Video.Screenshare.Foo.ExperimentGroup[0-7]. - auto content_type = it.first; - auto stats = it.second; - std::string uma_prefix = UmaPrefixForContentType(content_type); - std::string uma_suffix = UmaSuffixForContentType(content_type); - // Metrics can be sliced on either simulcast id or experiment id but not - // both. - RTC_DCHECK(videocontenttypehelpers::GetExperimentId(content_type) == 0 || - videocontenttypehelpers::GetSimulcastId(content_type) == 0); - - absl::optional e2e_delay_ms = - stats.e2e_delay_counter.Avg(kMinRequiredSamples); - if (e2e_delay_ms) { - RTC_HISTOGRAM_COUNTS_SPARSE_10000( - uma_prefix + ".EndToEndDelayInMs" + uma_suffix, *e2e_delay_ms); - log_stream << uma_prefix << ".EndToEndDelayInMs" << uma_suffix << " " - << *e2e_delay_ms << '\n'; - } - absl::optional e2e_delay_max_ms = stats.e2e_delay_counter.Max(); - if (e2e_delay_max_ms && e2e_delay_ms) { - RTC_HISTOGRAM_COUNTS_SPARSE_100000( - uma_prefix + ".EndToEndDelayMaxInMs" + uma_suffix, *e2e_delay_max_ms); - log_stream << uma_prefix << ".EndToEndDelayMaxInMs" << uma_suffix << " " - << *e2e_delay_max_ms << '\n'; - } - absl::optional interframe_delay_ms = - stats.interframe_delay_counter.Avg(kMinRequiredSamples); - if (interframe_delay_ms) { - RTC_HISTOGRAM_COUNTS_SPARSE_10000( - uma_prefix + ".InterframeDelayInMs" + uma_suffix, - *interframe_delay_ms); - log_stream << uma_prefix << ".InterframeDelayInMs" << uma_suffix << " " - << *interframe_delay_ms << '\n'; - } - absl::optional interframe_delay_max_ms = - stats.interframe_delay_counter.Max(); - if (interframe_delay_max_ms && interframe_delay_ms) { - RTC_HISTOGRAM_COUNTS_SPARSE_10000( - uma_prefix + ".InterframeDelayMaxInMs" + uma_suffix, - *interframe_delay_max_ms); - log_stream << uma_prefix << ".InterframeDelayMaxInMs" << uma_suffix << " " - << *interframe_delay_max_ms << '\n'; - } - - absl::optional interframe_delay_95p_ms = - stats.interframe_delay_percentiles.GetPercentile(0.95f); - if (interframe_delay_95p_ms && interframe_delay_ms != -1) { - RTC_HISTOGRAM_COUNTS_SPARSE_10000( - uma_prefix + ".InterframeDelay95PercentileInMs" + uma_suffix, - *interframe_delay_95p_ms); - log_stream << uma_prefix << ".InterframeDelay95PercentileInMs" - << uma_suffix << " " << *interframe_delay_95p_ms << '\n'; - } - - absl::optional width = stats.received_width.Avg(kMinRequiredSamples); - if (width) { - RTC_HISTOGRAM_COUNTS_SPARSE_10000( - uma_prefix + ".ReceivedWidthInPixels" + uma_suffix, *width); - log_stream << uma_prefix << ".ReceivedWidthInPixels" << uma_suffix << " " - << *width << '\n'; - } - - absl::optional height = stats.received_height.Avg(kMinRequiredSamples); - if (height) { - RTC_HISTOGRAM_COUNTS_SPARSE_10000( - uma_prefix + ".ReceivedHeightInPixels" + uma_suffix, *height); - log_stream << uma_prefix << ".ReceivedHeightInPixels" << uma_suffix << " " - << *height << '\n'; - } - - if (content_type != VideoContentType::UNSPECIFIED) { - // Don't report these 3 metrics unsliced, as more precise variants - // are reported separately in this method. - float flow_duration_sec = stats.flow_duration_ms / 1000.0; - if (flow_duration_sec >= metrics::kMinRunTimeInSeconds) { - int media_bitrate_kbps = static_cast(stats.total_media_bytes * 8 / - flow_duration_sec / 1000); - RTC_HISTOGRAM_COUNTS_SPARSE_10000( - uma_prefix + ".MediaBitrateReceivedInKbps" + uma_suffix, - media_bitrate_kbps); - log_stream << uma_prefix << ".MediaBitrateReceivedInKbps" << uma_suffix - << " " << media_bitrate_kbps << '\n'; - } - - int num_total_frames2 = - stats.frame_counts.key_frames + stats.frame_counts.delta_frames; - if (num_total_frames2 >= kMinRequiredSamples) { - int num_key_frames = stats.frame_counts.key_frames; - int key_frames_permille = - (num_key_frames * 1000 + num_total_frames2 / 2) / num_total_frames2; - RTC_HISTOGRAM_COUNTS_SPARSE_1000( - uma_prefix + ".KeyFramesReceivedInPermille" + uma_suffix, - key_frames_permille); - log_stream << uma_prefix << ".KeyFramesReceivedInPermille" << uma_suffix - << " " << key_frames_permille << '\n'; - } - - absl::optional qp2 = stats.qp_counter.Avg(kMinRequiredSamples); - if (qp2) { - RTC_HISTOGRAM_COUNTS_SPARSE_200( - uma_prefix + ".Decoded.Vp8.Qp" + uma_suffix, *qp2); - log_stream << uma_prefix << ".Decoded.Vp8.Qp" << uma_suffix << " " - << *qp2 << '\n'; - } - } - } - - StreamDataCounters rtp_rtx_stats = rtp_stats; - if (rtx_stats) - rtp_rtx_stats.Add(*rtx_stats); - int64_t elapsed_sec = - rtp_rtx_stats.TimeSinceFirstPacketInMs(clock_->TimeInMilliseconds()) / - 1000; - if (elapsed_sec >= metrics::kMinRunTimeInSeconds) { - RTC_HISTOGRAM_COUNTS_10000( - "WebRTC.Video.BitrateReceivedInKbps", - static_cast(rtp_rtx_stats.transmitted.TotalBytes() * 8 / - elapsed_sec / 1000)); - int media_bitrate_kbs = static_cast(rtp_stats.MediaPayloadBytes() * 8 / - elapsed_sec / 1000); - RTC_HISTOGRAM_COUNTS_10000("WebRTC.Video.MediaBitrateReceivedInKbps", - media_bitrate_kbs); - log_stream << "WebRTC.Video.MediaBitrateReceivedInKbps " - << media_bitrate_kbs << '\n'; - RTC_HISTOGRAM_COUNTS_10000( - "WebRTC.Video.PaddingBitrateReceivedInKbps", - static_cast(rtp_rtx_stats.transmitted.padding_bytes * 8 / - elapsed_sec / 1000)); - RTC_HISTOGRAM_COUNTS_10000( - "WebRTC.Video.RetransmittedBitrateReceivedInKbps", - static_cast(rtp_rtx_stats.retransmitted.TotalBytes() * 8 / - elapsed_sec / 1000)); - if (rtx_stats) { - RTC_HISTOGRAM_COUNTS_10000( - "WebRTC.Video.RtxBitrateReceivedInKbps", - static_cast(rtx_stats->transmitted.TotalBytes() * 8 / - elapsed_sec / 1000)); - } - const RtcpPacketTypeCounter& counters = stats_.rtcp_packet_type_counts; - RTC_HISTOGRAM_COUNTS_10000("WebRTC.Video.NackPacketsSentPerMinute", - counters.nack_packets * 60 / elapsed_sec); - RTC_HISTOGRAM_COUNTS_10000("WebRTC.Video.FirPacketsSentPerMinute", - counters.fir_packets * 60 / elapsed_sec); - RTC_HISTOGRAM_COUNTS_10000("WebRTC.Video.PliPacketsSentPerMinute", - counters.pli_packets * 60 / elapsed_sec); - if (counters.nack_requests > 0) { - RTC_HISTOGRAM_PERCENTAGE("WebRTC.Video.UniqueNackRequestsSentInPercent", - counters.UniqueNackRequestsInPercent()); - } - } - - if (num_certain_states_ >= kBadCallMinRequiredSamples) { - RTC_HISTOGRAM_PERCENTAGE("WebRTC.Video.BadCall.Any", - 100 * num_bad_states_ / num_certain_states_); - } - absl::optional fps_fraction = - fps_threshold_.FractionHigh(kBadCallMinRequiredSamples); - if (fps_fraction) { - RTC_HISTOGRAM_PERCENTAGE("WebRTC.Video.BadCall.FrameRate", - static_cast(100 * (1 - *fps_fraction))); - } - absl::optional variance_fraction = - variance_threshold_.FractionHigh(kBadCallMinRequiredSamples); - if (variance_fraction) { - RTC_HISTOGRAM_PERCENTAGE("WebRTC.Video.BadCall.FrameRateVariance", - static_cast(100 * *variance_fraction)); - } - absl::optional qp_fraction = - qp_threshold_.FractionHigh(kBadCallMinRequiredSamples); - if (qp_fraction) { - RTC_HISTOGRAM_PERCENTAGE("WebRTC.Video.BadCall.Qp", - static_cast(100 * *qp_fraction)); - } - - RTC_LOG(LS_INFO) << log_stream.str(); - video_quality_observer_->UpdateHistograms(); -} - -void ReceiveStatisticsProxy::QualitySample() { - int64_t now = clock_->TimeInMilliseconds(); - if (last_sample_time_ + kMinSampleLengthMs > now) - return; - - double fps = - render_fps_tracker_.ComputeRateForInterval(now - last_sample_time_); - absl::optional qp = qp_sample_.Avg(1); - - bool prev_fps_bad = !fps_threshold_.IsHigh().value_or(true); - bool prev_qp_bad = qp_threshold_.IsHigh().value_or(false); - bool prev_variance_bad = variance_threshold_.IsHigh().value_or(false); - bool prev_any_bad = prev_fps_bad || prev_qp_bad || prev_variance_bad; - - fps_threshold_.AddMeasurement(static_cast(fps)); - if (qp) - qp_threshold_.AddMeasurement(*qp); - absl::optional fps_variance_opt = fps_threshold_.CalculateVariance(); - double fps_variance = fps_variance_opt.value_or(0); - if (fps_variance_opt) { - variance_threshold_.AddMeasurement(static_cast(fps_variance)); - } - - bool fps_bad = !fps_threshold_.IsHigh().value_or(true); - bool qp_bad = qp_threshold_.IsHigh().value_or(false); - bool variance_bad = variance_threshold_.IsHigh().value_or(false); - bool any_bad = fps_bad || qp_bad || variance_bad; - - if (!prev_any_bad && any_bad) { - RTC_LOG(LS_INFO) << "Bad call (any) start: " << now; - } else if (prev_any_bad && !any_bad) { - RTC_LOG(LS_INFO) << "Bad call (any) end: " << now; - } - - if (!prev_fps_bad && fps_bad) { - RTC_LOG(LS_INFO) << "Bad call (fps) start: " << now; - } else if (prev_fps_bad && !fps_bad) { - RTC_LOG(LS_INFO) << "Bad call (fps) end: " << now; - } - - if (!prev_qp_bad && qp_bad) { - RTC_LOG(LS_INFO) << "Bad call (qp) start: " << now; - } else if (prev_qp_bad && !qp_bad) { - RTC_LOG(LS_INFO) << "Bad call (qp) end: " << now; - } - - if (!prev_variance_bad && variance_bad) { - RTC_LOG(LS_INFO) << "Bad call (variance) start: " << now; - } else if (prev_variance_bad && !variance_bad) { - RTC_LOG(LS_INFO) << "Bad call (variance) end: " << now; - } - - RTC_LOG(LS_VERBOSE) << "SAMPLE: sample_length: " << (now - last_sample_time_) - << " fps: " << fps << " fps_bad: " << fps_bad - << " qp: " << qp.value_or(-1) << " qp_bad: " << qp_bad - << " variance_bad: " << variance_bad - << " fps_variance: " << fps_variance; - - last_sample_time_ = now; - qp_sample_.Reset(); - - if (fps_threshold_.IsHigh() || variance_threshold_.IsHigh() || - qp_threshold_.IsHigh()) { - if (any_bad) - ++num_bad_states_; - ++num_certain_states_; - } -} - -void ReceiveStatisticsProxy::UpdateFramerate(int64_t now_ms) const { - int64_t old_frames_ms = now_ms - kRateStatisticsWindowSizeMs; - while (!frame_window_.empty() && - frame_window_.begin()->first < old_frames_ms) { - frame_window_.erase(frame_window_.begin()); - } - - size_t framerate = - (frame_window_.size() * 1000 + 500) / kRateStatisticsWindowSizeMs; - stats_.network_frame_rate = static_cast(framerate); -} - -void ReceiveStatisticsProxy::UpdateDecodeTimeHistograms( - int width, - int height, - int decode_time_ms) const { - bool is_4k = (width == 3840 || width == 4096) && height == 2160; - bool is_hd = width == 1920 && height == 1080; - // Only update histograms for 4k/HD and VP9/H264. - if ((is_4k || is_hd) && (last_codec_type_ == kVideoCodecVP9 || - last_codec_type_ == kVideoCodecH264)) { - const std::string kDecodeTimeUmaPrefix = - "WebRTC.Video.DecodeTimePerFrameInMs."; - - // Each histogram needs its own line for it to not be reused in the wrong - // way when the format changes. - if (last_codec_type_ == kVideoCodecVP9) { - bool is_sw_decoder = - stats_.decoder_implementation_name.compare(0, 6, "libvpx") == 0; - if (is_4k) { - if (is_sw_decoder) - RTC_HISTOGRAM_COUNTS_1000(kDecodeTimeUmaPrefix + "Vp9.4k.Sw", - decode_time_ms); - else - RTC_HISTOGRAM_COUNTS_1000(kDecodeTimeUmaPrefix + "Vp9.4k.Hw", - decode_time_ms); - } else { - if (is_sw_decoder) - RTC_HISTOGRAM_COUNTS_1000(kDecodeTimeUmaPrefix + "Vp9.Hd.Sw", - decode_time_ms); - else - RTC_HISTOGRAM_COUNTS_1000(kDecodeTimeUmaPrefix + "Vp9.Hd.Hw", - decode_time_ms); - } - } else { - bool is_sw_decoder = - stats_.decoder_implementation_name.compare(0, 6, "FFmpeg") == 0; - if (is_4k) { - if (is_sw_decoder) - RTC_HISTOGRAM_COUNTS_1000(kDecodeTimeUmaPrefix + "H264.4k.Sw", - decode_time_ms); - else - RTC_HISTOGRAM_COUNTS_1000(kDecodeTimeUmaPrefix + "H264.4k.Hw", - decode_time_ms); - - } else { - if (is_sw_decoder) - RTC_HISTOGRAM_COUNTS_1000(kDecodeTimeUmaPrefix + "H264.Hd.Sw", - decode_time_ms); - else - RTC_HISTOGRAM_COUNTS_1000(kDecodeTimeUmaPrefix + "H264.Hd.Hw", - decode_time_ms); - } - } - } -} - -absl::optional -ReceiveStatisticsProxy::GetCurrentEstimatedPlayoutNtpTimestampMs( - int64_t now_ms) const { - if (!last_estimated_playout_ntp_timestamp_ms_ || - !last_estimated_playout_time_ms_) { - return absl::nullopt; - } - int64_t elapsed_ms = now_ms - *last_estimated_playout_time_ms_; - return *last_estimated_playout_ntp_timestamp_ms_ + elapsed_ms; -} - -VideoReceiveStreamInterface::Stats ReceiveStatisticsProxy::GetStats() const { - MutexLock lock(&mutex_); - // Get current frame rates here, as only updating them on new frames prevents - // us from ever correctly displaying frame rate of 0. - int64_t now_ms = clock_->TimeInMilliseconds(); - UpdateFramerate(now_ms); - stats_.render_frame_rate = renders_fps_estimator_.Rate(now_ms).value_or(0); - stats_.decode_frame_rate = decode_fps_estimator_.Rate(now_ms).value_or(0); - stats_.interframe_delay_max_ms = - interframe_delay_max_moving_.Max(now_ms).value_or(-1); - stats_.freeze_count = video_quality_observer_->NumFreezes(); - stats_.pause_count = video_quality_observer_->NumPauses(); - stats_.total_freezes_duration_ms = - video_quality_observer_->TotalFreezesDurationMs(); - stats_.total_pauses_duration_ms = - video_quality_observer_->TotalPausesDurationMs(); - stats_.total_frames_duration_ms = - video_quality_observer_->TotalFramesDurationMs(); - stats_.sum_squared_frame_durations = - video_quality_observer_->SumSquaredFrameDurationsSec(); - stats_.content_type = last_content_type_; - stats_.timing_frame_info = timing_frame_info_counter_.Max(now_ms); - stats_.jitter_buffer_delay_seconds = - static_cast(current_delay_counter_.Sum(1).value_or(0)) / - rtc::kNumMillisecsPerSec; - stats_.jitter_buffer_emitted_count = current_delay_counter_.NumSamples(); - stats_.estimated_playout_ntp_timestamp_ms = - GetCurrentEstimatedPlayoutNtpTimestampMs(now_ms); - return stats_; -} - -void ReceiveStatisticsProxy::OnIncomingPayloadType(int payload_type) { - MutexLock lock(&mutex_); - stats_.current_payload_type = payload_type; -} - -void ReceiveStatisticsProxy::OnDecoderImplementationName( - const char* implementation_name) { - MutexLock lock(&mutex_); - stats_.decoder_implementation_name = implementation_name; -} - -void ReceiveStatisticsProxy::OnFrameBufferTimingsUpdated( - int max_decode_ms, - int current_delay_ms, - int target_delay_ms, - int jitter_buffer_ms, - int min_playout_delay_ms, - int render_delay_ms) { - MutexLock lock(&mutex_); - stats_.max_decode_ms = max_decode_ms; - stats_.current_delay_ms = current_delay_ms; - stats_.target_delay_ms = target_delay_ms; - stats_.jitter_buffer_ms = jitter_buffer_ms; - stats_.min_playout_delay_ms = min_playout_delay_ms; - stats_.render_delay_ms = render_delay_ms; - jitter_buffer_delay_counter_.Add(jitter_buffer_ms); - target_delay_counter_.Add(target_delay_ms); - current_delay_counter_.Add(current_delay_ms); - // Network delay (rtt/2) + target_delay_ms (jitter delay + decode time + - // render delay). - delay_counter_.Add(target_delay_ms + avg_rtt_ms_ / 2); -} - -void ReceiveStatisticsProxy::OnUniqueFramesCounted(int num_unique_frames) { - MutexLock lock(&mutex_); - num_unique_frames_.emplace(num_unique_frames); -} - -void ReceiveStatisticsProxy::OnTimingFrameInfoUpdated( - const TimingFrameInfo& info) { - MutexLock lock(&mutex_); - if (info.flags != VideoSendTiming::kInvalid) { - int64_t now_ms = clock_->TimeInMilliseconds(); - timing_frame_info_counter_.Add(info, now_ms); - } - - // Measure initial decoding latency between the first frame arriving and the - // first frame being decoded. - if (!first_frame_received_time_ms_.has_value()) { - first_frame_received_time_ms_ = info.receive_finish_ms; - } - if (stats_.first_frame_received_to_decoded_ms == -1 && - first_decoded_frame_time_ms_) { - stats_.first_frame_received_to_decoded_ms = - *first_decoded_frame_time_ms_ - *first_frame_received_time_ms_; - } -} - -void ReceiveStatisticsProxy::RtcpPacketTypesCounterUpdated( - uint32_t ssrc, - const RtcpPacketTypeCounter& packet_counter) { - MutexLock lock(&mutex_); - if (stats_.ssrc != ssrc) - return; - stats_.rtcp_packet_type_counts = packet_counter; -} - -void ReceiveStatisticsProxy::OnCname(uint32_t ssrc, absl::string_view cname) { - MutexLock lock(&mutex_); - // TODO(pbos): Handle both local and remote ssrcs here and RTC_DCHECK that we - // receive stats from one of them. - if (stats_.ssrc != ssrc) - return; - stats_.c_name = std::string(cname); -} - -void ReceiveStatisticsProxy::OnDecodedFrame(const VideoFrame& frame, - absl::optional qp, - int32_t decode_time_ms, - VideoContentType content_type) { - MutexLock lock(&mutex_); - - uint64_t now_ms = clock_->TimeInMilliseconds(); - - if (videocontenttypehelpers::IsScreenshare(content_type) != - videocontenttypehelpers::IsScreenshare(last_content_type_)) { - // Reset the quality observer if content type is switched. But first report - // stats for the previous part of the call. - video_quality_observer_->UpdateHistograms(); - video_quality_observer_.reset(new VideoQualityObserver(content_type)); - } - - video_quality_observer_->OnDecodedFrame(frame, qp, last_codec_type_); - - ContentSpecificStats* content_specific_stats = - &content_specific_stats_[content_type]; - ++stats_.frames_decoded; - if (qp) { - if (!stats_.qp_sum) { - if (stats_.frames_decoded != 1) { - RTC_LOG(LS_WARNING) - << "Frames decoded was not 1 when first qp value was received."; - } - stats_.qp_sum = 0; - } - *stats_.qp_sum += *qp; - content_specific_stats->qp_counter.Add(*qp); - } else if (stats_.qp_sum) { - RTC_LOG(LS_WARNING) - << "QP sum was already set and no QP was given for a frame."; - stats_.qp_sum.reset(); - } - decode_time_counter_.Add(decode_time_ms); - stats_.decode_ms = decode_time_ms; - stats_.total_decode_time_ms += decode_time_ms; - if (enable_decode_time_histograms_) { - UpdateDecodeTimeHistograms(frame.width(), frame.height(), decode_time_ms); - } - - last_content_type_ = content_type; - decode_fps_estimator_.Update(1, now_ms); - if (last_decoded_frame_time_ms_) { - int64_t interframe_delay_ms = now_ms - *last_decoded_frame_time_ms_; - RTC_DCHECK_GE(interframe_delay_ms, 0); - double interframe_delay = interframe_delay_ms / 1000.0; - stats_.total_inter_frame_delay += interframe_delay; - stats_.total_squared_inter_frame_delay += - interframe_delay * interframe_delay; - interframe_delay_max_moving_.Add(interframe_delay_ms, now_ms); - content_specific_stats->interframe_delay_counter.Add(interframe_delay_ms); - content_specific_stats->interframe_delay_percentiles.Add( - interframe_delay_ms); - content_specific_stats->flow_duration_ms += interframe_delay_ms; - } - if (stats_.frames_decoded == 1) { - first_decoded_frame_time_ms_.emplace(now_ms); - } - last_decoded_frame_time_ms_.emplace(now_ms); -} - -void ReceiveStatisticsProxy::OnRenderedFrame(const VideoFrame& frame) { - int width = frame.width(); - int height = frame.height(); - RTC_DCHECK_GT(width, 0); - RTC_DCHECK_GT(height, 0); - int64_t now_ms = clock_->TimeInMilliseconds(); - MutexLock lock(&mutex_); - - video_quality_observer_->OnRenderedFrame(frame, now_ms); - - ContentSpecificStats* content_specific_stats = - &content_specific_stats_[last_content_type_]; - renders_fps_estimator_.Update(1, now_ms); - ++stats_.frames_rendered; - stats_.width = width; - stats_.height = height; - render_fps_tracker_.AddSamples(1); - render_pixel_tracker_.AddSamples(sqrt(width * height)); - content_specific_stats->received_width.Add(width); - content_specific_stats->received_height.Add(height); - - // Consider taking stats_.render_delay_ms into account. - const int64_t time_until_rendering_ms = frame.render_time_ms() - now_ms; - if (time_until_rendering_ms < 0) { - sum_missed_render_deadline_ms_ += -time_until_rendering_ms; - ++num_delayed_frames_rendered_; - } - - if (frame.ntp_time_ms() > 0) { - int64_t delay_ms = clock_->CurrentNtpInMilliseconds() - frame.ntp_time_ms(); - if (delay_ms >= 0) { - content_specific_stats->e2e_delay_counter.Add(delay_ms); - } - } - QualitySample(); -} - -void ReceiveStatisticsProxy::OnSyncOffsetUpdated(int64_t video_playout_ntp_ms, - int64_t sync_offset_ms, - double estimated_freq_khz) { - MutexLock lock(&mutex_); - sync_offset_counter_.Add(std::abs(sync_offset_ms)); - stats_.sync_offset_ms = sync_offset_ms; - last_estimated_playout_ntp_timestamp_ms_ = video_playout_ntp_ms; - last_estimated_playout_time_ms_ = clock_->TimeInMilliseconds(); - - const double kMaxFreqKhz = 10000.0; - int offset_khz = kMaxFreqKhz; - // Should not be zero or negative. If so, report max. - if (estimated_freq_khz < kMaxFreqKhz && estimated_freq_khz > 0.0) - offset_khz = static_cast(std::fabs(estimated_freq_khz - 90.0) + 0.5); - - freq_offset_counter_.Add(offset_khz); -} - -void ReceiveStatisticsProxy::OnCompleteFrame(bool is_keyframe, - size_t size_bytes, - VideoContentType content_type) { - MutexLock lock(&mutex_); - if (is_keyframe) { - ++stats_.frame_counts.key_frames; - } else { - ++stats_.frame_counts.delta_frames; - } - - // Content type extension is set only for keyframes and should be propagated - // for all the following delta frames. Here we may receive frames out of order - // and miscategorise some delta frames near the layer switch. - // This may slightly offset calculated bitrate and keyframes permille metrics. - VideoContentType propagated_content_type = - is_keyframe ? content_type : last_content_type_; - - ContentSpecificStats* content_specific_stats = - &content_specific_stats_[propagated_content_type]; - - content_specific_stats->total_media_bytes += size_bytes; - if (is_keyframe) { - ++content_specific_stats->frame_counts.key_frames; - } else { - ++content_specific_stats->frame_counts.delta_frames; - } - - int64_t now_ms = clock_->TimeInMilliseconds(); - frame_window_.insert(std::make_pair(now_ms, size_bytes)); - UpdateFramerate(now_ms); -} - -void ReceiveStatisticsProxy::OnDroppedFrames(uint32_t frames_dropped) { - MutexLock lock(&mutex_); - stats_.frames_dropped += frames_dropped; -} - -void ReceiveStatisticsProxy::OnPreDecode(VideoCodecType codec_type, int qp) { - RTC_DCHECK_RUN_ON(&decode_thread_); - MutexLock lock(&mutex_); - last_codec_type_ = codec_type; - if (last_codec_type_ == kVideoCodecVP8 && qp != -1) { - qp_counters_.vp8.Add(qp); - qp_sample_.Add(qp); - } -} - -void ReceiveStatisticsProxy::OnStreamInactive() { - // TODO(sprang): Figure out any other state that should be reset. - - MutexLock lock(&mutex_); - // Don't report inter-frame delay if stream was paused. - last_decoded_frame_time_ms_.reset(); - video_quality_observer_->OnStreamInactive(); -} - -void ReceiveStatisticsProxy::OnRttUpdate(int64_t avg_rtt_ms, - int64_t max_rtt_ms) { - MutexLock lock(&mutex_); - avg_rtt_ms_ = avg_rtt_ms; -} - -void ReceiveStatisticsProxy::DecoderThreadStarting() { - RTC_DCHECK_RUN_ON(&main_thread_); -} - -void ReceiveStatisticsProxy::DecoderThreadStopped() { - RTC_DCHECK_RUN_ON(&main_thread_); - decode_thread_.Detach(); -} - -ReceiveStatisticsProxy::ContentSpecificStats::ContentSpecificStats() - : interframe_delay_percentiles(kMaxCommonInterframeDelayMs) {} - -ReceiveStatisticsProxy::ContentSpecificStats::~ContentSpecificStats() = default; - -void ReceiveStatisticsProxy::ContentSpecificStats::Add( - const ContentSpecificStats& other) { - e2e_delay_counter.Add(other.e2e_delay_counter); - interframe_delay_counter.Add(other.interframe_delay_counter); - flow_duration_ms += other.flow_duration_ms; - total_media_bytes += other.total_media_bytes; - received_height.Add(other.received_height); - received_width.Add(other.received_width); - qp_counter.Add(other.qp_counter); - frame_counts.key_frames += other.frame_counts.key_frames; - frame_counts.delta_frames += other.frame_counts.delta_frames; - interframe_delay_percentiles.Add(other.interframe_delay_percentiles); -} -} // namespace webrtc diff --git a/video/receive_statistics_proxy.h b/video/receive_statistics_proxy.h deleted file mode 100644 index f0b5148c02..0000000000 --- a/video/receive_statistics_proxy.h +++ /dev/null @@ -1,199 +0,0 @@ -/* - * Copyright (c) 2013 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 VIDEO_RECEIVE_STATISTICS_PROXY_H_ -#define VIDEO_RECEIVE_STATISTICS_PROXY_H_ - -#include -#include -#include -#include - -#include "absl/types/optional.h" -#include "api/field_trials_view.h" -#include "api/sequence_checker.h" -#include "call/video_receive_stream.h" -#include "modules/include/module_common_types.h" -#include "modules/video_coding/include/video_coding_defines.h" -#include "rtc_base/numerics/histogram_percentile_counter.h" -#include "rtc_base/numerics/moving_max_counter.h" -#include "rtc_base/numerics/sample_counter.h" -#include "rtc_base/rate_statistics.h" -#include "rtc_base/rate_tracker.h" -#include "rtc_base/synchronization/mutex.h" -#include "rtc_base/thread_annotations.h" -#include "video/quality_threshold.h" -#include "video/stats_counter.h" -#include "video/video_quality_observer.h" - -namespace webrtc { - -class Clock; -struct CodecSpecificInfo; - -class ReceiveStatisticsProxy : public VCMReceiveStatisticsCallback, - public RtcpCnameCallback, - public RtcpPacketTypeCounterObserver, - public CallStatsObserver { - public: - ReceiveStatisticsProxy(uint32_t remote_ssrc, - Clock* clock, - const FieldTrialsView* field_trials = nullptr); - ~ReceiveStatisticsProxy() = default; - - VideoReceiveStreamInterface::Stats GetStats() const; - - void OnDecodedFrame(const VideoFrame& frame, - absl::optional qp, - int32_t decode_time_ms, - VideoContentType content_type); - void OnSyncOffsetUpdated(int64_t video_playout_ntp_ms, - int64_t sync_offset_ms, - double estimated_freq_khz); - void OnRenderedFrame(const VideoFrame& frame); - void OnIncomingPayloadType(int payload_type); - void OnDecoderImplementationName(const char* implementation_name); - - void OnPreDecode(VideoCodecType codec_type, int qp); - - void OnUniqueFramesCounted(int num_unique_frames); - - // Indicates video stream has been paused (no incoming packets). - void OnStreamInactive(); - - // Overrides VCMReceiveStatisticsCallback. - void OnCompleteFrame(bool is_keyframe, - size_t size_bytes, - VideoContentType content_type) override; - void OnDroppedFrames(uint32_t frames_dropped) override; - void OnFrameBufferTimingsUpdated(int max_decode_ms, - int current_delay_ms, - int target_delay_ms, - int jitter_buffer_ms, - int min_playout_delay_ms, - int render_delay_ms) override; - - void OnTimingFrameInfoUpdated(const TimingFrameInfo& info) override; - - // Overrides RtcpCnameCallback. - void OnCname(uint32_t ssrc, absl::string_view cname) override; - - // Overrides RtcpPacketTypeCounterObserver. - void RtcpPacketTypesCounterUpdated( - uint32_t ssrc, - const RtcpPacketTypeCounter& packet_counter) override; - - // Implements CallStatsObserver. - void OnRttUpdate(int64_t avg_rtt_ms, int64_t max_rtt_ms) override; - - // Notification methods that are used to check our internal state and validate - // threading assumptions. These are called by VideoReceiveStreamInterface. - void DecoderThreadStarting(); - void DecoderThreadStopped(); - - // Produce histograms. Must be called after DecoderThreadStopped(), typically - // at the end of the call. - void UpdateHistograms(absl::optional fraction_lost, - const StreamDataCounters& rtp_stats, - const StreamDataCounters* rtx_stats); - - private: - struct QpCounters { - rtc::SampleCounter vp8; - }; - - struct ContentSpecificStats { - ContentSpecificStats(); - ~ContentSpecificStats(); - - void Add(const ContentSpecificStats& other); - - rtc::SampleCounter e2e_delay_counter; - rtc::SampleCounter interframe_delay_counter; - int64_t flow_duration_ms = 0; - int64_t total_media_bytes = 0; - rtc::SampleCounter received_width; - rtc::SampleCounter received_height; - rtc::SampleCounter qp_counter; - FrameCounts frame_counts; - rtc::HistogramPercentileCounter interframe_delay_percentiles; - }; - - void QualitySample() RTC_EXCLUSIVE_LOCKS_REQUIRED(mutex_); - - // Removes info about old frames and then updates the framerate. - void UpdateFramerate(int64_t now_ms) const - RTC_EXCLUSIVE_LOCKS_REQUIRED(mutex_); - - void UpdateDecodeTimeHistograms(int width, - int height, - int decode_time_ms) const - RTC_EXCLUSIVE_LOCKS_REQUIRED(mutex_); - - absl::optional GetCurrentEstimatedPlayoutNtpTimestampMs( - int64_t now_ms) const RTC_EXCLUSIVE_LOCKS_REQUIRED(mutex_); - - Clock* const clock_; - const int64_t start_ms_; - const bool enable_decode_time_histograms_; - - mutable Mutex mutex_; - int64_t last_sample_time_ RTC_GUARDED_BY(mutex_); - QualityThreshold fps_threshold_ RTC_GUARDED_BY(mutex_); - QualityThreshold qp_threshold_ RTC_GUARDED_BY(mutex_); - QualityThreshold variance_threshold_ RTC_GUARDED_BY(mutex_); - rtc::SampleCounter qp_sample_ RTC_GUARDED_BY(mutex_); - int num_bad_states_ RTC_GUARDED_BY(mutex_); - int num_certain_states_ RTC_GUARDED_BY(mutex_); - // Note: The `stats_.rtp_stats` member is not used or populated by this class. - mutable VideoReceiveStreamInterface::Stats stats_ RTC_GUARDED_BY(mutex_); - RateStatistics decode_fps_estimator_ RTC_GUARDED_BY(mutex_); - RateStatistics renders_fps_estimator_ RTC_GUARDED_BY(mutex_); - rtc::RateTracker render_fps_tracker_ RTC_GUARDED_BY(mutex_); - rtc::RateTracker render_pixel_tracker_ RTC_GUARDED_BY(mutex_); - rtc::SampleCounter sync_offset_counter_ RTC_GUARDED_BY(mutex_); - rtc::SampleCounter decode_time_counter_ RTC_GUARDED_BY(mutex_); - rtc::SampleCounter jitter_buffer_delay_counter_ RTC_GUARDED_BY(mutex_); - rtc::SampleCounter target_delay_counter_ RTC_GUARDED_BY(mutex_); - rtc::SampleCounter current_delay_counter_ RTC_GUARDED_BY(mutex_); - rtc::SampleCounter delay_counter_ RTC_GUARDED_BY(mutex_); - std::unique_ptr video_quality_observer_ - RTC_GUARDED_BY(mutex_); - mutable rtc::MovingMaxCounter interframe_delay_max_moving_ - RTC_GUARDED_BY(mutex_); - std::map content_specific_stats_ - RTC_GUARDED_BY(mutex_); - MaxCounter freq_offset_counter_ RTC_GUARDED_BY(mutex_); - QpCounters qp_counters_ RTC_GUARDED_BY(decode_thread_); - int64_t avg_rtt_ms_ RTC_GUARDED_BY(mutex_); - mutable std::map frame_window_ RTC_GUARDED_BY(&mutex_); - VideoContentType last_content_type_ RTC_GUARDED_BY(&mutex_); - VideoCodecType last_codec_type_ RTC_GUARDED_BY(&mutex_); - absl::optional first_frame_received_time_ms_ RTC_GUARDED_BY(&mutex_); - absl::optional first_decoded_frame_time_ms_ RTC_GUARDED_BY(&mutex_); - absl::optional last_decoded_frame_time_ms_ RTC_GUARDED_BY(&mutex_); - size_t num_delayed_frames_rendered_ RTC_GUARDED_BY(&mutex_); - int64_t sum_missed_render_deadline_ms_ RTC_GUARDED_BY(&mutex_); - // Mutable because calling Max() on MovingMaxCounter is not const. Yet it is - // called from const GetStats(). - mutable rtc::MovingMaxCounter timing_frame_info_counter_ - RTC_GUARDED_BY(&mutex_); - absl::optional num_unique_frames_ RTC_GUARDED_BY(mutex_); - absl::optional last_estimated_playout_ntp_timestamp_ms_ - RTC_GUARDED_BY(&mutex_); - absl::optional last_estimated_playout_time_ms_ - RTC_GUARDED_BY(&mutex_); - SequenceChecker decode_thread_; - SequenceChecker network_thread_; - SequenceChecker main_thread_; -}; - -} // namespace webrtc -#endif // VIDEO_RECEIVE_STATISTICS_PROXY_H_ diff --git a/video/receive_statistics_proxy_unittest.cc b/video/receive_statistics_proxy_unittest.cc deleted file mode 100644 index 1bef646f7f..0000000000 --- a/video/receive_statistics_proxy_unittest.cc +++ /dev/null @@ -1,1827 +0,0 @@ -/* - * Copyright 2016 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 "video/receive_statistics_proxy.h" - -#include -#include -#include -#include -#include - -#include "absl/types/optional.h" -#include "api/scoped_refptr.h" -#include "api/video/i420_buffer.h" -#include "api/video/video_frame.h" -#include "api/video/video_frame_buffer.h" -#include "api/video/video_rotation.h" -#include "system_wrappers/include/metrics.h" -#include "test/gtest.h" -#include "test/scoped_key_value_config.h" - -namespace webrtc { -namespace { -const int64_t kFreqOffsetProcessIntervalInMs = 40000; -const uint32_t kLocalSsrc = 123; -const uint32_t kRemoteSsrc = 456; -const int kMinRequiredSamples = 200; -const int kWidth = 1280; -const int kHeight = 720; -} // namespace - -// TODO(sakal): ReceiveStatisticsProxy is lacking unittesting. -class ReceiveStatisticsProxyTest : public ::testing::Test { - public: - explicit ReceiveStatisticsProxyTest(std::string field_trials = "") - : field_trials_(field_trials), - fake_clock_(1234), - config_(GetTestConfig()) {} - virtual ~ReceiveStatisticsProxyTest() {} - - protected: - virtual void SetUp() { - metrics::Reset(); - statistics_proxy_.reset(new ReceiveStatisticsProxy( - config_.rtp.remote_ssrc, &fake_clock_, &field_trials_)); - } - - VideoReceiveStreamInterface::Config GetTestConfig() { - VideoReceiveStreamInterface::Config config(nullptr); - config.rtp.local_ssrc = kLocalSsrc; - config.rtp.remote_ssrc = kRemoteSsrc; - return config; - } - - VideoFrame CreateFrame(int width, int height) { - return CreateVideoFrame(width, height, 0); - } - - VideoFrame CreateFrameWithRenderTimeMs(int64_t render_time_ms) { - return CreateVideoFrame(kWidth, kHeight, render_time_ms); - } - - VideoFrame CreateVideoFrame(int width, int height, int64_t render_time_ms) { - VideoFrame frame = - VideoFrame::Builder() - .set_video_frame_buffer(I420Buffer::Create(width, height)) - .set_timestamp_rtp(0) - .set_timestamp_ms(render_time_ms) - .set_rotation(kVideoRotation_0) - .build(); - frame.set_ntp_time_ms(fake_clock_.CurrentNtpInMilliseconds()); - return frame; - } - - test::ScopedKeyValueConfig field_trials_; - SimulatedClock fake_clock_; - const VideoReceiveStreamInterface::Config config_; - std::unique_ptr statistics_proxy_; -}; - -TEST_F(ReceiveStatisticsProxyTest, OnDecodedFrameIncreasesFramesDecoded) { - EXPECT_EQ(0u, statistics_proxy_->GetStats().frames_decoded); - webrtc::VideoFrame frame = CreateFrame(kWidth, kHeight); - for (uint32_t i = 1; i <= 3; ++i) { - statistics_proxy_->OnDecodedFrame(frame, absl::nullopt, 0, - VideoContentType::UNSPECIFIED); - EXPECT_EQ(i, statistics_proxy_->GetStats().frames_decoded); - } -} - -TEST_F(ReceiveStatisticsProxyTest, DecodedFpsIsReported) { - const int kFps = 20; - const int kRequiredSamples = metrics::kMinRunTimeInSeconds * kFps; - webrtc::VideoFrame frame = CreateFrame(kWidth, kHeight); - for (int i = 0; i < kRequiredSamples; ++i) { - statistics_proxy_->OnDecodedFrame(frame, absl::nullopt, 0, - VideoContentType::UNSPECIFIED); - fake_clock_.AdvanceTimeMilliseconds(1000 / kFps); - } - statistics_proxy_->UpdateHistograms(absl::nullopt, StreamDataCounters(), - nullptr); - EXPECT_METRIC_EQ(1, - metrics::NumSamples("WebRTC.Video.DecodedFramesPerSecond")); - EXPECT_METRIC_EQ( - 1, metrics::NumEvents("WebRTC.Video.DecodedFramesPerSecond", kFps)); -} - -TEST_F(ReceiveStatisticsProxyTest, DecodedFpsIsNotReportedForTooFewSamples) { - const int kFps = 20; - const int kRequiredSamples = metrics::kMinRunTimeInSeconds * kFps; - webrtc::VideoFrame frame = CreateFrame(kWidth, kHeight); - for (int i = 0; i < kRequiredSamples - 1; ++i) { - statistics_proxy_->OnDecodedFrame(frame, absl::nullopt, 0, - VideoContentType::UNSPECIFIED); - fake_clock_.AdvanceTimeMilliseconds(1000 / kFps); - } - statistics_proxy_->UpdateHistograms(absl::nullopt, StreamDataCounters(), - nullptr); - EXPECT_METRIC_EQ(0, - metrics::NumSamples("WebRTC.Video.DecodedFramesPerSecond")); -} - -TEST_F(ReceiveStatisticsProxyTest, - OnDecodedFrameWithQpDoesNotResetFramesDecodedOrTotalDecodeTime) { - EXPECT_EQ(0u, statistics_proxy_->GetStats().frames_decoded); - webrtc::VideoFrame frame = CreateFrame(kWidth, kHeight); - unsigned int expected_total_decode_time_ms = 0; - unsigned int expected_frames_decoded = 0; - for (uint32_t i = 1; i <= 3; ++i) { - statistics_proxy_->OnDecodedFrame(frame, absl::nullopt, 1, - VideoContentType::UNSPECIFIED); - expected_total_decode_time_ms += 1; - ++expected_frames_decoded; - EXPECT_EQ(expected_frames_decoded, - statistics_proxy_->GetStats().frames_decoded); - EXPECT_EQ(expected_total_decode_time_ms, - statistics_proxy_->GetStats().total_decode_time_ms); - } - statistics_proxy_->OnDecodedFrame(frame, 1u, 3, - VideoContentType::UNSPECIFIED); - ++expected_frames_decoded; - expected_total_decode_time_ms += 3; - EXPECT_EQ(expected_frames_decoded, - statistics_proxy_->GetStats().frames_decoded); - EXPECT_EQ(expected_total_decode_time_ms, - statistics_proxy_->GetStats().total_decode_time_ms); -} - -TEST_F(ReceiveStatisticsProxyTest, OnDecodedFrameIncreasesQpSum) { - EXPECT_EQ(absl::nullopt, statistics_proxy_->GetStats().qp_sum); - webrtc::VideoFrame frame = CreateFrame(kWidth, kHeight); - statistics_proxy_->OnDecodedFrame(frame, 3u, 0, - VideoContentType::UNSPECIFIED); - EXPECT_EQ(3u, statistics_proxy_->GetStats().qp_sum); - statistics_proxy_->OnDecodedFrame(frame, 127u, 0, - VideoContentType::UNSPECIFIED); - EXPECT_EQ(130u, statistics_proxy_->GetStats().qp_sum); -} - -TEST_F(ReceiveStatisticsProxyTest, OnDecodedFrameIncreasesTotalDecodeTime) { - EXPECT_EQ(absl::nullopt, statistics_proxy_->GetStats().qp_sum); - webrtc::VideoFrame frame = CreateFrame(kWidth, kHeight); - statistics_proxy_->OnDecodedFrame(frame, 3u, 4, - VideoContentType::UNSPECIFIED); - EXPECT_EQ(4u, statistics_proxy_->GetStats().total_decode_time_ms); - statistics_proxy_->OnDecodedFrame(frame, 127u, 7, - VideoContentType::UNSPECIFIED); - EXPECT_EQ(11u, statistics_proxy_->GetStats().total_decode_time_ms); -} - -TEST_F(ReceiveStatisticsProxyTest, ReportsContentType) { - const std::string kRealtimeString("realtime"); - const std::string kScreenshareString("screen"); - webrtc::VideoFrame frame = CreateFrame(kWidth, kHeight); - EXPECT_EQ(kRealtimeString, videocontenttypehelpers::ToString( - statistics_proxy_->GetStats().content_type)); - statistics_proxy_->OnDecodedFrame(frame, 3u, 0, - VideoContentType::SCREENSHARE); - EXPECT_EQ(kScreenshareString, - videocontenttypehelpers::ToString( - statistics_proxy_->GetStats().content_type)); - statistics_proxy_->OnDecodedFrame(frame, 3u, 0, - VideoContentType::UNSPECIFIED); - EXPECT_EQ(kRealtimeString, videocontenttypehelpers::ToString( - statistics_proxy_->GetStats().content_type)); -} - -TEST_F(ReceiveStatisticsProxyTest, ReportsMaxTotalInterFrameDelay) { - webrtc::VideoFrame frame = CreateFrame(kWidth, kHeight); - const TimeDelta kInterFrameDelay1 = TimeDelta::Millis(100); - const TimeDelta kInterFrameDelay2 = TimeDelta::Millis(200); - const TimeDelta kInterFrameDelay3 = TimeDelta::Millis(300); - double expected_total_inter_frame_delay = 0; - double expected_total_squared_inter_frame_delay = 0; - EXPECT_EQ(expected_total_inter_frame_delay, - statistics_proxy_->GetStats().total_inter_frame_delay); - EXPECT_EQ(expected_total_squared_inter_frame_delay, - statistics_proxy_->GetStats().total_squared_inter_frame_delay); - - statistics_proxy_->OnDecodedFrame(frame, absl::nullopt, 0, - VideoContentType::UNSPECIFIED); - EXPECT_DOUBLE_EQ(expected_total_inter_frame_delay, - statistics_proxy_->GetStats().total_inter_frame_delay); - EXPECT_DOUBLE_EQ( - expected_total_squared_inter_frame_delay, - statistics_proxy_->GetStats().total_squared_inter_frame_delay); - - fake_clock_.AdvanceTime(kInterFrameDelay1); - statistics_proxy_->OnDecodedFrame(frame, absl::nullopt, 0, - VideoContentType::UNSPECIFIED); - expected_total_inter_frame_delay += kInterFrameDelay1.seconds(); - expected_total_squared_inter_frame_delay += - pow(kInterFrameDelay1.seconds(), 2.0); - EXPECT_DOUBLE_EQ(expected_total_inter_frame_delay, - statistics_proxy_->GetStats().total_inter_frame_delay); - EXPECT_DOUBLE_EQ( - expected_total_squared_inter_frame_delay, - statistics_proxy_->GetStats().total_squared_inter_frame_delay); - - fake_clock_.AdvanceTime(kInterFrameDelay2); - statistics_proxy_->OnDecodedFrame(frame, absl::nullopt, 0, - VideoContentType::UNSPECIFIED); - expected_total_inter_frame_delay += kInterFrameDelay2.seconds(); - expected_total_squared_inter_frame_delay += - pow(kInterFrameDelay2.seconds(), 2.0); - EXPECT_DOUBLE_EQ(expected_total_inter_frame_delay, - statistics_proxy_->GetStats().total_inter_frame_delay); - EXPECT_DOUBLE_EQ( - expected_total_squared_inter_frame_delay, - statistics_proxy_->GetStats().total_squared_inter_frame_delay); - - fake_clock_.AdvanceTime(kInterFrameDelay3); - statistics_proxy_->OnDecodedFrame(frame, absl::nullopt, 0, - VideoContentType::UNSPECIFIED); - expected_total_inter_frame_delay += kInterFrameDelay3.seconds(); - expected_total_squared_inter_frame_delay += - pow(kInterFrameDelay3.seconds(), 2.0); - EXPECT_DOUBLE_EQ(expected_total_inter_frame_delay, - statistics_proxy_->GetStats().total_inter_frame_delay); - EXPECT_DOUBLE_EQ( - expected_total_squared_inter_frame_delay, - statistics_proxy_->GetStats().total_squared_inter_frame_delay); -} - -TEST_F(ReceiveStatisticsProxyTest, ReportsMaxInterframeDelay) { - webrtc::VideoFrame frame = CreateFrame(kWidth, kHeight); - const int64_t kInterframeDelayMs1 = 100; - const int64_t kInterframeDelayMs2 = 200; - const int64_t kInterframeDelayMs3 = 100; - EXPECT_EQ(-1, statistics_proxy_->GetStats().interframe_delay_max_ms); - statistics_proxy_->OnDecodedFrame(frame, absl::nullopt, 0, - VideoContentType::UNSPECIFIED); - EXPECT_EQ(-1, statistics_proxy_->GetStats().interframe_delay_max_ms); - - fake_clock_.AdvanceTimeMilliseconds(kInterframeDelayMs1); - statistics_proxy_->OnDecodedFrame(frame, absl::nullopt, 0, - VideoContentType::UNSPECIFIED); - EXPECT_EQ(kInterframeDelayMs1, - statistics_proxy_->GetStats().interframe_delay_max_ms); - - fake_clock_.AdvanceTimeMilliseconds(kInterframeDelayMs2); - statistics_proxy_->OnDecodedFrame(frame, absl::nullopt, 0, - VideoContentType::UNSPECIFIED); - EXPECT_EQ(kInterframeDelayMs2, - statistics_proxy_->GetStats().interframe_delay_max_ms); - - fake_clock_.AdvanceTimeMilliseconds(kInterframeDelayMs3); - statistics_proxy_->OnDecodedFrame(frame, absl::nullopt, 0, - VideoContentType::UNSPECIFIED); - // kInterframeDelayMs3 is smaller than kInterframeDelayMs2. - EXPECT_EQ(kInterframeDelayMs2, - statistics_proxy_->GetStats().interframe_delay_max_ms); -} - -TEST_F(ReceiveStatisticsProxyTest, ReportInterframeDelayInWindow) { - webrtc::VideoFrame frame = CreateFrame(kWidth, kHeight); - const int64_t kInterframeDelayMs1 = 900; - const int64_t kInterframeDelayMs2 = 750; - const int64_t kInterframeDelayMs3 = 700; - EXPECT_EQ(-1, statistics_proxy_->GetStats().interframe_delay_max_ms); - statistics_proxy_->OnDecodedFrame(frame, absl::nullopt, 0, - VideoContentType::UNSPECIFIED); - EXPECT_EQ(-1, statistics_proxy_->GetStats().interframe_delay_max_ms); - - fake_clock_.AdvanceTimeMilliseconds(kInterframeDelayMs1); - statistics_proxy_->OnDecodedFrame(frame, absl::nullopt, 0, - VideoContentType::UNSPECIFIED); - EXPECT_EQ(kInterframeDelayMs1, - statistics_proxy_->GetStats().interframe_delay_max_ms); - - fake_clock_.AdvanceTimeMilliseconds(kInterframeDelayMs2); - statistics_proxy_->OnDecodedFrame(frame, absl::nullopt, 0, - VideoContentType::UNSPECIFIED); - // Still first delay is the maximum - EXPECT_EQ(kInterframeDelayMs1, - statistics_proxy_->GetStats().interframe_delay_max_ms); - - fake_clock_.AdvanceTimeMilliseconds(kInterframeDelayMs3); - statistics_proxy_->OnDecodedFrame(frame, absl::nullopt, 0, - VideoContentType::UNSPECIFIED); - // Now the first sample is out of the window, so the second is the maximum. - EXPECT_EQ(kInterframeDelayMs2, - statistics_proxy_->GetStats().interframe_delay_max_ms); -} - -TEST_F(ReceiveStatisticsProxyTest, ReportsFreezeMetrics) { - const int64_t kFreezeDurationMs = 1000; - - VideoReceiveStreamInterface::Stats stats = statistics_proxy_->GetStats(); - EXPECT_EQ(0u, stats.freeze_count); - EXPECT_FALSE(stats.total_freezes_duration_ms); - - webrtc::VideoFrame frame = CreateFrame(kWidth, kHeight); - for (size_t i = 0; i < VideoQualityObserver::kMinFrameSamplesToDetectFreeze; - ++i) { - fake_clock_.AdvanceTimeMilliseconds(30); - statistics_proxy_->OnRenderedFrame(frame); - } - - // Freeze. - fake_clock_.AdvanceTimeMilliseconds(kFreezeDurationMs); - statistics_proxy_->OnRenderedFrame(frame); - - stats = statistics_proxy_->GetStats(); - EXPECT_EQ(1u, stats.freeze_count); - EXPECT_EQ(kFreezeDurationMs, stats.total_freezes_duration_ms); -} - -TEST_F(ReceiveStatisticsProxyTest, ReportsPauseMetrics) { - VideoReceiveStreamInterface::Stats stats = statistics_proxy_->GetStats(); - ASSERT_EQ(0u, stats.pause_count); - ASSERT_EQ(0u, stats.total_pauses_duration_ms); - - webrtc::VideoFrame frame = CreateFrame(kWidth, kHeight); - statistics_proxy_->OnRenderedFrame(frame); - - // Pause. - fake_clock_.AdvanceTimeMilliseconds(5432); - statistics_proxy_->OnStreamInactive(); - statistics_proxy_->OnRenderedFrame(frame); - - stats = statistics_proxy_->GetStats(); - EXPECT_EQ(1u, stats.pause_count); - EXPECT_EQ(5432u, stats.total_pauses_duration_ms); -} - -TEST_F(ReceiveStatisticsProxyTest, PauseBeforeFirstAndAfterLastFrameIgnored) { - VideoReceiveStreamInterface::Stats stats = statistics_proxy_->GetStats(); - ASSERT_EQ(0u, stats.pause_count); - ASSERT_EQ(0u, stats.total_pauses_duration_ms); - - webrtc::VideoFrame frame = CreateFrame(kWidth, kHeight); - - // Pause -> Frame -> Pause - fake_clock_.AdvanceTimeMilliseconds(5000); - statistics_proxy_->OnStreamInactive(); - statistics_proxy_->OnRenderedFrame(frame); - - fake_clock_.AdvanceTimeMilliseconds(30); - statistics_proxy_->OnRenderedFrame(frame); - - fake_clock_.AdvanceTimeMilliseconds(5000); - statistics_proxy_->OnStreamInactive(); - - stats = statistics_proxy_->GetStats(); - EXPECT_EQ(0u, stats.pause_count); - EXPECT_EQ(0u, stats.total_pauses_duration_ms); -} - -TEST_F(ReceiveStatisticsProxyTest, ReportsFramesDuration) { - VideoReceiveStreamInterface::Stats stats = statistics_proxy_->GetStats(); - ASSERT_EQ(0u, stats.total_frames_duration_ms); - - webrtc::VideoFrame frame = CreateFrame(kWidth, kHeight); - - // Emulate delay before first frame is rendered. This is needed to ensure - // that frame duration only covers time since first frame is rendered and - // not the total time. - fake_clock_.AdvanceTimeMilliseconds(5432); - - for (int i = 0; i <= 10; ++i) { - fake_clock_.AdvanceTimeMilliseconds(30); - statistics_proxy_->OnRenderedFrame(frame); - } - - stats = statistics_proxy_->GetStats(); - EXPECT_EQ(10 * 30u, stats.total_frames_duration_ms); -} - -TEST_F(ReceiveStatisticsProxyTest, ReportsSumSquaredFrameDurations) { - VideoReceiveStreamInterface::Stats stats = statistics_proxy_->GetStats(); - ASSERT_EQ(0u, stats.sum_squared_frame_durations); - - webrtc::VideoFrame frame = CreateFrame(kWidth, kHeight); - for (int i = 0; i <= 10; ++i) { - fake_clock_.AdvanceTimeMilliseconds(30); - statistics_proxy_->OnRenderedFrame(frame); - } - - stats = statistics_proxy_->GetStats(); - const double kExpectedSumSquaredFrameDurationsSecs = - 10 * (30 / 1000.0 * 30 / 1000.0); - EXPECT_EQ(kExpectedSumSquaredFrameDurationsSecs, - stats.sum_squared_frame_durations); -} - -TEST_F(ReceiveStatisticsProxyTest, OnDecodedFrameWithoutQpQpSumWontExist) { - webrtc::VideoFrame frame = CreateFrame(kWidth, kHeight); - EXPECT_EQ(absl::nullopt, statistics_proxy_->GetStats().qp_sum); - statistics_proxy_->OnDecodedFrame(frame, absl::nullopt, 0, - VideoContentType::UNSPECIFIED); - EXPECT_EQ(absl::nullopt, statistics_proxy_->GetStats().qp_sum); -} - -TEST_F(ReceiveStatisticsProxyTest, OnDecodedFrameWithoutQpResetsQpSum) { - webrtc::VideoFrame frame = CreateFrame(kWidth, kHeight); - EXPECT_EQ(absl::nullopt, statistics_proxy_->GetStats().qp_sum); - statistics_proxy_->OnDecodedFrame(frame, 3u, 0, - VideoContentType::UNSPECIFIED); - EXPECT_EQ(3u, statistics_proxy_->GetStats().qp_sum); - statistics_proxy_->OnDecodedFrame(frame, absl::nullopt, 0, - VideoContentType::UNSPECIFIED); - EXPECT_EQ(absl::nullopt, statistics_proxy_->GetStats().qp_sum); -} - -TEST_F(ReceiveStatisticsProxyTest, OnRenderedFrameIncreasesFramesRendered) { - EXPECT_EQ(0u, statistics_proxy_->GetStats().frames_rendered); - webrtc::VideoFrame frame = CreateFrame(kWidth, kHeight); - for (uint32_t i = 1; i <= 3; ++i) { - statistics_proxy_->OnRenderedFrame(frame); - EXPECT_EQ(i, statistics_proxy_->GetStats().frames_rendered); - } -} - -TEST_F(ReceiveStatisticsProxyTest, GetStatsReportsSsrc) { - EXPECT_EQ(kRemoteSsrc, statistics_proxy_->GetStats().ssrc); -} - -TEST_F(ReceiveStatisticsProxyTest, GetStatsReportsIncomingPayloadType) { - const int kPayloadType = 111; - statistics_proxy_->OnIncomingPayloadType(kPayloadType); - EXPECT_EQ(kPayloadType, statistics_proxy_->GetStats().current_payload_type); -} - -TEST_F(ReceiveStatisticsProxyTest, GetStatsReportsDecoderImplementationName) { - const char* kName = "decoderName"; - statistics_proxy_->OnDecoderImplementationName(kName); - EXPECT_STREQ( - kName, statistics_proxy_->GetStats().decoder_implementation_name.c_str()); -} - -TEST_F(ReceiveStatisticsProxyTest, GetStatsReportsOnCompleteFrame) { - const int kFrameSizeBytes = 1000; - statistics_proxy_->OnCompleteFrame(true, kFrameSizeBytes, - VideoContentType::UNSPECIFIED); - VideoReceiveStreamInterface::Stats stats = statistics_proxy_->GetStats(); - EXPECT_EQ(1, stats.network_frame_rate); - EXPECT_EQ(1, stats.frame_counts.key_frames); - EXPECT_EQ(0, stats.frame_counts.delta_frames); -} - -TEST_F(ReceiveStatisticsProxyTest, GetStatsReportsOnDroppedFrame) { - unsigned int dropped_frames = 0; - for (int i = 0; i < 10; ++i) { - statistics_proxy_->OnDroppedFrames(i); - dropped_frames += i; - } - VideoReceiveStreamInterface::Stats stats = statistics_proxy_->GetStats(); - EXPECT_EQ(dropped_frames, stats.frames_dropped); -} - -TEST_F(ReceiveStatisticsProxyTest, GetStatsReportsDecodeTimingStats) { - const int kMaxDecodeMs = 2; - const int kCurrentDelayMs = 3; - const int kTargetDelayMs = 4; - const int kJitterBufferMs = 5; - const int kMinPlayoutDelayMs = 6; - const int kRenderDelayMs = 7; - const int64_t kRttMs = 8; - statistics_proxy_->OnRttUpdate(kRttMs, 0); - statistics_proxy_->OnFrameBufferTimingsUpdated( - kMaxDecodeMs, kCurrentDelayMs, kTargetDelayMs, kJitterBufferMs, - kMinPlayoutDelayMs, kRenderDelayMs); - VideoReceiveStreamInterface::Stats stats = statistics_proxy_->GetStats(); - EXPECT_EQ(kMaxDecodeMs, stats.max_decode_ms); - EXPECT_EQ(kCurrentDelayMs, stats.current_delay_ms); - EXPECT_EQ(kTargetDelayMs, stats.target_delay_ms); - EXPECT_EQ(kJitterBufferMs, stats.jitter_buffer_ms); - EXPECT_EQ(kMinPlayoutDelayMs, stats.min_playout_delay_ms); - EXPECT_EQ(kRenderDelayMs, stats.render_delay_ms); -} - -TEST_F(ReceiveStatisticsProxyTest, GetStatsReportsRtcpPacketTypeCounts) { - const uint32_t kFirPackets = 33; - const uint32_t kPliPackets = 44; - const uint32_t kNackPackets = 55; - RtcpPacketTypeCounter counter; - counter.fir_packets = kFirPackets; - counter.pli_packets = kPliPackets; - counter.nack_packets = kNackPackets; - statistics_proxy_->RtcpPacketTypesCounterUpdated(kRemoteSsrc, counter); - VideoReceiveStreamInterface::Stats stats = statistics_proxy_->GetStats(); - EXPECT_EQ(kFirPackets, stats.rtcp_packet_type_counts.fir_packets); - EXPECT_EQ(kPliPackets, stats.rtcp_packet_type_counts.pli_packets); - EXPECT_EQ(kNackPackets, stats.rtcp_packet_type_counts.nack_packets); -} - -TEST_F(ReceiveStatisticsProxyTest, - GetStatsReportsNoRtcpPacketTypeCountsForUnknownSsrc) { - RtcpPacketTypeCounter counter; - counter.fir_packets = 33; - statistics_proxy_->RtcpPacketTypesCounterUpdated(kRemoteSsrc + 1, counter); - EXPECT_EQ(0u, - statistics_proxy_->GetStats().rtcp_packet_type_counts.fir_packets); -} - -TEST_F(ReceiveStatisticsProxyTest, GetStatsReportsFrameCounts) { - const int kKeyFrames = 3; - const int kDeltaFrames = 22; - for (int i = 0; i < kKeyFrames; i++) { - statistics_proxy_->OnCompleteFrame(true, 0, VideoContentType::UNSPECIFIED); - } - for (int i = 0; i < kDeltaFrames; i++) { - statistics_proxy_->OnCompleteFrame(false, 0, VideoContentType::UNSPECIFIED); - } - - VideoReceiveStreamInterface::Stats stats = statistics_proxy_->GetStats(); - EXPECT_EQ(kKeyFrames, stats.frame_counts.key_frames); - EXPECT_EQ(kDeltaFrames, stats.frame_counts.delta_frames); -} - -TEST_F(ReceiveStatisticsProxyTest, GetStatsReportsCName) { - const char* kName = "cName"; - statistics_proxy_->OnCname(kRemoteSsrc, kName); - EXPECT_STREQ(kName, statistics_proxy_->GetStats().c_name.c_str()); -} - -TEST_F(ReceiveStatisticsProxyTest, GetStatsReportsNoCNameForUnknownSsrc) { - const char* kName = "cName"; - statistics_proxy_->OnCname(kRemoteSsrc + 1, kName); - EXPECT_STREQ("", statistics_proxy_->GetStats().c_name.c_str()); -} - -TEST_F(ReceiveStatisticsProxyTest, ReportsLongestTimingFrameInfo) { - const int64_t kShortEndToEndDelay = 10; - const int64_t kMedEndToEndDelay = 20; - const int64_t kLongEndToEndDelay = 100; - const uint32_t kExpectedRtpTimestamp = 2; - TimingFrameInfo info; - absl::optional result; - info.rtp_timestamp = kExpectedRtpTimestamp - 1; - info.capture_time_ms = 0; - info.decode_finish_ms = kShortEndToEndDelay; - statistics_proxy_->OnTimingFrameInfoUpdated(info); - info.rtp_timestamp = - kExpectedRtpTimestamp; // this frame should be reported in the end. - info.capture_time_ms = 0; - info.decode_finish_ms = kLongEndToEndDelay; - statistics_proxy_->OnTimingFrameInfoUpdated(info); - info.rtp_timestamp = kExpectedRtpTimestamp + 1; - info.capture_time_ms = 0; - info.decode_finish_ms = kMedEndToEndDelay; - statistics_proxy_->OnTimingFrameInfoUpdated(info); - result = statistics_proxy_->GetStats().timing_frame_info; - EXPECT_TRUE(result); - EXPECT_EQ(kExpectedRtpTimestamp, result->rtp_timestamp); -} - -TEST_F(ReceiveStatisticsProxyTest, RespectsReportingIntervalForTimingFrames) { - TimingFrameInfo info; - const int64_t kShortEndToEndDelay = 10; - const uint32_t kExpectedRtpTimestamp = 2; - const int64_t kShortDelayMs = 1000; - const int64_t kLongDelayMs = 10000; - absl::optional result; - info.rtp_timestamp = kExpectedRtpTimestamp; - info.capture_time_ms = 0; - info.decode_finish_ms = kShortEndToEndDelay; - statistics_proxy_->OnTimingFrameInfoUpdated(info); - fake_clock_.AdvanceTimeMilliseconds(kShortDelayMs); - result = statistics_proxy_->GetStats().timing_frame_info; - EXPECT_TRUE(result); - EXPECT_EQ(kExpectedRtpTimestamp, result->rtp_timestamp); - fake_clock_.AdvanceTimeMilliseconds(kLongDelayMs); - result = statistics_proxy_->GetStats().timing_frame_info; - EXPECT_FALSE(result); -} - -TEST_F(ReceiveStatisticsProxyTest, LifetimeHistogramIsUpdated) { - const int64_t kTimeSec = 3; - fake_clock_.AdvanceTimeMilliseconds(kTimeSec * 1000); - // Need at least one frame to report stream lifetime. - statistics_proxy_->OnCompleteFrame(true, 1000, VideoContentType::UNSPECIFIED); - statistics_proxy_->UpdateHistograms(absl::nullopt, StreamDataCounters(), - nullptr); - EXPECT_METRIC_EQ( - 1, metrics::NumSamples("WebRTC.Video.ReceiveStreamLifetimeInSeconds")); - EXPECT_METRIC_EQ( - 1, metrics::NumEvents("WebRTC.Video.ReceiveStreamLifetimeInSeconds", - kTimeSec)); -} - -TEST_F(ReceiveStatisticsProxyTest, - LifetimeHistogramNotReportedForEmptyStreams) { - const int64_t kTimeSec = 3; - fake_clock_.AdvanceTimeMilliseconds(kTimeSec * 1000); - // No frames received. - statistics_proxy_->UpdateHistograms(absl::nullopt, StreamDataCounters(), - nullptr); - EXPECT_METRIC_EQ( - 0, metrics::NumSamples("WebRTC.Video.ReceiveStreamLifetimeInSeconds")); -} - -TEST_F(ReceiveStatisticsProxyTest, BadCallHistogramsAreUpdated) { - // Based on the tuning parameters this will produce 7 uncertain states, - // then 10 certainly bad states. There has to be 10 certain states before - // any histograms are recorded. - const int kNumBadSamples = 17; - // We only count one sample per second. - const int kBadFameIntervalMs = 1100; - - StreamDataCounters counters; - counters.first_packet_time_ms = fake_clock_.TimeInMilliseconds(); - - webrtc::VideoFrame frame = CreateFrame(kWidth, kHeight); - - for (int i = 0; i < kNumBadSamples; ++i) { - fake_clock_.AdvanceTimeMilliseconds(kBadFameIntervalMs); - statistics_proxy_->OnRenderedFrame(frame); - } - statistics_proxy_->UpdateHistograms(absl::nullopt, counters, nullptr); - EXPECT_METRIC_EQ(1, metrics::NumSamples("WebRTC.Video.BadCall.Any")); - EXPECT_METRIC_EQ(1, metrics::NumEvents("WebRTC.Video.BadCall.Any", 100)); - - EXPECT_METRIC_EQ(1, metrics::NumSamples("WebRTC.Video.BadCall.FrameRate")); - EXPECT_METRIC_EQ(1, - metrics::NumEvents("WebRTC.Video.BadCall.FrameRate", 100)); - - EXPECT_METRIC_EQ( - 0, metrics::NumSamples("WebRTC.Video.BadCall.FrameRateVariance")); - - EXPECT_METRIC_EQ(0, metrics::NumSamples("WebRTC.Video.BadCall.Qp")); -} - -TEST_F(ReceiveStatisticsProxyTest, PacketLossHistogramIsUpdated) { - statistics_proxy_->UpdateHistograms(10, StreamDataCounters(), nullptr); - EXPECT_METRIC_EQ( - 0, metrics::NumSamples("WebRTC.Video.ReceivedPacketsLostInPercent")); - - // Restart - SetUp(); - - // Min run time has passed. - fake_clock_.AdvanceTimeMilliseconds(metrics::kMinRunTimeInSeconds * 1000); - statistics_proxy_->UpdateHistograms(10, StreamDataCounters(), nullptr); - EXPECT_METRIC_EQ( - 1, metrics::NumSamples("WebRTC.Video.ReceivedPacketsLostInPercent")); - EXPECT_METRIC_EQ( - 1, metrics::NumEvents("WebRTC.Video.ReceivedPacketsLostInPercent", 10)); -} - -TEST_F(ReceiveStatisticsProxyTest, GetStatsReportsPlayoutTimestamp) { - const int64_t kVideoNtpMs = 21; - const int64_t kSyncOffsetMs = 22; - const double kFreqKhz = 90.0; - EXPECT_EQ(absl::nullopt, - statistics_proxy_->GetStats().estimated_playout_ntp_timestamp_ms); - statistics_proxy_->OnSyncOffsetUpdated(kVideoNtpMs, kSyncOffsetMs, kFreqKhz); - EXPECT_EQ(kVideoNtpMs, - statistics_proxy_->GetStats().estimated_playout_ntp_timestamp_ms); - fake_clock_.AdvanceTimeMilliseconds(13); - EXPECT_EQ(kVideoNtpMs + 13, - statistics_proxy_->GetStats().estimated_playout_ntp_timestamp_ms); - fake_clock_.AdvanceTimeMilliseconds(5); - EXPECT_EQ(kVideoNtpMs + 13 + 5, - statistics_proxy_->GetStats().estimated_playout_ntp_timestamp_ms); -} - -TEST_F(ReceiveStatisticsProxyTest, GetStatsReportsAvSyncOffset) { - const int64_t kVideoNtpMs = 21; - const int64_t kSyncOffsetMs = 22; - const double kFreqKhz = 90.0; - EXPECT_EQ(std::numeric_limits::max(), - statistics_proxy_->GetStats().sync_offset_ms); - statistics_proxy_->OnSyncOffsetUpdated(kVideoNtpMs, kSyncOffsetMs, kFreqKhz); - EXPECT_EQ(kSyncOffsetMs, statistics_proxy_->GetStats().sync_offset_ms); -} - -TEST_F(ReceiveStatisticsProxyTest, AvSyncOffsetHistogramIsUpdated) { - const int64_t kVideoNtpMs = 21; - const int64_t kSyncOffsetMs = 22; - const double kFreqKhz = 90.0; - for (int i = 0; i < kMinRequiredSamples; ++i) - statistics_proxy_->OnSyncOffsetUpdated(kVideoNtpMs, kSyncOffsetMs, - kFreqKhz); - statistics_proxy_->UpdateHistograms(absl::nullopt, StreamDataCounters(), - nullptr); - EXPECT_METRIC_EQ(1, metrics::NumSamples("WebRTC.Video.AVSyncOffsetInMs")); - EXPECT_METRIC_EQ( - 1, metrics::NumEvents("WebRTC.Video.AVSyncOffsetInMs", kSyncOffsetMs)); -} - -TEST_F(ReceiveStatisticsProxyTest, RtpToNtpFrequencyOffsetHistogramIsUpdated) { - const int64_t kVideoNtpMs = 21; - const int64_t kSyncOffsetMs = 22; - const double kFreqKhz = 90.0; - statistics_proxy_->OnSyncOffsetUpdated(kVideoNtpMs, kSyncOffsetMs, kFreqKhz); - statistics_proxy_->OnSyncOffsetUpdated(kVideoNtpMs, kSyncOffsetMs, - kFreqKhz + 2.2); - fake_clock_.AdvanceTimeMilliseconds(kFreqOffsetProcessIntervalInMs); - // Process interval passed, max diff: 2. - statistics_proxy_->OnSyncOffsetUpdated(kVideoNtpMs, kSyncOffsetMs, - kFreqKhz + 1.1); - statistics_proxy_->OnSyncOffsetUpdated(kVideoNtpMs, kSyncOffsetMs, - kFreqKhz - 4.2); - statistics_proxy_->OnSyncOffsetUpdated(kVideoNtpMs, kSyncOffsetMs, - kFreqKhz - 0.9); - fake_clock_.AdvanceTimeMilliseconds(kFreqOffsetProcessIntervalInMs); - // Process interval passed, max diff: 4. - statistics_proxy_->OnSyncOffsetUpdated(kVideoNtpMs, kSyncOffsetMs, kFreqKhz); - statistics_proxy_->UpdateHistograms(absl::nullopt, StreamDataCounters(), - nullptr); - // Average reported: (2 + 4) / 2 = 3. - EXPECT_METRIC_EQ(1, - metrics::NumSamples("WebRTC.Video.RtpToNtpFreqOffsetInKhz")); - EXPECT_METRIC_EQ( - 1, metrics::NumEvents("WebRTC.Video.RtpToNtpFreqOffsetInKhz", 3)); -} - -TEST_F(ReceiveStatisticsProxyTest, Vp8QpHistogramIsUpdated) { - const int kQp = 22; - - for (int i = 0; i < kMinRequiredSamples; ++i) - statistics_proxy_->OnPreDecode(kVideoCodecVP8, kQp); - - statistics_proxy_->UpdateHistograms(absl::nullopt, StreamDataCounters(), - nullptr); - EXPECT_METRIC_EQ(1, metrics::NumSamples("WebRTC.Video.Decoded.Vp8.Qp")); - EXPECT_METRIC_EQ(1, metrics::NumEvents("WebRTC.Video.Decoded.Vp8.Qp", kQp)); -} - -TEST_F(ReceiveStatisticsProxyTest, Vp8QpHistogramIsNotUpdatedForTooFewSamples) { - const int kQp = 22; - - for (int i = 0; i < kMinRequiredSamples - 1; ++i) - statistics_proxy_->OnPreDecode(kVideoCodecVP8, kQp); - - statistics_proxy_->UpdateHistograms(absl::nullopt, StreamDataCounters(), - nullptr); - EXPECT_METRIC_EQ(0, metrics::NumSamples("WebRTC.Video.Decoded.Vp8.Qp")); -} - -TEST_F(ReceiveStatisticsProxyTest, Vp8QpHistogramIsNotUpdatedIfNoQpValue) { - for (int i = 0; i < kMinRequiredSamples; ++i) - statistics_proxy_->OnPreDecode(kVideoCodecVP8, -1); - - statistics_proxy_->UpdateHistograms(absl::nullopt, StreamDataCounters(), - nullptr); - EXPECT_METRIC_EQ(0, metrics::NumSamples("WebRTC.Video.Decoded.Vp8.Qp")); -} - -TEST_F(ReceiveStatisticsProxyTest, - KeyFrameHistogramNotUpdatedForTooFewSamples) { - const bool kIsKeyFrame = false; - const int kFrameSizeBytes = 1000; - - for (int i = 0; i < kMinRequiredSamples - 1; ++i) - statistics_proxy_->OnCompleteFrame(kIsKeyFrame, kFrameSizeBytes, - VideoContentType::UNSPECIFIED); - - EXPECT_EQ(0, statistics_proxy_->GetStats().frame_counts.key_frames); - EXPECT_EQ(kMinRequiredSamples - 1, - statistics_proxy_->GetStats().frame_counts.delta_frames); - - statistics_proxy_->UpdateHistograms(absl::nullopt, StreamDataCounters(), - nullptr); - EXPECT_METRIC_EQ( - 0, metrics::NumSamples("WebRTC.Video.KeyFramesReceivedInPermille")); -} - -TEST_F(ReceiveStatisticsProxyTest, - KeyFrameHistogramUpdatedForMinRequiredSamples) { - const bool kIsKeyFrame = false; - const int kFrameSizeBytes = 1000; - - for (int i = 0; i < kMinRequiredSamples; ++i) - statistics_proxy_->OnCompleteFrame(kIsKeyFrame, kFrameSizeBytes, - VideoContentType::UNSPECIFIED); - - EXPECT_EQ(0, statistics_proxy_->GetStats().frame_counts.key_frames); - EXPECT_EQ(kMinRequiredSamples, - statistics_proxy_->GetStats().frame_counts.delta_frames); - - statistics_proxy_->UpdateHistograms(absl::nullopt, StreamDataCounters(), - nullptr); - EXPECT_METRIC_EQ( - 1, metrics::NumSamples("WebRTC.Video.KeyFramesReceivedInPermille")); - EXPECT_METRIC_EQ( - 1, metrics::NumEvents("WebRTC.Video.KeyFramesReceivedInPermille", 0)); -} - -TEST_F(ReceiveStatisticsProxyTest, KeyFrameHistogramIsUpdated) { - const int kFrameSizeBytes = 1000; - - for (int i = 0; i < kMinRequiredSamples; ++i) - statistics_proxy_->OnCompleteFrame(true, kFrameSizeBytes, - VideoContentType::UNSPECIFIED); - - for (int i = 0; i < kMinRequiredSamples; ++i) - statistics_proxy_->OnCompleteFrame(false, kFrameSizeBytes, - VideoContentType::UNSPECIFIED); - - EXPECT_EQ(kMinRequiredSamples, - statistics_proxy_->GetStats().frame_counts.key_frames); - EXPECT_EQ(kMinRequiredSamples, - statistics_proxy_->GetStats().frame_counts.delta_frames); - - statistics_proxy_->UpdateHistograms(absl::nullopt, StreamDataCounters(), - nullptr); - EXPECT_METRIC_EQ( - 1, metrics::NumSamples("WebRTC.Video.KeyFramesReceivedInPermille")); - EXPECT_METRIC_EQ( - 1, metrics::NumEvents("WebRTC.Video.KeyFramesReceivedInPermille", 500)); -} - -TEST_F(ReceiveStatisticsProxyTest, TimingHistogramsNotUpdatedForTooFewSamples) { - const int kMaxDecodeMs = 2; - const int kCurrentDelayMs = 3; - const int kTargetDelayMs = 4; - const int kJitterBufferMs = 5; - const int kMinPlayoutDelayMs = 6; - const int kRenderDelayMs = 7; - - for (int i = 0; i < kMinRequiredSamples - 1; ++i) { - statistics_proxy_->OnFrameBufferTimingsUpdated( - kMaxDecodeMs, kCurrentDelayMs, kTargetDelayMs, kJitterBufferMs, - kMinPlayoutDelayMs, kRenderDelayMs); - } - - statistics_proxy_->UpdateHistograms(absl::nullopt, StreamDataCounters(), - nullptr); - EXPECT_METRIC_EQ(0, metrics::NumSamples("WebRTC.Video.DecodeTimeInMs")); - EXPECT_METRIC_EQ(0, - metrics::NumSamples("WebRTC.Video.JitterBufferDelayInMs")); - EXPECT_METRIC_EQ(0, metrics::NumSamples("WebRTC.Video.TargetDelayInMs")); - EXPECT_METRIC_EQ(0, metrics::NumSamples("WebRTC.Video.CurrentDelayInMs")); - EXPECT_METRIC_EQ(0, metrics::NumSamples("WebRTC.Video.OnewayDelayInMs")); -} - -TEST_F(ReceiveStatisticsProxyTest, TimingHistogramsAreUpdated) { - const int kMaxDecodeMs = 2; - const int kCurrentDelayMs = 3; - const int kTargetDelayMs = 4; - const int kJitterBufferMs = 5; - const int kMinPlayoutDelayMs = 6; - const int kRenderDelayMs = 7; - - for (int i = 0; i < kMinRequiredSamples; ++i) { - statistics_proxy_->OnFrameBufferTimingsUpdated( - kMaxDecodeMs, kCurrentDelayMs, kTargetDelayMs, kJitterBufferMs, - kMinPlayoutDelayMs, kRenderDelayMs); - } - - statistics_proxy_->UpdateHistograms(absl::nullopt, StreamDataCounters(), - nullptr); - EXPECT_METRIC_EQ(1, - metrics::NumSamples("WebRTC.Video.JitterBufferDelayInMs")); - EXPECT_METRIC_EQ(1, metrics::NumSamples("WebRTC.Video.TargetDelayInMs")); - EXPECT_METRIC_EQ(1, metrics::NumSamples("WebRTC.Video.CurrentDelayInMs")); - EXPECT_METRIC_EQ(1, metrics::NumSamples("WebRTC.Video.OnewayDelayInMs")); - - EXPECT_METRIC_EQ(1, metrics::NumEvents("WebRTC.Video.JitterBufferDelayInMs", - kJitterBufferMs)); - EXPECT_METRIC_EQ( - 1, metrics::NumEvents("WebRTC.Video.TargetDelayInMs", kTargetDelayMs)); - EXPECT_METRIC_EQ( - 1, metrics::NumEvents("WebRTC.Video.CurrentDelayInMs", kCurrentDelayMs)); - EXPECT_METRIC_EQ( - 1, metrics::NumEvents("WebRTC.Video.OnewayDelayInMs", kTargetDelayMs)); -} - -TEST_F(ReceiveStatisticsProxyTest, DoesNotReportStaleFramerates) { - const int kDefaultFps = 30; - webrtc::VideoFrame frame = CreateFrame(kWidth, kHeight); - - for (int i = 0; i < kDefaultFps; ++i) { - // Since OnRenderedFrame is never called the fps in each sample will be 0, - // i.e. bad - frame.set_ntp_time_ms(fake_clock_.CurrentNtpInMilliseconds()); - statistics_proxy_->OnDecodedFrame(frame, absl::nullopt, 0, - VideoContentType::UNSPECIFIED); - statistics_proxy_->OnRenderedFrame(frame); - fake_clock_.AdvanceTimeMilliseconds(1000 / kDefaultFps); - } - - EXPECT_EQ(kDefaultFps, statistics_proxy_->GetStats().decode_frame_rate); - EXPECT_EQ(kDefaultFps, statistics_proxy_->GetStats().render_frame_rate); - - // FPS trackers in stats proxy have a 1000ms sliding window. - fake_clock_.AdvanceTimeMilliseconds(1000); - EXPECT_EQ(0, statistics_proxy_->GetStats().decode_frame_rate); - EXPECT_EQ(0, statistics_proxy_->GetStats().render_frame_rate); -} - -TEST_F(ReceiveStatisticsProxyTest, GetStatsReportsReceivedFrameStats) { - EXPECT_EQ(0, statistics_proxy_->GetStats().width); - EXPECT_EQ(0, statistics_proxy_->GetStats().height); - EXPECT_EQ(0u, statistics_proxy_->GetStats().frames_rendered); - - statistics_proxy_->OnRenderedFrame(CreateFrame(kWidth, kHeight)); - - EXPECT_EQ(kWidth, statistics_proxy_->GetStats().width); - EXPECT_EQ(kHeight, statistics_proxy_->GetStats().height); - EXPECT_EQ(1u, statistics_proxy_->GetStats().frames_rendered); -} - -TEST_F(ReceiveStatisticsProxyTest, - ReceivedFrameHistogramsAreNotUpdatedForTooFewSamples) { - for (int i = 0; i < kMinRequiredSamples - 1; ++i) - statistics_proxy_->OnRenderedFrame(CreateFrame(kWidth, kHeight)); - - statistics_proxy_->UpdateHistograms(absl::nullopt, StreamDataCounters(), - nullptr); - EXPECT_METRIC_EQ(0, - metrics::NumSamples("WebRTC.Video.ReceivedWidthInPixels")); - EXPECT_METRIC_EQ(0, - metrics::NumSamples("WebRTC.Video.ReceivedHeightInPixels")); - EXPECT_METRIC_EQ(0, - metrics::NumSamples("WebRTC.Video.RenderFramesPerSecond")); - EXPECT_METRIC_EQ( - 0, metrics::NumSamples("WebRTC.Video.RenderSqrtPixelsPerSecond")); -} - -TEST_F(ReceiveStatisticsProxyTest, ReceivedFrameHistogramsAreUpdated) { - for (int i = 0; i < kMinRequiredSamples; ++i) - statistics_proxy_->OnRenderedFrame(CreateFrame(kWidth, kHeight)); - - statistics_proxy_->UpdateHistograms(absl::nullopt, StreamDataCounters(), - nullptr); - EXPECT_METRIC_EQ(1, - metrics::NumSamples("WebRTC.Video.ReceivedWidthInPixels")); - EXPECT_METRIC_EQ(1, - metrics::NumSamples("WebRTC.Video.ReceivedHeightInPixels")); - EXPECT_METRIC_EQ(1, - metrics::NumSamples("WebRTC.Video.RenderFramesPerSecond")); - EXPECT_METRIC_EQ( - 1, metrics::NumSamples("WebRTC.Video.RenderSqrtPixelsPerSecond")); - EXPECT_METRIC_EQ( - 1, metrics::NumEvents("WebRTC.Video.ReceivedWidthInPixels", kWidth)); - EXPECT_METRIC_EQ( - 1, metrics::NumEvents("WebRTC.Video.ReceivedHeightInPixels", kHeight)); -} - -TEST_F(ReceiveStatisticsProxyTest, ZeroDelayReportedIfFrameNotDelayed) { - webrtc::VideoFrame frame = CreateFrame(kWidth, kHeight); - statistics_proxy_->OnDecodedFrame(frame, absl::nullopt, 0, - VideoContentType::UNSPECIFIED); - - // Frame not delayed, delayed frames to render: 0%. - const int64_t kNowMs = fake_clock_.TimeInMilliseconds(); - statistics_proxy_->OnRenderedFrame(CreateFrameWithRenderTimeMs(kNowMs)); - - // Min run time has passed. - fake_clock_.AdvanceTimeMilliseconds((metrics::kMinRunTimeInSeconds * 1000)); - statistics_proxy_->UpdateHistograms(absl::nullopt, StreamDataCounters(), - nullptr); - EXPECT_METRIC_EQ(1, - metrics::NumSamples("WebRTC.Video.DelayedFramesToRenderer")); - EXPECT_METRIC_EQ( - 1, metrics::NumEvents("WebRTC.Video.DelayedFramesToRenderer", 0)); - EXPECT_METRIC_EQ(0, metrics::NumSamples( - "WebRTC.Video.DelayedFramesToRenderer_AvgDelayInMs")); -} - -TEST_F(ReceiveStatisticsProxyTest, - DelayedFrameHistogramsAreNotUpdatedIfMinRuntimeHasNotPassed) { - webrtc::VideoFrame frame = CreateFrame(kWidth, kHeight); - statistics_proxy_->OnDecodedFrame(frame, absl::nullopt, 0, - VideoContentType::UNSPECIFIED); - - // Frame not delayed, delayed frames to render: 0%. - const int64_t kNowMs = fake_clock_.TimeInMilliseconds(); - statistics_proxy_->OnRenderedFrame(CreateFrameWithRenderTimeMs(kNowMs)); - - // Min run time has not passed. - fake_clock_.AdvanceTimeMilliseconds((metrics::kMinRunTimeInSeconds * 1000) - - 1); - statistics_proxy_->UpdateHistograms(absl::nullopt, StreamDataCounters(), - nullptr); - EXPECT_METRIC_EQ(0, - metrics::NumSamples("WebRTC.Video.DelayedFramesToRenderer")); - EXPECT_METRIC_EQ(0, metrics::NumSamples( - "WebRTC.Video.DelayedFramesToRenderer_AvgDelayInMs")); -} - -TEST_F(ReceiveStatisticsProxyTest, - DelayedFramesHistogramsAreNotUpdatedIfNoRenderedFrames) { - webrtc::VideoFrame frame = CreateFrame(kWidth, kHeight); - statistics_proxy_->OnDecodedFrame(frame, absl::nullopt, 0, - VideoContentType::UNSPECIFIED); - - // Min run time has passed. No rendered frames. - fake_clock_.AdvanceTimeMilliseconds((metrics::kMinRunTimeInSeconds * 1000)); - statistics_proxy_->UpdateHistograms(absl::nullopt, StreamDataCounters(), - nullptr); - EXPECT_METRIC_EQ(0, - metrics::NumSamples("WebRTC.Video.DelayedFramesToRenderer")); - EXPECT_METRIC_EQ(0, metrics::NumSamples( - "WebRTC.Video.DelayedFramesToRenderer_AvgDelayInMs")); -} - -TEST_F(ReceiveStatisticsProxyTest, DelayReportedIfFrameIsDelayed) { - webrtc::VideoFrame frame = CreateFrame(kWidth, kHeight); - statistics_proxy_->OnDecodedFrame(frame, absl::nullopt, 0, - VideoContentType::UNSPECIFIED); - - // Frame delayed 1 ms, delayed frames to render: 100%. - const int64_t kNowMs = fake_clock_.TimeInMilliseconds(); - statistics_proxy_->OnRenderedFrame(CreateFrameWithRenderTimeMs(kNowMs - 1)); - - // Min run time has passed. - fake_clock_.AdvanceTimeMilliseconds((metrics::kMinRunTimeInSeconds * 1000)); - statistics_proxy_->UpdateHistograms(absl::nullopt, StreamDataCounters(), - nullptr); - EXPECT_METRIC_EQ(1, - metrics::NumSamples("WebRTC.Video.DelayedFramesToRenderer")); - EXPECT_METRIC_EQ( - 1, metrics::NumEvents("WebRTC.Video.DelayedFramesToRenderer", 100)); - EXPECT_METRIC_EQ(1, metrics::NumSamples( - "WebRTC.Video.DelayedFramesToRenderer_AvgDelayInMs")); - EXPECT_METRIC_EQ( - 1, metrics::NumEvents("WebRTC.Video.DelayedFramesToRenderer_AvgDelayInMs", - 1)); -} - -TEST_F(ReceiveStatisticsProxyTest, AverageDelayOfDelayedFramesIsReported) { - webrtc::VideoFrame frame = CreateFrame(kWidth, kHeight); - statistics_proxy_->OnDecodedFrame(frame, absl::nullopt, 0, - VideoContentType::UNSPECIFIED); - - // Two frames delayed (6 ms, 10 ms), delayed frames to render: 50%. - const int64_t kNowMs = fake_clock_.TimeInMilliseconds(); - statistics_proxy_->OnRenderedFrame(CreateFrameWithRenderTimeMs(kNowMs - 10)); - statistics_proxy_->OnRenderedFrame(CreateFrameWithRenderTimeMs(kNowMs - 6)); - statistics_proxy_->OnRenderedFrame(CreateFrameWithRenderTimeMs(kNowMs)); - statistics_proxy_->OnRenderedFrame(CreateFrameWithRenderTimeMs(kNowMs + 1)); - - // Min run time has passed. - fake_clock_.AdvanceTimeMilliseconds((metrics::kMinRunTimeInSeconds * 1000)); - statistics_proxy_->UpdateHistograms(absl::nullopt, StreamDataCounters(), - nullptr); - EXPECT_METRIC_EQ(1, - metrics::NumSamples("WebRTC.Video.DelayedFramesToRenderer")); - EXPECT_METRIC_EQ( - 1, metrics::NumEvents("WebRTC.Video.DelayedFramesToRenderer", 50)); - EXPECT_METRIC_EQ(1, metrics::NumSamples( - "WebRTC.Video.DelayedFramesToRenderer_AvgDelayInMs")); - EXPECT_METRIC_EQ( - 1, metrics::NumEvents("WebRTC.Video.DelayedFramesToRenderer_AvgDelayInMs", - 8)); -} - -TEST_F(ReceiveStatisticsProxyTest, - RtcpHistogramsNotUpdatedIfMinRuntimeHasNotPassed) { - StreamDataCounters data_counters; - data_counters.first_packet_time_ms = fake_clock_.TimeInMilliseconds(); - - fake_clock_.AdvanceTimeMilliseconds((metrics::kMinRunTimeInSeconds * 1000) - - 1); - - RtcpPacketTypeCounter counter; - statistics_proxy_->RtcpPacketTypesCounterUpdated(kRemoteSsrc, counter); - - statistics_proxy_->UpdateHistograms(absl::nullopt, data_counters, nullptr); - EXPECT_METRIC_EQ(0, - metrics::NumSamples("WebRTC.Video.FirPacketsSentPerMinute")); - EXPECT_METRIC_EQ(0, - metrics::NumSamples("WebRTC.Video.PliPacketsSentPerMinute")); - EXPECT_METRIC_EQ( - 0, metrics::NumSamples("WebRTC.Video.NackPacketsSentPerMinute")); -} - -TEST_F(ReceiveStatisticsProxyTest, RtcpHistogramsAreUpdated) { - StreamDataCounters data_counters; - data_counters.first_packet_time_ms = fake_clock_.TimeInMilliseconds(); - fake_clock_.AdvanceTimeMilliseconds(metrics::kMinRunTimeInSeconds * 1000); - - const uint32_t kFirPackets = 100; - const uint32_t kPliPackets = 200; - const uint32_t kNackPackets = 300; - - RtcpPacketTypeCounter counter; - counter.fir_packets = kFirPackets; - counter.pli_packets = kPliPackets; - counter.nack_packets = kNackPackets; - statistics_proxy_->RtcpPacketTypesCounterUpdated(kRemoteSsrc, counter); - - statistics_proxy_->UpdateHistograms(absl::nullopt, data_counters, nullptr); - EXPECT_METRIC_EQ(1, - metrics::NumSamples("WebRTC.Video.FirPacketsSentPerMinute")); - EXPECT_METRIC_EQ(1, - metrics::NumSamples("WebRTC.Video.PliPacketsSentPerMinute")); - EXPECT_METRIC_EQ( - 1, metrics::NumSamples("WebRTC.Video.NackPacketsSentPerMinute")); - EXPECT_METRIC_EQ( - 1, metrics::NumEvents("WebRTC.Video.FirPacketsSentPerMinute", - kFirPackets * 60 / metrics::kMinRunTimeInSeconds)); - EXPECT_METRIC_EQ( - 1, metrics::NumEvents("WebRTC.Video.PliPacketsSentPerMinute", - kPliPackets * 60 / metrics::kMinRunTimeInSeconds)); - EXPECT_METRIC_EQ( - 1, metrics::NumEvents("WebRTC.Video.NackPacketsSentPerMinute", - kNackPackets * 60 / metrics::kMinRunTimeInSeconds)); -} - -class ReceiveStatisticsProxyTestWithFreezeDuration - : public ReceiveStatisticsProxyTest, - public ::testing::WithParamInterface< - std::tuple> { - protected: - const uint32_t frame_duration_ms_ = {std::get<0>(GetParam())}; - const uint32_t freeze_duration_ms_ = {std::get<1>(GetParam())}; - const uint32_t expected_freeze_count_ = {std::get<2>(GetParam())}; -}; - -// It is a freeze if: -// frame_duration_ms >= max(3 * avg_frame_duration, avg_frame_duration + 150) -// where avg_frame_duration is average duration of last 30 frames including -// the current one. -// -// Condition 1: 3 * avg_frame_duration > avg_frame_duration + 150 -const auto kFreezeDetectionCond1Freeze = std::make_tuple(150, 483, 1); -const auto kFreezeDetectionCond1NotFreeze = std::make_tuple(150, 482, 0); -// Condition 2: 3 * avg_frame_duration < avg_frame_duration + 150 -const auto kFreezeDetectionCond2Freeze = std::make_tuple(30, 185, 1); -const auto kFreezeDetectionCond2NotFreeze = std::make_tuple(30, 184, 0); - -INSTANTIATE_TEST_SUITE_P(_, - ReceiveStatisticsProxyTestWithFreezeDuration, - ::testing::Values(kFreezeDetectionCond1Freeze, - kFreezeDetectionCond1NotFreeze, - kFreezeDetectionCond2Freeze, - kFreezeDetectionCond2NotFreeze)); - -TEST_P(ReceiveStatisticsProxyTestWithFreezeDuration, FreezeDetection) { - VideoReceiveStreamInterface::Stats stats = statistics_proxy_->GetStats(); - EXPECT_EQ(0u, stats.freeze_count); - webrtc::VideoFrame frame = CreateFrame(kWidth, kHeight); - - // Add a very long frame. This is need to verify that average frame - // duration, which is supposed to be calculated as mean of durations of - // last 30 frames, is calculated correctly. - statistics_proxy_->OnRenderedFrame(frame); - fake_clock_.AdvanceTimeMilliseconds(2000); - - for (size_t i = 0; - i <= VideoQualityObserver::kAvgInterframeDelaysWindowSizeFrames; ++i) { - fake_clock_.AdvanceTimeMilliseconds(frame_duration_ms_); - statistics_proxy_->OnRenderedFrame(frame); - } - - fake_clock_.AdvanceTimeMilliseconds(freeze_duration_ms_); - statistics_proxy_->OnRenderedFrame(frame); - - stats = statistics_proxy_->GetStats(); - EXPECT_EQ(stats.freeze_count, expected_freeze_count_); -} - -class ReceiveStatisticsProxyTestWithContent - : public ReceiveStatisticsProxyTest, - public ::testing::WithParamInterface { - protected: - const webrtc::VideoContentType content_type_{GetParam()}; -}; - -INSTANTIATE_TEST_SUITE_P(ContentTypes, - ReceiveStatisticsProxyTestWithContent, - ::testing::Values(VideoContentType::UNSPECIFIED, - VideoContentType::SCREENSHARE)); - -TEST_P(ReceiveStatisticsProxyTestWithContent, InterFrameDelaysAreReported) { - const int kInterFrameDelayMs = 33; - webrtc::VideoFrame frame = CreateFrame(kWidth, kHeight); - - for (int i = 0; i < kMinRequiredSamples; ++i) { - statistics_proxy_->OnDecodedFrame(frame, absl::nullopt, 0, content_type_); - fake_clock_.AdvanceTimeMilliseconds(kInterFrameDelayMs); - } - // One extra with double the interval. - fake_clock_.AdvanceTimeMilliseconds(kInterFrameDelayMs); - statistics_proxy_->OnDecodedFrame(frame, absl::nullopt, 0, content_type_); - - statistics_proxy_->UpdateHistograms(absl::nullopt, StreamDataCounters(), - nullptr); - const int kExpectedInterFrame = - (kInterFrameDelayMs * (kMinRequiredSamples - 1) + - kInterFrameDelayMs * 2) / - kMinRequiredSamples; - if (videocontenttypehelpers::IsScreenshare(content_type_)) { - EXPECT_METRIC_EQ( - kExpectedInterFrame, - metrics::MinSample("WebRTC.Video.Screenshare.InterframeDelayInMs")); - EXPECT_METRIC_EQ( - kInterFrameDelayMs * 2, - metrics::MinSample("WebRTC.Video.Screenshare.InterframeDelayMaxInMs")); - } else { - EXPECT_METRIC_EQ(kExpectedInterFrame, - metrics::MinSample("WebRTC.Video.InterframeDelayInMs")); - EXPECT_METRIC_EQ(kInterFrameDelayMs * 2, - metrics::MinSample("WebRTC.Video.InterframeDelayMaxInMs")); - } -} - -TEST_P(ReceiveStatisticsProxyTestWithContent, - InterFrameDelaysPercentilesAreReported) { - const int kInterFrameDelayMs = 33; - const int kLastFivePercentsSamples = kMinRequiredSamples * 5 / 100; - webrtc::VideoFrame frame = CreateFrame(kWidth, kHeight); - - for (int i = 0; i <= kMinRequiredSamples - kLastFivePercentsSamples; ++i) { - fake_clock_.AdvanceTimeMilliseconds(kInterFrameDelayMs); - statistics_proxy_->OnDecodedFrame(frame, absl::nullopt, 0, content_type_); - } - // Last 5% of intervals are double in size. - for (int i = 0; i < kLastFivePercentsSamples; ++i) { - fake_clock_.AdvanceTimeMilliseconds(2 * kInterFrameDelayMs); - statistics_proxy_->OnDecodedFrame(frame, absl::nullopt, 0, content_type_); - } - // Final sample is outlier and 10 times as big. - fake_clock_.AdvanceTimeMilliseconds(10 * kInterFrameDelayMs); - statistics_proxy_->OnDecodedFrame(frame, absl::nullopt, 0, content_type_); - - statistics_proxy_->UpdateHistograms(absl::nullopt, StreamDataCounters(), - nullptr); - const int kExpectedInterFrame = kInterFrameDelayMs * 2; - if (videocontenttypehelpers::IsScreenshare(content_type_)) { - EXPECT_METRIC_EQ( - kExpectedInterFrame, - metrics::MinSample( - "WebRTC.Video.Screenshare.InterframeDelay95PercentileInMs")); - } else { - EXPECT_METRIC_EQ( - kExpectedInterFrame, - metrics::MinSample("WebRTC.Video.InterframeDelay95PercentileInMs")); - } -} - -TEST_P(ReceiveStatisticsProxyTestWithContent, - MaxInterFrameDelayOnlyWithValidAverage) { - const int kInterFrameDelayMs = 33; - webrtc::VideoFrame frame = CreateFrame(kWidth, kHeight); - - for (int i = 0; i < kMinRequiredSamples; ++i) { - statistics_proxy_->OnDecodedFrame(frame, absl::nullopt, 0, content_type_); - fake_clock_.AdvanceTimeMilliseconds(kInterFrameDelayMs); - } - - // `kMinRequiredSamples` samples, and thereby intervals, is required. That - // means we're one frame short of having a valid data set. - statistics_proxy_->UpdateHistograms(absl::nullopt, StreamDataCounters(), - nullptr); - EXPECT_METRIC_EQ(0, metrics::NumSamples("WebRTC.Video.InterframeDelayInMs")); - EXPECT_METRIC_EQ(0, - metrics::NumSamples("WebRTC.Video.InterframeDelayMaxInMs")); - EXPECT_METRIC_EQ( - 0, metrics::NumSamples("WebRTC.Video.Screenshare.InterframeDelayInMs")); - EXPECT_METRIC_EQ(0, metrics::NumSamples( - "WebRTC.Video.Screenshare.InterframeDelayMaxInMs")); -} - -TEST_P(ReceiveStatisticsProxyTestWithContent, MaxInterFrameDelayOnlyWithPause) { - const int kInterFrameDelayMs = 33; - webrtc::VideoFrame frame = CreateFrame(kWidth, kHeight); - - for (int i = 0; i <= kMinRequiredSamples; ++i) { - statistics_proxy_->OnDecodedFrame(frame, absl::nullopt, 0, content_type_); - fake_clock_.AdvanceTimeMilliseconds(kInterFrameDelayMs); - } - - // At this state, we should have a valid inter-frame delay. - // Indicate stream paused and make a large jump in time. - statistics_proxy_->OnStreamInactive(); - fake_clock_.AdvanceTimeMilliseconds(5000); - - // Insert two more frames. The interval during the pause should be disregarded - // in the stats. - statistics_proxy_->OnDecodedFrame(frame, absl::nullopt, 0, content_type_); - fake_clock_.AdvanceTimeMilliseconds(kInterFrameDelayMs); - statistics_proxy_->OnDecodedFrame(frame, absl::nullopt, 0, content_type_); - - statistics_proxy_->UpdateHistograms(absl::nullopt, StreamDataCounters(), - nullptr); - if (videocontenttypehelpers::IsScreenshare(content_type_)) { - EXPECT_METRIC_EQ( - 1, metrics::NumSamples("WebRTC.Video.Screenshare.InterframeDelayInMs")); - EXPECT_METRIC_EQ(1, metrics::NumSamples( - "WebRTC.Video.Screenshare.InterframeDelayMaxInMs")); - EXPECT_METRIC_EQ( - kInterFrameDelayMs, - metrics::MinSample("WebRTC.Video.Screenshare.InterframeDelayInMs")); - EXPECT_METRIC_EQ( - kInterFrameDelayMs, - metrics::MinSample("WebRTC.Video.Screenshare.InterframeDelayMaxInMs")); - } else { - EXPECT_METRIC_EQ(1, - metrics::NumSamples("WebRTC.Video.InterframeDelayInMs")); - EXPECT_METRIC_EQ( - 1, metrics::NumSamples("WebRTC.Video.InterframeDelayMaxInMs")); - EXPECT_METRIC_EQ(kInterFrameDelayMs, - metrics::MinSample("WebRTC.Video.InterframeDelayInMs")); - EXPECT_METRIC_EQ(kInterFrameDelayMs, - metrics::MinSample("WebRTC.Video.InterframeDelayMaxInMs")); - } -} - -TEST_P(ReceiveStatisticsProxyTestWithContent, FreezesAreReported) { - const int kInterFrameDelayMs = 33; - const int kFreezeDelayMs = 200; - const int kCallDurationMs = - kMinRequiredSamples * kInterFrameDelayMs + kFreezeDelayMs; - webrtc::VideoFrame frame = CreateFrame(kWidth, kHeight); - - for (int i = 0; i < kMinRequiredSamples; ++i) { - statistics_proxy_->OnDecodedFrame(frame, absl::nullopt, 0, content_type_); - statistics_proxy_->OnRenderedFrame(frame); - fake_clock_.AdvanceTimeMilliseconds(kInterFrameDelayMs); - } - // Add extra freeze. - fake_clock_.AdvanceTimeMilliseconds(kFreezeDelayMs); - statistics_proxy_->OnDecodedFrame(frame, absl::nullopt, 0, content_type_); - statistics_proxy_->OnRenderedFrame(frame); - - statistics_proxy_->UpdateHistograms(absl::nullopt, StreamDataCounters(), - nullptr); - const int kExpectedTimeBetweenFreezes = - kInterFrameDelayMs * (kMinRequiredSamples - 1); - const int kExpectedNumberFreezesPerMinute = 60 * 1000 / kCallDurationMs; - if (videocontenttypehelpers::IsScreenshare(content_type_)) { - EXPECT_METRIC_EQ( - kFreezeDelayMs + kInterFrameDelayMs, - metrics::MinSample("WebRTC.Video.Screenshare.MeanFreezeDurationMs")); - EXPECT_METRIC_EQ(kExpectedTimeBetweenFreezes, - metrics::MinSample( - "WebRTC.Video.Screenshare.MeanTimeBetweenFreezesMs")); - EXPECT_METRIC_EQ( - kExpectedNumberFreezesPerMinute, - metrics::MinSample("WebRTC.Video.Screenshare.NumberFreezesPerMinute")); - } else { - EXPECT_METRIC_EQ(kFreezeDelayMs + kInterFrameDelayMs, - metrics::MinSample("WebRTC.Video.MeanFreezeDurationMs")); - EXPECT_METRIC_EQ( - kExpectedTimeBetweenFreezes, - metrics::MinSample("WebRTC.Video.MeanTimeBetweenFreezesMs")); - EXPECT_METRIC_EQ(kExpectedNumberFreezesPerMinute, - metrics::MinSample("WebRTC.Video.NumberFreezesPerMinute")); - } -} - -TEST_P(ReceiveStatisticsProxyTestWithContent, HarmonicFrameRateIsReported) { - const int kFrameDurationMs = 33; - const int kFreezeDurationMs = 200; - const int kPauseDurationMs = 10000; - const int kCallDurationMs = kMinRequiredSamples * kFrameDurationMs + - kFreezeDurationMs + kPauseDurationMs; - webrtc::VideoFrame frame = CreateFrame(kWidth, kHeight); - - for (int i = 0; i < kMinRequiredSamples; ++i) { - fake_clock_.AdvanceTimeMilliseconds(kFrameDurationMs); - statistics_proxy_->OnDecodedFrame(frame, absl::nullopt, 0, content_type_); - statistics_proxy_->OnRenderedFrame(frame); - } - - // Freezes and pauses should be included into harmonic frame rate. - // Add freeze. - fake_clock_.AdvanceTimeMilliseconds(kFreezeDurationMs); - statistics_proxy_->OnDecodedFrame(frame, absl::nullopt, 0, content_type_); - statistics_proxy_->OnRenderedFrame(frame); - - // Add pause. - fake_clock_.AdvanceTimeMilliseconds(kPauseDurationMs); - statistics_proxy_->OnStreamInactive(); - statistics_proxy_->OnDecodedFrame(frame, absl::nullopt, 0, content_type_); - statistics_proxy_->OnRenderedFrame(frame); - - statistics_proxy_->UpdateHistograms(absl::nullopt, StreamDataCounters(), - nullptr); - double kSumSquaredFrameDurationSecs = - (kMinRequiredSamples - 1) * - (kFrameDurationMs / 1000.0 * kFrameDurationMs / 1000.0); - kSumSquaredFrameDurationSecs += - kFreezeDurationMs / 1000.0 * kFreezeDurationMs / 1000.0; - kSumSquaredFrameDurationSecs += - kPauseDurationMs / 1000.0 * kPauseDurationMs / 1000.0; - const int kExpectedHarmonicFrameRateFps = - std::round(kCallDurationMs / (1000 * kSumSquaredFrameDurationSecs)); - if (videocontenttypehelpers::IsScreenshare(content_type_)) { - EXPECT_METRIC_EQ( - kExpectedHarmonicFrameRateFps, - metrics::MinSample("WebRTC.Video.Screenshare.HarmonicFrameRate")); - } else { - EXPECT_METRIC_EQ(kExpectedHarmonicFrameRateFps, - metrics::MinSample("WebRTC.Video.HarmonicFrameRate")); - } -} - -TEST_P(ReceiveStatisticsProxyTestWithContent, PausesAreIgnored) { - const int kInterFrameDelayMs = 33; - const int kPauseDurationMs = 10000; - webrtc::VideoFrame frame = CreateFrame(kWidth, kHeight); - - for (int i = 0; i <= kMinRequiredSamples; ++i) { - statistics_proxy_->OnDecodedFrame(frame, absl::nullopt, 0, content_type_); - statistics_proxy_->OnRenderedFrame(frame); - fake_clock_.AdvanceTimeMilliseconds(kInterFrameDelayMs); - } - // Add a pause. - fake_clock_.AdvanceTimeMilliseconds(kPauseDurationMs); - statistics_proxy_->OnStreamInactive(); - - // Second playback interval with triple the length. - for (int i = 0; i <= kMinRequiredSamples * 3; ++i) { - statistics_proxy_->OnDecodedFrame(frame, absl::nullopt, 0, content_type_); - statistics_proxy_->OnRenderedFrame(frame); - fake_clock_.AdvanceTimeMilliseconds(kInterFrameDelayMs); - } - - statistics_proxy_->UpdateHistograms(absl::nullopt, StreamDataCounters(), - nullptr); - // Average of two playback intervals. - const int kExpectedTimeBetweenFreezes = - kInterFrameDelayMs * kMinRequiredSamples * 2; - if (videocontenttypehelpers::IsScreenshare(content_type_)) { - EXPECT_METRIC_EQ(-1, metrics::MinSample( - "WebRTC.Video.Screenshare.MeanFreezeDurationMs")); - EXPECT_METRIC_EQ(kExpectedTimeBetweenFreezes, - metrics::MinSample( - "WebRTC.Video.Screenshare.MeanTimeBetweenFreezesMs")); - } else { - EXPECT_METRIC_EQ(-1, - metrics::MinSample("WebRTC.Video.MeanFreezeDurationMs")); - EXPECT_METRIC_EQ( - kExpectedTimeBetweenFreezes, - metrics::MinSample("WebRTC.Video.MeanTimeBetweenFreezesMs")); - } -} - -TEST_P(ReceiveStatisticsProxyTestWithContent, ManyPausesAtTheBeginning) { - const int kInterFrameDelayMs = 33; - const int kPauseDurationMs = 10000; - webrtc::VideoFrame frame = CreateFrame(kWidth, kHeight); - - for (int i = 0; i <= kMinRequiredSamples; ++i) { - statistics_proxy_->OnDecodedFrame(frame, absl::nullopt, 0, content_type_); - fake_clock_.AdvanceTimeMilliseconds(kInterFrameDelayMs); - - statistics_proxy_->OnStreamInactive(); - fake_clock_.AdvanceTimeMilliseconds(kPauseDurationMs); - - statistics_proxy_->OnDecodedFrame(frame, absl::nullopt, 0, content_type_); - fake_clock_.AdvanceTimeMilliseconds(kInterFrameDelayMs); - } - - statistics_proxy_->UpdateHistograms(absl::nullopt, StreamDataCounters(), - nullptr); - // No freezes should be detected, as all long inter-frame delays were pauses. - if (videocontenttypehelpers::IsScreenshare(content_type_)) { - EXPECT_METRIC_EQ(-1, metrics::MinSample( - "WebRTC.Video.Screenshare.MeanFreezeDurationMs")); - } else { - EXPECT_METRIC_EQ(-1, - metrics::MinSample("WebRTC.Video.MeanFreezeDurationMs")); - } -} - -TEST_P(ReceiveStatisticsProxyTestWithContent, TimeInHdReported) { - const int kInterFrameDelayMs = 20; - webrtc::VideoFrame frame_hd = CreateFrame(1280, 720); - webrtc::VideoFrame frame_sd = CreateFrame(640, 360); - - // HD frames. - for (int i = 0; i < kMinRequiredSamples; ++i) { - statistics_proxy_->OnDecodedFrame(frame_hd, absl::nullopt, 0, - content_type_); - statistics_proxy_->OnRenderedFrame(frame_hd); - fake_clock_.AdvanceTimeMilliseconds(kInterFrameDelayMs); - } - // SD frames. - for (int i = 0; i < 2 * kMinRequiredSamples; ++i) { - statistics_proxy_->OnDecodedFrame(frame_sd, absl::nullopt, 0, - content_type_); - statistics_proxy_->OnRenderedFrame(frame_sd); - fake_clock_.AdvanceTimeMilliseconds(kInterFrameDelayMs); - } - // Extra last frame. - statistics_proxy_->OnRenderedFrame(frame_sd); - - statistics_proxy_->UpdateHistograms(absl::nullopt, StreamDataCounters(), - nullptr); - const int kExpectedTimeInHdPercents = 33; - if (videocontenttypehelpers::IsScreenshare(content_type_)) { - EXPECT_METRIC_EQ( - kExpectedTimeInHdPercents, - metrics::MinSample("WebRTC.Video.Screenshare.TimeInHdPercentage")); - } else { - EXPECT_METRIC_EQ(kExpectedTimeInHdPercents, - metrics::MinSample("WebRTC.Video.TimeInHdPercentage")); - } -} - -TEST_P(ReceiveStatisticsProxyTestWithContent, TimeInBlockyVideoReported) { - const int kInterFrameDelayMs = 20; - const int kHighQp = 80; - const int kLowQp = 30; - webrtc::VideoFrame frame = CreateFrame(kWidth, kHeight); - - // High quality frames. - for (int i = 0; i < kMinRequiredSamples; ++i) { - statistics_proxy_->OnDecodedFrame(frame, kLowQp, 0, content_type_); - statistics_proxy_->OnRenderedFrame(frame); - fake_clock_.AdvanceTimeMilliseconds(kInterFrameDelayMs); - } - // Blocky frames. - for (int i = 0; i < 2 * kMinRequiredSamples; ++i) { - statistics_proxy_->OnDecodedFrame(frame, kHighQp, 0, content_type_); - statistics_proxy_->OnRenderedFrame(frame); - fake_clock_.AdvanceTimeMilliseconds(kInterFrameDelayMs); - } - // Extra last frame. - statistics_proxy_->OnDecodedFrame(frame, kHighQp, 0, content_type_); - statistics_proxy_->OnRenderedFrame(frame); - - statistics_proxy_->UpdateHistograms(absl::nullopt, StreamDataCounters(), - nullptr); - const int kExpectedTimeInHdPercents = 66; - if (videocontenttypehelpers::IsScreenshare(content_type_)) { - EXPECT_METRIC_EQ( - kExpectedTimeInHdPercents, - metrics::MinSample( - "WebRTC.Video.Screenshare.TimeInBlockyVideoPercentage")); - } else { - EXPECT_METRIC_EQ( - kExpectedTimeInHdPercents, - metrics::MinSample("WebRTC.Video.TimeInBlockyVideoPercentage")); - } -} - -TEST_P(ReceiveStatisticsProxyTestWithContent, DownscalesReported) { - const int kInterFrameDelayMs = 2000; // To ensure long enough call duration. - - webrtc::VideoFrame frame_hd = CreateFrame(1280, 720); - webrtc::VideoFrame frame_sd = CreateFrame(640, 360); - webrtc::VideoFrame frame_ld = CreateFrame(320, 180); - - // Call once to pass content type. - statistics_proxy_->OnDecodedFrame(frame_hd, absl::nullopt, 0, content_type_); - - statistics_proxy_->OnRenderedFrame(frame_hd); - fake_clock_.AdvanceTimeMilliseconds(kInterFrameDelayMs); - - // Downscale. - statistics_proxy_->OnRenderedFrame(frame_sd); - fake_clock_.AdvanceTimeMilliseconds(kInterFrameDelayMs); - - // Downscale. - statistics_proxy_->OnRenderedFrame(frame_ld); - fake_clock_.AdvanceTimeMilliseconds(kInterFrameDelayMs); - - statistics_proxy_->UpdateHistograms(absl::nullopt, StreamDataCounters(), - nullptr); - const int kExpectedDownscales = 30; // 2 per 4 seconds = 30 per minute. - if (videocontenttypehelpers::IsScreenshare(content_type_)) { - EXPECT_METRIC_EQ( - kExpectedDownscales, - metrics::MinSample( - "WebRTC.Video.Screenshare.NumberResolutionDownswitchesPerMinute")); - } else { - EXPECT_METRIC_EQ(kExpectedDownscales, - metrics::MinSample( - "WebRTC.Video.NumberResolutionDownswitchesPerMinute")); - } -} - -TEST_P(ReceiveStatisticsProxyTestWithContent, DecodeTimeReported) { - const int kInterFrameDelayMs = 20; - const int kLowQp = 30; - const int kDecodeMs = 7; - - webrtc::VideoFrame frame = CreateFrame(kWidth, kHeight); - - for (int i = 0; i < kMinRequiredSamples; ++i) { - statistics_proxy_->OnDecodedFrame(frame, kLowQp, kDecodeMs, content_type_); - fake_clock_.AdvanceTimeMilliseconds(kInterFrameDelayMs); - } - statistics_proxy_->UpdateHistograms(absl::nullopt, StreamDataCounters(), - nullptr); - EXPECT_METRIC_EQ( - 1, metrics::NumEvents("WebRTC.Video.DecodeTimeInMs", kDecodeMs)); -} - -TEST_P(ReceiveStatisticsProxyTestWithContent, - StatsAreSlicedOnSimulcastAndExperiment) { - const uint8_t experiment_id = 1; - webrtc::VideoContentType content_type = content_type_; - videocontenttypehelpers::SetExperimentId(&content_type, experiment_id); - const int kInterFrameDelayMs1 = 30; - const int kInterFrameDelayMs2 = 50; - webrtc::VideoFrame frame = CreateFrame(kWidth, kHeight); - - videocontenttypehelpers::SetSimulcastId(&content_type, 1); - for (int i = 0; i <= kMinRequiredSamples; ++i) { - fake_clock_.AdvanceTimeMilliseconds(kInterFrameDelayMs1); - statistics_proxy_->OnDecodedFrame(frame, absl::nullopt, 0, content_type); - } - - videocontenttypehelpers::SetSimulcastId(&content_type, 2); - for (int i = 0; i <= kMinRequiredSamples; ++i) { - fake_clock_.AdvanceTimeMilliseconds(kInterFrameDelayMs2); - statistics_proxy_->OnDecodedFrame(frame, absl::nullopt, 0, content_type); - } - statistics_proxy_->UpdateHistograms(absl::nullopt, StreamDataCounters(), - nullptr); - - if (videocontenttypehelpers::IsScreenshare(content_type)) { - EXPECT_METRIC_EQ( - 1, metrics::NumSamples("WebRTC.Video.Screenshare.InterframeDelayInMs")); - EXPECT_METRIC_EQ(1, metrics::NumSamples( - "WebRTC.Video.Screenshare.InterframeDelayMaxInMs")); - EXPECT_METRIC_EQ(1, metrics::NumSamples( - "WebRTC.Video.Screenshare.InterframeDelayInMs.S0")); - EXPECT_METRIC_EQ(1, - metrics::NumSamples( - "WebRTC.Video.Screenshare.InterframeDelayMaxInMs.S0")); - EXPECT_METRIC_EQ(1, metrics::NumSamples( - "WebRTC.Video.Screenshare.InterframeDelayInMs.S1")); - EXPECT_METRIC_EQ(1, - metrics::NumSamples( - "WebRTC.Video.Screenshare.InterframeDelayMaxInMs.S1")); - EXPECT_METRIC_EQ( - 1, metrics::NumSamples("WebRTC.Video.Screenshare.InterframeDelayInMs" - ".ExperimentGroup0")); - EXPECT_METRIC_EQ( - 1, metrics::NumSamples("WebRTC.Video.Screenshare.InterframeDelayMaxInMs" - ".ExperimentGroup0")); - EXPECT_METRIC_EQ( - kInterFrameDelayMs1, - metrics::MinSample("WebRTC.Video.Screenshare.InterframeDelayInMs.S0")); - EXPECT_METRIC_EQ( - kInterFrameDelayMs2, - metrics::MinSample("WebRTC.Video.Screenshare.InterframeDelayInMs.S1")); - EXPECT_METRIC_EQ( - (kInterFrameDelayMs1 + kInterFrameDelayMs2) / 2, - metrics::MinSample("WebRTC.Video.Screenshare.InterframeDelayInMs")); - EXPECT_METRIC_EQ( - kInterFrameDelayMs2, - metrics::MinSample("WebRTC.Video.Screenshare.InterframeDelayMaxInMs")); - EXPECT_METRIC_EQ( - (kInterFrameDelayMs1 + kInterFrameDelayMs2) / 2, - metrics::MinSample( - "WebRTC.Video.Screenshare.InterframeDelayInMs.ExperimentGroup0")); - } else { - EXPECT_METRIC_EQ(1, - metrics::NumSamples("WebRTC.Video.InterframeDelayInMs")); - EXPECT_METRIC_EQ( - 1, metrics::NumSamples("WebRTC.Video.InterframeDelayMaxInMs")); - EXPECT_METRIC_EQ( - 1, metrics::NumSamples("WebRTC.Video.InterframeDelayInMs.S0")); - EXPECT_METRIC_EQ( - 1, metrics::NumSamples("WebRTC.Video.InterframeDelayMaxInMs.S0")); - EXPECT_METRIC_EQ( - 1, metrics::NumSamples("WebRTC.Video.InterframeDelayInMs.S1")); - EXPECT_METRIC_EQ( - 1, metrics::NumSamples("WebRTC.Video.InterframeDelayMaxInMs.S1")); - EXPECT_METRIC_EQ(1, metrics::NumSamples("WebRTC.Video.InterframeDelayInMs" - ".ExperimentGroup0")); - EXPECT_METRIC_EQ(1, - metrics::NumSamples("WebRTC.Video.InterframeDelayMaxInMs" - ".ExperimentGroup0")); - EXPECT_METRIC_EQ(kInterFrameDelayMs1, - metrics::MinSample("WebRTC.Video.InterframeDelayInMs.S0")); - EXPECT_METRIC_EQ(kInterFrameDelayMs2, - metrics::MinSample("WebRTC.Video.InterframeDelayInMs.S1")); - EXPECT_METRIC_EQ((kInterFrameDelayMs1 + kInterFrameDelayMs2) / 2, - metrics::MinSample("WebRTC.Video.InterframeDelayInMs")); - EXPECT_METRIC_EQ(kInterFrameDelayMs2, - metrics::MinSample("WebRTC.Video.InterframeDelayMaxInMs")); - EXPECT_METRIC_EQ((kInterFrameDelayMs1 + kInterFrameDelayMs2) / 2, - metrics::MinSample( - "WebRTC.Video.InterframeDelayInMs.ExperimentGroup0")); - } -} - -class ReceiveStatisticsProxyTestWithDecodeTimeHistograms - : public ::testing::WithParamInterface< - std::tuple>, - public ReceiveStatisticsProxyTest { - public: - ReceiveStatisticsProxyTestWithDecodeTimeHistograms() - : ReceiveStatisticsProxyTest( - std::get<0>(GetParam()) - ? "WebRTC-DecodeTimeHistogramsKillSwitch/Enabled/" - : "") {} - - protected: - const std::string kUmaPrefix = "WebRTC.Video.DecodeTimePerFrameInMs."; - const int expected_number_of_samples_ = {std::get<1>(GetParam())}; - const int width_ = {std::get<2>(GetParam())}; - const int height_ = {std::get<3>(GetParam())}; - const VideoCodecType codec_type_ = {std::get<4>(GetParam())}; - const std::string implementation_name_ = {std::get<5>(GetParam())}; - const std::string uma_histogram_name_ = - kUmaPrefix + (codec_type_ == kVideoCodecVP9 ? "Vp9." : "H264.") + - (height_ == 2160 ? "4k." : "Hd.") + - (implementation_name_.compare("ExternalDecoder") == 0 ? "Hw" : "Sw"); -}; - -TEST_P(ReceiveStatisticsProxyTestWithDecodeTimeHistograms, - DecodeTimeHistogramsUpdated) { - constexpr int kNumberOfFrames = 10; - constexpr int kDecodeTimeMs = 7; - constexpr int kFrameDurationMs = 1000 / 60; - - webrtc::VideoFrame frame = CreateFrame(width_, height_); - - statistics_proxy_->OnDecoderImplementationName(implementation_name_.c_str()); - statistics_proxy_->OnPreDecode(codec_type_, /*qp=*/0); - - for (int i = 0; i < kNumberOfFrames; ++i) { - statistics_proxy_->OnDecodedFrame(frame, /*qp=*/absl::nullopt, - kDecodeTimeMs, - VideoContentType::UNSPECIFIED); - fake_clock_.AdvanceTimeMilliseconds(kFrameDurationMs); - } - - EXPECT_METRIC_EQ(expected_number_of_samples_, - metrics::NumSamples(uma_histogram_name_)); - EXPECT_METRIC_EQ(expected_number_of_samples_, - metrics::NumEvents(uma_histogram_name_, kDecodeTimeMs)); -} - -const auto kVp94kHw = std::make_tuple(/*killswitch=*/false, - /*expected_number_of_samples=*/10, - /*width=*/3840, - /*height=*/2160, - kVideoCodecVP9, - /*implementation=*/"ExternalDecoder"); -const auto kVp94kSw = std::make_tuple(/*killswitch=*/false, - /*expected_number_of_samples=*/10, - /*width=*/3840, - /*height=*/2160, - kVideoCodecVP9, - /*implementation=*/"libvpx"); -const auto kVp9HdHw = std::make_tuple(/*killswitch=*/false, - /*expected_number_of_samples=*/10, - /*width=*/1920, - /*height=*/1080, - kVideoCodecVP9, - /*implementation=*/"ExternalDecoder"); -const auto kVp9HdSw = std::make_tuple(/*killswitch=*/false, - /*expected_number_of_samples=*/10, - /*width=*/1920, - /*height=*/1080, - kVideoCodecVP9, - /*implementation=*/"libvpx"); -const auto kH2644kHw = std::make_tuple(/*killswitch=*/false, - /*expected_number_of_samples=*/10, - /*width=*/3840, - /*height=*/2160, - kVideoCodecH264, - /*implementation=*/"ExternalDecoder"); -const auto kH2644kSw = std::make_tuple(/*killswitch=*/false, - /*expected_number_of_samples=*/10, - /*width=*/3840, - /*height=*/2160, - kVideoCodecH264, - /*implementation=*/"FFmpeg"); -const auto kH264HdHw = std::make_tuple(/*killswitch=*/false, - /*expected_number_of_samples=*/10, - /*width=*/1920, - /*height=*/1080, - kVideoCodecH264, - /*implementation=*/"ExternalDecoder"); -const auto kH264HdSw = std::make_tuple(/*killswitch=*/false, - /*expected_number_of_samples=*/10, - /*width=*/1920, - /*height=*/1080, - kVideoCodecH264, - /*implementation=*/"FFmpeg"); - -INSTANTIATE_TEST_SUITE_P(AllHistogramsPopulated, - ReceiveStatisticsProxyTestWithDecodeTimeHistograms, - ::testing::Values(kVp94kHw, - kVp94kSw, - kVp9HdHw, - kVp9HdSw, - kH2644kHw, - kH2644kSw, - kH264HdHw, - kH264HdSw)); - -const auto kKillswitchDisabled = - std::make_tuple(/*killswitch=*/false, - /*expected_number_of_samples=*/10, - /*width=*/1920, - /*height=*/1080, - kVideoCodecVP9, - /*implementation=*/"libvpx"); -const auto kKillswitchEnabled = - std::make_tuple(/*killswitch=*/true, - /*expected_number_of_samples=*/0, - /*width=*/1920, - /*height=*/1080, - kVideoCodecVP9, - /*implementation=*/"libvpx"); - -INSTANTIATE_TEST_SUITE_P(KillswitchEffective, - ReceiveStatisticsProxyTestWithDecodeTimeHistograms, - ::testing::Values(kKillswitchDisabled, - kKillswitchEnabled)); - -} // namespace webrtc diff --git a/video/video_quality_observer.cc b/video/video_quality_observer.cc deleted file mode 100644 index be7b08c887..0000000000 --- a/video/video_quality_observer.cc +++ /dev/null @@ -1,286 +0,0 @@ -/* - * Copyright (c) 2018 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 "video/video_quality_observer.h" - -#include -#include -#include -#include - -#include "rtc_base/logging.h" -#include "rtc_base/strings/string_builder.h" -#include "system_wrappers/include/metrics.h" - -namespace webrtc { -const uint32_t VideoQualityObserver::kMinFrameSamplesToDetectFreeze = 5; -const uint32_t VideoQualityObserver::kMinIncreaseForFreezeMs = 150; -const uint32_t VideoQualityObserver::kAvgInterframeDelaysWindowSizeFrames = 30; - -namespace { -constexpr int kMinVideoDurationMs = 3000; -constexpr int kMinRequiredSamples = 1; -constexpr int kPixelsInHighResolution = - 960 * 540; // CPU-adapted HD still counts. -constexpr int kPixelsInMediumResolution = 640 * 360; -constexpr int kBlockyQpThresholdVp8 = 70; -constexpr int kBlockyQpThresholdVp9 = 180; -constexpr int kMaxNumCachedBlockyFrames = 100; -// TODO(ilnik): Add H264/HEVC thresholds. -} // namespace - -VideoQualityObserver::VideoQualityObserver(VideoContentType content_type) - : last_frame_rendered_ms_(-1), - num_frames_rendered_(0), - first_frame_rendered_ms_(-1), - last_frame_pixels_(0), - is_last_frame_blocky_(false), - last_unfreeze_time_ms_(0), - render_interframe_delays_(kAvgInterframeDelaysWindowSizeFrames), - sum_squared_interframe_delays_secs_(0.0), - time_in_resolution_ms_(3, 0), - current_resolution_(Resolution::Low), - num_resolution_downgrades_(0), - time_in_blocky_video_ms_(0), - content_type_(content_type), - is_paused_(false) {} - -void VideoQualityObserver::UpdateHistograms() { - // Don't report anything on an empty video stream. - if (num_frames_rendered_ == 0) { - return; - } - - char log_stream_buf[2 * 1024]; - rtc::SimpleStringBuilder log_stream(log_stream_buf); - - if (last_frame_rendered_ms_ > last_unfreeze_time_ms_) { - smooth_playback_durations_.Add(last_frame_rendered_ms_ - - last_unfreeze_time_ms_); - } - - std::string uma_prefix = videocontenttypehelpers::IsScreenshare(content_type_) - ? "WebRTC.Video.Screenshare" - : "WebRTC.Video"; - - auto mean_time_between_freezes = - smooth_playback_durations_.Avg(kMinRequiredSamples); - if (mean_time_between_freezes) { - RTC_HISTOGRAM_COUNTS_SPARSE_100000(uma_prefix + ".MeanTimeBetweenFreezesMs", - *mean_time_between_freezes); - log_stream << uma_prefix << ".MeanTimeBetweenFreezesMs " - << *mean_time_between_freezes << "\n"; - } - auto avg_freeze_length = freezes_durations_.Avg(kMinRequiredSamples); - if (avg_freeze_length) { - RTC_HISTOGRAM_COUNTS_SPARSE_100000(uma_prefix + ".MeanFreezeDurationMs", - *avg_freeze_length); - log_stream << uma_prefix << ".MeanFreezeDurationMs " << *avg_freeze_length - << "\n"; - } - - int64_t video_duration_ms = - last_frame_rendered_ms_ - first_frame_rendered_ms_; - - if (video_duration_ms >= kMinVideoDurationMs) { - int time_spent_in_hd_percentage = static_cast( - time_in_resolution_ms_[Resolution::High] * 100 / video_duration_ms); - RTC_HISTOGRAM_COUNTS_SPARSE_100(uma_prefix + ".TimeInHdPercentage", - time_spent_in_hd_percentage); - log_stream << uma_prefix << ".TimeInHdPercentage " - << time_spent_in_hd_percentage << "\n"; - - int time_with_blocky_video_percentage = - static_cast(time_in_blocky_video_ms_ * 100 / video_duration_ms); - RTC_HISTOGRAM_COUNTS_SPARSE_100(uma_prefix + ".TimeInBlockyVideoPercentage", - time_with_blocky_video_percentage); - log_stream << uma_prefix << ".TimeInBlockyVideoPercentage " - << time_with_blocky_video_percentage << "\n"; - - int num_resolution_downgrades_per_minute = - num_resolution_downgrades_ * 60000 / video_duration_ms; - RTC_HISTOGRAM_COUNTS_SPARSE_100( - uma_prefix + ".NumberResolutionDownswitchesPerMinute", - num_resolution_downgrades_per_minute); - log_stream << uma_prefix << ".NumberResolutionDownswitchesPerMinute " - << num_resolution_downgrades_per_minute << "\n"; - - int num_freezes_per_minute = - freezes_durations_.NumSamples() * 60000 / video_duration_ms; - RTC_HISTOGRAM_COUNTS_SPARSE_100(uma_prefix + ".NumberFreezesPerMinute", - num_freezes_per_minute); - log_stream << uma_prefix << ".NumberFreezesPerMinute " - << num_freezes_per_minute << "\n"; - - if (sum_squared_interframe_delays_secs_ > 0.0) { - int harmonic_framerate_fps = std::round( - video_duration_ms / (1000 * sum_squared_interframe_delays_secs_)); - RTC_HISTOGRAM_COUNTS_SPARSE_100(uma_prefix + ".HarmonicFrameRate", - harmonic_framerate_fps); - log_stream << uma_prefix << ".HarmonicFrameRate " - << harmonic_framerate_fps << "\n"; - } - } - RTC_LOG(LS_INFO) << log_stream.str(); -} - -void VideoQualityObserver::OnRenderedFrame(const VideoFrame& frame, - int64_t now_ms) { - RTC_DCHECK_LE(last_frame_rendered_ms_, now_ms); - RTC_DCHECK_LE(last_unfreeze_time_ms_, now_ms); - - if (num_frames_rendered_ == 0) { - first_frame_rendered_ms_ = last_unfreeze_time_ms_ = now_ms; - } - - auto blocky_frame_it = blocky_frames_.find(frame.timestamp()); - - if (num_frames_rendered_ > 0) { - // Process inter-frame delay. - const int64_t interframe_delay_ms = now_ms - last_frame_rendered_ms_; - const double interframe_delays_secs = interframe_delay_ms / 1000.0; - - // Sum of squared inter frame intervals is used to calculate the harmonic - // frame rate metric. The metric aims to reflect overall experience related - // to smoothness of video playback and includes both freezes and pauses. - sum_squared_interframe_delays_secs_ += - interframe_delays_secs * interframe_delays_secs; - - if (!is_paused_) { - render_interframe_delays_.AddSample(interframe_delay_ms); - - bool was_freeze = false; - if (render_interframe_delays_.Size() >= kMinFrameSamplesToDetectFreeze) { - const absl::optional avg_interframe_delay = - render_interframe_delays_.GetAverageRoundedDown(); - RTC_DCHECK(avg_interframe_delay); - was_freeze = interframe_delay_ms >= - std::max(3 * *avg_interframe_delay, - *avg_interframe_delay + kMinIncreaseForFreezeMs); - } - - if (was_freeze) { - freezes_durations_.Add(interframe_delay_ms); - smooth_playback_durations_.Add(last_frame_rendered_ms_ - - last_unfreeze_time_ms_); - last_unfreeze_time_ms_ = now_ms; - } else { - // Count spatial metrics if there were no freeze. - time_in_resolution_ms_[current_resolution_] += interframe_delay_ms; - - if (is_last_frame_blocky_) { - time_in_blocky_video_ms_ += interframe_delay_ms; - } - } - } - } - - if (is_paused_) { - // If the stream was paused since the previous frame, do not count the - // pause toward smooth playback. Explicitly count the part before it and - // start the new smooth playback interval from this frame. - is_paused_ = false; - if (last_frame_rendered_ms_ > last_unfreeze_time_ms_) { - smooth_playback_durations_.Add(last_frame_rendered_ms_ - - last_unfreeze_time_ms_); - } - last_unfreeze_time_ms_ = now_ms; - - if (num_frames_rendered_ > 0) { - pauses_durations_.Add(now_ms - last_frame_rendered_ms_); - } - } - - int64_t pixels = frame.width() * frame.height(); - if (pixels >= kPixelsInHighResolution) { - current_resolution_ = Resolution::High; - } else if (pixels >= kPixelsInMediumResolution) { - current_resolution_ = Resolution::Medium; - } else { - current_resolution_ = Resolution::Low; - } - - if (pixels < last_frame_pixels_) { - ++num_resolution_downgrades_; - } - - last_frame_pixels_ = pixels; - last_frame_rendered_ms_ = now_ms; - - is_last_frame_blocky_ = blocky_frame_it != blocky_frames_.end(); - if (is_last_frame_blocky_) { - blocky_frames_.erase(blocky_frames_.begin(), ++blocky_frame_it); - } - - ++num_frames_rendered_; -} - -void VideoQualityObserver::OnDecodedFrame(const VideoFrame& frame, - absl::optional qp, - VideoCodecType codec) { - if (qp) { - absl::optional qp_blocky_threshold; - // TODO(ilnik): add other codec types when we have QP for them. - switch (codec) { - case kVideoCodecVP8: - qp_blocky_threshold = kBlockyQpThresholdVp8; - break; - case kVideoCodecVP9: - qp_blocky_threshold = kBlockyQpThresholdVp9; - break; - default: - qp_blocky_threshold = absl::nullopt; - } - - RTC_DCHECK(blocky_frames_.find(frame.timestamp()) == blocky_frames_.end()); - - if (qp_blocky_threshold && *qp > *qp_blocky_threshold) { - // Cache blocky frame. Its duration will be calculated in render callback. - if (blocky_frames_.size() > kMaxNumCachedBlockyFrames) { - RTC_LOG(LS_WARNING) << "Overflow of blocky frames cache."; - blocky_frames_.erase( - blocky_frames_.begin(), - std::next(blocky_frames_.begin(), kMaxNumCachedBlockyFrames / 2)); - } - - blocky_frames_.insert(frame.timestamp()); - } - } -} - -void VideoQualityObserver::OnStreamInactive() { - is_paused_ = true; -} - -uint32_t VideoQualityObserver::NumFreezes() const { - return freezes_durations_.NumSamples(); -} - -uint32_t VideoQualityObserver::NumPauses() const { - return pauses_durations_.NumSamples(); -} - -uint32_t VideoQualityObserver::TotalFreezesDurationMs() const { - return freezes_durations_.Sum(kMinRequiredSamples).value_or(0); -} - -uint32_t VideoQualityObserver::TotalPausesDurationMs() const { - return pauses_durations_.Sum(kMinRequiredSamples).value_or(0); -} - -uint32_t VideoQualityObserver::TotalFramesDurationMs() const { - return last_frame_rendered_ms_ - first_frame_rendered_ms_; -} - -double VideoQualityObserver::SumSquaredFrameDurationsSec() const { - return sum_squared_interframe_delays_secs_; -} - -} // namespace webrtc diff --git a/video/video_quality_observer.h b/video/video_quality_observer.h deleted file mode 100644 index 6494a6f43c..0000000000 --- a/video/video_quality_observer.h +++ /dev/null @@ -1,99 +0,0 @@ -/* - * Copyright (c) 2018 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 VIDEO_VIDEO_QUALITY_OBSERVER_H_ -#define VIDEO_VIDEO_QUALITY_OBSERVER_H_ - -#include - -#include -#include - -#include "absl/types/optional.h" -#include "api/video/video_codec_type.h" -#include "api/video/video_content_type.h" -#include "api/video/video_frame.h" -#include "rtc_base/numerics/moving_average.h" -#include "rtc_base/numerics/sample_counter.h" - -namespace webrtc { - -// Calculates spatial and temporal quality metrics and reports them to UMA -// stats. -class VideoQualityObserver { - public: - // Use either VideoQualityObserver::kBlockyQpThresholdVp8 or - // VideoQualityObserver::kBlockyQpThresholdVp9. - explicit VideoQualityObserver(VideoContentType content_type); - ~VideoQualityObserver() = default; - - void OnDecodedFrame(const VideoFrame& frame, - absl::optional qp, - VideoCodecType codec); - - void OnRenderedFrame(const VideoFrame& frame, int64_t now_ms); - - void OnStreamInactive(); - - uint32_t NumFreezes() const; - uint32_t NumPauses() const; - uint32_t TotalFreezesDurationMs() const; - uint32_t TotalPausesDurationMs() const; - uint32_t TotalFramesDurationMs() const; - double SumSquaredFrameDurationsSec() const; - - void UpdateHistograms(); - - static const uint32_t kMinFrameSamplesToDetectFreeze; - static const uint32_t kMinIncreaseForFreezeMs; - static const uint32_t kAvgInterframeDelaysWindowSizeFrames; - - private: - enum Resolution { - Low = 0, - Medium = 1, - High = 2, - }; - - int64_t last_frame_rendered_ms_; - int64_t num_frames_rendered_; - int64_t first_frame_rendered_ms_; - int64_t last_frame_pixels_; - bool is_last_frame_blocky_; - // Decoded timestamp of the last delayed frame. - int64_t last_unfreeze_time_ms_; - rtc::MovingAverage render_interframe_delays_; - double sum_squared_interframe_delays_secs_; - // An inter-frame delay is counted as a freeze if it's significantly longer - // than average inter-frame delay. - rtc::SampleCounter freezes_durations_; - rtc::SampleCounter pauses_durations_; - // Time between freezes. - rtc::SampleCounter smooth_playback_durations_; - // Counters for time spent in different resolutions. Time between each two - // Consecutive frames is counted to bin corresponding to the first frame - // resolution. - std::vector time_in_resolution_ms_; - // Resolution of the last decoded frame. Resolution enum is used as an index. - Resolution current_resolution_; - int num_resolution_downgrades_; - // Similar to resolution, time spent in high-QP video. - int64_t time_in_blocky_video_ms_; - // Content type of the last decoded frame. - VideoContentType content_type_; - bool is_paused_; - - // Set of decoded frames with high QP value. - std::set blocky_frames_; -}; - -} // namespace webrtc - -#endif // VIDEO_VIDEO_QUALITY_OBSERVER_H_