Bug 973408 - Set MediaDecoderReaders idle when they're not decoding. r=kinetik

This commit is contained in:
Chris Pearce 2014-03-11 11:44:10 +08:00
parent 161ebf7d5d
commit 517140e405
12 changed files with 145 additions and 102 deletions

View File

@ -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

View File

@ -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<nsIRunnable> 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) {

View File

@ -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

View File

@ -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)
{

View File

@ -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;

View File

@ -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<nsresult> 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<nsresult> result = mOmxDecoder->Play();
NS_ASSERTION(result == NS_OK, "OmxDecoder should be in play state to continue decoding");
}
} // namespace mozilla

View File

@ -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

View File

@ -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

View File

@ -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.

View File

@ -269,8 +269,6 @@ MediaDecodeTask::Decode()
// bakend support.
mDecoderReader->SetIgnoreAudioOutputFormat();
mDecoderReader->OnDecodeThreadStart();
MediaInfo mediaInfo;
nsAutoPtr<MetadataTags> tags;
nsresult rv = mDecoderReader->ReadMetadata(&mediaInfo, getter_Transfers(tags));
@ -289,8 +287,6 @@ MediaDecodeTask::Decode()
continue;
}
mDecoderReader->OnDecodeThreadFinish();
MediaQueue<AudioData>& audioQueue = mDecoderReader->AudioQueue();
uint32_t frameCount = audioQueue.FrameCount();
uint32_t channelCount = mediaInfo.mAudio.mChannels;

View File

@ -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()
{

View File

@ -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();