diff options
Diffstat (limited to 'third_party/libwebrtc/system_wrappers/source')
18 files changed, 2458 insertions, 0 deletions
diff --git a/third_party/libwebrtc/system_wrappers/source/DEPS b/third_party/libwebrtc/system_wrappers/source/DEPS new file mode 100644 index 0000000000..ac7f5a234f --- /dev/null +++ b/third_party/libwebrtc/system_wrappers/source/DEPS @@ -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", + ], +} diff --git a/third_party/libwebrtc/system_wrappers/source/clock.cc b/third_party/libwebrtc/system_wrappers/source/clock.cc new file mode 100644 index 0000000000..f7460b831c --- /dev/null +++ b/third_party/libwebrtc/system_wrappers/source/clock.cc @@ -0,0 +1,102 @@ +/* + * 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 "system_wrappers/include/clock.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()); + } + + NtpTime ConvertTimestampToNtpTime(Timestamp timestamp) override { + return TimeMicrosToNtp(timestamp.us()); + } +}; + +Clock* Clock::GetRealTimeClockRaw() { + static Clock* const clock = new RealTimeClock(); + return clock; +} + +SimulatedClock::SimulatedClock(int64_t initial_time_us) + : time_us_(initial_time_us) {} + +SimulatedClock::SimulatedClock(Timestamp initial_time) + : SimulatedClock(initial_time.us()) {} + +SimulatedClock::~SimulatedClock() {} + +Timestamp SimulatedClock::CurrentTime() { + return Timestamp::Micros(time_us_.load(std::memory_order_relaxed)); +} + +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); +} + +void SimulatedClock::AdvanceTimeMilliseconds(int64_t milliseconds) { + AdvanceTime(TimeDelta::Millis(milliseconds)); +} + +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) { + time_us_.fetch_add(delta.us(), std::memory_order_relaxed); +} + +} // namespace webrtc diff --git a/third_party/libwebrtc/system_wrappers/source/clock_unittest.cc b/third_party/libwebrtc/system_wrappers/source/clock_unittest.cc new file mode 100644 index 0000000000..f7b0ed7a47 --- /dev/null +++ b/third_party/libwebrtc/system_wrappers/source/clock_unittest.cc @@ -0,0 +1,34 @@ +/* + * 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 "system_wrappers/include/clock.h" + +#include "test/gtest.h" + +namespace webrtc { + +TEST(ClockTest, NtpTime) { + Clock* clock = Clock::GetRealTimeClock(); + + // To ensure the test runs correctly even on a heavily loaded system, do not + // compare the seconds/fractions and millisecond values directly. Instead, + // we check that the NTP time is between the "milliseconds" values returned + // right before and right after the call. + // The comparison includes 1 ms of margin to account for the rounding error in + // the conversion. + int64_t milliseconds_lower_bound = clock->CurrentNtpInMilliseconds(); + NtpTime ntp_time = clock->CurrentNtpTime(); + int64_t milliseconds_upper_bound = clock->CurrentNtpInMilliseconds(); + EXPECT_GT(milliseconds_lower_bound / 1000, kNtpJan1970); + EXPECT_LE(milliseconds_lower_bound - 1, ntp_time.ToMs()); + EXPECT_GE(milliseconds_upper_bound + 1, ntp_time.ToMs()); +} + +} // namespace webrtc diff --git a/third_party/libwebrtc/system_wrappers/source/cpu_features.cc b/third_party/libwebrtc/system_wrappers/source/cpu_features.cc new file mode 100644 index 0000000000..b676339eea --- /dev/null +++ b/third_party/libwebrtc/system_wrappers/source/cpu_features.cc @@ -0,0 +1,116 @@ +/* + * Copyright (c) 2011 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. + */ + +// Parts of this file derived from Chromium's base/cpu.cc. + +#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; + return 0; +} + +#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__) +static inline void __cpuid(int cpu_info[4], int info_type) { + __asm__ volatile( + "mov %%ebx, %%edi\n" + "cpuid\n" + "xchg %%edi, %%ebx\n" + : "=a"(cpu_info[0]), "=D"(cpu_info[1]), "=c"(cpu_info[2]), + "=d"(cpu_info[3]) + : "a"(info_type)); +} +#else +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), "c"(0)); +} +#endif +#endif // _MSC_VER +#endif // WEBRTC_ARCH_X86_FAMILY + +#if defined(WEBRTC_ARCH_X86_FAMILY) +// Actual feature detection for x86. +int GetCPUInfo(CPUFeature feature) { + int cpu_info[4]; + __cpuid(cpu_info, 1); + if (feature == kSSE2) { + return 0 != (cpu_info[3] & 0x04000000); + } + 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. +int GetCPUInfo(CPUFeature feature) { + (void)feature; + return 0; +} +#endif + +} // namespace webrtc diff --git a/third_party/libwebrtc/system_wrappers/source/cpu_features_android.cc b/third_party/libwebrtc/system_wrappers/source/cpu_features_android.cc new file mode 100644 index 0000000000..95cc609b09 --- /dev/null +++ b/third_party/libwebrtc/system_wrappers/source/cpu_features_android.cc @@ -0,0 +1,19 @@ +/* + * 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 <cpu-features.h> + +namespace webrtc { + +uint64_t GetCPUFeaturesARM(void) { + return android_getCpuFeatures(); +} + +} // namespace webrtc diff --git a/third_party/libwebrtc/system_wrappers/source/cpu_features_linux.cc b/third_party/libwebrtc/system_wrappers/source/cpu_features_linux.cc new file mode 100644 index 0000000000..2d79dde111 --- /dev/null +++ b/third_party/libwebrtc/system_wrappers/source/cpu_features_linux.cc @@ -0,0 +1,97 @@ +/* + * Copyright (c) 2016 The WebRTC project authors. All Rights Reserved. + * + * Use of this source code is governed by a BSD-style license + * that can be found in the LICENSE file in the root of the source + * tree. An additional intellectual property rights grant can be found + * in the file PATENTS. All contributing project authors may + * be found in the AUTHORS file in the root of the source tree. + */ + +#include <features.h> +#include <stdlib.h> +#include <string.h> + +#ifdef __GLIBC_PREREQ +#define WEBRTC_GLIBC_PREREQ(a, b) __GLIBC_PREREQ(a, b) +#else +#define WEBRTC_GLIBC_PREREQ(a, b) 0 +#endif + +#if WEBRTC_GLIBC_PREREQ(2, 16) +#include <sys/auxv.h> +#else +#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> + +namespace webrtc { + +uint64_t GetCPUFeaturesARM(void) { + uint64_t result = 0; + int architecture = 0; + uint64_t hwcap = 0; + const char* platform = NULL; +#if WEBRTC_GLIBC_PREREQ(2, 16) + hwcap = getauxval(AT_HWCAP); + platform = (const char*)getauxval(AT_PLATFORM); +#else + ElfW(auxv_t) auxv; + int fd = open("/proc/self/auxv", O_RDONLY); + if (fd >= 0) { + while (hwcap == 0 || platform == NULL) { + if (read(fd, &auxv, sizeof(auxv)) < (ssize_t)sizeof(auxv)) { + if (errno == EINTR) + continue; + break; + } + switch (auxv.a_type) { + case AT_HWCAP: + hwcap = auxv.a_un.a_val; + break; + case AT_PLATFORM: + platform = (const char*)auxv.a_un.a_val; + break; + } + } + close(fd); + } +#endif // WEBRTC_GLIBC_PREREQ(2, 16) +#if defined(__aarch64__) + (void)platform; + architecture = 8; + if ((hwcap & HWCAP_FP) != 0) + result |= kCPUFeatureVFPv3; + if ((hwcap & HWCAP_ASIMD) != 0) + result |= kCPUFeatureNEON; +#else + if (platform != NULL) { + /* expect a string in the form "v6l" or "v7l", etc. + */ + if (platform[0] == 'v' && '0' <= platform[1] && platform[1] <= '9' && + (platform[2] == 'l' || platform[2] == 'b')) { + architecture = platform[1] - '0'; + } + } + if ((hwcap & HWCAP_VFPv3) != 0) + result |= kCPUFeatureVFPv3; + if ((hwcap & HWCAP_NEON) != 0) + result |= kCPUFeatureNEON; +#endif + if (architecture >= 7) + result |= kCPUFeatureARMv7; + if (architecture >= 6) + result |= kCPUFeatureLDREXSTREX; + return result; +} + +} // namespace webrtc +#endif // WEBRTC_ARCH_ARM_FAMILY diff --git a/third_party/libwebrtc/system_wrappers/source/cpu_info.cc b/third_party/libwebrtc/system_wrappers/source/cpu_info.cc new file mode 100644 index 0000000000..94aed09c48 --- /dev/null +++ b/third_party/libwebrtc/system_wrappers/source/cpu_info.cc @@ -0,0 +1,72 @@ +/* + * Copyright (c) 2011 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/cpu_info.h" + +#if defined(WEBRTC_WIN) +#include <windows.h> +#elif defined(WEBRTC_LINUX) || defined(WEBRTC_BSD) +#include <unistd.h> +#elif defined(WEBRTC_MAC) +#include <sys/sysctl.h> +#elif defined(WEBRTC_FUCHSIA) +#include <zircon/syscalls.h> +#endif + +#include "rtc_base/logging.h" + +namespace internal { +static int DetectNumberOfCores() { + int number_of_cores; + +#if defined(WEBRTC_WIN) + SYSTEM_INFO si; + GetNativeSystemInfo(&si); + number_of_cores = static_cast<int>(si.dwNumberOfProcessors); +#elif defined(WEBRTC_LINUX) || defined(WEBRTC_ANDROID) || defined(WEBRTC_BSD) + number_of_cores = static_cast<int>(sysconf(_SC_NPROCESSORS_ONLN)); + if (number_of_cores <= 0) { + RTC_LOG(LS_ERROR) << "Failed to get number of cores"; + number_of_cores = 1; + } +#elif defined(WEBRTC_MAC) || defined(WEBRTC_IOS) + int name[] = {CTL_HW, HW_AVAILCPU}; + size_t size = sizeof(number_of_cores); + if (0 != sysctl(name, 2, &number_of_cores, &size, NULL, 0)) { + RTC_LOG(LS_ERROR) << "Failed to get number of cores"; + number_of_cores = 1; + } +#elif defined(WEBRTC_FUCHSIA) + number_of_cores = zx_system_get_num_cpus(); +#else + RTC_LOG(LS_ERROR) << "No function to get number of cores"; + number_of_cores = 1; +#endif + + RTC_LOG(LS_INFO) << "Available number of cores: " << number_of_cores; + + RTC_CHECK_GT(number_of_cores, 0); + return number_of_cores; +} +} // namespace internal + +namespace webrtc { + +uint32_t CpuInfo::DetectNumberOfCores() { + // Statically cache the number of system cores available since if the process + // is running in a sandbox, we may only be able to read the value once (before + // the sandbox is initialized) and not thereafter. + // For more information see crbug.com/176522. + static const uint32_t logical_cpus = + static_cast<uint32_t>(internal::DetectNumberOfCores()); + return logical_cpus; +} + +} // namespace webrtc diff --git a/third_party/libwebrtc/system_wrappers/source/denormal_disabler.cc b/third_party/libwebrtc/system_wrappers/source/denormal_disabler.cc new file mode 100644 index 0000000000..bb9c05643c --- /dev/null +++ b/third_party/libwebrtc/system_wrappers/source/denormal_disabler.cc @@ -0,0 +1,111 @@ +/* + * 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() : DenormalDisabler(/*enabled=*/true) {} + +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() : DenormalDisabler(/*enabled=*/false) {} + +DenormalDisabler::DenormalDisabler(bool enabled) + : status_word_(kUnspecifiedStatusWord), disabling_activated_(false) {} + +bool DenormalDisabler::IsSupported() { + return false; +} + +DenormalDisabler::~DenormalDisabler() = default; +#endif + +} // namespace webrtc diff --git a/third_party/libwebrtc/system_wrappers/source/denormal_disabler_unittest.cc b/third_party/libwebrtc/system_wrappers/source/denormal_disabler_unittest.cc new file mode 100644 index 0000000000..a2849f853f --- /dev/null +++ b/third_party/libwebrtc/system_wrappers/source/denormal_disabler_unittest.cc @@ -0,0 +1,226 @@ +/* + * 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, InfNotZeroedExplicitlySetEnabled) { + 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, NanNotZeroedExplicitlySetEnabled) { + 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 +inf and -inf are not zeroed regardless of whether +// architecture and compiler are supported. +TEST(DenormalDisabler, InfNotZeroed) { + DenormalDisabler denormal_disabler; + 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(DenormalDisabler, NanNotZeroed) { + DenormalDisabler denormal_disabler; + volatile float kNan = std::sqrt(-1.0f); + EXPECT_TRUE(std::isnan(kNan)); +} + +// 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; + for (float x : kDenormalDivisors) { + SCOPED_TRACE(x); + EXPECT_FALSE(DivisionIsDenormal(-kSmallest, x)); + EXPECT_FALSE(DivisionIsDenormal(kSmallest, x)); + } +} + +// Checks that denormals are zeroed if `DenormalDisabler` is enabled if +// architecture and compiler are supported. +TEST(DenormalDisabler, ZeroDenormalsExplicitlyEnabled) { + 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; + ASSERT_FALSE(DivisionIsDenormal(kSmallest, kDenormalDivisors[0])); + } + EXPECT_TRUE(DivisionIsDenormal(kSmallest, kDenormalDivisors[0])); +} + +// Checks that the `DenormalDisabler` dtor re-enables denormals if previously +// enabled and architecture and compiler are supported. +TEST(DenormalDisabler, RestoreDenormalsEnabledExplicitlyEnabled) { + 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; + ASSERT_FALSE(DivisionIsDenormal(kSmallest, kDenormalDivisors[0])); + { + DenormalDisabler d2; + ASSERT_FALSE(DivisionIsDenormal(kSmallest, kDenormalDivisors[0])); + } + EXPECT_FALSE(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, ZeroDenormalsNestedExplicitlyEnabled) { + 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; + for (float x : kDenormalDivisors) { + SCOPED_TRACE(x); + EXPECT_TRUE(DivisionIsDenormal(-kSmallest, x)); + EXPECT_TRUE(DivisionIsDenormal(kSmallest, x)); + } +} + +// Checks that `DenormalDisabler` does not zero denormals if architecture and +// compiler are not supported. +TEST(DenormalDisabler, DoNotZeroDenormalsIfUnsupportedExplicitlyEnabled) { + 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 diff --git a/third_party/libwebrtc/system_wrappers/source/field_trial.cc b/third_party/libwebrtc/system_wrappers/source/field_trial.cc new file mode 100644 index 0000000000..8f15b4eb7a --- /dev/null +++ b/third_party/libwebrtc/system_wrappers/source/field_trial.cc @@ -0,0 +1,181 @@ +// Copyright (c) 2014 The WebRTC project authors. All Rights Reserved. +// +// Use of this source code is governed by a BSD-style license +// that can be found in the LICENSE file in the root of the source +// tree. An additional intellectual property rights grant can be found +// in the file PATENTS. All contributing project authors may +// be found in the AUTHORS file in the root of the source tree. +// + +#include "system_wrappers/include/field_trial.h" + +#include <stddef.h> + +#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" + +// Simple field trial implementation, which allows client to +// specify desired flags in InitFieldTrialsFromString. +namespace webrtc { +namespace field_trial { + +static const char* trials_init_string = NULL; + +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/" +// Assigns the process to group "Enabled" on WebRTCExperimentFoo trial +// and to group "Enabled100kbps" on WebRTCExperimentBar. +// +// E.g. invalid config: +// "WebRTC-experiment1/Enabled" (note missing / separator at the end). +bool FieldTrialsStringIsValidInternal(const absl::string_view trials) { + if (trials.empty()) + return true; + + size_t next_item = 0; + std::map<absl::string_view, absl::string_view> field_trials; + while (next_item < trials.length()) { + size_t name_end = trials.find(kPersistentStringSeparator, next_item); + if (name_end == trials.npos || next_item == name_end) + return false; + size_t group_name_end = + trials.find(kPersistentStringSeparator, name_end + 1); + if (group_name_end == trials.npos || name_end + 1 == group_name_end) + return false; + absl::string_view name = trials.substr(next_item, name_end - next_item); + absl::string_view group_name = + trials.substr(name_end + 1, group_name_end - name_end - 1); + + next_item = group_name_end + 1; + + // Fail if duplicate with different group name. + if (field_trials.find(name) != field_trials.end() && + field_trials.find(name)->second != group_name) { + return false; + } + + field_trials[name] = group_name; + } + + return true; +} + +} // namespace + +bool FieldTrialsStringIsValid(absl::string_view trials_string) { + return FieldTrialsStringIsValidInternal(trials_string); +} + +void InsertOrReplaceFieldTrialStringsInMap( + std::map<std::string, std::string>* fieldtrial_map, + const absl::string_view trials_string) { + if (FieldTrialsStringIsValidInternal(trials_string)) { + 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)[std::string(tokens[idx])] = + std::string(tokens[idx + 1]); + } + } else { + RTC_DCHECK_NOTREACHED() << "Invalid field trials string:" << trials_string; + } +} + +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); + + // Merge into fieldtrial string. + std::string merged = ""; + for (auto const& fieldtrial : fieldtrial_map) { + merged += fieldtrial.first + '/' + fieldtrial.second + '/'; + } + return merged; +} + +#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(); + + absl::string_view trials_string(trials_init_string); + if (trials_string.empty()) + return std::string(); + + size_t next_item = 0; + while (next_item < trials_string.length()) { + // Find next name/value pair in field trial configuration string. + size_t field_name_end = + trials_string.find(kPersistentStringSeparator, next_item); + if (field_name_end == trials_string.npos || field_name_end == next_item) + break; + size_t field_value_end = + trials_string.find(kPersistentStringSeparator, field_name_end + 1); + if (field_value_end == trials_string.npos || + field_value_end == field_name_end + 1) + break; + 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 std::string(field_value); + } + return std::string(); +} +#endif // WEBRTC_EXCLUDE_FIELD_TRIAL_DEFAULT + +// Optionally initialize field trial from a string. +void InitFieldTrialsFromString(const char* trials_string) { + RTC_LOG(LS_INFO) << "Setting field trial string:" << trials_string; + if (trials_string) { + RTC_DCHECK(FieldTrialsStringIsValidInternal(trials_string)) + << "Invalid field trials string:" << trials_string; + }; + trials_init_string = trials_string; +} + +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 diff --git a/third_party/libwebrtc/system_wrappers/source/field_trial_unittest.cc b/third_party/libwebrtc/system_wrappers/source/field_trial_unittest.cc new file mode 100644 index 0000000000..ada6313e67 --- /dev/null +++ b/third_party/libwebrtc/system_wrappers/source/field_trial_unittest.cc @@ -0,0 +1,107 @@ +/* + * Copyright 2019 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/field_trial.h" + +#include "rtc_base/checks.h" +#include "test/gtest.h" +#include "test/testsupport/rtc_expect_death.h" + +namespace webrtc { +namespace field_trial { +#if GTEST_HAS_DEATH_TEST && RTC_DCHECK_IS_ON && !defined(WEBRTC_ANDROID) && \ + !defined(WEBRTC_EXCLUDE_FIELD_TRIAL_DEFAULT) +TEST(FieldTrialValidationTest, AcceptsValidInputs) { + InitFieldTrialsFromString(""); + InitFieldTrialsFromString("Audio/Enabled/"); + InitFieldTrialsFromString("Audio/Enabled/Video/Disabled/"); + EXPECT_TRUE(FieldTrialsStringIsValid("")); + EXPECT_TRUE(FieldTrialsStringIsValid("Audio/Enabled/")); + EXPECT_TRUE(FieldTrialsStringIsValid("Audio/Enabled/Video/Disabled/")); + + // Duplicate trials with the same value is fine + InitFieldTrialsFromString("Audio/Enabled/Audio/Enabled/"); + InitFieldTrialsFromString("Audio/Enabled/B/C/Audio/Enabled/"); + EXPECT_TRUE(FieldTrialsStringIsValid("Audio/Enabled/Audio/Enabled/")); + EXPECT_TRUE(FieldTrialsStringIsValid("Audio/Enabled/B/C/Audio/Enabled/")); +} + +TEST(FieldTrialValidationDeathTest, RejectsBadInputs) { + // Bad delimiters + RTC_EXPECT_DEATH(InitFieldTrialsFromString("Audio/EnabledVideo/Disabled/"), + "Invalid field trials string:"); + RTC_EXPECT_DEATH(InitFieldTrialsFromString("Audio/Enabled//Video/Disabled/"), + "Invalid field trials string:"); + RTC_EXPECT_DEATH(InitFieldTrialsFromString("/Audio/Enabled/Video/Disabled/"), + "Invalid field trials string:"); + RTC_EXPECT_DEATH(InitFieldTrialsFromString("Audio/Enabled/Video/Disabled"), + "Invalid field trials string:"); + RTC_EXPECT_DEATH( + InitFieldTrialsFromString("Audio/Enabled/Video/Disabled/garbage"), + "Invalid field trials string:"); + EXPECT_FALSE(FieldTrialsStringIsValid("Audio/EnabledVideo/Disabled/")); + EXPECT_FALSE(FieldTrialsStringIsValid("Audio/Enabled//Video/Disabled/")); + EXPECT_FALSE(FieldTrialsStringIsValid("/Audio/Enabled/Video/Disabled/")); + EXPECT_FALSE(FieldTrialsStringIsValid("Audio/Enabled/Video/Disabled")); + EXPECT_FALSE( + FieldTrialsStringIsValid("Audio/Enabled/Video/Disabled/garbage")); + + // Empty trial or group + RTC_EXPECT_DEATH(InitFieldTrialsFromString("Audio//"), + "Invalid field trials string:"); + RTC_EXPECT_DEATH(InitFieldTrialsFromString("/Enabled/"), + "Invalid field trials string:"); + RTC_EXPECT_DEATH(InitFieldTrialsFromString("//"), + "Invalid field trials string:"); + RTC_EXPECT_DEATH(InitFieldTrialsFromString("//Enabled"), + "Invalid field trials string:"); + EXPECT_FALSE(FieldTrialsStringIsValid("Audio//")); + EXPECT_FALSE(FieldTrialsStringIsValid("/Enabled/")); + EXPECT_FALSE(FieldTrialsStringIsValid("//")); + EXPECT_FALSE(FieldTrialsStringIsValid("//Enabled")); + + // Duplicate trials with different values is not fine + RTC_EXPECT_DEATH(InitFieldTrialsFromString("Audio/Enabled/Audio/Disabled/"), + "Invalid field trials string:"); + RTC_EXPECT_DEATH( + InitFieldTrialsFromString("Audio/Enabled/B/C/Audio/Disabled/"), + "Invalid field trials string:"); + EXPECT_FALSE(FieldTrialsStringIsValid("Audio/Enabled/Audio/Disabled/")); + EXPECT_FALSE(FieldTrialsStringIsValid("Audio/Enabled/B/C/Audio/Disabled/")); +} + +TEST(FieldTrialMergingTest, MergesValidInput) { + EXPECT_EQ(MergeFieldTrialsStrings("Video/Enabled/", "Audio/Enabled/"), + "Audio/Enabled/Video/Enabled/"); + EXPECT_EQ(MergeFieldTrialsStrings("Audio/Disabled/Video/Enabled/", + "Audio/Enabled/"), + "Audio/Enabled/Video/Enabled/"); + EXPECT_EQ( + MergeFieldTrialsStrings("Audio/Enabled/Video/Enabled/", "Audio/Enabled/"), + "Audio/Enabled/Video/Enabled/"); + EXPECT_EQ( + MergeFieldTrialsStrings("Audio/Enabled/Audio/Enabled/", "Video/Enabled/"), + "Audio/Enabled/Video/Enabled/"); +} + +TEST(FieldTrialMergingDeathTest, DchecksBadInput) { + RTC_EXPECT_DEATH(MergeFieldTrialsStrings("Audio/Enabled/", "garbage"), + "Invalid field trials string:"); +} + +TEST(FieldTrialMergingTest, HandlesEmptyInput) { + EXPECT_EQ(MergeFieldTrialsStrings("", "Audio/Enabled/"), "Audio/Enabled/"); + EXPECT_EQ(MergeFieldTrialsStrings("Audio/Enabled/", ""), "Audio/Enabled/"); + EXPECT_EQ(MergeFieldTrialsStrings("", ""), ""); +} +#endif // GTEST_HAS_DEATH_TEST && RTC_DCHECK_IS_ON && !defined(WEBRTC_ANDROID) + // && !defined(WEBRTC_EXCLUDE_FIELD_TRIAL_DEFAULT) + +} // namespace field_trial +} // namespace webrtc diff --git a/third_party/libwebrtc/system_wrappers/source/metrics.cc b/third_party/libwebrtc/system_wrappers/source/metrics.cc new file mode 100644 index 0000000000..39ca590070 --- /dev/null +++ b/third_party/libwebrtc/system_wrappers/source/metrics.cc @@ -0,0 +1,331 @@ +// Copyright (c) 2014 The WebRTC project authors. All Rights Reserved. +// +// Use of this source code is governed by a BSD-style license +// that can be found in the LICENSE file in the root of the source +// tree. An additional intellectual property rights grant can be found +// in the file PATENTS. All contributing project authors may +// be found in the AUTHORS file in the root of the source tree. +// + +#include "system_wrappers/include/metrics.h" + +#include <algorithm> + +#include "absl/strings/string_view.h" +#include "rtc_base/string_utils.h" +#include "rtc_base/synchronization/mutex.h" +#include "rtc_base/thread_annotations.h" + +// Default implementation of histogram methods for WebRTC clients that do not +// want to provide their own implementation. + +namespace webrtc { +namespace metrics { +class Histogram; + +namespace { +// Limit for the maximum number of sample values that can be stored. +// TODO(asapersson): Consider using bucket count (and set up +// linearly/exponentially spaced buckets) if samples are logged more frequently. +const int kMaxSampleMapSize = 300; + +class RtcHistogram { + public: + 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. + + MutexLock lock(&mutex_); + if (info_.samples.size() == kMaxSampleMapSize && + info_.samples.find(sample) == info_.samples.end()) { + return; + } + ++info_.samples[sample]; + } + + // Returns a copy (or nullptr if there are no samples) and clears samples. + std::unique_ptr<SampleInfo> GetAndReset() { + MutexLock lock(&mutex_); + if (info_.samples.empty()) + return nullptr; + + SampleInfo* copy = + new SampleInfo(info_.name, info_.min, info_.max, info_.bucket_count); + + std::swap(info_.samples, copy->samples); + + return std::unique_ptr<SampleInfo>(copy); + } + + const std::string& name() const { return info_.name; } + + // Functions only for testing. + void Reset() { + MutexLock lock(&mutex_); + info_.samples.clear(); + } + + int NumEvents(int sample) const { + MutexLock lock(&mutex_); + const auto it = info_.samples.find(sample); + return (it == info_.samples.end()) ? 0 : it->second; + } + + int NumSamples() const { + int num_samples = 0; + MutexLock lock(&mutex_); + for (const auto& sample : info_.samples) { + num_samples += sample.second; + } + return num_samples; + } + + int MinSample() const { + MutexLock lock(&mutex_); + return (info_.samples.empty()) ? -1 : info_.samples.begin()->first; + } + + std::map<int, int> Samples() const { + MutexLock lock(&mutex_); + return info_.samples; + } + + private: + mutable Mutex mutex_; + const int min_; + const int max_; + SampleInfo info_ RTC_GUARDED_BY(mutex_); +}; + +class RtcHistogramMap { + public: + RtcHistogramMap() {} + ~RtcHistogramMap() {} + + RtcHistogramMap(const RtcHistogramMap&) = delete; + RtcHistogramMap& operator=(const RtcHistogramMap&) = delete; + + Histogram* GetCountsHistogram(absl::string_view name, + int min, + int max, + int bucket_count) { + 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, min, max, bucket_count); + map_.emplace(name, hist); + return reinterpret_cast<Histogram*>(hist); + } + + 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_.emplace(name, hist); + return reinterpret_cast<Histogram*>(hist); + } + + 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(); + if (info) + histograms->insert(std::make_pair(kv.first, std::move(info))); + } + } + + // Functions only for testing. + void Reset() { + MutexLock lock(&mutex_); + for (const auto& kv : map_) + kv.second->Reset(); + } + + 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(absl::string_view name) const { + MutexLock lock(&mutex_); + const auto& it = map_.find(name); + return (it == map_.end()) ? 0 : it->second->NumSamples(); + } + + 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(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(); + } + + private: + mutable Mutex mutex_; + 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 std::atomic<RtcHistogramMap*> g_rtc_histogram_map(nullptr); + +void CreateMap() { + RtcHistogramMap* map = g_rtc_histogram_map.load(std::memory_order_acquire); + if (map == nullptr) { + RtcHistogramMap* new_map = new RtcHistogramMap(); + if (!g_rtc_histogram_map.compare_exchange_strong(map, new_map)) + delete new_map; + } +} + +// Set the first time we start using histograms. Used to make sure Enable() is +// not called thereafter. +#if RTC_DCHECK_IS_ON +static std::atomic<int> g_rtc_histogram_called(0); +#endif + +// Gets the map (or nullptr). +RtcHistogramMap* GetMap() { +#if RTC_DCHECK_IS_ON + g_rtc_histogram_called.store(1, std::memory_order_release); +#endif + return g_rtc_histogram_map.load(); +} +} // namespace + +#ifndef WEBRTC_EXCLUDE_METRICS_DEFAULT +// Implementation of histogram methods in +// webrtc/system_wrappers/interface/metrics.h. + +// Histogram with exponentially spaced buckets. +// Creates (or finds) histogram. +// The returned histogram pointer is cached (and used for adding samples in +// subsequent calls). +Histogram* HistogramFactoryGetCounts(absl::string_view name, + int min, + int max, + int bucket_count) { + // TODO(asapersson): Alternative implementation will be needed if this + // histogram type should be truly exponential. + return HistogramFactoryGetCountsLinear(name, min, max, bucket_count); +} + +// Histogram with linearly spaced buckets. +// Creates (or finds) histogram. +// The returned histogram pointer is cached (and used for adding samples in +// subsequent calls). +Histogram* HistogramFactoryGetCountsLinear(absl::string_view name, + int min, + int max, + int bucket_count) { + RtcHistogramMap* map = GetMap(); + if (!map) + return nullptr; + + return map->GetCountsHistogram(name, min, max, bucket_count); +} + +// Histogram with linearly spaced buckets. +// Creates (or finds) histogram. +// The returned histogram pointer is cached (and used for adding samples in +// subsequent calls). +Histogram* HistogramFactoryGetEnumeration(absl::string_view name, + int boundary) { + RtcHistogramMap* map = GetMap(); + if (!map) + return nullptr; + + return map->GetEnumerationHistogram(name, boundary); +} + +// Our default implementation reuses the non-sparse histogram. +Histogram* SparseHistogramFactoryGetEnumeration(absl::string_view name, + int boundary) { + return HistogramFactoryGetEnumeration(name, boundary); +} + +// 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); +} + +#endif // WEBRTC_EXCLUDE_METRICS_DEFAULT + +SampleInfo::SampleInfo(absl::string_view name, + int min, + int max, + size_t bucket_count) + : name(name), min(min), max(max), bucket_count(bucket_count) {} + +SampleInfo::~SampleInfo() {} + +// Implementation of global functions in metrics.h. +void Enable() { + RTC_DCHECK(g_rtc_histogram_map.load() == nullptr); +#if RTC_DCHECK_IS_ON + 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>, rtc::AbslStringViewCmp>* + histograms) { + histograms->clear(); + RtcHistogramMap* map = GetMap(); + if (map) + map->GetAndReset(histograms); +} + +void Reset() { + RtcHistogramMap* map = GetMap(); + if (map) + map->Reset(); +} + +int NumEvents(absl::string_view name, int sample) { + RtcHistogramMap* map = GetMap(); + return map ? map->NumEvents(name, sample) : 0; +} + +int NumSamples(absl::string_view name) { + RtcHistogramMap* map = GetMap(); + return map ? map->NumSamples(name) : 0; +} + +int MinSample(absl::string_view name) { + RtcHistogramMap* map = GetMap(); + return map ? map->MinSample(name) : -1; +} + +std::map<int, int> Samples(absl::string_view name) { + RtcHistogramMap* map = GetMap(); + return map ? map->Samples(name) : std::map<int, int>(); +} + +} // namespace metrics +} // namespace webrtc diff --git a/third_party/libwebrtc/system_wrappers/source/metrics_default_unittest.cc b/third_party/libwebrtc/system_wrappers/source/metrics_default_unittest.cc new file mode 100644 index 0000000000..a27e9038a3 --- /dev/null +++ b/third_party/libwebrtc/system_wrappers/source/metrics_default_unittest.cc @@ -0,0 +1,174 @@ +/* + * Copyright (c) 2016 The WebRTC project authors. All Rights Reserved. + * + * Use of this source code is governed by a BSD-style license + * that can be found in the LICENSE file in the root of the source + * tree. An additional intellectual property rights grant can be found + * in the file PATENTS. All contributing project authors may + * be found in the AUTHORS file in the root of the source tree. + */ + +#include <map> +#include <memory> +#include <string> +#include <utility> + +#include "rtc_base/checks.h" +#include "rtc_base/string_utils.h" +#include "system_wrappers/include/metrics.h" +#include "test/gtest.h" + +#if RTC_METRICS_ENABLED +namespace webrtc { + +namespace { +const int kSample = 22; +const char kName[] = "Name"; + +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; + + int num_samples = 0; + for (const auto& sample : it->second->samples) + num_samples += sample.second; + + return num_samples; +} + +int NumEvents(absl::string_view name, + int sample, + 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; + + const auto it_sample = it->second->samples.find(sample); + if (it_sample == it->second->samples.end()) + return 0; + + return it_sample->second; +} +} // namespace + +class MetricsDefaultTest : public ::testing::Test { + public: + MetricsDefaultTest() {} + + protected: + void SetUp() override { metrics::Reset(); } +}; + +TEST_F(MetricsDefaultTest, Reset) { + RTC_HISTOGRAM_PERCENTAGE(kName, kSample); + EXPECT_EQ(1, metrics::NumSamples(kName)); + metrics::Reset(); + EXPECT_EQ(0, metrics::NumSamples(kName)); +} + +TEST_F(MetricsDefaultTest, NumSamples) { + RTC_HISTOGRAM_PERCENTAGE(kName, 5); + RTC_HISTOGRAM_PERCENTAGE(kName, 5); + RTC_HISTOGRAM_PERCENTAGE(kName, 10); + EXPECT_EQ(3, metrics::NumSamples(kName)); + EXPECT_EQ(0, metrics::NumSamples("NonExisting")); +} + +TEST_F(MetricsDefaultTest, NumEvents) { + RTC_HISTOGRAM_PERCENTAGE(kName, 5); + RTC_HISTOGRAM_PERCENTAGE(kName, 5); + RTC_HISTOGRAM_PERCENTAGE(kName, 10); + EXPECT_EQ(2, metrics::NumEvents(kName, 5)); + EXPECT_EQ(1, metrics::NumEvents(kName, 10)); + EXPECT_EQ(0, metrics::NumEvents(kName, 11)); + EXPECT_EQ(0, metrics::NumEvents("NonExisting", 5)); +} + +TEST_F(MetricsDefaultTest, MinSample) { + RTC_HISTOGRAM_PERCENTAGE(kName, kSample); + RTC_HISTOGRAM_PERCENTAGE(kName, kSample + 1); + EXPECT_EQ(kSample, metrics::MinSample(kName)); + EXPECT_EQ(-1, metrics::MinSample("NonExisting")); +} + +TEST_F(MetricsDefaultTest, Overflow) { + const std::string kName = "Overflow"; + // Samples should end up in overflow bucket. + RTC_HISTOGRAM_PERCENTAGE(kName, 101); + EXPECT_EQ(1, metrics::NumSamples(kName)); + EXPECT_EQ(1, metrics::NumEvents(kName, 101)); + RTC_HISTOGRAM_PERCENTAGE(kName, 102); + EXPECT_EQ(2, metrics::NumSamples(kName)); + EXPECT_EQ(2, metrics::NumEvents(kName, 101)); +} + +TEST_F(MetricsDefaultTest, Underflow) { + const std::string kName = "Underflow"; + // Samples should end up in underflow bucket. + RTC_HISTOGRAM_COUNTS_10000(kName, 0); + EXPECT_EQ(1, metrics::NumSamples(kName)); + EXPECT_EQ(1, metrics::NumEvents(kName, 0)); + RTC_HISTOGRAM_COUNTS_10000(kName, -1); + EXPECT_EQ(2, metrics::NumSamples(kName)); + EXPECT_EQ(2, metrics::NumEvents(kName, 0)); +} + +TEST_F(MetricsDefaultTest, GetAndReset) { + 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); + RTC_HISTOGRAM_PERCENTAGE("Histogram1", 5); + RTC_HISTOGRAM_PERCENTAGE("Histogram1", 5); + RTC_HISTOGRAM_PERCENTAGE("Histogram2", 10); + EXPECT_EQ(3, metrics::NumSamples("Histogram1")); + EXPECT_EQ(1, metrics::NumSamples("Histogram2")); + + metrics::GetAndReset(&histograms); + EXPECT_EQ(2u, histograms.size()); + EXPECT_EQ(0, metrics::NumSamples("Histogram1")); + EXPECT_EQ(0, metrics::NumSamples("Histogram2")); + + EXPECT_EQ(3, NumSamples("Histogram1", histograms)); + EXPECT_EQ(1, NumSamples("Histogram2", histograms)); + EXPECT_EQ(1, NumEvents("Histogram1", 4, histograms)); + EXPECT_EQ(2, NumEvents("Histogram1", 5, histograms)); + EXPECT_EQ(1, NumEvents("Histogram2", 10, histograms)); + + // Add samples after reset. + metrics::GetAndReset(&histograms); + EXPECT_EQ(0u, histograms.size()); + RTC_HISTOGRAM_PERCENTAGE("Histogram1", 50); + RTC_HISTOGRAM_PERCENTAGE("Histogram2", 8); + EXPECT_EQ(1, metrics::NumSamples("Histogram1")); + EXPECT_EQ(1, metrics::NumSamples("Histogram2")); + EXPECT_EQ(1, metrics::NumEvents("Histogram1", 50)); + EXPECT_EQ(1, metrics::NumEvents("Histogram2", 8)); +} + +TEST_F(MetricsDefaultTest, TestMinMaxBucket) { + const std::string kName = "MinMaxCounts100"; + RTC_HISTOGRAM_COUNTS_100(kName, 4); + + 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); + EXPECT_EQ(1, histograms.begin()->second->min); + EXPECT_EQ(100, histograms.begin()->second->max); + EXPECT_EQ(50u, histograms.begin()->second->bucket_count); + EXPECT_EQ(1u, histograms.begin()->second->samples.size()); +} + +} // namespace webrtc +#endif diff --git a/third_party/libwebrtc/system_wrappers/source/metrics_unittest.cc b/third_party/libwebrtc/system_wrappers/source/metrics_unittest.cc new file mode 100644 index 0000000000..cd3807748c --- /dev/null +++ b/third_party/libwebrtc/system_wrappers/source/metrics_unittest.cc @@ -0,0 +1,136 @@ +/* + * Copyright (c) 2015 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/metrics.h" + +#include "absl/strings/string_view.h" +#include "test/gmock.h" +#include "test/gtest.h" + +using ::testing::ElementsAre; +using ::testing::IsEmpty; +using ::testing::Pair; + +#if RTC_METRICS_ENABLED +namespace webrtc { +namespace { +const int kSample = 22; + +void AddSparseSample(absl::string_view name, int sample) { + RTC_HISTOGRAM_COUNTS_SPARSE_100(name, sample); +} +void AddSampleWithVaryingName(int index, absl::string_view name, int sample) { + RTC_HISTOGRAMS_COUNTS_100(index, name, sample); +} +} // namespace + +class MetricsTest : public ::testing::Test { + public: + MetricsTest() {} + + protected: + void SetUp() override { metrics::Reset(); } +}; + +TEST_F(MetricsTest, InitiallyNoSamples) { + EXPECT_EQ(0, metrics::NumSamples("NonExisting")); + EXPECT_EQ(0, metrics::NumEvents("NonExisting", kSample)); + EXPECT_THAT(metrics::Samples("NonExisting"), IsEmpty()); +} + +TEST_F(MetricsTest, RtcHistogramPercent_AddSample) { + const std::string kName = "Percentage"; + RTC_HISTOGRAM_PERCENTAGE(kName, kSample); + EXPECT_EQ(1, metrics::NumSamples(kName)); + EXPECT_EQ(1, metrics::NumEvents(kName, kSample)); + EXPECT_THAT(metrics::Samples(kName), ElementsAre(Pair(kSample, 1))); +} + +TEST_F(MetricsTest, RtcHistogramEnumeration_AddSample) { + const std::string kName = "Enumeration"; + RTC_HISTOGRAM_ENUMERATION(kName, kSample, kSample + 1); + EXPECT_EQ(1, metrics::NumSamples(kName)); + EXPECT_EQ(1, metrics::NumEvents(kName, kSample)); + EXPECT_THAT(metrics::Samples(kName), ElementsAre(Pair(kSample, 1))); +} + +TEST_F(MetricsTest, RtcHistogramBoolean_AddSample) { + const std::string kName = "Boolean"; + const int kSample = 0; + RTC_HISTOGRAM_BOOLEAN(kName, kSample); + EXPECT_EQ(1, metrics::NumSamples(kName)); + EXPECT_EQ(1, metrics::NumEvents(kName, kSample)); + EXPECT_THAT(metrics::Samples(kName), ElementsAre(Pair(kSample, 1))); +} + +TEST_F(MetricsTest, RtcHistogramCountsSparse_AddSample) { + const std::string kName = "CountsSparse100"; + RTC_HISTOGRAM_COUNTS_SPARSE_100(kName, kSample); + EXPECT_EQ(1, metrics::NumSamples(kName)); + EXPECT_EQ(1, metrics::NumEvents(kName, kSample)); + EXPECT_THAT(metrics::Samples(kName), ElementsAre(Pair(kSample, 1))); +} + +TEST_F(MetricsTest, RtcHistogramCounts_AddSample) { + const std::string kName = "Counts100"; + RTC_HISTOGRAM_COUNTS_100(kName, kSample); + EXPECT_EQ(1, metrics::NumSamples(kName)); + EXPECT_EQ(1, metrics::NumEvents(kName, kSample)); + EXPECT_THAT(metrics::Samples(kName), ElementsAre(Pair(kSample, 1))); +} + +TEST_F(MetricsTest, RtcHistogramCounts_AddMultipleSamples) { + const std::string kName = "Counts200"; + const int kNumSamples = 10; + std::map<int, int> samples; + for (int i = 1; i <= kNumSamples; ++i) { + RTC_HISTOGRAM_COUNTS_200(kName, i); + EXPECT_EQ(1, metrics::NumEvents(kName, i)); + EXPECT_EQ(i, metrics::NumSamples(kName)); + samples[i] = 1; + } + EXPECT_EQ(samples, metrics::Samples(kName)); +} + +TEST_F(MetricsTest, RtcHistogramsCounts_AddSample) { + AddSampleWithVaryingName(0, "Name1", kSample); + AddSampleWithVaryingName(1, "Name2", kSample + 1); + AddSampleWithVaryingName(2, "Name3", kSample + 2); + EXPECT_EQ(1, metrics::NumSamples("Name1")); + EXPECT_EQ(1, metrics::NumSamples("Name2")); + EXPECT_EQ(1, metrics::NumSamples("Name3")); + EXPECT_EQ(1, metrics::NumEvents("Name1", kSample + 0)); + EXPECT_EQ(1, metrics::NumEvents("Name2", kSample + 1)); + EXPECT_EQ(1, metrics::NumEvents("Name3", kSample + 2)); + EXPECT_THAT(metrics::Samples("Name1"), ElementsAre(Pair(kSample + 0, 1))); + EXPECT_THAT(metrics::Samples("Name2"), ElementsAre(Pair(kSample + 1, 1))); + EXPECT_THAT(metrics::Samples("Name3"), ElementsAre(Pair(kSample + 2, 1))); +} + +#if RTC_DCHECK_IS_ON && GTEST_HAS_DEATH_TEST && !defined(WEBRTC_ANDROID) +using MetricsDeathTest = MetricsTest; +TEST_F(MetricsDeathTest, RtcHistogramsCounts_InvalidIndex) { + EXPECT_DEATH(RTC_HISTOGRAMS_COUNTS_1000(-1, "Name", kSample), ""); + EXPECT_DEATH(RTC_HISTOGRAMS_COUNTS_1000(3, "Name", kSample), ""); + EXPECT_DEATH(RTC_HISTOGRAMS_COUNTS_1000(3u, "Name", kSample), ""); +} +#endif + +TEST_F(MetricsTest, RtcHistogramSparse_NonConstantNameWorks) { + AddSparseSample("Sparse1", kSample); + AddSparseSample("Sparse2", kSample); + EXPECT_EQ(1, metrics::NumSamples("Sparse1")); + EXPECT_EQ(1, metrics::NumSamples("Sparse2")); + EXPECT_THAT(metrics::Samples("Sparse1"), ElementsAre(Pair(kSample, 1))); + EXPECT_THAT(metrics::Samples("Sparse2"), ElementsAre(Pair(kSample, 1))); +} + +} // namespace webrtc +#endif diff --git a/third_party/libwebrtc/system_wrappers/source/ntp_time_unittest.cc b/third_party/libwebrtc/system_wrappers/source/ntp_time_unittest.cc new file mode 100644 index 0000000000..0705531e37 --- /dev/null +++ b/third_party/libwebrtc/system_wrappers/source/ntp_time_unittest.cc @@ -0,0 +1,280 @@ +/* + * Copyright (c) 2014 The WebRTC project authors. All Rights Reserved. + * + * Use of this source code is governed by a BSD-style license + * that can be found in the LICENSE file in the root of the source + * tree. An additional intellectual property rights grant can be found + * in the file PATENTS. All contributing project authors may + * be found in the AUTHORS file in the root of the source tree. + */ + +#include "system_wrappers/include/ntp_time.h" + +#include <random> + +#include "system_wrappers/include/clock.h" +#include "test/gtest.h" + +namespace webrtc { +namespace { + +constexpr uint32_t kNtpSec = 0x12345678; +constexpr uint32_t kNtpFrac = 0x23456789; + +constexpr int64_t kOneSecQ32x32 = uint64_t{1} << 32; +constexpr int64_t kOneMsQ32x32 = 4294967; + +TEST(NtpTimeTest, NoValueMeansInvalid) { + NtpTime ntp; + EXPECT_FALSE(ntp.Valid()); +} + +TEST(NtpTimeTest, CanResetValue) { + NtpTime ntp(kNtpSec, kNtpFrac); + EXPECT_TRUE(ntp.Valid()); + ntp.Reset(); + EXPECT_FALSE(ntp.Valid()); +} + +TEST(NtpTimeTest, CanGetWhatIsSet) { + NtpTime ntp; + ntp.Set(kNtpSec, kNtpFrac); + EXPECT_EQ(kNtpSec, ntp.seconds()); + EXPECT_EQ(kNtpFrac, ntp.fractions()); +} + +TEST(NtpTimeTest, SetIsSameAs2ParameterConstructor) { + NtpTime ntp1(kNtpSec, kNtpFrac); + NtpTime ntp2; + EXPECT_NE(ntp1, ntp2); + + ntp2.Set(kNtpSec, kNtpFrac); + EXPECT_EQ(ntp1, ntp2); +} + +TEST(NtpTimeTest, ToMsMeansToNtpMilliseconds) { + SimulatedClock clock(0x123456789abc); + + NtpTime ntp = clock.CurrentNtpTime(); + EXPECT_EQ(ntp.ToMs(), clock.CurrentNtpInMilliseconds()); +} + +TEST(NtpTimeTest, CanExplicitlyConvertToAndFromUint64) { + uint64_t untyped_time = 0x123456789; + NtpTime time(untyped_time); + EXPECT_EQ(untyped_time, static_cast<uint64_t>(time)); + EXPECT_EQ(NtpTime(0x12345678, 0x90abcdef), NtpTime(0x1234567890abcdef)); +} + +TEST(NtpTimeTest, VerifyInt64MsToQ32x32NearZero) { + // Zero + EXPECT_EQ(Int64MsToQ32x32(0), 0); + + // Zero + 1 millisecond + EXPECT_EQ(Int64MsToQ32x32(1), kOneMsQ32x32); + + // Zero - 1 millisecond + EXPECT_EQ(Int64MsToQ32x32(-1), -kOneMsQ32x32); + + // Zero + 1 second + EXPECT_EQ(Int64MsToQ32x32(1000), kOneSecQ32x32); + + // Zero - 1 second + EXPECT_EQ(Int64MsToQ32x32(-1000), -kOneSecQ32x32); +} + +TEST(NtpTimeTest, VerifyInt64MsToUQ32x32NearZero) { + // Zero + EXPECT_EQ(Int64MsToUQ32x32(0), uint64_t{0}); + + // Zero + 1 millisecond + EXPECT_EQ(Int64MsToUQ32x32(1), uint64_t{kOneMsQ32x32}); + + // Zero - 1 millisecond + EXPECT_EQ(Int64MsToUQ32x32(-1), uint64_t{0}); // Clamped + + // Zero + 1 second + EXPECT_EQ(Int64MsToUQ32x32(1000), uint64_t{kOneSecQ32x32}); + + // Zero - 1 second + EXPECT_EQ(Int64MsToUQ32x32(-1000), uint64_t{0}); // Clamped +} + +TEST(NtpTimeTest, VerifyQ32x32ToInt64MsNearZero) { + // Zero + EXPECT_EQ(Q32x32ToInt64Ms(0), 0); + + // Zero + 1 millisecond + EXPECT_EQ(Q32x32ToInt64Ms(kOneMsQ32x32), 1); + + // Zero - 1 millisecond + EXPECT_EQ(Q32x32ToInt64Ms(-kOneMsQ32x32), -1); + + // Zero + 1 second + EXPECT_EQ(Q32x32ToInt64Ms(kOneSecQ32x32), 1000); + + // Zero - 1 second + EXPECT_EQ(Q32x32ToInt64Ms(-kOneSecQ32x32), -1000); +} + +TEST(NtpTimeTest, VerifyUQ32x32ToInt64MsNearZero) { + // Zero + EXPECT_EQ(UQ32x32ToInt64Ms(0), 0); + + // Zero + 1 millisecond + EXPECT_EQ(UQ32x32ToInt64Ms(kOneMsQ32x32), 1); + + // Zero + 1 second + EXPECT_EQ(UQ32x32ToInt64Ms(kOneSecQ32x32), 1000); +} + +TEST(NtpTimeTest, VerifyInt64MsToQ32x32NearMax) { + constexpr int64_t kMaxQ32x32 = std::numeric_limits<int64_t>::max(); + constexpr int64_t kBoundaryMs = (kMaxQ32x32 >> 32) * 1000 + 999; + + // Max + const int64_t boundary_q32x32 = Int64MsToQ32x32(kBoundaryMs); + EXPECT_LE(boundary_q32x32, kMaxQ32x32); + EXPECT_GT(boundary_q32x32, kMaxQ32x32 - kOneMsQ32x32); + + // Max + 1 millisecond + EXPECT_EQ(Int64MsToQ32x32(kBoundaryMs + 1), kMaxQ32x32); // Clamped + + // Max - 1 millisecond + EXPECT_LE(Int64MsToQ32x32(kBoundaryMs - 1), kMaxQ32x32 - kOneMsQ32x32); + + // Max + 1 second + EXPECT_EQ(Int64MsToQ32x32(kBoundaryMs + 1000), kMaxQ32x32); // Clamped + + // Max - 1 second + EXPECT_LE(Int64MsToQ32x32(kBoundaryMs - 1000), kMaxQ32x32 - kOneSecQ32x32); +} + +TEST(NtpTimeTest, VerifyInt64MsToUQ32x32NearMax) { + constexpr uint64_t kMaxUQ32x32 = std::numeric_limits<uint64_t>::max(); + constexpr int64_t kBoundaryMs = (kMaxUQ32x32 >> 32) * 1000 + 999; + + // Max + const uint64_t boundary_uq32x32 = Int64MsToUQ32x32(kBoundaryMs); + EXPECT_LE(boundary_uq32x32, kMaxUQ32x32); + EXPECT_GT(boundary_uq32x32, kMaxUQ32x32 - kOneMsQ32x32); + + // Max + 1 millisecond + EXPECT_EQ(Int64MsToUQ32x32(kBoundaryMs + 1), kMaxUQ32x32); // Clamped + + // Max - 1 millisecond + EXPECT_LE(Int64MsToUQ32x32(kBoundaryMs - 1), kMaxUQ32x32 - kOneMsQ32x32); + + // Max + 1 second + EXPECT_EQ(Int64MsToUQ32x32(kBoundaryMs + 1000), kMaxUQ32x32); // Clamped + + // Max - 1 second + EXPECT_LE(Int64MsToUQ32x32(kBoundaryMs - 1000), kMaxUQ32x32 - kOneSecQ32x32); +} + +TEST(NtpTimeTest, VerifyQ32x32ToInt64MsNearMax) { + constexpr int64_t kMaxQ32x32 = std::numeric_limits<int64_t>::max(); + constexpr int64_t kBoundaryMs = (kMaxQ32x32 >> 32) * 1000 + 1000; + + // Max + EXPECT_EQ(Q32x32ToInt64Ms(kMaxQ32x32), kBoundaryMs); + + // Max - 1 millisecond + EXPECT_EQ(Q32x32ToInt64Ms(kMaxQ32x32 - kOneMsQ32x32), kBoundaryMs - 1); + + // Max - 1 second + EXPECT_EQ(Q32x32ToInt64Ms(kMaxQ32x32 - kOneSecQ32x32), kBoundaryMs - 1000); +} + +TEST(NtpTimeTest, VerifyUQ32x32ToInt64MsNearMax) { + constexpr uint64_t kMaxUQ32x32 = std::numeric_limits<uint64_t>::max(); + constexpr int64_t kBoundaryMs = (kMaxUQ32x32 >> 32) * 1000 + 1000; + + // Max + EXPECT_EQ(UQ32x32ToInt64Ms(kMaxUQ32x32), kBoundaryMs); + + // Max - 1 millisecond + EXPECT_EQ(UQ32x32ToInt64Ms(kMaxUQ32x32 - kOneMsQ32x32), kBoundaryMs - 1); + + // Max - 1 second + EXPECT_EQ(UQ32x32ToInt64Ms(kMaxUQ32x32 - kOneSecQ32x32), kBoundaryMs - 1000); +} + +TEST(NtpTimeTest, VerifyInt64MsToQ32x32NearMin) { + constexpr int64_t kBoundaryQ32x32 = 0x8000000000000000; + constexpr int64_t kBoundaryMs = -int64_t{0x80000000} * 1000; + + // Min + EXPECT_EQ(Int64MsToQ32x32(kBoundaryMs), kBoundaryQ32x32); + + // Min + 1 millisecond + EXPECT_EQ(Q32x32ToInt64Ms(Int64MsToQ32x32(kBoundaryMs + 1)), kBoundaryMs + 1); + + // Min - 1 millisecond + EXPECT_EQ(Int64MsToQ32x32(kBoundaryMs - 1), kBoundaryQ32x32); // Clamped + + // Min + 1 second + EXPECT_EQ(Int64MsToQ32x32(kBoundaryMs + 1000), + kBoundaryQ32x32 + kOneSecQ32x32); + + // Min - 1 second + EXPECT_EQ(Int64MsToQ32x32(kBoundaryMs - 1000), kBoundaryQ32x32); // Clamped +} + +TEST(NtpTimeTest, VerifyQ32x32ToInt64MsNearMin) { + constexpr int64_t kBoundaryQ32x32 = 0x8000000000000000; + constexpr int64_t kBoundaryMs = -int64_t{0x80000000} * 1000; + + // Min + EXPECT_EQ(Q32x32ToInt64Ms(kBoundaryQ32x32), kBoundaryMs); + + // Min + 1 millisecond + EXPECT_EQ(Q32x32ToInt64Ms(kBoundaryQ32x32 + kOneMsQ32x32), kBoundaryMs + 1); + + // Min + 1 second + EXPECT_EQ(Q32x32ToInt64Ms(kBoundaryQ32x32 + kOneSecQ32x32), + kBoundaryMs + 1000); +} + +TEST(NtpTimeTest, VerifyInt64MsToQ32x32RoundTrip) { + constexpr int kIterations = 50000; + + std::mt19937 generator(123456789); + std::uniform_int_distribution<int64_t> distribution( + Q32x32ToInt64Ms(std::numeric_limits<int64_t>::min()), + Q32x32ToInt64Ms(std::numeric_limits<int64_t>::max())); + + for (int iteration = 0; iteration < kIterations; ++iteration) { + int64_t input_ms = distribution(generator); + int64_t transit_q32x32 = Int64MsToQ32x32(input_ms); + int64_t output_ms = Q32x32ToInt64Ms(transit_q32x32); + + ASSERT_EQ(input_ms, output_ms) + << "iteration = " << iteration << ", input_ms = " << input_ms + << ", transit_q32x32 = " << transit_q32x32 + << ", output_ms = " << output_ms; + } +} + +TEST(NtpTimeTest, VerifyInt64MsToUQ32x32RoundTrip) { + constexpr int kIterations = 50000; + + std::mt19937 generator(123456789); + std::uniform_int_distribution<uint64_t> distribution( + UQ32x32ToInt64Ms(std::numeric_limits<uint64_t>::min()), + UQ32x32ToInt64Ms(std::numeric_limits<uint64_t>::max())); + + for (int iteration = 0; iteration < kIterations; ++iteration) { + uint64_t input_ms = distribution(generator); + uint64_t transit_uq32x32 = Int64MsToUQ32x32(input_ms); + uint64_t output_ms = UQ32x32ToInt64Ms(transit_uq32x32); + + ASSERT_EQ(input_ms, output_ms) + << "iteration = " << iteration << ", input_ms = " << input_ms + << ", transit_uq32x32 = " << transit_uq32x32 + << ", output_ms = " << output_ms; + } +} + +} // namespace +} // namespace webrtc diff --git a/third_party/libwebrtc/system_wrappers/source/rtp_to_ntp_estimator.cc b/third_party/libwebrtc/system_wrappers/source/rtp_to_ntp_estimator.cc new file mode 100644 index 0000000000..ef5d9a7508 --- /dev/null +++ b/third_party/libwebrtc/system_wrappers/source/rtp_to_ntp_estimator.cc @@ -0,0 +1,153 @@ +/* + * 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 "system_wrappers/include/rtp_to_ntp_estimator.h" + +#include <stddef.h> + +#include <cmath> +#include <vector> + +#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. +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. +constexpr uint64_t kMaxAllowedRtcpNtpInterval = uint64_t{60 * 60} << 32; +} // namespace + +void RtpToNtpEstimator::UpdateParameters() { + size_t n = measurements_.size(); + if (n < 2) + return; + + // 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 (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 (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; + + double k = covariance_xy / variance_x; + double b = avg_y - k * avg_x; + params_ = {{.slope = k, .offset = b}}; +} + +RtpToNtpEstimator::UpdateResult RtpToNtpEstimator::UpdateMeasurements( + NtpTime ntp, + uint32_t rtp_timestamp) { + int64_t unwrapped_rtp_timestamp = unwrapper_.Unwrap(rtp_timestamp); + + RtcpMeasurement new_measurement = { + .ntp_time = ntp, .unwrapped_rtp_timestamp = unwrapped_rtp_timestamp}; + + 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 kInvalidMeasurement; + + 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; + 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) + << "Newer RTCP SR report with older RTP timestamp, dropping"; + invalid_sample = true; + } else if (unwrapped_rtp_timestamp - old_rtp_timestamp > (1 << 25)) { + // Sanity check. No jumps too far into the future in rtp. + invalid_sample = true; + } + } + + if (invalid_sample) { + ++consecutive_invalid_samples_; + if (consecutive_invalid_samples_ < kMaxInvalidSamples) { + return kInvalidMeasurement; + } + RTC_LOG(LS_WARNING) << "Multiple consecutively invalid RTCP SR reports, " + "clearing measurements."; + measurements_.clear(); + params_ = absl::nullopt; + } + consecutive_invalid_samples_ = 0; + + // Insert new RTCP SR report. + if (measurements_.size() == kNumRtcpReportsToUse) + measurements_.pop_back(); + + measurements_.push_front(new_measurement); + + // List updated, calculate new parameters. + UpdateParameters(); + return kNewMeasurement; +} + +NtpTime RtpToNtpEstimator::Estimate(uint32_t rtp_timestamp) const { + if (!params_) + return NtpTime(); + + double estimated = + static_cast<double>(unwrapper_.Unwrap(rtp_timestamp)) * params_->slope + + params_->offset + 0.5f; + + return NtpTime(rtc::saturated_cast<uint64_t>(estimated)); +} + +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 diff --git a/third_party/libwebrtc/system_wrappers/source/rtp_to_ntp_estimator_unittest.cc b/third_party/libwebrtc/system_wrappers/source/rtp_to_ntp_estimator_unittest.cc new file mode 100644 index 0000000000..81effd9452 --- /dev/null +++ b/third_party/libwebrtc/system_wrappers/source/rtp_to_ntp_estimator_unittest.cc @@ -0,0 +1,277 @@ +/* + * 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 "system_wrappers/include/rtp_to_ntp_estimator.h" + +#include <stddef.h> + +#include "rtc_base/random.h" +#include "test/gtest.h" + +namespace webrtc { +namespace { +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; + 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_EQ(estimator.UpdateMeasurements(NtpTime(2 * kOneMsInNtp), + -kTimestampTicksPerMs), + RtpToNtpEstimator::kInvalidMeasurement); +} + +TEST(WrapAroundTests, OldRtcpWrapped_OldRtpTimestamp_Wraparound_Detected) { + RtpToNtpEstimator estimator; + 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_EQ(estimator.UpdateMeasurements(NtpTime(1 + 3 * kOneMsInNtp), + 0xFFFFFFFE + kTimestampTicksPerMs), + RtpToNtpEstimator::kInvalidMeasurement); +} + +TEST(WrapAroundTests, OldRtcpWrapped_OldRtpTimestamp_NegativeWraparound) { + RtpToNtpEstimator estimator; + EXPECT_EQ(estimator.UpdateMeasurements(NtpTime(1), 0), + RtpToNtpEstimator::kNewMeasurement); + // Expected to fail since the older RTCP has a smaller RTP timestamp than the + // newer (old:0, new:-180). + EXPECT_EQ(estimator.UpdateMeasurements(NtpTime(1 + 2 * kOneMsInNtp), + 0xFFFFFFFF - 2 * kTimestampTicksPerMs), + RtpToNtpEstimator::kInvalidMeasurement); +} + +TEST(WrapAroundTests, NewRtcpWrapped) { + RtpToNtpEstimator estimator; + 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(estimator.Estimate(0xFFFFFFFF), NtpTime(1)); +} + +TEST(WrapAroundTests, RtpWrapped) { + RtpToNtpEstimator estimator; + EXPECT_EQ(estimator.UpdateMeasurements(NtpTime(1), + 0xFFFFFFFF - 2 * kTimestampTicksPerMs), + 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(estimator.Estimate(0xFFFFFFFF - 2 * kTimestampTicksPerMs), + NtpTime(1)); + // Two kTimestampTicksPerMs advanced. + EXPECT_EQ(estimator.Estimate(0xFFFFFFFF), NtpTime(1 + 2 * kOneMsInNtp)); + // Wrapped rtp. + EXPECT_EQ(estimator.Estimate(0xFFFFFFFF + kTimestampTicksPerMs), + NtpTime(1 + 3 * kOneMsInNtp)); +} + +TEST(WrapAroundTests, OldRtp_RtcpsWrapped) { + RtpToNtpEstimator estimator; + 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; + 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(estimator.Estimate(0xFFFFFFFF), NtpTime(1)); +} + +TEST(WrapAroundTests, GracefullyHandleRtpJump) { + RtpToNtpEstimator estimator; + 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(estimator.Estimate(0xFFFFFFFF), NtpTime(1)); + + uint32_t timestamp = 0xFFFFFFFF - 0xFFFFF; + uint64_t ntp_raw = 1 + 2 * kOneMsInNtp; + for (int i = 0; i < RtpToNtpEstimator::kMaxInvalidSamples - 1; ++i) { + EXPECT_EQ(estimator.UpdateMeasurements(NtpTime(ntp_raw), timestamp), + RtpToNtpEstimator::kInvalidMeasurement); + ntp_raw += kOneMsInNtp; + timestamp += kTimestampTicksPerMs; + } + 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); + + EXPECT_EQ(estimator.Estimate(timestamp), NtpTime(ntp_raw)); +} + +TEST(UpdateRtcpMeasurementTests, FailsForZeroNtp) { + RtpToNtpEstimator estimator; + + EXPECT_EQ(estimator.UpdateMeasurements(NtpTime(0), 0x12345678), + RtpToNtpEstimator::kInvalidMeasurement); +} + +TEST(UpdateRtcpMeasurementTests, FailsForEqualNtp) { + RtpToNtpEstimator estimator; + NtpTime ntp(0, 699925050); + uint32_t timestamp = 0x12345678; + + EXPECT_EQ(estimator.UpdateMeasurements(ntp, timestamp), + RtpToNtpEstimator::kNewMeasurement); + // Ntp time already added, list not updated. + EXPECT_EQ(estimator.UpdateMeasurements(ntp, timestamp + 1), + RtpToNtpEstimator::kSameMeasurement); +} + +TEST(UpdateRtcpMeasurementTests, FailsForOldNtp) { + RtpToNtpEstimator estimator; + uint64_t ntp_raw = 699925050; + NtpTime ntp(ntp_raw); + uint32_t timestamp = 0x12345678; + EXPECT_EQ(estimator.UpdateMeasurements(ntp, timestamp), + RtpToNtpEstimator::kNewMeasurement); + + // Old ntp time, list not updated. + EXPECT_EQ(estimator.UpdateMeasurements(NtpTime(ntp_raw - kOneMsInNtp), + timestamp + kTimestampTicksPerMs), + RtpToNtpEstimator::kInvalidMeasurement); +} + +TEST(UpdateRtcpMeasurementTests, FailsForTooNewNtp) { + RtpToNtpEstimator estimator; + + uint64_t ntp_raw = 699925050; + uint32_t timestamp = 0x12345678; + EXPECT_EQ(estimator.UpdateMeasurements(NtpTime(ntp_raw), timestamp), + RtpToNtpEstimator::kNewMeasurement); + + // Ntp time from far future, list not updated. + EXPECT_EQ(estimator.UpdateMeasurements(NtpTime(ntp_raw + 2 * kOneHourInNtp), + timestamp + 10 * kTimestampTicksPerMs), + RtpToNtpEstimator::kInvalidMeasurement); +} + +TEST(UpdateRtcpMeasurementTests, FailsForEqualTimestamp) { + RtpToNtpEstimator estimator; + + uint32_t timestamp = 0x12345678; + EXPECT_EQ(estimator.UpdateMeasurements(NtpTime(2), timestamp), + RtpToNtpEstimator::kNewMeasurement); + // Timestamp already added, list not updated. + EXPECT_EQ(estimator.UpdateMeasurements(NtpTime(3), timestamp), + RtpToNtpEstimator::kSameMeasurement); +} + +TEST(UpdateRtcpMeasurementTests, FailsForOldRtpTimestamp) { + RtpToNtpEstimator estimator; + uint32_t timestamp = 0x12345678; + + EXPECT_EQ(estimator.UpdateMeasurements(NtpTime(2), timestamp), + RtpToNtpEstimator::kNewMeasurement); + // Old timestamp, list not updated. + EXPECT_EQ(estimator.UpdateMeasurements(NtpTime(2 + kOneMsInNtp), + timestamp - kTimestampTicksPerMs), + RtpToNtpEstimator::kInvalidMeasurement); +} + +TEST(UpdateRtcpMeasurementTests, VerifyParameters) { + RtpToNtpEstimator estimator; + uint32_t timestamp = 0x12345678; + + EXPECT_EQ(estimator.UpdateMeasurements(NtpTime(kOneMsInNtp), timestamp), + RtpToNtpEstimator::kNewMeasurement); + + EXPECT_DOUBLE_EQ(estimator.EstimatedFrequencyKhz(), 0.0); + + // Add second report, parameters should be calculated. + 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 timestamp = 0x12345678; + + EXPECT_EQ(estimator.UpdateMeasurements(NtpTime(1), timestamp), + RtpToNtpEstimator::kNewMeasurement); + // Parameters are not calculated, conversion of RTP to NTP time should fail. + EXPECT_DOUBLE_EQ(estimator.EstimatedFrequencyKhz(), 0.0); + EXPECT_FALSE(estimator.Estimate(timestamp).Valid()); +} + +TEST(RtpToNtpTests, AveragesErrorOut) { + RtpToNtpEstimator estimator; + uint64_t ntp_raw = 90000000; // More than 1 ms. + ASSERT_GT(ntp_raw, kOneMsInNtp); + uint32_t timestamp = 0x12345678; + 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_raw += kNtpSecStep; + timestamp += kRtpStep; + // Add upto 1ms of errors to NTP and RTP timestamps passed to estimator. + EXPECT_EQ( + estimator.UpdateMeasurements( + NtpTime(ntp_raw + rand.Rand(-int{kOneMsInNtp}, int{kOneMsInNtp})), + timestamp + rand.Rand(-kRtpTicksPerMs, kRtpTicksPerMs)), + RtpToNtpEstimator::kNewMeasurement); + + NtpTime estimated_ntp = estimator.Estimate(timestamp); + EXPECT_TRUE(estimated_ntp.Valid()); + // Allow upto 2 ms of error. + EXPECT_NEAR(ntp_raw, static_cast<uint64_t>(estimated_ntp), 2 * kOneMsInNtp); + } +} + +} // namespace webrtc diff --git a/third_party/libwebrtc/system_wrappers/source/sleep.cc b/third_party/libwebrtc/system_wrappers/source/sleep.cc new file mode 100644 index 0000000000..e2fa486118 --- /dev/null +++ b/third_party/libwebrtc/system_wrappers/source/sleep.cc @@ -0,0 +1,36 @@ +/* + * 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. + */ +// An OS-independent sleep function. + +#include "system_wrappers/include/sleep.h" + +#ifdef _WIN32 +// For Sleep() +#include <windows.h> +#else +// For nanosleep() +#include <time.h> +#endif + +namespace webrtc { + +void SleepMs(int msecs) { +#ifdef _WIN32 + Sleep(msecs); +#else + struct timespec short_wait; + struct timespec remainder; + short_wait.tv_sec = msecs / 1000; + short_wait.tv_nsec = (msecs % 1000) * 1000 * 1000; + nanosleep(&short_wait, &remainder); +#endif +} + +} // namespace webrtc |