diff --git a/modules/rtp_rtcp/source/time_util.cc b/modules/rtp_rtcp/source/time_util.cc index cf1d984248..6ac280a61d 100644 --- a/modules/rtp_rtcp/source/time_util.cc +++ b/modules/rtp_rtcp/source/time_util.cc @@ -12,6 +12,8 @@ #include +#include "rtc_base/timeutils.h" + namespace webrtc { namespace { // TODO(danilchap): Make generic, optimize and move to base. @@ -19,20 +21,53 @@ inline int64_t DivideRoundToNearest(int64_t x, uint32_t y) { // Callers ensure x is positive and x + y / 2 doesn't overflow. return (x + y / 2) / y; } + +int64_t NtpOffsetUs() { + 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; +} + } // namespace +NtpTime TimeMicrosToNtp(int64_t time_us) { + // Calculate the offset once. + static int64_t ntp_offset_us = NtpOffsetUs(); + + int64_t time_ntp_us = time_us + ntp_offset_us; + RTC_DCHECK_GE(time_ntp_us, 0); // Time before year 1900 is unsupported. + + // TODO(danilchap): Convert both seconds and fraction together using int128 + // when that type is easily available. + // Currently conversion is done separetly for seconds and fraction of a second + // to avoid overflow. + + // Convert seconds to uint32 through uint64 for well-defined cast. + // Wrap around (will happen in 2036) is expected for ntp time. + uint32_t ntp_seconds = + static_cast(time_ntp_us / rtc::kNumMicrosecsPerSec); + + // Scale fractions of the second to ntp resolution. + constexpr int64_t kNtpInSecond = 1LL << 32; + int64_t us_fractions = time_ntp_us % rtc::kNumMicrosecsPerSec; + uint32_t ntp_fractions = + us_fractions * kNtpInSecond / rtc::kNumMicrosecsPerSec; + return NtpTime(ntp_seconds, ntp_fractions); +} + uint32_t SaturatedUsToCompactNtp(int64_t us) { constexpr uint32_t kMaxCompactNtp = 0xFFFFFFFF; - constexpr int64_t kMicrosecondsInSecond = 1000000; constexpr int kCompactNtpInSecond = 0x10000; if (us <= 0) return 0; - if (us >= kMaxCompactNtp * kMicrosecondsInSecond / kCompactNtpInSecond) + if (us >= kMaxCompactNtp * rtc::kNumMicrosecsPerSec / kCompactNtpInSecond) return kMaxCompactNtp; // To convert to compact ntp need to divide by 1e6 to get seconds, // then multiply by 0x10000 to get the final result. // To avoid float operations, multiplication and division swapped. - return DivideRoundToNearest(us * kCompactNtpInSecond, kMicrosecondsInSecond); + return DivideRoundToNearest(us * kCompactNtpInSecond, + rtc::kNumMicrosecsPerSec); } int64_t CompactNtpRttToMs(uint32_t compact_ntp_interval) { diff --git a/modules/rtp_rtcp/source/time_util.h b/modules/rtp_rtcp/source/time_util.h index a87a5b794c..3e7d1e29f1 100644 --- a/modules/rtp_rtcp/source/time_util.h +++ b/modules/rtp_rtcp/source/time_util.h @@ -17,6 +17,13 @@ namespace webrtc { +// Converts time obtained using rtc::TimeMicros to ntp format. +// TimeMicrosToNtp guarantees difference of the returned values matches +// difference of the passed values. +// As a result TimeMicrosToNtp(rtc::TimeMicros()) doesn't guarantte to match +// system time after first call. +NtpTime TimeMicrosToNtp(int64_t time_us); + // Converts NTP timestamp to RTP timestamp. inline uint32_t NtpToRtp(NtpTime ntp, uint32_t freq) { uint32_t tmp = (static_cast(ntp.fractions()) * freq) >> 32; diff --git a/modules/rtp_rtcp/source/time_util_unittest.cc b/modules/rtp_rtcp/source/time_util_unittest.cc index 6ff55dda55..cdf271284b 100644 --- a/modules/rtp_rtcp/source/time_util_unittest.cc +++ b/modules/rtp_rtcp/source/time_util_unittest.cc @@ -9,10 +9,49 @@ */ #include "modules/rtp_rtcp/source/time_util.h" +#include "rtc_base/fakeclock.h" +#include "rtc_base/timeutils.h" +#include "system_wrappers/include/clock.h" #include "test/gtest.h" namespace webrtc { +TEST(TimeUtilTest, TimeMicrosToNtpMatchRealTimeClockInitially) { + Clock* legacy_clock = Clock::GetRealTimeClock(); + NtpTime before_legacy_time = TimeMicrosToNtp(rtc::TimeMicros()); + NtpTime legacy_time = legacy_clock->CurrentNtpTime(); + NtpTime after_legacy_time = TimeMicrosToNtp(rtc::TimeMicros()); + + // This test will fail once every 136 years, when NtpTime wraparound. + // More often than that, it will fail if system adjust ntp time while test + // is running. + // To mitigate ntp time adjustment and potentional different precisions of + // Clock and TimeMicrosToNtp, relax expectation by a millisecond. + EXPECT_GE(legacy_time.ToMs(), before_legacy_time.ToMs() - 1); + EXPECT_LE(legacy_time.ToMs(), after_legacy_time.ToMs() + 1); +} + +TEST(TimeUtilTest, TimeMicrosToNtpDoesntChangeBetweenRuns) { + rtc::ScopedFakeClock clock; + // TimeMicrosToNtp is not pure: it behave differently between different + // execution of the program, but should behave same during same execution. + const int64_t time_us = 12345; + clock.SetTimeMicros(2); + NtpTime time_ntp = TimeMicrosToNtp(time_us); + clock.SetTimeMicros(time_us); + EXPECT_EQ(TimeMicrosToNtp(time_us), time_ntp); + clock.SetTimeMicros(1000000); + EXPECT_EQ(TimeMicrosToNtp(time_us), time_ntp); +} + +TEST(TimeUtilTest, TimeMicrosToNtpKeepsIntervals) { + rtc::ScopedFakeClock clock; + NtpTime time_ntp1 = TimeMicrosToNtp(rtc::TimeMicros()); + clock.AdvanceTimeMicros(20000); + NtpTime time_ntp2 = TimeMicrosToNtp(rtc::TimeMicros()); + EXPECT_EQ(time_ntp2.ToMs() - time_ntp1.ToMs(), 20); +} + TEST(TimeUtilTest, CompactNtp) { const uint32_t kNtpSec = 0x12345678; const uint32_t kNtpFrac = 0x23456789;