Merge commit 'upstream-main' into master

Bug: 261600888
Test: none, build files to be updated in follow up cl
Change-Id: Ib520938290c6bbdee4a9f73b6419b6c947a96ec4
This commit is contained in:
Jorge E. Moreira
2022-12-06 16:34:41 -08:00
5393 changed files with 541103 additions and 211666 deletions

View File

@ -16,6 +16,7 @@ rtc_library("system_wrappers") {
visibility = [ "*" ]
sources = [
"include/clock.h",
"include/cpu_features_wrapper.h",
"include/cpu_info.h",
"include/ntp_time.h",
"include/rtp_to_ntp_estimator.h",
@ -30,13 +31,15 @@ rtc_library("system_wrappers") {
defines = []
libs = []
deps = [
":cpu_features_api",
":field_trial",
"../api:array_view",
"../api/units:timestamp",
"../modules:module_api_public",
"../rtc_base:checks",
"../rtc_base:logging",
"../rtc_base:safe_conversions",
"../rtc_base:timeutils",
"../rtc_base/synchronization:mutex",
"../rtc_base/synchronization:rw_lock_wrapper",
"../rtc_base/system:arch",
"../rtc_base/system:rtc_export",
]
@ -50,15 +53,16 @@ rtc_library("system_wrappers") {
"/nsprpub/pr/include",
]
} else {
deps += [ ":cpu_features_android" ]
sources += [ "source/cpu_features_android.cc" ]
deps += [ "//third_party/android_sdk:cpu_features" ]
}
libs += [ "log" ]
}
if (is_linux) {
if (is_linux || is_chromeos) {
if (!build_with_chromium) {
deps += [ ":cpu_features_linux" ]
sources += [ "source/cpu_features_linux.cc" ]
}
libs += [ "rt" ]
@ -69,17 +73,10 @@ rtc_library("system_wrappers") {
# Windows needs ../rtc_base due to include of
# webrtc/rtc_base/win32.h in source/clock.cc.
deps += [ "../rtc_base" ]
deps += [ "../rtc_base:win32" ]
}
deps += [
"../rtc_base:rtc_base_approved",
"../rtc_base:rtc_numerics",
]
}
rtc_source_set("cpu_features_api") {
sources = [ "include/cpu_features_wrapper.h" ]
deps += [ "../rtc_base:rtc_numerics" ]
}
rtc_library("field_trial") {
@ -90,11 +87,16 @@ rtc_library("field_trial") {
defines = [ "WEBRTC_EXCLUDE_FIELD_TRIAL_DEFAULT" ]
}
deps = [
"../experiments:registered_field_trials",
"../rtc_base:checks",
"../rtc_base:logging",
"../rtc_base:stringutils",
"../rtc_base/containers:flat_set",
]
absl_deps = [
"//third_party/abseil-cpp/absl/algorithm:container",
"//third_party/abseil-cpp/absl/strings",
]
absl_deps = [ "//third_party/abseil-cpp/absl/strings" ]
}
rtc_library("metrics") {
@ -106,34 +108,32 @@ rtc_library("metrics") {
}
deps = [
"../rtc_base:checks",
"../rtc_base:rtc_base_approved",
"../rtc_base:macromagic",
"../rtc_base:stringutils",
"../rtc_base/synchronization:mutex",
]
absl_deps = [ "//third_party/abseil-cpp/absl/strings" ]
}
if (is_android && !build_with_mozilla) {
rtc_library("cpu_features_android") {
sources = [ "source/cpu_features_android.c" ]
deps = [ "//third_party/android_sdk:cpu_features" ]
rtc_library("denormal_disabler") {
visibility = [ "*" ]
public = [ "include/denormal_disabler.h" ]
sources = [ "source/denormal_disabler.cc" ]
deps = [
"../rtc_base:checks",
"../rtc_base/system:arch",
]
if (is_clang) {
cflags_cc = [ "-Wno-unused-private-field" ]
}
}
if (is_linux) {
rtc_library("cpu_features_linux") {
sources = [ "source/cpu_features_linux.c" ]
deps = [
":cpu_features_api",
"../rtc_base/system:arch",
]
}
}
if (rtc_include_tests) {
if (rtc_include_tests && !build_with_chromium) {
rtc_test("system_wrappers_unittests") {
testonly = true
sources = [
"source/clock_unittest.cc",
"source/denormal_disabler_unittest.cc",
"source/field_trial_unittest.cc",
"source/metrics_default_unittest.cc",
"source/metrics_unittest.cc",
@ -142,18 +142,21 @@ if (rtc_include_tests) {
]
deps = [
":denormal_disabler",
":field_trial",
":metrics",
":system_wrappers",
"../rtc_base:checks",
"../rtc_base:rtc_base_approved",
"../rtc_base:random",
"../rtc_base:stringutils",
"../test:rtc_expect_death",
"../test:test_main",
"../test:test_support",
"//testing/gtest",
"//third_party/abseil-cpp/absl/strings",
]
absl_deps = [ "//third_party/abseil-cpp/absl/strings" ]
if (is_android) {
deps += [ "//testing/android/native_test:native_test_support" ]

View File

@ -1,3 +1,2 @@
henrika@webrtc.org
mflodman@webrtc.org
nisse@webrtc.org

View File

@ -13,10 +13,10 @@
#include <stdint.h>
#include <atomic>
#include <memory>
#include "api/units/timestamp.h"
#include "rtc_base/synchronization/rw_lock_wrapper.h"
#include "rtc_base/system/rtc_export.h"
#include "system_wrappers/include/ntp_time.h"
@ -32,22 +32,20 @@ const double kMagicNtpFractionalUnit = 4.294967296E+9;
class RTC_EXPORT Clock {
public:
virtual ~Clock() {}
// Return a timestamp relative to an unspecified epoch.
virtual Timestamp CurrentTime() {
return Timestamp::Micros(TimeInMicroseconds());
}
virtual int64_t TimeInMilliseconds() { return CurrentTime().ms(); }
virtual int64_t TimeInMicroseconds() { return CurrentTime().us(); }
virtual Timestamp CurrentTime() = 0;
int64_t TimeInMilliseconds() { return CurrentTime().ms(); }
int64_t TimeInMicroseconds() { return CurrentTime().us(); }
// Retrieve an NTP absolute timestamp.
virtual NtpTime CurrentNtpTime() = 0;
// Retrieve an NTP absolute timestamp (with an epoch of Jan 1, 1900).
NtpTime CurrentNtpTime() { return ConvertTimestampToNtpTime(CurrentTime()); }
int64_t CurrentNtpInMilliseconds() { return CurrentNtpTime().ToMs(); }
// Retrieve an NTP absolute timestamp in milliseconds.
virtual int64_t CurrentNtpInMilliseconds() = 0;
// Converts an NTP timestamp to a millisecond timestamp.
static int64_t NtpToMs(uint32_t seconds, uint32_t fractions) {
return NtpTime(seconds, fractions).ToMs();
// Converts between a relative timestamp returned by this clock, to NTP time.
virtual NtpTime ConvertTimestampToNtpTime(Timestamp timestamp) = 0;
int64_t ConvertTimestampToNtpTimeInMilliseconds(int64_t timestamp_ms) {
return ConvertTimestampToNtpTime(Timestamp::Millis(timestamp_ms)).ToMs();
}
// Returns an instance of the real-time system clock implementation.
@ -56,20 +54,15 @@ class RTC_EXPORT Clock {
class SimulatedClock : public Clock {
public:
// The constructors assume an epoch of Jan 1, 1970.
explicit SimulatedClock(int64_t initial_time_us);
explicit SimulatedClock(Timestamp initial_time);
~SimulatedClock() override;
// Return a timestamp relative to some arbitrary source; the source is fixed
// for this clock.
// Return a timestamp with an epoch of Jan 1, 1970.
Timestamp CurrentTime() override;
// Retrieve an NTP absolute timestamp.
NtpTime CurrentNtpTime() override;
// Converts an NTP timestamp to a millisecond timestamp.
int64_t CurrentNtpInMilliseconds() override;
NtpTime ConvertTimestampToNtpTime(Timestamp timestamp) override;
// Advance the simulated clock with a given number of milliseconds or
// microseconds.
@ -78,8 +71,12 @@ class SimulatedClock : public Clock {
void AdvanceTime(TimeDelta delta);
private:
Timestamp time_;
std::unique_ptr<RWLockWrapper> lock_;
// The time is read and incremented with relaxed order. Each thread will see
// monotonically increasing time, and when threads post tasks or messages to
// one another, the synchronization done as part of the message passing should
// ensure that any causual chain of events on multiple threads also
// corresponds to monotonically increasing time.
std::atomic<int64_t> time_us_;
};
} // namespace webrtc

View File

@ -13,12 +13,10 @@
#include <stdint.h>
#if defined(__cplusplus) || defined(c_plusplus)
extern "C" {
#endif
namespace webrtc {
// List of features in x86.
typedef enum { kSSE2, kSSE3 } CPUFeature;
typedef enum { kSSE2, kSSE3, kAVX2 } CPUFeature;
// List of features in ARM.
enum {
@ -28,21 +26,17 @@ enum {
kCPUFeatureLDREXSTREX = (1 << 3)
};
typedef int (*WebRtc_CPUInfo)(CPUFeature feature);
// Returns true if the CPU supports the feature.
extern WebRtc_CPUInfo WebRtc_GetCPUInfo;
int GetCPUInfo(CPUFeature feature);
// No CPU feature is available => straight C path.
extern WebRtc_CPUInfo WebRtc_GetCPUInfoNoASM;
int GetCPUInfoNoASM(CPUFeature feature);
// Return the features in an ARM device.
// It detects the features in the hardware platform, and returns supported
// values in the above enum definition as a bitmask.
extern uint64_t WebRtc_GetCPUFeaturesARM(void);
uint64_t GetCPUFeaturesARM(void);
#if defined(__cplusplus) || defined(c_plusplus)
} // extern "C"
#endif
} // namespace webrtc
#endif // SYSTEM_WRAPPERS_INCLUDE_CPU_FEATURES_WRAPPER_H_

View File

@ -0,0 +1,54 @@
/*
* Copyright (c) 2021 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 SYSTEM_WRAPPERS_INCLUDE_DENORMAL_DISABLER_H_
#define SYSTEM_WRAPPERS_INCLUDE_DENORMAL_DISABLER_H_
#include "rtc_base/system/arch.h"
namespace webrtc {
// Activates the hardware (HW) way to flush denormals (see [1]) to zero as they
// can very seriously impact performance. At destruction time restores the
// denormals handling state read by the ctor; hence, supports nested calls.
// Equals a no-op if the architecture is not x86 or ARM or if the compiler is
// not CLANG.
// [1] https://en.wikipedia.org/wiki/Denormal_number
//
// Example usage:
//
// void Foo() {
// DenormalDisabler d;
// ...
// }
class DenormalDisabler {
public:
// Ctor. If `enabled` is true and architecture and compiler are supported,
// stores the HW settings for denormals, disables denormals and sets
// `disabling_activated_` to true. Otherwise, only sets `disabling_activated_`
// to false.
explicit DenormalDisabler(bool enabled);
DenormalDisabler(const DenormalDisabler&) = delete;
DenormalDisabler& operator=(const DenormalDisabler&) = delete;
// Dtor. If `disabling_activated_` is true, restores the denormals HW settings
// read by the ctor before denormals were disabled. Otherwise it's a no-op.
~DenormalDisabler();
// Returns true if architecture and compiler are supported.
static bool IsSupported();
private:
const int status_word_;
const bool disabling_activated_;
};
} // namespace webrtc
#endif // SYSTEM_WRAPPERS_INCLUDE_DENORMAL_DISABLER_H_

View File

@ -13,6 +13,9 @@
#include <string>
#include "absl/strings/string_view.h"
#include "rtc_base/containers/flat_set.h"
// Field trials allow webrtc clients (such as Chrome) to turn on feature code
// in binaries out in the field and gather information with that.
//
@ -24,7 +27,7 @@
// WEBRTC_EXCLUDE_FIELD_TRIAL_DEFAULT (if GN is used this can be achieved
// by setting the GN arg rtc_exclude_field_trial_default to true).
// 2. Provide an implementation of:
// std::string webrtc::field_trial::FindFullName(const std::string& trial).
// std::string webrtc::field_trial::FindFullName(absl::string_view trial).
//
// They are designed to wire up directly to chrome field trials and to speed up
// developers by reducing the need to wire APIs to control whether a feature is
@ -61,18 +64,18 @@ namespace field_trial {
// if the trial does not exists.
//
// Note: To keep things tidy append all the trial names with WebRTC.
std::string FindFullName(const std::string& name);
std::string FindFullName(absl::string_view name);
// Convenience method, returns true iff FindFullName(name) return a string that
// starts with "Enabled".
// TODO(tommi): Make sure all implementations support this.
inline bool IsEnabled(const char* name) {
inline bool IsEnabled(absl::string_view name) {
return FindFullName(name).find("Enabled") == 0;
}
// Convenience method, returns true iff FindFullName(name) return a string that
// starts with "Disabled".
inline bool IsDisabled(const char* name) {
inline bool IsDisabled(absl::string_view name) {
return FindFullName(name).find("Disabled") == 0;
}
@ -84,17 +87,28 @@ void InitFieldTrialsFromString(const char* trials_string);
const char* GetFieldTrialString();
#ifndef WEBRTC_EXCLUDE_FIELD_TRIAL_DEFAULT
// Validates the given field trial string.
bool FieldTrialsStringIsValid(const char* trials_string);
bool FieldTrialsStringIsValid(absl::string_view trials_string);
// Merges two field trial strings.
//
// If a key (trial) exists twice with conflicting values (groups), the value
// in 'second' takes precedence.
// Shall only be called with valid FieldTrial strings.
std::string MergeFieldTrialsStrings(const char* first, const char* second);
#endif // WEBRTC_EXCLUDE_FIELD_TRIAL_DEFAULT
std::string MergeFieldTrialsStrings(absl::string_view first,
absl::string_view second);
// This helper allows to temporary "register" a field trial within the current
// scope. This is only useful for tests that use the global field trial string,
// otherwise you can use `webrtc::FieldTrialsRegistry`.
//
// If you want to isolate changes to the global field trial string itself within
// the current scope you should use `webrtc::test::ScopedFieldTrials`.
class FieldTrialsAllowedInScopeForTesting {
public:
explicit FieldTrialsAllowedInScopeForTesting(flat_set<std::string> keys);
~FieldTrialsAllowedInScopeForTesting();
};
} // namespace field_trial
} // namespace webrtc

View File

@ -13,12 +13,14 @@
#include <stddef.h>
#include <atomic>
#include <map>
#include <memory>
#include <string>
#include "rtc_base/atomic_ops.h"
#include "absl/strings/string_view.h"
#include "rtc_base/checks.h"
#include "rtc_base/string_utils.h"
#if defined(RTC_DISABLE_METRICS)
#define RTC_METRICS_ENABLED 0
@ -76,12 +78,12 @@ void NoOp(const Ts&...) {}
// by setting the GN arg rtc_exclude_metrics_default to true).
// 2. Provide implementations of:
// Histogram* webrtc::metrics::HistogramFactoryGetCounts(
// const std::string& name, int sample, int min, int max,
// absl::string_view name, int sample, int min, int max,
// int bucket_count);
// Histogram* webrtc::metrics::HistogramFactoryGetEnumeration(
// const std::string& name, int sample, int boundary);
// absl::string_view name, int sample, int boundary);
// void webrtc::metrics::HistogramAdd(
// Histogram* histogram_pointer, const std::string& name, int sample);
// Histogram* histogram_pointer, absl::string_view name, int sample);
//
// Example usage:
//
@ -163,7 +165,7 @@ void NoOp(const Ts&...) {}
RTC_HISTOGRAM_ENUMERATION_SPARSE(name, sample, 2)
// Histogram for enumerators (evenly spaced buckets).
// |boundary| should be above the max enumerator sample.
// `boundary` should be above the max enumerator sample.
//
// TODO(qingsi): Refactor the default implementation given by RtcHistogram,
// which is already sparse, and remove the boundary argument from the macro.
@ -181,33 +183,29 @@ void NoOp(const Ts&...) {}
RTC_HISTOGRAM_ENUMERATION(name, sample, 2)
// Histogram for enumerators (evenly spaced buckets).
// |boundary| should be above the max enumerator sample.
// `boundary` should be above the max enumerator sample.
#define RTC_HISTOGRAM_ENUMERATION(name, sample, boundary) \
RTC_HISTOGRAM_COMMON_BLOCK_SLOW( \
name, sample, \
webrtc::metrics::HistogramFactoryGetEnumeration(name, boundary))
// The name of the histogram should not vary.
// TODO(asapersson): Consider changing string to const char*.
#define RTC_HISTOGRAM_COMMON_BLOCK(constant_name, sample, \
factory_get_invocation) \
do { \
static webrtc::metrics::Histogram* atomic_histogram_pointer = nullptr; \
webrtc::metrics::Histogram* histogram_pointer = \
rtc::AtomicOps::AcquireLoadPtr(&atomic_histogram_pointer); \
if (!histogram_pointer) { \
histogram_pointer = factory_get_invocation; \
webrtc::metrics::Histogram* prev_pointer = \
rtc::AtomicOps::CompareAndSwapPtr( \
&atomic_histogram_pointer, \
static_cast<webrtc::metrics::Histogram*>(nullptr), \
histogram_pointer); \
RTC_DCHECK(prev_pointer == nullptr || \
prev_pointer == histogram_pointer); \
} \
if (histogram_pointer) { \
webrtc::metrics::HistogramAdd(histogram_pointer, sample); \
} \
#define RTC_HISTOGRAM_COMMON_BLOCK(constant_name, sample, \
factory_get_invocation) \
do { \
static std::atomic<webrtc::metrics::Histogram*> atomic_histogram_pointer( \
nullptr); \
webrtc::metrics::Histogram* histogram_pointer = \
atomic_histogram_pointer.load(std::memory_order_acquire); \
if (!histogram_pointer) { \
histogram_pointer = factory_get_invocation; \
webrtc::metrics::Histogram* null_histogram = nullptr; \
atomic_histogram_pointer.compare_exchange_strong(null_histogram, \
histogram_pointer); \
} \
if (histogram_pointer) { \
webrtc::metrics::HistogramAdd(histogram_pointer, sample); \
} \
} while (0)
// The histogram is constructed/found for each call.
@ -223,7 +221,7 @@ void NoOp(const Ts&...) {}
// Helper macros.
// Macros for calling a histogram with varying name (e.g. when using a metric
// in different modes such as real-time vs screenshare). Fast, because pointer
// is cached. |index| should be different for different names. Allowed |index|
// is cached. `index` should be different for different names. Allowed `index`
// values are 0, 1, and 2.
#define RTC_HISTOGRAMS_COUNTS_100(index, name, sample) \
RTC_HISTOGRAMS_COMMON(index, name, sample, \
@ -270,7 +268,7 @@ void NoOp(const Ts&...) {}
macro_invocation; \
break; \
default: \
RTC_NOTREACHED(); \
RTC_DCHECK_NOTREACHED(); \
} \
} while (0)
@ -363,7 +361,7 @@ namespace webrtc {
namespace metrics {
// Time that should have elapsed for stats that are gathered once per call.
enum { kMinRunTimeInSeconds = 10 };
constexpr int kMinRunTimeInSeconds = 10;
class Histogram;
@ -371,32 +369,31 @@ class Histogram;
// histogram).
// Get histogram for counters.
Histogram* HistogramFactoryGetCounts(const std::string& name,
Histogram* HistogramFactoryGetCounts(absl::string_view name,
int min,
int max,
int bucket_count);
// Get histogram for counters with linear bucket spacing.
Histogram* HistogramFactoryGetCountsLinear(const std::string& name,
Histogram* HistogramFactoryGetCountsLinear(absl::string_view name,
int min,
int max,
int bucket_count);
// Get histogram for enumerators.
// |boundary| should be above the max enumerator sample.
Histogram* HistogramFactoryGetEnumeration(const std::string& name,
int boundary);
// `boundary` should be above the max enumerator sample.
Histogram* HistogramFactoryGetEnumeration(absl::string_view name, int boundary);
// Get sparse histogram for enumerators.
// |boundary| should be above the max enumerator sample.
Histogram* SparseHistogramFactoryGetEnumeration(const std::string& name,
// `boundary` should be above the max enumerator sample.
Histogram* SparseHistogramFactoryGetEnumeration(absl::string_view name,
int boundary);
// Function for adding a |sample| to a histogram.
// Function for adding a `sample` to a histogram.
void HistogramAdd(Histogram* histogram_pointer, int sample);
struct SampleInfo {
SampleInfo(const std::string& name, int min, int max, size_t bucket_count);
SampleInfo(absl::string_view name, int min, int max, size_t bucket_count);
~SampleInfo();
const std::string name;
@ -412,25 +409,26 @@ void Enable();
// Gets histograms and clears all samples.
void GetAndReset(
std::map<std::string, std::unique_ptr<SampleInfo>>* histograms);
std::map<std::string, std::unique_ptr<SampleInfo>, rtc::AbslStringViewCmp>*
histograms);
// Functions below are mainly for testing.
// Clears all samples.
void Reset();
// Returns the number of times the |sample| has been added to the histogram.
int NumEvents(const std::string& name, int sample);
// Returns the number of times the `sample` has been added to the histogram.
int NumEvents(absl::string_view name, int sample);
// Returns the total number of added samples to the histogram.
int NumSamples(const std::string& name);
int NumSamples(absl::string_view name);
// Returns the minimum sample value (or -1 if the histogram has no samples).
int MinSample(const std::string& name);
int MinSample(absl::string_view name);
// Returns a map with keys the samples with at least one event and values the
// number of events for that sample.
std::map<int, int> Samples(const std::string& name);
std::map<int, int> Samples(absl::string_view name);
} // namespace metrics
} // namespace webrtc

View File

@ -62,10 +62,10 @@ inline bool operator!=(const NtpTime& n1, const NtpTime& n2) {
return !(n1 == n2);
}
// Converts |int64_t| milliseconds to Q32.32-formatted fixed-point seconds.
// Converts `int64_t` milliseconds to Q32.32-formatted fixed-point seconds.
// Performs clamping if the result overflows or underflows.
inline int64_t Int64MsToQ32x32(int64_t milliseconds) {
// TODO(bugs.webrtc.org/10893): Change to use |rtc::saturated_cast| once the
// TODO(bugs.webrtc.org/10893): Change to use `rtc::saturated_cast` once the
// bug has been fixed.
double result =
std::round(milliseconds * (NtpTime::kFractionsPerSecond / 1000.0));
@ -85,10 +85,10 @@ inline int64_t Int64MsToQ32x32(int64_t milliseconds) {
return rtc::dchecked_cast<int64_t>(result);
}
// Converts |int64_t| milliseconds to UQ32.32-formatted fixed-point seconds.
// Converts `int64_t` milliseconds to UQ32.32-formatted fixed-point seconds.
// Performs clamping if the result overflows or underflows.
inline uint64_t Int64MsToUQ32x32(int64_t milliseconds) {
// TODO(bugs.webrtc.org/10893): Change to use |rtc::saturated_cast| once the
// TODO(bugs.webrtc.org/10893): Change to use `rtc::saturated_cast` once the
// bug has been fixed.
double result =
std::round(milliseconds * (NtpTime::kFractionsPerSecond / 1000.0));
@ -108,13 +108,13 @@ inline uint64_t Int64MsToUQ32x32(int64_t milliseconds) {
return rtc::dchecked_cast<uint64_t>(result);
}
// Converts Q32.32-formatted fixed-point seconds to |int64_t| milliseconds.
// Converts Q32.32-formatted fixed-point seconds to `int64_t` milliseconds.
inline int64_t Q32x32ToInt64Ms(int64_t q32x32) {
return rtc::dchecked_cast<int64_t>(
std::round(q32x32 * (1000.0 / NtpTime::kFractionsPerSecond)));
}
// Converts UQ32.32-formatted fixed-point seconds to |int64_t| milliseconds.
// Converts UQ32.32-formatted fixed-point seconds to `int64_t` milliseconds.
inline int64_t UQ32x32ToInt64Ms(uint64_t q32x32) {
return rtc::dchecked_cast<int64_t>(
std::round(q32x32 * (1000.0 / NtpTime::kFractionsPerSecond)));

View File

@ -18,60 +18,51 @@
#include "absl/types/optional.h"
#include "modules/include/module_common_types_public.h"
#include "rtc_base/checks.h"
#include "rtc_base/numerics/moving_median_filter.h"
#include "system_wrappers/include/ntp_time.h"
namespace webrtc {
// Class for converting an RTP timestamp to the NTP domain in milliseconds.
// Converts an RTP timestamp to the NTP domain.
// The class needs to be trained with (at least 2) RTP/NTP timestamp pairs from
// RTCP sender reports before the convertion can be done.
class RtpToNtpEstimator {
public:
RtpToNtpEstimator();
~RtpToNtpEstimator();
static constexpr int kMaxInvalidSamples = 3;
RtpToNtpEstimator() = default;
RtpToNtpEstimator(const RtpToNtpEstimator&) = delete;
RtpToNtpEstimator& operator=(const RtpToNtpEstimator&) = delete;
~RtpToNtpEstimator() = default;
enum UpdateResult { kInvalidMeasurement, kSameMeasurement, kNewMeasurement };
// Updates measurements with RTP/NTP timestamp pair from a RTCP sender report.
UpdateResult UpdateMeasurements(NtpTime ntp, uint32_t rtp_timestamp);
// Converts an RTP timestamp to the NTP domain.
// Returns invalid NtpTime (i.e. NtpTime(0)) on failure.
NtpTime Estimate(uint32_t rtp_timestamp) const;
// Returns estimated rtp_timestamp frequency, or 0 on failure.
double EstimatedFrequencyKhz() const;
private:
// Estimated parameters from RTP and NTP timestamp pairs in `measurements_`.
// Defines linear estimation: NtpTime (in units of 1s/2^32) =
// `Parameters::slope` * rtp_timestamp + `Parameters::offset`.
struct Parameters {
double slope;
double offset;
};
// RTP and NTP timestamp pair from a RTCP SR report.
struct RtcpMeasurement {
RtcpMeasurement(uint32_t ntp_secs,
uint32_t ntp_frac,
int64_t unwrapped_timestamp);
bool IsEqual(const RtcpMeasurement& other) const;
NtpTime ntp_time;
int64_t unwrapped_rtp_timestamp;
};
// Estimated parameters from RTP and NTP timestamp pairs in |measurements_|.
struct Parameters {
Parameters() : frequency_khz(0.0), offset_ms(0.0) {}
Parameters(double frequency_khz, double offset_ms)
: frequency_khz(frequency_khz), offset_ms(offset_ms) {}
double frequency_khz;
double offset_ms;
};
// Updates measurements with RTP/NTP timestamp pair from a RTCP sender report.
// |new_rtcp_sr| is set to true if a new report is added.
bool UpdateMeasurements(uint32_t ntp_secs,
uint32_t ntp_frac,
uint32_t rtp_timestamp,
bool* new_rtcp_sr);
// Converts an RTP timestamp to the NTP domain in milliseconds.
// Returns true on success, false otherwise.
bool Estimate(int64_t rtp_timestamp, int64_t* ntp_timestamp_ms) const;
// Returns estimated rtp to ntp linear transform parameters.
const absl::optional<Parameters> params() const;
static const int kMaxInvalidSamples = 3;
private:
void UpdateParameters();
int consecutive_invalid_samples_;
int consecutive_invalid_samples_ = 0;
std::list<RtcpMeasurement> measurements_;
absl::optional<Parameters> params_;
mutable TimestampUnwrapper unwrapper_;

View File

@ -0,0 +1,6 @@
specific_include_rules = {
# TODO(bugs.webrtc.org/10335): Remove rule when global string is removed.
"field_trial\.cc": [
"+experiments/registered_field_trials.h",
],
}

View File

@ -10,259 +10,78 @@
#include "system_wrappers/include/clock.h"
#if defined(WEBRTC_WIN)
// Windows needs to be included before mmsystem.h
#include "rtc_base/win32.h"
#include <mmsystem.h>
#elif defined(WEBRTC_POSIX)
#include <sys/time.h>
#include <time.h>
#endif // defined(WEBRTC_POSIX)
#include "rtc_base/synchronization/mutex.h"
#include "rtc_base/synchronization/rw_lock_wrapper.h"
#include "rtc_base/time_utils.h"
namespace webrtc {
namespace {
int64_t NtpOffsetUsCalledOnce() {
constexpr int64_t kNtpJan1970Sec = 2208988800;
int64_t clock_time = rtc::TimeMicros();
int64_t utc_time = rtc::TimeUTCMicros();
return utc_time - clock_time + kNtpJan1970Sec * rtc::kNumMicrosecsPerSec;
}
NtpTime TimeMicrosToNtp(int64_t time_us) {
static int64_t ntp_offset_us = NtpOffsetUsCalledOnce();
int64_t time_ntp_us = time_us + ntp_offset_us;
RTC_DCHECK_GE(time_ntp_us, 0); // Time before year 1900 is unsupported.
// Convert seconds to uint32 through uint64 for a well-defined cast.
// A wrap around, which will happen in 2036, is expected for NTP time.
uint32_t ntp_seconds =
static_cast<uint64_t>(time_ntp_us / rtc::kNumMicrosecsPerSec);
// Scale fractions of the second to NTP resolution.
constexpr int64_t kNtpFractionsInSecond = 1LL << 32;
int64_t us_fractions = time_ntp_us % rtc::kNumMicrosecsPerSec;
uint32_t ntp_fractions =
us_fractions * kNtpFractionsInSecond / rtc::kNumMicrosecsPerSec;
return NtpTime(ntp_seconds, ntp_fractions);
}
} // namespace
class RealTimeClock : public Clock {
public:
RealTimeClock() = default;
Timestamp CurrentTime() override {
return Timestamp::Micros(rtc::TimeMicros());
}
// Return a timestamp in milliseconds relative to some arbitrary source; the
// source is fixed for this clock.
int64_t TimeInMilliseconds() override { return rtc::TimeMillis(); }
// Return a timestamp in microseconds relative to some arbitrary source; the
// source is fixed for this clock.
int64_t TimeInMicroseconds() override { return rtc::TimeMicros(); }
// Retrieve an NTP absolute timestamp.
NtpTime CurrentNtpTime() override {
timeval tv = CurrentTimeVal();
double microseconds_in_seconds;
uint32_t seconds;
Adjust(tv, &seconds, &microseconds_in_seconds);
uint32_t fractions = static_cast<uint32_t>(
microseconds_in_seconds * kMagicNtpFractionalUnit + 0.5);
return NtpTime(seconds, fractions);
}
// Retrieve an NTP absolute timestamp in milliseconds.
int64_t CurrentNtpInMilliseconds() override {
timeval tv = CurrentTimeVal();
uint32_t seconds;
double microseconds_in_seconds;
Adjust(tv, &seconds, &microseconds_in_seconds);
return 1000 * static_cast<int64_t>(seconds) +
static_cast<int64_t>(1000.0 * microseconds_in_seconds + 0.5);
}
protected:
virtual timeval CurrentTimeVal() = 0;
static void Adjust(const timeval& tv,
uint32_t* adjusted_s,
double* adjusted_us_in_s) {
*adjusted_s = tv.tv_sec + kNtpJan1970;
*adjusted_us_in_s = tv.tv_usec / 1e6;
if (*adjusted_us_in_s >= 1) {
*adjusted_us_in_s -= 1;
++*adjusted_s;
} else if (*adjusted_us_in_s < -1) {
*adjusted_us_in_s += 1;
--*adjusted_s;
}
NtpTime ConvertTimestampToNtpTime(Timestamp timestamp) override {
return TimeMicrosToNtp(timestamp.us());
}
};
#if defined(WINUWP)
class WinUwpRealTimeClock final : public RealTimeClock {
public:
WinUwpRealTimeClock() = default;
~WinUwpRealTimeClock() override {}
protected:
timeval CurrentTimeVal() override {
// The rtc::SystemTimeNanos() method is already time offset from a base
// epoch value and might as be synchronized against an NTP time server as
// an added bonus.
auto nanos = rtc::SystemTimeNanos();
struct timeval tv;
tv.tv_sec = rtc::dchecked_cast<long>(nanos / 1000000000);
tv.tv_usec = rtc::dchecked_cast<long>(nanos / 1000);
return tv;
}
};
#elif defined(WEBRTC_WIN)
// TODO(pbos): Consider modifying the implementation to synchronize itself
// against system time (update ref_point_) periodically to
// prevent clock drift.
class WindowsRealTimeClock : public RealTimeClock {
public:
WindowsRealTimeClock()
: last_time_ms_(0),
num_timer_wraps_(0),
ref_point_(GetSystemReferencePoint()) {}
~WindowsRealTimeClock() override {}
protected:
struct ReferencePoint {
FILETIME file_time;
LARGE_INTEGER counter_ms;
};
timeval CurrentTimeVal() override {
const uint64_t FILETIME_1970 = 0x019db1ded53e8000;
FILETIME StartTime;
uint64_t Time;
struct timeval tv;
// We can't use query performance counter since they can change depending on
// speed stepping.
GetTime(&StartTime);
Time = (((uint64_t)StartTime.dwHighDateTime) << 32) +
(uint64_t)StartTime.dwLowDateTime;
// Convert the hecto-nano second time to tv format.
Time -= FILETIME_1970;
tv.tv_sec = (uint32_t)(Time / (uint64_t)10000000);
tv.tv_usec = (uint32_t)((Time % (uint64_t)10000000) / 10);
return tv;
}
void GetTime(FILETIME* current_time) {
DWORD t;
LARGE_INTEGER elapsed_ms;
{
MutexLock lock(&mutex_);
// time MUST be fetched inside the critical section to avoid non-monotonic
// last_time_ms_ values that'll register as incorrect wraparounds due to
// concurrent calls to GetTime.
t = timeGetTime();
if (t < last_time_ms_)
num_timer_wraps_++;
last_time_ms_ = t;
elapsed_ms.HighPart = num_timer_wraps_;
}
elapsed_ms.LowPart = t;
elapsed_ms.QuadPart = elapsed_ms.QuadPart - ref_point_.counter_ms.QuadPart;
// Translate to 100-nanoseconds intervals (FILETIME resolution)
// and add to reference FILETIME to get current FILETIME.
ULARGE_INTEGER filetime_ref_as_ul;
filetime_ref_as_ul.HighPart = ref_point_.file_time.dwHighDateTime;
filetime_ref_as_ul.LowPart = ref_point_.file_time.dwLowDateTime;
filetime_ref_as_ul.QuadPart +=
static_cast<ULONGLONG>((elapsed_ms.QuadPart) * 1000 * 10);
// Copy to result
current_time->dwHighDateTime = filetime_ref_as_ul.HighPart;
current_time->dwLowDateTime = filetime_ref_as_ul.LowPart;
}
static ReferencePoint GetSystemReferencePoint() {
ReferencePoint ref = {};
FILETIME ft0 = {};
FILETIME ft1 = {};
// Spin waiting for a change in system time. As soon as this change happens,
// get the matching call for timeGetTime() as soon as possible. This is
// assumed to be the most accurate offset that we can get between
// timeGetTime() and system time.
// Set timer accuracy to 1 ms.
timeBeginPeriod(1);
GetSystemTimeAsFileTime(&ft0);
do {
GetSystemTimeAsFileTime(&ft1);
ref.counter_ms.QuadPart = timeGetTime();
Sleep(0);
} while ((ft0.dwHighDateTime == ft1.dwHighDateTime) &&
(ft0.dwLowDateTime == ft1.dwLowDateTime));
ref.file_time = ft1;
timeEndPeriod(1);
return ref;
}
Mutex mutex_;
DWORD last_time_ms_;
LONG num_timer_wraps_;
const ReferencePoint ref_point_;
};
#elif defined(WEBRTC_POSIX)
class UnixRealTimeClock : public RealTimeClock {
public:
UnixRealTimeClock() {}
~UnixRealTimeClock() override {}
protected:
timeval CurrentTimeVal() override {
struct timeval tv;
struct timezone tz;
tz.tz_minuteswest = 0;
tz.tz_dsttime = 0;
gettimeofday(&tv, &tz);
return tv;
}
};
#endif // defined(WEBRTC_POSIX)
Clock* Clock::GetRealTimeClock() {
#if defined(WINUWP)
static Clock* const clock = new WinUwpRealTimeClock();
#elif defined(WEBRTC_WIN)
static Clock* const clock = new WindowsRealTimeClock();
#elif defined(WEBRTC_POSIX)
static Clock* const clock = new UnixRealTimeClock();
#else
static Clock* const clock = nullptr;
#endif
static Clock* const clock = new RealTimeClock();
return clock;
}
SimulatedClock::SimulatedClock(int64_t initial_time_us)
: SimulatedClock(Timestamp::Micros(initial_time_us)) {}
: time_us_(initial_time_us) {}
SimulatedClock::SimulatedClock(Timestamp initial_time)
: time_(initial_time), lock_(RWLockWrapper::CreateRWLock()) {}
: SimulatedClock(initial_time.us()) {}
SimulatedClock::~SimulatedClock() {}
Timestamp SimulatedClock::CurrentTime() {
ReadLockScoped synchronize(*lock_);
return time_;
return Timestamp::Micros(time_us_.load(std::memory_order_relaxed));
}
NtpTime SimulatedClock::CurrentNtpTime() {
int64_t now_ms = TimeInMilliseconds();
uint32_t seconds = (now_ms / 1000) + kNtpJan1970;
uint32_t fractions =
static_cast<uint32_t>((now_ms % 1000) * kMagicNtpFractionalUnit / 1000);
NtpTime SimulatedClock::ConvertTimestampToNtpTime(Timestamp timestamp) {
int64_t now_us = timestamp.us();
uint32_t seconds = (now_us / 1'000'000) + kNtpJan1970;
uint32_t fractions = static_cast<uint32_t>(
(now_us % 1'000'000) * kMagicNtpFractionalUnit / 1'000'000);
return NtpTime(seconds, fractions);
}
int64_t SimulatedClock::CurrentNtpInMilliseconds() {
return TimeInMilliseconds() + 1000 * static_cast<int64_t>(kNtpJan1970);
}
void SimulatedClock::AdvanceTimeMilliseconds(int64_t milliseconds) {
AdvanceTime(TimeDelta::Millis(milliseconds));
}
@ -271,9 +90,13 @@ void SimulatedClock::AdvanceTimeMicroseconds(int64_t microseconds) {
AdvanceTime(TimeDelta::Micros(microseconds));
}
// TODO(bugs.webrtc.org(12102): It's desirable to let a single thread own
// advancement of the clock. We could then replace this read-modify-write
// operation with just a thread checker. But currently, that breaks a couple of
// tests, in particular, RepeatingTaskTest.ClockIntegration and
// CallStatsTest.LastProcessedRtt.
void SimulatedClock::AdvanceTime(TimeDelta delta) {
WriteLockScoped synchronize(*lock_);
time_ += delta;
time_us_.fetch_add(delta.us(), std::memory_order_relaxed);
}
} // namespace webrtc

View File

@ -12,11 +12,14 @@
#include "rtc_base/system/arch.h"
#include "system_wrappers/include/cpu_features_wrapper.h"
#include "system_wrappers/include/field_trial.h"
#if defined(WEBRTC_ARCH_X86_FAMILY) && defined(_MSC_VER)
#include <intrin.h>
#endif
namespace webrtc {
// No CPU feature is available => straight C path.
int GetCPUInfoNoASM(CPUFeature feature) {
(void)feature;
@ -24,6 +27,22 @@ int GetCPUInfoNoASM(CPUFeature feature) {
}
#if defined(WEBRTC_ARCH_X86_FAMILY)
#if defined(WEBRTC_ENABLE_AVX2)
// xgetbv returns the value of an Intel Extended Control Register (XCR).
// Currently only XCR0 is defined by Intel so `xcr` should always be zero.
static uint64_t xgetbv(uint32_t xcr) {
#if defined(_MSC_VER)
return _xgetbv(xcr);
#else
uint32_t eax, edx;
__asm__ volatile("xgetbv" : "=a"(eax), "=d"(edx) : "c"(xcr));
return (static_cast<uint64_t>(edx) << 32) | eax;
#endif // _MSC_VER
}
#endif // WEBRTC_ENABLE_AVX2
#ifndef _MSC_VER
// Intrinsic for "cpuid".
#if defined(__pic__) && defined(__i386__)
@ -41,7 +60,7 @@ static inline void __cpuid(int cpu_info[4], int info_type) {
__asm__ volatile("cpuid\n"
: "=a"(cpu_info[0]), "=b"(cpu_info[1]), "=c"(cpu_info[2]),
"=d"(cpu_info[3])
: "a"(info_type));
: "a"(info_type), "c"(0));
}
#endif
#endif // _MSC_VER
@ -49,7 +68,7 @@ static inline void __cpuid(int cpu_info[4], int info_type) {
#if defined(WEBRTC_ARCH_X86_FAMILY)
// Actual feature detection for x86.
static int GetCPUInfo(CPUFeature feature) {
int GetCPUInfo(CPUFeature feature) {
int cpu_info[4];
__cpuid(cpu_info, 1);
if (feature == kSSE2) {
@ -58,15 +77,40 @@ static int GetCPUInfo(CPUFeature feature) {
if (feature == kSSE3) {
return 0 != (cpu_info[2] & 0x00000001);
}
#if defined(WEBRTC_ENABLE_AVX2)
if (feature == kAVX2 &&
!webrtc::field_trial::IsEnabled("WebRTC-Avx2SupportKillSwitch")) {
int cpu_info7[4];
__cpuid(cpu_info7, 0);
int num_ids = cpu_info7[0];
if (num_ids < 7) {
return 0;
}
// Interpret CPU feature information.
__cpuid(cpu_info7, 7);
// AVX instructions can be used when
// a) AVX are supported by the CPU,
// b) XSAVE is supported by the CPU,
// c) XSAVE is enabled by the kernel.
// Compiling with MSVC and /arch:AVX2 surprisingly generates BMI2
// instructions (see crbug.com/1315519).
return (cpu_info[2] & 0x10000000) != 0 /* AVX */ &&
(cpu_info[2] & 0x04000000) != 0 /* XSAVE */ &&
(cpu_info[2] & 0x08000000) != 0 /* OSXSAVE */ &&
(xgetbv(0) & 0x00000006) == 6 /* XSAVE enabled by kernel */ &&
(cpu_info7[1] & 0x00000020) != 0 /* AVX2 */ &&
(cpu_info7[1] & 0x00000100) != 0 /* BMI2 */;
}
#endif // WEBRTC_ENABLE_AVX2
return 0;
}
#else
// Default to straight C for other platforms.
static int GetCPUInfo(CPUFeature feature) {
int GetCPUInfo(CPUFeature feature) {
(void)feature;
return 0;
}
#endif
WebRtc_CPUInfo WebRtc_GetCPUInfo = GetCPUInfo;
WebRtc_CPUInfo WebRtc_GetCPUInfoNoASM = GetCPUInfoNoASM;
} // namespace webrtc

View File

@ -10,6 +10,10 @@
#include <cpu-features.h>
uint64_t WebRtc_GetCPUFeaturesARM(void) {
namespace webrtc {
uint64_t GetCPUFeaturesARM(void) {
return android_getCpuFeatures();
}
} // namespace webrtc

View File

@ -8,32 +8,39 @@
* be found in the AUTHORS file in the root of the source tree.
*/
#include <features.h>
#include <stdlib.h>
#include <string.h>
#include <features.h>
#ifndef __GLIBC_PREREQ
#define __GLIBC_PREREQ(a, b) 0
#ifdef __GLIBC_PREREQ
#define WEBRTC_GLIBC_PREREQ(a, b) __GLIBC_PREREQ(a, b)
#else
#define WEBRTC_GLIBC_PREREQ(a, b) 0
#endif
#if __GLIBC_PREREQ(2, 16)
#if WEBRTC_GLIBC_PREREQ(2, 16)
#include <sys/auxv.h>
#else
#include <fcntl.h>
#include <unistd.h>
#include <errno.h>
#include <fcntl.h>
#include <link.h>
#include <unistd.h>
#endif
#include "rtc_base/system/arch.h"
#include "system_wrappers/include/cpu_features_wrapper.h"
#if defined(WEBRTC_ARCH_ARM_FAMILY)
#include <asm/hwcap.h>
uint64_t WebRtc_GetCPUFeaturesARM(void) {
namespace webrtc {
uint64_t GetCPUFeaturesARM(void) {
uint64_t result = 0;
int architecture = 0;
unsigned long hwcap = 0;
uint64_t hwcap = 0;
const char* platform = NULL;
#if __GLIBC_PREREQ(2, 16)
#if WEBRTC_GLIBC_PREREQ(2, 16)
hwcap = getauxval(AT_HWCAP);
platform = (const char*)getauxval(AT_PLATFORM);
#else
@ -57,8 +64,9 @@ uint64_t WebRtc_GetCPUFeaturesARM(void) {
}
close(fd);
}
#endif // __GLIBC_PREREQ(2,16)
#endif // WEBRTC_GLIBC_PREREQ(2, 16)
#if defined(__aarch64__)
(void)platform;
architecture = 8;
if ((hwcap & HWCAP_FP) != 0)
result |= kCPUFeatureVFPv3;
@ -84,4 +92,6 @@ uint64_t WebRtc_GetCPUFeaturesARM(void) {
result |= kCPUFeatureLDREXSTREX;
return result;
}
} // namespace webrtc
#endif // WEBRTC_ARCH_ARM_FAMILY

View File

@ -32,7 +32,7 @@ static int DetectNumberOfCores() {
number_of_cores = static_cast<int>(si.dwNumberOfProcessors);
#elif defined(WEBRTC_LINUX) || defined(WEBRTC_ANDROID)
number_of_cores = static_cast<int>(sysconf(_SC_NPROCESSORS_ONLN));
if (number_of_cores < 0) {
if (number_of_cores <= 0) {
RTC_LOG(LS_ERROR) << "Failed to get number of cores";
number_of_cores = 1;
}

View File

@ -0,0 +1,107 @@
/*
* Copyright (c) 2021 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 "system_wrappers/include/denormal_disabler.h"
#include "rtc_base/checks.h"
namespace webrtc {
namespace {
#if defined(WEBRTC_ARCH_X86_FAMILY) && defined(__clang__)
#define WEBRTC_DENORMAL_DISABLER_X86_SUPPORTED
#endif
#if defined(WEBRTC_DENORMAL_DISABLER_X86_SUPPORTED) || \
defined(WEBRTC_ARCH_ARM_FAMILY)
#define WEBRTC_DENORMAL_DISABLER_SUPPORTED
#endif
constexpr int kUnspecifiedStatusWord = -1;
#if defined(WEBRTC_DENORMAL_DISABLER_SUPPORTED)
// Control register bit mask to disable denormals on the hardware.
#if defined(WEBRTC_DENORMAL_DISABLER_X86_SUPPORTED)
// On x86 two bits are used: flush-to-zero (FTZ) and denormals-are-zero (DAZ).
constexpr int kDenormalBitMask = 0x8040;
#elif defined(WEBRTC_ARCH_ARM_FAMILY)
// On ARM one bit is used: flush-to-zero (FTZ).
constexpr int kDenormalBitMask = 1 << 24;
#endif
// Reads the relevant CPU control register and returns its value for supported
// architectures and compilers. Otherwise returns `kUnspecifiedStatusWord`.
int ReadStatusWord() {
int result = kUnspecifiedStatusWord;
#if defined(WEBRTC_DENORMAL_DISABLER_X86_SUPPORTED)
asm volatile("stmxcsr %0" : "=m"(result));
#elif defined(WEBRTC_ARCH_ARM_FAMILY) && defined(WEBRTC_ARCH_32_BITS)
asm volatile("vmrs %[result], FPSCR" : [result] "=r"(result));
#elif defined(WEBRTC_ARCH_ARM_FAMILY) && defined(WEBRTC_ARCH_64_BITS)
asm volatile("mrs %x[result], FPCR" : [result] "=r"(result));
#endif
return result;
}
// Writes `status_word` in the relevant CPU control register if the architecture
// and the compiler are supported.
void SetStatusWord(int status_word) {
#if defined(WEBRTC_DENORMAL_DISABLER_X86_SUPPORTED)
asm volatile("ldmxcsr %0" : : "m"(status_word));
#elif defined(WEBRTC_ARCH_ARM_FAMILY) && defined(WEBRTC_ARCH_32_BITS)
asm volatile("vmsr FPSCR, %[src]" : : [src] "r"(status_word));
#elif defined(WEBRTC_ARCH_ARM_FAMILY) && defined(WEBRTC_ARCH_64_BITS)
asm volatile("msr FPCR, %x[src]" : : [src] "r"(status_word));
#endif
}
// Returns true if the status word indicates that denormals are enabled.
constexpr bool DenormalsEnabled(int status_word) {
return (status_word & kDenormalBitMask) != kDenormalBitMask;
}
#endif // defined(WEBRTC_DENORMAL_DISABLER_SUPPORTED)
} // namespace
#if defined(WEBRTC_DENORMAL_DISABLER_SUPPORTED)
DenormalDisabler::DenormalDisabler(bool enabled)
: status_word_(enabled ? ReadStatusWord() : kUnspecifiedStatusWord),
disabling_activated_(enabled && DenormalsEnabled(status_word_)) {
if (disabling_activated_) {
RTC_DCHECK_NE(status_word_, kUnspecifiedStatusWord);
SetStatusWord(status_word_ | kDenormalBitMask);
RTC_DCHECK(!DenormalsEnabled(ReadStatusWord()));
}
}
bool DenormalDisabler::IsSupported() {
return true;
}
DenormalDisabler::~DenormalDisabler() {
if (disabling_activated_) {
RTC_DCHECK_NE(status_word_, kUnspecifiedStatusWord);
SetStatusWord(status_word_);
}
}
#else
DenormalDisabler::DenormalDisabler(bool enabled)
: status_word_(kUnspecifiedStatusWord), disabling_activated_(false) {}
bool DenormalDisabler::IsSupported() {
return false;
}
DenormalDisabler::~DenormalDisabler() = default;
#endif
} // namespace webrtc

View File

@ -0,0 +1,146 @@
/*
* Copyright (c) 2021 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 "system_wrappers/include/denormal_disabler.h"
#include <cmath>
#include <limits>
#include <vector>
#include "rtc_base/checks.h"
#include "test/gtest.h"
namespace webrtc {
namespace {
constexpr float kSmallest = std::numeric_limits<float>::min();
// Float values such that, if used as divisors of `kSmallest`, the division
// produces a denormal or zero depending on whether denormals are enabled.
constexpr float kDenormalDivisors[] = {123.125f, 97.0f, 32.0f, 5.0f, 1.5f};
// Returns true if the result of `dividend` / `divisor` is a denormal.
// `dividend` and `divisor` must not be denormals.
bool DivisionIsDenormal(float dividend, float divisor) {
RTC_DCHECK_GE(std::fabsf(dividend), kSmallest);
RTC_DCHECK_GE(std::fabsf(divisor), kSmallest);
volatile float division = dividend / divisor;
return division != 0.0f && std::fabsf(division) < kSmallest;
}
} // namespace
class DenormalDisablerParametrization : public ::testing::TestWithParam<bool> {
};
// Checks that +inf and -inf are not zeroed regardless of whether
// architecture and compiler are supported.
TEST_P(DenormalDisablerParametrization, InfNotZeroed) {
DenormalDisabler denormal_disabler(/*enabled=*/GetParam());
constexpr float kMax = std::numeric_limits<float>::max();
for (float x : {-2.0f, 2.0f}) {
SCOPED_TRACE(x);
volatile float multiplication = kMax * x;
EXPECT_TRUE(std::isinf(multiplication));
}
}
// Checks that a NaN is not zeroed regardless of whether architecture and
// compiler are supported.
TEST_P(DenormalDisablerParametrization, NanNotZeroed) {
DenormalDisabler denormal_disabler(/*enabled=*/GetParam());
volatile float kNan = std::sqrt(-1.0f);
EXPECT_TRUE(std::isnan(kNan));
}
INSTANTIATE_TEST_SUITE_P(DenormalDisabler,
DenormalDisablerParametrization,
::testing::Values(false, true),
[](const ::testing::TestParamInfo<bool>& info) {
return info.param ? "enabled" : "disabled";
});
// Checks that denormals are not zeroed if `DenormalDisabler` is disabled and
// architecture and compiler are supported.
TEST(DenormalDisabler, DoNotZeroDenormalsIfDisabled) {
if (!DenormalDisabler::IsSupported()) {
GTEST_SKIP() << "Unsupported platform.";
}
ASSERT_TRUE(DivisionIsDenormal(kSmallest, kDenormalDivisors[0]))
<< "Precondition not met: denormals must be enabled.";
DenormalDisabler denormal_disabler(/*enabled=*/false);
for (float x : kDenormalDivisors) {
SCOPED_TRACE(x);
EXPECT_TRUE(DivisionIsDenormal(-kSmallest, x));
EXPECT_TRUE(DivisionIsDenormal(kSmallest, x));
}
}
// Checks that denormals are zeroed if `DenormalDisabler` is enabled if
// architecture and compiler are supported.
TEST(DenormalDisabler, ZeroDenormals) {
if (!DenormalDisabler::IsSupported()) {
GTEST_SKIP() << "Unsupported platform.";
}
DenormalDisabler denormal_disabler(/*enabled=*/true);
for (float x : kDenormalDivisors) {
SCOPED_TRACE(x);
EXPECT_FALSE(DivisionIsDenormal(-kSmallest, x));
EXPECT_FALSE(DivisionIsDenormal(kSmallest, x));
}
}
// Checks that the `DenormalDisabler` dtor re-enables denormals if previously
// enabled and architecture and compiler are supported.
TEST(DenormalDisabler, RestoreDenormalsEnabled) {
if (!DenormalDisabler::IsSupported()) {
GTEST_SKIP() << "Unsupported platform.";
}
ASSERT_TRUE(DivisionIsDenormal(kSmallest, kDenormalDivisors[0]))
<< "Precondition not met: denormals must be enabled.";
{
DenormalDisabler denormal_disabler(/*enabled=*/true);
ASSERT_FALSE(DivisionIsDenormal(kSmallest, kDenormalDivisors[0]));
}
EXPECT_TRUE(DivisionIsDenormal(kSmallest, kDenormalDivisors[0]));
}
// Checks that the `DenormalDisabler` dtor keeps denormals disabled if
// architecture and compiler are supported and if previously disabled - i.e.,
// nested usage is supported.
TEST(DenormalDisabler, ZeroDenormalsNested) {
if (!DenormalDisabler::IsSupported()) {
GTEST_SKIP() << "Unsupported platform.";
}
DenormalDisabler d1(/*enabled=*/true);
ASSERT_FALSE(DivisionIsDenormal(kSmallest, kDenormalDivisors[0]));
{
DenormalDisabler d2(/*enabled=*/true);
ASSERT_FALSE(DivisionIsDenormal(kSmallest, kDenormalDivisors[0]));
}
EXPECT_FALSE(DivisionIsDenormal(kSmallest, kDenormalDivisors[0]));
}
// Checks that `DenormalDisabler` does not zero denormals if architecture and
// compiler are not supported.
TEST(DenormalDisabler, DoNotZeroDenormalsIfUnsupported) {
if (DenormalDisabler::IsSupported()) {
GTEST_SKIP() << "This test should only run on platforms without support "
"for DenormalDisabler.";
}
DenormalDisabler denormal_disabler(/*enabled=*/true);
for (float x : kDenormalDivisors) {
SCOPED_TRACE(x);
EXPECT_TRUE(DivisionIsDenormal(-kSmallest, x));
EXPECT_TRUE(DivisionIsDenormal(kSmallest, x));
}
}
} // namespace webrtc

View File

@ -13,9 +13,13 @@
#include <map>
#include <string>
#include <utility>
#include "absl/algorithm/container.h"
#include "absl/strings/string_view.h"
#include "experiments/registered_field_trials.h"
#include "rtc_base/checks.h"
#include "rtc_base/containers/flat_set.h"
#include "rtc_base/logging.h"
#include "rtc_base/string_encode.h"
@ -26,9 +30,15 @@ namespace field_trial {
static const char* trials_init_string = NULL;
#ifndef WEBRTC_EXCLUDE_FIELD_TRIAL_DEFAULT
namespace {
constexpr char kPersistentStringSeparator = '/';
flat_set<std::string>& TestKeys() {
static auto* test_keys = new flat_set<std::string>();
return *test_keys;
}
// Validates the given field trial string.
// E.g.:
// "WebRTC-experimentFoo/Enabled/WebRTC-experimentBar/Enabled100kbps/"
@ -68,9 +78,10 @@ bool FieldTrialsStringIsValidInternal(const absl::string_view trials) {
return true;
}
} // namespace
bool FieldTrialsStringIsValid(const char* trials_string) {
bool FieldTrialsStringIsValid(absl::string_view trials_string) {
return FieldTrialsStringIsValidInternal(trials_string);
}
@ -78,18 +89,19 @@ void InsertOrReplaceFieldTrialStringsInMap(
std::map<std::string, std::string>* fieldtrial_map,
const absl::string_view trials_string) {
if (FieldTrialsStringIsValidInternal(trials_string)) {
std::vector<std::string> tokens;
rtc::split(std::string(trials_string), '/', &tokens);
std::vector<absl::string_view> tokens = rtc::split(trials_string, '/');
// Skip last token which is empty due to trailing '/'.
for (size_t idx = 0; idx < tokens.size() - 1; idx += 2) {
(*fieldtrial_map)[tokens[idx]] = tokens[idx + 1];
(*fieldtrial_map)[std::string(tokens[idx])] =
std::string(tokens[idx + 1]);
}
} else {
RTC_DCHECK(false) << "Invalid field trials string:" << trials_string;
RTC_DCHECK_NOTREACHED() << "Invalid field trials string:" << trials_string;
}
}
std::string MergeFieldTrialsStrings(const char* first, const char* second) {
std::string MergeFieldTrialsStrings(absl::string_view first,
absl::string_view second) {
std::map<std::string, std::string> fieldtrial_map;
InsertOrReplaceFieldTrialStringsInMap(&fieldtrial_map, first);
InsertOrReplaceFieldTrialStringsInMap(&fieldtrial_map, second);
@ -102,11 +114,18 @@ std::string MergeFieldTrialsStrings(const char* first, const char* second) {
return merged;
}
std::string FindFullName(const std::string& name) {
#ifndef WEBRTC_EXCLUDE_FIELD_TRIAL_DEFAULT
std::string FindFullName(absl::string_view name) {
#if WEBRTC_STRICT_FIELD_TRIALS
RTC_DCHECK(absl::c_linear_search(kRegisteredFieldTrials, name) ||
TestKeys().contains(name))
<< name << " is not registered.";
#endif
if (trials_init_string == NULL)
return std::string();
std::string trials_string(trials_init_string);
absl::string_view trials_string(trials_init_string);
if (trials_string.empty())
return std::string();
@ -122,14 +141,14 @@ std::string FindFullName(const std::string& name) {
if (field_value_end == trials_string.npos ||
field_value_end == field_name_end + 1)
break;
std::string field_name(trials_string, next_item,
field_name_end - next_item);
std::string field_value(trials_string, field_name_end + 1,
field_value_end - field_name_end - 1);
absl::string_view field_name =
trials_string.substr(next_item, field_name_end - next_item);
absl::string_view field_value = trials_string.substr(
field_name_end + 1, field_value_end - field_name_end - 1);
next_item = field_value_end + 1;
if (name == field_name)
return field_value;
return std::string(field_value);
}
return std::string();
}
@ -138,12 +157,10 @@ std::string FindFullName(const std::string& name) {
// Optionally initialize field trial from a string.
void InitFieldTrialsFromString(const char* trials_string) {
RTC_LOG(LS_INFO) << "Setting field trial string:" << trials_string;
#ifndef WEBRTC_EXCLUDE_FIELD_TRIAL_DEFAULT
if (trials_string) {
RTC_DCHECK(FieldTrialsStringIsValidInternal(trials_string))
<< "Invalid field trials string:" << trials_string;
};
#endif // WEBRTC_EXCLUDE_FIELD_TRIAL_DEFAULT
trials_init_string = trials_string;
}
@ -151,5 +168,14 @@ const char* GetFieldTrialString() {
return trials_init_string;
}
FieldTrialsAllowedInScopeForTesting::FieldTrialsAllowedInScopeForTesting(
flat_set<std::string> keys) {
TestKeys() = std::move(keys);
}
FieldTrialsAllowedInScopeForTesting::~FieldTrialsAllowedInScopeForTesting() {
TestKeys().clear();
}
} // namespace field_trial
} // namespace webrtc

View File

@ -11,7 +11,8 @@
#include <algorithm>
#include "rtc_base/constructor_magic.h"
#include "absl/strings/string_view.h"
#include "rtc_base/string_utils.h"
#include "rtc_base/synchronization/mutex.h"
#include "rtc_base/thread_annotations.h"
@ -30,11 +31,14 @@ const int kMaxSampleMapSize = 300;
class RtcHistogram {
public:
RtcHistogram(const std::string& name, int min, int max, int bucket_count)
RtcHistogram(absl::string_view name, int min, int max, int bucket_count)
: min_(min), max_(max), info_(name, min, max, bucket_count) {
RTC_DCHECK_GT(bucket_count, 0);
}
RtcHistogram(const RtcHistogram&) = delete;
RtcHistogram& operator=(const RtcHistogram&) = delete;
void Add(int sample) {
sample = std::min(sample, max_);
sample = std::max(sample, min_ - 1); // Underflow bucket.
@ -99,8 +103,6 @@ class RtcHistogram {
const int min_;
const int max_;
SampleInfo info_ RTC_GUARDED_BY(mutex_);
RTC_DISALLOW_COPY_AND_ASSIGN(RtcHistogram);
};
class RtcHistogramMap {
@ -108,7 +110,10 @@ class RtcHistogramMap {
RtcHistogramMap() {}
~RtcHistogramMap() {}
Histogram* GetCountsHistogram(const std::string& name,
RtcHistogramMap(const RtcHistogramMap&) = delete;
RtcHistogramMap& operator=(const RtcHistogramMap&) = delete;
Histogram* GetCountsHistogram(absl::string_view name,
int min,
int max,
int bucket_count) {
@ -118,23 +123,24 @@ class RtcHistogramMap {
return reinterpret_cast<Histogram*>(it->second.get());
RtcHistogram* hist = new RtcHistogram(name, min, max, bucket_count);
map_[name].reset(hist);
map_.emplace(name, hist);
return reinterpret_cast<Histogram*>(hist);
}
Histogram* GetEnumerationHistogram(const std::string& name, int boundary) {
Histogram* GetEnumerationHistogram(absl::string_view name, int boundary) {
MutexLock lock(&mutex_);
const auto& it = map_.find(name);
if (it != map_.end())
return reinterpret_cast<Histogram*>(it->second.get());
RtcHistogram* hist = new RtcHistogram(name, 1, boundary, boundary + 1);
map_[name].reset(hist);
map_.emplace(name, hist);
return reinterpret_cast<Histogram*>(hist);
}
void GetAndReset(
std::map<std::string, std::unique_ptr<SampleInfo>>* histograms) {
void GetAndReset(std::map<std::string,
std::unique_ptr<SampleInfo>,
rtc::AbslStringViewCmp>* histograms) {
MutexLock lock(&mutex_);
for (const auto& kv : map_) {
std::unique_ptr<SampleInfo> info = kv.second->GetAndReset();
@ -150,25 +156,25 @@ class RtcHistogramMap {
kv.second->Reset();
}
int NumEvents(const std::string& name, int sample) const {
int NumEvents(absl::string_view name, int sample) const {
MutexLock lock(&mutex_);
const auto& it = map_.find(name);
return (it == map_.end()) ? 0 : it->second->NumEvents(sample);
}
int NumSamples(const std::string& name) const {
int NumSamples(absl::string_view name) const {
MutexLock lock(&mutex_);
const auto& it = map_.find(name);
return (it == map_.end()) ? 0 : it->second->NumSamples();
}
int MinSample(const std::string& name) const {
int MinSample(absl::string_view name) const {
MutexLock lock(&mutex_);
const auto& it = map_.find(name);
return (it == map_.end()) ? -1 : it->second->MinSample();
}
std::map<int, int> Samples(const std::string& name) const {
std::map<int, int> Samples(absl::string_view name) const {
MutexLock lock(&mutex_);
const auto& it = map_.find(name);
return (it == map_.end()) ? std::map<int, int>() : it->second->Samples();
@ -176,25 +182,21 @@ class RtcHistogramMap {
private:
mutable Mutex mutex_;
std::map<std::string, std::unique_ptr<RtcHistogram>> map_
RTC_GUARDED_BY(mutex_);
RTC_DISALLOW_COPY_AND_ASSIGN(RtcHistogramMap);
std::map<std::string, std::unique_ptr<RtcHistogram>, rtc::AbslStringViewCmp>
map_ RTC_GUARDED_BY(mutex_);
};
// RtcHistogramMap is allocated upon call to Enable().
// The histogram getter functions, which return pointer values to the histograms
// in the map, are cached in WebRTC. Therefore, this memory is not freed by the
// application (the memory will be reclaimed by the OS).
static RtcHistogramMap* volatile g_rtc_histogram_map = nullptr;
static std::atomic<RtcHistogramMap*> g_rtc_histogram_map(nullptr);
void CreateMap() {
RtcHistogramMap* map = rtc::AtomicOps::AcquireLoadPtr(&g_rtc_histogram_map);
RtcHistogramMap* map = g_rtc_histogram_map.load(std::memory_order_acquire);
if (map == nullptr) {
RtcHistogramMap* new_map = new RtcHistogramMap();
RtcHistogramMap* old_map = rtc::AtomicOps::CompareAndSwapPtr(
&g_rtc_histogram_map, static_cast<RtcHistogramMap*>(nullptr), new_map);
if (old_map != nullptr)
if (!g_rtc_histogram_map.compare_exchange_strong(map, new_map))
delete new_map;
}
}
@ -202,15 +204,15 @@ void CreateMap() {
// Set the first time we start using histograms. Used to make sure Enable() is
// not called thereafter.
#if RTC_DCHECK_IS_ON
static volatile int g_rtc_histogram_called = 0;
static std::atomic<int> g_rtc_histogram_called(0);
#endif
// Gets the map (or nullptr).
RtcHistogramMap* GetMap() {
#if RTC_DCHECK_IS_ON
rtc::AtomicOps::ReleaseStore(&g_rtc_histogram_called, 1);
g_rtc_histogram_called.store(1, std::memory_order_release);
#endif
return g_rtc_histogram_map;
return g_rtc_histogram_map.load();
}
} // namespace
@ -222,7 +224,7 @@ RtcHistogramMap* GetMap() {
// Creates (or finds) histogram.
// The returned histogram pointer is cached (and used for adding samples in
// subsequent calls).
Histogram* HistogramFactoryGetCounts(const std::string& name,
Histogram* HistogramFactoryGetCounts(absl::string_view name,
int min,
int max,
int bucket_count) {
@ -235,7 +237,7 @@ Histogram* HistogramFactoryGetCounts(const std::string& name,
// Creates (or finds) histogram.
// The returned histogram pointer is cached (and used for adding samples in
// subsequent calls).
Histogram* HistogramFactoryGetCountsLinear(const std::string& name,
Histogram* HistogramFactoryGetCountsLinear(absl::string_view name,
int min,
int max,
int bucket_count) {
@ -250,7 +252,7 @@ Histogram* HistogramFactoryGetCountsLinear(const std::string& name,
// Creates (or finds) histogram.
// The returned histogram pointer is cached (and used for adding samples in
// subsequent calls).
Histogram* HistogramFactoryGetEnumeration(const std::string& name,
Histogram* HistogramFactoryGetEnumeration(absl::string_view name,
int boundary) {
RtcHistogramMap* map = GetMap();
if (!map)
@ -260,12 +262,12 @@ Histogram* HistogramFactoryGetEnumeration(const std::string& name,
}
// Our default implementation reuses the non-sparse histogram.
Histogram* SparseHistogramFactoryGetEnumeration(const std::string& name,
Histogram* SparseHistogramFactoryGetEnumeration(absl::string_view name,
int boundary) {
return HistogramFactoryGetEnumeration(name, boundary);
}
// Fast path. Adds |sample| to cached |histogram_pointer|.
// Fast path. Adds `sample` to cached `histogram_pointer`.
void HistogramAdd(Histogram* histogram_pointer, int sample) {
RtcHistogram* ptr = reinterpret_cast<RtcHistogram*>(histogram_pointer);
ptr->Add(sample);
@ -273,7 +275,7 @@ void HistogramAdd(Histogram* histogram_pointer, int sample) {
#endif // WEBRTC_EXCLUDE_METRICS_DEFAULT
SampleInfo::SampleInfo(const std::string& name,
SampleInfo::SampleInfo(absl::string_view name,
int min,
int max,
size_t bucket_count)
@ -283,15 +285,16 @@ SampleInfo::~SampleInfo() {}
// Implementation of global functions in metrics.h.
void Enable() {
RTC_DCHECK(g_rtc_histogram_map == nullptr);
RTC_DCHECK(g_rtc_histogram_map.load() == nullptr);
#if RTC_DCHECK_IS_ON
RTC_DCHECK_EQ(0, rtc::AtomicOps::AcquireLoad(&g_rtc_histogram_called));
RTC_DCHECK_EQ(0, g_rtc_histogram_called.load(std::memory_order_acquire));
#endif
CreateMap();
}
void GetAndReset(
std::map<std::string, std::unique_ptr<SampleInfo>>* histograms) {
std::map<std::string, std::unique_ptr<SampleInfo>, rtc::AbslStringViewCmp>*
histograms) {
histograms->clear();
RtcHistogramMap* map = GetMap();
if (map)
@ -304,22 +307,22 @@ void Reset() {
map->Reset();
}
int NumEvents(const std::string& name, int sample) {
int NumEvents(absl::string_view name, int sample) {
RtcHistogramMap* map = GetMap();
return map ? map->NumEvents(name, sample) : 0;
}
int NumSamples(const std::string& name) {
int NumSamples(absl::string_view name) {
RtcHistogramMap* map = GetMap();
return map ? map->NumSamples(name) : 0;
}
int MinSample(const std::string& name) {
int MinSample(absl::string_view name) {
RtcHistogramMap* map = GetMap();
return map ? map->MinSample(name) : -1;
}
std::map<int, int> Samples(const std::string& name) {
std::map<int, int> Samples(absl::string_view name) {
RtcHistogramMap* map = GetMap();
return map ? map->Samples(name) : std::map<int, int>();
}

View File

@ -14,6 +14,7 @@
#include <utility>
#include "rtc_base/checks.h"
#include "rtc_base/string_utils.h"
#include "system_wrappers/include/metrics.h"
#include "test/gtest.h"
@ -24,10 +25,10 @@ namespace {
const int kSample = 22;
const char kName[] = "Name";
int NumSamples(
const std::string& name,
const std::map<std::string, std::unique_ptr<metrics::SampleInfo>>&
histograms) {
int NumSamples(absl::string_view name,
const std::map<std::string,
std::unique_ptr<metrics::SampleInfo>,
rtc::AbslStringViewCmp>& histograms) {
const auto it = histograms.find(name);
if (it == histograms.end())
return 0;
@ -39,10 +40,11 @@ int NumSamples(
return num_samples;
}
int NumEvents(const std::string& name,
int NumEvents(absl::string_view name,
int sample,
const std::map<std::string, std::unique_ptr<metrics::SampleInfo>>&
histograms) {
const std::map<std::string,
std::unique_ptr<metrics::SampleInfo>,
rtc::AbslStringViewCmp>& histograms) {
const auto it = histograms.find(name);
if (it == histograms.end())
return 0;
@ -118,7 +120,9 @@ TEST_F(MetricsDefaultTest, Underflow) {
}
TEST_F(MetricsDefaultTest, GetAndReset) {
std::map<std::string, std::unique_ptr<metrics::SampleInfo>> histograms;
std::map<std::string, std::unique_ptr<metrics::SampleInfo>,
rtc::AbslStringViewCmp>
histograms;
metrics::GetAndReset(&histograms);
EXPECT_EQ(0u, histograms.size());
RTC_HISTOGRAM_PERCENTAGE("Histogram1", 4);
@ -154,7 +158,9 @@ TEST_F(MetricsDefaultTest, TestMinMaxBucket) {
const std::string kName = "MinMaxCounts100";
RTC_HISTOGRAM_COUNTS_100(kName, 4);
std::map<std::string, std::unique_ptr<metrics::SampleInfo>> histograms;
std::map<std::string, std::unique_ptr<metrics::SampleInfo>,
rtc::AbslStringViewCmp>
histograms;
metrics::GetAndReset(&histograms);
EXPECT_EQ(1u, histograms.size());
EXPECT_EQ(kName, histograms.begin()->second->name);

View File

@ -10,6 +10,7 @@
#include "system_wrappers/include/metrics.h"
#include "absl/strings/string_view.h"
#include "test/gmock.h"
#include "test/gtest.h"
@ -22,10 +23,10 @@ namespace webrtc {
namespace {
const int kSample = 22;
void AddSparseSample(const std::string& name, int sample) {
void AddSparseSample(absl::string_view name, int sample) {
RTC_HISTOGRAM_COUNTS_SPARSE_100(name, sample);
}
void AddSampleWithVaryingName(int index, const std::string& name, int sample) {
void AddSampleWithVaryingName(int index, absl::string_view name, int sample) {
RTC_HISTOGRAMS_COUNTS_100(index, name, sample);
}
} // namespace

View File

@ -56,7 +56,6 @@ TEST(NtpTimeTest, ToMsMeansToNtpMilliseconds) {
SimulatedClock clock(0x123456789abc);
NtpTime ntp = clock.CurrentNtpTime();
EXPECT_EQ(ntp.ToMs(), Clock::NtpToMs(ntp.seconds(), ntp.fractions()));
EXPECT_EQ(ntp.ToMs(), clock.CurrentNtpInMilliseconds());
}

View File

@ -18,132 +18,85 @@
#include "api/array_view.h"
#include "rtc_base/checks.h"
#include "rtc_base/logging.h"
#include "rtc_base/numerics/safe_conversions.h"
namespace webrtc {
namespace {
// Maximum number of RTCP SR reports to use to map between RTP and NTP.
const size_t kNumRtcpReportsToUse = 20;
constexpr size_t kNumRtcpReportsToUse = 20;
// Don't allow NTP timestamps to jump more than 1 hour. Chosen arbitrary as big
// enough to not affect normal use-cases. Yet it is smaller than RTP wrap-around
// half-period (90khz RTP clock wrap-arounds every 13.25 hours). After half of
// wrap-around period it is impossible to unwrap RTP timestamps correctly.
const int kMaxAllowedRtcpNtpIntervalMs = 60 * 60 * 1000;
constexpr uint64_t kMaxAllowedRtcpNtpInterval = uint64_t{60 * 60} << 32;
} // namespace
bool Contains(const std::list<RtpToNtpEstimator::RtcpMeasurement>& measurements,
const RtpToNtpEstimator::RtcpMeasurement& other) {
for (const auto& measurement : measurements) {
if (measurement.IsEqual(other))
return true;
}
return false;
}
// Given x[] and y[] writes out such k and b that line y=k*x+b approximates
// given points in the best way (Least Squares Method).
bool LinearRegression(rtc::ArrayView<const double> x,
rtc::ArrayView<const double> y,
double* k,
double* b) {
size_t n = x.size();
void RtpToNtpEstimator::UpdateParameters() {
size_t n = measurements_.size();
if (n < 2)
return false;
return;
if (y.size() != n)
return false;
// Run linear regression:
// Given x[] and y[] writes out such k and b that line y=k*x+b approximates
// given points in the best way (Least Squares Method).
auto x = [](const RtcpMeasurement& m) {
return static_cast<double>(m.unwrapped_rtp_timestamp);
};
auto y = [](const RtcpMeasurement& m) {
return static_cast<double>(static_cast<uint64_t>(m.ntp_time));
};
double avg_x = 0;
double avg_y = 0;
for (size_t i = 0; i < n; ++i) {
avg_x += x[i];
avg_y += y[i];
for (const RtcpMeasurement& m : measurements_) {
avg_x += x(m);
avg_y += y(m);
}
avg_x /= n;
avg_y /= n;
double variance_x = 0;
double covariance_xy = 0;
for (size_t i = 0; i < n; ++i) {
double normalized_x = x[i] - avg_x;
double normalized_y = y[i] - avg_y;
for (const RtcpMeasurement& m : measurements_) {
double normalized_x = x(m) - avg_x;
double normalized_y = y(m) - avg_y;
variance_x += normalized_x * normalized_x;
covariance_xy += normalized_x * normalized_y;
}
if (std::fabs(variance_x) < 1e-8)
return false;
*k = static_cast<double>(covariance_xy / variance_x);
*b = static_cast<double>(avg_y - (*k) * avg_x);
return true;
}
} // namespace
RtpToNtpEstimator::RtcpMeasurement::RtcpMeasurement(uint32_t ntp_secs,
uint32_t ntp_frac,
int64_t unwrapped_timestamp)
: ntp_time(ntp_secs, ntp_frac),
unwrapped_rtp_timestamp(unwrapped_timestamp) {}
bool RtpToNtpEstimator::RtcpMeasurement::IsEqual(
const RtcpMeasurement& other) const {
// Use || since two equal timestamps will result in zero frequency and in
// RtpToNtpMs, |rtp_timestamp_ms| is estimated by dividing by the frequency.
return (ntp_time == other.ntp_time) ||
(unwrapped_rtp_timestamp == other.unwrapped_rtp_timestamp);
}
// Class for converting an RTP timestamp to the NTP domain.
RtpToNtpEstimator::RtpToNtpEstimator() : consecutive_invalid_samples_(0) {}
RtpToNtpEstimator::~RtpToNtpEstimator() {}
void RtpToNtpEstimator::UpdateParameters() {
if (measurements_.size() < 2)
return;
std::vector<double> x;
std::vector<double> y;
x.reserve(measurements_.size());
y.reserve(measurements_.size());
for (auto it = measurements_.begin(); it != measurements_.end(); ++it) {
x.push_back(it->unwrapped_rtp_timestamp);
y.push_back(it->ntp_time.ToMs());
}
double slope, offset;
if (!LinearRegression(x, y, &slope, &offset)) {
return;
}
params_.emplace(1 / slope, offset);
double k = covariance_xy / variance_x;
double b = avg_y - k * avg_x;
params_ = {{.slope = k, .offset = b}};
}
bool RtpToNtpEstimator::UpdateMeasurements(uint32_t ntp_secs,
uint32_t ntp_frac,
uint32_t rtp_timestamp,
bool* new_rtcp_sr) {
*new_rtcp_sr = false;
RtpToNtpEstimator::UpdateResult RtpToNtpEstimator::UpdateMeasurements(
NtpTime ntp,
uint32_t rtp_timestamp) {
int64_t unwrapped_rtp_timestamp = unwrapper_.Unwrap(rtp_timestamp);
RtcpMeasurement new_measurement(ntp_secs, ntp_frac, unwrapped_rtp_timestamp);
RtcpMeasurement new_measurement = {
.ntp_time = ntp, .unwrapped_rtp_timestamp = unwrapped_rtp_timestamp};
if (Contains(measurements_, new_measurement)) {
// RTCP SR report already added.
return true;
for (const RtcpMeasurement& measurement : measurements_) {
// Use || since two equal timestamps will result in zero frequency.
if (measurement.ntp_time == ntp ||
measurement.unwrapped_rtp_timestamp == unwrapped_rtp_timestamp) {
return kSameMeasurement;
}
}
if (!new_measurement.ntp_time.Valid())
return false;
return kInvalidMeasurement;
int64_t ntp_ms_new = new_measurement.ntp_time.ToMs();
uint64_t ntp_new = static_cast<uint64_t>(new_measurement.ntp_time);
bool invalid_sample = false;
if (!measurements_.empty()) {
int64_t old_rtp_timestamp = measurements_.front().unwrapped_rtp_timestamp;
int64_t old_ntp_ms = measurements_.front().ntp_time.ToMs();
if (ntp_ms_new <= old_ntp_ms ||
ntp_ms_new > old_ntp_ms + kMaxAllowedRtcpNtpIntervalMs) {
uint64_t old_ntp = static_cast<uint64_t>(measurements_.front().ntp_time);
if (ntp_new <= old_ntp || ntp_new > old_ntp + kMaxAllowedRtcpNtpInterval) {
invalid_sample = true;
} else if (unwrapped_rtp_timestamp <= old_rtp_timestamp) {
RTC_LOG(LS_WARNING)
@ -158,7 +111,7 @@ bool RtpToNtpEstimator::UpdateMeasurements(uint32_t ntp_secs,
if (invalid_sample) {
++consecutive_invalid_samples_;
if (consecutive_invalid_samples_ < kMaxInvalidSamples) {
return false;
return kInvalidMeasurement;
}
RTC_LOG(LS_WARNING) << "Multiple consecutively invalid RTCP SR reports, "
"clearing measurements.";
@ -172,37 +125,29 @@ bool RtpToNtpEstimator::UpdateMeasurements(uint32_t ntp_secs,
measurements_.pop_back();
measurements_.push_front(new_measurement);
*new_rtcp_sr = true;
// List updated, calculate new parameters.
UpdateParameters();
return true;
return kNewMeasurement;
}
bool RtpToNtpEstimator::Estimate(int64_t rtp_timestamp,
int64_t* ntp_timestamp_ms) const {
NtpTime RtpToNtpEstimator::Estimate(uint32_t rtp_timestamp) const {
if (!params_)
return false;
return NtpTime();
int64_t rtp_timestamp_unwrapped = unwrapper_.Unwrap(rtp_timestamp);
double estimated =
static_cast<double>(unwrapper_.Unwrap(rtp_timestamp)) * params_->slope +
params_->offset + 0.5f;
// params_calculated_ should not be true unless ms params.frequency_khz has
// been calculated to something non zero.
RTC_DCHECK_NE(params_->frequency_khz, 0.0);
double rtp_ms =
static_cast<double>(rtp_timestamp_unwrapped) / params_->frequency_khz +
params_->offset_ms + 0.5f;
if (rtp_ms < 0)
return false;
*ntp_timestamp_ms = rtp_ms;
return true;
return NtpTime(rtc::saturated_cast<uint64_t>(estimated));
}
const absl::optional<RtpToNtpEstimator::Parameters> RtpToNtpEstimator::params()
const {
return params_;
double RtpToNtpEstimator::EstimatedFrequencyKhz() const {
if (!params_.has_value()) {
return 0.0;
}
static constexpr double kNtpUnitPerMs = 4.294967296E6; // 2^32 / 1000.
return kNtpUnitPerMs / params_->slope;
}
} // namespace webrtc

View File

@ -17,332 +17,249 @@
namespace webrtc {
namespace {
const uint32_t kOneMsInNtpFrac = 4294967;
const uint32_t kOneHourInNtpSec = 60 * 60;
const uint32_t kTimestampTicksPerMs = 90;
constexpr uint64_t kOneMsInNtp = 4294967;
constexpr uint64_t kOneHourInNtp = uint64_t{60 * 60} << 32;
constexpr uint32_t kTimestampTicksPerMs = 90;
} // namespace
TEST(WrapAroundTests, OldRtcpWrapped_OldRtpTimestamp) {
RtpToNtpEstimator estimator;
bool new_sr;
uint32_t ntp_sec = 0;
uint32_t ntp_frac = 1;
uint32_t timestamp = 0;
EXPECT_TRUE(
estimator.UpdateMeasurements(ntp_sec, ntp_frac, timestamp, &new_sr));
ntp_frac += kOneMsInNtpFrac;
timestamp -= kTimestampTicksPerMs;
EXPECT_EQ(estimator.UpdateMeasurements(NtpTime(kOneMsInNtp), 0),
RtpToNtpEstimator::kNewMeasurement);
// No wraparound will be detected, since we are not allowed to wrap below 0,
// but there will be huge rtp timestamp jump, e.g. old_timestamp = 0,
// new_timestamp = 4294967295, which should be detected.
EXPECT_FALSE(
estimator.UpdateMeasurements(ntp_sec, ntp_frac, timestamp, &new_sr));
EXPECT_EQ(estimator.UpdateMeasurements(NtpTime(2 * kOneMsInNtp),
-kTimestampTicksPerMs),
RtpToNtpEstimator::kInvalidMeasurement);
}
TEST(WrapAroundTests, OldRtcpWrapped_OldRtpTimestamp_Wraparound_Detected) {
RtpToNtpEstimator estimator;
bool new_sr;
uint32_t ntp_sec = 0;
uint32_t ntp_frac = 1;
uint32_t timestamp = 0xFFFFFFFE;
EXPECT_TRUE(
estimator.UpdateMeasurements(ntp_sec, ntp_frac, timestamp, &new_sr));
ntp_frac += 2 * kOneMsInNtpFrac;
timestamp += 2 * kTimestampTicksPerMs;
EXPECT_TRUE(
estimator.UpdateMeasurements(ntp_sec, ntp_frac, timestamp, &new_sr));
ntp_frac += kOneMsInNtpFrac;
timestamp -= kTimestampTicksPerMs;
EXPECT_EQ(estimator.UpdateMeasurements(NtpTime(1), 0xFFFFFFFE),
RtpToNtpEstimator::kNewMeasurement);
EXPECT_EQ(estimator.UpdateMeasurements(NtpTime(1 + 2 * kOneMsInNtp),
0xFFFFFFFE + 2 * kTimestampTicksPerMs),
RtpToNtpEstimator::kNewMeasurement);
// Expected to fail since the older RTCP has a smaller RTP timestamp than the
// newer (old:10, new:4294967206).
EXPECT_FALSE(
estimator.UpdateMeasurements(ntp_sec, ntp_frac, timestamp, &new_sr));
EXPECT_EQ(estimator.UpdateMeasurements(NtpTime(1 + 3 * kOneMsInNtp),
0xFFFFFFFE + kTimestampTicksPerMs),
RtpToNtpEstimator::kInvalidMeasurement);
}
TEST(WrapAroundTests, NewRtcpWrapped) {
RtpToNtpEstimator estimator;
bool new_sr;
uint32_t ntp_sec = 0;
uint32_t ntp_frac = 1;
uint32_t timestamp = 0xFFFFFFFF;
EXPECT_TRUE(
estimator.UpdateMeasurements(ntp_sec, ntp_frac, timestamp, &new_sr));
ntp_frac += kOneMsInNtpFrac;
timestamp += kTimestampTicksPerMs;
EXPECT_TRUE(
estimator.UpdateMeasurements(ntp_sec, ntp_frac, timestamp, &new_sr));
int64_t timestamp_ms = -1;
EXPECT_TRUE(estimator.Estimate(0xFFFFFFFF, &timestamp_ms));
EXPECT_EQ(estimator.UpdateMeasurements(NtpTime(1), 0xFFFFFFFF),
RtpToNtpEstimator::kNewMeasurement);
EXPECT_EQ(estimator.UpdateMeasurements(NtpTime(1 + kOneMsInNtp),
0xFFFFFFFF + kTimestampTicksPerMs),
RtpToNtpEstimator::kNewMeasurement);
// Since this RTP packet has the same timestamp as the RTCP packet constructed
// at time 0 it should be mapped to 0 as well.
EXPECT_EQ(0, timestamp_ms);
EXPECT_EQ(estimator.Estimate(0xFFFFFFFF), NtpTime(1));
}
TEST(WrapAroundTests, RtpWrapped) {
RtpToNtpEstimator estimator;
bool new_sr;
uint32_t ntp_sec = 0;
uint32_t ntp_frac = 1;
uint32_t timestamp = 0xFFFFFFFF - 2 * kTimestampTicksPerMs;
EXPECT_TRUE(
estimator.UpdateMeasurements(ntp_sec, ntp_frac, timestamp, &new_sr));
ntp_frac += kOneMsInNtpFrac;
timestamp += kTimestampTicksPerMs;
EXPECT_TRUE(
estimator.UpdateMeasurements(ntp_sec, ntp_frac, timestamp, &new_sr));
EXPECT_EQ(estimator.UpdateMeasurements(NtpTime(1),
0xFFFFFFFF - 2 * kTimestampTicksPerMs),
RtpToNtpEstimator::kNewMeasurement);
EXPECT_EQ(estimator.UpdateMeasurements(NtpTime(1 + kOneMsInNtp),
0xFFFFFFFF - kTimestampTicksPerMs),
RtpToNtpEstimator::kNewMeasurement);
int64_t timestamp_ms = -1;
EXPECT_TRUE(
estimator.Estimate(0xFFFFFFFF - 2 * kTimestampTicksPerMs, &timestamp_ms));
// Since this RTP packet has the same timestamp as the RTCP packet constructed
// at time 0 it should be mapped to 0 as well.
EXPECT_EQ(0, timestamp_ms);
EXPECT_EQ(estimator.Estimate(0xFFFFFFFF - 2 * kTimestampTicksPerMs),
NtpTime(1));
// Two kTimestampTicksPerMs advanced.
timestamp += kTimestampTicksPerMs;
EXPECT_TRUE(estimator.Estimate(timestamp, &timestamp_ms));
EXPECT_EQ(2, timestamp_ms);
EXPECT_EQ(estimator.Estimate(0xFFFFFFFF), NtpTime(1 + 2 * kOneMsInNtp));
// Wrapped rtp.
timestamp += kTimestampTicksPerMs;
EXPECT_TRUE(estimator.Estimate(timestamp, &timestamp_ms));
EXPECT_EQ(3, timestamp_ms);
EXPECT_EQ(estimator.Estimate(0xFFFFFFFF + kTimestampTicksPerMs),
NtpTime(1 + 3 * kOneMsInNtp));
}
TEST(WrapAroundTests, OldRtp_RtcpsWrapped) {
RtpToNtpEstimator estimator;
bool new_sr;
uint32_t ntp_sec = 0;
uint32_t ntp_frac = 1;
uint32_t timestamp = 0xFFFFFFFF;
EXPECT_TRUE(
estimator.UpdateMeasurements(ntp_sec, ntp_frac, timestamp, &new_sr));
ntp_frac += kOneMsInNtpFrac;
timestamp += kTimestampTicksPerMs;
EXPECT_TRUE(
estimator.UpdateMeasurements(ntp_sec, ntp_frac, timestamp, &new_sr));
timestamp -= 2 * kTimestampTicksPerMs;
int64_t timestamp_ms = 0xFFFFFFFF;
EXPECT_FALSE(estimator.Estimate(timestamp, &timestamp_ms));
EXPECT_EQ(estimator.UpdateMeasurements(NtpTime(1), 0xFFFFFFFF),
RtpToNtpEstimator::kNewMeasurement);
EXPECT_EQ(estimator.UpdateMeasurements(NtpTime(1 + kOneMsInNtp),
0xFFFFFFFF + kTimestampTicksPerMs),
RtpToNtpEstimator::kNewMeasurement);
EXPECT_FALSE(estimator.Estimate(0xFFFFFFFF - kTimestampTicksPerMs).Valid());
}
TEST(WrapAroundTests, OldRtp_NewRtcpWrapped) {
RtpToNtpEstimator estimator;
bool new_sr;
uint32_t ntp_sec = 0;
uint32_t ntp_frac = 1;
uint32_t timestamp = 0xFFFFFFFF;
EXPECT_TRUE(
estimator.UpdateMeasurements(ntp_sec, ntp_frac, timestamp, &new_sr));
ntp_frac += kOneMsInNtpFrac;
timestamp += kTimestampTicksPerMs;
EXPECT_TRUE(
estimator.UpdateMeasurements(ntp_sec, ntp_frac, timestamp, &new_sr));
timestamp -= kTimestampTicksPerMs;
int64_t timestamp_ms = -1;
EXPECT_TRUE(estimator.Estimate(timestamp, &timestamp_ms));
EXPECT_EQ(estimator.UpdateMeasurements(NtpTime(1), 0xFFFFFFFF),
RtpToNtpEstimator::kNewMeasurement);
EXPECT_EQ(estimator.UpdateMeasurements(NtpTime(1 + kOneMsInNtp),
0xFFFFFFFF + kTimestampTicksPerMs),
RtpToNtpEstimator::kNewMeasurement);
// Constructed at the same time as the first RTCP and should therefore be
// mapped to zero.
EXPECT_EQ(0, timestamp_ms);
EXPECT_EQ(estimator.Estimate(0xFFFFFFFF), NtpTime(1));
}
TEST(WrapAroundTests, GracefullyHandleRtpJump) {
RtpToNtpEstimator estimator;
bool new_sr;
uint32_t ntp_sec = 0;
uint32_t ntp_frac = 1;
uint32_t timestamp = 0;
EXPECT_TRUE(
estimator.UpdateMeasurements(ntp_sec, ntp_frac, timestamp, &new_sr));
ntp_frac += kOneMsInNtpFrac;
timestamp += kTimestampTicksPerMs;
EXPECT_TRUE(
estimator.UpdateMeasurements(ntp_sec, ntp_frac, timestamp, &new_sr));
ntp_frac += kOneMsInNtpFrac;
timestamp -= kTimestampTicksPerMs;
int64_t timestamp_ms = -1;
EXPECT_TRUE(estimator.Estimate(timestamp, &timestamp_ms));
EXPECT_EQ(estimator.UpdateMeasurements(NtpTime(1), 0xFFFFFFFF),
RtpToNtpEstimator::kNewMeasurement);
EXPECT_EQ(estimator.UpdateMeasurements(NtpTime(1 + kOneMsInNtp),
0xFFFFFFFF + kTimestampTicksPerMs),
RtpToNtpEstimator::kNewMeasurement);
// Constructed at the same time as the first RTCP and should therefore be
// mapped to zero.
EXPECT_EQ(0, timestamp_ms);
EXPECT_EQ(estimator.Estimate(0xFFFFFFFF), NtpTime(1));
timestamp -= 0xFFFFF;
uint32_t timestamp = 0xFFFFFFFF - 0xFFFFF;
uint64_t ntp_raw = 1 + 2 * kOneMsInNtp;
for (int i = 0; i < RtpToNtpEstimator::kMaxInvalidSamples - 1; ++i) {
EXPECT_FALSE(
estimator.UpdateMeasurements(ntp_sec, ntp_frac, timestamp, &new_sr));
ntp_frac += kOneMsInNtpFrac;
EXPECT_EQ(estimator.UpdateMeasurements(NtpTime(ntp_raw), timestamp),
RtpToNtpEstimator::kInvalidMeasurement);
ntp_raw += kOneMsInNtp;
timestamp += kTimestampTicksPerMs;
}
EXPECT_TRUE(
estimator.UpdateMeasurements(ntp_sec, ntp_frac, timestamp, &new_sr));
ntp_frac += kOneMsInNtpFrac;
timestamp += kTimestampTicksPerMs;
EXPECT_TRUE(
estimator.UpdateMeasurements(ntp_sec, ntp_frac, timestamp, &new_sr));
ntp_frac += kOneMsInNtpFrac;
EXPECT_EQ(estimator.UpdateMeasurements(NtpTime(ntp_raw), timestamp),
RtpToNtpEstimator::kNewMeasurement);
ntp_raw += kOneMsInNtp;
timestamp += kTimestampTicksPerMs;
EXPECT_EQ(estimator.UpdateMeasurements(NtpTime(ntp_raw), timestamp),
RtpToNtpEstimator::kNewMeasurement);
timestamp_ms = -1;
EXPECT_TRUE(estimator.Estimate(timestamp, &timestamp_ms));
// 6 milliseconds has passed since the start of the test.
EXPECT_EQ(6, timestamp_ms);
EXPECT_EQ(estimator.Estimate(timestamp), NtpTime(ntp_raw));
}
TEST(UpdateRtcpMeasurementTests, FailsForZeroNtp) {
RtpToNtpEstimator estimator;
uint32_t ntp_sec = 0;
uint32_t ntp_frac = 0;
uint32_t timestamp = 0x12345678;
bool new_sr;
EXPECT_FALSE(
estimator.UpdateMeasurements(ntp_sec, ntp_frac, timestamp, &new_sr));
EXPECT_FALSE(new_sr);
EXPECT_EQ(estimator.UpdateMeasurements(NtpTime(0), 0x12345678),
RtpToNtpEstimator::kInvalidMeasurement);
}
TEST(UpdateRtcpMeasurementTests, FailsForEqualNtp) {
RtpToNtpEstimator estimator;
uint32_t ntp_sec = 0;
uint32_t ntp_frac = 699925050;
NtpTime ntp(0, 699925050);
uint32_t timestamp = 0x12345678;
bool new_sr;
EXPECT_TRUE(
estimator.UpdateMeasurements(ntp_sec, ntp_frac, timestamp, &new_sr));
EXPECT_TRUE(new_sr);
EXPECT_EQ(estimator.UpdateMeasurements(ntp, timestamp),
RtpToNtpEstimator::kNewMeasurement);
// Ntp time already added, list not updated.
++timestamp;
EXPECT_TRUE(
estimator.UpdateMeasurements(ntp_sec, ntp_frac, timestamp, &new_sr));
EXPECT_FALSE(new_sr);
EXPECT_EQ(estimator.UpdateMeasurements(ntp, timestamp + 1),
RtpToNtpEstimator::kSameMeasurement);
}
TEST(UpdateRtcpMeasurementTests, FailsForOldNtp) {
RtpToNtpEstimator estimator;
uint32_t ntp_sec = 1;
uint32_t ntp_frac = 699925050;
uint64_t ntp_raw = 699925050;
NtpTime ntp(ntp_raw);
uint32_t timestamp = 0x12345678;
bool new_sr;
EXPECT_TRUE(
estimator.UpdateMeasurements(ntp_sec, ntp_frac, timestamp, &new_sr));
EXPECT_TRUE(new_sr);
EXPECT_EQ(estimator.UpdateMeasurements(ntp, timestamp),
RtpToNtpEstimator::kNewMeasurement);
// Old ntp time, list not updated.
ntp_frac -= kOneMsInNtpFrac;
timestamp += kTimestampTicksPerMs;
EXPECT_FALSE(
estimator.UpdateMeasurements(ntp_sec, ntp_frac, timestamp, &new_sr));
EXPECT_EQ(estimator.UpdateMeasurements(NtpTime(ntp_raw - kOneMsInNtp),
timestamp + kTimestampTicksPerMs),
RtpToNtpEstimator::kInvalidMeasurement);
}
TEST(UpdateRtcpMeasurementTests, FailsForTooNewNtp) {
RtpToNtpEstimator estimator;
uint32_t ntp_sec = 1;
uint32_t ntp_frac = 699925050;
uint64_t ntp_raw = 699925050;
uint32_t timestamp = 0x12345678;
bool new_sr;
EXPECT_TRUE(
estimator.UpdateMeasurements(ntp_sec, ntp_frac, timestamp, &new_sr));
EXPECT_TRUE(new_sr);
EXPECT_EQ(estimator.UpdateMeasurements(NtpTime(ntp_raw), timestamp),
RtpToNtpEstimator::kNewMeasurement);
// Ntp time from far future, list not updated.
ntp_sec += kOneHourInNtpSec * 2;
timestamp += kTimestampTicksPerMs * 10;
EXPECT_FALSE(
estimator.UpdateMeasurements(ntp_sec, ntp_frac, timestamp, &new_sr));
EXPECT_EQ(estimator.UpdateMeasurements(NtpTime(ntp_raw + 2 * kOneHourInNtp),
timestamp + 10 * kTimestampTicksPerMs),
RtpToNtpEstimator::kInvalidMeasurement);
}
TEST(UpdateRtcpMeasurementTests, FailsForEqualTimestamp) {
RtpToNtpEstimator estimator;
uint32_t ntp_sec = 0;
uint32_t ntp_frac = 2;
uint32_t timestamp = 0x12345678;
bool new_sr;
EXPECT_TRUE(
estimator.UpdateMeasurements(ntp_sec, ntp_frac, timestamp, &new_sr));
EXPECT_TRUE(new_sr);
EXPECT_EQ(estimator.UpdateMeasurements(NtpTime(2), timestamp),
RtpToNtpEstimator::kNewMeasurement);
// Timestamp already added, list not updated.
++ntp_frac;
EXPECT_TRUE(
estimator.UpdateMeasurements(ntp_sec, ntp_frac, timestamp, &new_sr));
EXPECT_FALSE(new_sr);
EXPECT_EQ(estimator.UpdateMeasurements(NtpTime(3), timestamp),
RtpToNtpEstimator::kSameMeasurement);
}
TEST(UpdateRtcpMeasurementTests, FailsForOldRtpTimestamp) {
RtpToNtpEstimator estimator;
uint32_t ntp_sec = 0;
uint32_t ntp_frac = 2;
uint32_t timestamp = 0x12345678;
bool new_sr;
EXPECT_TRUE(
estimator.UpdateMeasurements(ntp_sec, ntp_frac, timestamp, &new_sr));
EXPECT_TRUE(new_sr);
EXPECT_EQ(estimator.UpdateMeasurements(NtpTime(2), timestamp),
RtpToNtpEstimator::kNewMeasurement);
// Old timestamp, list not updated.
ntp_frac += kOneMsInNtpFrac;
timestamp -= kTimestampTicksPerMs;
EXPECT_FALSE(
estimator.UpdateMeasurements(ntp_sec, ntp_frac, timestamp, &new_sr));
EXPECT_FALSE(new_sr);
EXPECT_EQ(estimator.UpdateMeasurements(NtpTime(2 + kOneMsInNtp),
timestamp - kTimestampTicksPerMs),
RtpToNtpEstimator::kInvalidMeasurement);
}
TEST(UpdateRtcpMeasurementTests, VerifyParameters) {
RtpToNtpEstimator estimator;
uint32_t ntp_sec = 1;
uint32_t ntp_frac = 2;
uint32_t timestamp = 0x12345678;
bool new_sr;
EXPECT_TRUE(
estimator.UpdateMeasurements(ntp_sec, ntp_frac, timestamp, &new_sr));
EXPECT_TRUE(new_sr);
EXPECT_FALSE(estimator.params());
EXPECT_EQ(estimator.UpdateMeasurements(NtpTime(kOneMsInNtp), timestamp),
RtpToNtpEstimator::kNewMeasurement);
EXPECT_DOUBLE_EQ(estimator.EstimatedFrequencyKhz(), 0.0);
// Add second report, parameters should be calculated.
ntp_frac += kOneMsInNtpFrac;
timestamp += kTimestampTicksPerMs;
EXPECT_TRUE(
estimator.UpdateMeasurements(ntp_sec, ntp_frac, timestamp, &new_sr));
EXPECT_TRUE(estimator.params());
EXPECT_DOUBLE_EQ(90.0, estimator.params()->frequency_khz);
EXPECT_NE(0.0, estimator.params()->offset_ms);
EXPECT_EQ(estimator.UpdateMeasurements(NtpTime(2 * kOneMsInNtp),
timestamp + kTimestampTicksPerMs),
RtpToNtpEstimator::kNewMeasurement);
EXPECT_NEAR(estimator.EstimatedFrequencyKhz(), kTimestampTicksPerMs, 0.01);
}
TEST(RtpToNtpTests, FailsForNoParameters) {
RtpToNtpEstimator estimator;
uint32_t ntp_sec = 1;
uint32_t ntp_frac = 2;
uint32_t timestamp = 0x12345678;
bool new_sr;
EXPECT_TRUE(
estimator.UpdateMeasurements(ntp_sec, ntp_frac, timestamp, &new_sr));
EXPECT_TRUE(new_sr);
EXPECT_EQ(estimator.UpdateMeasurements(NtpTime(1), timestamp),
RtpToNtpEstimator::kNewMeasurement);
// Parameters are not calculated, conversion of RTP to NTP time should fail.
EXPECT_FALSE(estimator.params());
int64_t timestamp_ms = -1;
EXPECT_FALSE(estimator.Estimate(timestamp, &timestamp_ms));
EXPECT_DOUBLE_EQ(estimator.EstimatedFrequencyKhz(), 0.0);
EXPECT_FALSE(estimator.Estimate(timestamp).Valid());
}
TEST(RtpToNtpTests, AveragesErrorOut) {
RtpToNtpEstimator estimator;
uint32_t ntp_sec = 1;
uint32_t ntp_frac = 90000000; // More than 1 ms.
uint64_t ntp_raw = 90000000; // More than 1 ms.
ASSERT_GT(ntp_raw, kOneMsInNtp);
uint32_t timestamp = 0x12345678;
const int kNtpSecStep = 1; // 1 second.
const int kRtpTicksPerMs = 90;
const int kRtpStep = kRtpTicksPerMs * 1000;
bool new_sr;
EXPECT_TRUE(
estimator.UpdateMeasurements(ntp_sec, ntp_frac, timestamp, &new_sr));
EXPECT_TRUE(new_sr);
constexpr uint64_t kNtpSecStep = uint64_t{1} << 32; // 1 second.
constexpr int kRtpTicksPerMs = 90;
constexpr int kRtpStep = kRtpTicksPerMs * 1000;
EXPECT_EQ(estimator.UpdateMeasurements(NtpTime(ntp_raw), timestamp),
RtpToNtpEstimator::kNewMeasurement);
Random rand(1123536L);
for (size_t i = 0; i < 1000; i++) {
// Advance both timestamps by exactly 1 second.
ntp_sec += kNtpSecStep;
ntp_raw += kNtpSecStep;
timestamp += kRtpStep;
// Add upto 1ms of errors to NTP and RTP timestamps passed to estimator.
EXPECT_TRUE(estimator.UpdateMeasurements(
ntp_sec,
ntp_frac + rand.Rand(-static_cast<int>(kOneMsInNtpFrac),
static_cast<int>(kOneMsInNtpFrac)),
timestamp + rand.Rand(-kRtpTicksPerMs, kRtpTicksPerMs), &new_sr));
EXPECT_TRUE(new_sr);
EXPECT_EQ(
estimator.UpdateMeasurements(
NtpTime(ntp_raw + rand.Rand(-int{kOneMsInNtp}, int{kOneMsInNtp})),
timestamp + rand.Rand(-kRtpTicksPerMs, kRtpTicksPerMs)),
RtpToNtpEstimator::kNewMeasurement);
int64_t estimated_ntp_ms;
EXPECT_TRUE(estimator.Estimate(timestamp, &estimated_ntp_ms));
NtpTime estimated_ntp = estimator.Estimate(timestamp);
EXPECT_TRUE(estimated_ntp.Valid());
// Allow upto 2 ms of error.
EXPECT_NEAR(NtpTime(ntp_sec, ntp_frac).ToMs(), estimated_ntp_ms, 2);
EXPECT_NEAR(ntp_raw, static_cast<uint64_t>(estimated_ntp), 2 * kOneMsInNtp);
}
}