Coverage Report

Created: 2018-09-25 14:53

/src/mozilla-central/dom/media/MediaSegment.h
Line
Count
Source (jump to first uncovered line)
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_MEDIASEGMENT_H_
7
#define MOZILLA_MEDIASEGMENT_H_
8
9
#include "nsTArray.h"
10
#include "nsIPrincipal.h"
11
#include "nsProxyRelease.h"
12
#ifdef MOZILLA_INTERNAL_API
13
#include "mozilla/TimeStamp.h"
14
#endif
15
#include <algorithm>
16
#include "Latency.h"
17
18
namespace mozilla {
19
20
/**
21
 * Track or graph rate in Hz. Maximum 1 << TRACK_RATE_MAX_BITS Hz. This
22
 * maximum avoids overflow in conversions between track rates and conversions
23
 * from seconds.
24
 */
25
typedef int32_t TrackRate;
26
const int64_t TRACK_RATE_MAX_BITS = 20;
27
const TrackRate TRACK_RATE_MAX = 1 << TRACK_RATE_MAX_BITS;
28
29
/**
30
 * A number of ticks at a rate determined by some underlying track (e.g.
31
 * audio sample rate). We want to make sure that multiplying TrackTicks by
32
 * a TrackRate doesn't overflow, so we set its max accordingly.
33
 * StreamTime should be used instead when we're working with MediaStreamGraph's
34
 * rate, but TrackTicks can be used outside MediaStreams when we have data
35
 * at a different rate.
36
 */
37
typedef int64_t TrackTicks;
38
const int64_t TRACK_TICKS_MAX = INT64_MAX >> TRACK_RATE_MAX_BITS;
39
40
/**
41
 * We represent media times in 64-bit audio frame counts or ticks.
42
 * All tracks in a MediaStreamGraph have the same rate.
43
 */
44
typedef int64_t MediaTime;
45
const int64_t MEDIA_TIME_MAX = TRACK_TICKS_MAX;
46
47
/**
48
 * Media time relative to the start of a StreamTracks.
49
 */
50
typedef MediaTime StreamTime;
51
const StreamTime STREAM_TIME_MAX = MEDIA_TIME_MAX;
52
53
/**
54
 * Media time relative to the start of the graph timeline.
55
 */
56
typedef MediaTime GraphTime;
57
const GraphTime GRAPH_TIME_MAX = MEDIA_TIME_MAX;
58
59
/**
60
 * The number of chunks allocated by default for a MediaSegment.
61
 * Appending more chunks than this will cause further allocations.
62
 *
63
 * 16 is an arbitrary number intended to cover the most common cases in the
64
 * MediaStreamGraph (1 with silence and 1-2 with data for a realtime track)
65
 * with some margin.
66
 */
67
const size_t DEFAULT_SEGMENT_CAPACITY = 16;
68
69
/**
70
 * We pass the principal through the MediaStreamGraph by wrapping it in a thread
71
 * safe nsMainThreadPtrHandle, since it cannot be used directly off the main
72
 * thread. We can compare two PrincipalHandles to each other on any thread, but
73
 * they can only be created and converted back to nsIPrincipal* on main thread.
74
 */
75
typedef nsMainThreadPtrHandle<nsIPrincipal> PrincipalHandle;
76
77
inline PrincipalHandle MakePrincipalHandle(nsIPrincipal* aPrincipal)
78
{
79
  RefPtr<nsMainThreadPtrHolder<nsIPrincipal>> holder =
80
    new nsMainThreadPtrHolder<nsIPrincipal>(
81
      "MakePrincipalHandle::nsIPrincipal", aPrincipal);
82
  return PrincipalHandle(holder);
83
}
84
85
0
#define PRINCIPAL_HANDLE_NONE nullptr
86
87
inline nsIPrincipal* GetPrincipalFromHandle(const PrincipalHandle& aPrincipalHandle)
88
0
{
89
0
  MOZ_ASSERT(NS_IsMainThread());
90
0
  return aPrincipalHandle.get();
91
0
}
92
93
inline bool PrincipalHandleMatches(const PrincipalHandle& aPrincipalHandle,
94
                                   nsIPrincipal* aOther)
95
0
{
96
0
  if (!aOther) {
97
0
    return false;
98
0
  }
99
0
100
0
  nsIPrincipal* principal = GetPrincipalFromHandle(aPrincipalHandle);
101
0
  if (!principal) {
102
0
    return false;
103
0
  }
104
0
105
0
  bool result;
106
0
  if (NS_FAILED(principal->Equals(aOther, &result))) {
107
0
    NS_ERROR("Principal check failed");
108
0
    return false;
109
0
  }
110
0
111
0
  return result;
112
0
}
113
114
/**
115
 * A MediaSegment is a chunk of media data sequential in time. Different
116
 * types of data have different subclasses of MediaSegment, all inheriting
117
 * from MediaSegmentBase.
118
 * All MediaSegment data is timed using StreamTime. The actual tick rate
119
 * is defined on a per-track basis. For some track types, this can be
120
 * a fixed constant for all tracks of that type (e.g. 1MHz for video).
121
 *
122
 * Each media segment defines a concept of "null media data" (e.g. silence
123
 * for audio or "no video frame" for video), which can be efficiently
124
 * represented. This is used for padding.
125
 */
126
class MediaSegment {
127
public:
128
  MediaSegment(const MediaSegment&) = delete;
129
  MediaSegment& operator= (const MediaSegment&) = delete;
130
131
  virtual ~MediaSegment()
132
  {
133
    MOZ_COUNT_DTOR(MediaSegment);
134
  }
135
136
  enum Type {
137
    AUDIO,
138
    VIDEO,
139
    TYPE_COUNT
140
  };
141
142
  /**
143
   * Gets the total duration of the segment.
144
   */
145
  StreamTime GetDuration() const { return mDuration; }
146
  Type GetType() const { return mType; }
147
148
  /**
149
   * Gets the last principal id that was appended to this segment.
150
   */
151
  const PrincipalHandle& GetLastPrincipalHandle() const { return mLastPrincipalHandle; }
152
  /**
153
   * Called by the MediaStreamGraph as it appends a chunk with a different
154
   * principal id than the current one.
155
   */
156
  void SetLastPrincipalHandle(PrincipalHandle aLastPrincipalHandle)
157
  {
158
    mLastPrincipalHandle = std::forward<PrincipalHandle>(aLastPrincipalHandle);
159
  }
160
161
  /**
162
   * Returns true if all chunks in this segment are null.
163
   */
164
  virtual bool IsNull() const = 0;
165
166
  /**
167
   * Create a MediaSegment of the same type.
168
   */
169
  virtual MediaSegment* CreateEmptyClone() const = 0;
170
  /**
171
   * Moves contents of aSource to the end of this segment.
172
   */
173
  virtual void AppendFrom(MediaSegment* aSource) = 0;
174
  /**
175
   * Append a slice of aSource to this segment.
176
   */
177
  virtual void AppendSlice(const MediaSegment& aSource,
178
                           StreamTime aStart, StreamTime aEnd) = 0;
179
  /**
180
   * Replace all contents up to aDuration with null data.
181
   */
182
  virtual void ForgetUpTo(StreamTime aDuration) = 0;
183
  /**
184
   * Forget all data buffered after a given point
185
   */
186
  virtual void FlushAfter(StreamTime aNewEnd) = 0;
187
  /**
188
   * Insert aDuration of null data at the start of the segment.
189
   */
190
  virtual void InsertNullDataAtStart(StreamTime aDuration) = 0;
191
  /**
192
   * Insert aDuration of null data at the end of the segment.
193
   */
194
  virtual void AppendNullData(StreamTime aDuration) = 0;
195
  /**
196
   * Replace contents with disabled (silence/black) data of the same duration
197
   */
198
  virtual void ReplaceWithDisabled() = 0;
199
  /**
200
   * Replace contents with null data of the same duration
201
   */
202
  virtual void ReplaceWithNull() = 0;
203
  /**
204
   * Remove all contents, setting duration to 0.
205
   */
206
  virtual void Clear() = 0;
207
208
  virtual size_t SizeOfExcludingThis(MallocSizeOf aMallocSizeOf) const
209
  {
210
    return 0;
211
  }
212
213
  virtual size_t SizeOfIncludingThis(MallocSizeOf aMallocSizeOf) const
214
  {
215
    return aMallocSizeOf(this) + SizeOfExcludingThis(aMallocSizeOf);
216
  }
217
218
protected:
219
  explicit MediaSegment(Type aType)
220
    : mDuration(0), mType(aType), mLastPrincipalHandle(PRINCIPAL_HANDLE_NONE)
221
  {
222
    MOZ_COUNT_CTOR(MediaSegment);
223
  }
224
225
  MediaSegment(MediaSegment&& aSegment)
226
    : mDuration(std::move(aSegment.mDuration))
227
    , mType(std::move(aSegment.mType))
228
    , mLastPrincipalHandle(std::move(aSegment.mLastPrincipalHandle))
229
0
  {
230
0
    MOZ_COUNT_CTOR(MediaSegment);
231
0
  }
232
233
  StreamTime mDuration; // total of mDurations of all chunks
234
  Type mType;
235
236
  // The latest principal handle that the MediaStreamGraph has processed for
237
  // this segment.
238
  PrincipalHandle mLastPrincipalHandle;
239
};
240
241
/**
242
 * C is the implementation class subclassed from MediaSegmentBase.
243
 * C must contain a Chunk class.
244
 */
245
template <class C, class Chunk> class MediaSegmentBase : public MediaSegment {
246
public:
247
  bool IsNull() const override
248
0
  {
249
0
    for (typename C::ConstChunkIterator iter(*this); !iter.IsEnded(); iter.Next()) {
250
0
      if (!iter->IsNull()) {
251
0
        return false;
252
0
      }
253
0
    }
254
0
    return true;
255
0
  }
256
  MediaSegment* CreateEmptyClone() const override
257
0
  {
258
0
    return new C();
259
0
  }
260
  void AppendFrom(MediaSegment* aSource) override
261
0
  {
262
0
    NS_ASSERTION(aSource->GetType() == C::StaticType(), "Wrong type");
263
0
    AppendFromInternal(static_cast<C*>(aSource));
264
0
  }
265
  void AppendFrom(C* aSource)
266
  {
267
    AppendFromInternal(aSource);
268
  }
269
  void AppendSlice(const MediaSegment& aSource,
270
                   StreamTime aStart, StreamTime aEnd) override
271
0
  {
272
0
    NS_ASSERTION(aSource.GetType() == C::StaticType(), "Wrong type");
273
0
    AppendSliceInternal(static_cast<const C&>(aSource), aStart, aEnd);
274
0
  }
275
  void AppendSlice(const C& aOther, StreamTime aStart, StreamTime aEnd)
276
  {
277
    AppendSliceInternal(aOther, aStart, aEnd);
278
  }
279
  /**
280
   * Replace the first aDuration ticks with null media data, because the data
281
   * will not be required again.
282
   */
283
  void ForgetUpTo(StreamTime aDuration) override
284
0
  {
285
0
    if (mChunks.IsEmpty() || aDuration <= 0) {
286
0
      return;
287
0
    }
288
0
    if (mChunks[0].IsNull()) {
289
0
      StreamTime extraToForget = std::min(aDuration, mDuration) - mChunks[0].GetDuration();
290
0
      if (extraToForget > 0) {
291
0
        RemoveLeading(extraToForget, 1);
292
0
        mChunks[0].mDuration += extraToForget;
293
0
        mDuration += extraToForget;
294
0
      }
295
0
      return;
296
0
    }
297
0
    RemoveLeading(aDuration, 0);
298
0
    mChunks.InsertElementAt(0)->SetNull(aDuration);
299
0
    mDuration += aDuration;
300
0
  }
301
  void FlushAfter(StreamTime aNewEnd) override
302
0
  {
303
0
    if (mChunks.IsEmpty()) {
304
0
      return;
305
0
    }
306
0
307
0
    if (mChunks[0].IsNull()) {
308
0
      StreamTime extraToKeep = aNewEnd - mChunks[0].GetDuration();
309
0
      if (extraToKeep < 0) {
310
0
        // reduce the size of the Null, get rid of everthing else
311
0
        mChunks[0].SetNull(aNewEnd);
312
0
        extraToKeep = 0;
313
0
      }
314
0
      RemoveTrailing(extraToKeep, 1);
315
0
    } else {
316
0
      if (aNewEnd > mDuration) {
317
0
        NS_ASSERTION(aNewEnd <= mDuration, "can't add data in FlushAfter");
318
0
        return;
319
0
      }
320
0
      RemoveTrailing(aNewEnd, 0);
321
0
    }
322
0
    mDuration = aNewEnd;
323
0
  }
324
  void InsertNullDataAtStart(StreamTime aDuration) override
325
0
  {
326
0
    if (aDuration <= 0) {
327
0
      return;
328
0
    }
329
0
    if (!mChunks.IsEmpty() && mChunks[0].IsNull()) {
330
0
      mChunks[0].mDuration += aDuration;
331
0
    } else {
332
0
      mChunks.InsertElementAt(0)->SetNull(aDuration);
333
0
    }
334
0
#ifdef MOZILLA_INTERNAL_API
335
0
    mChunks[0].mTimeStamp = mozilla::TimeStamp::Now();
336
0
#endif
337
0
    mDuration += aDuration;
338
0
  }
339
  void AppendNullData(StreamTime aDuration) override
340
0
  {
341
0
    if (aDuration <= 0) {
342
0
      return;
343
0
    }
344
0
    if (!mChunks.IsEmpty() && mChunks[mChunks.Length() - 1].IsNull()) {
345
0
      mChunks[mChunks.Length() - 1].mDuration += aDuration;
346
0
    } else {
347
0
      mChunks.AppendElement()->SetNull(aDuration);
348
0
    }
349
0
    mDuration += aDuration;
350
0
  }
351
  void ReplaceWithDisabled() override
352
0
  {
353
0
    if (GetType() != AUDIO) {
354
0
      MOZ_CRASH("Disabling unknown segment type");
355
0
    }
356
0
    ReplaceWithNull();
357
0
  }
358
  void ReplaceWithNull() override
359
0
  {
360
0
    StreamTime duration = GetDuration();
361
0
    Clear();
362
0
    AppendNullData(duration);
363
0
  }
364
  void Clear() override
365
0
  {
366
0
    mDuration = 0;
367
0
    mChunks.ClearAndRetainStorage();
368
0
    mChunks.SetCapacity(DEFAULT_SEGMENT_CAPACITY);
369
0
  }
370
371
  class ChunkIterator {
372
  public:
373
    explicit ChunkIterator(MediaSegmentBase<C, Chunk>& aSegment)
374
0
      : mSegment(aSegment), mIndex(0) {}
375
0
    bool IsEnded() { return mIndex >= mSegment.mChunks.Length(); }
376
0
    void Next() { ++mIndex; }
377
0
    Chunk& operator*() { return mSegment.mChunks[mIndex]; }
378
    Chunk* operator->() { return &mSegment.mChunks[mIndex]; }
379
  private:
380
    MediaSegmentBase<C, Chunk>& mSegment;
381
    uint32_t mIndex;
382
  };
383
  class ConstChunkIterator {
384
  public:
385
    explicit ConstChunkIterator(const MediaSegmentBase<C, Chunk>& aSegment)
386
      : mSegment(aSegment), mIndex(0) {}
387
    bool IsEnded() { return mIndex >= mSegment.mChunks.Length(); }
388
    void Next() { ++mIndex; }
389
    const Chunk& operator*() { return mSegment.mChunks[mIndex]; }
390
0
    const Chunk* operator->() { return &mSegment.mChunks[mIndex]; }
391
  private:
392
    const MediaSegmentBase<C, Chunk>& mSegment;
393
    uint32_t mIndex;
394
  };
395
396
  Chunk* FindChunkContaining(StreamTime aOffset, StreamTime* aStart = nullptr)
397
  {
398
    if (aOffset < 0) {
399
      return nullptr;
400
    }
401
    StreamTime offset = 0;
402
    for (uint32_t i = 0; i < mChunks.Length(); ++i) {
403
      Chunk& c = mChunks[i];
404
      StreamTime nextOffset = offset + c.GetDuration();
405
      if (aOffset < nextOffset) {
406
        if (aStart) {
407
          *aStart = offset;
408
        }
409
        return &c;
410
      }
411
      offset = nextOffset;
412
    }
413
    return nullptr;
414
  }
415
416
  void RemoveLeading(StreamTime aDuration)
417
  {
418
    RemoveLeading(aDuration, 0);
419
  }
420
421
#ifdef MOZILLA_INTERNAL_API
422
  void GetStartTime(TimeStamp &aTime) {
423
    aTime = mChunks[0].mTimeStamp;
424
  }
425
#endif
426
427
  size_t SizeOfExcludingThis(MallocSizeOf aMallocSizeOf) const override
428
0
  {
429
0
    size_t amount = mChunks.ShallowSizeOfExcludingThis(aMallocSizeOf);
430
0
    for (size_t i = 0; i < mChunks.Length(); i++) {
431
0
      amount += mChunks[i].SizeOfExcludingThisIfUnshared(aMallocSizeOf);
432
0
    }
433
0
    return amount;
434
0
  }
435
436
  size_t SizeOfIncludingThis(MallocSizeOf aMallocSizeOf) const override
437
0
  {
438
0
    return aMallocSizeOf(this) + SizeOfExcludingThis(aMallocSizeOf);
439
0
  }
440
441
  Chunk* GetLastChunk()
442
  {
443
    if (mChunks.IsEmpty()) {
444
      return nullptr;
445
    }
446
    return &mChunks[mChunks.Length() - 1];
447
  }
448
449
protected:
450
  explicit MediaSegmentBase(Type aType)
451
    : MediaSegment(aType)
452
    , mChunks()
453
0
  {}
454
455
  MediaSegmentBase(MediaSegmentBase&& aSegment)
456
    : MediaSegment(std::move(aSegment))
457
    , mChunks()
458
#ifdef MOZILLA_INTERNAL_API
459
    , mTimeStamp(std::move(aSegment.mTimeStamp))
460
#endif
461
0
  {
462
0
    mChunks.SwapElements(aSegment.mChunks);
463
0
    MOZ_ASSERT(mChunks.Capacity() >= DEFAULT_SEGMENT_CAPACITY,
464
0
               "Capacity must be retained in self after swap");
465
0
    MOZ_ASSERT(aSegment.mChunks.Capacity() >= DEFAULT_SEGMENT_CAPACITY,
466
0
               "Capacity must be retained in other after swap");
467
0
  }
468
469
  /**
470
   * Appends the contents of aSource to this segment, clearing aSource.
471
   */
472
  void AppendFromInternal(MediaSegmentBase<C, Chunk>* aSource)
473
0
  {
474
0
    MOZ_ASSERT(aSource->mDuration >= 0);
475
0
    mDuration += aSource->mDuration;
476
0
    aSource->mDuration = 0;
477
0
    size_t offset = 0;
478
0
    if (!mChunks.IsEmpty() && !aSource->mChunks.IsEmpty() &&
479
0
        mChunks[mChunks.Length() - 1].CanCombineWithFollowing(aSource->mChunks[0])) {
480
0
      mChunks[mChunks.Length() - 1].mDuration += aSource->mChunks[0].mDuration;
481
0
      offset = 1;
482
0
    }
483
0
484
0
    for (; offset < aSource->mChunks.Length(); ++offset) {
485
0
      mChunks.AppendElement(std::move(aSource->mChunks[offset]));
486
0
    }
487
0
488
0
    aSource->mChunks.ClearAndRetainStorage();
489
0
    MOZ_ASSERT(aSource->mChunks.Capacity() >= DEFAULT_SEGMENT_CAPACITY,
490
0
               "Capacity must be retained after appending from aSource");
491
0
  }
492
493
  void AppendSliceInternal(const MediaSegmentBase<C, Chunk>& aSource,
494
                           StreamTime aStart, StreamTime aEnd)
495
0
  {
496
0
    MOZ_ASSERT(aStart <= aEnd, "Endpoints inverted");
497
0
    NS_ASSERTION(aStart >= 0 && aEnd <= aSource.mDuration, "Slice out of range");
498
0
    mDuration += aEnd - aStart;
499
0
    StreamTime offset = 0;
500
0
    for (uint32_t i = 0; i < aSource.mChunks.Length() && offset < aEnd; ++i) {
501
0
      const Chunk& c = aSource.mChunks[i];
502
0
      StreamTime start = std::max(aStart, offset);
503
0
      StreamTime nextOffset = offset + c.GetDuration();
504
0
      StreamTime end = std::min(aEnd, nextOffset);
505
0
      if (start < end) {
506
0
        if (!mChunks.IsEmpty() &&
507
0
            mChunks[mChunks.Length() - 1].CanCombineWithFollowing(c)) {
508
0
          MOZ_ASSERT(start - offset >= 0 && end - offset <= aSource.mDuration,
509
0
                     "Slice out of bounds");
510
0
          mChunks[mChunks.Length() - 1].mDuration += end - start;
511
0
        } else {
512
0
          mChunks.AppendElement(c)->SliceTo(start - offset, end - offset);
513
0
        }
514
0
      }
515
0
      offset = nextOffset;
516
0
    }
517
0
  }
518
519
  Chunk* AppendChunk(StreamTime aDuration)
520
0
  {
521
0
    MOZ_ASSERT(aDuration >= 0);
522
0
    Chunk* c = mChunks.AppendElement();
523
0
    c->mDuration = aDuration;
524
0
    mDuration += aDuration;
525
0
    return c;
526
0
  }
527
528
  void RemoveLeading(StreamTime aDuration, uint32_t aStartIndex)
529
0
  {
530
0
    NS_ASSERTION(aDuration >= 0, "Can't remove negative duration");
531
0
    StreamTime t = aDuration;
532
0
    uint32_t chunksToRemove = 0;
533
0
    for (uint32_t i = aStartIndex; i < mChunks.Length() && t > 0; ++i) {
534
0
      Chunk* c = &mChunks[i];
535
0
      if (c->GetDuration() > t) {
536
0
        c->SliceTo(t, c->GetDuration());
537
0
        t = 0;
538
0
        break;
539
0
      }
540
0
      t -= c->GetDuration();
541
0
      chunksToRemove = i + 1 - aStartIndex;
542
0
    }
543
0
    if (aStartIndex == 0 && chunksToRemove == mChunks.Length()) {
544
0
      mChunks.ClearAndRetainStorage();
545
0
    } else {
546
0
      mChunks.RemoveElementsAt(aStartIndex, chunksToRemove);
547
0
    }
548
0
    mDuration -= aDuration - t;
549
0
550
0
    MOZ_ASSERT(mChunks.Capacity() >= DEFAULT_SEGMENT_CAPACITY,
551
0
               "Capacity must be retained after removing chunks");
552
0
  }
553
554
  void RemoveTrailing(StreamTime aKeep, uint32_t aStartIndex)
555
0
  {
556
0
    NS_ASSERTION(aKeep >= 0, "Can't keep negative duration");
557
0
    StreamTime t = aKeep;
558
0
    uint32_t i;
559
0
    for (i = aStartIndex; i < mChunks.Length(); ++i) {
560
0
      Chunk* c = &mChunks[i];
561
0
      if (c->GetDuration() > t) {
562
0
        c->SliceTo(0, t);
563
0
        break;
564
0
      }
565
0
      t -= c->GetDuration();
566
0
      if (t == 0) {
567
0
        break;
568
0
      }
569
0
    }
570
0
    if (i+1 < mChunks.Length()) {
571
0
      mChunks.RemoveElementsAt(i+1, mChunks.Length() - (i+1));
572
0
    }
573
0
    MOZ_ASSERT(mChunks.Capacity() >= DEFAULT_SEGMENT_CAPACITY,
574
0
               "Capacity must be retained after removing chunks");
575
0
    // Caller must adjust mDuration
576
0
  }
577
578
  AutoTArray<Chunk, DEFAULT_SEGMENT_CAPACITY> mChunks;
579
#ifdef MOZILLA_INTERNAL_API
580
  mozilla::TimeStamp mTimeStamp;
581
#endif
582
};
583
584
} // namespace mozilla
585
586
#endif /* MOZILLA_MEDIASEGMENT_H_ */