/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */ /* vim: set ts=8 sts=2 et sw=2 tw=80: */ /* This Source Code Form is subject to the terms of the Mozilla Public * License, v. 2.0. If a copy of the MPL was not distributed with this * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ #ifndef TIME_UNITS_H #define TIME_UNITS_H #include #include #include "Intervals.h" #include "mozilla/CheckedInt.h" #include "mozilla/FloatingPoint.h" #include "mozilla/Maybe.h" #include "mozilla/TimeStamp.h" #include "mozilla/IntegerPrintfMacros.h" #include "nsPrintfCString.h" namespace mozilla::media { class TimeIntervals; } // namespace mozilla::media // CopyChooser specialization for nsTArray template <> struct nsTArray_RelocationStrategy { using Type = nsTArray_RelocateUsingMoveConstructor; }; namespace mozilla { // Number of milliseconds per second. 1e3. static const int64_t MSECS_PER_S = 1000; // Number of microseconds per second. 1e6. static const int64_t USECS_PER_S = 1000000; // Number of nanoseconds per second. 1e9. static const int64_t NSECS_PER_S = 1000000000; namespace media { #ifndef PROCESS_DECODE_LOG # define PROCESS_DECODE_LOG(sample) \ MOZ_LOG(sPDMLog, mozilla::LogLevel::Verbose, \ ("ProcessDecode: mDuration=%" PRIu64 "µs ; mTime=%" PRIu64 \ "µs ; mTimecode=%" PRIu64 "µs", \ (sample)->mDuration.ToMicroseconds(), \ (sample)->mTime.ToMicroseconds(), \ (sample)->mTimecode.ToMicroseconds())) #endif // PROCESS_DECODE_LOG // TimeUnit is a class that represents a time value, that can be negative or // positive. // // Internally, it works by storing a numerator (the tick numbers), that uses // checked arithmetics, and a denominator (the base), that is a regular integer // on which arithmetics is never performed, and is only set at construction, or // replaced. // // Dividing the tick count by the base always yields a value in seconds, // but it's very useful to have a base that is dependant on the context: it can // be the sample-rate of an audio stream, the time base of an mp4, that's often // 90000 because it's divisible by 24, 25 and 30. // // Keeping time like this allows performing calculations on time values with // maximum precision, without having to have to care about rounding errors or // precision loss. // // If not specified, the base is 1e6, representing microseconds, for historical // reasons. Users can gradually move to more precise representations when // needed. // // INT64_MAX has the special meaning of being +∞, and INT64_MIN means -∞. Any // other value corresponds to a valid time. // // If an overflow or other problem occurs, the underlying CheckedInt is // invalid and a crash is triggered. class TimeUnit final { public: constexpr TimeUnit(CheckedInt64 aTicks, int64_t aBase) : mTicks(aTicks), mBase(aBase) { MOZ_RELEASE_ASSERT(mBase > 0); } explicit constexpr TimeUnit(CheckedInt64 aTicks) : mTicks(aTicks), mBase(USECS_PER_S) {} // Return the maximum number of ticks that a TimeUnit can contain. static constexpr int64_t MaxTicks() { return std::numeric_limits::max() - 1; } // This is only precise up to a point, which is aValue * aBase <= 2^53 - 1 static TimeUnit FromSeconds(double aValue, int64_t aBase = USECS_PER_S); static constexpr TimeUnit FromMicroseconds(int64_t aValue) { return TimeUnit(aValue, USECS_PER_S); } static TimeUnit FromHns(int64_t aValue, int64_t aBase) { // Truncating here would mean a loss of precision. return TimeUnit::FromNanoseconds(aValue * 100).ToBase(aBase); } static constexpr TimeUnit FromNanoseconds(int64_t aValue) { return TimeUnit(aValue, NSECS_PER_S); } static TimeUnit FromInfinity(); static TimeUnit FromNegativeInfinity(); static TimeUnit FromTimeDuration(const TimeDuration& aDuration); static constexpr TimeUnit Zero(int64_t aBase = USECS_PER_S) { return TimeUnit(0, aBase); } static constexpr TimeUnit Zero(const TimeUnit& aOther) { return TimeUnit(0, aOther.mBase); } static TimeUnit Invalid(); int64_t ToMilliseconds() const; int64_t ToMicroseconds() const; int64_t ToNanoseconds() const; int64_t ToTicksAtRate(int64_t aRate) const; double ToSeconds() const; nsCString ToString() const; TimeDuration ToTimeDuration() const; bool IsInfinite() const; bool IsPositive() const; bool IsPositiveOrZero() const; bool IsZero() const; bool IsNegative() const; // Returns true if the fractions are equal when converted to the smallest // base. bool EqualsAtLowestResolution(const TimeUnit& aOther) const; // Strict equality -- the fractions must be exactly equal bool operator==(const TimeUnit& aOther) const; bool operator!=(const TimeUnit& aOther) const; bool operator>=(const TimeUnit& aOther) const; bool operator>(const TimeUnit& aOther) const; bool operator<=(const TimeUnit& aOther) const; bool operator<(const TimeUnit& aOther) const; TimeUnit operator%(const TimeUnit& aOther) const; TimeUnit operator+(const TimeUnit& aOther) const; TimeUnit operator-(const TimeUnit& aOther) const; TimeUnit& operator+=(const TimeUnit& aOther); TimeUnit& operator-=(const TimeUnit& aOther); template TimeUnit operator*(T aVal) const { // See bug 853398 for the reason to block double multiplier. // If required, use MultDouble below and with caution. static_assert(std::is_integral_v, "Must be an integral type"); return TimeUnit(mTicks * aVal, mBase); } TimeUnit MultDouble(double aVal) const; friend TimeUnit operator/(const TimeUnit& aUnit, int64_t aVal) { MOZ_DIAGNOSTIC_ASSERT(0 <= aVal && aVal <= UINT32_MAX); return TimeUnit(aUnit.mTicks / aVal, aUnit.mBase); } friend TimeUnit operator%(const TimeUnit& aUnit, int64_t aVal) { MOZ_DIAGNOSTIC_ASSERT(0 <= aVal && aVal <= UINT32_MAX); return TimeUnit(aUnit.mTicks % aVal, aUnit.mBase); } struct TruncatePolicy { template static T policy(T& aValue) { return static_cast(aValue); } }; struct RoundPolicy { template static T policy(T& aValue) { return std::round(aValue); } }; template TimeUnit ToBase(int64_t aTargetBase) const { double dummy = 0.0; return ToBase(aTargetBase, dummy); } template TimeUnit ToBase(const TimeUnit& aTimeUnit) const { double dummy = 0.0; return ToBase(aTimeUnit, dummy); } // Allow returning the same value, in a base that matches another TimeUnit. template TimeUnit ToBase(const TimeUnit& aTimeUnit, double& aOutError) const { int64_t targetBase = aTimeUnit.mBase; return ToBase(targetBase, aOutError); } template TimeUnit ToBase(int64_t aTargetBase, double& aOutError) const { aOutError = 0.0; CheckedInt ticks = mTicks * aTargetBase; if (ticks.isValid()) { imaxdiv_t rv = imaxdiv(ticks.value(), mBase); if (!rv.rem) { return TimeUnit(rv.quot, aTargetBase); } } double approx = static_cast(mTicks.value()) * static_cast(aTargetBase) / static_cast(mBase); double integer; aOutError = modf(approx, &integer); return TimeUnit(AssertedCast(RoundingPolicy::policy(approx)), aTargetBase); } bool IsValid() const; constexpr TimeUnit() = default; TimeUnit(const TimeUnit&) = default; TimeUnit& operator=(const TimeUnit&) = default; bool IsPosInf() const; bool IsNegInf() const; // Allow serializing a TimeUnit via IPC friend IPC::ParamTraits; #ifndef VISIBLE_TIMEUNIT_INTERNALS private: #endif int64_t ToCommonUnit(int64_t aRatio) const; // Reduce a TimeUnit to the smallest possible ticks and base. This is useful // to comparison with big time values that can otherwise overflow. TimeUnit Reduced() const; CheckedInt64 mTicks{0}; // Default base is microseconds. int64_t mBase{USECS_PER_S}; }; using NullableTimeUnit = Maybe; using TimeInterval = Interval; // A set of intervals, containing TimeUnit. class TimeIntervals : public IntervalSet { public: using BaseType = IntervalSet; using InnerType = TimeUnit; // We can't use inherited constructors yet. So we have to duplicate all the // constructors found in IntervalSet base class. // all this could be later replaced with: // using IntervalSet::IntervalSet; // MOZ_IMPLICIT as we want to enable initialization in the form: // TimeIntervals i = ... like we would do with IntervalSet i = ... MOZ_IMPLICIT TimeIntervals(const BaseType& aOther) : BaseType(aOther) {} MOZ_IMPLICIT TimeIntervals(BaseType&& aOther) : BaseType(std::move(aOther)) {} explicit TimeIntervals(const BaseType::ElemType& aOther) : BaseType(aOther) {} explicit TimeIntervals(BaseType::ElemType&& aOther) : BaseType(std::move(aOther)) {} static TimeIntervals Invalid() { return TimeIntervals(TimeInterval(TimeUnit::FromNegativeInfinity(), TimeUnit::FromNegativeInfinity())); } bool IsInvalid() const { return Length() == 1 && Start(0).IsNegInf() && End(0).IsNegInf(); } // Returns the same interval, with a microsecond resolution. This is used to // compare TimeUnits internal to demuxers (that use a base from the container) // to floating point numbers in seconds from content. TimeIntervals ToMicrosecondResolution() const { TimeIntervals output; for (const auto& interval : mIntervals) { TimeInterval reducedPrecision{interval.mStart.ToBase(USECS_PER_S), interval.mEnd.ToBase(USECS_PER_S), interval.mFuzz.ToBase(USECS_PER_S)}; output += reducedPrecision; } return output; } nsCString ToString() const { nsCString dump; for (const auto& interval : mIntervals) { dump += nsPrintfCString("[%s],", interval.ToString().get()); } return dump; } TimeIntervals() = default; }; using TimeRange = Interval; // A set of intervals, containing doubles that are seconds. class TimeRanges : public IntervalSet { public: using BaseType = IntervalSet; using InnerType = double; using nld = std::numeric_limits; // We can't use inherited constructors yet. So we have to duplicate all the // constructors found in IntervalSet base class. // all this could be later replaced with: // using IntervalSet::IntervalSet; // MOZ_IMPLICIT as we want to enable initialization in the form: // TimeIntervals i = ... like we would do with IntervalSet i = ... MOZ_IMPLICIT TimeRanges(const BaseType& aOther) : BaseType(aOther) {} MOZ_IMPLICIT TimeRanges(BaseType&& aOther) : BaseType(std::move(aOther)) {} explicit TimeRanges(const BaseType::ElemType& aOther) : BaseType(aOther) {} explicit TimeRanges(BaseType::ElemType&& aOther) : BaseType(std::move(aOther)) {} static TimeRanges Invalid() { return TimeRanges(TimeRange(-nld::infinity(), nld::infinity())); } bool IsInvalid() const { return Length() == 1 && Start(0) == -nld::infinity() && End(0) == nld::infinity(); } // Convert from TimeUnit-based intervals to second-based TimeRanges. explicit TimeRanges(const TimeIntervals& aIntervals) { for (const auto& interval : aIntervals) { Add(TimeRange(interval.mStart.ToSeconds(), interval.mEnd.ToSeconds())); } } TimeRanges ToMicrosecondResolution() const; TimeRanges() = default; }; } // namespace media } // namespace mozilla #endif // TIME_UNITS_H