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

     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_ */

mercurial