diff --git a/content/media/MediaDecoderReader.h b/content/media/MediaDecoderReader.h index 957c5a07760e..4189731ad495 100644 --- a/content/media/MediaDecoderReader.h +++ b/content/media/MediaDecoderReader.h @@ -78,16 +78,16 @@ public: int64_t aEndTime, int64_t aCurrentTime) = 0; - // Called when the decode thread is started, before calling any other - // decode, read metadata, or seek functions. Do any thread local setup - // in this function. - virtual void OnDecodeThreadStart() {} - - // Called when the decode thread is about to finish, after all calls to - // any other decode, read metadata, or seek functions. Any backend specific - // thread local tear down must be done in this function. Note that another - // decode thread could start up and run in future. - virtual void OnDecodeThreadFinish() {} + // Called to move the reader into idle/active state. When the reader is + // created it is assumed to be active (i.e. not idle). When the media + // element is paused and we don't need to decode any more data, the state + // machine calls SetIdle() to inform the reader that its decoder won't be + // needed for a while. When we need to decode data again, the state machine + // calls SetActive() to activate the decoder. The reader can use these + // notifications to enter/exit a low power state when the decoder isn't + // needed, if desired. This is most useful on mobile. + virtual void SetIdle() { } + virtual void SetActive() { } // Tell the reader that the data decoded are not for direct playback, so it // can accept more files, in particular those which have more channels than diff --git a/content/media/MediaDecoderStateMachine.cpp b/content/media/MediaDecoderStateMachine.cpp index 393227722cbb..02fbfb1ff1d4 100644 --- a/content/media/MediaDecoderStateMachine.cpp +++ b/content/media/MediaDecoderStateMachine.cpp @@ -175,6 +175,7 @@ MediaDecoderStateMachine::MediaDecoderStateMachine(MediaDecoder* aDecoder, mAmpleAudioThresholdUsecs(AMPLE_AUDIO_USECS), mDispatchedAudioDecodeTask(false), mDispatchedVideoDecodeTask(false), + mIsReaderIdle(false), mAudioCaptured(false), mTransportSeekable(true), mMediaSeekable(true), @@ -559,6 +560,7 @@ MediaDecoderStateMachine::DecodeVideo() mDispatchedVideoDecodeTask = false; return; } + EnsureActive(); // We don't want to consider skipping to the next keyframe if we've // only just started up the decode loop, so wait until we've decoded @@ -628,6 +630,8 @@ MediaDecoderStateMachine::DecodeVideo() mDispatchedVideoDecodeTask = false; if (NeedToDecodeVideo()) { EnsureVideoDecodeTaskQueued(); + } else { + UpdateIdleState(); } } @@ -651,6 +655,7 @@ MediaDecoderStateMachine::DecodeAudio() mDispatchedAudioDecodeTask = false; return; } + EnsureActive(); // We don't want to consider skipping to the next keyframe if we've // only just started up the decode loop, so wait until we've decoded @@ -683,6 +688,8 @@ MediaDecoderStateMachine::DecodeAudio() mDispatchedAudioDecodeTask = false; if (NeedToDecodeAudio()) { EnsureAudioDecodeTaskQueued(); + } else { + UpdateIdleState(); } } @@ -702,6 +709,7 @@ MediaDecoderStateMachine::CheckIfDecodeComplete() // We've finished decoding all active streams, // so move to COMPLETED state. mState = DECODER_STATE_COMPLETED; + UpdateIdleState(); ScheduleStateMachine(); } DECODER_LOG(PR_LOG_DEBUG, @@ -1053,6 +1061,8 @@ void MediaDecoderStateMachine::StopPlayback() mDecoder->GetReentrantMonitor().NotifyAll(); NS_ASSERTION(!IsPlaying(), "Should report not playing at end of StopPlayback()"); mDecoder->UpdateStreamBlockingForStateMachinePlaying(); + + UpdateIdleState(); } void MediaDecoderStateMachine::SetSyncPointForMediaStream() @@ -1317,6 +1327,11 @@ void MediaDecoderStateMachine::StartDecoding() mIsVideoDecoding = HasVideo() && !mReader->VideoQueue().IsFinished(); mIsAudioDecoding = HasAudio() && !mReader->AudioQueue().IsFinished(); + CheckIfDecodeComplete(); + if (mState == DECODER_STATE_COMPLETED) { + return; + } + // Reset other state to pristine values before starting decode. mSkipToNextKeyFrame = false; mIsAudioPrerolling = true; @@ -1482,6 +1497,64 @@ MediaDecoderStateMachine::EnqueueDecodeMetadataTask() return NS_OK; } +void +MediaDecoderStateMachine::EnsureActive() +{ + AssertCurrentThreadInMonitor(); + MOZ_ASSERT(OnDecodeThread()); + if (!mIsReaderIdle) { + return; + } + mIsReaderIdle = false; + SetReaderActive(); +} + +void +MediaDecoderStateMachine::SetReaderIdle() +{ + DECODER_LOG(PR_LOG_DEBUG, ("%p SetReaderIdle()", mDecoder.get())); + MOZ_ASSERT(OnDecodeThread()); + mReader->SetIdle(); +} + +void +MediaDecoderStateMachine::SetReaderActive() +{ + DECODER_LOG(PR_LOG_DEBUG, ("%p SetReaderActive()", mDecoder.get())); + MOZ_ASSERT(OnDecodeThread()); + mReader->SetActive(); +} + +void +MediaDecoderStateMachine::UpdateIdleState() +{ + AssertCurrentThreadInMonitor(); + + // If we're in completed state, we should not need to decode anything else. + MOZ_ASSERT(mState != DECODER_STATE_COMPLETED || + (!NeedToDecodeAudio() && !NeedToDecodeVideo())); + + bool needIdle = mDecoder->GetState() == MediaDecoder::PLAY_STATE_PAUSED && + !NeedToDecodeAudio() && + !NeedToDecodeVideo() && + !IsPlaying(); + + if (mIsReaderIdle == needIdle) { + return; + } + mIsReaderIdle = needIdle; + nsRefPtr event; + if (mIsReaderIdle) { + event = NS_NewRunnableMethod(this, &MediaDecoderStateMachine::SetReaderIdle); + } else { + event = NS_NewRunnableMethod(this, &MediaDecoderStateMachine::SetReaderActive); + } + if (NS_FAILED(mDecodeTaskQueue->Dispatch(event)) && + mState != DECODER_STATE_SHUTDOWN) { + NS_WARNING("Failed to dispatch event to set decoder idle state"); + } +} + nsresult MediaDecoderStateMachine::EnqueueDecodeSeekTask() { @@ -1698,6 +1771,7 @@ nsresult MediaDecoderStateMachine::DecodeMetadata() if (mState != DECODER_STATE_DECODING_METADATA) { return NS_ERROR_FAILURE; } + EnsureActive(); nsresult res; MediaInfo info; @@ -1803,6 +1877,7 @@ void MediaDecoderStateMachine::DecodeSeek() if (mState != DECODER_STATE_SEEKING) { return; } + EnsureActive(); // During the seek, don't have a lock on the decoder state, // otherwise long seek operations can block the main thread. @@ -1905,7 +1980,12 @@ void MediaDecoderStateMachine::DecodeSeek() DECODER_LOG(PR_LOG_DEBUG, ("%p Changed state from SEEKING (to %lld) to COMPLETED", mDecoder.get(), seekTime)); stopEvent = NS_NewRunnableMethod(mDecoder, &MediaDecoder::SeekingStoppedAtEnd); + // Explicitly set our state so we don't decode further, and so + // we report playback ended to the media element. mState = DECODER_STATE_COMPLETED; + mIsAudioDecoding = false; + mIsVideoDecoding = false; + UpdateIdleState(); } else { DECODER_LOG(PR_LOG_DEBUG, ("%p Changed state from SEEKING (to %lld) to DECODING", mDecoder.get(), seekTime)); @@ -2508,11 +2588,11 @@ void MediaDecoderStateMachine::StartBuffering() mDecoder.get(), decodeDuration.ToSeconds())); #ifdef PR_LOGGING MediaDecoder::Statistics stats = mDecoder->GetStatistics(); -#endif DECODER_LOG(PR_LOG_DEBUG, ("%p Playback rate: %.1lfKB/s%s download rate: %.1lfKB/s%s", mDecoder.get(), stats.mPlaybackRate/1024, stats.mPlaybackRateReliable ? "" : " (unreliable)", stats.mDownloadRate/1024, stats.mDownloadRateReliable ? "" : " (unreliable)")); +#endif } nsresult MediaDecoderStateMachine::GetBuffered(dom::TimeRanges* aBuffered) { diff --git a/content/media/MediaDecoderStateMachine.h b/content/media/MediaDecoderStateMachine.h index 70cccc5546ff..b8aa22b4290a 100644 --- a/content/media/MediaDecoderStateMachine.h +++ b/content/media/MediaDecoderStateMachine.h @@ -564,6 +564,22 @@ private: // The decoder monitor must be held. nsresult EnqueueDecodeSeekTask(); + // Calls the reader's SetIdle(), with aIsIdle as parameter. This is only + // called in a task dispatched to the decode task queue, don't call it + // directly. + void SetReaderIdle(); + void SetReaderActive(); + + // Re-evaluates the state and determines whether we should set the reader + // to idle mode. This is threadsafe, and can be called from any thread. + // The decoder monitor must be held. + void UpdateIdleState(); + + // Called before we do anything on the decode task queue to set the reader + // as not idle if it was idle. This is called before we decode, seek, or + // decode metadata (in case we were dormant or awaiting resources). + void EnsureActive(); + // Queries our state to see whether the decode has finished for all streams. // If so, we move into DECODER_STATE_COMPLETED and schedule the state machine // to run. @@ -831,6 +847,12 @@ private: // the video decode. bool mDispatchedVideoDecodeTask; + // True when the reader is initialized, but has been ordered "idle" by the + // state machine. This happens when the MediaQueue's of decoded data are + // "full" and playback is paused. The reader may choose to use the idle + // notification to enter a low power state. + bool mIsReaderIdle; + // If the video decode is falling behind the audio, we'll start dropping the // inter-frames up until the next keyframe which is at or before the current // playback position. skipToNextKeyframe is true if we're currently diff --git a/content/media/directshow/DirectShowReader.cpp b/content/media/directshow/DirectShowReader.cpp index bafd3f83026f..5db6be583628 100644 --- a/content/media/directshow/DirectShowReader.cpp +++ b/content/media/directshow/DirectShowReader.cpp @@ -393,21 +393,6 @@ DirectShowReader::Seek(int64_t aTargetUs, return DecodeToTarget(aTargetUs); } -void -DirectShowReader::OnDecodeThreadStart() -{ - MOZ_ASSERT(mDecoder->OnDecodeThread(), "Should be on decode thread."); - HRESULT hr = CoInitializeEx(0, COINIT_MULTITHREADED); - NS_ENSURE_TRUE_VOID(SUCCEEDED(hr)); -} - -void -DirectShowReader::OnDecodeThreadFinish() -{ - NS_ASSERTION(mDecoder->OnDecodeThread(), "Should be on decode thread."); - CoUninitialize(); -} - void DirectShowReader::NotifyDataArrived(const char* aBuffer, uint32_t aLength, int64_t aOffset) { diff --git a/content/media/directshow/DirectShowReader.h b/content/media/directshow/DirectShowReader.h index e5337b12b5bb..292d010d9ef5 100644 --- a/content/media/directshow/DirectShowReader.h +++ b/content/media/directshow/DirectShowReader.h @@ -65,9 +65,6 @@ public: int64_t aEndTime, int64_t aCurrentTime) MOZ_OVERRIDE; - void OnDecodeThreadStart() MOZ_OVERRIDE; - void OnDecodeThreadFinish() MOZ_OVERRIDE; - void NotifyDataArrived(const char* aBuffer, uint32_t aLength, int64_t aOffset) MOZ_OVERRIDE; diff --git a/content/media/omx/MediaOmxReader.cpp b/content/media/omx/MediaOmxReader.cpp index de2c3ee3db19..37023f0bb453 100644 --- a/content/media/omx/MediaOmxReader.cpp +++ b/content/media/omx/MediaOmxReader.cpp @@ -361,17 +361,19 @@ static uint64_t BytesToTime(int64_t offset, uint64_t length, uint64_t durationUs return uint64_t(double(durationUs) * perc); } -void MediaOmxReader::OnDecodeThreadFinish() { - if (mOmxDecoder.get()) { - mOmxDecoder->Pause(); +void MediaOmxReader::SetIdle() { + if (!mOmxDecoder.get()) { + return; } + mOmxDecoder->Pause(); } -void MediaOmxReader::OnDecodeThreadStart() { - if (mOmxDecoder.get()) { - DebugOnly result = mOmxDecoder->Play(); - NS_ASSERTION(result == NS_OK, "OmxDecoder should be in play state to continue decoding"); +void MediaOmxReader::SetActive() { + if (!mOmxDecoder.get()) { + return; } + DebugOnly result = mOmxDecoder->Play(); + NS_ASSERTION(result == NS_OK, "OmxDecoder should be in play state to continue decoding"); } } // namespace mozilla diff --git a/content/media/omx/MediaOmxReader.h b/content/media/omx/MediaOmxReader.h index fc57b6e82fda..65bee7baa414 100644 --- a/content/media/omx/MediaOmxReader.h +++ b/content/media/omx/MediaOmxReader.h @@ -79,9 +79,9 @@ public: MetadataTags** aTags); virtual nsresult Seek(int64_t aTime, int64_t aStartTime, int64_t aEndTime, int64_t aCurrentTime); - virtual void OnDecodeThreadStart() MOZ_OVERRIDE; + virtual void SetIdle() MOZ_OVERRIDE; + virtual void SetActive() MOZ_OVERRIDE; - virtual void OnDecodeThreadFinish() MOZ_OVERRIDE; }; } // namespace mozilla diff --git a/content/media/omx/RtspOmxReader.cpp b/content/media/omx/RtspOmxReader.cpp index 584193795903..92c762e66672 100644 --- a/content/media/omx/RtspOmxReader.cpp +++ b/content/media/omx/RtspOmxReader.cpp @@ -299,37 +299,8 @@ nsresult RtspOmxReader::Seek(int64_t aTime, int64_t aStartTime, return MediaOmxReader::Seek(aTime, aStartTime, aEndTime, aCurrentTime); } -void RtspOmxReader::OnDecodeThreadStart() { - // Start RTSP streaming right after starting the decoding thread in - // MediaDecoderStateMachine and before starting OMXCodec decoding. - if (mRtspResource) { - nsIStreamingProtocolController* controller = - mRtspResource->GetMediaStreamController(); - if (controller) { - controller->Play(); - } - } - - // Call parent class to start OMXCodec decoding. - MediaOmxReader::OnDecodeThreadStart(); -} - -void RtspOmxReader::OnDecodeThreadFinish() { - // Call parent class to pause OMXCodec decoding. - MediaOmxReader::OnDecodeThreadFinish(); - - // Stop RTSP streaming right before destroying the decoding thread in - // MediaDecoderStateMachine and after pausing OMXCodec decoding. - // RTSP streaming should not be paused until OMXCodec has been paused and - // until the decoding thread in MediaDecoderStateMachine is about to be - // destroyed. Otherwise, RtspMediaSource::read() would block the binder - // thread of OMXCodecObserver::onMessage() --> OMXCodec::on_message() --> - // OMXCodec::drainInputBuffer() due to network data starvation. Because - // OMXCodec::mLock is held by the binder thread in this case, all other - // threads would be blocked when they try to lock this mutex. As a result, the - // decoding thread in MediaDecoderStateMachine would be blocked forever in - // OMXCodec::read() if there is no enough data for RtspMediaSource::read() to - // return. +void RtspOmxReader::SetIdle() { + // Need to pause RTSP streaming OMXCodec decoding. if (mRtspResource) { nsIStreamingProtocolController* controller = mRtspResource->GetMediaStreamController(); @@ -337,6 +308,23 @@ void RtspOmxReader::OnDecodeThreadFinish() { controller->Pause(); } } + + // Call parent class to set OMXCodec idle. + MediaOmxReader::SetIdle(); +} + +void RtspOmxReader::SetActive() { + // Need to start RTSP streaming OMXCodec decoding. + if (mRtspResource) { + nsIStreamingProtocolController* controller = + mRtspResource->GetMediaStreamController(); + if (controller) { + controller->Play(); + } + } + + // Call parent class to set OMXCodec active. + MediaOmxReader::SetActive(); } } // namespace mozilla diff --git a/content/media/omx/RtspOmxReader.h b/content/media/omx/RtspOmxReader.h index f616ce7d0cc8..80093662a9d8 100644 --- a/content/media/omx/RtspOmxReader.h +++ b/content/media/omx/RtspOmxReader.h @@ -71,9 +71,8 @@ public: return nullptr; } - virtual void OnDecodeThreadStart() MOZ_OVERRIDE; - - virtual void OnDecodeThreadFinish() MOZ_OVERRIDE; + virtual void SetIdle() MOZ_OVERRIDE; + virtual void SetActive() MOZ_OVERRIDE; private: // A pointer to RtspMediaResource for calling the Rtsp specific function. diff --git a/content/media/webaudio/MediaBufferDecoder.cpp b/content/media/webaudio/MediaBufferDecoder.cpp index 290de200e88d..d22080974156 100644 --- a/content/media/webaudio/MediaBufferDecoder.cpp +++ b/content/media/webaudio/MediaBufferDecoder.cpp @@ -269,8 +269,6 @@ MediaDecodeTask::Decode() // bakend support. mDecoderReader->SetIgnoreAudioOutputFormat(); - mDecoderReader->OnDecodeThreadStart(); - MediaInfo mediaInfo; nsAutoPtr tags; nsresult rv = mDecoderReader->ReadMetadata(&mediaInfo, getter_Transfers(tags)); @@ -289,8 +287,6 @@ MediaDecodeTask::Decode() continue; } - mDecoderReader->OnDecodeThreadFinish(); - MediaQueue& audioQueue = mDecoderReader->AudioQueue(); uint32_t frameCount = audioQueue.FrameCount(); uint32_t channelCount = mediaInfo.mAudio.mChannels; diff --git a/content/media/wmf/WMFReader.cpp b/content/media/wmf/WMFReader.cpp index 8cde69e6b35f..9418a58cc9b8 100644 --- a/content/media/wmf/WMFReader.cpp +++ b/content/media/wmf/WMFReader.cpp @@ -80,28 +80,6 @@ WMFReader::~WMFReader() MOZ_COUNT_DTOR(WMFReader); } -void -WMFReader::OnDecodeThreadStart() -{ - NS_ASSERTION(mDecoder->OnDecodeThread(), "Should be on decode thread."); - - // XXX WebAudio will call this on the main thread so CoInit will definitely - // fail. You cannot change the concurrency model once already set. - // The main thread will continue to be STA, which seems to work, but MSDN - // recommends that MTA be used. - mCOMInitialized = SUCCEEDED(CoInitializeEx(0, COINIT_MULTITHREADED)); - NS_ENSURE_TRUE_VOID(mCOMInitialized); -} - -void -WMFReader::OnDecodeThreadFinish() -{ - NS_ASSERTION(mDecoder->OnDecodeThread(), "Should be on decode thread."); - if (mCOMInitialized) { - CoUninitialize(); - } -} - bool WMFReader::InitializeDXVA() { diff --git a/content/media/wmf/WMFReader.h b/content/media/wmf/WMFReader.h index 6231bff75c2f..2dc200acf500 100644 --- a/content/media/wmf/WMFReader.h +++ b/content/media/wmf/WMFReader.h @@ -47,10 +47,6 @@ public: int64_t aStartTime, int64_t aEndTime, int64_t aCurrentTime) MOZ_OVERRIDE; - - void OnDecodeThreadStart() MOZ_OVERRIDE; - void OnDecodeThreadFinish() MOZ_OVERRIDE; - private: HRESULT ConfigureAudioDecoder();