From 26b5e352766320a743cff38e20d87a1d48efbb0a Mon Sep 17 00:00:00 2001 From: Sebastian Jansson Date: Fri, 7 Jun 2019 11:05:31 +0200 Subject: [PATCH] Adds Frequency unit type. Bug: webrtc:10674 Change-Id: Ic0ddca46d8522d994bbeba072a73836b506fe40f Reviewed-on: https://webrtc-review.googlesource.com/c/src/+/138261 Commit-Queue: Sebastian Jansson Reviewed-by: Niels Moller Cr-Commit-Position: refs/heads/master@{#28192} --- api/units/BUILD.gn | 19 ++++ api/units/data_rate.h | 51 ++++++++-- api/units/data_rate_unittest.cc | 13 +++ api/units/frequency.cc | 28 ++++++ api/units/frequency.h | 89 ++++++++++++++++++ api/units/frequency_unittest.cc | 159 ++++++++++++++++++++++++++++++++ api/units/time_delta.cc | 1 + api/units/time_delta.h | 1 + rtc_base/units/unit_base.h | 20 ++++ 9 files changed, 371 insertions(+), 10 deletions(-) create mode 100644 api/units/frequency.cc create mode 100644 api/units/frequency.h create mode 100644 api/units/frequency_unittest.cc diff --git a/api/units/BUILD.gn b/api/units/BUILD.gn index e86999d666..1f72579f70 100644 --- a/api/units/BUILD.gn +++ b/api/units/BUILD.gn @@ -17,6 +17,7 @@ rtc_source_set("data_rate") { deps = [ ":data_size", + ":frequency", ":time_delta", "..:array_view", "../../rtc_base:checks", @@ -55,6 +56,22 @@ rtc_source_set("time_delta") { ] } +rtc_source_set("frequency") { + visibility = [ "*" ] + sources = [ + "frequency.cc", + "frequency.h", + ] + + deps = [ + ":time_delta", + "..:array_view", + "../../rtc_base:checks", + "../../rtc_base:stringutils", + "../../rtc_base/units:unit_base", + ] +} + rtc_source_set("timestamp") { visibility = [ "*" ] sources = [ @@ -77,12 +94,14 @@ if (rtc_include_tests) { sources = [ "data_rate_unittest.cc", "data_size_unittest.cc", + "frequency_unittest.cc", "time_delta_unittest.cc", "timestamp_unittest.cc", ] deps = [ ":data_rate", ":data_size", + ":frequency", ":time_delta", ":timestamp", "../../rtc_base:logging", diff --git a/api/units/data_rate.h b/api/units/data_rate.h index ccbebdcba2..3ecdce657c 100644 --- a/api/units/data_rate.h +++ b/api/units/data_rate.h @@ -20,21 +20,12 @@ #include #include "api/units/data_size.h" +#include "api/units/frequency.h" #include "api/units/time_delta.h" #include "rtc_base/checks.h" #include "rtc_base/units/unit_base.h" namespace webrtc { -namespace data_rate_impl { -inline int64_t Microbits(const DataSize& size) { - constexpr int64_t kMaxBeforeConversion = - std::numeric_limits::max() / 8000000; - RTC_DCHECK_LE(size.bytes(), kMaxBeforeConversion) - << "size is too large to be expressed in microbytes"; - return size.bytes() * 8000000; -} -} // namespace data_rate_impl - // DataRate is a class that represents a given data rate. This can be used to // represent bandwidth, encoding bitrate, etc. The internal storage is bits per // second (bps). @@ -92,6 +83,24 @@ class DataRate final : public rtc_units_impl::RelativeUnit { static constexpr bool one_sided = true; }; +namespace data_rate_impl { +inline int64_t Microbits(const DataSize& size) { + constexpr int64_t kMaxBeforeConversion = + std::numeric_limits::max() / 8000000; + RTC_DCHECK_LE(size.bytes(), kMaxBeforeConversion) + << "size is too large to be expressed in microbits"; + return size.bytes() * 8000000; +} + +inline int64_t MillibytePerSec(const DataRate& size) { + constexpr int64_t kMaxBeforeConversion = + std::numeric_limits::max() / (1000 / 8); + RTC_DCHECK_LE(size.bps(), kMaxBeforeConversion) + << "rate is too large to be expressed in microbytes per second"; + return size.bps() * (1000 / 8); +} +} // namespace data_rate_impl + inline DataRate operator/(const DataSize size, const TimeDelta duration) { return DataRate::bps(data_rate_impl::Microbits(size) / duration.us()); } @@ -106,6 +115,28 @@ inline DataSize operator*(const TimeDelta duration, const DataRate rate) { return rate * duration; } +inline DataSize operator/(const DataRate rate, const Frequency frequency) { + int64_t millihertz = frequency.millihertz(); + // Note that the value is truncated here reather than rounded, potentially + // introducing an error of .5 bytes if rounding were expected. + return DataSize::bytes(data_rate_impl::MillibytePerSec(rate) / millihertz); +} +inline Frequency operator/(const DataRate rate, const DataSize size) { + return Frequency::millihertz(data_rate_impl::MillibytePerSec(rate) / + size.bytes()); +} +inline DataRate operator*(const DataSize size, const Frequency frequency) { + int64_t millihertz = frequency.millihertz(); + int64_t kMaxBeforeConversion = + std::numeric_limits::max() / 8 / millihertz; + RTC_DCHECK_LE(size.bytes(), kMaxBeforeConversion); + int64_t millibits_per_second = size.bytes() * 8 * millihertz; + return DataRate::bps((millibits_per_second + 500) / 1000); +} +inline DataRate operator*(const Frequency frequency, const DataSize size) { + return size * frequency; +} + std::string ToString(DataRate value); inline std::string ToLogString(DataRate value) { return ToString(value); diff --git a/api/units/data_rate_unittest.cc b/api/units/data_rate_unittest.cc index e5bfc573d9..a56ccb2c2f 100644 --- a/api/units/data_rate_unittest.cc +++ b/api/units/data_rate_unittest.cc @@ -161,6 +161,19 @@ TEST(UnitConversionTest, DataRateAndDataSizeAndTimeDelta) { EXPECT_EQ((size_c / rate_b).seconds(), kBytes * 8 / kBitsPerSecond); } +TEST(UnitConversionTest, DataRateAndDataSizeAndFrequency) { + const int64_t kHertz = 30; + const int64_t kBitsPerSecond = 96000; + const int64_t kBytes = 1200; + const Frequency freq_a = Frequency::hertz(kHertz); + const DataRate rate_b = DataRate::bps(kBitsPerSecond); + const DataSize size_c = DataSize::bytes(kBytes); + EXPECT_EQ((freq_a * size_c).bps(), kHertz * kBytes * 8); + EXPECT_EQ((size_c * freq_a).bps(), kHertz * kBytes * 8); + EXPECT_EQ((rate_b / size_c).hertz(), kBitsPerSecond / kBytes / 8); + EXPECT_EQ((rate_b / freq_a).bytes(), kBitsPerSecond / kHertz / 8); +} + TEST(UnitConversionTest, DivisionFailsOnLargeSize) { // Note that the failure is expected since the current implementation is // implementated in a way that does not support division of large sizes. If diff --git a/api/units/frequency.cc b/api/units/frequency.cc new file mode 100644 index 0000000000..f7e38ca13c --- /dev/null +++ b/api/units/frequency.cc @@ -0,0 +1,28 @@ +/* + * Copyright (c) 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 "api/units/frequency.h" +#include "rtc_base/strings/string_builder.h" + +namespace webrtc { +std::string ToString(Frequency value) { + char buf[64]; + rtc::SimpleStringBuilder sb(buf); + if (value.IsPlusInfinity()) { + sb << "+inf Hz"; + } else if (value.IsMinusInfinity()) { + sb << "-inf Hz"; + } else if (value.millihertz() % 1000 != 0) { + sb.AppendFormat("%.3f Hz", value.hertz()); + } else { + sb << value.hertz() << " Hz"; + } + return sb.str(); +} +} // namespace webrtc diff --git a/api/units/frequency.h b/api/units/frequency.h new file mode 100644 index 0000000000..e9aa64a6e8 --- /dev/null +++ b/api/units/frequency.h @@ -0,0 +1,89 @@ +/* + * Copyright (c) 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. + */ +#ifndef API_UNITS_FREQUENCY_H_ +#define API_UNITS_FREQUENCY_H_ + +#ifdef UNIT_TEST +#include // no-presubmit-check TODO(webrtc:8982) +#endif // UNIT_TEST + +#include +#include +#include +#include + +#include "api/units/time_delta.h" +#include "rtc_base/units/unit_base.h" + +namespace webrtc { + +class Frequency final : public rtc_units_impl::RelativeUnit { + public: + Frequency() = delete; + template + static constexpr Frequency Hertz() { + return FromStaticFraction(); + } + template + static Frequency hertz(T hertz) { + static_assert(std::is_arithmetic::value, ""); + return FromFraction<1000>(hertz); + } + template + static Frequency millihertz(T hertz) { + static_assert(std::is_arithmetic::value, ""); + return FromValue(hertz); + } + template + T hertz() const { + return ToFraction<1000, T>(); + } + template + T millihertz() const { + return ToValue(); + } + + private: + friend class rtc_units_impl::UnitBase; + using RelativeUnit::RelativeUnit; + static constexpr bool one_sided = true; +}; + +inline Frequency operator/(int64_t nominator, const TimeDelta& interval) { + constexpr int64_t kKiloPerMicro = 1000 * 1000000; + RTC_DCHECK_LE(nominator, std::numeric_limits::max() / kKiloPerMicro); + RTC_CHECK(interval.IsFinite()); + RTC_CHECK(!interval.IsZero()); + return Frequency::millihertz(nominator * kKiloPerMicro / interval.us()); +} + +inline TimeDelta operator/(int64_t nominator, const Frequency& frequency) { + constexpr int64_t kMegaPerMilli = 1000000 * 1000; + RTC_DCHECK_LE(nominator, std::numeric_limits::max() / kMegaPerMilli); + RTC_CHECK(frequency.IsFinite()); + RTC_CHECK(!frequency.IsZero()); + return TimeDelta::us(nominator * kMegaPerMilli / frequency.millihertz()); +} + +std::string ToString(Frequency value); +inline std::string ToLogString(Frequency value) { + return ToString(value); +} + +#ifdef UNIT_TEST +inline std::ostream& operator<<( // no-presubmit-check TODO(webrtc:8982) + std::ostream& stream, // no-presubmit-check TODO(webrtc:8982) + Frequency value) { + return stream << ToString(value); +} +#endif // UNIT_TEST + +} // namespace webrtc +#endif // API_UNITS_FREQUENCY_H_ diff --git a/api/units/frequency_unittest.cc b/api/units/frequency_unittest.cc new file mode 100644 index 0000000000..cabfdfaf4b --- /dev/null +++ b/api/units/frequency_unittest.cc @@ -0,0 +1,159 @@ +/* + * Copyright (c) 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 "api/units/frequency.h" + +#include + +#include "test/gtest.h" + +namespace webrtc { +namespace test { +TEST(FrequencyTest, ConstExpr) { + constexpr Frequency kFrequencyZero = Frequency::Zero(); + constexpr Frequency kFrequencyPlusInf = Frequency::PlusInfinity(); + constexpr Frequency kFrequencyMinusInf = Frequency::MinusInfinity(); + static_assert(kFrequencyZero.IsZero(), ""); + static_assert(kFrequencyPlusInf.IsPlusInfinity(), ""); + static_assert(kFrequencyMinusInf.IsMinusInfinity(), ""); + + static_assert(kFrequencyPlusInf > kFrequencyZero, ""); +} + +TEST(FrequencyTest, GetBackSameValues) { + const int64_t kValue = 31; + EXPECT_EQ(Frequency::hertz(kValue).hertz(), kValue); + EXPECT_EQ(Frequency::Zero().hertz(), 0); +} + +TEST(FrequencyTest, GetDifferentPrefix) { + const int64_t kValue = 30000; + EXPECT_EQ(Frequency::millihertz(kValue).hertz(), kValue / 1000); + EXPECT_EQ(Frequency::hertz(kValue).millihertz(), kValue * 1000); +} + +TEST(FrequencyTest, IdentityChecks) { + const int64_t kValue = 31; + EXPECT_TRUE(Frequency::Zero().IsZero()); + EXPECT_FALSE(Frequency::hertz(kValue).IsZero()); + + EXPECT_TRUE(Frequency::PlusInfinity().IsInfinite()); + EXPECT_TRUE(Frequency::MinusInfinity().IsInfinite()); + EXPECT_FALSE(Frequency::Zero().IsInfinite()); + EXPECT_FALSE(Frequency::hertz(kValue).IsInfinite()); + + EXPECT_FALSE(Frequency::PlusInfinity().IsFinite()); + EXPECT_FALSE(Frequency::MinusInfinity().IsFinite()); + EXPECT_TRUE(Frequency::hertz(kValue).IsFinite()); + EXPECT_TRUE(Frequency::Zero().IsFinite()); + + EXPECT_TRUE(Frequency::PlusInfinity().IsPlusInfinity()); + EXPECT_FALSE(Frequency::MinusInfinity().IsPlusInfinity()); + + EXPECT_TRUE(Frequency::MinusInfinity().IsMinusInfinity()); + EXPECT_FALSE(Frequency::PlusInfinity().IsMinusInfinity()); +} + +TEST(FrequencyTest, ComparisonOperators) { + const int64_t kSmall = 42; + const int64_t kLarge = 45; + const Frequency small = Frequency::hertz(kSmall); + const Frequency large = Frequency::hertz(kLarge); + + EXPECT_EQ(Frequency::Zero(), Frequency::hertz(0)); + EXPECT_EQ(Frequency::PlusInfinity(), Frequency::PlusInfinity()); + EXPECT_EQ(small, Frequency::hertz(kSmall)); + EXPECT_LE(small, Frequency::hertz(kSmall)); + EXPECT_GE(small, Frequency::hertz(kSmall)); + EXPECT_NE(small, Frequency::hertz(kLarge)); + EXPECT_LE(small, Frequency::hertz(kLarge)); + EXPECT_LT(small, Frequency::hertz(kLarge)); + EXPECT_GE(large, Frequency::hertz(kSmall)); + EXPECT_GT(large, Frequency::hertz(kSmall)); + EXPECT_LT(Frequency::Zero(), small); + + EXPECT_GT(Frequency::PlusInfinity(), large); + EXPECT_LT(Frequency::MinusInfinity(), Frequency::Zero()); +} + +TEST(FrequencyTest, Clamping) { + const Frequency upper = Frequency::hertz(800); + const Frequency lower = Frequency::hertz(100); + const Frequency under = Frequency::hertz(100); + const Frequency inside = Frequency::hertz(500); + const Frequency over = Frequency::hertz(1000); + EXPECT_EQ(under.Clamped(lower, upper), lower); + EXPECT_EQ(inside.Clamped(lower, upper), inside); + EXPECT_EQ(over.Clamped(lower, upper), upper); + + Frequency mutable_frequency = lower; + mutable_frequency.Clamp(lower, upper); + EXPECT_EQ(mutable_frequency, lower); + mutable_frequency = inside; + mutable_frequency.Clamp(lower, upper); + EXPECT_EQ(mutable_frequency, inside); + mutable_frequency = over; + mutable_frequency.Clamp(lower, upper); + EXPECT_EQ(mutable_frequency, upper); +} + +TEST(FrequencyTest, MathOperations) { + const int64_t kValueA = 457; + const int64_t kValueB = 260; + const Frequency frequency_a = Frequency::hertz(kValueA); + const Frequency frequency_b = Frequency::hertz(kValueB); + EXPECT_EQ((frequency_a + frequency_b).hertz(), kValueA + kValueB); + EXPECT_EQ((frequency_a - frequency_b).hertz(), kValueA - kValueB); + + EXPECT_EQ((Frequency::hertz(kValueA) * kValueB).hertz(), + kValueA * kValueB); + + EXPECT_EQ((frequency_b / 10).hertz(), kValueB / 10); + EXPECT_EQ(frequency_b / frequency_a, static_cast(kValueB) / kValueA); + + Frequency mutable_frequency = Frequency::hertz(kValueA); + mutable_frequency += Frequency::hertz(kValueB); + EXPECT_EQ(mutable_frequency, Frequency::hertz(kValueA + kValueB)); + mutable_frequency -= Frequency::hertz(kValueB); + EXPECT_EQ(mutable_frequency, Frequency::hertz(kValueA)); +} +TEST(FrequencyTest, Rounding) { + const Frequency freq_high = Frequency::hertz(23.976); + EXPECT_EQ(freq_high.hertz(), 24); + EXPECT_EQ(freq_high.RoundDownTo(Frequency::hertz(1)), Frequency::hertz(23)); + EXPECT_EQ(freq_high.RoundTo(Frequency::hertz(1)), Frequency::hertz(24)); + EXPECT_EQ(freq_high.RoundUpTo(Frequency::hertz(1)), Frequency::hertz(24)); + + const Frequency freq_low = Frequency::hertz(23.4); + EXPECT_EQ(freq_low.hertz(), 23); + EXPECT_EQ(freq_low.RoundDownTo(Frequency::hertz(1)), Frequency::hertz(23)); + EXPECT_EQ(freq_low.RoundTo(Frequency::hertz(1)), Frequency::hertz(23)); + EXPECT_EQ(freq_low.RoundUpTo(Frequency::hertz(1)), Frequency::hertz(24)); +} + +TEST(FrequencyTest, InfinityOperations) { + const double kValue = 267; + const Frequency finite = Frequency::hertz(kValue); + EXPECT_TRUE((Frequency::PlusInfinity() + finite).IsPlusInfinity()); + EXPECT_TRUE((Frequency::PlusInfinity() - finite).IsPlusInfinity()); + EXPECT_TRUE((finite + Frequency::PlusInfinity()).IsPlusInfinity()); + EXPECT_TRUE((finite - Frequency::MinusInfinity()).IsPlusInfinity()); + + EXPECT_TRUE((Frequency::MinusInfinity() + finite).IsMinusInfinity()); + EXPECT_TRUE((Frequency::MinusInfinity() - finite).IsMinusInfinity()); + EXPECT_TRUE((finite + Frequency::MinusInfinity()).IsMinusInfinity()); + EXPECT_TRUE((finite - Frequency::PlusInfinity()).IsMinusInfinity()); +} + +TEST(UnitConversionTest, TimeDeltaAndFrequency) { + EXPECT_EQ(1 / Frequency::hertz(50), TimeDelta::ms(20)); + EXPECT_EQ(1 / TimeDelta::ms(20), Frequency::hertz(50)); +} +} // namespace test +} // namespace webrtc diff --git a/api/units/time_delta.cc b/api/units/time_delta.cc index 9fe1308295..31bf3e0106 100644 --- a/api/units/time_delta.cc +++ b/api/units/time_delta.cc @@ -32,4 +32,5 @@ std::string ToString(TimeDelta value) { } return sb.str(); } + } // namespace webrtc diff --git a/api/units/time_delta.h b/api/units/time_delta.h index f8c6af457e..4ab83ec1c1 100644 --- a/api/units/time_delta.h +++ b/api/units/time_delta.h @@ -22,6 +22,7 @@ #include "rtc_base/units/unit_base.h" namespace webrtc { + // TimeDelta represents the difference between two timestamps. Commonly this can // be a duration. However since two Timestamps are not guaranteed to have the // same epoch (they might come from different computers, making exact diff --git a/rtc_base/units/unit_base.h b/rtc_base/units/unit_base.h index 37b60a0bb9..5bcc0d84ff 100644 --- a/rtc_base/units/unit_base.h +++ b/rtc_base/units/unit_base.h @@ -67,6 +67,26 @@ class UnitBase { constexpr bool operator<(const Unit_T& other) const { return value_ < other.value_; } + Unit_T RoundTo(const Unit_T& resolution) const { + RTC_DCHECK(IsFinite()); + RTC_DCHECK(resolution.IsFinite()); + RTC_DCHECK_GT(resolution.value_, 0); + return Unit_T((value_ + resolution.value_ / 2) / resolution.value_) * + resolution.value_; + } + Unit_T RoundUpTo(const Unit_T& resolution) const { + RTC_DCHECK(IsFinite()); + RTC_DCHECK(resolution.IsFinite()); + RTC_DCHECK_GT(resolution.value_, 0); + return Unit_T((value_ + resolution.value_ - 1) / resolution.value_) * + resolution.value_; + } + Unit_T RoundDownTo(const Unit_T& resolution) const { + RTC_DCHECK(IsFinite()); + RTC_DCHECK(resolution.IsFinite()); + RTC_DCHECK_GT(resolution.value_, 0); + return Unit_T(value_ / resolution.value_) * resolution.value_; + } protected: template