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

View File

@ -356,23 +356,14 @@ protected:
// to audio stream to play audio data. // to audio stream to play audio data.
void AudioLoop(); void AudioLoop();
// Stop or pause playback of media. This has two modes, denoted by // Sets internal state which causes playback of media to pause.
// aMode being either AUDIO_PAUSE or AUDIO_SHUTDOWN. // The decoder monitor must be held. Called on the main, state machine,
// // and decode threads.
// AUDIO_PAUSE: Suspends the audio stream to be resumed later. void StopPlayback();
// 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);
// Resume playback of media. Must be called with the decode monitor held. // Sets internal state which causes playback of media to begin or resume.
// This resumes a paused audio stream. The decoder monitor must be held with // Must be called with the decode monitor held. Called on the state machine
// exactly one lock count. Called on the state machine thread. // and decode threads.
void StartPlayback(); void StartPlayback();
// Moves the decoder into decoding state. Called on the state machine // Moves the decoder into decoding state. Called on the state machine
@ -418,12 +409,6 @@ protected:
// to call. // to call.
void DecodeThreadRun(); 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. // The size of the decoded YCbCr frame.
// Accessed on state machine thread. // Accessed on state machine thread.
PRUint32 mCbCrSize; PRUint32 mCbCrSize;
@ -470,9 +455,10 @@ protected:
// this value. Accessed on main and state machine thread. // this value. Accessed on main and state machine thread.
PRInt64 mSeekTime; PRInt64 mSeekTime;
// The audio stream resource. Used on the state machine, audio, and // The audio stream resource. Used on the state machine, and audio threads.
// main threads. You must hold the mAudioReentrantMonitor, and must // This is created and destroyed on the audio thread, while holding the
// NOT hold the decoder monitor when using the audio stream! // 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; nsRefPtr<nsAudioStream> mAudioStream;
// The reader, don't call its methods with the decoder monitor held. // The reader, don't call its methods with the decoder monitor held.