|
1 /* -*- Mode: C++; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*- */ |
|
2 /* This Source Code Form is subject to the terms of the Mozilla Public |
|
3 * License, v. 2.0. If a copy of the MPL was not distributed with this file, |
|
4 * You can obtain one at http://mozilla.org/MPL/2.0/. */ |
|
5 |
|
6 #ifndef MOZILLA_STREAMBUFFER_H_ |
|
7 #define MOZILLA_STREAMBUFFER_H_ |
|
8 |
|
9 #include "MediaSegment.h" |
|
10 #include "nsAutoPtr.h" |
|
11 |
|
12 namespace mozilla { |
|
13 |
|
14 /** |
|
15 * Media time relative to the start of a StreamBuffer. |
|
16 */ |
|
17 typedef MediaTime StreamTime; |
|
18 const StreamTime STREAM_TIME_MAX = MEDIA_TIME_MAX; |
|
19 |
|
20 /** |
|
21 * Track rate in Hz. Maximum 1 << MEDIA_TIME_FRAC_BITS Hz. This ensures |
|
22 * calculations below don't overflow. |
|
23 */ |
|
24 typedef int32_t TrackRate; |
|
25 const TrackRate TRACK_RATE_MAX = 1 << MEDIA_TIME_FRAC_BITS; |
|
26 |
|
27 /** |
|
28 * Unique ID for track within a StreamBuffer. Tracks from different |
|
29 * StreamBuffers may have the same ID; this matters when appending StreamBuffers, |
|
30 * since tracks with the same ID are matched. Only IDs greater than 0 are allowed. |
|
31 */ |
|
32 typedef int32_t TrackID; |
|
33 const TrackID TRACK_NONE = 0; |
|
34 |
|
35 inline TrackTicks TimeToTicksRoundUp(TrackRate aRate, StreamTime aTime) |
|
36 { |
|
37 NS_ASSERTION(0 < aRate && aRate <= TRACK_RATE_MAX, "Bad rate"); |
|
38 NS_ASSERTION(0 <= aTime && aTime <= STREAM_TIME_MAX, "Bad time"); |
|
39 return (aTime*aRate + (1 << MEDIA_TIME_FRAC_BITS) - 1) >> MEDIA_TIME_FRAC_BITS; |
|
40 } |
|
41 |
|
42 inline TrackTicks TimeToTicksRoundDown(TrackRate aRate, StreamTime aTime) |
|
43 { |
|
44 NS_ASSERTION(0 < aRate && aRate <= TRACK_RATE_MAX, "Bad rate"); |
|
45 NS_ASSERTION(0 <= aTime && aTime <= STREAM_TIME_MAX, "Bad time"); |
|
46 return (aTime*aRate) >> MEDIA_TIME_FRAC_BITS; |
|
47 } |
|
48 |
|
49 inline StreamTime TicksToTimeRoundUp(TrackRate aRate, TrackTicks aTicks) |
|
50 { |
|
51 NS_ASSERTION(0 < aRate && aRate <= TRACK_RATE_MAX, "Bad rate"); |
|
52 NS_ASSERTION(0 <= aTicks && aTicks <= TRACK_TICKS_MAX, "Bad samples"); |
|
53 return ((aTicks << MEDIA_TIME_FRAC_BITS) + aRate - 1)/aRate; |
|
54 } |
|
55 |
|
56 inline StreamTime TicksToTimeRound(TrackRate aRate, TrackTicks aTicks) |
|
57 { |
|
58 NS_ASSERTION(0 < aRate && aRate <= TRACK_RATE_MAX, "Bad rate"); |
|
59 NS_ASSERTION(0 <= aTicks && aTicks <= TRACK_TICKS_MAX, "Bad samples"); |
|
60 return ((aTicks << MEDIA_TIME_FRAC_BITS) + aRate/2)/aRate; |
|
61 } |
|
62 |
|
63 inline StreamTime TicksToTimeRoundDown(TrackRate aRate, TrackTicks aTicks) |
|
64 { |
|
65 NS_ASSERTION(0 < aRate && aRate <= TRACK_RATE_MAX, "Bad rate"); |
|
66 NS_WARN_IF_FALSE(0 <= aTicks && aTicks <= TRACK_TICKS_MAX, "Bad samples"); |
|
67 return (aTicks << MEDIA_TIME_FRAC_BITS)/aRate; |
|
68 } |
|
69 |
|
70 /** |
|
71 * This object contains the decoded data for a stream's tracks. |
|
72 * A StreamBuffer can be appended to. Logically a StreamBuffer only gets longer, |
|
73 * but we also have the ability to "forget" data before a certain time that |
|
74 * we know won't be used again. (We prune a whole number of seconds internally.) |
|
75 * |
|
76 * StreamBuffers should only be used from one thread at a time. |
|
77 * |
|
78 * A StreamBuffer has a set of tracks that can be of arbitrary types --- |
|
79 * the data for each track is a MediaSegment. The set of tracks can vary |
|
80 * over the timeline of the StreamBuffer. |
|
81 */ |
|
82 class StreamBuffer { |
|
83 public: |
|
84 /** |
|
85 * Every track has a start time --- when it started in the StreamBuffer. |
|
86 * It has an end flag; when false, no end point is known; when true, |
|
87 * the track ends when the data we have for the track runs out. |
|
88 * Tracks have a unique ID assigned at creation. This allows us to identify |
|
89 * the same track across StreamBuffers. A StreamBuffer should never have |
|
90 * two tracks with the same ID (even if they don't overlap in time). |
|
91 * TODO Tracks can also be enabled and disabled over time. |
|
92 * TODO Add TimeVarying<TrackTicks,bool> mEnabled. |
|
93 * Takes ownership of aSegment. |
|
94 */ |
|
95 class Track { |
|
96 public: |
|
97 Track(TrackID aID, TrackRate aRate, TrackTicks aStart, MediaSegment* aSegment) |
|
98 : mStart(aStart), |
|
99 mSegment(aSegment), |
|
100 mRate(aRate), |
|
101 mID(aID), |
|
102 mEnded(false) |
|
103 { |
|
104 MOZ_COUNT_CTOR(Track); |
|
105 |
|
106 NS_ASSERTION(aID > TRACK_NONE, "Bad track ID"); |
|
107 NS_ASSERTION(0 < aRate && aRate <= TRACK_RATE_MAX, "Invalid rate"); |
|
108 NS_ASSERTION(0 <= aStart && aStart <= aSegment->GetDuration(), "Bad start position"); |
|
109 } |
|
110 ~Track() |
|
111 { |
|
112 MOZ_COUNT_DTOR(Track); |
|
113 } |
|
114 template <class T> T* Get() const |
|
115 { |
|
116 if (mSegment->GetType() == T::StaticType()) { |
|
117 return static_cast<T*>(mSegment.get()); |
|
118 } |
|
119 return nullptr; |
|
120 } |
|
121 MediaSegment* GetSegment() const { return mSegment; } |
|
122 TrackRate GetRate() const { return mRate; } |
|
123 TrackID GetID() const { return mID; } |
|
124 bool IsEnded() const { return mEnded; } |
|
125 TrackTicks GetStart() const { return mStart; } |
|
126 TrackTicks GetEnd() const { return mSegment->GetDuration(); } |
|
127 StreamTime GetEndTimeRoundDown() const |
|
128 { |
|
129 return mozilla::TicksToTimeRoundDown(mRate, mSegment->GetDuration()); |
|
130 } |
|
131 StreamTime GetStartTimeRoundDown() const |
|
132 { |
|
133 return mozilla::TicksToTimeRoundDown(mRate, mStart); |
|
134 } |
|
135 TrackTicks TimeToTicksRoundDown(StreamTime aTime) const |
|
136 { |
|
137 return mozilla::TimeToTicksRoundDown(mRate, aTime); |
|
138 } |
|
139 StreamTime TicksToTimeRoundDown(TrackTicks aTicks) const |
|
140 { |
|
141 return mozilla::TicksToTimeRoundDown(mRate, aTicks); |
|
142 } |
|
143 MediaSegment::Type GetType() const { return mSegment->GetType(); } |
|
144 |
|
145 void SetEnded() { mEnded = true; } |
|
146 void AppendFrom(Track* aTrack) |
|
147 { |
|
148 NS_ASSERTION(!mEnded, "Can't append to ended track"); |
|
149 NS_ASSERTION(aTrack->mID == mID, "IDs must match"); |
|
150 NS_ASSERTION(aTrack->mStart == 0, "Source track must start at zero"); |
|
151 NS_ASSERTION(aTrack->mSegment->GetType() == GetType(), "Track types must match"); |
|
152 NS_ASSERTION(aTrack->mRate == mRate, "Track rates must match"); |
|
153 |
|
154 mSegment->AppendFrom(aTrack->mSegment); |
|
155 mEnded = aTrack->mEnded; |
|
156 } |
|
157 MediaSegment* RemoveSegment() |
|
158 { |
|
159 return mSegment.forget(); |
|
160 } |
|
161 void ForgetUpTo(TrackTicks aTime) |
|
162 { |
|
163 mSegment->ForgetUpTo(aTime); |
|
164 } |
|
165 |
|
166 size_t SizeOfIncludingThis(MallocSizeOf aMallocSizeOf) const |
|
167 { |
|
168 size_t amount = aMallocSizeOf(this); |
|
169 if (mSegment) { |
|
170 amount += mSegment->SizeOfIncludingThis(aMallocSizeOf); |
|
171 } |
|
172 return amount; |
|
173 } |
|
174 |
|
175 protected: |
|
176 friend class StreamBuffer; |
|
177 |
|
178 // Start offset is in ticks at rate mRate |
|
179 TrackTicks mStart; |
|
180 // The segment data starts at the start of the owning StreamBuffer, i.e., |
|
181 // there's mStart silence/no video at the beginning. |
|
182 nsAutoPtr<MediaSegment> mSegment; |
|
183 TrackRate mRate; // rate in ticks per second |
|
184 // Unique ID |
|
185 TrackID mID; |
|
186 // True when the track ends with the data in mSegment |
|
187 bool mEnded; |
|
188 }; |
|
189 |
|
190 class CompareTracksByID { |
|
191 public: |
|
192 bool Equals(Track* aA, Track* aB) const { |
|
193 return aA->GetID() == aB->GetID(); |
|
194 } |
|
195 bool LessThan(Track* aA, Track* aB) const { |
|
196 return aA->GetID() < aB->GetID(); |
|
197 } |
|
198 }; |
|
199 |
|
200 StreamBuffer() |
|
201 : mTracksKnownTime(0), mForgottenTime(0) |
|
202 { |
|
203 MOZ_COUNT_CTOR(StreamBuffer); |
|
204 } |
|
205 ~StreamBuffer() |
|
206 { |
|
207 MOZ_COUNT_DTOR(StreamBuffer); |
|
208 } |
|
209 |
|
210 size_t SizeOfExcludingThis(MallocSizeOf aMallocSizeOf) const |
|
211 { |
|
212 size_t amount = 0; |
|
213 amount += mTracks.SizeOfExcludingThis(aMallocSizeOf); |
|
214 for (size_t i = 0; i < mTracks.Length(); i++) { |
|
215 amount += mTracks[i]->SizeOfIncludingThis(aMallocSizeOf); |
|
216 } |
|
217 return amount; |
|
218 } |
|
219 |
|
220 /** |
|
221 * Takes ownership of aSegment. Don't do this while iterating, or while |
|
222 * holding a Track reference. |
|
223 * aSegment must have aStart worth of null data. |
|
224 */ |
|
225 Track& AddTrack(TrackID aID, TrackRate aRate, TrackTicks aStart, MediaSegment* aSegment) |
|
226 { |
|
227 NS_ASSERTION(TimeToTicksRoundDown(aRate, mTracksKnownTime) <= aStart, |
|
228 "Start time too early"); |
|
229 NS_ASSERTION(!FindTrack(aID), "Track with this ID already exists"); |
|
230 |
|
231 return **mTracks.InsertElementSorted(new Track(aID, aRate, aStart, aSegment), |
|
232 CompareTracksByID()); |
|
233 } |
|
234 void AdvanceKnownTracksTime(StreamTime aKnownTime) |
|
235 { |
|
236 NS_ASSERTION(aKnownTime >= mTracksKnownTime, "Can't move tracks-known time earlier"); |
|
237 mTracksKnownTime = aKnownTime; |
|
238 } |
|
239 |
|
240 /** |
|
241 * The end time for the StreamBuffer is the latest time for which we have |
|
242 * data for all tracks that haven't ended by that time. |
|
243 */ |
|
244 StreamTime GetEnd() const; |
|
245 |
|
246 /** |
|
247 * Returns the earliest time >= 0 at which all tracks have ended |
|
248 * and all their data has been played out and no new tracks can be added, |
|
249 * or STREAM_TIME_MAX if there is no such time. |
|
250 */ |
|
251 StreamTime GetAllTracksEnd() const; |
|
252 |
|
253 #ifdef DEBUG |
|
254 void DumpTrackInfo() const; |
|
255 #endif |
|
256 |
|
257 Track* FindTrack(TrackID aID); |
|
258 |
|
259 class TrackIter { |
|
260 public: |
|
261 /** |
|
262 * Iterate through the tracks of aBuffer in order of ID. |
|
263 */ |
|
264 TrackIter(const StreamBuffer& aBuffer) : |
|
265 mBuffer(&aBuffer.mTracks), mIndex(0), mMatchType(false) {} |
|
266 /** |
|
267 * Iterate through the tracks of aBuffer with type aType, in order of ID. |
|
268 */ |
|
269 TrackIter(const StreamBuffer& aBuffer, MediaSegment::Type aType) : |
|
270 mBuffer(&aBuffer.mTracks), mIndex(0), mType(aType), mMatchType(true) { FindMatch(); } |
|
271 bool IsEnded() { return mIndex >= mBuffer->Length(); } |
|
272 void Next() |
|
273 { |
|
274 ++mIndex; |
|
275 FindMatch(); |
|
276 } |
|
277 Track* get() { return mBuffer->ElementAt(mIndex); } |
|
278 Track& operator*() { return *mBuffer->ElementAt(mIndex); } |
|
279 Track* operator->() { return mBuffer->ElementAt(mIndex); } |
|
280 private: |
|
281 void FindMatch() |
|
282 { |
|
283 if (!mMatchType) |
|
284 return; |
|
285 while (mIndex < mBuffer->Length() && |
|
286 mBuffer->ElementAt(mIndex)->GetType() != mType) { |
|
287 ++mIndex; |
|
288 } |
|
289 } |
|
290 |
|
291 const nsTArray<nsAutoPtr<Track> >* mBuffer; |
|
292 uint32_t mIndex; |
|
293 MediaSegment::Type mType; |
|
294 bool mMatchType; |
|
295 }; |
|
296 friend class TrackIter; |
|
297 |
|
298 /** |
|
299 * Forget stream data before aTime; they will no longer be needed. |
|
300 * Also can forget entire tracks that have ended at or before aTime. |
|
301 * Can't be used to forget beyond GetEnd(). |
|
302 */ |
|
303 void ForgetUpTo(StreamTime aTime); |
|
304 /** |
|
305 * Returns the latest time passed to ForgetUpTo. |
|
306 */ |
|
307 StreamTime GetForgottenDuration() |
|
308 { |
|
309 return mForgottenTime; |
|
310 } |
|
311 |
|
312 protected: |
|
313 // Any new tracks added will start at or after this time. In other words, the track |
|
314 // list is complete and correct for all times less than this time. |
|
315 StreamTime mTracksKnownTime; |
|
316 StreamTime mForgottenTime; |
|
317 // All known tracks for this StreamBuffer |
|
318 nsTArray<nsAutoPtr<Track> > mTracks; |
|
319 }; |
|
320 |
|
321 } |
|
322 |
|
323 #endif /* MOZILLA_STREAMBUFFER_H_ */ |
|
324 |