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_STREAMBUFFER_H_ michael@0: #define MOZILLA_STREAMBUFFER_H_ michael@0: michael@0: #include "MediaSegment.h" michael@0: #include "nsAutoPtr.h" michael@0: michael@0: namespace mozilla { michael@0: michael@0: /** michael@0: * Media time relative to the start of a StreamBuffer. michael@0: */ michael@0: typedef MediaTime StreamTime; michael@0: const StreamTime STREAM_TIME_MAX = MEDIA_TIME_MAX; michael@0: michael@0: /** michael@0: * Track rate in Hz. Maximum 1 << MEDIA_TIME_FRAC_BITS Hz. This ensures michael@0: * calculations below don't overflow. michael@0: */ michael@0: typedef int32_t TrackRate; michael@0: const TrackRate TRACK_RATE_MAX = 1 << MEDIA_TIME_FRAC_BITS; michael@0: michael@0: /** michael@0: * Unique ID for track within a StreamBuffer. Tracks from different michael@0: * StreamBuffers may have the same ID; this matters when appending StreamBuffers, michael@0: * since tracks with the same ID are matched. Only IDs greater than 0 are allowed. michael@0: */ michael@0: typedef int32_t TrackID; michael@0: const TrackID TRACK_NONE = 0; michael@0: michael@0: inline TrackTicks TimeToTicksRoundUp(TrackRate aRate, StreamTime aTime) michael@0: { michael@0: NS_ASSERTION(0 < aRate && aRate <= TRACK_RATE_MAX, "Bad rate"); michael@0: NS_ASSERTION(0 <= aTime && aTime <= STREAM_TIME_MAX, "Bad time"); michael@0: return (aTime*aRate + (1 << MEDIA_TIME_FRAC_BITS) - 1) >> MEDIA_TIME_FRAC_BITS; michael@0: } michael@0: michael@0: inline TrackTicks TimeToTicksRoundDown(TrackRate aRate, StreamTime aTime) michael@0: { michael@0: NS_ASSERTION(0 < aRate && aRate <= TRACK_RATE_MAX, "Bad rate"); michael@0: NS_ASSERTION(0 <= aTime && aTime <= STREAM_TIME_MAX, "Bad time"); michael@0: return (aTime*aRate) >> MEDIA_TIME_FRAC_BITS; michael@0: } michael@0: michael@0: inline StreamTime TicksToTimeRoundUp(TrackRate aRate, TrackTicks aTicks) michael@0: { michael@0: NS_ASSERTION(0 < aRate && aRate <= TRACK_RATE_MAX, "Bad rate"); michael@0: NS_ASSERTION(0 <= aTicks && aTicks <= TRACK_TICKS_MAX, "Bad samples"); michael@0: return ((aTicks << MEDIA_TIME_FRAC_BITS) + aRate - 1)/aRate; michael@0: } michael@0: michael@0: inline StreamTime TicksToTimeRound(TrackRate aRate, TrackTicks aTicks) michael@0: { michael@0: NS_ASSERTION(0 < aRate && aRate <= TRACK_RATE_MAX, "Bad rate"); michael@0: NS_ASSERTION(0 <= aTicks && aTicks <= TRACK_TICKS_MAX, "Bad samples"); michael@0: return ((aTicks << MEDIA_TIME_FRAC_BITS) + aRate/2)/aRate; michael@0: } michael@0: michael@0: inline StreamTime TicksToTimeRoundDown(TrackRate aRate, TrackTicks aTicks) michael@0: { michael@0: NS_ASSERTION(0 < aRate && aRate <= TRACK_RATE_MAX, "Bad rate"); michael@0: NS_WARN_IF_FALSE(0 <= aTicks && aTicks <= TRACK_TICKS_MAX, "Bad samples"); michael@0: return (aTicks << MEDIA_TIME_FRAC_BITS)/aRate; michael@0: } michael@0: michael@0: /** michael@0: * This object contains the decoded data for a stream's tracks. michael@0: * A StreamBuffer can be appended to. Logically a StreamBuffer only gets longer, michael@0: * but we also have the ability to "forget" data before a certain time that michael@0: * we know won't be used again. (We prune a whole number of seconds internally.) michael@0: * michael@0: * StreamBuffers should only be used from one thread at a time. michael@0: * michael@0: * A StreamBuffer has a set of tracks that can be of arbitrary types --- michael@0: * the data for each track is a MediaSegment. The set of tracks can vary michael@0: * over the timeline of the StreamBuffer. michael@0: */ michael@0: class StreamBuffer { michael@0: public: michael@0: /** michael@0: * Every track has a start time --- when it started in the StreamBuffer. michael@0: * It has an end flag; when false, no end point is known; when true, michael@0: * the track ends when the data we have for the track runs out. michael@0: * Tracks have a unique ID assigned at creation. This allows us to identify michael@0: * the same track across StreamBuffers. A StreamBuffer should never have michael@0: * two tracks with the same ID (even if they don't overlap in time). michael@0: * TODO Tracks can also be enabled and disabled over time. michael@0: * TODO Add TimeVarying mEnabled. michael@0: * Takes ownership of aSegment. michael@0: */ michael@0: class Track { michael@0: public: michael@0: Track(TrackID aID, TrackRate aRate, TrackTicks aStart, MediaSegment* aSegment) michael@0: : mStart(aStart), michael@0: mSegment(aSegment), michael@0: mRate(aRate), michael@0: mID(aID), michael@0: mEnded(false) michael@0: { michael@0: MOZ_COUNT_CTOR(Track); michael@0: michael@0: NS_ASSERTION(aID > TRACK_NONE, "Bad track ID"); michael@0: NS_ASSERTION(0 < aRate && aRate <= TRACK_RATE_MAX, "Invalid rate"); michael@0: NS_ASSERTION(0 <= aStart && aStart <= aSegment->GetDuration(), "Bad start position"); michael@0: } michael@0: ~Track() michael@0: { michael@0: MOZ_COUNT_DTOR(Track); michael@0: } michael@0: template T* Get() const michael@0: { michael@0: if (mSegment->GetType() == T::StaticType()) { michael@0: return static_cast(mSegment.get()); michael@0: } michael@0: return nullptr; michael@0: } michael@0: MediaSegment* GetSegment() const { return mSegment; } michael@0: TrackRate GetRate() const { return mRate; } michael@0: TrackID GetID() const { return mID; } michael@0: bool IsEnded() const { return mEnded; } michael@0: TrackTicks GetStart() const { return mStart; } michael@0: TrackTicks GetEnd() const { return mSegment->GetDuration(); } michael@0: StreamTime GetEndTimeRoundDown() const michael@0: { michael@0: return mozilla::TicksToTimeRoundDown(mRate, mSegment->GetDuration()); michael@0: } michael@0: StreamTime GetStartTimeRoundDown() const michael@0: { michael@0: return mozilla::TicksToTimeRoundDown(mRate, mStart); michael@0: } michael@0: TrackTicks TimeToTicksRoundDown(StreamTime aTime) const michael@0: { michael@0: return mozilla::TimeToTicksRoundDown(mRate, aTime); michael@0: } michael@0: StreamTime TicksToTimeRoundDown(TrackTicks aTicks) const michael@0: { michael@0: return mozilla::TicksToTimeRoundDown(mRate, aTicks); michael@0: } michael@0: MediaSegment::Type GetType() const { return mSegment->GetType(); } michael@0: michael@0: void SetEnded() { mEnded = true; } michael@0: void AppendFrom(Track* aTrack) michael@0: { michael@0: NS_ASSERTION(!mEnded, "Can't append to ended track"); michael@0: NS_ASSERTION(aTrack->mID == mID, "IDs must match"); michael@0: NS_ASSERTION(aTrack->mStart == 0, "Source track must start at zero"); michael@0: NS_ASSERTION(aTrack->mSegment->GetType() == GetType(), "Track types must match"); michael@0: NS_ASSERTION(aTrack->mRate == mRate, "Track rates must match"); michael@0: michael@0: mSegment->AppendFrom(aTrack->mSegment); michael@0: mEnded = aTrack->mEnded; michael@0: } michael@0: MediaSegment* RemoveSegment() michael@0: { michael@0: return mSegment.forget(); michael@0: } michael@0: void ForgetUpTo(TrackTicks aTime) michael@0: { michael@0: mSegment->ForgetUpTo(aTime); michael@0: } michael@0: michael@0: size_t SizeOfIncludingThis(MallocSizeOf aMallocSizeOf) const michael@0: { michael@0: size_t amount = aMallocSizeOf(this); michael@0: if (mSegment) { michael@0: amount += mSegment->SizeOfIncludingThis(aMallocSizeOf); michael@0: } michael@0: return amount; michael@0: } michael@0: michael@0: protected: michael@0: friend class StreamBuffer; michael@0: michael@0: // Start offset is in ticks at rate mRate michael@0: TrackTicks mStart; michael@0: // The segment data starts at the start of the owning StreamBuffer, i.e., michael@0: // there's mStart silence/no video at the beginning. michael@0: nsAutoPtr mSegment; michael@0: TrackRate mRate; // rate in ticks per second michael@0: // Unique ID michael@0: TrackID mID; michael@0: // True when the track ends with the data in mSegment michael@0: bool mEnded; michael@0: }; michael@0: michael@0: class CompareTracksByID { michael@0: public: michael@0: bool Equals(Track* aA, Track* aB) const { michael@0: return aA->GetID() == aB->GetID(); michael@0: } michael@0: bool LessThan(Track* aA, Track* aB) const { michael@0: return aA->GetID() < aB->GetID(); michael@0: } michael@0: }; michael@0: michael@0: StreamBuffer() michael@0: : mTracksKnownTime(0), mForgottenTime(0) michael@0: { michael@0: MOZ_COUNT_CTOR(StreamBuffer); michael@0: } michael@0: ~StreamBuffer() michael@0: { michael@0: MOZ_COUNT_DTOR(StreamBuffer); michael@0: } michael@0: michael@0: size_t SizeOfExcludingThis(MallocSizeOf aMallocSizeOf) const michael@0: { michael@0: size_t amount = 0; michael@0: amount += mTracks.SizeOfExcludingThis(aMallocSizeOf); michael@0: for (size_t i = 0; i < mTracks.Length(); i++) { michael@0: amount += mTracks[i]->SizeOfIncludingThis(aMallocSizeOf); michael@0: } michael@0: return amount; michael@0: } michael@0: michael@0: /** michael@0: * Takes ownership of aSegment. Don't do this while iterating, or while michael@0: * holding a Track reference. michael@0: * aSegment must have aStart worth of null data. michael@0: */ michael@0: Track& AddTrack(TrackID aID, TrackRate aRate, TrackTicks aStart, MediaSegment* aSegment) michael@0: { michael@0: NS_ASSERTION(TimeToTicksRoundDown(aRate, mTracksKnownTime) <= aStart, michael@0: "Start time too early"); michael@0: NS_ASSERTION(!FindTrack(aID), "Track with this ID already exists"); michael@0: michael@0: return **mTracks.InsertElementSorted(new Track(aID, aRate, aStart, aSegment), michael@0: CompareTracksByID()); michael@0: } michael@0: void AdvanceKnownTracksTime(StreamTime aKnownTime) michael@0: { michael@0: NS_ASSERTION(aKnownTime >= mTracksKnownTime, "Can't move tracks-known time earlier"); michael@0: mTracksKnownTime = aKnownTime; michael@0: } michael@0: michael@0: /** michael@0: * The end time for the StreamBuffer is the latest time for which we have michael@0: * data for all tracks that haven't ended by that time. michael@0: */ michael@0: StreamTime GetEnd() const; michael@0: michael@0: /** michael@0: * Returns the earliest time >= 0 at which all tracks have ended michael@0: * and all their data has been played out and no new tracks can be added, michael@0: * or STREAM_TIME_MAX if there is no such time. michael@0: */ michael@0: StreamTime GetAllTracksEnd() const; michael@0: michael@0: #ifdef DEBUG michael@0: void DumpTrackInfo() const; michael@0: #endif michael@0: michael@0: Track* FindTrack(TrackID aID); michael@0: michael@0: class TrackIter { michael@0: public: michael@0: /** michael@0: * Iterate through the tracks of aBuffer in order of ID. michael@0: */ michael@0: TrackIter(const StreamBuffer& aBuffer) : michael@0: mBuffer(&aBuffer.mTracks), mIndex(0), mMatchType(false) {} michael@0: /** michael@0: * Iterate through the tracks of aBuffer with type aType, in order of ID. michael@0: */ michael@0: TrackIter(const StreamBuffer& aBuffer, MediaSegment::Type aType) : michael@0: mBuffer(&aBuffer.mTracks), mIndex(0), mType(aType), mMatchType(true) { FindMatch(); } michael@0: bool IsEnded() { return mIndex >= mBuffer->Length(); } michael@0: void Next() michael@0: { michael@0: ++mIndex; michael@0: FindMatch(); michael@0: } michael@0: Track* get() { return mBuffer->ElementAt(mIndex); } michael@0: Track& operator*() { return *mBuffer->ElementAt(mIndex); } michael@0: Track* operator->() { return mBuffer->ElementAt(mIndex); } michael@0: private: michael@0: void FindMatch() michael@0: { michael@0: if (!mMatchType) michael@0: return; michael@0: while (mIndex < mBuffer->Length() && michael@0: mBuffer->ElementAt(mIndex)->GetType() != mType) { michael@0: ++mIndex; michael@0: } michael@0: } michael@0: michael@0: const nsTArray >* mBuffer; michael@0: uint32_t mIndex; michael@0: MediaSegment::Type mType; michael@0: bool mMatchType; michael@0: }; michael@0: friend class TrackIter; michael@0: michael@0: /** michael@0: * Forget stream data before aTime; they will no longer be needed. michael@0: * Also can forget entire tracks that have ended at or before aTime. michael@0: * Can't be used to forget beyond GetEnd(). michael@0: */ michael@0: void ForgetUpTo(StreamTime aTime); michael@0: /** michael@0: * Returns the latest time passed to ForgetUpTo. michael@0: */ michael@0: StreamTime GetForgottenDuration() michael@0: { michael@0: return mForgottenTime; michael@0: } michael@0: michael@0: protected: michael@0: // Any new tracks added will start at or after this time. In other words, the track michael@0: // list is complete and correct for all times less than this time. michael@0: StreamTime mTracksKnownTime; michael@0: StreamTime mForgottenTime; michael@0: // All known tracks for this StreamBuffer michael@0: nsTArray > mTracks; michael@0: }; michael@0: michael@0: } michael@0: michael@0: #endif /* MOZILLA_STREAMBUFFER_H_ */ michael@0: