Delete video_legacy build target

Bug: None
Change-Id: I8c3e7cb408ca09b5e60f72b103764e2b43a4d696
Reviewed-on: https://webrtc-review.googlesource.com/c/src/+/265843
Commit-Queue: Danil Chapovalov <danilchap@webrtc.org>
Reviewed-by: Mirko Bonadei <mbonadei@webrtc.org>
Reviewed-by: Erik Språng <sprang@webrtc.org>
Cr-Commit-Position: refs/heads/main@{#37243}
This commit is contained in:
Danil Chapovalov 2022-06-16 15:02:51 +02:00 committed by WebRTC LUCI CQ
parent 38a28603fd
commit 611f2c8d16
10 changed files with 0 additions and 4144 deletions

View File

@ -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",
]

View File

@ -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" ]
}
}

View File

@ -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 <algorithm>
#include <memory>
#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<CallStats::RttTime>* 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<CallStats::RttTime>& 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<CallStats::RttTime>& reports) {
RTC_DCHECK(!reports.empty());
int64_t sum = 0;
for (std::list<CallStats::RttTime>::const_iterator it = reports.begin();
it != reports.end(); ++it) {
sum += it->rtt;
}
return sum / reports.size();
}
int64_t GetNewAvgRttMs(const std::list<CallStats::RttTime>& 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

View File

@ -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 <list>
#include <memory>
#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<RttTime> 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<CallStatsObserver*> 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_

View File

@ -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 <memory>
#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<ProcessThread> 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

View File

@ -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 <algorithm>
#include <cmath>
#include <utility>
#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<int> 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<int>(
(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<int>(num_delayed_frames_rendered_ *
100 / frames_rendered));
if (num_delayed_frames_rendered_ > 0) {
RTC_HISTOGRAM_COUNTS_1000(
"WebRTC.Video.DelayedFramesToRenderer_AvgDelayInMs",
static_cast<int>(sum_missed_render_deadline_ms_ /
num_delayed_frames_rendered_));
}
}
}
}
const int kMinRequiredSamples = 200;
int samples = static_cast<int>(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<int> 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<int> 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<int> 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<int> 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<int> 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<int> 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<int> 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<VideoContentType, ContentSpecificStats> 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<int> 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<int> 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<int> 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<int> 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<uint32_t> 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<int> 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<int> 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<int>(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<int> 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<int>(rtp_rtx_stats.transmitted.TotalBytes() * 8 /
elapsed_sec / 1000));
int media_bitrate_kbs = static_cast<int>(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<int>(rtp_rtx_stats.transmitted.padding_bytes * 8 /
elapsed_sec / 1000));
RTC_HISTOGRAM_COUNTS_10000(
"WebRTC.Video.RetransmittedBitrateReceivedInKbps",
static_cast<int>(rtp_rtx_stats.retransmitted.TotalBytes() * 8 /
elapsed_sec / 1000));
if (rtx_stats) {
RTC_HISTOGRAM_COUNTS_10000(
"WebRTC.Video.RtxBitrateReceivedInKbps",
static_cast<int>(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<double> fps_fraction =
fps_threshold_.FractionHigh(kBadCallMinRequiredSamples);
if (fps_fraction) {
RTC_HISTOGRAM_PERCENTAGE("WebRTC.Video.BadCall.FrameRate",
static_cast<int>(100 * (1 - *fps_fraction)));
}
absl::optional<double> variance_fraction =
variance_threshold_.FractionHigh(kBadCallMinRequiredSamples);
if (variance_fraction) {
RTC_HISTOGRAM_PERCENTAGE("WebRTC.Video.BadCall.FrameRateVariance",
static_cast<int>(100 * *variance_fraction));
}
absl::optional<double> qp_fraction =
qp_threshold_.FractionHigh(kBadCallMinRequiredSamples);
if (qp_fraction) {
RTC_HISTOGRAM_PERCENTAGE("WebRTC.Video.BadCall.Qp",
static_cast<int>(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<int> 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<int>(fps));
if (qp)
qp_threshold_.AddMeasurement(*qp);
absl::optional<double> fps_variance_opt = fps_threshold_.CalculateVariance();
double fps_variance = fps_variance_opt.value_or(0);
if (fps_variance_opt) {
variance_threshold_.AddMeasurement(static_cast<int>(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<int>(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<int64_t>
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<double>(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<uint8_t> 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<int>(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

View File

@ -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 <map>
#include <memory>
#include <string>
#include <vector>
#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<uint8_t> 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<int> 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<int64_t> 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<VideoQualityObserver> video_quality_observer_
RTC_GUARDED_BY(mutex_);
mutable rtc::MovingMaxCounter<int> interframe_delay_max_moving_
RTC_GUARDED_BY(mutex_);
std::map<VideoContentType, ContentSpecificStats> 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<int64_t, size_t> frame_window_ RTC_GUARDED_BY(&mutex_);
VideoContentType last_content_type_ RTC_GUARDED_BY(&mutex_);
VideoCodecType last_codec_type_ RTC_GUARDED_BY(&mutex_);
absl::optional<int64_t> first_frame_received_time_ms_ RTC_GUARDED_BY(&mutex_);
absl::optional<int64_t> first_decoded_frame_time_ms_ RTC_GUARDED_BY(&mutex_);
absl::optional<int64_t> 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<TimingFrameInfo> timing_frame_info_counter_
RTC_GUARDED_BY(&mutex_);
absl::optional<int> num_unique_frames_ RTC_GUARDED_BY(mutex_);
absl::optional<int64_t> last_estimated_playout_ntp_timestamp_ms_
RTC_GUARDED_BY(&mutex_);
absl::optional<int64_t> 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_

File diff suppressed because it is too large Load Diff

View File

@ -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 <algorithm>
#include <cmath>
#include <cstdint>
#include <string>
#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<int>(
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<int>(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<int64_t> 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<uint8_t> qp,
VideoCodecType codec) {
if (qp) {
absl::optional<int> 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

View File

@ -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 <stdint.h>
#include <set>
#include <vector>
#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<uint8_t> 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<int64_t> 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<int64_t> blocky_frames_;
};
} // namespace webrtc
#endif // VIDEO_VIDEO_QUALITY_OBSERVER_H_