From e8ac187b02e0d93577d4d3013e08763e0dfeaba7 Mon Sep 17 00:00:00 2001 From: Paul Adenot Date: Mon, 26 Nov 2012 15:13:08 +0100 Subject: [PATCH] Bug 793274 - Make sure to have enough frames pushed to the AudioStream before starting it. r=kinetik --- .../html/content/src/nsHTMLAudioElement.cpp | 3 + content/media/AudioStream.cpp | 66 +++++++++++++++---- content/media/AudioStream.h | 18 ++++- content/media/MediaDecoderStateMachine.cpp | 25 +++++++ 4 files changed, 97 insertions(+), 15 deletions(-) diff --git a/content/html/content/src/nsHTMLAudioElement.cpp b/content/html/content/src/nsHTMLAudioElement.cpp index c3014cc220ee..098e0fd97773 100644 --- a/content/html/content/src/nsHTMLAudioElement.cpp +++ b/content/html/content/src/nsHTMLAudioElement.cpp @@ -176,6 +176,9 @@ nsHTMLAudioElement::MozWriteAudio(const JS::Value& aData, JSContext* aCx, uint32 // AudioDataValue is 'float', but it's not worth it for this deprecated API. nsAutoArrayPtr audioData(new AudioDataValue[writeLen * mChannels]); ConvertAudioSamples(frames, audioData.get(), writeLen * mChannels); + if (!mAudioStream->IsStarted()) { + mAudioStream->Start(); + } nsresult rv = mAudioStream->Write(audioData.get(), writeLen); if (NS_FAILED(rv)) { diff --git a/content/media/AudioStream.cpp b/content/media/AudioStream.cpp index b2a535a628a2..ecb54710c13d 100644 --- a/content/media/AudioStream.cpp +++ b/content/media/AudioStream.cpp @@ -59,6 +59,8 @@ class NativeAudioStream : public AudioStream uint32_t Available(); void SetVolume(double aVolume); void Drain(); + nsresult Start(); + bool IsStarted(); void Pause(); void Resume(); int64_t GetPosition(); @@ -181,6 +183,7 @@ AudioStream::AudioStream() : mInRate(0), mOutRate(0), mChannels(0), + mWritten(0), mAudioClock(this) {} @@ -276,6 +279,11 @@ nsresult AudioStream::SetPreservesPitch(bool aPreservesPitch) return NS_OK; } +int64_t AudioStream::GetWritten() +{ + return mWritten; +} + NativeAudioStream::NativeAudioStream() : mVolume(1.0), mAudioHandle(0), @@ -386,6 +394,8 @@ nsresult NativeAudioStream::Write(const AudioDataValue* aBuf, uint32_t aFrames) written = WriteToBackend(aBuf, samples); } + mWritten += aFrames; + if (written == -1) { PR_LOG(gAudioStreamLog, PR_LOG_ERROR, ("NativeAudioStream: sa_stream_write error")); mInError = true; @@ -455,6 +465,19 @@ void NativeAudioStream::Drain() } } +nsresult NativeAudioStream::Start() +{ + // Since sydneyaudio is a push API, the playback is started when enough frames + // have been written. Hence, Start() is a noop. + return NS_OK; +} + +bool NativeAudioStream::IsStarted() +{ + // See the comment for the |Start()| method. + return true; +} + void NativeAudioStream::Pause() { if (mInError) @@ -597,6 +620,8 @@ class BufferedAudioStream : public AudioStream uint32_t Available(); void SetVolume(double aVolume); void Drain(); + nsresult Start(); + bool IsStarted(); void Pause(); void Resume(); int64_t GetPosition(); @@ -782,24 +807,19 @@ BufferedAudioStream::Write(const AudioDataValue* aBuf, uint32_t aFrames) src += available; bytesToCopy -= available; - if (mState != STARTED) { - int r; - { - MonitorAutoUnlock mon(mMonitor); - r = cubeb_stream_start(mCubebStream); - } - mState = r == CUBEB_OK ? STARTED : ERRORED; - } - - if (mState != STARTED) { - return NS_ERROR_FAILURE; - } - if (bytesToCopy > 0) { + // If we are not playing, but our buffer is full, start playing to make + // room for soon-to-be-decoded data. + if (!IsStarted()) { + MonitorAutoUnlock mon(mMonitor); + Start(); + } mon.Wait(); } } + mWritten += aFrames; + return NS_OK; } @@ -838,6 +858,26 @@ BufferedAudioStream::Drain() } } +nsresult +BufferedAudioStream::Start() +{ + if (!mCubebStream) { + return NS_ERROR_FAILURE; + } + if (mState != STARTED) { + int r = cubeb_stream_start(mCubebStream); + mState = r == CUBEB_OK ? STARTED : ERRORED; + return mState == STARTED ? NS_OK : NS_ERROR_FAILURE; + } + return NS_OK; +} + +bool +BufferedAudioStream::IsStarted() +{ + return mState == STARTED ? true : false; +} + void BufferedAudioStream::Pause() { diff --git a/content/media/AudioStream.h b/content/media/AudioStream.h index 35bfce031af2..522936cd5975 100644 --- a/content/media/AudioStream.h +++ b/content/media/AudioStream.h @@ -44,6 +44,8 @@ class AudioClock // Get the current pitch preservation state. // Called on the audio thread. bool GetPreservesPitch(); + // Get the number of frames written to the backend. + int64_t GetWritten(); private: // This AudioStream holds a strong reference to this AudioClock. This // pointer is garanteed to always be valid. @@ -128,10 +130,20 @@ public: // Block until buffered audio data has been consumed. virtual void Drain() = 0; - // Pause audio playback + // Start the stream. + virtual nsresult Start() = 0; + + // Check if the stream is started. + virtual bool IsStarted() = 0; + + // Return the number of frames written so far in the stream. This allow the + // caller to check if it is safe to start the stream, if needed. + virtual int64_t GetWritten(); + + // Pause audio playback. virtual void Pause() = 0; - // Resume audio playback + // Resume audio playback. virtual void Resume() = 0; // Return the position in microseconds of the audio frame being played by @@ -171,6 +183,8 @@ protected: // Output rate in Hz (characteristic of the playback rate) int mOutRate; int mChannels; + // Number of frames written to the buffers. + int64_t mWritten; AudioClock mAudioClock; nsAutoPtr mTimeStretcher; }; diff --git a/content/media/MediaDecoderStateMachine.cpp b/content/media/MediaDecoderStateMachine.cpp index 673b7f45bcd4..d26c48d8aba4 100644 --- a/content/media/MediaDecoderStateMachine.cpp +++ b/content/media/MediaDecoderStateMachine.cpp @@ -109,6 +109,9 @@ static const uint32_t QUICK_BUFFERING_LOW_DATA_USECS = 1000000; // QUICK_BUFFERING_LOW_DATA_USECS. PR_STATIC_ASSERT(QUICK_BUFFERING_LOW_DATA_USECS <= AMPLE_AUDIO_USECS); +// This value has been chosen empirically. +static const uint32_t AUDIOSTREAM_MIN_WRITE_BEFORE_START_USECS = 200000; + static TimeDuration UsecsToDuration(int64_t aUsecs) { return TimeDuration::FromMilliseconds(static_cast(aUsecs) / USECS_PER_MS); } @@ -952,6 +955,19 @@ bool MediaDecoderStateMachine::IsPlaying() return !mPlayStartTime.IsNull(); } +// If we have already written enough frames to the AudioStream, start the +// playback. +static void +StartAudioStreamPlaybackIfNeeded(AudioStream* aStream) +{ + // We want to have enough data in the buffer to start the stream. + if (!aStream->IsStarted() && + static_cast(aStream->GetWritten()) / aStream->GetRate() >= + static_cast(AUDIOSTREAM_MIN_WRITE_BEFORE_START_USECS) / USECS_PER_S) { + aStream->Start(); + } +} + static void WriteSilence(AudioStream* aStream, uint32_t aFrames) { uint32_t numSamples = aFrames * aStream->GetChannels(); @@ -959,6 +975,8 @@ static void WriteSilence(AudioStream* aStream, uint32_t aFrames) buf.SetLength(numSamples); memset(buf.Elements(), 0, numSamples * sizeof(AudioDataValue)); aStream->Write(buf.Elements(), aFrames); + + StartAudioStreamPlaybackIfNeeded(aStream); } void MediaDecoderStateMachine::AudioLoop() @@ -1109,6 +1127,11 @@ void MediaDecoderStateMachine::AudioLoop() mState != DECODER_STATE_SHUTDOWN && !mStopAudioThread) { + // If the media was too short to trigger the start of the audio stream, + // start it now. + if (!mAudioStream->IsStarted()) { + mAudioStream->Start(); + } // Last frame pushed to audio hardware, wait for the audio to finish, // before the audio thread terminates. bool seeking = false; @@ -1213,6 +1236,8 @@ uint32_t MediaDecoderStateMachine::PlayFromAudioQueue(uint64_t aFrameOffset, mAudioStream->Write(audio->mAudioData, audio->mFrames); + StartAudioStreamPlaybackIfNeeded(mAudioStream); + offset = audio->mOffset; frames = audio->mFrames;