Bug 968016 - Use SharedThreadPool instead of manually managed threads for the media decoding. r=kinetik

This commit is contained in:
Chris Pearce 2014-02-18 11:53:52 +13:00
parent 490247e674
commit b162ede7bd
4 changed files with 99 additions and 282 deletions

View File

@ -1482,7 +1482,7 @@ void MediaDecoder::UpdatePlaybackOffset(int64_t aOffset)
bool MediaDecoder::OnStateMachineThread() const
{
return IsCurrentThread(MediaDecoderStateMachine::GetStateMachineThread());
return mDecoderStateMachine->OnStateMachineThread();
}
void MediaDecoder::NotifyAudioAvailableListener()

View File

@ -28,7 +28,8 @@
#include "nsITimer.h"
#include "nsContentUtils.h"
#include "MediaShutdownManager.h"
#include "SharedThreadPool.h"
#include "MediaTaskQueue.h"
#include "prenv.h"
#include "mozilla/Preferences.h"
#include "gfx2DGlue.h"
@ -146,17 +147,12 @@ static int64_t DurationToUsecs(TimeDuration aDuration) {
return static_cast<int64_t>(aDuration.ToSeconds() * USECS_PER_S);
}
// Owns the global state machine thread and counts of
// state machine and decoder threads. There should
// only be one instance of this class.
class StateMachineTracker
{
private:
StateMachineTracker() :
mMonitor("media.statemachinetracker"),
mStateMachineCount(0),
mDecodeThreadCount(0),
mStateMachineThread(nullptr)
StateMachineTracker()
: mMonitor("media.statemachinetracker")
, mStateMachineCount(0)
{
MOZ_COUNT_CTOR(StateMachineTracker);
NS_ASSERTION(NS_IsMainThread(), "Should be on main thread.");
@ -165,7 +161,6 @@ private:
~StateMachineTracker()
{
NS_ASSERTION(NS_IsMainThread(), "Should be on main thread.");
MOZ_COUNT_DTOR(StateMachineTracker);
}
@ -193,37 +188,6 @@ public:
return mStateMachineThread->GetThread();
}
// Requests that a decode thread be created for aStateMachine. The thread
// may be created immediately, or after some delay, once a thread becomes
// available. The request can be cancelled using CancelCreateDecodeThread().
// It's the callers responsibility to not call this more than once for any
// given state machine.
nsresult RequestCreateDecodeThread(MediaDecoderStateMachine* aStateMachine);
// Cancels a request made by RequestCreateDecodeThread to create a decode
// thread for aStateMachine.
nsresult CancelCreateDecodeThread(MediaDecoderStateMachine* aStateMachine);
// Maximum number of active decode threads allowed. When more
// than this number are active the thread creation will fail.
static const uint32_t MAX_DECODE_THREADS = 25;
// Returns the number of active decode threads.
// Call on any thread. Holds the internal monitor so don't
// call with any other monitor held to avoid deadlock.
uint32_t GetDecodeThreadCount();
// Keep track of the fact that a decode thread was destroyed.
// Call on any thread. Holds the internal monitor so don't
// call with any other monitor held to avoid deadlock.
void NoteDecodeThreadDestroyed();
#ifdef DEBUG
// Returns true if aStateMachine has a pending request for a
// decode thread.
bool IsQueued(MediaDecoderStateMachine* aStateMachine);
#endif
private:
// Holds global instance of StateMachineTracker.
// Writable on main thread only.
@ -238,19 +202,10 @@ private:
// main thread only.
uint32_t mStateMachineCount;
// Number of instances of decoder threads that are
// currently instantiated. Access only with the
// mMonitor lock held. Can be used from any thread.
uint32_t mDecodeThreadCount;
// Global state machine thread. Write on the main thread
// only, read from the decoder threads. Synchronized via
// the mMonitor.
nsRefPtr<StateMachineThread> mStateMachineThread;
// Queue of state machines waiting for decode threads. Entries at the front
// get their threads first.
nsDeque mPending;
};
StateMachineTracker* StateMachineTracker::sInstance = nullptr;
@ -277,22 +232,6 @@ void StateMachineTracker::EnsureGlobalStateMachine()
mStateMachineCount++;
}
#ifdef DEBUG
bool StateMachineTracker::IsQueued(MediaDecoderStateMachine* aStateMachine)
{
ReentrantMonitorAutoEnter mon(mMonitor);
int32_t size = mPending.GetSize();
for (int i = 0; i < size; ++i) {
MediaDecoderStateMachine* m =
static_cast<MediaDecoderStateMachine*>(mPending.ObjectAt(i));
if (m == aStateMachine) {
return true;
}
}
return false;
}
#endif
void StateMachineTracker::CleanupGlobalStateMachine()
{
NS_ASSERTION(NS_IsMainThread(), "Should be on main thread.");
@ -301,81 +240,16 @@ void StateMachineTracker::CleanupGlobalStateMachine()
mStateMachineCount--;
if (mStateMachineCount == 0) {
DECODER_LOG(PR_LOG_DEBUG, ("Destroying media state machine thread"));
NS_ASSERTION(mPending.GetSize() == 0, "Shouldn't all requests be handled by now?");
{
ReentrantMonitorAutoEnter mon(mMonitor);
mStateMachineThread->Shutdown();
mStateMachineThread = nullptr;
NS_ASSERTION(mDecodeThreadCount == 0, "Decode thread count must be zero.");
sInstance = nullptr;
}
delete this;
}
}
void StateMachineTracker::NoteDecodeThreadDestroyed()
{
ReentrantMonitorAutoEnter mon(mMonitor);
--mDecodeThreadCount;
while (mDecodeThreadCount < MAX_DECODE_THREADS && mPending.GetSize() > 0) {
MediaDecoderStateMachine* m =
static_cast<MediaDecoderStateMachine*>(mPending.PopFront());
nsresult rv;
{
ReentrantMonitorAutoExit exitMon(mMonitor);
rv = m->StartDecodeThread();
}
if (NS_SUCCEEDED(rv)) {
++mDecodeThreadCount;
}
}
}
uint32_t StateMachineTracker::GetDecodeThreadCount()
{
ReentrantMonitorAutoEnter mon(mMonitor);
return mDecodeThreadCount;
}
nsresult StateMachineTracker::CancelCreateDecodeThread(MediaDecoderStateMachine* aStateMachine) {
ReentrantMonitorAutoEnter mon(mMonitor);
int32_t size = mPending.GetSize();
for (int32_t i = 0; i < size; ++i) {
void* m = static_cast<MediaDecoderStateMachine*>(mPending.ObjectAt(i));
if (m == aStateMachine) {
mPending.RemoveObjectAt(i);
break;
}
}
NS_ASSERTION(!IsQueued(aStateMachine), "State machine should no longer have queued request.");
return NS_OK;
}
nsresult StateMachineTracker::RequestCreateDecodeThread(MediaDecoderStateMachine* aStateMachine)
{
NS_ENSURE_STATE(aStateMachine);
ReentrantMonitorAutoEnter mon(mMonitor);
if (mPending.GetSize() > 0 || mDecodeThreadCount + 1 >= MAX_DECODE_THREADS) {
// If there's already state machines in the queue, or we've exceeded the
// limit, append the state machine to the queue of state machines waiting
// for a decode thread. This ensures state machines already waiting get
// their threads first.
mPending.Push(aStateMachine);
return NS_OK;
}
nsresult rv;
{
ReentrantMonitorAutoExit exitMon(mMonitor);
rv = aStateMachine->StartDecodeThread();
}
if (NS_SUCCEEDED(rv)) {
++mDecodeThreadCount;
}
NS_ASSERTION(mDecodeThreadCount <= MAX_DECODE_THREADS,
"Should keep to thread limit!");
return NS_OK;
}
MediaDecoderStateMachine::MediaDecoderStateMachine(MediaDecoder* aDecoder,
MediaDecoderReader* aReader,
bool aRealTime) :
@ -405,7 +279,7 @@ MediaDecoderStateMachine::MediaDecoderStateMachine(MediaDecoder* aDecoder,
mAudioCompleted(false),
mGotDurationFromMetaData(false),
mStopDecodeThread(true),
mDecodeThreadIdle(false),
mDispatchedEventToDecode(false),
mStopAudioThread(true),
mQuickBuffering(false),
mIsRunning(false),
@ -415,7 +289,6 @@ MediaDecoderStateMachine::MediaDecoderStateMachine(MediaDecoder* aDecoder,
mRealTime(aRealTime),
mDidThrottleAudioDecoding(false),
mDidThrottleVideoDecoding(false),
mRequestedNewDecodeThread(false),
mEventManager(aDecoder),
mLastFrameStatus(MediaDecoderOwner::NEXT_FRAME_UNINITIALIZED)
{
@ -463,12 +336,15 @@ MediaDecoderStateMachine::~MediaDecoderStateMachine()
MOZ_COUNT_DTOR(MediaDecoderStateMachine);
NS_ASSERTION(!mPendingWakeDecoder.get(),
"WakeDecoder should have been revoked already");
NS_ASSERTION(!StateMachineTracker::Instance().IsQueued(this),
"Should not have a pending request for a new decode thread");
NS_ASSERTION(!mRequestedNewDecodeThread,
"Should not have (or flagged) a pending request for a new decode thread");
if (mTimer)
if (mDecodeTaskQueue) {
mDecodeTaskQueue->Shutdown();
mDecodeTaskQueue = nullptr;
}
if (mTimer) {
mTimer->Cancel();
}
mTimer = nullptr;
mReader = nullptr;
@ -508,51 +384,45 @@ int64_t MediaDecoderStateMachine::GetDecodedAudioDuration() {
void MediaDecoderStateMachine::DecodeThreadRun()
{
NS_ASSERTION(OnDecodeThread(), "Should be on decode thread.");
mReader->OnDecodeThreadStart();
ReentrantMonitorAutoEnter mon(mDecoder->GetReentrantMonitor());
{
ReentrantMonitorAutoEnter mon(mDecoder->GetReentrantMonitor());
if (mState == DECODER_STATE_DECODING_METADATA &&
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 decode thread"));
}
while (mState != DECODER_STATE_SHUTDOWN &&
mState != DECODER_STATE_COMPLETED &&
mState != DECODER_STATE_DORMANT &&
!mStopDecodeThread)
{
if (mState == DECODER_STATE_DECODING || mState == DECODER_STATE_BUFFERING) {
DecodeLoop();
} else if (mState == DECODER_STATE_SEEKING) {
DecodeSeek();
} else if (mState == DECODER_STATE_DECODING_METADATA) {
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 decode thread"));
}
} else if (mState == DECODER_STATE_WAIT_FOR_RESOURCES) {
mDecoder->GetReentrantMonitor().Wait();
if (!mReader->IsWaitingMediaResources()) {
// change state to DECODER_STATE_WAIT_FOR_RESOURCES
StartDecodeMetadata();
}
} else if (mState == DECODER_STATE_DORMANT) {
mDecoder->GetReentrantMonitor().Wait();
}
}
mDecodeThreadIdle = true;
DECODER_LOG(PR_LOG_DEBUG, ("%p Decode thread finished", mDecoder.get()));
if (mState == DECODER_STATE_DECODING_METADATA &&
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 decode thread"));
}
mReader->OnDecodeThreadFinish();
while (mState != DECODER_STATE_SHUTDOWN &&
mState != DECODER_STATE_COMPLETED &&
mState != DECODER_STATE_DORMANT &&
!mStopDecodeThread)
{
if (mState == DECODER_STATE_DECODING || mState == DECODER_STATE_BUFFERING) {
DecodeLoop();
} else if (mState == DECODER_STATE_SEEKING) {
DecodeSeek();
} else if (mState == DECODER_STATE_DECODING_METADATA) {
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 decode thread"));
}
} else if (mState == DECODER_STATE_WAIT_FOR_RESOURCES) {
mDecoder->GetReentrantMonitor().Wait();
if (!mReader->IsWaitingMediaResources()) {
// change state to DECODER_STATE_WAIT_FOR_RESOURCES
StartDecodeMetadata();
}
} else if (mState == DECODER_STATE_DORMANT) {
mDecoder->GetReentrantMonitor().Wait();
}
}
DECODER_LOG(PR_LOG_DEBUG, ("%p Decode thread finished", mDecoder.get()));
mDispatchedEventToDecode = false;
mon.NotifyAll();
}
void MediaDecoderStateMachine::SendStreamAudio(AudioData* aAudio,
@ -1312,10 +1182,21 @@ uint32_t MediaDecoderStateMachine::PlayFromAudioQueue(uint64_t aFrameOffset,
nsresult MediaDecoderStateMachine::Init(MediaDecoderStateMachine* aCloneDonor)
{
MOZ_ASSERT(NS_IsMainThread());
RefPtr<SharedThreadPool> decodePool(
SharedThreadPool::Get(NS_LITERAL_CSTRING("Media Decode"),
Preferences::GetUint("media.num-decode-threads", 25)));
NS_ENSURE_TRUE(decodePool, NS_ERROR_FAILURE);
mDecodeTaskQueue = new MediaTaskQueue(decodePool.forget());
NS_ENSURE_TRUE(mDecodeTaskQueue, NS_ERROR_FAILURE);
MediaDecoderReader* cloneReader = nullptr;
if (aCloneDonor) {
cloneReader = static_cast<MediaDecoderStateMachine*>(aCloneDonor)->mReader;
}
return mReader->Init(cloneReader);
}
@ -1706,30 +1587,8 @@ void MediaDecoderStateMachine::StopDecodeThread()
{
NS_ASSERTION(OnStateMachineThread(), "Should be on state machine thread.");
AssertCurrentThreadInMonitor();
if (mRequestedNewDecodeThread) {
// We've requested that the decode be created, but it hasn't been yet.
// Cancel that request.
NS_ASSERTION(!mDecodeThread,
"Shouldn't have a decode thread until after request processed");
StateMachineTracker::Instance().CancelCreateDecodeThread(this);
mRequestedNewDecodeThread = false;
}
mStopDecodeThread = true;
mDecoder->GetReentrantMonitor().NotifyAll();
if (mDecodeThread) {
DECODER_LOG(PR_LOG_DEBUG, ("%p Shutdown decode thread", mDecoder.get()));
{
ReentrantMonitorAutoExit exitMon(mDecoder->GetReentrantMonitor());
mDecodeThread->Shutdown();
StateMachineTracker::Instance().NoteDecodeThreadDestroyed();
}
mDecodeThread = nullptr;
mDecodeThreadIdle = false;
}
NS_ASSERTION(!mRequestedNewDecodeThread,
"Any pending requests for decode threads must be canceled and unflagged");
NS_ASSERTION(!StateMachineTracker::Instance().IsQueued(this),
"Any pending requests for decode threads must be canceled");
}
void MediaDecoderStateMachine::StopAudioThread()
@ -1768,63 +1627,15 @@ MediaDecoderStateMachine::ScheduleDecodeThread()
if (mState >= DECODER_STATE_COMPLETED) {
return NS_OK;
}
if (mDecodeThread) {
NS_ASSERTION(!mRequestedNewDecodeThread,
"Shouldn't have requested new decode thread when we have a decode thread");
// We already have a decode thread...
if (mDecodeThreadIdle) {
// ... and it's not been shutdown yet, wake it up.
nsCOMPtr<nsIRunnable> event =
NS_NewRunnableMethod(this, &MediaDecoderStateMachine::DecodeThreadRun);
mDecodeThread->Dispatch(event, NS_DISPATCH_NORMAL);
mDecodeThreadIdle = false;
}
return NS_OK;
} else if (!mRequestedNewDecodeThread) {
// We don't already have a decode thread, request a new one.
mRequestedNewDecodeThread = true;
ReentrantMonitorAutoExit mon(mDecoder->GetReentrantMonitor());
StateMachineTracker::Instance().RequestCreateDecodeThread(this);
if (!mDispatchedEventToDecode) {
nsresult rv = mDecodeTaskQueue->Dispatch(
NS_NewRunnableMethod(this, &MediaDecoderStateMachine::DecodeThreadRun));
NS_ENSURE_SUCCESS(rv, rv);
mDispatchedEventToDecode = true;
}
return NS_OK;
}
nsresult
MediaDecoderStateMachine::StartDecodeThread()
{
NS_ASSERTION(StateMachineTracker::Instance().GetDecodeThreadCount() <
StateMachineTracker::MAX_DECODE_THREADS,
"Should not have reached decode thread limit");
ReentrantMonitorAutoEnter mon(mDecoder->GetReentrantMonitor());
NS_ASSERTION(!StateMachineTracker::Instance().IsQueued(this),
"Should not already have a pending request for a new decode thread.");
NS_ASSERTION(OnStateMachineThread(), "Should be on state machine thread.");
NS_ASSERTION(!mDecodeThread, "Should not have decode thread yet");
NS_ASSERTION(mRequestedNewDecodeThread, "Should have requested this...");
mRequestedNewDecodeThread = false;
nsresult rv = NS_NewNamedThread("Media Decode",
getter_AddRefs(mDecodeThread),
nullptr,
MEDIA_THREAD_STACK_SIZE);
if (NS_FAILED(rv)) {
// Give up, report error to media element.
nsCOMPtr<nsIRunnable> event =
NS_NewRunnableMethod(mDecoder, &MediaDecoder::DecodeError);
NS_DispatchToMainThread(event, NS_DISPATCH_NORMAL);
return rv;
}
nsCOMPtr<nsIRunnable> event =
NS_NewRunnableMethod(this, &MediaDecoderStateMachine::DecodeThreadRun);
mDecodeThread->Dispatch(event, NS_DISPATCH_NORMAL);
mDecodeThreadIdle = false;
return NS_OK;
}
nsresult
MediaDecoderStateMachine::StartAudioThread()
{
@ -2206,13 +2017,17 @@ nsresult MediaDecoderStateMachine::RunStateMachine()
return NS_OK;
}
StopDecodeThread();
{
ReentrantMonitorAutoExit exitMon(mDecoder->GetReentrantMonitor());
// Wait for the thread decoding to exit.
mDecodeTaskQueue->Shutdown();
mReader->ReleaseMediaResources();
}
// Now that those threads are stopped, there's no possibility of
// mPendingWakeDecoder being needed again. Revoke it.
mPendingWakeDecoder = nullptr;
{
ReentrantMonitorAutoExit exitMon(mDecoder->GetReentrantMonitor());
mReader->ReleaseMediaResources();
}
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
@ -2903,9 +2718,14 @@ nsresult MediaDecoderStateMachine::ScheduleStateMachine(int64_t aUsecs) {
return res;
}
bool MediaDecoderStateMachine::OnDecodeThread() const
{
return mDecodeTaskQueue->IsCurrentThreadIn();
}
bool MediaDecoderStateMachine::OnStateMachineThread() const
{
return IsCurrentThread(GetStateMachineThread());
return IsCurrentThread(GetStateMachineThread());
}
nsIThread* MediaDecoderStateMachine::GetStateMachineThread()

View File

@ -91,6 +91,8 @@ namespace mozilla {
class AudioSegment;
class VideoSegment;
class MediaTaskQueue;
class SharedThreadPool;
// GetCurrentTime is defined in winbase.h as zero argument macro forwarding to
// GetTickCount() and conflicts with MediaDecoderStateMachine::GetCurrentTime
@ -176,9 +178,7 @@ public:
// Functions used by assertions to ensure we're calling things
// on the appropriate threads.
bool OnDecodeThread() const {
return IsCurrentThread(mDecodeThread);
}
bool OnDecodeThread() const;
bool OnStateMachineThread() const;
bool OnAudioThread() const {
return IsCurrentThread(mAudioThread);
@ -314,12 +314,6 @@ public:
// earlier, in which case the request is discarded.
nsresult ScheduleStateMachine(int64_t aUsecs = 0);
// Creates and starts a new decode thread. Don't call this directly,
// request a new decode thread by calling
// StateMachineTracker::RequestCreateDecodeThread().
// The decoder monitor must not be held. Called on the state machine thread.
nsresult StartDecodeThread();
// Timer function to implement ScheduleStateMachine(aUsecs).
void TimeoutExpired();
@ -617,8 +611,10 @@ private:
// The "audio push thread".
nsCOMPtr<nsIThread> mAudioThread;
// Thread for decoding video in background. The "decode thread".
nsCOMPtr<nsIThread> mDecodeThread;
// The task queue in which we run decode tasks. This is referred to as
// the "decode thread", though in practise tasks can run on a different
// thread every time they're called.
RefPtr<MediaTaskQueue> mDecodeTaskQueue;
// Timer to call the state machine Run() method. Used by
// ScheduleStateMachine(). Access protected by decoder monitor.
@ -780,12 +776,10 @@ private:
// and decode threads. Syncrhonised by decoder monitor.
bool mStopDecodeThread;
// True when the decode thread run function has finished, but the thread
// has not necessarily been shut down yet. This can happen if we switch
// from COMPLETED state to SEEKING before the state machine has a chance
// to run in the COMPLETED state and shutdown the decode thread.
// Synchronised by the decoder monitor.
bool mDecodeThreadIdle;
// True if we've dispatched an event to the decode task queue to call
// DecodeThreadRun(). We use this flag to prevent us from dispatching
// unneccessary runnables, since the decode thread runs in a loop.
bool mDispatchedEventToDecode;
// False while audio thread should be running. Accessed state machine
// and audio threads. Syncrhonised by decoder monitor.
@ -827,10 +821,6 @@ private:
bool mDidThrottleAudioDecoding;
bool mDidThrottleVideoDecoding;
// True if we've requested a new decode thread, but it has not yet been
// created. Synchronized by the decoder monitor.
bool mRequestedNewDecodeThread;
// 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.

View File

@ -126,6 +126,13 @@ MediaTaskQueue::Runner::Run()
// in this task queue.
event->Run();
// Drop the reference to event. The event will hold a reference to the
// object it's calling, and we don't want to keep it alive, it may be
// making assumptions what holds references to it. This is especially
// the case if the object is waiting for us to shutdown, so that it
// can shutdown (like in the MediaDecoderStateMachine's SHUTDOWN case).
event = nullptr;
{
MonitorAutoLock mon(mQueue->mQueueMonitor);
if (mQueue->mTasks.size() == 0) {