Bug 973408 - Split innards of MediaDecoderStateMachine::DecodeLoop() into sub DecodeAudio/DecodeVideo functions. r=kinetik

This commit is contained in:
Chris Pearce 2014-03-11 11:44:09 +08:00
parent 7a3ec7f3ef
commit b078dcfd7a
2 changed files with 202 additions and 162 deletions

View File

@ -188,8 +188,6 @@ MediaDecoderStateMachine::MediaDecoderStateMachine(MediaDecoder* aDecoder,
mDispatchedRunEvent(false),
mDecodeThreadWaiting(false),
mRealTime(aRealTime),
mDidThrottleAudioDecoding(false),
mDidThrottleVideoDecoding(false),
mEventManager(aDecoder),
mLastFrameStatus(MediaDecoderOwner::NEXT_FRAME_UNINITIALIZED)
{
@ -543,6 +541,153 @@ bool MediaDecoderStateMachine::HaveEnoughDecodedVideo()
return true;
}
bool
MediaDecoderStateMachine::NeedToDecodeVideo()
{
AssertCurrentThreadInMonitor();
NS_ASSERTION(OnDecodeThread(), "Should be on decode thread.");
return mIsVideoDecoding && !HaveEnoughDecodedVideo();
}
void
MediaDecoderStateMachine::DecodeVideo()
{
AssertCurrentThreadInMonitor();
NS_ASSERTION(OnDecodeThread(), "Should be on decode thread.");
if ((mState != DECODER_STATE_DECODING && mState != DECODER_STATE_BUFFERING) ||
mStopDecodeThread ||
!NeedToDecodeVideo()) {
return;
}
// 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
// some frames before enabling the keyframe skip logic on video.
if (mIsVideoPrerolling &&
(static_cast<uint32_t>(mReader->VideoQueue().GetSize())
>= mVideoPrerollFrames * mPlaybackRate))
{
mIsVideoPrerolling = false;
}
// We'll skip the video decode to the nearest keyframe if we're low on
// audio, or if we're low on video, provided we're not running low on
// data to decode. If we're running low on downloaded data to decode,
// we won't start keyframe skipping, as we'll be pausing playback to buffer
// soon anyway and we'll want to be able to display frames immediately
// after buffering finishes.
if (mState == DECODER_STATE_DECODING &&
!mSkipToNextKeyFrame &&
mIsVideoDecoding &&
((!mIsAudioPrerolling && mIsAudioDecoding &&
GetDecodedAudioDuration() < mLowAudioThresholdUsecs * mPlaybackRate) ||
(!mIsVideoPrerolling && mIsVideoDecoding &&
(static_cast<uint32_t>(mReader->VideoQueue().GetSize())
< LOW_VIDEO_FRAMES * mPlaybackRate))) &&
!HasLowUndecodedData())
{
mSkipToNextKeyFrame = true;
DECODER_LOG(PR_LOG_DEBUG, ("%p Skipping video decode to the next keyframe", mDecoder.get()));
}
// Time the video decode, so that if it's slow, we can increase our low
// audio threshold to reduce the chance of an audio underrun while we're
// waiting for a video decode to complete.
TimeDuration decodeTime;
{
int64_t currentTime = GetMediaTime();
ReentrantMonitorAutoExit exitMon(mDecoder->GetReentrantMonitor());
TimeStamp start = TimeStamp::Now();
mIsVideoDecoding = mReader->DecodeVideoFrame(mSkipToNextKeyFrame, currentTime);
decodeTime = TimeStamp::Now() - start;
}
if (!mIsVideoDecoding) {
// Playback ended for this stream, close the sample queue.
mReader->VideoQueue().Finish();
if (!mIsAudioDecoding) {
// We've also finished audio decoding, or we don't have audio,
// so move to COMPLETED state.
mState = DECODER_STATE_COMPLETED;
ScheduleStateMachine();
}
}
if (THRESHOLD_FACTOR * DurationToUsecs(decodeTime) > mLowAudioThresholdUsecs &&
!HasLowUndecodedData())
{
mLowAudioThresholdUsecs =
std::min(THRESHOLD_FACTOR * DurationToUsecs(decodeTime), AMPLE_AUDIO_USECS);
mAmpleAudioThresholdUsecs = std::max(THRESHOLD_FACTOR * mLowAudioThresholdUsecs,
mAmpleAudioThresholdUsecs);
DECODER_LOG(PR_LOG_DEBUG,
("Slow video decode, set mLowAudioThresholdUsecs=%lld mAmpleAudioThresholdUsecs=%lld",
mLowAudioThresholdUsecs, mAmpleAudioThresholdUsecs));
}
SendStreamData();
// The ready state can change when we've decoded data, so update the
// ready state, so that DOM events can fire.
UpdateReadyState();
}
bool
MediaDecoderStateMachine::NeedToDecodeAudio()
{
AssertCurrentThreadInMonitor();
NS_ASSERTION(OnDecodeThread(), "Should be on decode thread.");
return mIsAudioDecoding &&
!HaveEnoughDecodedAudio(mAmpleAudioThresholdUsecs * mPlaybackRate);
}
void
MediaDecoderStateMachine::DecodeAudio()
{
AssertCurrentThreadInMonitor();
NS_ASSERTION(OnDecodeThread(), "Should be on decode thread.");
if ((mState != DECODER_STATE_DECODING && mState != DECODER_STATE_BUFFERING) ||
mStopDecodeThread ||
!NeedToDecodeAudio()) {
return;
}
// 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
// some audio data before enabling the keyframe skip logic on audio.
if (mIsAudioPrerolling &&
GetDecodedAudioDuration() >= mAudioPrerollUsecs * mPlaybackRate) {
mIsAudioPrerolling = false;
}
{
ReentrantMonitorAutoExit exitMon(mDecoder->GetReentrantMonitor());
mIsAudioDecoding = mReader->DecodeAudioData();
}
if (!mIsAudioDecoding) {
// Playback ended for this stream, close the sample queue.
mReader->AudioQueue().Finish();
if (!mIsVideoDecoding) {
// We've also finished video decoding, or we don't have video,
// so move to COMPLETED state.
mState = DECODER_STATE_COMPLETED;
ScheduleStateMachine();
}
}
SendStreamData();
// Notify to ensure that the AudioLoop() is not waiting, in case it was
// waiting for more audio to be decoded.
mDecoder->GetReentrantMonitor().NotifyAll();
// The ready state can change when we've decoded data, so update the
// ready state, so that DOM events can fire.
UpdateReadyState();
}
void MediaDecoderStateMachine::DecodeLoop()
{
ReentrantMonitorAutoEnter mon(mDecoder->GetReentrantMonitor());
@ -563,110 +708,13 @@ void MediaDecoderStateMachine::DecodeLoop()
!mStopDecodeThread &&
(mIsVideoDecoding || mIsAudioDecoding))
{
// 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
// some frames before enabling the keyframe skip logic on video.
if (mIsVideoPrerolling &&
(static_cast<uint32_t>(mReader->VideoQueue().GetSize())
>= mVideoPrerollFrames * mPlaybackRate))
{
mIsVideoPrerolling = false;
}
// 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
// some audio data before enabling the keyframe skip logic on audio.
if (mIsAudioPrerolling &&
GetDecodedAudioDuration() >= mAudioPrerollUsecs * mPlaybackRate) {
mIsAudioPrerolling = false;
}
// We'll skip the video decode to the nearest keyframe if we're low on
// audio, or if we're low on video, provided we're not running low on
// data to decode. If we're running low on downloaded data to decode,
// we won't start keyframe skipping, as we'll be pausing playback to buffer
// soon anyway and we'll want to be able to display frames immediately
// after buffering finishes.
if (mState == DECODER_STATE_DECODING &&
!mSkipToNextKeyFrame &&
mIsVideoDecoding &&
((!mIsAudioPrerolling && mIsAudioDecoding && !mDidThrottleAudioDecoding &&
GetDecodedAudioDuration() < mLowAudioThresholdUsecs * mPlaybackRate) ||
(!mIsVideoPrerolling && mIsVideoDecoding && !mDidThrottleVideoDecoding &&
(static_cast<uint32_t>(mReader->VideoQueue().GetSize())
< LOW_VIDEO_FRAMES * mPlaybackRate))) &&
!HasLowUndecodedData())
{
mSkipToNextKeyFrame = true;
DECODER_LOG(PR_LOG_DEBUG, ("%p Skipping video decode to the next keyframe", mDecoder.get()));
}
// Video decode.
bool throttleVideoDecoding = !mIsVideoDecoding || HaveEnoughDecodedVideo();
if (mDidThrottleVideoDecoding && !throttleVideoDecoding) {
mIsVideoPrerolling = true;
}
mDidThrottleVideoDecoding = throttleVideoDecoding;
if (!throttleVideoDecoding) {
// Time the video decode, so that if it's slow, we can increase our low
// audio threshold to reduce the chance of an audio underrun while we're
// waiting for a video decode to complete.
TimeDuration decodeTime;
{
int64_t currentTime = GetMediaTime();
ReentrantMonitorAutoExit exitMon(mDecoder->GetReentrantMonitor());
TimeStamp start = TimeStamp::Now();
mIsVideoDecoding = mReader->DecodeVideoFrame(mSkipToNextKeyFrame, currentTime);
decodeTime = TimeStamp::Now() - start;
if (!mIsVideoDecoding) {
// Playback ended for this stream, close the sample queue.
mReader->VideoQueue().Finish();
}
}
if (THRESHOLD_FACTOR * DurationToUsecs(decodeTime) > mLowAudioThresholdUsecs &&
!HasLowUndecodedData())
{
mLowAudioThresholdUsecs =
std::min(THRESHOLD_FACTOR * DurationToUsecs(decodeTime), AMPLE_AUDIO_USECS);
mAmpleAudioThresholdUsecs = std::max(THRESHOLD_FACTOR * mLowAudioThresholdUsecs,
mAmpleAudioThresholdUsecs);
DECODER_LOG(PR_LOG_DEBUG,
("Slow video decode, set mLowAudioThresholdUsecs=%lld mAmpleAudioThresholdUsecs=%lld",
mLowAudioThresholdUsecs, mAmpleAudioThresholdUsecs));
}
}
// Audio decode.
bool throttleAudioDecoding =
!mIsAudioDecoding ||
HaveEnoughDecodedAudio(mAmpleAudioThresholdUsecs * mPlaybackRate);
if (mDidThrottleAudioDecoding && !throttleAudioDecoding) {
mIsAudioPrerolling = true;
}
mDidThrottleAudioDecoding = throttleAudioDecoding;
if (!mDidThrottleAudioDecoding) {
ReentrantMonitorAutoExit exitMon(mDecoder->GetReentrantMonitor());
mIsAudioDecoding = mReader->DecodeAudioData();
if (!mIsAudioDecoding) {
// Playback ended for this stream, close the sample queue.
mReader->AudioQueue().Finish();
}
}
SendStreamData();
// Notify to ensure that the AudioLoop() is not waiting, in case it was
// waiting for more audio to be decoded.
mDecoder->GetReentrantMonitor().NotifyAll();
// The ready state can change when we've decoded data, so update the
// ready state, so that DOM events can fire.
UpdateReadyState();
DecodeAudio();
DecodeVideo();
if ((mState == DECODER_STATE_DECODING || mState == DECODER_STATE_BUFFERING) &&
!mStopDecodeThread &&
(mIsVideoDecoding || mIsAudioDecoding) &&
throttleAudioDecoding && throttleVideoDecoding)
!NeedToDecodeAudio() && !NeedToDecodeVideo())
{
// All active bitstreams' decode is well ahead of the playback
// position, we may as well wait for the playback to catch up. Note the
@ -688,18 +736,8 @@ void MediaDecoderStateMachine::DecodeLoop()
mDecoder->GetReentrantMonitor().Wait();
mDecodeThreadWaiting = false;
}
} // End decode loop.
if (!mStopDecodeThread &&
mState != DECODER_STATE_SHUTDOWN &&
mState != DECODER_STATE_DORMANT &&
mState != DECODER_STATE_SEEKING)
{
mState = DECODER_STATE_COMPLETED;
ScheduleStateMachine();
}
mDispatchedEventToDecode = false;
mon.NotifyAll();
DECODER_LOG(PR_LOG_DEBUG, ("%p Exiting DecodeLoop", mDecoder.get()));
@ -1614,26 +1652,26 @@ void MediaDecoderStateMachine::SetFrameBufferLength(uint32_t aLength)
}
void
MediaDecoderStateMachine::CallDecodeMetadata()
MediaDecoderStateMachine::DecodeError()
{
ReentrantMonitorAutoEnter mon(mDecoder->GetReentrantMonitor());
AssertCurrentThreadInMonitor();
NS_ASSERTION(OnDecodeThread(), "Should be on decode thread.");
if (mState != DECODER_STATE_DECODING_METADATA) {
return;
}
if (NS_FAILED(DecodeMetadata())) {
NS_ASSERTION(mState == DECODER_STATE_SHUTDOWN,
"Should be in shutdown state if metadata loading fails.");
DECODER_LOG(PR_LOG_DEBUG, ("Decode metadata failed, shutting down decoder"));
// Dispatch the event to call DecodeError synchronously. This ensures
// we're in shutdown state by the time we exit the decode thread.
// If we just moved to shutdown state here on the decode thread, we may
// cause the state machine to shutdown/free memory without closing its
// media stream properly, and we'll get callbacks from the media stream
// causing a crash.
// Change state to shutdown before sending error report to MediaDecoder
// and the HTMLMediaElement, so that our pipeline can start exiting
// cleanly during the sync dispatch below.
DECODER_LOG(PR_LOG_DEBUG, ("%p Changed state to SHUTDOWN", mDecoder.get()));
ScheduleStateMachine();
mState = DECODER_STATE_SHUTDOWN;
mDecoder->GetReentrantMonitor().NotifyAll();
// Dispatch the event to call DecodeError synchronously. This ensures
// we're in shutdown state by the time we exit the decode thread.
// If we just moved to shutdown state here on the decode thread, we may
// cause the state machine to shutdown/free memory without closing its
// media stream properly, and we'll get callbacks from the media stream
// causing a crash.
{
nsCOMPtr<nsIRunnable> event =
NS_NewRunnableMethod(mDecoder, &MediaDecoder::DecodeError);
ReentrantMonitorAutoExit exitMon(mDecoder->GetReentrantMonitor());
@ -1641,6 +1679,19 @@ MediaDecoderStateMachine::CallDecodeMetadata()
}
}
void
MediaDecoderStateMachine::CallDecodeMetadata()
{
ReentrantMonitorAutoEnter mon(mDecoder->GetReentrantMonitor());
if (mState != DECODER_STATE_DECODING_METADATA) {
return;
}
if (NS_FAILED(DecodeMetadata())) {
DECODER_LOG(PR_LOG_DEBUG, ("Decode metadata failed, shutting down decoder"));
DecodeError();
}
}
nsresult MediaDecoderStateMachine::DecodeMetadata()
{
AssertCurrentThreadInMonitor();
@ -1739,9 +1790,6 @@ void MediaDecoderStateMachine::DecodeSeek()
return;
}
mDidThrottleAudioDecoding = false;
mDidThrottleVideoDecoding = false;
// During the seek, don't have a lock on the decoder state,
// otherwise long seek operations can block the main thread.
// The events dispatched to the main thread are SYNC calls.
@ -1815,6 +1863,8 @@ void MediaDecoderStateMachine::DecodeSeek()
NS_DispatchToMainThread(event, NS_DISPATCH_NORMAL);
}
}
} else {
DecodeError();
}
}
mDecoder->StartProgressUpdates();
@ -1998,13 +2048,6 @@ nsresult MediaDecoderStateMachine::RunStateMachine()
StartPlayback();
}
if (IsPausedAndDecoderWaiting()) {
// The decode buffers are full, and playback is paused. Shutdown the
// decode thread.
StopDecodeThread();
return NS_OK;
}
// We're playing and/or our decode buffers aren't full. Ensure we have
// an active decode thread.
if (NS_FAILED(EnqueueDecodeTask())) {
@ -2020,13 +2063,6 @@ nsresult MediaDecoderStateMachine::RunStateMachine()
}
case DECODER_STATE_BUFFERING: {
if (IsPausedAndDecoderWaiting()) {
// The decode buffers are full, and playback is paused. Shutdown the
// decode thread.
StopDecodeThread();
return NS_OK;
}
TimeStamp now = TimeStamp::Now();
NS_ASSERTION(!mBufferingStart.IsNull(), "Must know buffering start time.");
@ -2492,16 +2528,6 @@ nsresult MediaDecoderStateMachine::GetBuffered(dom::TimeRanges* aBuffered) {
return res;
}
bool MediaDecoderStateMachine::IsPausedAndDecoderWaiting() {
AssertCurrentThreadInMonitor();
NS_ASSERTION(OnStateMachineThread(), "Should be on state machine thread.");
return
mDecodeThreadWaiting &&
mDecoder->GetState() != MediaDecoder::PLAY_STATE_PLAYING &&
(mState == DECODER_STATE_DECODING || mState == DECODER_STATE_BUFFERING);
}
nsresult MediaDecoderStateMachine::Run()
{
ReentrantMonitorAutoEnter mon(mDecoder->GetReentrantMonitor());

View File

@ -402,6 +402,20 @@ private:
};
WakeDecoderRunnable* GetWakeDecoderRunnable();
// True if our buffers of decoded audio are not full, and we should
// decode more.
bool NeedToDecodeAudio();
// Decodes some audio. This should be run on the decode task queue.
void DecodeAudio();
// True if our buffers of decoded video are not full, and we should
// decode more.
bool NeedToDecodeVideo();
// Decodes some video. This should be run on the decode task queue.
void DecodeVideo();
// Returns true if we've got less than aAudioUsecs microseconds of decoded
// and playable data. The decoder monitor must be held.
bool HasLowDecodedData(int64_t aAudioUsecs) const;
@ -525,6 +539,12 @@ private:
// thread. The decoder monitor must be held.
void StartDecoding();
// Moves the decoder into the shutdown state, and dispatches an error
// event to the media element. This begins shutting down the decoder.
// The decoder monitor must be held. This is only called on the
// decode thread.
void DecodeError();
void StartWaitForResources();
// Dispatches a task to the decode task queue to begin decoding metadata.
@ -877,12 +897,6 @@ private:
// True is we are decoding a realtime stream, like a camera stream
bool mRealTime;
// Record whether audio and video decoding were throttled during the
// previous iteration of DecodeLoop. When we transition from
// throttled to not-throttled we need to preroll decoding.
bool mDidThrottleAudioDecoding;
bool mDidThrottleVideoDecoding;
// Manager for queuing and dispatching MozAudioAvailable events. The
// event manager is accessed from the state machine and audio threads,
// and takes care of synchronizing access to its internal queue.