michael@0: /* -*- Mode: C++; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*- */ michael@0: /* This Source Code Form is subject to the terms of the Mozilla Public michael@0: * License, v. 2.0. If a copy of the MPL was not distributed with this file, michael@0: * You can obtain one at http://mozilla.org/MPL/2.0/. */ michael@0: michael@0: #ifndef MOZILLA_MEDIASEGMENT_H_ michael@0: #define MOZILLA_MEDIASEGMENT_H_ michael@0: michael@0: #include "nsTArray.h" michael@0: #ifdef MOZILLA_INTERNAL_API michael@0: #include "mozilla/TimeStamp.h" michael@0: #endif michael@0: #include michael@0: #include "Latency.h" michael@0: michael@0: namespace mozilla { michael@0: michael@0: /** michael@0: * We represent media times in 64-bit fixed point. So 1 MediaTime is michael@0: * 1/(2^MEDIA_TIME_FRAC_BITS) seconds. michael@0: */ michael@0: typedef int64_t MediaTime; michael@0: const int64_t MEDIA_TIME_FRAC_BITS = 20; michael@0: const int64_t MEDIA_TIME_MAX = INT64_MAX; michael@0: michael@0: inline MediaTime MillisecondsToMediaTime(int32_t aMS) michael@0: { michael@0: return (MediaTime(aMS) << MEDIA_TIME_FRAC_BITS)/1000; michael@0: } michael@0: michael@0: inline MediaTime SecondsToMediaTime(double aS) michael@0: { michael@0: NS_ASSERTION(aS <= (MEDIA_TIME_MAX >> MEDIA_TIME_FRAC_BITS), michael@0: "Out of range"); michael@0: return MediaTime(aS * (1 << MEDIA_TIME_FRAC_BITS)); michael@0: } michael@0: michael@0: inline double MediaTimeToSeconds(MediaTime aTime) michael@0: { michael@0: return aTime*(1.0/(1 << MEDIA_TIME_FRAC_BITS)); michael@0: } michael@0: michael@0: inline int64_t MediaTimeToMicroseconds(MediaTime aTime) michael@0: { michael@0: return aTime*(1000000.0/(1 << MEDIA_TIME_FRAC_BITS)); michael@0: } michael@0: michael@0: /** michael@0: * A number of ticks at a rate determined by some underlying track (e.g. michael@0: * audio sample rate). We want to make sure that multiplying TrackTicks by michael@0: * 2^MEDIA_TIME_FRAC_BITS doesn't overflow, so we set its max accordingly. michael@0: */ michael@0: typedef int64_t TrackTicks; michael@0: const int64_t TRACK_TICKS_MAX = INT64_MAX >> MEDIA_TIME_FRAC_BITS; michael@0: michael@0: /** michael@0: * A MediaSegment is a chunk of media data sequential in time. Different michael@0: * types of data have different subclasses of MediaSegment, all inheriting michael@0: * from MediaSegmentBase. michael@0: * All MediaSegment data is timed using TrackTicks. The actual tick rate michael@0: * is defined on a per-track basis. For some track types, this can be michael@0: * a fixed constant for all tracks of that type (e.g. 1MHz for video). michael@0: * michael@0: * Each media segment defines a concept of "null media data" (e.g. silence michael@0: * for audio or "no video frame" for video), which can be efficiently michael@0: * represented. This is used for padding. michael@0: */ michael@0: class MediaSegment { michael@0: public: michael@0: virtual ~MediaSegment() michael@0: { michael@0: MOZ_COUNT_DTOR(MediaSegment); michael@0: } michael@0: michael@0: enum Type { michael@0: AUDIO, michael@0: VIDEO, michael@0: TYPE_COUNT michael@0: }; michael@0: michael@0: /** michael@0: * Gets the total duration of the segment. michael@0: */ michael@0: TrackTicks GetDuration() const { return mDuration; } michael@0: Type GetType() const { return mType; } michael@0: michael@0: /** michael@0: * Create a MediaSegment of the same type. michael@0: */ michael@0: virtual MediaSegment* CreateEmptyClone() const = 0; michael@0: /** michael@0: * Moves contents of aSource to the end of this segment. michael@0: */ michael@0: virtual void AppendFrom(MediaSegment* aSource) = 0; michael@0: /** michael@0: * Append a slice of aSource to this segment. michael@0: */ michael@0: virtual void AppendSlice(const MediaSegment& aSource, michael@0: TrackTicks aStart, TrackTicks aEnd) = 0; michael@0: /** michael@0: * Replace all contents up to aDuration with null data. michael@0: */ michael@0: virtual void ForgetUpTo(TrackTicks aDuration) = 0; michael@0: /** michael@0: * Insert aDuration of null data at the start of the segment. michael@0: */ michael@0: virtual void InsertNullDataAtStart(TrackTicks aDuration) = 0; michael@0: /** michael@0: * Insert aDuration of null data at the end of the segment. michael@0: */ michael@0: virtual void AppendNullData(TrackTicks aDuration) = 0; michael@0: /** michael@0: * Replace contents with disabled data of the same duration michael@0: */ michael@0: virtual void ReplaceWithDisabled() = 0; michael@0: /** michael@0: * Remove all contents, setting duration to 0. michael@0: */ michael@0: virtual void Clear() = 0; michael@0: michael@0: virtual size_t SizeOfExcludingThis(MallocSizeOf aMallocSizeOf) const michael@0: { michael@0: return 0; michael@0: } michael@0: michael@0: virtual size_t SizeOfIncludingThis(MallocSizeOf aMallocSizeOf) const michael@0: { michael@0: return aMallocSizeOf(this) + SizeOfExcludingThis(aMallocSizeOf); michael@0: } michael@0: michael@0: protected: michael@0: MediaSegment(Type aType) : mDuration(0), mType(aType) michael@0: { michael@0: MOZ_COUNT_CTOR(MediaSegment); michael@0: } michael@0: michael@0: TrackTicks mDuration; // total of mDurations of all chunks michael@0: Type mType; michael@0: }; michael@0: michael@0: /** michael@0: * C is the implementation class subclassed from MediaSegmentBase. michael@0: * C must contain a Chunk class. michael@0: */ michael@0: template class MediaSegmentBase : public MediaSegment { michael@0: public: michael@0: virtual MediaSegment* CreateEmptyClone() const michael@0: { michael@0: return new C(); michael@0: } michael@0: virtual void AppendFrom(MediaSegment* aSource) michael@0: { michael@0: NS_ASSERTION(aSource->GetType() == C::StaticType(), "Wrong type"); michael@0: AppendFromInternal(static_cast(aSource)); michael@0: } michael@0: void AppendFrom(C* aSource) michael@0: { michael@0: AppendFromInternal(aSource); michael@0: } michael@0: virtual void AppendSlice(const MediaSegment& aSource, michael@0: TrackTicks aStart, TrackTicks aEnd) michael@0: { michael@0: NS_ASSERTION(aSource.GetType() == C::StaticType(), "Wrong type"); michael@0: AppendSliceInternal(static_cast(aSource), aStart, aEnd); michael@0: } michael@0: void AppendSlice(const C& aOther, TrackTicks aStart, TrackTicks aEnd) michael@0: { michael@0: AppendSliceInternal(aOther, aStart, aEnd); michael@0: } michael@0: /** michael@0: * Replace the first aDuration ticks with null media data, because the data michael@0: * will not be required again. michael@0: */ michael@0: virtual void ForgetUpTo(TrackTicks aDuration) michael@0: { michael@0: if (mChunks.IsEmpty() || aDuration <= 0) { michael@0: return; michael@0: } michael@0: if (mChunks[0].IsNull()) { michael@0: TrackTicks extraToForget = std::min(aDuration, mDuration) - mChunks[0].GetDuration(); michael@0: if (extraToForget > 0) { michael@0: RemoveLeading(extraToForget, 1); michael@0: mChunks[0].mDuration += extraToForget; michael@0: mDuration += extraToForget; michael@0: } michael@0: return; michael@0: } michael@0: RemoveLeading(aDuration, 0); michael@0: mChunks.InsertElementAt(0)->SetNull(aDuration); michael@0: mDuration += aDuration; michael@0: } michael@0: virtual void InsertNullDataAtStart(TrackTicks aDuration) michael@0: { michael@0: if (aDuration <= 0) { michael@0: return; michael@0: } michael@0: if (!mChunks.IsEmpty() && mChunks[0].IsNull()) { michael@0: mChunks[0].mDuration += aDuration; michael@0: } else { michael@0: mChunks.InsertElementAt(0)->SetNull(aDuration); michael@0: } michael@0: #ifdef MOZILLA_INTERNAL_API michael@0: mChunks[0].mTimeStamp = mozilla::TimeStamp::Now(); michael@0: #endif michael@0: mDuration += aDuration; michael@0: } michael@0: virtual void AppendNullData(TrackTicks aDuration) michael@0: { michael@0: if (aDuration <= 0) { michael@0: return; michael@0: } michael@0: if (!mChunks.IsEmpty() && mChunks[mChunks.Length() - 1].IsNull()) { michael@0: mChunks[mChunks.Length() - 1].mDuration += aDuration; michael@0: } else { michael@0: mChunks.AppendElement()->SetNull(aDuration); michael@0: } michael@0: mDuration += aDuration; michael@0: } michael@0: virtual void ReplaceWithDisabled() michael@0: { michael@0: if (GetType() != AUDIO) { michael@0: MOZ_CRASH("Disabling unknown segment type"); michael@0: } michael@0: TrackTicks duration = GetDuration(); michael@0: Clear(); michael@0: AppendNullData(duration); michael@0: } michael@0: virtual void Clear() michael@0: { michael@0: mDuration = 0; michael@0: mChunks.Clear(); michael@0: } michael@0: michael@0: class ChunkIterator { michael@0: public: michael@0: ChunkIterator(MediaSegmentBase& aSegment) michael@0: : mSegment(aSegment), mIndex(0) {} michael@0: bool IsEnded() { return mIndex >= mSegment.mChunks.Length(); } michael@0: void Next() { ++mIndex; } michael@0: Chunk& operator*() { return mSegment.mChunks[mIndex]; } michael@0: Chunk* operator->() { return &mSegment.mChunks[mIndex]; } michael@0: private: michael@0: MediaSegmentBase& mSegment; michael@0: uint32_t mIndex; michael@0: }; michael@0: michael@0: void RemoveLeading(TrackTicks aDuration) michael@0: { michael@0: RemoveLeading(aDuration, 0); michael@0: } michael@0: michael@0: #ifdef MOZILLA_INTERNAL_API michael@0: void GetStartTime(TimeStamp &aTime) { michael@0: aTime = mChunks[0].mTimeStamp; michael@0: } michael@0: #endif michael@0: michael@0: virtual size_t SizeOfExcludingThis(MallocSizeOf aMallocSizeOf) const MOZ_OVERRIDE michael@0: { michael@0: size_t amount = mChunks.SizeOfExcludingThis(aMallocSizeOf); michael@0: for (size_t i = 0; i < mChunks.Length(); i++) { michael@0: amount += mChunks[i].SizeOfExcludingThisIfUnshared(aMallocSizeOf); michael@0: } michael@0: return amount; michael@0: } michael@0: michael@0: virtual size_t SizeOfIncludingThis(MallocSizeOf aMallocSizeOf) const MOZ_OVERRIDE michael@0: { michael@0: return aMallocSizeOf(this) + SizeOfExcludingThis(aMallocSizeOf); michael@0: } michael@0: michael@0: protected: michael@0: MediaSegmentBase(Type aType) : MediaSegment(aType) {} michael@0: michael@0: /** michael@0: * Appends the contents of aSource to this segment, clearing aSource. michael@0: */ michael@0: void AppendFromInternal(MediaSegmentBase* aSource) michael@0: { michael@0: MOZ_ASSERT(aSource->mDuration >= 0); michael@0: mDuration += aSource->mDuration; michael@0: aSource->mDuration = 0; michael@0: if (!mChunks.IsEmpty() && !aSource->mChunks.IsEmpty() && michael@0: mChunks[mChunks.Length() - 1].CanCombineWithFollowing(aSource->mChunks[0])) { michael@0: mChunks[mChunks.Length() - 1].mDuration += aSource->mChunks[0].mDuration; michael@0: aSource->mChunks.RemoveElementAt(0); michael@0: } michael@0: mChunks.MoveElementsFrom(aSource->mChunks); michael@0: } michael@0: michael@0: void AppendSliceInternal(const MediaSegmentBase& aSource, michael@0: TrackTicks aStart, TrackTicks aEnd) michael@0: { michael@0: MOZ_ASSERT(aStart <= aEnd, "Endpoints inverted"); michael@0: NS_WARN_IF_FALSE(aStart >= 0 && aEnd <= aSource.mDuration, "Slice out of range"); michael@0: mDuration += aEnd - aStart; michael@0: TrackTicks offset = 0; michael@0: for (uint32_t i = 0; i < aSource.mChunks.Length() && offset < aEnd; ++i) { michael@0: const Chunk& c = aSource.mChunks[i]; michael@0: TrackTicks start = std::max(aStart, offset); michael@0: TrackTicks nextOffset = offset + c.GetDuration(); michael@0: TrackTicks end = std::min(aEnd, nextOffset); michael@0: if (start < end) { michael@0: mChunks.AppendElement(c)->SliceTo(start - offset, end - offset); michael@0: } michael@0: offset = nextOffset; michael@0: } michael@0: } michael@0: michael@0: Chunk* AppendChunk(TrackTicks aDuration) michael@0: { michael@0: MOZ_ASSERT(aDuration >= 0); michael@0: Chunk* c = mChunks.AppendElement(); michael@0: c->mDuration = aDuration; michael@0: mDuration += aDuration; michael@0: return c; michael@0: } michael@0: michael@0: Chunk* FindChunkContaining(TrackTicks aOffset, TrackTicks* aStart = nullptr) michael@0: { michael@0: if (aOffset < 0) { michael@0: return nullptr; michael@0: } michael@0: TrackTicks offset = 0; michael@0: for (uint32_t i = 0; i < mChunks.Length(); ++i) { michael@0: Chunk& c = mChunks[i]; michael@0: TrackTicks nextOffset = offset + c.GetDuration(); michael@0: if (aOffset < nextOffset) { michael@0: if (aStart) { michael@0: *aStart = offset; michael@0: } michael@0: return &c; michael@0: } michael@0: offset = nextOffset; michael@0: } michael@0: return nullptr; michael@0: } michael@0: michael@0: Chunk* GetLastChunk() michael@0: { michael@0: if (mChunks.IsEmpty()) { michael@0: return nullptr; michael@0: } michael@0: return &mChunks[mChunks.Length() - 1]; michael@0: } michael@0: michael@0: void RemoveLeading(TrackTicks aDuration, uint32_t aStartIndex) michael@0: { michael@0: NS_ASSERTION(aDuration >= 0, "Can't remove negative duration"); michael@0: TrackTicks t = aDuration; michael@0: uint32_t chunksToRemove = 0; michael@0: for (uint32_t i = aStartIndex; i < mChunks.Length() && t > 0; ++i) { michael@0: Chunk* c = &mChunks[i]; michael@0: if (c->GetDuration() > t) { michael@0: c->SliceTo(t, c->GetDuration()); michael@0: t = 0; michael@0: break; michael@0: } michael@0: t -= c->GetDuration(); michael@0: chunksToRemove = i + 1 - aStartIndex; michael@0: } michael@0: mChunks.RemoveElementsAt(aStartIndex, chunksToRemove); michael@0: mDuration -= aDuration - t; michael@0: } michael@0: michael@0: nsTArray mChunks; michael@0: #ifdef MOZILLA_INTERNAL_API michael@0: mozilla::TimeStamp mTimeStamp; michael@0: #endif michael@0: }; michael@0: michael@0: } michael@0: michael@0: #endif /* MOZILLA_MEDIASEGMENT_H_ */