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  * We pass the principal through the MediaStreamGraph by wrapping it in a thread
61  * safe nsMainThreadPtrHandle, since it cannot be used directly off the main
62  * thread. We can compare two PrincipalHandles to each other on any thread, but
63  * they can only be created and converted back to nsIPrincipal* on main thread.
64  */
65 typedef nsMainThreadPtrHandle<nsIPrincipal> PrincipalHandle;
66 
MakePrincipalHandle(nsIPrincipal * aPrincipal)67 inline PrincipalHandle MakePrincipalHandle(nsIPrincipal* aPrincipal) {
68   RefPtr<nsMainThreadPtrHolder<nsIPrincipal>> holder =
69       new nsMainThreadPtrHolder<nsIPrincipal>(
70           "MakePrincipalHandle::nsIPrincipal", aPrincipal);
71   return PrincipalHandle(holder);
72 }
73 
74 #define PRINCIPAL_HANDLE_NONE nullptr
75 
GetPrincipalFromHandle(PrincipalHandle & aPrincipalHandle)76 inline nsIPrincipal* GetPrincipalFromHandle(PrincipalHandle& aPrincipalHandle) {
77   MOZ_ASSERT(NS_IsMainThread());
78   return aPrincipalHandle.get();
79 }
80 
PrincipalHandleMatches(PrincipalHandle & aPrincipalHandle,nsIPrincipal * aOther)81 inline bool PrincipalHandleMatches(PrincipalHandle& aPrincipalHandle,
82                                    nsIPrincipal* aOther) {
83   if (!aOther) {
84     return false;
85   }
86 
87   nsIPrincipal* principal = GetPrincipalFromHandle(aPrincipalHandle);
88   if (!principal) {
89     return false;
90   }
91 
92   bool result;
93   if (NS_FAILED(principal->Equals(aOther, &result))) {
94     NS_ERROR("Principal check failed");
95     return false;
96   }
97 
98   return result;
99 }
100 
101 /**
102  * A MediaSegment is a chunk of media data sequential in time. Different
103  * types of data have different subclasses of MediaSegment, all inheriting
104  * from MediaSegmentBase.
105  * All MediaSegment data is timed using StreamTime. The actual tick rate
106  * is defined on a per-track basis. For some track types, this can be
107  * a fixed constant for all tracks of that type (e.g. 1MHz for video).
108  *
109  * Each media segment defines a concept of "null media data" (e.g. silence
110  * for audio or "no video frame" for video), which can be efficiently
111  * represented. This is used for padding.
112  */
113 class MediaSegment {
114  public:
115   MediaSegment(const MediaSegment&) = delete;
116   MediaSegment& operator=(const MediaSegment&) = delete;
117 
~MediaSegment()118   virtual ~MediaSegment() { MOZ_COUNT_DTOR(MediaSegment); }
119 
120   enum Type { AUDIO, VIDEO, TYPE_COUNT };
121 
122   /**
123    * Gets the total duration of the segment.
124    */
GetDuration()125   StreamTime GetDuration() const { return mDuration; }
GetType()126   Type GetType() const { return mType; }
127 
128   /**
129    * Gets the last principal id that was appended to this segment.
130    */
GetLastPrincipalHandle()131   PrincipalHandle GetLastPrincipalHandle() const {
132     return mLastPrincipalHandle;
133   }
134   /**
135    * Called by the MediaStreamGraph as it appends a chunk with a different
136    * principal id than the current one.
137    */
SetLastPrincipalHandle(const PrincipalHandle & aLastPrincipalHandle)138   void SetLastPrincipalHandle(const PrincipalHandle& aLastPrincipalHandle) {
139     mLastPrincipalHandle = aLastPrincipalHandle;
140   }
141 
142   /**
143    * Returns true if all chunks in this segment are null.
144    */
145   virtual bool IsNull() const = 0;
146 
147   /**
148    * Create a MediaSegment of the same type.
149    */
150   virtual MediaSegment* CreateEmptyClone() const = 0;
151   /**
152    * Moves contents of aSource to the end of this segment.
153    */
154   virtual void AppendFrom(MediaSegment* aSource) = 0;
155   /**
156    * Append a slice of aSource to this segment.
157    */
158   virtual void AppendSlice(const MediaSegment& aSource, StreamTime aStart,
159                            StreamTime aEnd) = 0;
160   /**
161    * Replace all contents up to aDuration with null data.
162    */
163   virtual void ForgetUpTo(StreamTime aDuration) = 0;
164   /**
165    * Forget all data buffered after a given point
166    */
167   virtual void FlushAfter(StreamTime aNewEnd) = 0;
168   /**
169    * Insert aDuration of null data at the start of the segment.
170    */
171   virtual void InsertNullDataAtStart(StreamTime aDuration) = 0;
172   /**
173    * Insert aDuration of null data at the end of the segment.
174    */
175   virtual void AppendNullData(StreamTime aDuration) = 0;
176   /**
177    * Replace contents with disabled (silence/black) data of the same duration
178    */
179   virtual void ReplaceWithDisabled() = 0;
180   /**
181    * Replace contents with null data of the same duration
182    */
183   virtual void ReplaceWithNull() = 0;
184   /**
185    * Remove all contents, setting duration to 0.
186    */
187   virtual void Clear() = 0;
188 
SizeOfExcludingThis(MallocSizeOf aMallocSizeOf)189   virtual size_t SizeOfExcludingThis(MallocSizeOf aMallocSizeOf) const {
190     return 0;
191   }
192 
SizeOfIncludingThis(MallocSizeOf aMallocSizeOf)193   virtual size_t SizeOfIncludingThis(MallocSizeOf aMallocSizeOf) const {
194     return aMallocSizeOf(this) + SizeOfExcludingThis(aMallocSizeOf);
195   }
196 
197  protected:
MediaSegment(Type aType)198   explicit MediaSegment(Type aType)
199       : mDuration(0),
200         mType(aType),
201         mLastPrincipalHandle(PRINCIPAL_HANDLE_NONE) {
202     MOZ_COUNT_CTOR(MediaSegment);
203   }
204 
MediaSegment(MediaSegment && aSegment)205   MediaSegment(MediaSegment&& aSegment)
206       : mDuration(Move(aSegment.mDuration)),
207         mType(Move(aSegment.mType)),
208         mLastPrincipalHandle(Move(aSegment.mLastPrincipalHandle)) {
209     MOZ_COUNT_CTOR(MediaSegment);
210   }
211 
212   StreamTime mDuration;  // total of mDurations of all chunks
213   Type mType;
214 
215   // The latest principal handle that the MediaStreamGraph has processed for
216   // this segment.
217   PrincipalHandle mLastPrincipalHandle;
218 };
219 
220 /**
221  * C is the implementation class subclassed from MediaSegmentBase.
222  * C must contain a Chunk class.
223  */
224 template <class C, class Chunk>
225 class MediaSegmentBase : public MediaSegment {
226  public:
IsNull()227   bool IsNull() const override {
228     for (typename C::ConstChunkIterator iter(*this); !iter.IsEnded();
229          iter.Next()) {
230       if (!iter->IsNull()) {
231         return false;
232       }
233     }
234     return true;
235   }
CreateEmptyClone()236   MediaSegment* CreateEmptyClone() const override { return new C(); }
AppendFrom(MediaSegment * aSource)237   void AppendFrom(MediaSegment* aSource) override {
238     NS_ASSERTION(aSource->GetType() == C::StaticType(), "Wrong type");
239     AppendFromInternal(static_cast<C*>(aSource));
240   }
AppendFrom(C * aSource)241   void AppendFrom(C* aSource) { AppendFromInternal(aSource); }
AppendSlice(const MediaSegment & aSource,StreamTime aStart,StreamTime aEnd)242   void AppendSlice(const MediaSegment& aSource, StreamTime aStart,
243                    StreamTime aEnd) override {
244     NS_ASSERTION(aSource.GetType() == C::StaticType(), "Wrong type");
245     AppendSliceInternal(static_cast<const C&>(aSource), aStart, aEnd);
246   }
AppendSlice(const C & aOther,StreamTime aStart,StreamTime aEnd)247   void AppendSlice(const C& aOther, StreamTime aStart, StreamTime aEnd) {
248     AppendSliceInternal(aOther, aStart, aEnd);
249   }
250   /**
251    * Replace the first aDuration ticks with null media data, because the data
252    * will not be required again.
253    */
ForgetUpTo(StreamTime aDuration)254   void ForgetUpTo(StreamTime aDuration) override {
255     if (mChunks.IsEmpty() || aDuration <= 0) {
256       return;
257     }
258     if (mChunks[0].IsNull()) {
259       StreamTime extraToForget =
260           std::min(aDuration, mDuration) - mChunks[0].GetDuration();
261       if (extraToForget > 0) {
262         RemoveLeading(extraToForget, 1);
263         mChunks[0].mDuration += extraToForget;
264         mDuration += extraToForget;
265       }
266       return;
267     }
268     RemoveLeading(aDuration, 0);
269     mChunks.InsertElementAt(0)->SetNull(aDuration);
270     mDuration += aDuration;
271   }
FlushAfter(StreamTime aNewEnd)272   void FlushAfter(StreamTime aNewEnd) override {
273     if (mChunks.IsEmpty()) {
274       return;
275     }
276 
277     if (mChunks[0].IsNull()) {
278       StreamTime extraToKeep = aNewEnd - mChunks[0].GetDuration();
279       if (extraToKeep < 0) {
280         // reduce the size of the Null, get rid of everthing else
281         mChunks[0].SetNull(aNewEnd);
282         extraToKeep = 0;
283       }
284       RemoveTrailing(extraToKeep, 1);
285     } else {
286       if (aNewEnd > mDuration) {
287         NS_ASSERTION(aNewEnd <= mDuration, "can't add data in FlushAfter");
288         return;
289       }
290       RemoveTrailing(aNewEnd, 0);
291     }
292     mDuration = aNewEnd;
293   }
InsertNullDataAtStart(StreamTime aDuration)294   void InsertNullDataAtStart(StreamTime aDuration) override {
295     if (aDuration <= 0) {
296       return;
297     }
298     if (!mChunks.IsEmpty() && mChunks[0].IsNull()) {
299       mChunks[0].mDuration += aDuration;
300     } else {
301       mChunks.InsertElementAt(0)->SetNull(aDuration);
302     }
303 #ifdef MOZILLA_INTERNAL_API
304     mChunks[0].mTimeStamp = mozilla::TimeStamp::Now();
305 #endif
306     mDuration += aDuration;
307   }
AppendNullData(StreamTime aDuration)308   void AppendNullData(StreamTime aDuration) override {
309     if (aDuration <= 0) {
310       return;
311     }
312     if (!mChunks.IsEmpty() && mChunks[mChunks.Length() - 1].IsNull()) {
313       mChunks[mChunks.Length() - 1].mDuration += aDuration;
314     } else {
315       mChunks.AppendElement()->SetNull(aDuration);
316     }
317     mDuration += aDuration;
318   }
ReplaceWithDisabled()319   void ReplaceWithDisabled() override {
320     if (GetType() != AUDIO) {
321       MOZ_CRASH("Disabling unknown segment type");
322     }
323     ReplaceWithNull();
324   }
ReplaceWithNull()325   void ReplaceWithNull() override {
326     StreamTime duration = GetDuration();
327     Clear();
328     AppendNullData(duration);
329   }
Clear()330   void Clear() override {
331     mDuration = 0;
332     mChunks.Clear();
333   }
334 
335   class ChunkIterator {
336    public:
ChunkIterator(MediaSegmentBase<C,Chunk> & aSegment)337     explicit ChunkIterator(MediaSegmentBase<C, Chunk>& aSegment)
338         : mSegment(aSegment), mIndex(0) {}
IsEnded()339     bool IsEnded() { return mIndex >= mSegment.mChunks.Length(); }
Next()340     void Next() { ++mIndex; }
341     Chunk& operator*() { return mSegment.mChunks[mIndex]; }
342     Chunk* operator->() { return &mSegment.mChunks[mIndex]; }
343 
344    private:
345     MediaSegmentBase<C, Chunk>& mSegment;
346     uint32_t mIndex;
347   };
348   class ConstChunkIterator {
349    public:
ConstChunkIterator(const MediaSegmentBase<C,Chunk> & aSegment)350     explicit ConstChunkIterator(const MediaSegmentBase<C, Chunk>& aSegment)
351         : mSegment(aSegment), mIndex(0) {}
IsEnded()352     bool IsEnded() { return mIndex >= mSegment.mChunks.Length(); }
Next()353     void Next() { ++mIndex; }
354     const Chunk& operator*() { return mSegment.mChunks[mIndex]; }
355     const Chunk* operator->() { return &mSegment.mChunks[mIndex]; }
356 
357    private:
358     const MediaSegmentBase<C, Chunk>& mSegment;
359     uint32_t mIndex;
360   };
361 
362   Chunk* FindChunkContaining(StreamTime aOffset, StreamTime* aStart = nullptr) {
363     if (aOffset < 0) {
364       return nullptr;
365     }
366     StreamTime offset = 0;
367     for (uint32_t i = 0; i < mChunks.Length(); ++i) {
368       Chunk& c = mChunks[i];
369       StreamTime nextOffset = offset + c.GetDuration();
370       if (aOffset < nextOffset) {
371         if (aStart) {
372           *aStart = offset;
373         }
374         return &c;
375       }
376       offset = nextOffset;
377     }
378     return nullptr;
379   }
380 
RemoveLeading(StreamTime aDuration)381   void RemoveLeading(StreamTime aDuration) { RemoveLeading(aDuration, 0); }
382 
383 #ifdef MOZILLA_INTERNAL_API
GetStartTime(TimeStamp & aTime)384   void GetStartTime(TimeStamp& aTime) { aTime = mChunks[0].mTimeStamp; }
385 #endif
386 
SizeOfExcludingThis(MallocSizeOf aMallocSizeOf)387   size_t SizeOfExcludingThis(MallocSizeOf aMallocSizeOf) const override {
388     size_t amount = mChunks.ShallowSizeOfExcludingThis(aMallocSizeOf);
389     for (size_t i = 0; i < mChunks.Length(); i++) {
390       amount += mChunks[i].SizeOfExcludingThisIfUnshared(aMallocSizeOf);
391     }
392     return amount;
393   }
394 
SizeOfIncludingThis(MallocSizeOf aMallocSizeOf)395   size_t SizeOfIncludingThis(MallocSizeOf aMallocSizeOf) const override {
396     return aMallocSizeOf(this) + SizeOfExcludingThis(aMallocSizeOf);
397   }
398 
GetLastChunk()399   Chunk* GetLastChunk() {
400     if (mChunks.IsEmpty()) {
401       return nullptr;
402     }
403     return &mChunks[mChunks.Length() - 1];
404   }
405 
406  protected:
MediaSegmentBase(Type aType)407   explicit MediaSegmentBase(Type aType) : MediaSegment(aType) {}
408 
MediaSegmentBase(MediaSegmentBase && aSegment)409   MediaSegmentBase(MediaSegmentBase&& aSegment)
410       : MediaSegment(Move(aSegment)),
411         mChunks(Move(aSegment.mChunks))
412 #ifdef MOZILLA_INTERNAL_API
413         ,
414         mTimeStamp(Move(aSegment.mTimeStamp))
415 #endif
416   {
417   }
418 
419   /**
420    * Appends the contents of aSource to this segment, clearing aSource.
421    */
AppendFromInternal(MediaSegmentBase<C,Chunk> * aSource)422   void AppendFromInternal(MediaSegmentBase<C, Chunk>* aSource) {
423     MOZ_ASSERT(aSource->mDuration >= 0);
424     mDuration += aSource->mDuration;
425     aSource->mDuration = 0;
426     if (!mChunks.IsEmpty() && !aSource->mChunks.IsEmpty() &&
427         mChunks[mChunks.Length() - 1].CanCombineWithFollowing(
428             aSource->mChunks[0])) {
429       mChunks[mChunks.Length() - 1].mDuration += aSource->mChunks[0].mDuration;
430       aSource->mChunks.RemoveElementAt(0);
431     }
432     mChunks.AppendElements(Move(aSource->mChunks));
433   }
434 
AppendSliceInternal(const MediaSegmentBase<C,Chunk> & aSource,StreamTime aStart,StreamTime aEnd)435   void AppendSliceInternal(const MediaSegmentBase<C, Chunk>& aSource,
436                            StreamTime aStart, StreamTime aEnd) {
437     MOZ_ASSERT(aStart <= aEnd, "Endpoints inverted");
438     NS_ASSERTION(aStart >= 0 && aEnd <= aSource.mDuration,
439                  "Slice out of range");
440     mDuration += aEnd - aStart;
441     StreamTime offset = 0;
442     for (uint32_t i = 0; i < aSource.mChunks.Length() && offset < aEnd; ++i) {
443       const Chunk& c = aSource.mChunks[i];
444       StreamTime start = std::max(aStart, offset);
445       StreamTime nextOffset = offset + c.GetDuration();
446       StreamTime end = std::min(aEnd, nextOffset);
447       if (start < end) {
448         if (!mChunks.IsEmpty() &&
449             mChunks[mChunks.Length() - 1].CanCombineWithFollowing(c)) {
450           MOZ_ASSERT(start - offset >= 0 && end - offset <= aSource.mDuration,
451                      "Slice out of bounds");
452           mChunks[mChunks.Length() - 1].mDuration += end - start;
453         } else {
454           mChunks.AppendElement(c)->SliceTo(start - offset, end - offset);
455         }
456       }
457       offset = nextOffset;
458     }
459   }
460 
AppendChunk(StreamTime aDuration)461   Chunk* AppendChunk(StreamTime aDuration) {
462     MOZ_ASSERT(aDuration >= 0);
463     Chunk* c = mChunks.AppendElement();
464     c->mDuration = aDuration;
465     mDuration += aDuration;
466     return c;
467   }
468 
RemoveLeading(StreamTime aDuration,uint32_t aStartIndex)469   void RemoveLeading(StreamTime aDuration, uint32_t aStartIndex) {
470     NS_ASSERTION(aDuration >= 0, "Can't remove negative duration");
471     StreamTime t = aDuration;
472     uint32_t chunksToRemove = 0;
473     for (uint32_t i = aStartIndex; i < mChunks.Length() && t > 0; ++i) {
474       Chunk* c = &mChunks[i];
475       if (c->GetDuration() > t) {
476         c->SliceTo(t, c->GetDuration());
477         t = 0;
478         break;
479       }
480       t -= c->GetDuration();
481       chunksToRemove = i + 1 - aStartIndex;
482     }
483     mChunks.RemoveElementsAt(aStartIndex, chunksToRemove);
484     mDuration -= aDuration - t;
485   }
486 
RemoveTrailing(StreamTime aKeep,uint32_t aStartIndex)487   void RemoveTrailing(StreamTime aKeep, uint32_t aStartIndex) {
488     NS_ASSERTION(aKeep >= 0, "Can't keep negative duration");
489     StreamTime t = aKeep;
490     uint32_t i;
491     for (i = aStartIndex; i < mChunks.Length(); ++i) {
492       Chunk* c = &mChunks[i];
493       if (c->GetDuration() > t) {
494         c->SliceTo(0, t);
495         break;
496       }
497       t -= c->GetDuration();
498       if (t == 0) {
499         break;
500       }
501     }
502     if (i + 1 < mChunks.Length()) {
503       mChunks.RemoveElementsAt(i + 1, mChunks.Length() - (i + 1));
504     }
505     // Caller must adjust mDuration
506   }
507 
508   nsTArray<Chunk> mChunks;
509 #ifdef MOZILLA_INTERNAL_API
510   mozilla::TimeStamp mTimeStamp;
511 #endif
512 };
513 
514 }  // namespace mozilla
515 
516 #endif /* MOZILLA_MEDIASEGMENT_H_ */
517