|
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_TRACKUNIONSTREAM_H_ |
|
7 #define MOZILLA_TRACKUNIONSTREAM_H_ |
|
8 |
|
9 #include "MediaStreamGraph.h" |
|
10 #include <algorithm> |
|
11 |
|
12 namespace mozilla { |
|
13 |
|
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 |
|
19 |
|
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) {} |
|
31 |
|
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 } |
|
117 |
|
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 } |
|
124 |
|
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 } |
|
135 |
|
136 protected: |
|
137 TrackIDFilterCallback mFilterCallback; |
|
138 |
|
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 }; |
|
162 |
|
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; |
|
170 |
|
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)); |
|
176 |
|
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)); |
|
191 |
|
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"); |
|
226 |
|
227 TrackRate rate = outputTrack->GetRate(); |
|
228 MediaSegment* segment = map->mSegment; |
|
229 MediaStream* source = map->mInputPort->GetSource(); |
|
230 |
|
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; |
|
238 |
|
239 if (aInputTrack->IsEnded() && |
|
240 aInputTrack->GetEndTimeRoundDown() <= inputEnd) { |
|
241 inputTrackEndPoint = aInputTrack->GetEnd(); |
|
242 *aOutputTrackFinished = true; |
|
243 } |
|
244 |
|
245 if (interval.mStart >= interval.mEnd) |
|
246 break; |
|
247 next = interval.mEnd; |
|
248 |
|
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); |
|
258 |
|
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. |
|
315 |
|
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 } |
|
345 |
|
346 nsTArray<TrackMapEntry> mTrackMap; |
|
347 TrackID mMaxTrackID; |
|
348 }; |
|
349 |
|
350 } |
|
351 |
|
352 #endif /* MOZILLA_MEDIASTREAMGRAPH_H_ */ |