mirror of
https://github.com/mozilla/gecko-dev.git
synced 2024-10-08 10:44:56 +00:00
5c1a697b73
Some of those are not avoidable: we use int64_t inside TimeUnit, and mp4 uses u64 as the numerator. It's not problematic in practice. Differential Revision: https://phabricator.services.mozilla.com/D176051
441 lines
14 KiB
C++
441 lines
14 KiB
C++
/* -*- 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/. */
|
|
|
|
#include <type_traits>
|
|
|
|
#include "TimeUnits.h"
|
|
#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"
|
|
#include "nsStringFwd.h"
|
|
|
|
namespace mozilla::media {
|
|
class TimeIntervals;
|
|
} // namespace mozilla::media
|
|
|
|
namespace mozilla {
|
|
|
|
namespace media {
|
|
|
|
TimeUnit TimeUnit::FromSeconds(double aValue, int64_t aBase) {
|
|
MOZ_ASSERT(!std::isnan(aValue));
|
|
MOZ_ASSERT(aBase > 0);
|
|
|
|
if (std::isinf(aValue)) {
|
|
return aValue > 0 ? FromInfinity() : FromNegativeInfinity();
|
|
}
|
|
// Warn that a particular value won't be able to be roundtrip at the same
|
|
// base -- we can keep this for some time until we're confident this is
|
|
// stable.
|
|
double inBase = aValue * static_cast<double>(aBase);
|
|
if (inBase > static_cast<double>(std::numeric_limits<int64_t>::max())) {
|
|
NS_WARNING(nsPrintfCString("Warning: base %" PRId64
|
|
" is too high to represent %lfs accurately: "
|
|
"overflows int64_t",
|
|
aBase, aValue)
|
|
.get());
|
|
MOZ_DIAGNOSTIC_ASSERT(false);
|
|
}
|
|
if (inBase > std::pow(2, std::numeric_limits<double>::digits) - 1) {
|
|
NS_WARNING(
|
|
nsPrintfCString("Warning base %" PRId64
|
|
" is too high to represent %lfs accurately: greater "
|
|
"than the max integer representable in a double.",
|
|
aBase, aValue)
|
|
.get());
|
|
MOZ_DIAGNOSTIC_ASSERT(false);
|
|
}
|
|
return TimeUnit(static_cast<int64_t>(inBase), aBase);
|
|
}
|
|
|
|
TimeUnit TimeUnit::FromInfinity() { return TimeUnit(INT64_MAX); }
|
|
|
|
TimeUnit TimeUnit::FromNegativeInfinity() { return TimeUnit(INT64_MIN); }
|
|
|
|
TimeUnit TimeUnit::FromTimeDuration(const TimeDuration& aDuration) {
|
|
// This could be made to choose the base
|
|
return TimeUnit(AssertedCast<int64_t>(aDuration.ToMicroseconds()),
|
|
USECS_PER_S);
|
|
}
|
|
|
|
TimeUnit TimeUnit::Invalid() {
|
|
TimeUnit ret;
|
|
ret.mTicks = CheckedInt64(INT64_MAX);
|
|
// Force an overflow to render the CheckedInt invalid.
|
|
ret.mTicks += 1;
|
|
return ret;
|
|
}
|
|
|
|
int64_t TimeUnit::ToMilliseconds() const { return ToCommonUnit(MSECS_PER_S); }
|
|
|
|
int64_t TimeUnit::ToMicroseconds() const { return ToCommonUnit(USECS_PER_S); }
|
|
|
|
int64_t TimeUnit::ToNanoseconds() const { return ToCommonUnit(NSECS_PER_S); }
|
|
|
|
int64_t TimeUnit::ToTicksAtRate(int64_t aRate) const {
|
|
// Common case
|
|
if (aRate == mBase) {
|
|
return mTicks.value();
|
|
}
|
|
// Approximation
|
|
return mTicks.value() * aRate / mBase;
|
|
}
|
|
|
|
double TimeUnit::ToSeconds() const {
|
|
if (IsPosInf()) {
|
|
return PositiveInfinity<double>();
|
|
}
|
|
if (IsNegInf()) {
|
|
return NegativeInfinity<double>();
|
|
}
|
|
return static_cast<double>(mTicks.value()) / static_cast<double>(mBase);
|
|
}
|
|
|
|
nsCString TimeUnit::ToString() const {
|
|
nsCString dump;
|
|
if (mTicks.isValid()) {
|
|
dump += nsPrintfCString("{%" PRId64 ",%" PRId64 "}", mTicks.value(), mBase);
|
|
} else {
|
|
dump += nsLiteralCString("{invalid}"_ns);
|
|
}
|
|
return dump;
|
|
}
|
|
|
|
TimeDuration TimeUnit::ToTimeDuration() const {
|
|
return TimeDuration::FromSeconds(ToSeconds());
|
|
}
|
|
|
|
bool TimeUnit::IsInfinite() const { return IsPosInf() || IsNegInf(); }
|
|
|
|
bool TimeUnit::IsPositive() const { return mTicks.value() > 0; }
|
|
|
|
bool TimeUnit::IsPositiveOrZero() const { return mTicks.value() >= 0; }
|
|
|
|
bool TimeUnit::IsZero() const { return mTicks.value() == 0; }
|
|
|
|
bool TimeUnit::IsNegative() const { return mTicks.value() < 0; }
|
|
|
|
// Returns true if the fractions are equal when converted to the smallest
|
|
// base.
|
|
bool TimeUnit::EqualsAtLowestResolution(const TimeUnit& aOther) const {
|
|
MOZ_ASSERT(IsValid() && aOther.IsValid());
|
|
if (aOther.mBase == mBase) {
|
|
return mTicks == aOther.mTicks;
|
|
}
|
|
if (mBase > aOther.mBase) {
|
|
TimeUnit thisInBase = ToBase(aOther.mBase);
|
|
return thisInBase.mTicks == aOther.mTicks;
|
|
}
|
|
TimeUnit otherInBase = aOther.ToBase(mBase);
|
|
return otherInBase.mTicks == mTicks;
|
|
}
|
|
|
|
// Strict equality -- the fractions must be exactly equal
|
|
bool TimeUnit::operator==(const TimeUnit& aOther) const {
|
|
MOZ_ASSERT(IsValid() && aOther.IsValid());
|
|
if (aOther.mBase == mBase) {
|
|
return mTicks == aOther.mTicks;
|
|
}
|
|
// debatable mathematically
|
|
if ((IsPosInf() && aOther.IsPosInf()) || (IsNegInf() && aOther.IsNegInf())) {
|
|
return true;
|
|
}
|
|
if ((IsPosInf() && !aOther.IsPosInf()) ||
|
|
(IsNegInf() && !aOther.IsNegInf())) {
|
|
return false;
|
|
}
|
|
CheckedInt<int64_t> lhs = mTicks * aOther.mBase;
|
|
CheckedInt<int64_t> rhs = aOther.mTicks * mBase;
|
|
if (lhs.isValid() && rhs.isValid()) {
|
|
return lhs == rhs;
|
|
}
|
|
// Reduce the fractions and try again
|
|
const TimeUnit a = Reduced();
|
|
const TimeUnit b = Reduced();
|
|
lhs = a.mTicks * b.mBase;
|
|
rhs = b.mTicks * a.mBase;
|
|
|
|
if (lhs.isValid() && rhs.isValid()) {
|
|
return lhs.value() == rhs.value();
|
|
}
|
|
// last ditch, convert the reduced fractions to doubles
|
|
double lhsFloating =
|
|
static_cast<double>(a.mTicks.value()) * static_cast<double>(a.mBase);
|
|
double rhsFloating =
|
|
static_cast<double>(b.mTicks.value()) * static_cast<double>(b.mBase);
|
|
|
|
return lhsFloating == rhsFloating;
|
|
}
|
|
bool TimeUnit::operator!=(const TimeUnit& aOther) const {
|
|
MOZ_ASSERT(IsValid() && aOther.IsValid());
|
|
return !(aOther == *this);
|
|
}
|
|
bool TimeUnit::operator>=(const TimeUnit& aOther) const {
|
|
MOZ_ASSERT(IsValid() && aOther.IsValid());
|
|
if (aOther.mBase == mBase) {
|
|
return mTicks.value() >= aOther.mTicks.value();
|
|
}
|
|
if ((!IsPosInf() && aOther.IsPosInf()) ||
|
|
(IsNegInf() && !aOther.IsNegInf())) {
|
|
return false;
|
|
}
|
|
if ((IsPosInf() && !aOther.IsPosInf()) ||
|
|
(!IsNegInf() && aOther.IsNegInf())) {
|
|
return true;
|
|
}
|
|
CheckedInt<int64_t> lhs = mTicks * aOther.mBase;
|
|
CheckedInt<int64_t> rhs = aOther.mTicks * mBase;
|
|
if (lhs.isValid() && rhs.isValid()) {
|
|
return lhs.value() >= rhs.value();
|
|
}
|
|
// Reduce the fractions and try again
|
|
const TimeUnit a = Reduced();
|
|
const TimeUnit b = aOther.Reduced();
|
|
lhs = a.mTicks * b.mBase;
|
|
rhs = b.mTicks * a.mBase;
|
|
|
|
if (lhs.isValid() && rhs.isValid()) {
|
|
return lhs.value() >= rhs.value();
|
|
}
|
|
// last ditch, convert the reduced fractions to doubles
|
|
double lhsFloating =
|
|
static_cast<double>(a.mTicks.value()) * static_cast<double>(a.mBase);
|
|
double rhsFloating =
|
|
static_cast<double>(b.mTicks.value()) * static_cast<double>(b.mBase);
|
|
|
|
return lhsFloating >= rhsFloating;
|
|
}
|
|
bool TimeUnit::operator>(const TimeUnit& aOther) const {
|
|
return !(*this <= aOther);
|
|
}
|
|
bool TimeUnit::operator<=(const TimeUnit& aOther) const {
|
|
MOZ_ASSERT(IsValid() && aOther.IsValid());
|
|
if (aOther.mBase == mBase) {
|
|
return mTicks.value() <= aOther.mTicks.value();
|
|
}
|
|
if ((!IsPosInf() && aOther.IsPosInf()) ||
|
|
(IsNegInf() && !aOther.IsNegInf())) {
|
|
return true;
|
|
}
|
|
if ((IsPosInf() && !aOther.IsPosInf()) ||
|
|
(!IsNegInf() && aOther.IsNegInf())) {
|
|
return false;
|
|
}
|
|
CheckedInt<int64_t> lhs = mTicks * aOther.mBase;
|
|
CheckedInt<int64_t> rhs = aOther.mTicks * mBase;
|
|
if (lhs.isValid() && rhs.isValid()) {
|
|
return lhs.value() <= rhs.value();
|
|
}
|
|
// Reduce the fractions and try again
|
|
const TimeUnit a = Reduced();
|
|
const TimeUnit b = aOther.Reduced();
|
|
lhs = a.mTicks * b.mBase;
|
|
rhs = b.mTicks * a.mBase;
|
|
if (lhs.isValid() && rhs.isValid()) {
|
|
return lhs.value() <= rhs.value();
|
|
}
|
|
// last ditch, convert the reduced fractions to doubles
|
|
double lhsFloating =
|
|
static_cast<double>(a.mTicks.value()) * static_cast<double>(a.mBase);
|
|
double rhsFloating =
|
|
static_cast<double>(b.mTicks.value()) * static_cast<double>(b.mBase);
|
|
|
|
return lhsFloating <= rhsFloating;
|
|
}
|
|
bool TimeUnit::operator<(const TimeUnit& aOther) const {
|
|
return !(*this >= aOther);
|
|
}
|
|
|
|
TimeUnit TimeUnit::operator%(const TimeUnit& aOther) const {
|
|
MOZ_ASSERT(IsValid() && aOther.IsValid());
|
|
if (aOther.mBase == mBase) {
|
|
return TimeUnit(mTicks % aOther.mTicks, mBase);
|
|
}
|
|
// This path can be made better if need be.
|
|
double a = ToSeconds();
|
|
double b = aOther.ToSeconds();
|
|
return TimeUnit::FromSeconds(fmod(a, b), mBase);
|
|
}
|
|
|
|
TimeUnit TimeUnit::operator+(const TimeUnit& aOther) const {
|
|
if (IsInfinite() || aOther.IsInfinite()) {
|
|
// When adding at least one infinite value, the result is either
|
|
// +/-Inf, or NaN. So do the calculation in floating point for
|
|
// simplicity.
|
|
double result = ToSeconds() + aOther.ToSeconds();
|
|
return std::isnan(result) ? TimeUnit::Invalid() : FromSeconds(result);
|
|
}
|
|
if (aOther.mBase == mBase) {
|
|
return TimeUnit(mTicks + aOther.mTicks, mBase);
|
|
}
|
|
if (aOther.IsZero()) {
|
|
return *this;
|
|
}
|
|
if (IsZero()) {
|
|
return aOther;
|
|
}
|
|
|
|
double error;
|
|
TimeUnit inBase = aOther.ToBase(mBase, error);
|
|
if (error == 0.0) {
|
|
return *this + inBase;
|
|
}
|
|
|
|
// Last ditch: not exact
|
|
double a = ToSeconds();
|
|
double b = aOther.ToSeconds();
|
|
return TimeUnit::FromSeconds(a + b, mBase);
|
|
}
|
|
|
|
TimeUnit TimeUnit::operator-(const TimeUnit& aOther) const {
|
|
if (IsInfinite() || aOther.IsInfinite()) {
|
|
// When subtracting at least one infinite value, the result is either
|
|
// +/-Inf, or NaN. So do the calculation in floating point for
|
|
// simplicity.
|
|
double result = ToSeconds() - aOther.ToSeconds();
|
|
return std::isnan(result) ? TimeUnit::Invalid() : FromSeconds(result);
|
|
}
|
|
if (aOther.mBase == mBase) {
|
|
return TimeUnit(mTicks - aOther.mTicks, mBase);
|
|
}
|
|
if (aOther.IsZero()) {
|
|
return *this;
|
|
}
|
|
|
|
if (IsZero()) {
|
|
return TimeUnit(-aOther.mTicks, aOther.mBase);
|
|
}
|
|
|
|
double error = 0.0;
|
|
TimeUnit inBase = aOther.ToBase(mBase, error);
|
|
if (error == 0) {
|
|
return *this - inBase;
|
|
}
|
|
|
|
// Last ditch: not exact
|
|
double a = ToSeconds();
|
|
double b = aOther.ToSeconds();
|
|
return TimeUnit::FromSeconds(a - b, mBase);
|
|
}
|
|
TimeUnit& TimeUnit::operator+=(const TimeUnit& aOther) {
|
|
if (aOther.mBase == mBase) {
|
|
mTicks += aOther.mTicks;
|
|
return *this;
|
|
}
|
|
*this = *this + aOther;
|
|
return *this;
|
|
}
|
|
TimeUnit& TimeUnit::operator-=(const TimeUnit& aOther) {
|
|
if (aOther.mBase == mBase) {
|
|
mTicks -= aOther.mTicks;
|
|
return *this;
|
|
}
|
|
*this = *this - aOther;
|
|
return *this;
|
|
}
|
|
|
|
TimeUnit TimeUnit::MultDouble(double aVal) const {
|
|
double multiplied = AssertedCast<double>(mTicks.value()) * aVal;
|
|
// Check is the result of the multiplication can be represented exactly as
|
|
// an integer, in a double.
|
|
if (multiplied > std::pow(2, std::numeric_limits<double>::digits) - 1) {
|
|
printf_stderr("TimeUnit tick count after multiplication %" PRId64
|
|
" * %lf is too"
|
|
" high for the result to be exact",
|
|
mTicks.value(), aVal);
|
|
MOZ_CRASH();
|
|
}
|
|
// static_cast is ok, the magnitude of the number has been checked just above.
|
|
return TimeUnit(static_cast<int64_t>(multiplied), mBase);
|
|
}
|
|
TimeUnit TimeUnit::ToBase(int64_t aTargetBase) const {
|
|
double dummy = 0.0;
|
|
return ToBase(aTargetBase, dummy);
|
|
}
|
|
|
|
TimeUnit 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.
|
|
TimeUnit TimeUnit::ToBase(const TimeUnit& aTimeUnit, double& aOutError) const {
|
|
int64_t targetBase = aTimeUnit.mBase;
|
|
return ToBase(targetBase, aOutError);
|
|
}
|
|
|
|
TimeUnit 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>(approx), aTargetBase);
|
|
}
|
|
|
|
bool TimeUnit::IsValid() const { return mTicks.isValid(); }
|
|
|
|
bool TimeUnit::IsPosInf() const {
|
|
return mTicks.isValid() && mTicks.value() == INT64_MAX;
|
|
}
|
|
bool TimeUnit::IsNegInf() const {
|
|
return mTicks.isValid() && mTicks.value() == INT64_MIN;
|
|
}
|
|
|
|
int64_t TimeUnit::ToCommonUnit(int64_t aRatio) const {
|
|
CheckedInt<int64_t> rv = mTicks;
|
|
// Avoid the risk overflowing in common cases, e.g. converting a TimeUnit
|
|
// with a base of 1e9 back to nanoseconds.
|
|
if (mBase == aRatio) {
|
|
return rv.value();
|
|
}
|
|
// Avoid overflowing in other common cases, e.g. converting a TimeUnit with
|
|
// a base of 1e9 to microseconds: the denominator is divisible by the target
|
|
// unit so we can reorder the computation and keep the number within int64_t
|
|
// range.
|
|
if (aRatio < mBase && (mBase % aRatio) == 0) {
|
|
int64_t exactDivisor = mBase / aRatio;
|
|
rv /= exactDivisor;
|
|
return rv.value();
|
|
}
|
|
rv *= aRatio;
|
|
rv /= mBase;
|
|
if (rv.isValid()) {
|
|
return rv.value();
|
|
}
|
|
// Last ditch, perform the computation in floating point.
|
|
double ratioFloating = AssertedCast<double>(aRatio);
|
|
double baseFloating = AssertedCast<double>(mBase);
|
|
double ticksFloating = static_cast<double>(mTicks.value());
|
|
double approx = ticksFloating * (ratioFloating / baseFloating);
|
|
return AssertedCast<int64_t>(approx);
|
|
}
|
|
|
|
// Reduce a TimeUnit to the smallest possible ticks and base. This is useful
|
|
// to comparison with big time values that can otherwise overflow.
|
|
TimeUnit TimeUnit::Reduced() const {
|
|
int64_t gcd = GCD(mTicks.value(), mBase);
|
|
return TimeUnit(mTicks.value() / gcd, mBase / gcd);
|
|
}
|
|
|
|
CheckedInt64 mTicks{0};
|
|
// Default base is microseconds.
|
|
int64_t mBase{USECS_PER_S};
|
|
}; // namespace media
|
|
|
|
} // namespace mozilla
|