Bug 943461. Part 5: Don't allow a stream to finish before it has produced output up to mStateComputedTime. r=padenot
--- a/content/media/AudioNodeExternalInputStream.cpp
+++ b/content/media/AudioNodeExternalInputStream.cpp
@@ -319,17 +319,18 @@ ConvertSegmentToAudioBlock(AudioSegment*
while (!ci.IsEnded()) {
CopyChunkToBlock(*ci, aBlock, duration);
duration += ci->GetDuration();
ci.Next();
}
}
void
-AudioNodeExternalInputStream::ProduceOutput(GraphTime aFrom, GraphTime aTo)
+AudioNodeExternalInputStream::ProduceOutput(GraphTime aFrom, GraphTime aTo,
+ uint32_t aFlags)
{
// According to spec, number of outputs is always 1.
mLastChunks.SetLength(1);
// GC stuff can result in our input stream being destroyed before this stream.
// Handle that.
if (mInputs.IsEmpty()) {
mLastChunks[0].SetNull(WEBAUDIO_BLOCK_SIZE);
--- a/content/media/AudioNodeExternalInputStream.h
+++ b/content/media/AudioNodeExternalInputStream.h
@@ -20,17 +20,17 @@ namespace mozilla {
* input --- handling any number of audio tracks, resampling them from whatever
* sample rate they're using, and handling blocking of the input MediaStream.
*/
class AudioNodeExternalInputStream : public AudioNodeStream {
public:
AudioNodeExternalInputStream(AudioNodeEngine* aEngine, TrackRate aSampleRate);
~AudioNodeExternalInputStream();
- virtual void ProduceOutput(GraphTime aFrom, GraphTime aTo) MOZ_OVERRIDE;
+ virtual void ProduceOutput(GraphTime aFrom, GraphTime aTo, uint32_t aFlags) MOZ_OVERRIDE;
private:
// For storing pointers and data about input tracks, like the last TrackTick which
// was read, and the associated speex resampler.
struct TrackMapEntry {
~TrackMapEntry();
/**
--- a/content/media/AudioNodeStream.cpp
+++ b/content/media/AudioNodeStream.cpp
@@ -394,26 +394,21 @@ AudioNodeStream::UpMixDownMixChunk(const
aOutputChannels.Length() - aOutputChannelCount);
}
}
}
// The MediaStreamGraph guarantees that this is actually one block, for
// AudioNodeStreams.
void
-AudioNodeStream::ProduceOutput(GraphTime aFrom, GraphTime aTo)
+AudioNodeStream::ProduceOutput(GraphTime aFrom, GraphTime aTo, uint32_t aFlags)
{
- if (mMarkAsFinishedAfterThisBlock) {
- // This stream was finished the last time that we looked at it, and all
- // of the depending streams have finished their output as well, so now
- // it's time to mark this stream as finished.
- FinishOutput();
- }
-
EnsureTrack(AUDIO_TRACK, mSampleRate);
+ // No more tracks will be coming
+ mBuffer.AdvanceKnownTracksTime(STREAM_TIME_MAX);
uint16_t outputCount = std::max(uint16_t(1), mEngine->OutputCount());
mLastChunks.SetLength(outputCount);
if (mMuted) {
for (uint16_t i = 0; i < outputCount; ++i) {
mLastChunks[i].SetNull(WEBAUDIO_BLOCK_SIZE);
}
@@ -441,17 +436,26 @@ AudioNodeStream::ProduceOutput(GraphTime
}
if (mDisabledTrackIDs.Contains(static_cast<TrackID>(AUDIO_TRACK))) {
for (uint32_t i = 0; i < mLastChunks.Length(); ++i) {
mLastChunks[i].SetNull(WEBAUDIO_BLOCK_SIZE);
}
}
- AdvanceOutputSegment();
+ if (!IsFinishedOnGraphThread()) {
+ // Don't output anything after we've finished!
+ AdvanceOutputSegment();
+ if (mMarkAsFinishedAfterThisBlock && (aFlags & ALLOW_FINISH)) {
+ // This stream was finished the last time that we looked at it, and all
+ // of the depending streams have finished their output as well, so now
+ // it's time to mark this stream as finished.
+ FinishOutput();
+ }
+ }
}
void
AudioNodeStream::AdvanceOutputSegment()
{
StreamBuffer::Track* track = EnsureTrack(AUDIO_TRACK, mSampleRate);
AudioSegment* segment = track->Get<AudioSegment>();
--- a/content/media/AudioNodeStream.h
+++ b/content/media/AudioNodeStream.h
@@ -79,25 +79,25 @@ public:
dom::ChannelCountMode aChannelCountMoe,
dom::ChannelInterpretation aChannelInterpretation);
void SetAudioParamHelperStream()
{
MOZ_ASSERT(!mAudioParamStream, "Can only do this once");
mAudioParamStream = true;
}
- virtual AudioNodeStream* AsAudioNodeStream() { return this; }
+ virtual AudioNodeStream* AsAudioNodeStream() MOZ_OVERRIDE { return this; }
// Graph thread only
void SetStreamTimeParameterImpl(uint32_t aIndex, MediaStream* aRelativeToStream,
double aStreamTime);
void SetChannelMixingParametersImpl(uint32_t aNumberOfChannels,
dom::ChannelCountMode aChannelCountMoe,
dom::ChannelInterpretation aChannelInterpretation);
- virtual void ProduceOutput(GraphTime aFrom, GraphTime aTo);
+ virtual void ProduceOutput(GraphTime aFrom, GraphTime aTo, uint32_t aFlags) MOZ_OVERRIDE;
TrackTicks GetCurrentPosition();
bool IsAudioParamStream() const
{
return mAudioParamStream;
}
void Mute() {
mMuted = true;
}
--- a/content/media/MediaStreamGraph.cpp
+++ b/content/media/MediaStreamGraph.cpp
@@ -1087,17 +1087,17 @@ MediaStreamGraphImpl::ProduceDataForStre
GraphTime aTo)
{
GraphTime t = aFrom;
while (t < aTo) {
GraphTime next = RoundUpToNextAudioBlock(aSampleRate, t);
for (uint32_t i = aStreamIndex; i < mStreams.Length(); ++i) {
ProcessedMediaStream* ps = mStreams[i]->AsProcessedStream();
if (ps) {
- ps->ProduceOutput(t, next);
+ ps->ProduceOutput(t, next, (next == aTo) ? ProcessedMediaStream::ALLOW_FINISH : 0);
}
}
t = next;
}
NS_ASSERTION(t == aTo, "Something went wrong with rounding to block boundaries");
}
void
@@ -1192,17 +1192,18 @@ MediaStreamGraphImpl::RunThread()
}
#endif
// Since an AudioNodeStream is present, go ahead and
// produce audio block by block for all the rest of the streams.
ProduceDataForStreamsBlockByBlock(i, n->SampleRate(), prevComputedTime, mStateComputedTime);
ticksProcessed += TimeToTicksRoundDown(n->SampleRate(), mStateComputedTime - prevComputedTime);
doneAllProducing = true;
} else {
- ps->ProduceOutput(prevComputedTime, mStateComputedTime);
+ ps->ProduceOutput(prevComputedTime, mStateComputedTime,
+ ProcessedMediaStream::ALLOW_FINISH);
NS_ASSERTION(stream->mBuffer.GetEnd() >=
GraphTimeToStreamTime(stream, mStateComputedTime),
"Stream did not produce enough data");
}
}
}
NotifyHasCurrentData(stream);
if (mRealtime) {
--- a/content/media/MediaStreamGraph.h
+++ b/content/media/MediaStreamGraph.h
@@ -953,20 +953,29 @@ public:
return mInputs.Length();
}
virtual void DestroyImpl();
/**
* This gets called after we've computed the blocking states for all
* streams (mBlocked is up to date up to mStateComputedTime).
* Also, we've produced output for all streams up to this one. If this stream
* is not in a cycle, then all its source streams have produced data.
- * Generate output up to mStateComputedTime.
+ * Generate output from aFrom to aTo.
* This is called only on streams that have not finished.
+ * ProduceOutput is allowed to call FinishOnGraphThread only if ALLOW_FINISH
+ * is in aFlags. (This flag will be set when aTo >= mStateComputedTime, i.e.
+ * when we've producing the last block of data we need to produce.) Otherwise
+ * we can get into a situation where we've determined the stream should not
+ * block before mStateComputedTime, but the stream finishes before
+ * mStateComputedTime, violating the invariant that finished streams are blocked.
*/
- virtual void ProduceOutput(GraphTime aFrom, GraphTime aTo) = 0;
+ enum {
+ ALLOW_FINISH = 0x01
+ };
+ virtual void ProduceOutput(GraphTime aFrom, GraphTime aTo, uint32_t aFlags) = 0;
void SetAutofinishImpl(bool aAutofinish) { mAutofinish = aAutofinish; }
/**
* Forward SetTrackEnabled() to the input MediaStream(s) and translate the ID
*/
virtual void ForwardTrackEnabled(TrackID aOutputID, bool aEnabled) {};
bool InCycle() const { return mInCycle; }
--- a/content/media/TrackUnionStream.h
+++ b/content/media/TrackUnionStream.h
@@ -24,27 +24,27 @@ namespace mozilla {
*/
class TrackUnionStream : public ProcessedMediaStream {
public:
TrackUnionStream(DOMMediaStream* aWrapper) :
ProcessedMediaStream(aWrapper),
mFilterCallback(nullptr),
mMaxTrackID(0) {}
- virtual void RemoveInput(MediaInputPort* aPort)
+ virtual void RemoveInput(MediaInputPort* aPort) MOZ_OVERRIDE
{
for (int32_t i = mTrackMap.Length() - 1; i >= 0; --i) {
if (mTrackMap[i].mInputPort == aPort) {
EndTrack(i);
mTrackMap.RemoveElementAt(i);
}
}
ProcessedMediaStream::RemoveInput(aPort);
}
- virtual void ProduceOutput(GraphTime aFrom, GraphTime aTo)
+ virtual void ProduceOutput(GraphTime aFrom, GraphTime aTo, uint32_t aFlags) MOZ_OVERRIDE
{
nsAutoTArray<bool,8> mappedTracksFinished;
nsAutoTArray<bool,8> mappedTracksWithMatchingInputTracks;
for (uint32_t i = 0; i < mTrackMap.Length(); ++i) {
mappedTracksFinished.AppendElement(true);
mappedTracksWithMatchingInputTracks.AppendElement(false);
}
bool allFinished = true;
@@ -90,17 +90,17 @@ public:
EndTrack(i);
} else {
allFinished = false;
}
if (!mappedTracksWithMatchingInputTracks[i]) {
mTrackMap.RemoveElementAt(i);
}
}
- if (allFinished && mAutofinish) {
+ if (allFinished && mAutofinish && (aFlags & ALLOW_FINISH)) {
// All streams have finished and won't add any more tracks, and
// all our tracks have actually finished and been removed from our map,
// so we're finished now.
FinishOnGraphThread();
}
mBuffer.AdvanceKnownTracksTime(GraphTimeToStreamTime(aTo));
if (allHaveCurrentData) {
// We can make progress if we're not blocked
@@ -112,17 +112,17 @@ public:
// Returns true to allow the track to act as an input; false to reject it entirely.
typedef bool (*TrackIDFilterCallback)(StreamBuffer::Track*);
void SetTrackIDFilter(TrackIDFilterCallback aCallback) {
mFilterCallback = aCallback;
}
// Forward SetTrackEnabled(output_track_id, enabled) to the Source MediaStream,
// translating the output track ID into the correct ID in the source.
- virtual void ForwardTrackEnabled(TrackID aOutputID, bool aEnabled) {
+ virtual void ForwardTrackEnabled(TrackID aOutputID, bool aEnabled) MOZ_OVERRIDE {
for (int32_t i = mTrackMap.Length() - 1; i >= 0; --i) {
if (mTrackMap[i].mOutputTrackID == aOutputID) {
mTrackMap[i].mInputPort->GetSource()->
SetTrackEnabled(mTrackMap[i].mInputTrackID, aEnabled);
}
}
}