diff options
Diffstat (limited to 'dom/media/StreamTracks.h')
-rw-r--r-- | dom/media/StreamTracks.h | 348 |
1 files changed, 348 insertions, 0 deletions
diff --git a/dom/media/StreamTracks.h b/dom/media/StreamTracks.h new file mode 100644 index 000000000..0152c3bae --- /dev/null +++ b/dom/media/StreamTracks.h @@ -0,0 +1,348 @@ +/* -*- 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_STREAMTRACKS_H_ +#define MOZILLA_STREAMTRACKS_H_ + +#include "MediaSegment.h" +#include "nsAutoPtr.h" + +namespace mozilla { + +/** + * Unique ID for track within a StreamTracks. Tracks from different + * StreamTrackss may have the same ID; this matters when appending StreamTrackss, + * since tracks with the same ID are matched. Only IDs greater than 0 are allowed. + */ +typedef int32_t TrackID; +const TrackID TRACK_NONE = 0; +const TrackID TRACK_INVALID = -1; +const TrackID TRACK_ANY = -2; + +inline bool IsTrackIDExplicit(const TrackID& aId) { + return aId > TRACK_NONE; +} + +inline TrackTicks RateConvertTicksRoundDown(TrackRate aOutRate, + TrackRate aInRate, + TrackTicks aTicks) +{ + NS_ASSERTION(0 < aOutRate && aOutRate <= TRACK_RATE_MAX, "Bad out rate"); + NS_ASSERTION(0 < aInRate && aInRate <= TRACK_RATE_MAX, "Bad in rate"); + NS_ASSERTION(0 <= aTicks && aTicks <= TRACK_TICKS_MAX, "Bad ticks"); + return (aTicks * aOutRate) / aInRate; +} +inline TrackTicks RateConvertTicksRoundUp(TrackRate aOutRate, + TrackRate aInRate, TrackTicks aTicks) +{ + NS_ASSERTION(0 < aOutRate && aOutRate <= TRACK_RATE_MAX, "Bad out rate"); + NS_ASSERTION(0 < aInRate && aInRate <= TRACK_RATE_MAX, "Bad in rate"); + NS_ASSERTION(0 <= aTicks && aTicks <= TRACK_TICKS_MAX, "Bad ticks"); + return (aTicks * aOutRate + aInRate - 1) / aInRate; +} + +/** + * This object contains the decoded data for a stream's tracks. + * A StreamTracks can be appended to. Logically a StreamTracks only gets longer, + * but we also have the ability to "forget" data before a certain time that + * we know won't be used again. (We prune a whole number of seconds internally.) + * + * StreamTrackss should only be used from one thread at a time. + * + * A StreamTracks has a set of tracks that can be of arbitrary types --- + * the data for each track is a MediaSegment. The set of tracks can vary + * over the timeline of the StreamTracks. + */ +class StreamTracks +{ +public: + /** + * Every track has a start time --- when it started in the StreamTracks. + * It has an end flag; when false, no end point is known; when true, + * the track ends when the data we have for the track runs out. + * Tracks have a unique ID assigned at creation. This allows us to identify + * the same track across StreamTrackss. A StreamTracks should never have + * two tracks with the same ID (even if they don't overlap in time). + * TODO Tracks can also be enabled and disabled over time. + * Takes ownership of aSegment. + */ + class Track final + { + Track(TrackID aID, StreamTime aStart, MediaSegment* aSegment) + : mStart(aStart), + mSegment(aSegment), + mID(aID), + mEnded(false) + { + MOZ_COUNT_CTOR(Track); + + NS_ASSERTION(aID > TRACK_NONE, "Bad track ID"); + NS_ASSERTION(0 <= aStart && aStart <= aSegment->GetDuration(), "Bad start position"); + } + + public: + ~Track() + { + MOZ_COUNT_DTOR(Track); + } + + template <class T> T* Get() const + { + if (mSegment->GetType() == T::StaticType()) { + return static_cast<T*>(mSegment.get()); + } + return nullptr; + } + + MediaSegment* GetSegment() const { return mSegment; } + TrackID GetID() const { return mID; } + bool IsEnded() const { return mEnded; } + StreamTime GetStart() const { return mStart; } + StreamTime GetEnd() const { return mSegment->GetDuration(); } + MediaSegment::Type GetType() const { return mSegment->GetType(); } + + void SetEnded() { mEnded = true; } + void AppendFrom(Track* aTrack) + { + NS_ASSERTION(!mEnded, "Can't append to ended track"); + NS_ASSERTION(aTrack->mID == mID, "IDs must match"); + NS_ASSERTION(aTrack->mStart == 0, "Source track must start at zero"); + NS_ASSERTION(aTrack->mSegment->GetType() == GetType(), "Track types must match"); + + mSegment->AppendFrom(aTrack->mSegment); + mEnded = aTrack->mEnded; + } + MediaSegment* RemoveSegment() + { + return mSegment.forget(); + } + void ForgetUpTo(StreamTime aTime) + { + mSegment->ForgetUpTo(aTime); + } + void FlushAfter(StreamTime aNewEnd) + { + // Forget everything after a given endpoint + // a specified amount + mSegment->FlushAfter(aNewEnd); + } + + size_t SizeOfIncludingThis(MallocSizeOf aMallocSizeOf) const + { + size_t amount = aMallocSizeOf(this); + if (mSegment) { + amount += mSegment->SizeOfIncludingThis(aMallocSizeOf); + } + return amount; + } + + private: + friend class StreamTracks; + + // Start offset is in ticks at rate mRate + StreamTime mStart; + // The segment data starts at the start of the owning StreamTracks, i.e., + // there's mStart silence/no video at the beginning. + nsAutoPtr<MediaSegment> mSegment; + // Unique ID + TrackID mID; + // True when the track ends with the data in mSegment + bool mEnded; + }; + + class MOZ_STACK_CLASS CompareTracksByID final + { + public: + bool Equals(Track* aA, Track* aB) const { + return aA->GetID() == aB->GetID(); + } + bool LessThan(Track* aA, Track* aB) const { + return aA->GetID() < aB->GetID(); + } + }; + + StreamTracks() + : mGraphRate(0) + , mTracksKnownTime(0) + , mForgottenTime(0) + , mTracksDirty(false) +#ifdef DEBUG + , mGraphRateIsSet(false) +#endif + { + MOZ_COUNT_CTOR(StreamTracks); + } + ~StreamTracks() + { + MOZ_COUNT_DTOR(StreamTracks); + } + + size_t SizeOfExcludingThis(MallocSizeOf aMallocSizeOf) const + { + size_t amount = 0; + amount += mTracks.ShallowSizeOfExcludingThis(aMallocSizeOf); + for (size_t i = 0; i < mTracks.Length(); i++) { + amount += mTracks[i]->SizeOfIncludingThis(aMallocSizeOf); + } + return amount; + } + + /** + * Initialize the graph rate for use in calculating StreamTimes from track + * ticks. Called when a MediaStream's graph pointer is initialized. + */ + void InitGraphRate(TrackRate aGraphRate) + { + mGraphRate = aGraphRate; +#if DEBUG + MOZ_ASSERT(!mGraphRateIsSet); + mGraphRateIsSet = true; +#endif + } + + TrackRate GraphRate() const + { + MOZ_ASSERT(mGraphRateIsSet); + return mGraphRate; + } + + /** + * Takes ownership of aSegment. Don't do this while iterating, or while + * holding a Track reference. + * aSegment must have aStart worth of null data. + */ + Track& AddTrack(TrackID aID, StreamTime aStart, MediaSegment* aSegment) + { + NS_ASSERTION(!FindTrack(aID), "Track with this ID already exists"); + + Track* track = new Track(aID, aStart, aSegment); + mTracks.InsertElementSorted(track, CompareTracksByID()); + mTracksDirty = true; + + if (mTracksKnownTime == STREAM_TIME_MAX) { + // There exists code like + // http://mxr.mozilla.org/mozilla-central/source/media/webrtc/signaling/src/mediapipeline/MediaPipeline.cpp?rev=96b197deb91e&mark=1292-1297#1292 + NS_WARNING("Adding track to StreamTracks that should have no more tracks"); + } else { + NS_ASSERTION(mTracksKnownTime <= aStart, "Start time too early"); + } + return *track; + } + + void AdvanceKnownTracksTime(StreamTime aKnownTime) + { + NS_ASSERTION(aKnownTime >= mTracksKnownTime, "Can't move tracks-known time earlier"); + mTracksKnownTime = aKnownTime; + } + + /** + * The end time for the StreamTracks is the latest time for which we have + * data for all tracks that haven't ended by that time. + */ + StreamTime GetEnd() const; + + /** + * Returns the earliest time >= 0 at which all tracks have ended + * and all their data has been played out and no new tracks can be added, + * or STREAM_TIME_MAX if there is no such time. + */ + StreamTime GetAllTracksEnd() const; + +#ifdef DEBUG + void DumpTrackInfo() const; +#endif + + Track* FindTrack(TrackID aID); + + class MOZ_STACK_CLASS TrackIter final + { + public: + /** + * Iterate through the tracks of aBuffer in order of ID. + */ + explicit TrackIter(const StreamTracks& aBuffer) : + mBuffer(&aBuffer.mTracks), mIndex(0), mMatchType(false) {} + /** + * Iterate through the tracks of aBuffer with type aType, in order of ID. + */ + TrackIter(const StreamTracks& aBuffer, MediaSegment::Type aType) : + mBuffer(&aBuffer.mTracks), mIndex(0), mType(aType), mMatchType(true) { FindMatch(); } + bool IsEnded() { return mIndex >= mBuffer->Length(); } + void Next() + { + ++mIndex; + FindMatch(); + } + Track* get() { return mBuffer->ElementAt(mIndex); } + Track& operator*() { return *mBuffer->ElementAt(mIndex); } + Track* operator->() { return mBuffer->ElementAt(mIndex); } + private: + void FindMatch() + { + if (!mMatchType) + return; + while (mIndex < mBuffer->Length() && + mBuffer->ElementAt(mIndex)->GetType() != mType) { + ++mIndex; + } + } + + const nsTArray<nsAutoPtr<Track> >* mBuffer; + uint32_t mIndex; + MediaSegment::Type mType; + bool mMatchType; + }; + friend class TrackIter; + + /** + * Forget stream data before aTime; they will no longer be needed. + * Also can forget entire tracks that have ended at or before aTime. + * Can't be used to forget beyond GetEnd(). + */ + void ForgetUpTo(StreamTime aTime); + /** + * Clears out all Tracks and the data they are holding. + * MediaStreamGraph calls this during forced shutdown. + */ + void Clear(); + /** + * Returns the latest time passed to ForgetUpTo. + */ + StreamTime GetForgottenDuration() + { + return mForgottenTime; + } + + bool GetAndResetTracksDirty() + { + if (!mTracksDirty) { + return false; + } + + mTracksDirty = false; + return true; + } + +protected: + TrackRate mGraphRate; // StreamTime per second + // Any new tracks added will start at or after this time. In other words, the track + // list is complete and correct for all times less than this time. + StreamTime mTracksKnownTime; + StreamTime mForgottenTime; + +private: + // All known tracks for this StreamTracks + nsTArray<nsAutoPtr<Track>> mTracks; + bool mTracksDirty; + +#ifdef DEBUG + bool mGraphRateIsSet; +#endif +}; + +} // namespace mozilla + +#endif /* MOZILLA_STREAMTRACKS_H_ */ + |