summaryrefslogtreecommitdiffstats
path: root/dom/media/TimeUnits.h
blob: bd8c84311d42d2c244ff2c26af0f0c74864f2784 (plain)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
/* -*- 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 <limits>
#include <type_traits>

#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<mozilla::media::TimeIntervals> {
  using Type =
      nsTArray_RelocateUsingMoveConstructor<mozilla::media::TimeIntervals>;
};

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<int64_t> 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<int64_t>::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<RoundPolicy>(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 <typename T>
  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<T>, "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 <typename T>
    static T policy(T& aValue) {
      return static_cast<T>(aValue);
    }
  };

  struct RoundPolicy {
    template <typename T>
    static T policy(T& aValue) {
      return std::round(aValue);
    }
  };

  template <class RoundingPolicy = TruncatePolicy>
  TimeUnit ToBase(int64_t aTargetBase) const {
    double dummy = 0.0;
    return ToBase<RoundingPolicy>(aTargetBase, dummy);
  }

  template <class RoundingPolicy = TruncatePolicy>
  TimeUnit ToBase(const TimeUnit& aTimeUnit) const {
    double dummy = 0.0;
    return ToBase<RoundingPolicy>(aTimeUnit, dummy);
  }

  // Allow returning the same value, in a base that matches another TimeUnit.
  template <class RoundingPolicy = TruncatePolicy>
  TimeUnit ToBase(const TimeUnit& aTimeUnit, double& aOutError) const {
    int64_t targetBase = aTimeUnit.mBase;
    return ToBase<RoundingPolicy>(targetBase, aOutError);
  }

  template <class RoundingPolicy = TruncatePolicy>
  TimeUnit ToBase(int64_t aTargetBase, double& aOutError) const {
    aOutError = 0.0;
    CheckedInt<int64_t> 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<double>(mTicks.value()) *
                    static_cast<double>(aTargetBase) /
                    static_cast<double>(mBase);
    double integer;
    aOutError = modf(approx, &integer);
    return TimeUnit(AssertedCast<int64_t>(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<mozilla::media::TimeUnit>;

#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<TimeUnit>;

using TimeInterval = Interval<TimeUnit>;

// A set of intervals, containing TimeUnit.
class TimeIntervals : public IntervalSet<TimeUnit> {
 public:
  using BaseType = IntervalSet<TimeUnit>;
  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<TimeUnit>::IntervalSet;

  // MOZ_IMPLICIT as we want to enable initialization in the form:
  // TimeIntervals i = ... like we would do with IntervalSet<T> 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<double>;

// A set of intervals, containing doubles that are seconds.
class TimeRanges : public IntervalSet<double> {
 public:
  using BaseType = IntervalSet<double>;
  using InnerType = double;
  using nld = std::numeric_limits<double>;

  // 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<TimeUnit>::IntervalSet;

  // MOZ_IMPLICIT as we want to enable initialization in the form:
  // TimeIntervals i = ... like we would do with IntervalSet<T> 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