summaryrefslogtreecommitdiff
path: root/dom/media/StreamTracks.h
blob: 0152c3baeaf6310242cb82b10da914eb6a2f675d (plain)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
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_ */