From 40e5cc784519762217410e86c740d546ba824cf5 Mon Sep 17 00:00:00 2001 From: Jean-Yves Avenard Date: Thu, 7 May 2015 10:24:10 +1000 Subject: [PATCH] Bug 1159579: Add Interval and IntervalSet objects. r=mattwoodrow Along with a TimeIntervals class that reimplement all of dom::TimeRanges features. --- dom/media/Intervals.h | 525 ++++++++++++++++++++++++ dom/media/TimeUnits.h | 76 ++++ dom/media/gtest/TestIntervalSet.cpp | 591 ++++++++++++++++++++++++++++ dom/media/gtest/moz.build | 1 + dom/media/moz.build | 1 + 5 files changed, 1194 insertions(+) create mode 100644 dom/media/Intervals.h create mode 100644 dom/media/gtest/TestIntervalSet.cpp diff --git a/dom/media/Intervals.h b/dom/media/Intervals.h new file mode 100644 index 000000000000..a589ad51beaf --- /dev/null +++ b/dom/media/Intervals.h @@ -0,0 +1,525 @@ +/* -*- 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 INTERVALS_H +#define INTERVALS_H + +#include +#include "mozilla/TypeTraits.h" +#include "nsTArray.h" + +namespace mozilla { +namespace media { + +/* Interval defines an interval between two points. Unlike a traditional + interval [A,B] where A <= x <= B, the upper boundary B is exclusive: A <= x < B + (e.g [A,B[ or [A,B) depending on where you're living) + It provides basic interval arithmetic and fuzzy edges. + The type T must provides a default constructor and +, -, <, <= and == + operators. + */ +template +class Interval +{ +public: + typedef Interval SelfType; + + Interval() + : mStart(T()) + , mEnd(T()) + , mFuzz(T()) + {} + + template + Interval(StartArg&& aStart, EndArg&& aEnd) + : mStart(Forward(aStart)) + , mEnd(Forward(aEnd)) + , mFuzz() + { + MOZ_ASSERT(aStart <= aEnd); + } + + template + Interval(StartArg&& aStart, EndArg&& aEnd, FuzzArg&& aFuzz) + : mStart(Forward(aStart)) + , mEnd(Forward(aEnd)) + , mFuzz(Forward(aFuzz)) + { + MOZ_ASSERT(aStart <= aEnd); + } + + Interval(const SelfType& aOther) + : mStart(aOther.mStart) + , mEnd(aOther.mEnd) + , mFuzz(aOther.mFuzz) + {} + + Interval(SelfType&& aOther) + : mStart(Move(aOther.mStart)) + , mEnd(Move(aOther.mEnd)) + , mFuzz(Move(aOther.mFuzz)) + { } + + SelfType& operator= (const SelfType& aOther) + { + mStart = aOther.mStart; + mEnd = aOther.mEnd; + mFuzz = aOther.mFuzz; + return *this; + } + + SelfType& operator= (SelfType&& aOther) + { + MOZ_ASSERT(&aOther != this, "self-moves are prohibited"); + this->~Interval(); + new(this) Interval(Move(aOther)); + return *this; + } + + // Basic interval arithmetic operator definition. + SelfType operator+ (const SelfType& aOther) const + { + return SelfType(mStart + aOther.mStart, + mEnd + aOther.mEnd, + mFuzz + aOther.mFuzz); + } + + // Basic interval arithmetic operator definition. + SelfType operator- (const SelfType& aOther) const + { + return SelfType(mStart - aOther.mEnd, + mEnd - aOther.mStart, + mFuzz + aOther.mFuzz); + } + + bool operator== (const SelfType& aOther) const + { + return mStart == aOther.mStart && mEnd == aOther.mEnd; + } + + bool operator!= (const SelfType& aOther) const + { + return !(*this == aOther); + } + + bool Contains(const T& aX) const + { + return mStart - mFuzz <= aX && aX < mEnd + mFuzz; + } + + bool ContainsStrict(const T& aX) const + { + return mStart <= aX && aX < mEnd; + } + + bool Contains(const SelfType& aOther) const + { + return (mStart - mFuzz <= aOther.mStart + aOther.mFuzz) && + (aOther.mEnd + aOther.mFuzz <= mEnd - mFuzz); + } + + bool ContainsStrict(const SelfType& aOther) const + { + return mStart <= aOther.mStart && aOther.mEnd <= mEnd; + } + + bool Intersects(const SelfType& aOther) const + { + return (mStart - mFuzz <= aOther.mEnd + aOther.mFuzz) && + (aOther.mStart - aOther.mFuzz <= mEnd + mFuzz); + } + + // Returns true if aOther is strictly to the right of this and contiguous. + // This operation isn't commutative. + bool Contiguous(const SelfType& aOther) const + { + return mEnd <= aOther.mStart && aOther.mStart - mEnd <= mFuzz + aOther.mFuzz; + } + + SelfType Union(const SelfType& aOther) const + { + SelfType result(*this); + if (aOther.mStart < mStart) { + result.mStart = aOther.mStart; + } + if (mEnd < aOther.mEnd) { + result.mEnd = aOther.mEnd; + } + if (mFuzz < aOther.mFuzz) { + result.mFuzz = aOther.mFuzz; + } + return result; + } + + SelfType Intersection(const SelfType& aOther) const + { + const T& s = std::max(mStart, aOther.mStart); + const T& e = std::min(mEnd, aOther.mEnd); + const T& f = std::max(mFuzz, aOther.mFuzz); + if (s < e) { + return SelfType(s, e, f); + } + // Return an empty interval. + return SelfType(); + } + + T Length() const + { + return mEnd - mStart; + } + + bool IsEmpty() const + { + return mStart == mEnd; + } + + T mStart; + T mEnd; + T mFuzz; + +private: +}; + +template +class IntervalSet +{ +public: + typedef IntervalSet SelfType; + typedef Interval ElemType; + typedef nsAutoTArray ContainerType; + typedef typename ContainerType::index_type IndexType; + + IntervalSet() + { + } + ~IntervalSet() + { + } + + IntervalSet(const SelfType& aOther) + : mIntervals(aOther.mIntervals) + { + } + + IntervalSet(SelfType&& aOther) + : mIntervals(Move(aOther.mIntervals)) + { + } + + explicit IntervalSet(const ElemType& aOther) + { + mIntervals.AppendElement(aOther); + } + + explicit IntervalSet(ElemType&& aOther) + { + mIntervals.AppendElement(Move(aOther)); + } + + SelfType& operator= (const SelfType& aOther) + { + mIntervals = aOther.mIntervals; + return *this; + } + + SelfType& operator= (SelfType&& aOther) + { + MOZ_ASSERT(&aOther != this, "self-moves are prohibited"); + this->~IntervalSet(); + new(this) IntervalSet(Move(aOther)); + return *this; + } + + SelfType& operator= (const ElemType& aInterval) + { + mIntervals.Clear(); + mIntervals.AppendElement(aInterval); + return *this; + } + + SelfType& operator= (ElemType&& aInterval) + { + mIntervals.Clear(); + mIntervals.AppendElement(Move(aInterval)); + return *this; + } + + // + and += operator will append the provided interval or intervalset. + // Note that the result is not normalized. Call Normalize() as required. + // Alternatively, use Union() + + SelfType& Add(const SelfType& aIntervals) + { + mIntervals.AppendElements(aIntervals.mIntervals); + return *this; + } + + SelfType& Add(const ElemType& aInterval) + { + mIntervals.AppendElement(aInterval); + return *this; + } + + SelfType& operator+= (const SelfType& aIntervals) + { + Add(aIntervals); + return *this; + } + + SelfType& operator+= (const ElemType& aInterval) + { + Add(aInterval); + return *this; + } + + SelfType operator+ (const SelfType& aIntervals) const + { + SelfType intervals(*this); + intervals.Add(aIntervals); + return intervals; + } + + SelfType operator+ (const ElemType& aInterval) + { + SelfType intervals(*this); + intervals.Add(aInterval); + return intervals; + } + + friend SelfType operator+ (const ElemType& aInterval, + const SelfType& aIntervals) + { + SelfType intervals; + intervals.Add(aInterval); + intervals.Add(aIntervals); + return intervals; + } + + // Mutate this IntervalSet to be the union of this and aOther. + // Resulting IntervalSet is normalized. + SelfType& Union(const SelfType& aOther) + { + Add(aOther); + Normalize(); + return *this; + } + + SelfType& Union(const ElemType& aInterval) + { + Add(aInterval); + Normalize(); + return *this; + } + + // Mutate this TimeRange to be the intersection of this and aOther. + SelfType& Intersection(const SelfType& aOther) + { + ContainerType intersection; + + const ContainerType& other = aOther.mIntervals; + IndexType i = 0, j = 0; + for (; i < mIntervals.Length() && j < other.Length();) { + if (mIntervals[i].Intersects(other[j])) { + intersection.AppendElement(mIntervals[i].Intersection(other[j])); + } + if (mIntervals[i].mEnd < other[j].mEnd) { + i++; + } else { + j++; + } + } + + mIntervals = intersection; + return *this; + } + + SelfType& Intersection(const ElemType& aInterval) + { + SelfType intervals(aInterval); + return Intersection(intervals); + } + + const ElemType& operator[] (IndexType aIndex) const + { + return mIntervals[aIndex]; + } + + // Returns the start boundary of the first interval. Or a default constructed + // T if IntervalSet is empty (and aExists if provided will be set to false). + T GetStart(bool* aExists = nullptr) const + { + bool exists = !mIntervals.IsEmpty(); + + if (aExists) { + *aExists = exists; + } + + if (exists) { + return mIntervals[0].mStart; + } else { + return T(); + } + } + + // Returns the end boundary of the last interval. Or a default constructed T + // if IntervalSet is empty (and aExists if provided will be set to false). + T GetEnd(bool* aExists = nullptr) const + { + bool exists = !mIntervals.IsEmpty(); + if (aExists) { + *aExists = exists; + } + + if (exists) { + return mIntervals.LastElement().mEnd; + } else { + return T(); + } + } + + IndexType Length() const + { + return mIntervals.Length(); + } + + T Start(IndexType aIndex) const + { + return mIntervals[aIndex].mStart; + } + + T Start(IndexType aIndex, bool& aExists) const + { + aExists = aIndex < mIntervals.Length(); + + if (aExists) { + return mIntervals[aIndex].mStart; + } else { + return T(); + } + } + + T End(IndexType aIndex) const + { + return mIntervals[aIndex].mEnd; + } + + T End(IndexType aIndex, bool& aExists) const + { + aExists = aIndex < mIntervals.Length(); + + if (aExists) { + return mIntervals[aIndex].mEnd; + } else { + return T(); + } + } + + bool Contains(const T& aX) { + for (const auto& interval : mIntervals) { + if (interval.Contains(aX)) { + return true; + } + } + return false; + } + + bool ContainsStrict(const T& aX) { + for (const auto& interval : mIntervals) { + if (interval.ContainsStrict(aX)) { + return true; + } + } + return false; + } + + void Normalize() + { + if (mIntervals.Length() >= 2) { + ContainerType normalized; + + mIntervals.Sort(CompareIntervals()); + + // This merges the intervals. + ElemType current(mIntervals[0]); + for (IndexType i = 1; i < mIntervals.Length(); i++) { + if (current.Contains(mIntervals[i])) { + continue; + } + if (current.Intersects(mIntervals[i])) { + current = current.Union(mIntervals[i]); + } else { + normalized.AppendElement(current); + current = mIntervals[i]; + } + } + + normalized.AppendElement(current); + + mIntervals = normalized; + } + } + + // Shift all values by aOffset. + void Shift(T aOffset) + { + for (auto& interval : mIntervals) { + interval.mStart += aOffset; + interval.mEnd += aOffset; + } + } + + static const IndexType NoIndex = IndexType(-1); + + IndexType Find(T aValue) const + { + for (IndexType i = 0; i < mIntervals.Length(); i++) { + if (mIntervals[i].Contains(aValue)) { + return i; + } + } + return NoIndex; + } + +protected: + ContainerType mIntervals; + +private: + struct CompareIntervals + { + bool Equals(const ElemType& aT1, const ElemType& aT2) const + { + return aT1.mStart == aT2.mStart && aT1.mEnd == aT2.mEnd; + } + + bool LessThan(const ElemType& aT1, const ElemType& aT2) const { + return aT1.mStart - aT1.mFuzz < aT2.mStart + aT2.mFuzz; + } + }; +}; + + // clang doesn't allow for this to be defined inline of IntervalSet. +template +IntervalSet Union(const IntervalSet& aIntervals1, + const IntervalSet& aIntervals2) +{ + IntervalSet intervals(aIntervals1); + intervals.Union(aIntervals2); + return intervals; +} + +template +IntervalSet Intersection(const IntervalSet& aIntervals1, + const IntervalSet& aIntervals2) +{ + IntervalSet intersection(aIntervals1); + intersection.Intersection(aIntervals2); + return intersection; +} + +} // namespace media +} // namespace mozilla + +#endif // INTERVALS_H diff --git a/dom/media/TimeUnits.h b/dom/media/TimeUnits.h index 1af4be2033aa..0380b7dc0881 100644 --- a/dom/media/TimeUnits.h +++ b/dom/media/TimeUnits.h @@ -7,9 +7,11 @@ #ifndef TIME_UNITS_H #define TIME_UNITS_H +#include "Intervals.h" #include "VideoUtils.h" #include "mozilla/CheckedInt.h" #include "mozilla/FloatingPoint.h" +#include "mozilla/dom/TimeRanges.h" namespace mozilla { namespace media { @@ -40,6 +42,9 @@ struct Microseconds { } } + bool operator == (const Microseconds& aOther) const { + return mValue == aOther.mValue; + } bool operator > (const Microseconds& aOther) const { return mValue > aOther.mValue; } @@ -87,6 +92,10 @@ public: return double(mValue.value()) / USECS_PER_S; } + bool operator == (const TimeUnit& aOther) const { + MOZ_ASSERT(IsValid() && aOther.IsValid()); + return mValue.value() == aOther.mValue.value(); + } bool operator >= (const TimeUnit& aOther) const { MOZ_ASSERT(IsValid() && aOther.IsValid()); return mValue.value() >= aOther.mValue.value(); @@ -113,9 +122,18 @@ public: return mValue.isValid(); } + TimeUnit() + : mValue(CheckedInt64(0)) + {} + explicit TimeUnit(const Microseconds& aMicroseconds) : mValue(aMicroseconds.mValue) {} + TimeUnit& operator = (const Microseconds& aMicroseconds) + { + mValue = aMicroseconds.mValue; + return *this; + } TimeUnit(const TimeUnit&) = default; @@ -130,6 +148,64 @@ private: CheckedInt64 mValue; }; +typedef Interval TimeInterval; + +class TimeIntervals : public IntervalSet +{ +public: + typedef IntervalSet BaseType; + + // 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(Move(aOther)) + {} + explicit TimeIntervals(const BaseType::ElemType& aOther) + : BaseType(aOther) + {} + explicit TimeIntervals(BaseType::ElemType&& aOther) + : BaseType(Move(aOther)) + {} + + TimeIntervals() = default; + + // Make TimeIntervals interchangeable with dom::TimeRanges. + explicit TimeIntervals(dom::TimeRanges* aRanges) + { + for (uint32_t i = 0; i < aRanges->Length(); i++) { + ErrorResult rv; + *this += + TimeInterval(TimeUnit::FromSeconds(aRanges->Start(i, rv)), + TimeUnit::FromSeconds(aRanges->End(i, rv))); + } + } + TimeIntervals& operator = (dom::TimeRanges* aRanges) + { + *this = TimeIntervals(aRanges); + return *this; + } + + static TimeIntervals FromTimeRanges(dom::TimeRanges* aRanges) + { + return TimeIntervals(aRanges); + } + + void ToTimeRanges(dom::TimeRanges* aRanges) const + { + for (IndexType i = 0; i < Length(); i++) { + aRanges->Add(Start(i).ToSeconds(), End(i).ToSeconds()); + } + } +}; + } // namespace media } // namespace mozilla diff --git a/dom/media/gtest/TestIntervalSet.cpp b/dom/media/gtest/TestIntervalSet.cpp new file mode 100644 index 000000000000..db6ac3607789 --- /dev/null +++ b/dom/media/gtest/TestIntervalSet.cpp @@ -0,0 +1,591 @@ +/* -*- Mode: C++; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*- */ +/* 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 "gtest/gtest.h" +#include "mozilla/dom/TimeRanges.h" +#include "TimeUnits.h" +#include "Intervals.h" +#include +#include + +using namespace mozilla; + +typedef media::Interval ByteInterval; +typedef media::Interval IntInterval; + +ByteInterval CreateByteInterval(int32_t aStart, int32_t aEnd) +{ + ByteInterval test(aStart, aEnd); + return test; +} + +media::IntervalSet CreateByteIntervalSet(int32_t aStart, int32_t aEnd) +{ + media::IntervalSet test; + test += ByteInterval(aStart, aEnd); + return test; +} + +TEST(IntervalSet, Constructors) +{ + const int32_t start = 1; + const int32_t end = 2; + const int32_t fuzz = 0; + + // Compiler exercise. + ByteInterval test1(start, end); + ByteInterval test2(test1); + ByteInterval test3(start, end, fuzz); + ByteInterval test4(test3); + ByteInterval test5 = CreateByteInterval(start, end); + + media::IntervalSet blah1(test1); + media::IntervalSet blah2 = blah1; + media::IntervalSet blah3 = blah1 + test1; + media::IntervalSet blah4 = test1 + blah1; + media::IntervalSet blah5 = CreateByteIntervalSet(start, end); + (void)test1; (void)test2; (void)test3; (void)test4; (void)test5; + (void)blah1; (void)blah2; (void)blah3; (void)blah4; (void)blah5; +} + +media::TimeInterval CreateTimeInterval(int32_t aStart, int32_t aEnd) +{ + // Copy constructor test + media::Microseconds startus(aStart); + media::TimeUnit start(startus); + media::TimeUnit end; + // operator= test + end = media::Microseconds(aEnd); + media::TimeInterval ti(start, end); + return ti; +} + +media::TimeIntervals CreateTimeIntervals(int32_t aStart, int32_t aEnd) +{ + media::TimeIntervals test; + test += CreateTimeInterval(aStart, aEnd); + return test; +} + +TEST(IntervalSet, TimeIntervalsConstructors) +{ + const media::Microseconds start(1); + const media::Microseconds end(2); + const media::Microseconds fuzz; + + // Compiler exercise. + media::TimeInterval test1(start, end); + media::TimeInterval test2(test1); + media::TimeInterval test3(start, end, fuzz); + media::TimeInterval test4(test3); + media::TimeInterval test5 = CreateTimeInterval(start.mValue, end.mValue); + + media::TimeIntervals blah1(test1); + media::TimeIntervals blah2(blah1); + media::TimeIntervals blah3 = blah1 + test1; + media::TimeIntervals blah4 = test1 + blah1; + media::TimeIntervals blah5 = CreateTimeIntervals(start.mValue, end.mValue); + (void)test1; (void)test2; (void)test3; (void)test4; (void)test5; + (void)blah1; (void)blah2; (void)blah3; (void)blah4; (void)blah5; +} + +TEST(IntervalSet, Length) +{ + IntInterval i(15, 25); + EXPECT_EQ(10, i.Length()); +} + +TEST(IntervalSet, Intersection) +{ + IntInterval i0(10, 20); + IntInterval i1(15, 25); + IntInterval i = i0.Intersection(i1); + EXPECT_EQ(15, i.mStart); + EXPECT_EQ(20, i.mEnd); +} + +TEST(IntervalSet, Equals) +{ + IntInterval i0(10, 20); + IntInterval i1(10, 20); + EXPECT_EQ(i0, i1); + + IntInterval i2(5, 20); + EXPECT_NE(i0, i2); + + IntInterval i3(10, 15); + EXPECT_NE(i0, i2); +} + +TEST(IntervalSet, IntersectionIntervalSet) +{ + media::IntervalSet i0; + i0 += IntInterval(5, 10); + i0 += IntInterval(20, 25); + i0 += IntInterval(40, 60); + + media::IntervalSet i1; + i1.Add(IntInterval(7, 15)); + i1.Add(IntInterval(16, 27)); + i1.Add(IntInterval(45, 50)); + i1.Add(IntInterval(53, 57)); + + media::IntervalSet i = media::Intersection(i0, i1); + + EXPECT_EQ(4u, i.Length()); + + EXPECT_EQ(7, i[0].mStart); + EXPECT_EQ(10, i[0].mEnd); + + EXPECT_EQ(20, i[1].mStart); + EXPECT_EQ(25, i[1].mEnd); + + EXPECT_EQ(45, i[2].mStart); + EXPECT_EQ(50, i[2].mEnd); + + EXPECT_EQ(53, i[3].mStart); + EXPECT_EQ(57, i[3].mEnd); +} + +template +static void Compare(media::IntervalSet aI1, media::IntervalSet aI2) +{ + media::IntervalSet i1(aI1); + media::IntervalSet i2(aI1); + EXPECT_EQ(i1.Length(), i2.Length()); + if (i1.Length() != i2.Length()) { + return; + } + for (uint32_t i = 0; i < i1.Length(); i++) { + EXPECT_EQ(i1[i].mStart, i2[i].mStart); + EXPECT_EQ(i1[i].mEnd, i2[i].mEnd); + } +} + +TEST(IntervalSet, IntersectionNormalizedIntervalSet) +{ + media::IntervalSet i0; + i0 += IntInterval(5, 10); + i0 += IntInterval(8, 25); + i0 += IntInterval(24, 60); + + media::IntervalSet i1; + i1.Add(IntInterval(7, 15)); + i1.Add(IntInterval(10, 27)); + i1.Add(IntInterval(45, 50)); + i1.Add(IntInterval(53, 57)); + + // Compare intersections to ensure an intersection of normalized intervalsets + // is equal to the intersection of non-normalized intervalsets. + media::IntervalSet intersection = media::Intersection(i0, i1); + + media::IntervalSet i0_normalize(i0); + i0_normalize.Normalize(); + media::IntervalSet i1_normalize(i1); + i1_normalize.Normalize(); + media::IntervalSet intersection_normalize = + media::Intersection(i0_normalize, i1_normalize); + Compare(intersection, intersection_normalize); +} + +static void GeneratePermutations(media::IntervalSet aI1, + media::IntervalSet aI2) +{ + media::IntervalSet i_ref = media::Intersection(aI1, aI2); + // Test all permutations possible + std::vector comb1; + for (uint32_t i = 0; i < aI1.Length(); i++) { + comb1.push_back(i); + } + std::vector comb2; + for (uint32_t i = 0; i < aI2.Length(); i++) { + comb2.push_back(i); + } + + do { + do { + // Create intervals according to new indexes. + media::IntervalSet i_0; + for (uint32_t i = 0; i < comb1.size(); i++) { + i_0 += aI1[comb1[i]]; + } + media::IntervalSet i_1; + for (uint32_t i = 0; i < comb2.size(); i++) { + i_1 += aI2[comb2[i]]; + } + // Check intersections yield the same result. + Compare(i_0.Intersection(i_1), i_ref); + } while (std::next_permutation(comb2.begin(), comb2.end())); + } while (std::next_permutation(comb1.begin(), comb1.end())); +} + +TEST(IntervalSet, IntersectionUnorderedIntervalSet) +{ + media::IntervalSet i0; + i0 += IntInterval(5, 10); + i0 += IntInterval(20, 25); + i0 += IntInterval(40, 60); + + media::IntervalSet i1; + i1.Add(IntInterval(7, 15)); + i1.Add(IntInterval(16, 27)); + i1.Add(IntInterval(45, 50)); + i1.Add(IntInterval(53, 57)); + + GeneratePermutations(i0, i1); +} + +TEST(IntervalSet, IntersectionUnorderedNonNormalizedIntervalSet) +{ + media::IntervalSet i0; + i0 += IntInterval(5, 10); + i0 += IntInterval(8, 25); + i0 += IntInterval(24, 60); + + media::IntervalSet i1; + i1.Add(IntInterval(7, 15)); + i1.Add(IntInterval(10, 27)); + i1.Add(IntInterval(45, 50)); + i1.Add(IntInterval(53, 57)); + + GeneratePermutations(i0, i1); +} + +static media::IntervalSet Duplicate(const media::IntervalSet& aValue) +{ + media::IntervalSet value(aValue); + return value; +} + +TEST(IntervalSet, Normalize) +{ + media::IntervalSet i; + // Test IntervalSet + Interval operator. + i = i + IntInterval(20, 30); + // Test Internal + IntervalSet operator. + i = IntInterval(2, 7) + i; + // Test Interval + IntervalSet operator + i = IntInterval(1, 8) + i; + media::IntervalSet interval; + interval += IntInterval(5, 10); + // Test += with move. + i += Duplicate(interval); + // Test = with move and add with move. + i = Duplicate(interval) + i; + + media::IntervalSet o(i); + o.Normalize(); + + EXPECT_EQ(2u, o.Length()); + + EXPECT_EQ(1, o[0].mStart); + EXPECT_EQ(10, o[0].mEnd); + + EXPECT_EQ(20, o[1].mStart); + EXPECT_EQ(30, o[1].mEnd); +} + +TEST(IntervalSet, Union) +{ + media::IntervalSet i0; + i0 += IntInterval(5, 10); + i0 += IntInterval(20, 25); + i0 += IntInterval(40, 60); + + media::IntervalSet i1; + i1.Add(IntInterval(7, 15)); + i1.Add(IntInterval(16, 27)); + i1.Add(IntInterval(45, 50)); + i1.Add(IntInterval(53, 57)); + + media::IntervalSet i = media::Union(i0, i1); + + EXPECT_EQ(3u, i.Length()); + + EXPECT_EQ(5, i[0].mStart); + EXPECT_EQ(15, i[0].mEnd); + + EXPECT_EQ(16, i[1].mStart); + EXPECT_EQ(27, i[1].mEnd); + + EXPECT_EQ(40, i[2].mStart); + EXPECT_EQ(60, i[2].mEnd); +} + +TEST(IntervalSet, UnionNotOrdered) +{ + media::IntervalSet i0; + i0 += IntInterval(20, 25); + i0 += IntInterval(40, 60); + i0 += IntInterval(5, 10); + + media::IntervalSet i1; + i1.Add(IntInterval(16, 27)); + i1.Add(IntInterval(7, 15)); + i1.Add(IntInterval(53, 57)); + i1.Add(IntInterval(45, 50)); + + media::IntervalSet i = media::Union(i0, i1); + + EXPECT_EQ(3u, i.Length()); + + EXPECT_EQ(5, i[0].mStart); + EXPECT_EQ(15, i[0].mEnd); + + EXPECT_EQ(16, i[1].mStart); + EXPECT_EQ(27, i[1].mEnd); + + EXPECT_EQ(40, i[2].mStart); + EXPECT_EQ(60, i[2].mEnd); +} + +TEST(IntervalSet, NormalizeFuzz) +{ + media::IntervalSet i0; + i0 += IntInterval(11, 25, 0); + i0 += IntInterval(5, 10, 1); + i0 += IntInterval(40, 60, 1); + i0.Normalize(); + + EXPECT_EQ(2u, i0.Length()); + + EXPECT_EQ(5, i0[0].mStart); + EXPECT_EQ(25, i0[0].mEnd); + + EXPECT_EQ(40, i0[1].mStart); + EXPECT_EQ(60, i0[1].mEnd); +} + +TEST(IntervalSet, UnionFuzz) +{ + media::IntervalSet i0; + i0 += IntInterval(5, 10, 1); + i0 += IntInterval(11, 25, 0); + i0 += IntInterval(40, 60, 1); + + media::IntervalSet i1; + i1.Add(IntInterval(7, 15, 1)); + i1.Add(IntInterval(16, 27, 1)); + i1.Add(IntInterval(45, 50, 1)); + i1.Add(IntInterval(53, 57, 1)); + + media::IntervalSet i = media::Union(i0, i1); + + EXPECT_EQ(2u, i.Length()); + + EXPECT_EQ(5, i[0].mStart); + EXPECT_EQ(27, i[0].mEnd); + + EXPECT_EQ(40, i[1].mStart); + EXPECT_EQ(60, i[1].mEnd); + + i0.Normalize(); + EXPECT_EQ(2u, i0.Length()); + EXPECT_EQ(5, i0[0].mStart); + EXPECT_EQ(25, i0[0].mEnd); + EXPECT_EQ(40, i0[1].mStart); + EXPECT_EQ(60, i0[1].mEnd); +} + +TEST(IntervalSet, Contiguous) +{ + EXPECT_FALSE(IntInterval(5, 10).Contiguous(IntInterval(11, 25))); + EXPECT_TRUE(IntInterval(5, 10).Contiguous(IntInterval(10, 25))); + EXPECT_TRUE(IntInterval(5, 10, 1).Contiguous(IntInterval(11, 25))); + EXPECT_TRUE(IntInterval(5, 10).Contiguous(IntInterval(11, 25, 1))); +} + +TEST(IntervalSet, TimeRangesSeconds) +{ + media::TimeIntervals i0; + i0 += media::TimeInterval(media::TimeUnit::FromSeconds(20), media::TimeUnit::FromSeconds(25)); + i0 += media::TimeInterval(media::TimeUnit::FromSeconds(40), media::TimeUnit::FromSeconds(60)); + i0 += media::TimeInterval(media::TimeUnit::FromSeconds(5), media::TimeUnit::FromSeconds(10)); + + media::TimeIntervals i1; + i1.Add(media::TimeInterval(media::TimeUnit::FromSeconds(16), media::TimeUnit::FromSeconds(27))); + i1.Add(media::TimeInterval(media::TimeUnit::FromSeconds(7), media::TimeUnit::FromSeconds(15))); + i1.Add(media::TimeInterval(media::TimeUnit::FromSeconds(53), media::TimeUnit::FromSeconds(57))); + i1.Add(media::TimeInterval(media::TimeUnit::FromSeconds(45), media::TimeUnit::FromSeconds(50))); + + media::TimeIntervals i(i0 + i1); + nsRefPtr tr = new dom::TimeRanges(); + i.ToTimeRanges(tr); + EXPECT_EQ(tr->Length(), i.Length()); + for (dom::TimeRanges::index_type index = 0; index < tr->Length(); index++) { + ErrorResult rv; + EXPECT_EQ(tr->Start(index, rv), i[index].mStart.ToSeconds()); + EXPECT_EQ(tr->Start(index, rv), i.Start(index).ToSeconds()); + EXPECT_EQ(tr->End(index, rv), i[index].mEnd.ToSeconds()); + EXPECT_EQ(tr->End(index, rv), i.End(index).ToSeconds()); + } + + i.Normalize(); + tr->Normalize(); + EXPECT_EQ(tr->Length(), i.Length()); + for (dom::TimeRanges::index_type index = 0; index < tr->Length(); index++) { + ErrorResult rv; + EXPECT_EQ(tr->Start(index, rv), i[index].mStart.ToSeconds()); + EXPECT_EQ(tr->Start(index, rv), i.Start(index).ToSeconds()); + EXPECT_EQ(tr->End(index, rv), i[index].mEnd.ToSeconds()); + EXPECT_EQ(tr->End(index, rv), i.End(index).ToSeconds()); + } +} + +static void CheckTimeRanges(dom::TimeRanges* aTr, const media::TimeIntervals& aTi) +{ + EXPECT_EQ(aTr->Length(), aTi.Length()); + for (dom::TimeRanges::index_type i = 0; i < aTr->Length(); i++) { + ErrorResult rv; + EXPECT_EQ(aTr->Start(i, rv), aTi[i].mStart.ToSeconds()); + EXPECT_EQ(aTr->Start(i, rv), aTi.Start(i).ToSeconds()); + EXPECT_EQ(aTr->End(i, rv), aTi[i].mEnd.ToSeconds()); + EXPECT_EQ(aTr->End(i, rv), aTi.End(i).ToSeconds()); + } +} + +TEST(IntervalSet, TimeRangesConversion) +{ + nsRefPtr tr = new dom::TimeRanges(); + tr->Add(20, 25); + tr->Add(40, 60); + tr->Add(5, 10); + tr->Add(16, 27); + tr->Add(53, 57); + tr->Add(45, 50); + + // explicit copy constructor + media::TimeIntervals i1(tr); + CheckTimeRanges(tr, i1); + + // static FromTimeRanges + media::TimeIntervals i2 = media::TimeIntervals::FromTimeRanges(tr); + CheckTimeRanges(tr, i2); + + media::TimeIntervals i3; + // operator=(TimeRanges*) + i3 = tr; + CheckTimeRanges(tr, i3); + + i1.Normalize(); + tr->Normalize(); + + CheckTimeRanges(tr, i1); + + // operator= test + i1 = tr.get(); + CheckTimeRanges(tr, i1); +} + +TEST(IntervalSet, TimeRangesMicroseconds) +{ + media::TimeIntervals i0; + + // Test media::Microseconds and TimeUnit interchangeability (compilation only) + media::TimeUnit time1{media::Microseconds(5)}; + media::Microseconds microseconds(5); + media::TimeUnit time2 = media::TimeUnit(microseconds); + EXPECT_EQ(time1, time2); + + i0 += media::TimeInterval(media::Microseconds(20), media::Microseconds(25)); + i0 += media::TimeInterval(media::Microseconds(40), media::Microseconds(60)); + i0 += media::TimeInterval(media::Microseconds(5), media::Microseconds(10)); + + media::TimeIntervals i1; + i1.Add(media::TimeInterval(media::Microseconds(16), media::Microseconds(27))); + i1.Add(media::TimeInterval(media::Microseconds(7), media::Microseconds(15))); + i1.Add(media::TimeInterval(media::Microseconds(53), media::Microseconds(57))); + i1.Add(media::TimeInterval(media::Microseconds(45), media::Microseconds(50))); + + media::TimeIntervals i(i0 + i1); + nsRefPtr tr = new dom::TimeRanges(); + i.ToTimeRanges(tr); + EXPECT_EQ(tr->Length(), i.Length()); + for (dom::TimeRanges::index_type index = 0; index < tr->Length(); index++) { + ErrorResult rv; + EXPECT_EQ(tr->Start(index, rv), i[index].mStart.ToSeconds()); + EXPECT_EQ(tr->Start(index, rv), i.Start(index).ToSeconds()); + EXPECT_EQ(tr->End(index, rv), i[index].mEnd.ToSeconds()); + EXPECT_EQ(tr->End(index, rv), i.End(index).ToSeconds()); + } + + i.Normalize(); + tr->Normalize(); + EXPECT_EQ(tr->Length(), i.Length()); + for (dom::TimeRanges::index_type index = 0; index < tr->Length(); index++) { + ErrorResult rv; + EXPECT_EQ(tr->Start(index, rv), i[index].mStart.ToSeconds()); + EXPECT_EQ(tr->Start(index, rv), i.Start(index).ToSeconds()); + EXPECT_EQ(tr->End(index, rv), i[index].mEnd.ToSeconds()); + EXPECT_EQ(tr->End(index, rv), i.End(index).ToSeconds()); + } +} + +template +class Foo +{ +public: + Foo() + : mArg1(1) + , mArg2(2) + , mArg3(3) + {} + + Foo(T a1, T a2, T a3) + : mArg1(a1) + , mArg2(a2) + , mArg3(a3) + {} + + Foo operator+ (const Foo& aOther) const + { + Foo blah; + blah.mArg1 += aOther.mArg1; + blah.mArg2 += aOther.mArg2; + blah.mArg3 += aOther.mArg3; + return blah; + } + Foo operator- (const Foo& aOther) const + { + Foo blah; + blah.mArg1 -= aOther.mArg1; + blah.mArg2 -= aOther.mArg2; + blah.mArg3 -= aOther.mArg3; + return blah; + } + bool operator< (const Foo& aOther) const + { + return mArg1 < aOther.mArg1; + } + bool operator== (const Foo& aOther) const + { + return mArg1 == aOther.mArg1; + } + bool operator<= (const Foo& aOther) const + { + return mArg1 <= aOther.mArg1; + } + +private: + int32_t mArg1; + int32_t mArg2; + int32_t mArg3; +}; + +TEST(IntervalSet, FooIntervalSet) +{ + media::Interval> i(Foo(), Foo(4,5,6)); + media::IntervalSet> is; + is += i; + is += i; + is.Add(i); + is = is + i; + is = i + is; + EXPECT_EQ(5u, is.Length()); + is.Normalize(); + EXPECT_EQ(1u, is.Length()); + EXPECT_EQ(Foo(), is[0].mStart); + EXPECT_EQ(Foo(4,5,6), is[0].mEnd); +} diff --git a/dom/media/gtest/moz.build b/dom/media/gtest/moz.build index 5abc1bb1901a..7311123ec8d8 100644 --- a/dom/media/gtest/moz.build +++ b/dom/media/gtest/moz.build @@ -9,6 +9,7 @@ UNIFIED_SOURCES += [ 'TestAudioCompactor.cpp', 'TestGMPCrossOrigin.cpp', 'TestGMPRemoveAndDelete.cpp', + 'TestIntervalSet.cpp', 'TestMP4Demuxer.cpp', 'TestMP4Reader.cpp', 'TestTrackEncoder.cpp', diff --git a/dom/media/moz.build b/dom/media/moz.build index cea682ecd3ac..8ccd120ba1bf 100644 --- a/dom/media/moz.build +++ b/dom/media/moz.build @@ -113,6 +113,7 @@ EXPORTS += [ 'EncodedBufferCache.h', 'FileBlockCache.h', 'GraphDriver.h', + 'Intervals.h', 'Latency.h', 'MediaCache.h', 'MediaData.h',