TimeUnits.h (13508B)
1 /* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */ 2 /* vim: set ts=8 sts=2 et sw=2 tw=80: */ 3 /* This Source Code Form is subject to the terms of the Mozilla Public 4 * License, v. 2.0. If a copy of the MPL was not distributed with this 5 * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ 6 7 #ifndef TIME_UNITS_H 8 #define TIME_UNITS_H 9 10 #include <limits> 11 #include <type_traits> 12 13 #include "Intervals.h" 14 #include "mozilla/CheckedInt.h" 15 #include "mozilla/FloatingPoint.h" 16 #include "mozilla/IntegerPrintfMacros.h" 17 #include "mozilla/Maybe.h" 18 #include "mozilla/TimeStamp.h" 19 #include "nsPrintfCString.h" 20 21 namespace mozilla::media { 22 class TimeIntervals; 23 } // namespace mozilla::media 24 // CopyChooser specialization for nsTArray 25 template <> 26 struct nsTArray_RelocationStrategy<mozilla::media::TimeIntervals> { 27 using Type = 28 nsTArray_RelocateUsingMoveConstructor<mozilla::media::TimeIntervals>; 29 }; 30 31 namespace mozilla { 32 33 // Number of milliseconds per second. 1e3. 34 static const int64_t MSECS_PER_S = 1000; 35 36 // Number of microseconds per second. 1e6. 37 static const int64_t USECS_PER_S = 1000000; 38 39 // Number of nanoseconds per second. 1e9. 40 static const int64_t NSECS_PER_S = 1000000000; 41 42 namespace media { 43 44 #ifndef PROCESS_DECODE_LOG 45 # define PROCESS_DECODE_LOG(sample) \ 46 MOZ_LOG(sPDMLog, mozilla::LogLevel::Verbose, \ 47 ("ProcessDecode: mDuration=%" PRIu64 "µs ; mTime=%" PRIu64 \ 48 "µs ; mTimecode=%" PRIu64 "µs", \ 49 (sample)->mDuration.ToMicroseconds(), \ 50 (sample)->mTime.ToMicroseconds(), \ 51 (sample)->mTimecode.ToMicroseconds())) 52 #endif // PROCESS_DECODE_LOG 53 54 // TimeUnit is a class that represents a time value, that can be negative or 55 // positive. 56 // 57 // Internally, it works by storing a numerator (the tick numbers), that uses 58 // checked arithmetics, and a denominator (the base), that is a regular integer 59 // on which arithmetics is never performed, and is only set at construction, or 60 // replaced. 61 // 62 // Dividing the tick count by the base always yields a value in seconds, 63 // but it's very useful to have a base that is dependant on the context: it can 64 // be the sample-rate of an audio stream, the time base of an mp4, that's often 65 // 90000 because it's divisible by 24, 25 and 30. 66 // 67 // Keeping time like this allows performing calculations on time values with 68 // maximum precision, without having to have to care about rounding errors or 69 // precision loss. 70 // 71 // If not specified, the base is 1e6, representing microseconds, for historical 72 // reasons. Users can gradually move to more precise representations when 73 // needed. 74 // 75 // INT64_MAX has the special meaning of being +∞, and INT64_MIN means -∞. Any 76 // other value corresponds to a valid time. 77 // 78 // If an overflow or other problem occurs, the underlying CheckedInt<int64_t> is 79 // invalid and a crash is triggered. 80 class TimeUnit final { 81 public: 82 constexpr TimeUnit(CheckedInt64 aTicks, int64_t aBase) 83 : mTicks(aTicks), mBase(aBase) { 84 MOZ_RELEASE_ASSERT(mBase > 0); 85 // aBase is often from a uint32_t and assumed less than 2^32. 86 MOZ_DIAGNOSTIC_ASSERT(mBase <= UINT32_MAX); 87 } 88 89 explicit constexpr TimeUnit(CheckedInt64 aTicks) 90 : mTicks(aTicks), mBase(USECS_PER_S) {} 91 92 // Return the maximum number of ticks that a TimeUnit can contain. 93 static constexpr int64_t MaxTicks() { 94 return std::numeric_limits<int64_t>::max() - 1; 95 } 96 97 // These are only precise up to a point, which is aValue * aBase <= 2^53 - 1 98 static TimeUnit FromSeconds(double aValue, int64_t aBase = USECS_PER_S); 99 static TimeUnit FromSecondsWithBaseOf(double aSeconds, 100 const TimeUnit& aOtherForBase) { 101 return FromSeconds(aSeconds, aOtherForBase.mBase); 102 } 103 104 static constexpr TimeUnit FromMicroseconds(int64_t aValue) { 105 return TimeUnit(aValue, USECS_PER_S); 106 } 107 static TimeUnit FromHns(int64_t aValue, int64_t aBase) { 108 // Truncating here would mean a loss of precision. 109 return TimeUnit::FromNanoseconds(aValue * 100).ToBase<RoundPolicy>(aBase); 110 } 111 static constexpr TimeUnit FromNanoseconds(int64_t aValue) { 112 return TimeUnit(aValue, NSECS_PER_S); 113 } 114 static TimeUnit FromInfinity(); 115 static TimeUnit FromNegativeInfinity(); 116 static TimeUnit FromTimeDuration(const TimeDuration& aDuration); 117 static constexpr TimeUnit Zero(int64_t aBase = USECS_PER_S) { 118 return TimeUnit(0, aBase); 119 } 120 static constexpr TimeUnit Zero(const TimeUnit& aOther) { 121 return TimeUnit(0, aOther.mBase); 122 } 123 static TimeUnit Invalid(); 124 int64_t ToMilliseconds() const; 125 int64_t ToMicroseconds() const; 126 int64_t ToNanoseconds() const; 127 int64_t ToTicksAtRate(int64_t aRate) const; 128 // Only to be used in release assertions or unit testing, returns true if this 129 // TimeUnit has base aBase 130 bool IsBase(int64_t aBase) const; 131 double ToSeconds() const; 132 nsCString ToString() const; 133 TimeDuration ToTimeDuration() const; 134 bool IsInfinite() const; 135 bool IsPositive() const; 136 bool IsPositiveOrZero() const; 137 bool IsZero() const; 138 bool IsNegative() const; 139 140 // Returns true if the fractions are equal when converted to the smallest 141 // base. 142 bool EqualsAtLowestResolution(const TimeUnit& aOther) const; 143 // Strict equality -- the fractions must be exactly equal 144 bool operator==(const TimeUnit& aOther) const; 145 bool operator!=(const TimeUnit& aOther) const; 146 bool operator>=(const TimeUnit& aOther) const; 147 bool operator>(const TimeUnit& aOther) const; 148 bool operator<=(const TimeUnit& aOther) const; 149 bool operator<(const TimeUnit& aOther) const; 150 TimeUnit operator%(const TimeUnit& aOther) const; 151 TimeUnit operator+(const TimeUnit& aOther) const; 152 TimeUnit operator-(const TimeUnit& aOther) const; 153 TimeUnit& operator+=(const TimeUnit& aOther); 154 TimeUnit& operator-=(const TimeUnit& aOther); 155 template <typename T> 156 TimeUnit operator*(T aVal) const { 157 // See bug 853398 for the reason to block double multiplier. 158 // If required, use MultDouble below and with caution. 159 static_assert(std::is_integral_v<T>, "Must be an integral type"); 160 return TimeUnit(mTicks * aVal, mBase); 161 } 162 TimeUnit MultDouble(double aVal) const; 163 friend TimeUnit operator/(const TimeUnit& aUnit, int64_t aVal) { 164 MOZ_DIAGNOSTIC_ASSERT(0 <= aVal && aVal <= UINT32_MAX); 165 return TimeUnit(aUnit.mTicks / aVal, aUnit.mBase); 166 } 167 friend TimeUnit operator%(const TimeUnit& aUnit, int64_t aVal) { 168 MOZ_DIAGNOSTIC_ASSERT(0 <= aVal && aVal <= UINT32_MAX); 169 return TimeUnit(aUnit.mTicks % aVal, aUnit.mBase); 170 } 171 172 struct TruncatePolicy { 173 template <typename T> 174 static T policy(T& aValue) { 175 return static_cast<T>(aValue); 176 } 177 }; 178 179 struct FloorPolicy { 180 template <typename T> 181 static T policy(T& aValue) { 182 return std::floor(aValue); 183 } 184 }; 185 186 struct RoundPolicy { 187 template <typename T> 188 static T policy(T& aValue) { 189 return std::round(aValue); 190 } 191 }; 192 193 struct CeilingPolicy { 194 template <typename T> 195 static T policy(T& aValue) { 196 return std::ceil(aValue); 197 } 198 }; 199 200 template <class RoundingPolicy = TruncatePolicy> 201 TimeUnit ToBase(int64_t aTargetBase) const { 202 double dummy = 0.0; 203 return ToBase<RoundingPolicy>(aTargetBase, dummy); 204 } 205 206 template <class RoundingPolicy = TruncatePolicy> 207 TimeUnit ToBase(const TimeUnit& aTimeUnit) const { 208 double dummy = 0.0; 209 return ToBase<RoundingPolicy>(aTimeUnit, dummy); 210 } 211 212 // Allow returning the same value, in a base that matches another TimeUnit. 213 template <class RoundingPolicy = TruncatePolicy> 214 TimeUnit ToBase(const TimeUnit& aTimeUnit, double& aOutError) const { 215 int64_t targetBase = aTimeUnit.mBase; 216 return ToBase<RoundingPolicy>(targetBase, aOutError); 217 } 218 219 template <class RoundingPolicy = TruncatePolicy> 220 TimeUnit ToBase(int64_t aTargetBase, double& aOutError) const { 221 aOutError = 0.0; 222 if (mTicks.value() == INT64_MAX || mTicks.value() == INT64_MIN) { 223 // -ve or +ve infinity 224 return TimeUnit(mTicks, aTargetBase); 225 } 226 CheckedInt<int64_t> ticks = mTicks * aTargetBase; 227 if (ticks.isValid()) { 228 imaxdiv_t rv = imaxdiv(ticks.value(), mBase); 229 if (!rv.rem) { 230 return TimeUnit(rv.quot, aTargetBase); 231 } 232 } 233 double approx = static_cast<double>(mTicks.value()) * 234 static_cast<double>(aTargetBase) / 235 static_cast<double>(mBase); 236 double integer; 237 aOutError = modf(approx, &integer); 238 return TimeUnit(AssertedCast<int64_t>(RoundingPolicy::policy(approx)), 239 aTargetBase); 240 } 241 242 bool IsValid() const; 243 244 constexpr TimeUnit() = default; 245 246 TimeUnit(const TimeUnit&) = default; 247 248 TimeUnit& operator=(const TimeUnit&) = default; 249 250 bool IsPosInf() const; 251 bool IsNegInf() const; 252 253 // Allow serializing a TimeUnit via IPC 254 friend IPC::ParamTraits<mozilla::media::TimeUnit>; 255 256 #ifndef VISIBLE_TIMEUNIT_INTERNALS 257 private: 258 #endif 259 int64_t ToCommonUnit(int64_t aRatio) const; 260 // Reduce a TimeUnit to the smallest possible ticks and base. This is useful 261 // to comparison with big time values that can otherwise overflow. 262 TimeUnit Reduced() const; 263 264 CheckedInt64 mTicks{0}; 265 // Default base is microseconds. 266 int64_t mBase{USECS_PER_S}; 267 }; 268 269 using NullableTimeUnit = Maybe<TimeUnit>; 270 271 using TimeInterval = Interval<TimeUnit>; 272 273 // A set of intervals, containing TimeUnit. 274 class TimeIntervals : public IntervalSet<TimeUnit> { 275 public: 276 using BaseType = IntervalSet<TimeUnit>; 277 using InnerType = TimeUnit; 278 279 // We can't use inherited constructors yet. So we have to duplicate all the 280 // constructors found in IntervalSet base class. 281 // all this could be later replaced with: 282 // using IntervalSet<TimeUnit>::IntervalSet; 283 284 // MOZ_IMPLICIT as we want to enable initialization in the form: 285 // TimeIntervals i = ... like we would do with IntervalSet<T> i = ... 286 MOZ_IMPLICIT TimeIntervals(const BaseType& aOther) : BaseType(aOther) {} 287 MOZ_IMPLICIT TimeIntervals(BaseType&& aOther) : BaseType(std::move(aOther)) {} 288 explicit TimeIntervals(const BaseType::ElemType& aOther) : BaseType(aOther) {} 289 explicit TimeIntervals(BaseType::ElemType&& aOther) 290 : BaseType(std::move(aOther)) {} 291 292 static TimeIntervals Invalid() { 293 return TimeIntervals(TimeInterval(TimeUnit::FromNegativeInfinity(), 294 TimeUnit::FromNegativeInfinity())); 295 } 296 bool IsInvalid() const { 297 return Length() == 1 && Start(0).IsNegInf() && End(0).IsNegInf(); 298 } 299 300 // Returns the same interval, with a given base resolution. 301 TimeIntervals ToBase(const TimeUnit& aBase) const { 302 TimeIntervals output; 303 for (const auto& interval : mIntervals) { 304 TimeInterval convertedInterval{interval.mStart.ToBase(aBase), 305 interval.mEnd.ToBase(aBase), 306 interval.mFuzz.ToBase(aBase)}; 307 output += convertedInterval; 308 } 309 return output; 310 } 311 312 // Returns the same interval, with a microsecond resolution. This is used to 313 // compare TimeUnits internal to demuxers (that use a base from the container) 314 // to floating point numbers in seconds from content. 315 TimeIntervals ToMicrosecondResolution() const { 316 TimeIntervals output; 317 318 for (const auto& interval : mIntervals) { 319 TimeInterval reducedPrecision{interval.mStart.ToBase(USECS_PER_S), 320 interval.mEnd.ToBase(USECS_PER_S), 321 interval.mFuzz.ToBase(USECS_PER_S)}; 322 output += reducedPrecision; 323 } 324 return output; 325 } 326 327 nsCString ToString() const { 328 nsCString dump; 329 for (const auto& interval : mIntervals) { 330 dump += nsPrintfCString("[%s],", interval.ToString().get()); 331 } 332 return dump; 333 } 334 335 TimeIntervals() = default; 336 }; 337 338 using TimeRange = Interval<double>; 339 340 // A set of intervals, containing doubles that are seconds. 341 class TimeRanges : public IntervalSet<double> { 342 public: 343 using BaseType = IntervalSet<double>; 344 using InnerType = double; 345 using nld = std::numeric_limits<double>; 346 347 // We can't use inherited constructors yet. So we have to duplicate all the 348 // constructors found in IntervalSet base class. 349 // all this could be later replaced with: 350 // using IntervalSet<TimeUnit>::IntervalSet; 351 352 // MOZ_IMPLICIT as we want to enable initialization in the form: 353 // TimeIntervals i = ... like we would do with IntervalSet<T> i = ... 354 MOZ_IMPLICIT TimeRanges(const BaseType& aOther) : BaseType(aOther) {} 355 MOZ_IMPLICIT TimeRanges(BaseType&& aOther) : BaseType(std::move(aOther)) {} 356 explicit TimeRanges(const BaseType::ElemType& aOther) : BaseType(aOther) {} 357 explicit TimeRanges(BaseType::ElemType&& aOther) 358 : BaseType(std::move(aOther)) {} 359 360 static TimeRanges Invalid() { 361 return TimeRanges(TimeRange(-nld::infinity(), nld::infinity())); 362 } 363 bool IsInvalid() const { 364 return Length() == 1 && Start(0) == -nld::infinity() && 365 End(0) == nld::infinity(); 366 } 367 // Convert from TimeUnit-based intervals to second-based TimeRanges. 368 explicit TimeRanges(const TimeIntervals& aIntervals) { 369 for (const auto& interval : aIntervals) { 370 Add(TimeRange(interval.mStart.ToSeconds(), interval.mEnd.ToSeconds())); 371 } 372 } 373 374 TimeRanges ToMicrosecondResolution() const; 375 376 TimeRanges() = default; 377 }; 378 379 } // namespace media 380 } // namespace mozilla 381 382 #endif // TIME_UNITS_H