1.1 --- /dev/null Thu Jan 01 00:00:00 1970 +0000 1.2 +++ b/content/media/TrackUnionStream.h Wed Dec 31 06:09:35 2014 +0100 1.3 @@ -0,0 +1,352 @@ 1.4 +/* -*- Mode: C++; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*-*/ 1.5 +/* This Source Code Form is subject to the terms of the Mozilla Public 1.6 + * License, v. 2.0. If a copy of the MPL was not distributed with this file, 1.7 + * You can obtain one at http://mozilla.org/MPL/2.0/. */ 1.8 + 1.9 +#ifndef MOZILLA_TRACKUNIONSTREAM_H_ 1.10 +#define MOZILLA_TRACKUNIONSTREAM_H_ 1.11 + 1.12 +#include "MediaStreamGraph.h" 1.13 +#include <algorithm> 1.14 + 1.15 +namespace mozilla { 1.16 + 1.17 +#ifdef PR_LOGGING 1.18 +#define STREAM_LOG(type, msg) PR_LOG(gMediaStreamGraphLog, type, msg) 1.19 +#else 1.20 +#define STREAM_LOG(type, msg) 1.21 +#endif 1.22 + 1.23 +/** 1.24 + * See MediaStreamGraph::CreateTrackUnionStream. 1.25 + * This file is only included by MediaStreamGraph.cpp so it's OK to put the 1.26 + * entire implementation in this header file. 1.27 + */ 1.28 +class TrackUnionStream : public ProcessedMediaStream { 1.29 +public: 1.30 + TrackUnionStream(DOMMediaStream* aWrapper) : 1.31 + ProcessedMediaStream(aWrapper), 1.32 + mFilterCallback(nullptr), 1.33 + mMaxTrackID(0) {} 1.34 + 1.35 + virtual void RemoveInput(MediaInputPort* aPort) MOZ_OVERRIDE 1.36 + { 1.37 + for (int32_t i = mTrackMap.Length() - 1; i >= 0; --i) { 1.38 + if (mTrackMap[i].mInputPort == aPort) { 1.39 + EndTrack(i); 1.40 + mTrackMap.RemoveElementAt(i); 1.41 + } 1.42 + } 1.43 + ProcessedMediaStream::RemoveInput(aPort); 1.44 + } 1.45 + virtual void ProcessInput(GraphTime aFrom, GraphTime aTo, uint32_t aFlags) MOZ_OVERRIDE 1.46 + { 1.47 + if (IsFinishedOnGraphThread()) { 1.48 + return; 1.49 + } 1.50 + nsAutoTArray<bool,8> mappedTracksFinished; 1.51 + nsAutoTArray<bool,8> mappedTracksWithMatchingInputTracks; 1.52 + for (uint32_t i = 0; i < mTrackMap.Length(); ++i) { 1.53 + mappedTracksFinished.AppendElement(true); 1.54 + mappedTracksWithMatchingInputTracks.AppendElement(false); 1.55 + } 1.56 + bool allFinished = true; 1.57 + bool allHaveCurrentData = true; 1.58 + for (uint32_t i = 0; i < mInputs.Length(); ++i) { 1.59 + MediaStream* stream = mInputs[i]->GetSource(); 1.60 + if (!stream->IsFinishedOnGraphThread()) { 1.61 + // XXX we really should check whether 'stream' has finished within time aTo, 1.62 + // not just that it's finishing when all its queued data eventually runs 1.63 + // out. 1.64 + allFinished = false; 1.65 + } 1.66 + if (!stream->HasCurrentData()) { 1.67 + allHaveCurrentData = false; 1.68 + } 1.69 + for (StreamBuffer::TrackIter tracks(stream->GetStreamBuffer()); 1.70 + !tracks.IsEnded(); tracks.Next()) { 1.71 + bool found = false; 1.72 + for (uint32_t j = 0; j < mTrackMap.Length(); ++j) { 1.73 + TrackMapEntry* map = &mTrackMap[j]; 1.74 + if (map->mInputPort == mInputs[i] && map->mInputTrackID == tracks->GetID()) { 1.75 + bool trackFinished; 1.76 + StreamBuffer::Track* outputTrack = mBuffer.FindTrack(map->mOutputTrackID); 1.77 + if (!outputTrack || outputTrack->IsEnded()) { 1.78 + trackFinished = true; 1.79 + } else { 1.80 + CopyTrackData(tracks.get(), j, aFrom, aTo, &trackFinished); 1.81 + } 1.82 + mappedTracksFinished[j] = trackFinished; 1.83 + mappedTracksWithMatchingInputTracks[j] = true; 1.84 + found = true; 1.85 + break; 1.86 + } 1.87 + } 1.88 + if (!found && (!mFilterCallback || mFilterCallback(tracks.get()))) { 1.89 + bool trackFinished = false; 1.90 + uint32_t mapIndex = AddTrack(mInputs[i], tracks.get(), aFrom); 1.91 + CopyTrackData(tracks.get(), mapIndex, aFrom, aTo, &trackFinished); 1.92 + mappedTracksFinished.AppendElement(trackFinished); 1.93 + mappedTracksWithMatchingInputTracks.AppendElement(true); 1.94 + } 1.95 + } 1.96 + } 1.97 + for (int32_t i = mTrackMap.Length() - 1; i >= 0; --i) { 1.98 + if (mappedTracksFinished[i]) { 1.99 + EndTrack(i); 1.100 + } else { 1.101 + allFinished = false; 1.102 + } 1.103 + if (!mappedTracksWithMatchingInputTracks[i]) { 1.104 + mTrackMap.RemoveElementAt(i); 1.105 + } 1.106 + } 1.107 + if (allFinished && mAutofinish && (aFlags & ALLOW_FINISH)) { 1.108 + // All streams have finished and won't add any more tracks, and 1.109 + // all our tracks have actually finished and been removed from our map, 1.110 + // so we're finished now. 1.111 + FinishOnGraphThread(); 1.112 + } else { 1.113 + mBuffer.AdvanceKnownTracksTime(GraphTimeToStreamTime(aTo)); 1.114 + } 1.115 + if (allHaveCurrentData) { 1.116 + // We can make progress if we're not blocked 1.117 + mHasCurrentData = true; 1.118 + } 1.119 + } 1.120 + 1.121 + // Consumers may specify a filtering callback to apply to every input track. 1.122 + // Returns true to allow the track to act as an input; false to reject it entirely. 1.123 + typedef bool (*TrackIDFilterCallback)(StreamBuffer::Track*); 1.124 + void SetTrackIDFilter(TrackIDFilterCallback aCallback) { 1.125 + mFilterCallback = aCallback; 1.126 + } 1.127 + 1.128 + // Forward SetTrackEnabled(output_track_id, enabled) to the Source MediaStream, 1.129 + // translating the output track ID into the correct ID in the source. 1.130 + virtual void ForwardTrackEnabled(TrackID aOutputID, bool aEnabled) MOZ_OVERRIDE { 1.131 + for (int32_t i = mTrackMap.Length() - 1; i >= 0; --i) { 1.132 + if (mTrackMap[i].mOutputTrackID == aOutputID) { 1.133 + mTrackMap[i].mInputPort->GetSource()-> 1.134 + SetTrackEnabled(mTrackMap[i].mInputTrackID, aEnabled); 1.135 + } 1.136 + } 1.137 + } 1.138 + 1.139 +protected: 1.140 + TrackIDFilterCallback mFilterCallback; 1.141 + 1.142 + // Only non-ended tracks are allowed to persist in this map. 1.143 + struct TrackMapEntry { 1.144 + // mEndOfConsumedInputTicks is the end of the input ticks that we've consumed. 1.145 + // 0 if we haven't consumed any yet. 1.146 + TrackTicks mEndOfConsumedInputTicks; 1.147 + // mEndOfLastInputIntervalInInputStream is the timestamp for the end of the 1.148 + // previous interval which was unblocked for both the input and output 1.149 + // stream, in the input stream's timeline, or -1 if there wasn't one. 1.150 + StreamTime mEndOfLastInputIntervalInInputStream; 1.151 + // mEndOfLastInputIntervalInOutputStream is the timestamp for the end of the 1.152 + // previous interval which was unblocked for both the input and output 1.153 + // stream, in the output stream's timeline, or -1 if there wasn't one. 1.154 + StreamTime mEndOfLastInputIntervalInOutputStream; 1.155 + MediaInputPort* mInputPort; 1.156 + // We keep track IDs instead of track pointers because 1.157 + // tracks can be removed without us being notified (e.g. 1.158 + // when a finished track is forgotten.) When we need a Track*, 1.159 + // we call StreamBuffer::FindTrack, which will return null if 1.160 + // the track has been deleted. 1.161 + TrackID mInputTrackID; 1.162 + TrackID mOutputTrackID; 1.163 + nsAutoPtr<MediaSegment> mSegment; 1.164 + }; 1.165 + 1.166 + uint32_t AddTrack(MediaInputPort* aPort, StreamBuffer::Track* aTrack, 1.167 + GraphTime aFrom) 1.168 + { 1.169 + // Use the ID of the source track if we can, otherwise allocate a new 1.170 + // unique ID 1.171 + TrackID id = std::max(mMaxTrackID + 1, aTrack->GetID()); 1.172 + mMaxTrackID = id; 1.173 + 1.174 + TrackRate rate = aTrack->GetRate(); 1.175 + // Round up the track start time so the track, if anything, starts a 1.176 + // little later than the true time. This means we'll have enough 1.177 + // samples in our input stream to go just beyond the destination time. 1.178 + TrackTicks outputStart = TimeToTicksRoundUp(rate, GraphTimeToStreamTime(aFrom)); 1.179 + 1.180 + nsAutoPtr<MediaSegment> segment; 1.181 + segment = aTrack->GetSegment()->CreateEmptyClone(); 1.182 + for (uint32_t j = 0; j < mListeners.Length(); ++j) { 1.183 + MediaStreamListener* l = mListeners[j]; 1.184 + l->NotifyQueuedTrackChanges(Graph(), id, rate, outputStart, 1.185 + MediaStreamListener::TRACK_EVENT_CREATED, 1.186 + *segment); 1.187 + } 1.188 + segment->AppendNullData(outputStart); 1.189 + StreamBuffer::Track* track = 1.190 + &mBuffer.AddTrack(id, rate, outputStart, segment.forget()); 1.191 + STREAM_LOG(PR_LOG_DEBUG, ("TrackUnionStream %p adding track %d for input stream %p track %d, start ticks %lld", 1.192 + this, id, aPort->GetSource(), aTrack->GetID(), 1.193 + (long long)outputStart)); 1.194 + 1.195 + TrackMapEntry* map = mTrackMap.AppendElement(); 1.196 + map->mEndOfConsumedInputTicks = 0; 1.197 + map->mEndOfLastInputIntervalInInputStream = -1; 1.198 + map->mEndOfLastInputIntervalInOutputStream = -1; 1.199 + map->mInputPort = aPort; 1.200 + map->mInputTrackID = aTrack->GetID(); 1.201 + map->mOutputTrackID = track->GetID(); 1.202 + map->mSegment = aTrack->GetSegment()->CreateEmptyClone(); 1.203 + return mTrackMap.Length() - 1; 1.204 + } 1.205 + void EndTrack(uint32_t aIndex) 1.206 + { 1.207 + StreamBuffer::Track* outputTrack = mBuffer.FindTrack(mTrackMap[aIndex].mOutputTrackID); 1.208 + if (!outputTrack || outputTrack->IsEnded()) 1.209 + return; 1.210 + for (uint32_t j = 0; j < mListeners.Length(); ++j) { 1.211 + MediaStreamListener* l = mListeners[j]; 1.212 + TrackTicks offset = outputTrack->GetSegment()->GetDuration(); 1.213 + nsAutoPtr<MediaSegment> segment; 1.214 + segment = outputTrack->GetSegment()->CreateEmptyClone(); 1.215 + l->NotifyQueuedTrackChanges(Graph(), outputTrack->GetID(), 1.216 + outputTrack->GetRate(), offset, 1.217 + MediaStreamListener::TRACK_EVENT_ENDED, 1.218 + *segment); 1.219 + } 1.220 + outputTrack->SetEnded(); 1.221 + } 1.222 + void CopyTrackData(StreamBuffer::Track* aInputTrack, 1.223 + uint32_t aMapIndex, GraphTime aFrom, GraphTime aTo, 1.224 + bool* aOutputTrackFinished) 1.225 + { 1.226 + TrackMapEntry* map = &mTrackMap[aMapIndex]; 1.227 + StreamBuffer::Track* outputTrack = mBuffer.FindTrack(map->mOutputTrackID); 1.228 + MOZ_ASSERT(outputTrack && !outputTrack->IsEnded(), "Can't copy to ended track"); 1.229 + 1.230 + TrackRate rate = outputTrack->GetRate(); 1.231 + MediaSegment* segment = map->mSegment; 1.232 + MediaStream* source = map->mInputPort->GetSource(); 1.233 + 1.234 + GraphTime next; 1.235 + *aOutputTrackFinished = false; 1.236 + for (GraphTime t = aFrom; t < aTo; t = next) { 1.237 + MediaInputPort::InputInterval interval = map->mInputPort->GetNextInputInterval(t); 1.238 + interval.mEnd = std::min(interval.mEnd, aTo); 1.239 + StreamTime inputEnd = source->GraphTimeToStreamTime(interval.mEnd); 1.240 + TrackTicks inputTrackEndPoint = TRACK_TICKS_MAX; 1.241 + 1.242 + if (aInputTrack->IsEnded() && 1.243 + aInputTrack->GetEndTimeRoundDown() <= inputEnd) { 1.244 + inputTrackEndPoint = aInputTrack->GetEnd(); 1.245 + *aOutputTrackFinished = true; 1.246 + } 1.247 + 1.248 + if (interval.mStart >= interval.mEnd) 1.249 + break; 1.250 + next = interval.mEnd; 1.251 + 1.252 + // Ticks >= startTicks and < endTicks are in the interval 1.253 + StreamTime outputEnd = GraphTimeToStreamTime(interval.mEnd); 1.254 + TrackTicks startTicks = outputTrack->GetEnd(); 1.255 + StreamTime outputStart = GraphTimeToStreamTime(interval.mStart); 1.256 + NS_WARN_IF_FALSE(startTicks == TimeToTicksRoundUp(rate, outputStart), 1.257 + "Samples missing"); 1.258 + TrackTicks endTicks = TimeToTicksRoundUp(rate, outputEnd); 1.259 + TrackTicks ticks = endTicks - startTicks; 1.260 + StreamTime inputStart = source->GraphTimeToStreamTime(interval.mStart); 1.261 + 1.262 + if (interval.mInputIsBlocked) { 1.263 + // Maybe the input track ended? 1.264 + segment->AppendNullData(ticks); 1.265 + STREAM_LOG(PR_LOG_DEBUG+1, ("TrackUnionStream %p appending %lld ticks of null data to track %d", 1.266 + this, (long long)ticks, outputTrack->GetID())); 1.267 + } else { 1.268 + // Figuring out which samples to use from the input stream is tricky 1.269 + // because its start time and our start time may differ by a fraction 1.270 + // of a tick. Assuming the input track hasn't ended, we have to ensure 1.271 + // that 'ticks' samples are gathered, even though a tick boundary may 1.272 + // occur between outputStart and outputEnd but not between inputStart 1.273 + // and inputEnd. 1.274 + // These are the properties we need to ensure: 1.275 + // 1) Exactly 'ticks' ticks of output are produced, i.e. 1.276 + // inputEndTicks - inputStartTicks = ticks. 1.277 + // 2) inputEndTicks <= aInputTrack->GetSegment()->GetDuration(). 1.278 + // 3) In any sequence of intervals where neither stream is blocked, 1.279 + // the content of the input track we use is a contiguous sequence of 1.280 + // ticks with no gaps or overlaps. 1.281 + if (map->mEndOfLastInputIntervalInInputStream != inputStart || 1.282 + map->mEndOfLastInputIntervalInOutputStream != outputStart) { 1.283 + // Start of a new series of intervals where neither stream is blocked. 1.284 + map->mEndOfConsumedInputTicks = TimeToTicksRoundDown(rate, inputStart) - 1; 1.285 + } 1.286 + TrackTicks inputStartTicks = map->mEndOfConsumedInputTicks; 1.287 + TrackTicks inputEndTicks = inputStartTicks + ticks; 1.288 + map->mEndOfConsumedInputTicks = inputEndTicks; 1.289 + map->mEndOfLastInputIntervalInInputStream = inputEnd; 1.290 + map->mEndOfLastInputIntervalInOutputStream = outputEnd; 1.291 + // Now we prove that the above properties hold: 1.292 + // Property #1: trivial by construction. 1.293 + // Property #3: trivial by construction. Between every two 1.294 + // intervals where both streams are not blocked, the above if condition 1.295 + // is false and mEndOfConsumedInputTicks advances exactly to match 1.296 + // the ticks that were consumed. 1.297 + // Property #2: 1.298 + // Let originalOutputStart be the value of outputStart and originalInputStart 1.299 + // be the value of inputStart when the body of the "if" block was last 1.300 + // executed. 1.301 + // Let allTicks be the sum of the values of 'ticks' computed since then. 1.302 + // The interval [originalInputStart/rate, inputEnd/rate) is the 1.303 + // same length as the interval [originalOutputStart/rate, outputEnd/rate), 1.304 + // so the latter interval can have at most one more integer in it. Thus 1.305 + // TimeToTicksRoundUp(rate, outputEnd) - TimeToTicksRoundUp(rate, originalOutputStart) 1.306 + // <= TimeToTicksRoundDown(rate, inputEnd) - TimeToTicksRoundDown(rate, originalInputStart) + 1 1.307 + // Then 1.308 + // inputEndTicks = TimeToTicksRoundDown(rate, originalInputStart) - 1 + allTicks 1.309 + // = TimeToTicksRoundDown(rate, originalInputStart) - 1 + TimeToTicksRoundUp(rate, outputEnd) - TimeToTicksRoundUp(rate, originalOutputStart) 1.310 + // <= TimeToTicksRoundDown(rate, originalInputStart) - 1 + TimeToTicksRoundDown(rate, inputEnd) - TimeToTicksRoundDown(rate, originalInputStart) + 1 1.311 + // = TimeToTicksRoundDown(rate, inputEnd) 1.312 + // <= inputEnd/rate 1.313 + // (now using the fact that inputEnd <= track->GetEndTimeRoundDown() for a non-ended track) 1.314 + // <= TicksToTimeRoundDown(rate, aInputTrack->GetSegment()->GetDuration())/rate 1.315 + // <= rate*aInputTrack->GetSegment()->GetDuration()/rate 1.316 + // = aInputTrack->GetSegment()->GetDuration() 1.317 + // as required. 1.318 + 1.319 + if (inputStartTicks < 0) { 1.320 + // Data before the start of the track is just null. 1.321 + // We have to add a small amount of delay to ensure that there is 1.322 + // always a sample available if we see an interval that contains a 1.323 + // tick boundary on the output stream's timeline but does not contain 1.324 + // a tick boundary on the input stream's timeline. 1 tick delay is 1.325 + // necessary and sufficient. 1.326 + segment->AppendNullData(-inputStartTicks); 1.327 + inputStartTicks = 0; 1.328 + } 1.329 + if (inputEndTicks > inputStartTicks) { 1.330 + segment->AppendSlice(*aInputTrack->GetSegment(), 1.331 + std::min(inputTrackEndPoint, inputStartTicks), 1.332 + std::min(inputTrackEndPoint, inputEndTicks)); 1.333 + } 1.334 + STREAM_LOG(PR_LOG_DEBUG+1, ("TrackUnionStream %p appending %lld ticks of input data to track %d", 1.335 + this, (long long)(std::min(inputTrackEndPoint, inputEndTicks) - std::min(inputTrackEndPoint, inputStartTicks)), 1.336 + outputTrack->GetID())); 1.337 + } 1.338 + ApplyTrackDisabling(outputTrack->GetID(), segment); 1.339 + for (uint32_t j = 0; j < mListeners.Length(); ++j) { 1.340 + MediaStreamListener* l = mListeners[j]; 1.341 + l->NotifyQueuedTrackChanges(Graph(), outputTrack->GetID(), 1.342 + outputTrack->GetRate(), startTicks, 0, 1.343 + *segment); 1.344 + } 1.345 + outputTrack->GetSegment()->AppendFrom(segment); 1.346 + } 1.347 + } 1.348 + 1.349 + nsTArray<TrackMapEntry> mTrackMap; 1.350 + TrackID mMaxTrackID; 1.351 +}; 1.352 + 1.353 +} 1.354 + 1.355 +#endif /* MOZILLA_MEDIASTREAMGRAPH_H_ */