Bug 592833 - Merge all media state machines into a single thread. r=roc

This commit is contained in:
Chris Pearce 2011-07-12 15:39:34 +12:00
parent 09f21e3e39
commit f75f5e57e5
6 changed files with 306 additions and 160 deletions

View File

@ -1987,11 +1987,11 @@ void nsHTMLMediaElement::NetworkError()
void nsHTMLMediaElement::DecodeError()
{
if (mDecoder) {
mDecoder->Shutdown();
mDecoder = nsnull;
}
if (mIsLoadingFromSourceChildren) {
if (mDecoder) {
mDecoder->Shutdown();
mDecoder = nsnull;
}
mError = nsnull;
if (mSourceLoadCandidate) {
DispatchAsyncSourceError(mSourceLoadCandidate);

View File

@ -46,6 +46,7 @@
#include "nsTArray.h"
#include "VideoUtils.h"
#include "nsBuiltinDecoder.h"
#include "nsBuiltinDecoderStateMachine.h"
using namespace mozilla;
@ -122,20 +123,6 @@ PRBool nsBuiltinDecoder::Init(nsHTMLMediaElement* aElement)
return PR_TRUE;
}
void nsBuiltinDecoder::Stop()
{
NS_ASSERTION(NS_IsMainThread(), "Should be on main thread");
// The decode thread must die before the state machine can die.
// The state machine must die before the reader.
// The state machine must die before the decoder.
if (mStateMachineThread)
mStateMachineThread->Shutdown();
mStateMachineThread = nsnull;
mDecoderStateMachine = nsnull;
}
void nsBuiltinDecoder::Shutdown()
{
NS_ASSERTION(NS_IsMainThread(), "Should be on main thread.");
@ -161,17 +148,6 @@ void nsBuiltinDecoder::Shutdown()
ChangeState(PLAY_STATE_SHUTDOWN);
nsMediaDecoder::Shutdown();
// We can't destroy mDecoderStateMachine until mStateMachineThread is shut down.
// It's unsafe to Shutdown() the decode thread here, as
// nsIThread::Shutdown() may run events, such as JS event handlers,
// and we could be running at an unsafe time such as during element
// destruction.
// So we destroy the decoder on the main thread in an asynchronous event.
// See bug 468721.
nsCOMPtr<nsIRunnable> event =
NS_NewRunnableMethod(this, &nsBuiltinDecoder::Stop);
NS_DispatchToMainThread(event, NS_DISPATCH_NORMAL);
nsContentUtils::UnregisterShutdownObserver(this);
}
@ -183,8 +159,8 @@ nsBuiltinDecoder::~nsBuiltinDecoder()
}
nsresult nsBuiltinDecoder::Load(nsMediaStream* aStream,
nsIStreamListener** aStreamListener,
nsMediaDecoder* aCloneDonor)
nsIStreamListener** aStreamListener,
nsMediaDecoder* aCloneDonor)
{
NS_ASSERTION(NS_IsMainThread(), "Should be on main thread.");
if (aStreamListener) {
@ -229,7 +205,7 @@ nsresult nsBuiltinDecoder::Load(nsMediaStream* aStream,
ChangeState(PLAY_STATE_LOADING);
return StartStateMachineThread();
return ScheduleStateMachineThread();
}
nsresult nsBuiltinDecoder::RequestFrameBufferLength(PRUint32 aLength)
@ -244,39 +220,25 @@ nsresult nsBuiltinDecoder::RequestFrameBufferLength(PRUint32 aLength)
return res;
}
nsresult nsBuiltinDecoder::CreateStateMachineThread()
nsresult nsBuiltinDecoder::ScheduleStateMachineThread()
{
if (mShuttingDown)
return NS_OK;
NS_ASSERTION(NS_IsMainThread(), "Should be on main thread.");
NS_ASSERTION(mDecoderStateMachine,
"Must have state machine to start state machine thread");
if (mStateMachineThread) {
return NS_OK;
}
nsresult res = NS_NewThread(getter_AddRefs(mStateMachineThread));
if (NS_FAILED(res)) {
Shutdown();
return res;
}
return NS_OK;
}
nsresult nsBuiltinDecoder::StartStateMachineThread()
{
if (mShuttingDown)
return NS_OK;
NS_ASSERTION(mDecoderStateMachine,
"Must have state machine to start state machine thread");
nsresult res = CreateStateMachineThread();
if (NS_FAILED(res)) return res;
return mStateMachineThread->Dispatch(mDecoderStateMachine, NS_DISPATCH_NORMAL);
ReentrantMonitorAutoEnter mon(mReentrantMonitor);
nsBuiltinDecoderStateMachine* m =
static_cast<nsBuiltinDecoderStateMachine*>(mDecoderStateMachine.get());
return m->ScheduleStateMachine();
}
nsresult nsBuiltinDecoder::Play()
{
NS_ASSERTION(NS_IsMainThread(), "Should be on main thread.");
ReentrantMonitorAutoEnter mon(mReentrantMonitor);
nsresult res = StartStateMachineThread();
nsresult res = ScheduleStateMachineThread();
NS_ENSURE_SUCCESS(res,res);
if (mPlayState == PLAY_STATE_SEEKING) {
mNextState = PLAY_STATE_PLAYING;
@ -314,7 +276,7 @@ nsresult nsBuiltinDecoder::Seek(double aTime)
ChangeState(PLAY_STATE_SEEKING);
}
return StartStateMachineThread();
return ScheduleStateMachineThread();
}
nsresult nsBuiltinDecoder::PlaybackRateChanged()
@ -548,7 +510,7 @@ nsBuiltinDecoder::GetStatistics()
double nsBuiltinDecoder::ComputePlaybackRate(PRPackedBool* aReliable)
{
GetReentrantMonitor().AssertCurrentThreadIn();
NS_ASSERTION(NS_IsMainThread() || IsCurrentThread(mStateMachineThread),
NS_ASSERTION(NS_IsMainThread() || OnStateMachineThread(),
"Should be on main or state machine thread.");
PRInt64 length = mStream ? mStream->GetLength() : -1;
@ -561,7 +523,7 @@ double nsBuiltinDecoder::ComputePlaybackRate(PRPackedBool* aReliable)
void nsBuiltinDecoder::UpdatePlaybackRate()
{
NS_ASSERTION(NS_IsMainThread() || IsCurrentThread(mStateMachineThread),
NS_ASSERTION(NS_IsMainThread() || OnStateMachineThread(),
"Should be on main or state machine thread.");
GetReentrantMonitor().AssertCurrentThreadIn();
if (!mStream)
@ -921,3 +883,7 @@ void nsBuiltinDecoder::UpdatePlaybackOffset(PRInt64 aOffset)
ReentrantMonitorAutoEnter mon(mReentrantMonitor);
mPlaybackPosition = NS_MAX(aOffset, mPlaybackPosition);
}
PRBool nsBuiltinDecoder::OnStateMachineThread() const {
return IsCurrentThread(nsBuiltinDecoderStateMachine::GetStateMachineThread());
}

View File

@ -269,6 +269,9 @@ public:
// on the appropriate threads.
virtual PRBool OnDecodeThread() const = 0;
// Returns PR_TRUE if the current thread is the state machine thread.
virtual PRBool OnStateMachineThread() const = 0;
virtual nsHTMLMediaElement::NextFrameStatus GetNextFrameStatus() = 0;
// Cause state transitions. These methods obtain the decoder monitor
@ -425,19 +428,13 @@ class nsBuiltinDecoder : public nsMediaDecoder
// Tells our nsMediaStream to put all loads in the background.
virtual void MoveLoadsToBackground();
// Stop the state machine thread and drop references to the thread and
// state machine.
void Stop();
void AudioAvailable(float* aFrameBuffer, PRUint32 aFrameBufferLength, float aTime);
// Called by the state machine to notify the decoder that the duration
// has changed.
void DurationChanged();
PRBool OnStateMachineThread() const {
return IsCurrentThread(mStateMachineThread);
}
PRBool OnStateMachineThread() const;
PRBool OnDecodeThread() const {
return mDecoderStateMachine->OnDecodeThread();
@ -567,14 +564,9 @@ public:
// Notifies the element that decoding has failed.
void DecodeError();
// Ensures the state machine thread is running, starting a new one
// if necessary.
nsresult StartStateMachineThread();
// Creates the state machine thread. The state machine may wish to create
// the state machine thread without running it immediately if it needs to
// schedule it to run in future.
nsresult CreateStateMachineThread();
// Schedules the state machine to run one cycle on the shared state
// machine thread. Main thread only.
nsresult ScheduleStateMachineThread();
/******
* The following members should be accessed with the decoder lock held.
@ -595,9 +587,6 @@ public:
// time of the last decoded video frame).
nsChannelStatistics mPlaybackStatistics;
// Thread to manage playback state machine.
nsCOMPtr<nsIThread> mStateMachineThread;
// The current playback position of the media resource in units of
// seconds. This is updated approximately at the framerate of the
// video (if it is a video) or the callback period of the audio.

View File

@ -171,6 +171,28 @@ public:
const PRUint32 mRate;
};
static PRUint32 gStateMachineCount = 0;
static nsIThread* gStateMachineThread = 0;
nsIThread* nsBuiltinDecoderStateMachine::GetStateMachineThread() {
return gStateMachineThread;
}
// Shuts down a thread asynchronously.
class ShutdownThreadEvent : public nsRunnable
{
public:
ShutdownThreadEvent(nsIThread* aThread) : mThread(aThread) {}
~ShutdownThreadEvent() {}
NS_IMETHOD Run() {
mThread->Shutdown();
mThread = nsnull;
return NS_OK;
}
private:
nsCOMPtr<nsIThread> mThread;
};
nsBuiltinDecoderStateMachine::nsBuiltinDecoderStateMachine(nsBuiltinDecoder* aDecoder,
nsBuiltinDecoderReader* aReader) :
mDecoder(aDecoder),
@ -194,17 +216,40 @@ nsBuiltinDecoderStateMachine::nsBuiltinDecoderStateMachine(nsBuiltinDecoder* aDe
mDecodeThreadIdle(PR_FALSE),
mStopAudioThread(PR_TRUE),
mQuickBuffering(PR_FALSE),
mEventManager(aDecoder)
mEventManager(aDecoder),
mIsRunning(PR_FALSE),
mRunAgain(PR_FALSE),
mDispatchedRunEvent(PR_FALSE)
{
MOZ_COUNT_CTOR(nsBuiltinDecoderStateMachine);
NS_ASSERTION(NS_IsMainThread(), "Should be on main thread.");
if (gStateMachineCount == 0) {
NS_ASSERTION(!gStateMachineThread, "Should have null state machine thread!");
nsresult res = NS_NewThread(&gStateMachineThread);
NS_ABORT_IF_FALSE(NS_SUCCEEDED(res), "Can't create media state machine thread");
}
gStateMachineCount++;
}
nsBuiltinDecoderStateMachine::~nsBuiltinDecoderStateMachine()
{
NS_ASSERTION(NS_IsMainThread(), "Should be on main thread.");
MOZ_COUNT_DTOR(nsBuiltinDecoderStateMachine);
if (mTimer)
mTimer->Cancel();
mTimer = nsnull;
NS_ASSERTION(NS_IsMainThread(), "Should be on main thread.");
NS_ABORT_IF_FALSE(gStateMachineCount > 0,
"State machine ref count must be > 0");
gStateMachineCount--;
if (gStateMachineCount == 0) {
LOG(PR_LOG_DEBUG, ("Destroying media state machine thread"));
nsCOMPtr<nsIRunnable> event = new ShutdownThreadEvent(gStateMachineThread);
NS_RELEASE(gStateMachineThread);
gStateMachineThread = nsnull;
NS_DispatchToMainThread(event);
}
}
PRBool nsBuiltinDecoderStateMachine::HasFutureAudio() const {
@ -257,7 +302,7 @@ void nsBuiltinDecoderStateMachine::DecodeThreadRun()
}
mDecodeThreadIdle = PR_TRUE;
LOG(PR_LOG_DEBUG, ("%p Decode thread finished", mDecoder));
LOG(PR_LOG_DEBUG, ("%p Decode thread finished", mDecoder.get()));
}
void nsBuiltinDecoderStateMachine::DecodeLoop()
@ -337,7 +382,7 @@ void nsBuiltinDecoderStateMachine::DecodeLoop()
{
skipToNextKeyframe = PR_TRUE;
LOG(PR_LOG_DEBUG, ("%p Skipping video decode to the next keyframe", mDecoder));
LOG(PR_LOG_DEBUG, ("%p Skipping video decode to the next keyframe", mDecoder.get()));
}
// Video decode.
@ -415,7 +460,7 @@ void nsBuiltinDecoderStateMachine::DecodeLoop()
ScheduleStateMachine();
}
LOG(PR_LOG_DEBUG, ("%p Exiting DecodeLoop", mDecoder));
LOG(PR_LOG_DEBUG, ("%p Exiting DecodeLoop", mDecoder.get()));
}
PRBool nsBuiltinDecoderStateMachine::IsPlaying()
@ -428,7 +473,7 @@ PRBool nsBuiltinDecoderStateMachine::IsPlaying()
void nsBuiltinDecoderStateMachine::AudioLoop()
{
NS_ASSERTION(OnAudioThread(), "Should be on audio thread.");
LOG(PR_LOG_DEBUG, ("%p Begun audio thread/loop", mDecoder));
LOG(PR_LOG_DEBUG, ("%p Begun audio thread/loop", mDecoder.get()));
PRInt64 audioDuration = 0;
PRInt64 audioStartTime = -1;
PRUint32 channels, rate;
@ -603,7 +648,7 @@ void nsBuiltinDecoderStateMachine::AudioLoop()
mEventManager.Drain(mAudioEndTime);
}
}
LOG(PR_LOG_DEBUG, ("%p Reached audio stream end.", mDecoder));
LOG(PR_LOG_DEBUG, ("%p Reached audio stream end.", mDecoder.get()));
{
// Must hold lock while shutting down and anulling audio stream to prevent
// state machine thread trying to use it while we're destroying it.
@ -616,7 +661,7 @@ void nsBuiltinDecoderStateMachine::AudioLoop()
// Kick the decode thread; it may be sleeping waiting for this to finish.
mDecoder->GetReentrantMonitor().NotifyAll();
}
LOG(PR_LOG_DEBUG, ("%p Audio stream finished playing, audio thread exit", mDecoder));
LOG(PR_LOG_DEBUG, ("%p Audio stream finished playing, audio thread exit", mDecoder.get()));
}
PRUint32 nsBuiltinDecoderStateMachine::PlaySilence(PRUint32 aSamples,
@ -715,7 +760,7 @@ void nsBuiltinDecoderStateMachine::StartPlayback()
{
NS_ASSERTION(!IsPlaying(), "Shouldn't be playing when StartPlayback() is called");
mDecoder->GetReentrantMonitor().AssertCurrentThreadIn();
LOG(PR_LOG_DEBUG, ("%p StartPlayback", mDecoder));
LOG(PR_LOG_DEBUG, ("%p StartPlayback", mDecoder.get()));
mDecoder->mPlaybackStatistics.Start(TimeStamp::Now());
mPlayStartTime = TimeStamp::Now();
NS_ASSERTION(IsPlaying(), "Should report playing by end of StartPlayback()");
@ -844,7 +889,7 @@ void nsBuiltinDecoderStateMachine::Shutdown()
// Change state before issuing shutdown request to threads so those
// threads can start exiting cleanly during the Shutdown call.
LOG(PR_LOG_DEBUG, ("%p Changed state to SHUTDOWN", mDecoder));
LOG(PR_LOG_DEBUG, ("%p Changed state to SHUTDOWN", mDecoder.get()));
ScheduleStateMachine();
mState = DECODER_STATE_SHUTDOWN;
mDecoder->GetReentrantMonitor().NotifyAll();
@ -870,7 +915,7 @@ void nsBuiltinDecoderStateMachine::Play()
// when the state machine notices the decoder's state change to PLAYING.
ReentrantMonitorAutoEnter mon(mDecoder->GetReentrantMonitor());
if (mState == DECODER_STATE_BUFFERING) {
LOG(PR_LOG_DEBUG, ("%p Changed state from BUFFERING to DECODING", mDecoder));
LOG(PR_LOG_DEBUG, ("%p Changed state from BUFFERING to DECODING", mDecoder.get()));
mState = DECODER_STATE_DECODING;
mDecodeStartTime = TimeStamp::Now();
}
@ -911,20 +956,19 @@ void nsBuiltinDecoderStateMachine::Seek(double aTime)
NS_ASSERTION(mEndTime != -1, "Should know end time by now");
mSeekTime = NS_MIN(mSeekTime, mEndTime);
mSeekTime = NS_MAX(mStartTime, mSeekTime);
LOG(PR_LOG_DEBUG, ("%p Changed state to SEEKING (to %f)", mDecoder, aTime));
LOG(PR_LOG_DEBUG, ("%p Changed state to SEEKING (to %f)", mDecoder.get(), aTime));
mState = DECODER_STATE_SEEKING;
ScheduleStateMachine();
}
void nsBuiltinDecoderStateMachine::StopDecodeThread()
{
NS_ASSERTION(OnStateMachineThread() || OnDecodeThread(),
"Should be on state machine thread.");
NS_ASSERTION(OnStateMachineThread(), "Should be on state machine thread.");
mDecoder->GetReentrantMonitor().AssertCurrentThreadIn();
mStopDecodeThread = PR_TRUE;
mDecoder->GetReentrantMonitor().NotifyAll();
if (mDecodeThread) {
LOG(PR_LOG_DEBUG, ("%p Shutdown decode thread", mDecoder));
LOG(PR_LOG_DEBUG, ("%p Shutdown decode thread", mDecoder.get()));
{
ReentrantMonitorAutoExit exitMon(mDecoder->GetReentrantMonitor());
mDecodeThread->Shutdown();
@ -940,7 +984,7 @@ void nsBuiltinDecoderStateMachine::StopAudioThread()
mStopAudioThread = PR_TRUE;
mDecoder->GetReentrantMonitor().NotifyAll();
if (mAudioThread) {
LOG(PR_LOG_DEBUG, ("%p Shutdown audio thread", mDecoder));
LOG(PR_LOG_DEBUG, ("%p Shutdown audio thread", mDecoder.get()));
{
ReentrantMonitorAutoExit exitMon(mDecoder->GetReentrantMonitor());
mAudioThread->Shutdown();
@ -952,8 +996,7 @@ void nsBuiltinDecoderStateMachine::StopAudioThread()
nsresult
nsBuiltinDecoderStateMachine::StartDecodeThread()
{
NS_ASSERTION(IsCurrentThread(mDecoder->mStateMachineThread),
"Should be on state machine thread.");
NS_ASSERTION(OnStateMachineThread(), "Should be on state machine thread.");
mDecoder->GetReentrantMonitor().AssertCurrentThreadIn();
mStopDecodeThread = PR_FALSE;
if ((mDecodeThread && !mDecodeThreadIdle) || mState >= DECODER_STATE_COMPLETED)
@ -1072,7 +1115,7 @@ nsresult nsBuiltinDecoderStateMachine::DecodeMetadata()
NS_ASSERTION(mState == DECODER_STATE_DECODING_METADATA,
"Only call when in metadata decoding state");
LOG(PR_LOG_DEBUG, ("%p Decoding Media Headers", mDecoder));
LOG(PR_LOG_DEBUG, ("%p Decoding Media Headers", mDecoder.get()));
nsresult res;
nsVideoInfo info;
{
@ -1082,10 +1125,19 @@ nsresult nsBuiltinDecoderStateMachine::DecodeMetadata()
mInfo = info;
if (NS_FAILED(res) || (!info.mHasVideo && !info.mHasAudio)) {
mState = DECODER_STATE_SHUTDOWN;
// 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. Note the state machine shutdown joins this decode
// thread during shutdown (and other state machines can run on the state
// machine thread while the join is waiting), so it's safe to do this
// synchronously.
nsCOMPtr<nsIRunnable> event =
NS_NewRunnableMethod(mDecoder, &nsBuiltinDecoder::DecodeError);
NS_DispatchToMainThread(event, NS_DISPATCH_NORMAL);
ReentrantMonitorAutoExit exitMon(mDecoder->GetReentrantMonitor());
NS_DispatchToMainThread(event, NS_DISPATCH_SYNC);
return NS_ERROR_FAILURE;
}
mDecoder->StartProgressUpdates();
@ -1107,7 +1159,7 @@ nsresult nsBuiltinDecoderStateMachine::DecodeMetadata()
"Active seekable media should have end time");
NS_ASSERTION(!mSeekable || GetDuration() != -1, "Seekable media should have duration");
LOG(PR_LOG_DEBUG, ("%p Media goes from %lld to %lld (duration %lld) seekable=%d",
mDecoder, mStartTime, mEndTime, GetDuration(), mSeekable));
mDecoder.get(), mStartTime, mEndTime, GetDuration(), mSeekable));
// Inform the element that we've loaded the metadata and the first frame,
// setting the default framebuffer size for audioavailable events. Also,
@ -1126,7 +1178,7 @@ nsresult nsBuiltinDecoderStateMachine::DecodeMetadata()
NS_DispatchToMainThread(metadataLoadedEvent, NS_DISPATCH_NORMAL);
if (mState == DECODER_STATE_DECODING_METADATA) {
LOG(PR_LOG_DEBUG, ("%p Changed state from DECODING_METADATA to DECODING", mDecoder));
LOG(PR_LOG_DEBUG, ("%p Changed state from DECODING_METADATA to DECODING", mDecoder.get()));
StartDecoding();
}
@ -1223,21 +1275,22 @@ void nsBuiltinDecoderStateMachine::DecodeSeek()
return;
// Try to decode another frame to detect if we're at the end...
LOG(PR_LOG_DEBUG, ("Seek completed, mCurrentFrameTime=%lld\n", mCurrentFrameTime));
LOG(PR_LOG_DEBUG, ("%p Seek completed, mCurrentFrameTime=%lld\n",
mDecoder.get(), mCurrentFrameTime));
// Change state to DECODING or COMPLETED now. SeekingStopped will
// call nsBuiltinDecoderStateMachine::Seek to reset our state to SEEKING
// if we need to seek again.
nsCOMPtr<nsIRunnable> stopEvent;
if (GetMediaTime() == mEndTime) {
LOG(PR_LOG_DEBUG, ("%p Changed state from SEEKING (to %lld) to COMPLETED",
mDecoder, seekTime));
mDecoder.get(), seekTime));
stopEvent = NS_NewRunnableMethod(mDecoder, &nsBuiltinDecoder::SeekingStoppedAtEnd);
mState = DECODER_STATE_COMPLETED;
} else {
LOG(PR_LOG_DEBUG, ("%p Changed state from SEEKING (to %lld) to DECODING",
mDecoder, seekTime));
mDecoder.get(), seekTime));
stopEvent = NS_NewRunnableMethod(mDecoder, &nsBuiltinDecoder::SeekingStopped);
StartDecoding();
}
@ -1254,13 +1307,41 @@ void nsBuiltinDecoderStateMachine::DecodeSeek()
ScheduleStateMachine();
}
nsresult nsBuiltinDecoderStateMachine::Run()
{
ReentrantMonitorAutoEnter mon(mDecoder->GetReentrantMonitor());
NS_ASSERTION(IsCurrentThread(mDecoder->mStateMachineThread),
"Should be on state machine thread.");
// Runnable to dispose of the decoder and state machine on the main thread.
class nsDecoderDisposeEvent : public nsRunnable {
public:
nsDecoderDisposeEvent(already_AddRefed<nsBuiltinDecoder> aDecoder)
: mDecoder(aDecoder) {}
NS_IMETHOD Run() {
NS_ASSERTION(NS_IsMainThread(), "Must be on main thread.");
mDecoder = nsnull;
return NS_OK;
}
private:
nsRefPtr<nsBuiltinDecoder> mDecoder;
};
// Runnable which dispatches an event to the main thread to dispose of the
// decoder and state machine. This runs on the state machine thread after
// the state machine has shutdown, and all events for that state machine have
// finished running.
class nsDispatchDisposeEvent : public nsRunnable {
public:
nsDispatchDisposeEvent(already_AddRefed<nsBuiltinDecoder> aDecoder)
: mDecoder(aDecoder) {}
NS_IMETHOD Run() {
NS_DispatchToMainThread(new nsDecoderDisposeEvent(mDecoder.forget()),
NS_DISPATCH_NORMAL);
return NS_OK;
}
private:
nsRefPtr<nsBuiltinDecoder> mDecoder;
};
nsresult nsBuiltinDecoderStateMachine::RunStateMachine()
{
mDecoder->GetReentrantMonitor().AssertCurrentThreadIn();
mTimeout = TimeStamp();
nsMediaStream* stream = mDecoder->GetCurrentStream();
NS_ENSURE_TRUE(stream, NS_ERROR_NULL_POINTER);
@ -1273,6 +1354,21 @@ nsresult nsBuiltinDecoderStateMachine::Run()
StopDecodeThread();
NS_ASSERTION(mState == DECODER_STATE_SHUTDOWN,
"How did we escape from the shutdown state???");
// We must daisy-chain these events to destroy the decoder. We must
// destroy the decoder on the main thread, but we can't destroy the
// decoder while this thread holds the decoder monitor. We can't
// dispatch an event to the main thread to destroy the decoder from
// here, as the event may run before the dispatch returns, and we
// hold the decoder monitor here. We also want to guarantee that the
// state machine is destroyed on the main thread, and so the
// event runner running this function (which holds a reference to the
// state machine) needs to finish and be released in order to allow
// that. So we dispatch an event to run after this event runner has
// finished and released its monitor/references. That event then will
// dispatch an event to the main thread to release the decoder and
// state machine.
NS_DispatchToCurrentThread(
new nsDispatchDisposeEvent(mDecoder.forget()));
return NS_OK;
}
@ -1312,7 +1408,8 @@ nsresult nsBuiltinDecoderStateMachine::Run()
!stream->IsSuspended())
{
LOG(PR_LOG_DEBUG,
("Buffering: %.3lfs/%ds, timeout in %.3lfs %s",
("%p Buffering: %.3lfs/%ds, timeout in %.3lfs %s",
mDecoder.get(),
GetUndecodedData() / static_cast<double>(USECS_PER_S),
BUFFERING_WAIT,
BUFFERING_WAIT - elapsed.ToSeconds(),
@ -1320,9 +1417,9 @@ nsresult nsBuiltinDecoderStateMachine::Run()
ScheduleStateMachine(USECS_PER_S);
return NS_OK;
} else {
LOG(PR_LOG_DEBUG, ("%p Changed state from BUFFERING to DECODING", mDecoder));
LOG(PR_LOG_DEBUG, ("%p Changed state from BUFFERING to DECODING", mDecoder.get()));
LOG(PR_LOG_DEBUG, ("%p Buffered for %.3lfs",
mDecoder,
mDecoder.get(),
(now - mBufferingStart).ToSeconds()));
StartDecoding();
}
@ -1348,6 +1445,15 @@ nsresult nsBuiltinDecoderStateMachine::Run()
case DECODER_STATE_COMPLETED: {
StopDecodeThread();
if (mState != DECODER_STATE_COMPLETED) {
// While we're waiting for the decode thread to shutdown, we can
// change state, for example to seeking or shutdown state.
// Whatever changed our state should have scheduled another state
// machine run.
NS_ASSERTION(IsStateMachineScheduled(), "Must have timer scheduled");
return NS_OK;
}
nsresult res = StartAudioThread();
if (NS_FAILED(res)) return res;
@ -1359,9 +1465,9 @@ nsresult nsBuiltinDecoderStateMachine::Run()
(HasAudio() && !mAudioCompleted)))
{
AdvanceFrame();
NS_ASSERTION(mDecoder->GetState() == nsBuiltinDecoder::PLAY_STATE_PAUSED ||
IsStateMachineScheduled(),
"Must have timer scheduled");
NS_ASSERTION(mDecoder->GetState() != nsBuiltinDecoder::PLAY_STATE_PLAYING ||
IsStateMachineScheduled(),
"Must have timer scheduled");
return NS_OK;
}
@ -1370,8 +1476,8 @@ nsresult nsBuiltinDecoderStateMachine::Run()
StopPlayback();
if (mState != DECODER_STATE_COMPLETED) {
// We've changed state. Whatever changed our state should have
// scheduled another state machine run.
// While we're presenting a frame we can change state. Whatever changed
// our state should have scheduled another state machine run.
NS_ASSERTION(IsStateMachineScheduled(), "Must have timer scheduled");
return NS_OK;
}
@ -1412,7 +1518,7 @@ void nsBuiltinDecoderStateMachine::RenderVideoFrame(VideoData* aData,
PRInt64
nsBuiltinDecoderStateMachine::GetAudioClock()
{
NS_ASSERTION(IsCurrentThread(mDecoder->mStateMachineThread), "Should be on state machine thread.");
NS_ASSERTION(OnStateMachineThread(), "Should be on state machine thread.");
mDecoder->GetReentrantMonitor().AssertCurrentThreadIn();
if (!HasAudio())
return -1;
@ -1425,7 +1531,7 @@ nsBuiltinDecoderStateMachine::GetAudioClock()
void nsBuiltinDecoderStateMachine::AdvanceFrame()
{
NS_ASSERTION(IsCurrentThread(mDecoder->mStateMachineThread), "Should be on state machine thread.");
NS_ASSERTION(OnStateMachineThread(), "Should be on state machine thread.");
mDecoder->GetReentrantMonitor().AssertCurrentThreadIn();
if (mDecoder->GetState() != nsBuiltinDecoder::PLAY_STATE_PLAYING) {
@ -1605,7 +1711,7 @@ VideoData* nsBuiltinDecoderStateMachine::FindStartTime()
// first acutal audio sample we have, we'll inject silence during playback
// to ensure the audio starts at the correct time.
mAudioStartTime = mStartTime;
LOG(PR_LOG_DEBUG, ("%p Media start time is %lld", mDecoder, mStartTime));
LOG(PR_LOG_DEBUG, ("%p Media start time is %lld", mDecoder.get(), mStartTime));
return v;
}
@ -1662,10 +1768,10 @@ void nsBuiltinDecoderStateMachine::StartBuffering()
UpdateReadyState();
mState = DECODER_STATE_BUFFERING;
LOG(PR_LOG_DEBUG, ("%p Changed state from DECODING to BUFFERING, decoded for %.3lfs",
mDecoder, decodeDuration.ToSeconds()));
mDecoder.get(), decodeDuration.ToSeconds()));
nsMediaDecoder::Statistics stats = mDecoder->GetStatistics();
LOG(PR_LOG_DEBUG, ("%p Playback rate: %.1lfKB/s%s download rate: %.1lfKB/s%s",
mDecoder,
mDecoder.get(),
stats.mPlaybackRate/1024, stats.mPlaybackRateReliable ? "" : " (unreliable)",
stats.mDownloadRate/1024, stats.mDownloadRateReliable ? "" : " (unreliable)"));
}
@ -1679,11 +1785,61 @@ nsresult nsBuiltinDecoderStateMachine::GetBuffered(nsTimeRanges* aBuffered) {
return res;
}
static void RunStateMachine(nsITimer *aTimer, void *aClosure) {
nsresult nsBuiltinDecoderStateMachine::Run()
{
ReentrantMonitorAutoEnter mon(mDecoder->GetReentrantMonitor());
NS_ASSERTION(OnStateMachineThread(), "Should be on state machine thread.");
return CallRunStateMachine();
}
nsresult nsBuiltinDecoderStateMachine::CallRunStateMachine()
{
mDecoder->GetReentrantMonitor().AssertCurrentThreadIn();
NS_ASSERTION(OnStateMachineThread(), "Should be on state machine thread.");
// This will be set to PR_TRUE by ScheduleStateMachine() if it's called
// while we're in RunStateMachine().
mRunAgain = PR_FALSE;
// Set to PR_TRUE whenever we dispatch an event to run this state machine.
// This flag prevents us from dispatching
mDispatchedRunEvent = PR_FALSE;
mTimeout = TimeStamp();
mIsRunning = PR_TRUE;
nsresult res = RunStateMachine();
mIsRunning = PR_FALSE;
if (mRunAgain && !mDispatchedRunEvent) {
mDispatchedRunEvent = PR_TRUE;
return NS_DispatchToCurrentThread(this);
}
return res;
}
static void TimeoutExpired(nsITimer *aTimer, void *aClosure) {
nsBuiltinDecoderStateMachine *machine =
static_cast<nsBuiltinDecoderStateMachine*>(aClosure);
NS_ASSERTION(machine, "Must have been passed state machine");
machine->Run();
machine->TimeoutExpired();
}
void nsBuiltinDecoderStateMachine::TimeoutExpired()
{
ReentrantMonitorAutoEnter mon(mDecoder->GetReentrantMonitor());
NS_ASSERTION(OnStateMachineThread(), "Must be on state machine thread");
if (mIsRunning) {
mRunAgain = PR_TRUE;
} else if (!mDispatchedRunEvent) {
// We don't have an event dispatched to run the state machine, so we
// can just run it from here.
CallRunStateMachine();
}
// Otherwise, an event has already been dispatched to run the state machine
// as soon as possible. Nothing else needed to do, the state machine is
// going to run anyway.
}
nsresult nsBuiltinDecoderStateMachine::ScheduleStateMachine() {
@ -1692,6 +1848,8 @@ nsresult nsBuiltinDecoderStateMachine::ScheduleStateMachine() {
nsresult nsBuiltinDecoderStateMachine::ScheduleStateMachine(PRInt64 aUsecs) {
mDecoder->GetReentrantMonitor().AssertCurrentThreadIn();
NS_ABORT_IF_FALSE(gStateMachineThread,
"Must have a state machine thread to schedule");
if (mState == DECODER_STATE_SHUTDOWN) {
return NS_ERROR_FAILURE;
@ -1704,34 +1862,43 @@ nsresult nsBuiltinDecoderStateMachine::ScheduleStateMachine(PRInt64 aUsecs) {
// We've already scheduled a timer set to expire at or before this time,
// or have an event dispatched to run the state machine.
return NS_OK;
} else if (timeout < mTimeout && mTimer) {
}
if (mTimer) {
// We've been asked to schedule a timer to run before an existing timer.
// Cancel the existing timer.
mTimer->Cancel();
}
}
// Ensure the state machine thread is alive; we'll be running on it!
nsresult res = mDecoder->CreateStateMachineThread();
if (NS_FAILED(res)) return res;
PRUint32 ms = static_cast<PRUint32>((aUsecs / USECS_PER_MS) & 0xFFFFFFFF);
if (ms == 0) {
if (mIsRunning) {
// We're currently running this state machine on the state machine
// thread. Signal it to run again once it finishes its current cycle.
mRunAgain = PR_TRUE;
return NS_OK;
} else if (!mDispatchedRunEvent) {
// We're not currently running this state machine on the state machine
// thread. Dispatch an event to run one cycle of the state machine.
mDispatchedRunEvent = PR_TRUE;
return gStateMachineThread->Dispatch(this, NS_DISPATCH_NORMAL);
}
// We're not currently running this state machine on the state machine
// thread, but something has already dispatched an event to run it again,
// so just exit; it's going to run real soon.
return NS_OK;
}
mTimeout = timeout;
PRUint32 ms =
static_cast<PRUint32>((aUsecs / USECS_PER_MS) & 0xFFFFFFFF);
if (ms == 0) {
// We've been asked to schedule a timer to run ASAP, so just dispatch an
// event rather than using a timer.
return mDecoder->mStateMachineThread->Dispatch(this, NS_DISPATCH_NORMAL);
}
nsresult res;
if (!mTimer) {
mTimer = do_CreateInstance("@mozilla.org/timer;1", &res);
if (NS_FAILED(res)) return res;
mTimer->SetTarget(mDecoder->mStateMachineThread);
mTimer->SetTarget(gStateMachineThread);
}
res = mTimer->InitWithFuncCallback(RunStateMachine,
res = mTimer->InitWithFuncCallback(::TimeoutExpired,
this,
ms,
nsITimer::TYPE_ONE_SHOT);

View File

@ -173,8 +173,7 @@ public:
virtual void UpdatePlaybackPosition(PRInt64 aTime);
virtual void StartBuffering();
// State machine thread run function. Polls the state, sends frames to be
// displayed at appropriate times, and generally manages the decode.
// State machine thread run function. Defers to RunStateMachine().
NS_IMETHOD Run();
// This is called on the state machine thread and audio thread.
@ -215,13 +214,19 @@ public:
}
PRBool OnStateMachineThread() const {
return mDecoder->OnStateMachineThread();
return IsCurrentThread(GetStateMachineThread());
}
// The decoder object that created this state machine. The decoder
// always outlives us since it controls our lifetime. This is accessed
// read only on the AV, state machine, audio and main thread.
nsBuiltinDecoder* mDecoder;
// The decoder object that created this state machine. The state machine
// holds a strong reference to the decoder to ensure that the decoder stays
// alive once media element has started the decoder shutdown process, and has
// dropped its reference to the decoder. This enables the state machine to
// keep using the decoder's monitor until the state machine has finished
// shutting down, without fear of the monitor being destroyed. After
// shutting down, the state machine will then release this reference,
// causing the decoder to be destroyed. This is accessed on the decode,
// state machine, audio and main threads.
nsRefPtr<nsBuiltinDecoder> mDecoder;
// The decoder monitor must be obtained before modifying this state.
// NotifyAll on the monitor must be called when the state is changed by
@ -250,14 +255,23 @@ public:
// Accessed on the main and state machine threads.
virtual void SetFrameBufferLength(PRUint32 aLength);
// Schedules the state machine thread to run the state machine.
// Returns the shared state machine thread.
static nsIThread* GetStateMachineThread();
// Schedules the shared state machine thread to run the state machine.
// If the state machine thread is the currently running the state machine,
// we wait until that has completely finished before running the state
// machine again.
nsresult ScheduleStateMachine();
// Schedules the state machine thread to run the state machine
// Schedules the shared state machine thread to run the state machine
// in aUsecs microseconds from now, if it's not already scheduled to run
// earlier, in which case the request is discarded.
nsresult ScheduleStateMachine(PRInt64 aUsecs);
// Timer function to implement ScheduleStateMachine(aUsecs).
void TimeoutExpired();
protected:
// Returns PR_TRUE if we've got less than aAudioUsecs microseconds of decoded
@ -418,8 +432,17 @@ protected:
// to call.
void DecodeThreadRun();
// State machine thread run function. Defers to RunStateMachine().
nsresult CallRunStateMachine();
// Performs one "cycle" of the state machine. Polls the state, and may send
// a video frame to be displayed, and generally manages the decode. Called
// periodically via timer to ensure the video stays in sync.
nsresult RunStateMachine();
PRBool IsStateMachineScheduled() const {
return !mTimeout.IsNull();
mDecoder->GetReentrantMonitor().AssertCurrentThreadIn();
return !mTimeout.IsNull() || mRunAgain;
}
// The size of the decoded YCbCr frame.
@ -561,6 +584,21 @@ protected:
// Synchronised via decoder monitor.
PRPackedBool mQuickBuffering;
// PR_TRUE if the shared state machine thread is currently running this
// state machine.
PRPackedBool mIsRunning;
// PR_TRUE if we should run the state machine again once the current
// state machine run has finished.
PRPackedBool mRunAgain;
// PR_TRUE if we've dispatched an event to run the state machine. It's
// imperative that we don't dispatch multiple events to run the state
// machine at the same time, as our code assume all events are synchronous.
// If we dispatch multiple events, the second event can run while the
// first is shutting down a thread, causing inconsistent state.
PRPackedBool mDispatchedRunEvent;
private:
// Manager for queuing and dispatching MozAudioAvailable events. The
// event manager is accessed from the state machine and audio threads,

View File

@ -65,20 +65,6 @@ class nsTimeRanges;
#define FRAMEBUFFER_LENGTH_MIN 512
#define FRAMEBUFFER_LENGTH_MAX 16384
// Shuts down a thread asynchronously.
class ShutdownThreadEvent : public nsRunnable
{
public:
ShutdownThreadEvent(nsIThread* aThread) : mThread(aThread) {}
~ShutdownThreadEvent() {}
NS_IMETHOD Run() {
mThread->Shutdown();
return NS_OK;
}
private:
nsCOMPtr<nsIThread> mThread;
};
// All methods of nsMediaDecoder must be called from the main thread only
// with the exception of GetImageContainer, SetVideoData and GetStatistics,
// which can be called from any thread.