diff options
Diffstat (limited to 'dom/media/MediaSegment.h')
-rw-r--r-- | dom/media/MediaSegment.h | 508 |
1 files changed, 508 insertions, 0 deletions
diff --git a/dom/media/MediaSegment.h b/dom/media/MediaSegment.h new file mode 100644 index 000000000..5c4805b30 --- /dev/null +++ b/dom/media/MediaSegment.h @@ -0,0 +1,508 @@ +/* -*- 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/. */ + +#ifndef MOZILLA_MEDIASEGMENT_H_ +#define MOZILLA_MEDIASEGMENT_H_ + +#include "nsTArray.h" +#include "nsIPrincipal.h" +#include "nsProxyRelease.h" +#ifdef MOZILLA_INTERNAL_API +#include "mozilla/TimeStamp.h" +#endif +#include <algorithm> +#include "Latency.h" + +namespace mozilla { + +/** + * Track or graph rate in Hz. Maximum 1 << TRACK_RATE_MAX_BITS Hz. This + * maximum avoids overflow in conversions between track rates and conversions + * from seconds. + */ +typedef int32_t TrackRate; +const int64_t TRACK_RATE_MAX_BITS = 20; +const TrackRate TRACK_RATE_MAX = 1 << TRACK_RATE_MAX_BITS; + +/** + * A number of ticks at a rate determined by some underlying track (e.g. + * audio sample rate). We want to make sure that multiplying TrackTicks by + * a TrackRate doesn't overflow, so we set its max accordingly. + * StreamTime should be used instead when we're working with MediaStreamGraph's + * rate, but TrackTicks can be used outside MediaStreams when we have data + * at a different rate. + */ +typedef int64_t TrackTicks; +const int64_t TRACK_TICKS_MAX = INT64_MAX >> TRACK_RATE_MAX_BITS; + +/** + * We represent media times in 64-bit audio frame counts or ticks. + * All tracks in a MediaStreamGraph have the same rate. + */ +typedef int64_t MediaTime; +const int64_t MEDIA_TIME_MAX = TRACK_TICKS_MAX; + +/** + * Media time relative to the start of a StreamTracks. + */ +typedef MediaTime StreamTime; +const StreamTime STREAM_TIME_MAX = MEDIA_TIME_MAX; + +/** + * Media time relative to the start of the graph timeline. + */ +typedef MediaTime GraphTime; +const GraphTime GRAPH_TIME_MAX = MEDIA_TIME_MAX; + +/** + * We pass the principal through the MediaStreamGraph by wrapping it in a thread + * safe nsMainThreadPtrHandle, since it cannot be used directly off the main + * thread. We can compare two PrincipalHandles to each other on any thread, but + * they can only be created and converted back to nsIPrincipal* on main thread. + */ +typedef nsMainThreadPtrHandle<nsIPrincipal> PrincipalHandle; + +inline PrincipalHandle MakePrincipalHandle(nsIPrincipal* aPrincipal) +{ + RefPtr<nsMainThreadPtrHolder<nsIPrincipal>> holder = + new nsMainThreadPtrHolder<nsIPrincipal>(aPrincipal); + return PrincipalHandle(holder); +} + +#define PRINCIPAL_HANDLE_NONE nullptr + +inline nsIPrincipal* GetPrincipalFromHandle(PrincipalHandle& aPrincipalHandle) +{ + MOZ_ASSERT(NS_IsMainThread()); + return aPrincipalHandle.get(); +} + +inline bool PrincipalHandleMatches(PrincipalHandle& aPrincipalHandle, + nsIPrincipal* aOther) +{ + if (!aOther) { + return false; + } + + nsIPrincipal* principal = GetPrincipalFromHandle(aPrincipalHandle); + if (!principal) { + return false; + } + + bool result; + if (NS_FAILED(principal->Equals(aOther, &result))) { + NS_ERROR("Principal check failed"); + return false; + } + + return result; +} + +/** + * A MediaSegment is a chunk of media data sequential in time. Different + * types of data have different subclasses of MediaSegment, all inheriting + * from MediaSegmentBase. + * All MediaSegment data is timed using StreamTime. The actual tick rate + * is defined on a per-track basis. For some track types, this can be + * a fixed constant for all tracks of that type (e.g. 1MHz for video). + * + * Each media segment defines a concept of "null media data" (e.g. silence + * for audio or "no video frame" for video), which can be efficiently + * represented. This is used for padding. + */ +class MediaSegment { +public: + virtual ~MediaSegment() + { + MOZ_COUNT_DTOR(MediaSegment); + } + + enum Type { + AUDIO, + VIDEO, + TYPE_COUNT + }; + + /** + * Gets the total duration of the segment. + */ + StreamTime GetDuration() const { return mDuration; } + Type GetType() const { return mType; } + + /** + * Gets the last principal id that was appended to this segment. + */ + PrincipalHandle GetLastPrincipalHandle() const { return mLastPrincipalHandle; } + /** + * Called by the MediaStreamGraph as it appends a chunk with a different + * principal id than the current one. + */ + void SetLastPrincipalHandle(PrincipalHandle aLastPrincipalHandle) + { + mLastPrincipalHandle = aLastPrincipalHandle; + } + + /** + * Create a MediaSegment of the same type. + */ + virtual MediaSegment* CreateEmptyClone() const = 0; + /** + * Moves contents of aSource to the end of this segment. + */ + virtual void AppendFrom(MediaSegment* aSource) = 0; + /** + * Append a slice of aSource to this segment. + */ + virtual void AppendSlice(const MediaSegment& aSource, + StreamTime aStart, StreamTime aEnd) = 0; + /** + * Replace all contents up to aDuration with null data. + */ + virtual void ForgetUpTo(StreamTime aDuration) = 0; + /** + * Forget all data buffered after a given point + */ + virtual void FlushAfter(StreamTime aNewEnd) = 0; + /** + * Insert aDuration of null data at the start of the segment. + */ + virtual void InsertNullDataAtStart(StreamTime aDuration) = 0; + /** + * Insert aDuration of null data at the end of the segment. + */ + virtual void AppendNullData(StreamTime aDuration) = 0; + /** + * Replace contents with disabled (silence/black) data of the same duration + */ + virtual void ReplaceWithDisabled() = 0; + /** + * Replace contents with null data of the same duration + */ + virtual void ReplaceWithNull() = 0; + /** + * Remove all contents, setting duration to 0. + */ + virtual void Clear() = 0; + + virtual size_t SizeOfExcludingThis(MallocSizeOf aMallocSizeOf) const + { + return 0; + } + + virtual size_t SizeOfIncludingThis(MallocSizeOf aMallocSizeOf) const + { + return aMallocSizeOf(this) + SizeOfExcludingThis(aMallocSizeOf); + } + +protected: + explicit MediaSegment(Type aType) + : mDuration(0), mType(aType), mLastPrincipalHandle(PRINCIPAL_HANDLE_NONE) + { + MOZ_COUNT_CTOR(MediaSegment); + } + + StreamTime mDuration; // total of mDurations of all chunks + Type mType; + + // The latest principal handle that the MediaStreamGraph has processed for + // this segment. + PrincipalHandle mLastPrincipalHandle; +}; + +/** + * C is the implementation class subclassed from MediaSegmentBase. + * C must contain a Chunk class. + */ +template <class C, class Chunk> class MediaSegmentBase : public MediaSegment { +public: + MediaSegment* CreateEmptyClone() const override + { + return new C(); + } + void AppendFrom(MediaSegment* aSource) override + { + NS_ASSERTION(aSource->GetType() == C::StaticType(), "Wrong type"); + AppendFromInternal(static_cast<C*>(aSource)); + } + void AppendFrom(C* aSource) + { + AppendFromInternal(aSource); + } + void AppendSlice(const MediaSegment& aSource, + StreamTime aStart, StreamTime aEnd) override + { + NS_ASSERTION(aSource.GetType() == C::StaticType(), "Wrong type"); + AppendSliceInternal(static_cast<const C&>(aSource), aStart, aEnd); + } + void AppendSlice(const C& aOther, StreamTime aStart, StreamTime aEnd) + { + AppendSliceInternal(aOther, aStart, aEnd); + } + /** + * Replace the first aDuration ticks with null media data, because the data + * will not be required again. + */ + void ForgetUpTo(StreamTime aDuration) override + { + if (mChunks.IsEmpty() || aDuration <= 0) { + return; + } + if (mChunks[0].IsNull()) { + StreamTime extraToForget = std::min(aDuration, mDuration) - mChunks[0].GetDuration(); + if (extraToForget > 0) { + RemoveLeading(extraToForget, 1); + mChunks[0].mDuration += extraToForget; + mDuration += extraToForget; + } + return; + } + RemoveLeading(aDuration, 0); + mChunks.InsertElementAt(0)->SetNull(aDuration); + mDuration += aDuration; + } + void FlushAfter(StreamTime aNewEnd) override + { + if (mChunks.IsEmpty()) { + return; + } + + if (mChunks[0].IsNull()) { + StreamTime extraToKeep = aNewEnd - mChunks[0].GetDuration(); + if (extraToKeep < 0) { + // reduce the size of the Null, get rid of everthing else + mChunks[0].SetNull(aNewEnd); + extraToKeep = 0; + } + RemoveTrailing(extraToKeep, 1); + } else { + if (aNewEnd > mDuration) { + NS_ASSERTION(aNewEnd <= mDuration, "can't add data in FlushAfter"); + return; + } + RemoveTrailing(aNewEnd, 0); + } + mDuration = aNewEnd; + } + void InsertNullDataAtStart(StreamTime aDuration) override + { + if (aDuration <= 0) { + return; + } + if (!mChunks.IsEmpty() && mChunks[0].IsNull()) { + mChunks[0].mDuration += aDuration; + } else { + mChunks.InsertElementAt(0)->SetNull(aDuration); + } +#ifdef MOZILLA_INTERNAL_API + mChunks[0].mTimeStamp = mozilla::TimeStamp::Now(); +#endif + mDuration += aDuration; + } + void AppendNullData(StreamTime aDuration) override + { + if (aDuration <= 0) { + return; + } + if (!mChunks.IsEmpty() && mChunks[mChunks.Length() - 1].IsNull()) { + mChunks[mChunks.Length() - 1].mDuration += aDuration; + } else { + mChunks.AppendElement()->SetNull(aDuration); + } + mDuration += aDuration; + } + void ReplaceWithDisabled() override + { + if (GetType() != AUDIO) { + MOZ_CRASH("Disabling unknown segment type"); + } + ReplaceWithNull(); + } + void ReplaceWithNull() override + { + StreamTime duration = GetDuration(); + Clear(); + AppendNullData(duration); + } + void Clear() override + { + mDuration = 0; + mChunks.Clear(); + } + + class ChunkIterator { + public: + explicit ChunkIterator(MediaSegmentBase<C, Chunk>& aSegment) + : mSegment(aSegment), mIndex(0) {} + bool IsEnded() { return mIndex >= mSegment.mChunks.Length(); } + void Next() { ++mIndex; } + Chunk& operator*() { return mSegment.mChunks[mIndex]; } + Chunk* operator->() { return &mSegment.mChunks[mIndex]; } + private: + MediaSegmentBase<C, Chunk>& mSegment; + uint32_t mIndex; + }; + class ConstChunkIterator { + public: + explicit ConstChunkIterator(const MediaSegmentBase<C, Chunk>& aSegment) + : mSegment(aSegment), mIndex(0) {} + bool IsEnded() { return mIndex >= mSegment.mChunks.Length(); } + void Next() { ++mIndex; } + const Chunk& operator*() { return mSegment.mChunks[mIndex]; } + const Chunk* operator->() { return &mSegment.mChunks[mIndex]; } + private: + const MediaSegmentBase<C, Chunk>& mSegment; + uint32_t mIndex; + }; + + Chunk* FindChunkContaining(StreamTime aOffset, StreamTime* aStart = nullptr) + { + if (aOffset < 0) { + return nullptr; + } + StreamTime offset = 0; + for (uint32_t i = 0; i < mChunks.Length(); ++i) { + Chunk& c = mChunks[i]; + StreamTime nextOffset = offset + c.GetDuration(); + if (aOffset < nextOffset) { + if (aStart) { + *aStart = offset; + } + return &c; + } + offset = nextOffset; + } + return nullptr; + } + + void RemoveLeading(StreamTime aDuration) + { + RemoveLeading(aDuration, 0); + } + +#ifdef MOZILLA_INTERNAL_API + void GetStartTime(TimeStamp &aTime) { + aTime = mChunks[0].mTimeStamp; + } +#endif + + size_t SizeOfExcludingThis(MallocSizeOf aMallocSizeOf) const override + { + size_t amount = mChunks.ShallowSizeOfExcludingThis(aMallocSizeOf); + for (size_t i = 0; i < mChunks.Length(); i++) { + amount += mChunks[i].SizeOfExcludingThisIfUnshared(aMallocSizeOf); + } + return amount; + } + + size_t SizeOfIncludingThis(MallocSizeOf aMallocSizeOf) const override + { + return aMallocSizeOf(this) + SizeOfExcludingThis(aMallocSizeOf); + } + + Chunk* GetLastChunk() + { + if (mChunks.IsEmpty()) { + return nullptr; + } + return &mChunks[mChunks.Length() - 1]; + } + +protected: + explicit MediaSegmentBase(Type aType) : MediaSegment(aType) {} + + /** + * Appends the contents of aSource to this segment, clearing aSource. + */ + void AppendFromInternal(MediaSegmentBase<C, Chunk>* aSource) + { + MOZ_ASSERT(aSource->mDuration >= 0); + mDuration += aSource->mDuration; + aSource->mDuration = 0; + if (!mChunks.IsEmpty() && !aSource->mChunks.IsEmpty() && + mChunks[mChunks.Length() - 1].CanCombineWithFollowing(aSource->mChunks[0])) { + mChunks[mChunks.Length() - 1].mDuration += aSource->mChunks[0].mDuration; + aSource->mChunks.RemoveElementAt(0); + } + mChunks.AppendElements(Move(aSource->mChunks)); + } + + void AppendSliceInternal(const MediaSegmentBase<C, Chunk>& aSource, + StreamTime aStart, StreamTime aEnd) + { + MOZ_ASSERT(aStart <= aEnd, "Endpoints inverted"); + NS_ASSERTION(aStart >= 0 && aEnd <= aSource.mDuration, "Slice out of range"); + mDuration += aEnd - aStart; + StreamTime offset = 0; + for (uint32_t i = 0; i < aSource.mChunks.Length() && offset < aEnd; ++i) { + const Chunk& c = aSource.mChunks[i]; + StreamTime start = std::max(aStart, offset); + StreamTime nextOffset = offset + c.GetDuration(); + StreamTime end = std::min(aEnd, nextOffset); + if (start < end) { + mChunks.AppendElement(c)->SliceTo(start - offset, end - offset); + } + offset = nextOffset; + } + } + + Chunk* AppendChunk(StreamTime aDuration) + { + MOZ_ASSERT(aDuration >= 0); + Chunk* c = mChunks.AppendElement(); + c->mDuration = aDuration; + mDuration += aDuration; + return c; + } + + void RemoveLeading(StreamTime aDuration, uint32_t aStartIndex) + { + NS_ASSERTION(aDuration >= 0, "Can't remove negative duration"); + StreamTime t = aDuration; + uint32_t chunksToRemove = 0; + for (uint32_t i = aStartIndex; i < mChunks.Length() && t > 0; ++i) { + Chunk* c = &mChunks[i]; + if (c->GetDuration() > t) { + c->SliceTo(t, c->GetDuration()); + t = 0; + break; + } + t -= c->GetDuration(); + chunksToRemove = i + 1 - aStartIndex; + } + mChunks.RemoveElementsAt(aStartIndex, chunksToRemove); + mDuration -= aDuration - t; + } + + void RemoveTrailing(StreamTime aKeep, uint32_t aStartIndex) + { + NS_ASSERTION(aKeep >= 0, "Can't keep negative duration"); + StreamTime t = aKeep; + uint32_t i; + for (i = aStartIndex; i < mChunks.Length(); ++i) { + Chunk* c = &mChunks[i]; + if (c->GetDuration() > t) { + c->SliceTo(0, t); + break; + } + t -= c->GetDuration(); + if (t == 0) { + break; + } + } + if (i+1 < mChunks.Length()) { + mChunks.RemoveElementsAt(i+1, mChunks.Length() - (i+1)); + } + // Caller must adjust mDuration + } + + nsTArray<Chunk> mChunks; +#ifdef MOZILLA_INTERNAL_API + mozilla::TimeStamp mTimeStamp; +#endif +}; + +} // namespace mozilla + +#endif /* MOZILLA_MEDIASEGMENT_H_ */ |