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