Thu, 22 Jan 2015 13:21:57 +0100
Incorporate requested changes from Mozilla in review:
https://bugzilla.mozilla.org/show_bug.cgi?id=1123480#c6
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/. */
6 #ifndef MOZILLA_TRACKUNIONSTREAM_H_
7 #define MOZILLA_TRACKUNIONSTREAM_H_
9 #include "MediaStreamGraph.h"
10 #include <algorithm>
12 namespace mozilla {
14 #ifdef PR_LOGGING
15 #define STREAM_LOG(type, msg) PR_LOG(gMediaStreamGraphLog, type, msg)
16 #else
17 #define STREAM_LOG(type, msg)
18 #endif
20 /**
21 * See MediaStreamGraph::CreateTrackUnionStream.
22 * This file is only included by MediaStreamGraph.cpp so it's OK to put the
23 * entire implementation in this header file.
24 */
25 class TrackUnionStream : public ProcessedMediaStream {
26 public:
27 TrackUnionStream(DOMMediaStream* aWrapper) :
28 ProcessedMediaStream(aWrapper),
29 mFilterCallback(nullptr),
30 mMaxTrackID(0) {}
32 virtual void RemoveInput(MediaInputPort* aPort) MOZ_OVERRIDE
33 {
34 for (int32_t i = mTrackMap.Length() - 1; i >= 0; --i) {
35 if (mTrackMap[i].mInputPort == aPort) {
36 EndTrack(i);
37 mTrackMap.RemoveElementAt(i);
38 }
39 }
40 ProcessedMediaStream::RemoveInput(aPort);
41 }
42 virtual void ProcessInput(GraphTime aFrom, GraphTime aTo, uint32_t aFlags) MOZ_OVERRIDE
43 {
44 if (IsFinishedOnGraphThread()) {
45 return;
46 }
47 nsAutoTArray<bool,8> mappedTracksFinished;
48 nsAutoTArray<bool,8> mappedTracksWithMatchingInputTracks;
49 for (uint32_t i = 0; i < mTrackMap.Length(); ++i) {
50 mappedTracksFinished.AppendElement(true);
51 mappedTracksWithMatchingInputTracks.AppendElement(false);
52 }
53 bool allFinished = true;
54 bool allHaveCurrentData = true;
55 for (uint32_t i = 0; i < mInputs.Length(); ++i) {
56 MediaStream* stream = mInputs[i]->GetSource();
57 if (!stream->IsFinishedOnGraphThread()) {
58 // XXX we really should check whether 'stream' has finished within time aTo,
59 // not just that it's finishing when all its queued data eventually runs
60 // out.
61 allFinished = false;
62 }
63 if (!stream->HasCurrentData()) {
64 allHaveCurrentData = false;
65 }
66 for (StreamBuffer::TrackIter tracks(stream->GetStreamBuffer());
67 !tracks.IsEnded(); tracks.Next()) {
68 bool found = false;
69 for (uint32_t j = 0; j < mTrackMap.Length(); ++j) {
70 TrackMapEntry* map = &mTrackMap[j];
71 if (map->mInputPort == mInputs[i] && map->mInputTrackID == tracks->GetID()) {
72 bool trackFinished;
73 StreamBuffer::Track* outputTrack = mBuffer.FindTrack(map->mOutputTrackID);
74 if (!outputTrack || outputTrack->IsEnded()) {
75 trackFinished = true;
76 } else {
77 CopyTrackData(tracks.get(), j, aFrom, aTo, &trackFinished);
78 }
79 mappedTracksFinished[j] = trackFinished;
80 mappedTracksWithMatchingInputTracks[j] = true;
81 found = true;
82 break;
83 }
84 }
85 if (!found && (!mFilterCallback || mFilterCallback(tracks.get()))) {
86 bool trackFinished = false;
87 uint32_t mapIndex = AddTrack(mInputs[i], tracks.get(), aFrom);
88 CopyTrackData(tracks.get(), mapIndex, aFrom, aTo, &trackFinished);
89 mappedTracksFinished.AppendElement(trackFinished);
90 mappedTracksWithMatchingInputTracks.AppendElement(true);
91 }
92 }
93 }
94 for (int32_t i = mTrackMap.Length() - 1; i >= 0; --i) {
95 if (mappedTracksFinished[i]) {
96 EndTrack(i);
97 } else {
98 allFinished = false;
99 }
100 if (!mappedTracksWithMatchingInputTracks[i]) {
101 mTrackMap.RemoveElementAt(i);
102 }
103 }
104 if (allFinished && mAutofinish && (aFlags & ALLOW_FINISH)) {
105 // All streams have finished and won't add any more tracks, and
106 // all our tracks have actually finished and been removed from our map,
107 // so we're finished now.
108 FinishOnGraphThread();
109 } else {
110 mBuffer.AdvanceKnownTracksTime(GraphTimeToStreamTime(aTo));
111 }
112 if (allHaveCurrentData) {
113 // We can make progress if we're not blocked
114 mHasCurrentData = true;
115 }
116 }
118 // Consumers may specify a filtering callback to apply to every input track.
119 // Returns true to allow the track to act as an input; false to reject it entirely.
120 typedef bool (*TrackIDFilterCallback)(StreamBuffer::Track*);
121 void SetTrackIDFilter(TrackIDFilterCallback aCallback) {
122 mFilterCallback = aCallback;
123 }
125 // Forward SetTrackEnabled(output_track_id, enabled) to the Source MediaStream,
126 // translating the output track ID into the correct ID in the source.
127 virtual void ForwardTrackEnabled(TrackID aOutputID, bool aEnabled) MOZ_OVERRIDE {
128 for (int32_t i = mTrackMap.Length() - 1; i >= 0; --i) {
129 if (mTrackMap[i].mOutputTrackID == aOutputID) {
130 mTrackMap[i].mInputPort->GetSource()->
131 SetTrackEnabled(mTrackMap[i].mInputTrackID, aEnabled);
132 }
133 }
134 }
136 protected:
137 TrackIDFilterCallback mFilterCallback;
139 // Only non-ended tracks are allowed to persist in this map.
140 struct TrackMapEntry {
141 // mEndOfConsumedInputTicks is the end of the input ticks that we've consumed.
142 // 0 if we haven't consumed any yet.
143 TrackTicks mEndOfConsumedInputTicks;
144 // mEndOfLastInputIntervalInInputStream is the timestamp for the end of the
145 // previous interval which was unblocked for both the input and output
146 // stream, in the input stream's timeline, or -1 if there wasn't one.
147 StreamTime mEndOfLastInputIntervalInInputStream;
148 // mEndOfLastInputIntervalInOutputStream is the timestamp for the end of the
149 // previous interval which was unblocked for both the input and output
150 // stream, in the output stream's timeline, or -1 if there wasn't one.
151 StreamTime mEndOfLastInputIntervalInOutputStream;
152 MediaInputPort* mInputPort;
153 // We keep track IDs instead of track pointers because
154 // tracks can be removed without us being notified (e.g.
155 // when a finished track is forgotten.) When we need a Track*,
156 // we call StreamBuffer::FindTrack, which will return null if
157 // the track has been deleted.
158 TrackID mInputTrackID;
159 TrackID mOutputTrackID;
160 nsAutoPtr<MediaSegment> mSegment;
161 };
163 uint32_t AddTrack(MediaInputPort* aPort, StreamBuffer::Track* aTrack,
164 GraphTime aFrom)
165 {
166 // Use the ID of the source track if we can, otherwise allocate a new
167 // unique ID
168 TrackID id = std::max(mMaxTrackID + 1, aTrack->GetID());
169 mMaxTrackID = id;
171 TrackRate rate = aTrack->GetRate();
172 // Round up the track start time so the track, if anything, starts a
173 // little later than the true time. This means we'll have enough
174 // samples in our input stream to go just beyond the destination time.
175 TrackTicks outputStart = TimeToTicksRoundUp(rate, GraphTimeToStreamTime(aFrom));
177 nsAutoPtr<MediaSegment> segment;
178 segment = aTrack->GetSegment()->CreateEmptyClone();
179 for (uint32_t j = 0; j < mListeners.Length(); ++j) {
180 MediaStreamListener* l = mListeners[j];
181 l->NotifyQueuedTrackChanges(Graph(), id, rate, outputStart,
182 MediaStreamListener::TRACK_EVENT_CREATED,
183 *segment);
184 }
185 segment->AppendNullData(outputStart);
186 StreamBuffer::Track* track =
187 &mBuffer.AddTrack(id, rate, outputStart, segment.forget());
188 STREAM_LOG(PR_LOG_DEBUG, ("TrackUnionStream %p adding track %d for input stream %p track %d, start ticks %lld",
189 this, id, aPort->GetSource(), aTrack->GetID(),
190 (long long)outputStart));
192 TrackMapEntry* map = mTrackMap.AppendElement();
193 map->mEndOfConsumedInputTicks = 0;
194 map->mEndOfLastInputIntervalInInputStream = -1;
195 map->mEndOfLastInputIntervalInOutputStream = -1;
196 map->mInputPort = aPort;
197 map->mInputTrackID = aTrack->GetID();
198 map->mOutputTrackID = track->GetID();
199 map->mSegment = aTrack->GetSegment()->CreateEmptyClone();
200 return mTrackMap.Length() - 1;
201 }
202 void EndTrack(uint32_t aIndex)
203 {
204 StreamBuffer::Track* outputTrack = mBuffer.FindTrack(mTrackMap[aIndex].mOutputTrackID);
205 if (!outputTrack || outputTrack->IsEnded())
206 return;
207 for (uint32_t j = 0; j < mListeners.Length(); ++j) {
208 MediaStreamListener* l = mListeners[j];
209 TrackTicks offset = outputTrack->GetSegment()->GetDuration();
210 nsAutoPtr<MediaSegment> segment;
211 segment = outputTrack->GetSegment()->CreateEmptyClone();
212 l->NotifyQueuedTrackChanges(Graph(), outputTrack->GetID(),
213 outputTrack->GetRate(), offset,
214 MediaStreamListener::TRACK_EVENT_ENDED,
215 *segment);
216 }
217 outputTrack->SetEnded();
218 }
219 void CopyTrackData(StreamBuffer::Track* aInputTrack,
220 uint32_t aMapIndex, GraphTime aFrom, GraphTime aTo,
221 bool* aOutputTrackFinished)
222 {
223 TrackMapEntry* map = &mTrackMap[aMapIndex];
224 StreamBuffer::Track* outputTrack = mBuffer.FindTrack(map->mOutputTrackID);
225 MOZ_ASSERT(outputTrack && !outputTrack->IsEnded(), "Can't copy to ended track");
227 TrackRate rate = outputTrack->GetRate();
228 MediaSegment* segment = map->mSegment;
229 MediaStream* source = map->mInputPort->GetSource();
231 GraphTime next;
232 *aOutputTrackFinished = false;
233 for (GraphTime t = aFrom; t < aTo; t = next) {
234 MediaInputPort::InputInterval interval = map->mInputPort->GetNextInputInterval(t);
235 interval.mEnd = std::min(interval.mEnd, aTo);
236 StreamTime inputEnd = source->GraphTimeToStreamTime(interval.mEnd);
237 TrackTicks inputTrackEndPoint = TRACK_TICKS_MAX;
239 if (aInputTrack->IsEnded() &&
240 aInputTrack->GetEndTimeRoundDown() <= inputEnd) {
241 inputTrackEndPoint = aInputTrack->GetEnd();
242 *aOutputTrackFinished = true;
243 }
245 if (interval.mStart >= interval.mEnd)
246 break;
247 next = interval.mEnd;
249 // Ticks >= startTicks and < endTicks are in the interval
250 StreamTime outputEnd = GraphTimeToStreamTime(interval.mEnd);
251 TrackTicks startTicks = outputTrack->GetEnd();
252 StreamTime outputStart = GraphTimeToStreamTime(interval.mStart);
253 NS_WARN_IF_FALSE(startTicks == TimeToTicksRoundUp(rate, outputStart),
254 "Samples missing");
255 TrackTicks endTicks = TimeToTicksRoundUp(rate, outputEnd);
256 TrackTicks ticks = endTicks - startTicks;
257 StreamTime inputStart = source->GraphTimeToStreamTime(interval.mStart);
259 if (interval.mInputIsBlocked) {
260 // Maybe the input track ended?
261 segment->AppendNullData(ticks);
262 STREAM_LOG(PR_LOG_DEBUG+1, ("TrackUnionStream %p appending %lld ticks of null data to track %d",
263 this, (long long)ticks, outputTrack->GetID()));
264 } else {
265 // Figuring out which samples to use from the input stream is tricky
266 // because its start time and our start time may differ by a fraction
267 // of a tick. Assuming the input track hasn't ended, we have to ensure
268 // that 'ticks' samples are gathered, even though a tick boundary may
269 // occur between outputStart and outputEnd but not between inputStart
270 // and inputEnd.
271 // These are the properties we need to ensure:
272 // 1) Exactly 'ticks' ticks of output are produced, i.e.
273 // inputEndTicks - inputStartTicks = ticks.
274 // 2) inputEndTicks <= aInputTrack->GetSegment()->GetDuration().
275 // 3) In any sequence of intervals where neither stream is blocked,
276 // the content of the input track we use is a contiguous sequence of
277 // ticks with no gaps or overlaps.
278 if (map->mEndOfLastInputIntervalInInputStream != inputStart ||
279 map->mEndOfLastInputIntervalInOutputStream != outputStart) {
280 // Start of a new series of intervals where neither stream is blocked.
281 map->mEndOfConsumedInputTicks = TimeToTicksRoundDown(rate, inputStart) - 1;
282 }
283 TrackTicks inputStartTicks = map->mEndOfConsumedInputTicks;
284 TrackTicks inputEndTicks = inputStartTicks + ticks;
285 map->mEndOfConsumedInputTicks = inputEndTicks;
286 map->mEndOfLastInputIntervalInInputStream = inputEnd;
287 map->mEndOfLastInputIntervalInOutputStream = outputEnd;
288 // Now we prove that the above properties hold:
289 // Property #1: trivial by construction.
290 // Property #3: trivial by construction. Between every two
291 // intervals where both streams are not blocked, the above if condition
292 // is false and mEndOfConsumedInputTicks advances exactly to match
293 // the ticks that were consumed.
294 // Property #2:
295 // Let originalOutputStart be the value of outputStart and originalInputStart
296 // be the value of inputStart when the body of the "if" block was last
297 // executed.
298 // Let allTicks be the sum of the values of 'ticks' computed since then.
299 // The interval [originalInputStart/rate, inputEnd/rate) is the
300 // same length as the interval [originalOutputStart/rate, outputEnd/rate),
301 // so the latter interval can have at most one more integer in it. Thus
302 // TimeToTicksRoundUp(rate, outputEnd) - TimeToTicksRoundUp(rate, originalOutputStart)
303 // <= TimeToTicksRoundDown(rate, inputEnd) - TimeToTicksRoundDown(rate, originalInputStart) + 1
304 // Then
305 // inputEndTicks = TimeToTicksRoundDown(rate, originalInputStart) - 1 + allTicks
306 // = TimeToTicksRoundDown(rate, originalInputStart) - 1 + TimeToTicksRoundUp(rate, outputEnd) - TimeToTicksRoundUp(rate, originalOutputStart)
307 // <= TimeToTicksRoundDown(rate, originalInputStart) - 1 + TimeToTicksRoundDown(rate, inputEnd) - TimeToTicksRoundDown(rate, originalInputStart) + 1
308 // = TimeToTicksRoundDown(rate, inputEnd)
309 // <= inputEnd/rate
310 // (now using the fact that inputEnd <= track->GetEndTimeRoundDown() for a non-ended track)
311 // <= TicksToTimeRoundDown(rate, aInputTrack->GetSegment()->GetDuration())/rate
312 // <= rate*aInputTrack->GetSegment()->GetDuration()/rate
313 // = aInputTrack->GetSegment()->GetDuration()
314 // as required.
316 if (inputStartTicks < 0) {
317 // Data before the start of the track is just null.
318 // We have to add a small amount of delay to ensure that there is
319 // always a sample available if we see an interval that contains a
320 // tick boundary on the output stream's timeline but does not contain
321 // a tick boundary on the input stream's timeline. 1 tick delay is
322 // necessary and sufficient.
323 segment->AppendNullData(-inputStartTicks);
324 inputStartTicks = 0;
325 }
326 if (inputEndTicks > inputStartTicks) {
327 segment->AppendSlice(*aInputTrack->GetSegment(),
328 std::min(inputTrackEndPoint, inputStartTicks),
329 std::min(inputTrackEndPoint, inputEndTicks));
330 }
331 STREAM_LOG(PR_LOG_DEBUG+1, ("TrackUnionStream %p appending %lld ticks of input data to track %d",
332 this, (long long)(std::min(inputTrackEndPoint, inputEndTicks) - std::min(inputTrackEndPoint, inputStartTicks)),
333 outputTrack->GetID()));
334 }
335 ApplyTrackDisabling(outputTrack->GetID(), segment);
336 for (uint32_t j = 0; j < mListeners.Length(); ++j) {
337 MediaStreamListener* l = mListeners[j];
338 l->NotifyQueuedTrackChanges(Graph(), outputTrack->GetID(),
339 outputTrack->GetRate(), startTicks, 0,
340 *segment);
341 }
342 outputTrack->GetSegment()->AppendFrom(segment);
343 }
344 }
346 nsTArray<TrackMapEntry> mTrackMap;
347 TrackID mMaxTrackID;
348 };
350 }
352 #endif /* MOZILLA_MEDIASTREAMGRAPH_H_ */