content/media/TrackUnionStream.h

Thu, 22 Jan 2015 13:21:57 +0100

author
Michael Schloh von Bennewitz <michael@schloh.com>
date
Thu, 22 Jan 2015 13:21:57 +0100
branch
TOR_BUG_9701
changeset 15
b8a032363ba2
permissions
-rw-r--r--

Incorporate requested changes from Mozilla in review:
https://bugzilla.mozilla.org/show_bug.cgi?id=1123480#c6

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

mercurial