Bug 592833 - Move audio stream management to audio thread. r=roc

This commit is contained in:
Chris Pearce 2011-07-06 10:05:08 +12:00
parent d343bdfd2e
commit bfd7ad4423
2 changed files with 110 additions and 164 deletions

View File

@ -175,7 +175,6 @@ nsBuiltinDecoderStateMachine::nsBuiltinDecoderStateMachine(nsBuiltinDecoder* aDe
nsBuiltinDecoderReader* aReader) :
mDecoder(aDecoder),
mState(DECODER_STATE_DECODING_METADATA),
mAudioReentrantMonitor("media.audiostream"),
mCbCrSize(0),
mPlayDuration(0),
mStartTime(-1),
@ -443,6 +442,15 @@ void nsBuiltinDecoderStateMachine::AudioLoop()
channels = mInfo.mAudioChannels;
rate = mInfo.mAudioRate;
NS_ASSERTION(audioStartTime != -1, "Should have audio start time by now");
// We must hold the monitor while creating or destroying the audio stream,
// or whenever we use it off the audio thread.
mAudioStream = nsAudioStream::AllocateStream();
mAudioStream->Init(channels,
rate,
MOZ_SOUND_DATA_FORMAT);
volume = mVolume;
mAudioStream->SetVolume(volume);
}
while (1) {
@ -459,6 +467,9 @@ void nsBuiltinDecoderStateMachine::AudioLoop()
(mReader->mAudioQueue.GetSize() == 0 &&
!mReader->mAudioQueue.AtEndOfStream())))
{
if (!IsPlaying() && !mAudioStream->IsPaused()) {
mAudioStream->Pause();
}
samplesAtLastSleep = audioDuration;
mon.Wait();
}
@ -471,24 +482,21 @@ void nsBuiltinDecoderStateMachine::AudioLoop()
break;
}
// We only want to go to the expense of taking the audio monitor and
// changing the volume if it's the first time we've entered the loop
// (as we must sync the volume in case it's changed since the
// nsAudioStream was created) or if the volume has changed.
// We only want to go to the expense of changing the volume if
// the volume has changed.
setVolume = volume != mVolume;
volume = mVolume;
if (IsPlaying() && mAudioStream->IsPaused()) {
mAudioStream->Resume();
}
}
if (setVolume || minWriteSamples == -1) {
ReentrantMonitorAutoEnter audioMon(mAudioReentrantMonitor);
if (mAudioStream) {
if (setVolume) {
mAudioStream->SetVolume(volume);
}
if (minWriteSamples == -1) {
minWriteSamples = mAudioStream->GetMinWriteSamples();
}
}
if (setVolume) {
mAudioStream->SetVolume(volume);
}
if (minWriteSamples == -1) {
minWriteSamples = mAudioStream->GetMinWriteSamples();
}
NS_ASSERTION(mReader->mAudioQueue.GetSize() > 0,
"Should have data to play");
@ -570,41 +578,38 @@ void nsBuiltinDecoderStateMachine::AudioLoop()
{
// Last sample pushed to audio hardware, wait for the audio to finish,
// before the audio thread terminates.
ReentrantMonitorAutoEnter audioMon(mAudioReentrantMonitor);
if (mAudioStream) {
PRBool seeking = PR_FALSE;
PRBool seeking = PR_FALSE;
{
ReentrantMonitorAutoEnter mon(mDecoder->GetReentrantMonitor());
PRInt64 oldPosition = -1;
PRInt64 position = GetMediaTime();
while (oldPosition != position &&
mAudioEndTime - position > 0 &&
mState != DECODER_STATE_SEEKING &&
mState != DECODER_STATE_SHUTDOWN)
{
ReentrantMonitorAutoExit audioExit(mAudioReentrantMonitor);
ReentrantMonitorAutoEnter mon(mDecoder->GetReentrantMonitor());
PRInt64 position = GetMediaTime();
while (oldPosition != position &&
mAudioEndTime - position > 0 &&
mState != DECODER_STATE_SEEKING &&
mState != DECODER_STATE_SHUTDOWN)
{
const PRInt64 DRAIN_BLOCK_USECS = 100000;
Wait(NS_MIN(mAudioEndTime - position, DRAIN_BLOCK_USECS));
oldPosition = position;
position = GetMediaTime();
}
if (mState == DECODER_STATE_SEEKING) {
seeking = PR_TRUE;
}
}
if (!seeking && mAudioStream && !mAudioStream->IsPaused()) {
mAudioStream->Drain();
// Fire one last event for any extra samples that didn't fill a framebuffer.
mEventManager.Drain(mAudioEndTime);
const PRInt64 DRAIN_BLOCK_USECS = 100000;
Wait(NS_MIN(mAudioEndTime - position, DRAIN_BLOCK_USECS));
oldPosition = position;
position = GetMediaTime();
}
seeking = mState == DECODER_STATE_SEEKING;
}
if (!seeking && !mAudioStream->IsPaused()) {
mAudioStream->Drain();
// Fire one last event for any extra samples that didn't fill a framebuffer.
mEventManager.Drain(mAudioEndTime);
}
LOG(PR_LOG_DEBUG, ("%p Reached audio stream end.", mDecoder));
}
LOG(PR_LOG_DEBUG, ("%p Reached audio stream end.", mDecoder));
{
// Must hold lock while shutting down and anulling audio stream to prevent
// state machine thread trying to use it while we're destroying it.
ReentrantMonitorAutoEnter mon(mDecoder->GetReentrantMonitor());
mAudioStream->Shutdown();
mAudioStream = nsnull;
mEventManager.Clear();
mAudioCompleted = PR_TRUE;
UpdateReadyState();
// Kick the decode and state machine threads; they may be sleeping waiting
@ -619,12 +624,8 @@ PRUint32 nsBuiltinDecoderStateMachine::PlaySilence(PRUint32 aSamples,
PRUint64 aSampleOffset)
{
ReentrantMonitorAutoEnter audioMon(mAudioReentrantMonitor);
if (!mAudioStream || mAudioStream->IsPaused()) {
// The state machine has paused since we've released the decoder
// monitor and acquired the audio monitor. Don't write any audio.
return 0;
}
NS_ASSERTION(OnAudioThread(), "Only call on audio thread.");
NS_ASSERTION(!mAudioStream->IsPaused(), "Don't play when paused");
PRUint32 maxSamples = SILENCE_BYTES_CHUNK / aChannels;
PRUint32 samples = NS_MIN(aSamples, maxSamples);
PRUint32 numValues = samples * aChannels;
@ -640,6 +641,8 @@ PRUint32 nsBuiltinDecoderStateMachine::PlaySilence(PRUint32 aSamples,
PRUint32 nsBuiltinDecoderStateMachine::PlayFromAudioQueue(PRUint64 aSampleOffset,
PRUint32 aChannels)
{
NS_ASSERTION(OnAudioThread(), "Only call on audio thread.");
NS_ASSERTION(!mAudioStream->IsPaused(), "Don't play when paused");
nsAutoPtr<SoundData> sound(mReader->mAudioQueue.PopFront());
{
ReentrantMonitorAutoEnter mon(mDecoder->GetReentrantMonitor());
@ -650,34 +653,28 @@ PRUint32 nsBuiltinDecoderStateMachine::PlayFromAudioQueue(PRUint64 aSampleOffset
}
PRInt64 offset = -1;
PRUint32 samples = 0;
{
ReentrantMonitorAutoEnter audioMon(mAudioReentrantMonitor);
if (!mAudioStream) {
return 0;
}
// The state machine could have paused since we've released the decoder
// monitor and acquired the audio monitor. Rather than acquire both
// monitors, the audio stream also maintains whether its paused or not.
// This prevents us from doing a blocking write while holding the audio
// monitor while paused; we would block, and the state machine won't be
// able to acquire the audio monitor in order to resume or destroy the
// audio stream.
if (!mAudioStream->IsPaused()) {
mAudioStream->Write(sound->mAudioData,
sound->AudioDataLength(),
PR_TRUE);
// The state machine could have paused since we've released the decoder
// monitor and acquired the audio monitor. Rather than acquire both
// monitors, the audio stream also maintains whether its paused or not.
// This prevents us from doing a blocking write while holding the audio
// monitor while paused; we would block, and the state machine won't be
// able to acquire the audio monitor in order to resume or destroy the
// audio stream.
if (!mAudioStream->IsPaused()) {
mAudioStream->Write(sound->mAudioData,
sound->AudioDataLength(),
PR_TRUE);
offset = sound->mOffset;
samples = sound->mSamples;
offset = sound->mOffset;
samples = sound->mSamples;
// Dispatch events to the DOM for the audio just written.
mEventManager.QueueWrittenAudioData(sound->mAudioData.get(),
sound->AudioDataLength(),
(aSampleOffset + samples) * aChannels);
} else {
mReader->mAudioQueue.PushFront(sound);
sound.forget();
}
// Dispatch events to the DOM for the audio just written.
mEventManager.QueueWrittenAudioData(sound->mAudioData.get(),
sound->AudioDataLength(),
(aSampleOffset + samples) * aChannels);
} else {
mReader->mAudioQueue.PushFront(sound);
sound.forget();
}
if (offset != -1) {
mDecoder->UpdatePlaybackOffset(offset);
@ -694,7 +691,7 @@ nsresult nsBuiltinDecoderStateMachine::Init(nsDecoderStateMachine* aCloneDonor)
return mReader->Init(cloneReader);
}
void nsBuiltinDecoderStateMachine::StopPlayback(eStopMode aMode)
void nsBuiltinDecoderStateMachine::StopPlayback()
{
NS_ASSERTION(OnStateMachineThread() || OnDecodeThread(),
"Should be on state machine thread.");
@ -711,49 +708,17 @@ void nsBuiltinDecoderStateMachine::StopPlayback(eStopMode aMode)
mPlayDuration += DurationToUsecs(TimeStamp::Now() - mPlayStartTime);
mPlayStartTime = TimeStamp();
}
if (HasAudio()) {
ReentrantMonitorAutoExit exitMon(mDecoder->GetReentrantMonitor());
ReentrantMonitorAutoEnter audioMon(mAudioReentrantMonitor);
if (mAudioStream) {
if (aMode == AUDIO_PAUSE) {
mAudioStream->Pause();
} else if (aMode == AUDIO_SHUTDOWN) {
mAudioStream->Shutdown();
mAudioStream = nsnull;
mEventManager.Clear();
}
}
}
NS_ASSERTION(!IsPlaying(), "Should report not playing at end of StopPlayback()");
}
void nsBuiltinDecoderStateMachine::StartPlayback()
{
NS_ASSERTION(OnStateMachineThread() || OnDecodeThread(),
"Should be on state machine thread.");
NS_ASSERTION(!IsPlaying(), "Shouldn't be playing when StartPlayback() is called");
mDecoder->GetReentrantMonitor().AssertCurrentThreadIn();
LOG(PR_LOG_DEBUG, ("%p StartPlayback", mDecoder));
mDecoder->mPlaybackStatistics.Start(TimeStamp::Now());
if (HasAudio()) {
PRInt32 rate = mInfo.mAudioRate;
PRInt32 channels = mInfo.mAudioChannels;
{
ReentrantMonitorAutoExit exitMon(mDecoder->GetReentrantMonitor());
ReentrantMonitorAutoEnter audioMon(mAudioReentrantMonitor);
if (mAudioStream) {
// We have an audiostream, so it must have been paused the last time
// StopPlayback() was called.
mAudioStream->Resume();
} else {
// No audiostream, create one.
mAudioStream = nsAudioStream::AllocateStream();
mAudioStream->Init(channels, rate, MOZ_SOUND_DATA_FORMAT);
mAudioStream->SetVolume(mVolume);
}
}
}
mPlayStartTime = TimeStamp::Now();
NS_ASSERTION(IsPlaying(), "Should report playing by end of StartPlayback()");
mDecoder->GetReentrantMonitor().NotifyAll();
}
@ -968,8 +933,6 @@ void nsBuiltinDecoderStateMachine::StopDecodeThread()
void nsBuiltinDecoderStateMachine::StopAudioThread()
{
NS_ASSERTION(OnStateMachineThread() || OnDecodeThread(),
"Should be on state machine thread.");
mDecoder->GetReentrantMonitor().AssertCurrentThreadIn();
mStopAudioThread = PR_TRUE;
mDecoder->GetReentrantMonitor().NotifyAll();
@ -1206,20 +1169,20 @@ void nsBuiltinDecoderStateMachine::DecodeSeek()
// The seek target is different than the current playback position,
// we'll need to seek the playback position, so shutdown our decode
// and audio threads.
StopPlayback(AUDIO_SHUTDOWN);
StopPlayback();
StopAudioThread();
ResetPlayback();
nsresult res;
{
ReentrantMonitorAutoExit exitMon(mDecoder->GetReentrantMonitor());
// Now perform the seek. We must not hold the state machine monitor
// while we seek, since the seek decodes.
// while we seek, since the seek reads, which could block on I/O.
res = mReader->Seek(seekTime,
mStartTime,
mEndTime,
mediaTime);
}
if (NS_SUCCEEDED(res)){
if (NS_SUCCEEDED(res)) {
SoundData* audio = HasAudio() ? mReader->mAudioQueue.PeekFront() : nsnull;
NS_ASSERTION(!audio || (audio->mTime <= seekTime &&
seekTime <= audio->mTime + audio->mDuration),
@ -1292,7 +1255,7 @@ nsresult nsBuiltinDecoderStateMachine::Run()
switch (mState) {
case DECODER_STATE_SHUTDOWN:
if (IsPlaying()) {
StopPlayback(AUDIO_SHUTDOWN);
StopPlayback();
}
StopAudioThread();
StopDecodeThread();
@ -1329,9 +1292,6 @@ nsresult nsBuiltinDecoderStateMachine::Run()
}
AdvanceFrame();
if (mState != DECODER_STATE_DECODING)
continue;
}
break;
@ -1352,7 +1312,7 @@ nsresult nsBuiltinDecoderStateMachine::Run()
case DECODER_STATE_BUFFERING:
{
if (IsPlaying()) {
StopPlayback(AUDIO_PAUSE);
StopPlayback();
mDecoder->GetReentrantMonitor().NotifyAll();
}
@ -1419,12 +1379,9 @@ nsresult nsBuiltinDecoderStateMachine::Run()
(mReader->mVideoQueue.GetSize() > 0 ||
(HasAudio() && !mAudioCompleted)));
if (mAudioStream) {
// Close the audio stream so that next time audio is used a new stream
// is created. The StopPlayback call also resets the IsPlaying() state
// so audio is restarted correctly.
StopPlayback(AUDIO_SHUTDOWN);
}
// StopPlayback in order to reset the IsPlaying() state so audio
// is restarted correctly.
StopPlayback();
if (mState != DECODER_STATE_COMPLETED)
continue;
@ -1482,9 +1439,13 @@ PRInt64
nsBuiltinDecoderStateMachine::GetAudioClock()
{
NS_ASSERTION(IsCurrentThread(mDecoder->mStateMachineThread), "Should be on state machine thread.");
if (!mAudioStream || !HasAudio())
mDecoder->GetReentrantMonitor().AssertCurrentThreadIn();
if (!HasAudio())
return -1;
PRInt64 t = mAudioStream->GetPosition();
PRInt64 t = -1;
if (!mAudioStream)
return -1;
t = mAudioStream->GetPosition();
return (t == -1) ? -1 : t + mAudioStartTime;
}
@ -1623,16 +1584,11 @@ void nsBuiltinDecoderStateMachine::AdvanceFrame()
if (remainingTime > 0) {
Wait(remainingTime);
}
} else {
if (IsPlaying()) {
StopPlayback(AUDIO_PAUSE);
mDecoder->GetReentrantMonitor().NotifyAll();
}
if (mState == DECODER_STATE_DECODING ||
mState == DECODER_STATE_COMPLETED) {
mDecoder->GetReentrantMonitor().Wait();
}
} else if (mState == DECODER_STATE_DECODING ||
mState == DECODER_STATE_COMPLETED)
{
StopPlayback();
mDecoder->GetReentrantMonitor().Wait();
}
}
@ -1642,8 +1598,12 @@ void nsBuiltinDecoderStateMachine::Wait(PRInt64 aUsecs) {
TimeStamp now;
while ((now = TimeStamp::Now()) < end &&
mState != DECODER_STATE_SHUTDOWN &&
mState != DECODER_STATE_SEEKING)
mState != DECODER_STATE_SEEKING &&
(!OnAudioThread() || !mStopAudioThread))
{
if (OnAudioThread() && !IsPlaying()) {
break;
}
PRInt64 ms = static_cast<PRInt64>(NS_round((end - now).ToSeconds() * 1000));
if (ms == 0 || ms > PR_UINT32_MAX) {
break;

View File

@ -356,23 +356,14 @@ protected:
// to audio stream to play audio data.
void AudioLoop();
// Stop or pause playback of media. This has two modes, denoted by
// aMode being either AUDIO_PAUSE or AUDIO_SHUTDOWN.
//
// AUDIO_PAUSE: Suspends the audio stream to be resumed later.
// This does not close the OS based audio stream
//
// AUDIO_SHUTDOWN: Closes and destroys the audio stream and
// releases any OS resources.
//
// The decoder monitor must be held with exactly one lock count. Called
// on the state machine thread.
enum eStopMode {AUDIO_PAUSE, AUDIO_SHUTDOWN};
void StopPlayback(eStopMode aMode);
// Sets internal state which causes playback of media to pause.
// The decoder monitor must be held. Called on the main, state machine,
// and decode threads.
void StopPlayback();
// Resume playback of media. Must be called with the decode monitor held.
// This resumes a paused audio stream. The decoder monitor must be held with
// exactly one lock count. Called on the state machine thread.
// Sets internal state which causes playback of media to begin or resume.
// Must be called with the decode monitor held. Called on the state machine
// and decode threads.
void StartPlayback();
// Moves the decoder into decoding state. Called on the state machine
@ -418,12 +409,6 @@ protected:
// to call.
void DecodeThreadRun();
// ReentrantMonitor on mAudioStream. This monitor must be held in
// order to delete or use the audio stream. This stops us destroying
// the audio stream while it's being used on another thread
// (typically when it's being written to on the audio thread).
ReentrantMonitor mAudioReentrantMonitor;
// The size of the decoded YCbCr frame.
// Accessed on state machine thread.
PRUint32 mCbCrSize;
@ -470,9 +455,10 @@ protected:
// this value. Accessed on main and state machine thread.
PRInt64 mSeekTime;
// The audio stream resource. Used on the state machine, audio, and
// main threads. You must hold the mAudioReentrantMonitor, and must
// NOT hold the decoder monitor when using the audio stream!
// The audio stream resource. Used on the state machine, and audio threads.
// This is created and destroyed on the audio thread, while holding the
// decoder monitor, so if this is used off the audio thread, you must
// first acquire the decoder monitor and check that it is non-null.
nsRefPtr<nsAudioStream> mAudioStream;
// The reader, don't call its methods with the decoder monitor held.