mirror of
https://github.com/mozilla/gecko-dev.git
synced 2024-10-24 02:35:41 +00:00
Bug 973408 - Split innards of MediaDecoderStateMachine::DecodeLoop() into sub DecodeAudio/DecodeVideo functions. r=kinetik
This commit is contained in:
parent
7a3ec7f3ef
commit
b078dcfd7a
@ -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());
|
||||
|
@ -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.
|
||||
|
Loading…
Reference in New Issue
Block a user