diff --git a/dom/media/MediaDecoderReader.h b/dom/media/MediaDecoderReader.h index 6e0974ffac25..0d2388adcd04 100644 --- a/dom/media/MediaDecoderReader.h +++ b/dom/media/MediaDecoderReader.h @@ -278,7 +278,8 @@ public: enum NotDecodedReason { END_OF_STREAM, DECODE_ERROR, - WAITING_FOR_DATA + WAITING_FOR_DATA, + CANCELED }; // Receives the result of a RequestAudioData() call. diff --git a/dom/media/MediaDecoderStateMachine.cpp b/dom/media/MediaDecoderStateMachine.cpp index 4d194837c9a2..7d30ca9f28b6 100644 --- a/dom/media/MediaDecoderStateMachine.cpp +++ b/dom/media/MediaDecoderStateMachine.cpp @@ -632,6 +632,11 @@ bool MediaDecoderStateMachine::NeedToDecodeAudio() { AssertCurrentThreadInMonitor(); + SAMPLE_LOG("NeedToDecodeAudio() isDec=%d decToTar=%d minPrl=%d seek=%d enufAud=%d", + IsAudioDecoding(), mDecodeToSeekTarget, mMinimizePreroll, + mState == DECODER_STATE_SEEKING, + HaveEnoughDecodedAudio(mAmpleAudioThresholdUsecs * mPlaybackRate)); + return IsAudioDecoding() && ((mState == DECODER_STATE_SEEKING && mDecodeToSeekTarget) || (!mMinimizePreroll && @@ -833,6 +838,11 @@ MediaDecoderStateMachine::OnNotDecoded(MediaData::Type aType, return; } + if (aReason == RequestSampleCallback::CANCELED) { + DispatchDecodeTasksIfNeeded(); + return; + } + // This is an EOS. Finish off the queue, and then handle things based on our // state. MOZ_ASSERT(aReason == RequestSampleCallback::END_OF_STREAM); @@ -1722,6 +1732,11 @@ MediaDecoderStateMachine::DispatchDecodeTasksIfNeeded() !needToDecodeVideo && !IsPlaying(); + SAMPLE_LOG("DispatchDecodeTasksIfNeeded needAudio=%d dispAudio=%d needVideo=%d dispVid=%d needIdle=%d", + needToDecodeAudio, mAudioRequestPending, + needToDecodeVideo, mVideoRequestPending, + needIdle); + if (needToDecodeAudio) { EnsureAudioDecodeTaskQueued(); } @@ -1729,10 +1744,6 @@ MediaDecoderStateMachine::DispatchDecodeTasksIfNeeded() EnsureVideoDecodeTaskQueued(); } - SAMPLE_LOG("DispatchDecodeTasksIfNeeded needAudio=%d dispAudio=%d needVideo=%d dispVid=%d needIdle=%d", - needToDecodeAudio, mAudioRequestPending, - needToDecodeVideo, mVideoRequestPending, - needIdle); if (needIdle) { RefPtr event = NS_NewRunnableMethod( @@ -2251,9 +2262,10 @@ void MediaDecoderStateMachine::DecodeSeek() return; } + mDecodeToSeekTarget = false; + if (!currentTimeChanged) { DECODER_LOG("Seek !currentTimeChanged..."); - mDecodeToSeekTarget = false; nsresult rv = mDecodeTaskQueue->Dispatch( NS_NewRunnableMethod(this, &MediaDecoderStateMachine::SeekCompleted)); if (NS_FAILED(rv)) { @@ -2387,6 +2399,13 @@ MediaDecoderStateMachine::SeekCompleted() // Try to decode another frame to detect if we're at the end... DECODER_LOG("Seek completed, mCurrentFrameTime=%lld", mCurrentFrameTime); + mCurrentSeekTarget = SeekTarget(); + + // Reset quick buffering status. This ensures that if we began the + // seek while quick-buffering, we won't bypass quick buffering mode + // if we need to buffer after the seek. + mQuickBuffering = false; + // Prevent changes in playback position before 'seeked' is fired for we // expect currentTime equals seek target in 'seeked' callback. mScheduler->FreezeScheduling(); @@ -2395,11 +2414,6 @@ MediaDecoderStateMachine::SeekCompleted() NS_DispatchToMainThread(stopEvent, NS_DISPATCH_SYNC); } - // Reset quick buffering status. This ensures that if we began the - // seek while quick-buffering, we won't bypass quick buffering mode - // if we need to buffer after the seek. - mQuickBuffering = false; - ScheduleStateMachine(); mScheduler->ThawScheduling(); } diff --git a/dom/media/fmp4/MP4Reader.cpp b/dom/media/fmp4/MP4Reader.cpp index 06f482cae4de..d0daca0ce5ff 100644 --- a/dom/media/fmp4/MP4Reader.cpp +++ b/dom/media/fmp4/MP4Reader.cpp @@ -33,8 +33,10 @@ PRLogModuleInfo* GetDemuxerLog() { return log; } #define LOG(...) PR_LOG(GetDemuxerLog(), PR_LOG_DEBUG, (__VA_ARGS__)) +#define VLOG(...) PR_LOG(GetDemuxerLog(), PR_LOG_DEBUG+1, (__VA_ARGS__)) #else #define LOG(...) +#define VLOG(...) #endif using namespace mp4_demuxer; @@ -134,14 +136,28 @@ MP4Reader::MP4Reader(AbstractMediaDecoder* aDecoder) MP4Reader::~MP4Reader() { - MOZ_ASSERT(NS_IsMainThread(), "Must be on main thread."); MOZ_COUNT_DTOR(MP4Reader); - Shutdown(); } +class DestroyPDMTask : public nsRunnable { +public: + DestroyPDMTask(nsAutoPtr& aPDM) + : mPDM(aPDM) + {} + NS_IMETHOD Run() MOZ_OVERRIDE { + MOZ_ASSERT(NS_IsMainThread()); + mPDM = nullptr; + return NS_OK; + } +private: + nsAutoPtr mPDM; +}; + void MP4Reader::Shutdown() { + MOZ_ASSERT(GetTaskQueue()->IsCurrentThreadIn()); + if (mAudio.mDecoder) { Flush(kAudio); mAudio.mDecoder->Shutdown(); @@ -164,8 +180,10 @@ MP4Reader::Shutdown() mQueuedVideoSample = nullptr; if (mPlatform) { - mPlatform->Shutdown(); - mPlatform = nullptr; + // PDMs are supposed to be destroyed on the main thread... + nsRefPtr task(new DestroyPDMTask(mPlatform)); + MOZ_ASSERT(!mPlatform); + NS_DispatchToMainThread(task); } } @@ -206,12 +224,10 @@ MP4Reader::Init(MediaDecoderReader* aCloneDonor) InitLayersBackendType(); - mAudio.mTaskQueue = new MediaTaskQueue( - SharedThreadPool::Get(NS_LITERAL_CSTRING("MP4 Audio Decode"))); + mAudio.mTaskQueue = new MediaTaskQueue(GetMediaDecodeThreadPool()); NS_ENSURE_TRUE(mAudio.mTaskQueue, NS_ERROR_FAILURE); - mVideo.mTaskQueue = new MediaTaskQueue( - SharedThreadPool::Get(NS_LITERAL_CSTRING("MP4 Video Decode"))); + mVideo.mTaskQueue = new MediaTaskQueue(GetMediaDecodeThreadPool()); NS_ENSURE_TRUE(mVideo.mTaskQueue, NS_ERROR_FAILURE); static bool sSetupPrefCache = false; @@ -483,6 +499,176 @@ MP4Reader::GetDecoderData(TrackType aTrack) return (aTrack == kAudio) ? mAudio : mVideo; } +void +MP4Reader::RequestVideoData(bool aSkipToNextKeyframe, + int64_t aTimeThreshold) +{ + MOZ_ASSERT(GetTaskQueue()->IsCurrentThreadIn()); + VLOG("RequestVideoData skip=%d time=%lld", aSkipToNextKeyframe, aTimeThreshold); + + // Record number of frames decoded and parsed. Automatically update the + // stats counters using the AutoNotifyDecoded stack-based class. + uint32_t parsed = 0, decoded = 0; + AbstractMediaDecoder::AutoNotifyDecoded autoNotify(mDecoder, parsed, decoded); + + MOZ_ASSERT(HasVideo() && mPlatform && mVideo.mDecoder); + + if (aSkipToNextKeyframe) { + if (!SkipVideoDemuxToNextKeyFrame(aTimeThreshold, parsed) || + NS_FAILED(mVideo.mDecoder->Flush())) { + NS_WARNING("Failed to skip/flush video when skipping-to-next-keyframe."); + } + } + + auto& decoder = GetDecoderData(kVideo); + MonitorAutoLock lock(decoder.mMonitor); + decoder.mOutputRequested = true; + ScheduleUpdate(kVideo); + + // Report the number of "decoded" frames as the difference in the + // mNumSamplesOutput field since the last time we were called. + uint64_t delta = mVideo.mNumSamplesOutput - mLastReportedNumDecodedFrames; + decoded = static_cast(delta); + mLastReportedNumDecodedFrames = mVideo.mNumSamplesOutput; +} + +void +MP4Reader::RequestAudioData() +{ + MOZ_ASSERT(GetTaskQueue()->IsCurrentThreadIn()); + VLOG("RequestAudioData"); + auto& decoder = GetDecoderData(kAudio); + MonitorAutoLock lock(decoder.mMonitor); + decoder.mOutputRequested = true; + ScheduleUpdate(kAudio); +} + +void +MP4Reader::ScheduleUpdate(TrackType aTrack) +{ + auto& decoder = GetDecoderData(aTrack); + decoder.mMonitor.AssertCurrentThreadOwns(); + if (decoder.mUpdateScheduled) { + return; + } + VLOG("SchedulingUpdate(%s)", TrackTypeToStr(aTrack)); + decoder.mUpdateScheduled = true; + RefPtr task( + NS_NewRunnableMethodWithArg(this, &MP4Reader::Update, aTrack)); + GetTaskQueue()->Dispatch(task.forget()); +} + +bool +MP4Reader::NeedInput(DecoderData& aDecoder) +{ + aDecoder.mMonitor.AssertCurrentThreadOwns(); + // We try to keep a few more compressed samples input than decoded samples + // have been output, provided the state machine has requested we send it a + // decoded sample. To account for H.264 streams which may require a longer + // run of input than we input, decoders fire an "input exhausted" callback, + // which overrides our "few more samples" threshold. + return + !aDecoder.mError && + !aDecoder.mEOS && + aDecoder.mOutputRequested && + aDecoder.mOutput.IsEmpty() && + (aDecoder.mInputExhausted || + aDecoder.mNumSamplesInput - aDecoder.mNumSamplesOutput < aDecoder.mDecodeAhead); +} + +void +MP4Reader::Update(TrackType aTrack) +{ + MOZ_ASSERT(GetTaskQueue()->IsCurrentThreadIn()); + + bool needInput = false; + bool needOutput = false; + bool eos = false; + auto& decoder = GetDecoderData(aTrack); + nsRefPtr output; + { + MonitorAutoLock lock(decoder.mMonitor); + decoder.mUpdateScheduled = false; + if (NeedInput(decoder)) { + needInput = true; + decoder.mInputExhausted = false; + decoder.mNumSamplesInput++; + } + needOutput = decoder.mOutputRequested; + if (needOutput && !decoder.mOutput.IsEmpty()) { + output = decoder.mOutput[0]; + decoder.mOutput.RemoveElementAt(0); + } + eos = decoder.mEOS; + } + VLOG("Update(%s) ni=%d no=%d iex=%d or=%d fl=%d", + TrackTypeToStr(aTrack), + needInput, + needOutput, + decoder.mInputExhausted, + decoder.mOutputRequested, + decoder.mIsFlushing); + + if (needInput) { + MP4Sample* sample = PopSample(aTrack); + if (sample) { + decoder.mDecoder->Input(sample); + } else { + { + MonitorAutoLock lock(decoder.mMonitor); + MOZ_ASSERT(!decoder.mEOS); + eos = decoder.mEOS = true; + } + decoder.mDecoder->Drain(); + } + } + if (needOutput) { + if (output) { + ReturnOutput(output, aTrack); + } else if (eos) { + ReturnEOS(aTrack); + } + } +} + +void +MP4Reader::ReturnOutput(MediaData* aData, TrackType aTrack) +{ + auto& decoder = GetDecoderData(aTrack); + { + MonitorAutoLock lock(decoder.mMonitor); + MOZ_ASSERT(decoder.mOutputRequested); + decoder.mOutputRequested = false; + if (decoder.mDiscontinuity) { + decoder.mDiscontinuity = false; + aData->mDiscontinuity = true; + } + } + + if (aTrack == kAudio) { + AudioData* audioData = static_cast(aData); + + if (audioData->mChannels != mInfo.mAudio.mChannels || + audioData->mRate != mInfo.mAudio.mRate) { + LOG("MP4Reader::ReturnOutput change of sampling rate:%d->%d", + mInfo.mAudio.mRate, audioData->mRate); + mInfo.mAudio.mRate = audioData->mRate; + mInfo.mAudio.mChannels = audioData->mChannels; + } + + GetCallback()->OnAudioDecoded(audioData); + } else if (aTrack == kVideo) { + GetCallback()->OnVideoDecoded(static_cast(aData)); + } +} + +void +MP4Reader::ReturnEOS(TrackType aTrack) +{ + GetCallback()->OnNotDecoded(aTrack == kAudio ? MediaData::AUDIO_DATA : MediaData::VIDEO_DATA, + RequestSampleCallback::END_OF_STREAM); +} + MP4Sample* MP4Reader::PopSample(TrackType aTrack) { @@ -501,108 +687,12 @@ MP4Reader::PopSample(TrackType aTrack) } } -// How async decoding works: -// -// When MP4Reader::Decode() is called: -// * Lock the DecoderData. We assume the state machine wants -// output from the decoder (in future, we'll assume decoder wants input -// when the output MediaQueue isn't "full"). -// * Cache the value of mNumSamplesOutput, as prevFramesOutput. -// * While we've not output data (mNumSamplesOutput != prevNumFramesOutput) -// and while we still require input, we demux and input data in the reader. -// We assume we require input if -// ((mNumSamplesInput - mNumSamplesOutput) < sDecodeAheadMargin) or -// mInputExhausted is true. Before we send input, we reset mInputExhausted -// and increment mNumFrameInput, and drop the lock on DecoderData. -// * Once we no longer require input, we wait on the DecoderData -// lock for output, or for the input exhausted callback. If we receive the -// input exhausted callback, we go back and input more data. -// * When our output callback is called, we take the DecoderData lock and -// increment mNumSamplesOutput. We notify the DecoderData lock. This will -// awaken the Decode thread, and unblock it, and it will return. -bool -MP4Reader::Decode(TrackType aTrack) -{ - DecoderData& data = GetDecoderData(aTrack); - MOZ_ASSERT(data.mDecoder); - - data.mMonitor.Lock(); - uint64_t prevNumFramesOutput = data.mNumSamplesOutput; - while (prevNumFramesOutput == data.mNumSamplesOutput) { - data.mMonitor.AssertCurrentThreadOwns(); - if (data.mError) { - // Decode error! - data.mMonitor.Unlock(); - return false; - } - // Send input to the decoder, if we need to. We assume the decoder - // needs input if it's told us it's out of input, or we're beneath the - // "low water mark" for the amount of input we've sent it vs the amount - // out output we've received. We always try to keep the decoder busy if - // possible, so we try to maintain at least a few input samples ahead, - // if we need output. - while (prevNumFramesOutput == data.mNumSamplesOutput && - (data.mInputExhausted || - (data.mNumSamplesInput - data.mNumSamplesOutput) < data.mDecodeAhead) && - !data.mEOS) { - data.mMonitor.AssertCurrentThreadOwns(); - data.mMonitor.Unlock(); - nsAutoPtr compressed(PopSample(aTrack)); - if (!compressed) { - // EOS, or error. Send the decoder a signal to drain. - LOG("MP4Reader: EOS or error - no samples available"); - LOG("Draining %s", TrackTypeToStr(aTrack)); - data.mMonitor.Lock(); - MOZ_ASSERT(!data.mEOS); - data.mEOS = true; - MOZ_ASSERT(!data.mDrainComplete); - data.mDrainComplete = false; - data.mMonitor.Unlock(); - data.mDecoder->Drain(); - } else { -#ifdef LOG_SAMPLE_DECODE - LOG("PopSample %s time=%lld dur=%lld", TrackTypeToStr(aTrack), - compressed->composition_timestamp, compressed->duration); -#endif - data.mMonitor.Lock(); - data.mDrainComplete = false; - data.mInputExhausted = false; - data.mNumSamplesInput++; - data.mMonitor.Unlock(); - - if (NS_FAILED(data.mDecoder->Input(compressed))) { - return false; - } - // If Input() failed, we let the auto pointer delete |compressed|. - // Otherwise, we assume the decoder will delete it when it's finished - // with it. - compressed.forget(); - } - data.mMonitor.Lock(); - } - data.mMonitor.AssertCurrentThreadOwns(); - while (!data.mError && - prevNumFramesOutput == data.mNumSamplesOutput && - (!data.mInputExhausted || data.mEOS) && - !data.mDrainComplete) { - data.mMonitor.Wait(); - } - if (data.mError || - (data.mEOS && data.mDrainComplete)) { - break; - } - } - data.mMonitor.AssertCurrentThreadOwns(); - bool rv = !(data.mDrainComplete || data.mError); - data.mMonitor.Unlock(); - return rv; -} - nsresult MP4Reader::ResetDecode() { - Flush(kAudio); + MOZ_ASSERT(GetTaskQueue()->IsCurrentThreadIn()); Flush(kVideo); + Flush(kAudio); return MediaDecoderReader::ResetDecode(); } @@ -610,7 +700,7 @@ void MP4Reader::Output(TrackType aTrack, MediaData* aSample) { #ifdef LOG_SAMPLE_DECODE - LOG("Decoded %s sample time=%lld dur=%lld", + VLOG("Decoded %s sample time=%lld dur=%lld", TrackTypeToStr(aTrack), aSample->mTime, aSample->mDuration); #endif @@ -620,40 +710,20 @@ MP4Reader::Output(TrackType aTrack, MediaData* aSample) return; } - DecoderData& data = GetDecoderData(aTrack); + auto& decoder = GetDecoderData(aTrack); // Don't accept output while we're flushing. - MonitorAutoLock mon(data.mMonitor); - if (data.mIsFlushing) { + MonitorAutoLock mon(decoder.mMonitor); + if (decoder.mIsFlushing) { LOG("MP4Reader produced output while flushing, discarding."); mon.NotifyAll(); return; } - switch (aTrack) { - case kAudio: { - MOZ_ASSERT(aSample->mType == MediaData::AUDIO_DATA); - AudioData* audioData = static_cast(aSample); - AudioQueue().Push(audioData); - if (audioData->mChannels != mInfo.mAudio.mChannels || - audioData->mRate != mInfo.mAudio.mRate) { - LOG("MP4Reader::Output change of sampling rate:%d->%d", - mInfo.mAudio.mRate, audioData->mRate); - mInfo.mAudio.mRate = audioData->mRate; - mInfo.mAudio.mChannels = audioData->mChannels; - } - break; - } - case kVideo: { - MOZ_ASSERT(aSample->mType == MediaData::VIDEO_DATA); - VideoQueue().Push(static_cast(aSample)); - break; - } - default: - break; + decoder.mOutput.AppendElement(aSample); + decoder.mNumSamplesOutput++; + if (NeedInput(decoder) || decoder.mOutputRequested) { + ScheduleUpdate(aTrack); } - - data.mNumSamplesOutput++; - mon.NotifyAll(); } void @@ -671,28 +741,26 @@ MP4Reader::InputExhausted(TrackType aTrack) DecoderData& data = GetDecoderData(aTrack); MonitorAutoLock mon(data.mMonitor); data.mInputExhausted = true; - mon.NotifyAll(); + ScheduleUpdate(aTrack); } void MP4Reader::Error(TrackType aTrack) { DecoderData& data = GetDecoderData(aTrack); - MonitorAutoLock mon(data.mMonitor); - data.mError = true; - mon.NotifyAll(); -} - -bool -MP4Reader::DecodeAudioData() -{ - MOZ_ASSERT(HasAudio() && mPlatform && mAudio.mDecoder); - return Decode(kAudio); + { + MonitorAutoLock mon(data.mMonitor); + data.mError = true; + } + GetCallback()->OnNotDecoded(aTrack == kVideo ? MediaData::VIDEO_DATA : MediaData::AUDIO_DATA, + RequestSampleCallback::DECODE_ERROR); } void MP4Reader::Flush(TrackType aTrack) { + MOZ_ASSERT(GetTaskQueue()->IsCurrentThreadIn()); + VLOG("Flush(%s) BEGIN", TrackTypeToStr(aTrack)); DecoderData& data = GetDecoderData(aTrack); if (!data.mDecoder) { return; @@ -710,12 +778,28 @@ MP4Reader::Flush(TrackType aTrack) { MonitorAutoLock mon(data.mMonitor); data.mIsFlushing = false; + data.mOutput.Clear(); + data.mNumSamplesInput = 0; + data.mNumSamplesOutput = 0; + data.mInputExhausted = false; + if (data.mOutputRequested) { + GetCallback()->OnNotDecoded(aTrack == kVideo ? MediaData::VIDEO_DATA : MediaData::AUDIO_DATA, + RequestSampleCallback::CANCELED); + } + data.mOutputRequested = false; + data.mDiscontinuity = true; } + if (aTrack == kVideo) { + mQueuedVideoSample = nullptr; + } + VLOG("Flush(%s) END", TrackTypeToStr(aTrack)); } bool MP4Reader::SkipVideoDemuxToNextKeyFrame(int64_t aTimeThreshold, uint32_t& parsed) { + MOZ_ASSERT(GetTaskQueue()->IsCurrentThreadIn()); + MOZ_ASSERT(mVideo.mDecoder); Flush(kVideo); @@ -725,6 +809,12 @@ MP4Reader::SkipVideoDemuxToNextKeyFrame(int64_t aTimeThreshold, uint32_t& parsed nsAutoPtr compressed(PopSample(kVideo)); if (!compressed) { // EOS, or error. Let the state machine know. + GetCallback()->OnNotDecoded(MediaData::VIDEO_DATA, + RequestSampleCallback::END_OF_STREAM); + { + MonitorAutoLock mon(mVideo.mMonitor); + mVideo.mEOS = true; + } return false; } parsed++; @@ -739,47 +829,16 @@ MP4Reader::SkipVideoDemuxToNextKeyFrame(int64_t aTimeThreshold, uint32_t& parsed return true; } -bool -MP4Reader::DecodeVideoFrame(bool &aKeyframeSkip, - int64_t aTimeThreshold) -{ - // Record number of frames decoded and parsed. Automatically update the - // stats counters using the AutoNotifyDecoded stack-based class. - uint32_t parsed = 0, decoded = 0; - AbstractMediaDecoder::AutoNotifyDecoded autoNotify(mDecoder, parsed, decoded); - - MOZ_ASSERT(HasVideo() && mPlatform && mVideo.mDecoder); - - if (aKeyframeSkip) { - bool ok = SkipVideoDemuxToNextKeyFrame(aTimeThreshold, parsed); - if (!ok) { - NS_WARNING("Failed to skip demux up to next keyframe"); - return false; - } - aKeyframeSkip = false; - nsresult rv = mVideo.mDecoder->Flush(); - NS_ENSURE_SUCCESS(rv, false); - } - - bool rv = Decode(kVideo); - { - // Report the number of "decoded" frames as the difference in the - // mNumSamplesOutput field since the last time we were called. - MonitorAutoLock mon(mVideo.mMonitor); - uint64_t delta = mVideo.mNumSamplesOutput - mLastReportedNumDecodedFrames; - decoded = static_cast(delta); - mLastReportedNumDecodedFrames = mVideo.mNumSamplesOutput; - } - return rv; -} - void MP4Reader::Seek(int64_t aTime, int64_t aStartTime, int64_t aEndTime, int64_t aCurrentTime) { + LOG("MP4Reader::Seek(%lld)", aTime); + MOZ_ASSERT(GetTaskQueue()->IsCurrentThreadIn()); if (!mDecoder->GetResource()->IsTransportSeekable() || !mDemuxer->CanSeek()) { + VLOG("Seek() END (Unseekable)"); GetCallback()->OnSeekCompleted(NS_ERROR_FAILURE); return; } @@ -793,7 +852,7 @@ MP4Reader::Seek(int64_t aTime, mDemuxer->SeekAudio( mQueuedVideoSample ? mQueuedVideoSample->composition_timestamp : aTime); } - + LOG("MP4Reader::Seek(%lld) exit", aTime); GetCallback()->OnSeekCompleted(NS_OK); } diff --git a/dom/media/fmp4/MP4Reader.h b/dom/media/fmp4/MP4Reader.h index e410cbf3b363..af7cf2112b0f 100644 --- a/dom/media/fmp4/MP4Reader.h +++ b/dom/media/fmp4/MP4Reader.h @@ -28,6 +28,8 @@ class MP4Stream; class MP4Reader : public MediaDecoderReader { + typedef mp4_demuxer::TrackType TrackType; + public: explicit MP4Reader(AbstractMediaDecoder* aDecoder); @@ -35,10 +37,11 @@ public: virtual nsresult Init(MediaDecoderReader* aCloneDonor) MOZ_OVERRIDE; - virtual bool DecodeAudioData() MOZ_OVERRIDE; - virtual bool DecodeVideoFrame(bool &aKeyframeSkip, + virtual void RequestVideoData(bool aSkipToNextKeyframe, int64_t aTimeThreshold) MOZ_OVERRIDE; + virtual void RequestAudioData() MOZ_OVERRIDE; + virtual bool HasAudio() MOZ_OVERRIDE; virtual bool HasVideo() MOZ_OVERRIDE; @@ -75,6 +78,17 @@ public: private: + void ReturnEOS(TrackType aTrack); + void ReturnOutput(MediaData* aData, TrackType aTrack); + + // Sends input to decoder for aTrack, and output to the state machine, + // if necessary. + void Update(TrackType aTrack); + + // Enqueues a task to call Update(aTrack) on the decoder task queue. + // Lock for corresponding track must be held. + void ScheduleUpdate(TrackType aTrack); + void ExtractCryptoInitData(nsTArray& aInitData); // Initializes mLayersBackendType if possible. @@ -86,10 +100,11 @@ private: bool SkipVideoDemuxToNextKeyFrame(int64_t aTimeThreshold, uint32_t& parsed); + // DecoderCallback proxies the MediaDataDecoderCallback calls to these + // functions. void Output(mp4_demuxer::TrackType aType, MediaData* aSample); void InputExhausted(mp4_demuxer::TrackType aTrack); void Error(mp4_demuxer::TrackType aTrack); - bool Decode(mp4_demuxer::TrackType aTrack); void Flush(mp4_demuxer::TrackType aTrack); void DrainComplete(mp4_demuxer::TrackType aTrack); void UpdateIndex(); @@ -144,7 +159,10 @@ private: , mError(false) , mIsFlushing(false) , mDrainComplete(false) + , mOutputRequested(false) + , mUpdateScheduled(false) , mEOS(false) + , mDiscontinuity(false) { } @@ -155,6 +173,10 @@ private: nsRefPtr mTaskQueue; // Callback that receives output and error notifications from the decoder. nsAutoPtr mCallback; + // Decoded samples returned my mDecoder awaiting being returned to + // state machine upon request. + nsTArray > mOutput; + // Monitor that protects all non-threadsafe state; the primitives // that follow. Monitor mMonitor; @@ -167,7 +189,10 @@ private: bool mError; bool mIsFlushing; bool mDrainComplete; + bool mOutputRequested; + bool mUpdateScheduled; bool mEOS; + bool mDiscontinuity; }; DecoderData mAudio; DecoderData mVideo; @@ -175,6 +200,10 @@ private: // decoder. nsAutoPtr mQueuedVideoSample; + // Returns true when the decoder for this track needs input. + // aDecoder.mMonitor must be locked. + bool NeedInput(DecoderData& aDecoder); + // The last number of decoded output frames that we've reported to // MediaDecoder::NotifyDecoded(). We diff the number of output video // frames every time that DecodeVideoData() is called, and report the diff --git a/dom/media/fmp4/PlatformDecoderModule.h b/dom/media/fmp4/PlatformDecoderModule.h index 23d11423a675..1ee322855174 100644 --- a/dom/media/fmp4/PlatformDecoderModule.h +++ b/dom/media/fmp4/PlatformDecoderModule.h @@ -164,8 +164,9 @@ public: // media data that the decoder accepts as valid input and produces as // output is determined when the MediaDataDecoder is created. // -// All functions must be threadsafe, and be able to be called on an -// arbitrary thread. +// All functions are only called on the decode task queue. Don't block +// inside these functions, unless it's explicitly noted that you should +// (like in Flush() and Drain()). // // Decoding is done asynchronously. Any async work can be done on the // MediaTaskQueue passed into the PlatformDecoderModules's Create*Decoder() diff --git a/dom/media/gtest/TestMP4Reader.cpp b/dom/media/gtest/TestMP4Reader.cpp index 4a13b92df639..a107f8604830 100644 --- a/dom/media/gtest/TestMP4Reader.cpp +++ b/dom/media/gtest/TestMP4Reader.cpp @@ -36,6 +36,8 @@ public: decoder->SetResource(resource); reader->Init(nullptr); + reader->SetTaskQueue( + new MediaTaskQueue(SharedThreadPool::Get(NS_LITERAL_CSTRING("TestMP4Reader")))); { // This needs to be done before invoking GetBuffered. This is normally // done by MediaDecoderStateMachine. @@ -55,6 +57,10 @@ public: private: virtual ~TestBinding() { + reader->GetTaskQueue()->Dispatch(NS_NewRunnableMethod(reader, + &MP4Reader::Shutdown)); + reader->GetTaskQueue()->Shutdown(); + decoder = nullptr; resource = nullptr; reader = nullptr;