diff --git a/content/media/video/public/nsAudioStream.h b/content/media/video/public/nsAudioStream.h index 1c8c6e153a8a..2f78c3efbc23 100644 --- a/content/media/video/public/nsAudioStream.h +++ b/content/media/video/public/nsAudioStream.h @@ -40,6 +40,7 @@ #include "nscore.h" #include "prlog.h" +#include "nsTArray.h" extern PRLogModuleInfo* gAudioStreamLog; @@ -117,6 +118,12 @@ class nsAudioStream SampleFormat mFormat; + // When a Write() request is made, and the number of samples + // requested to be written exceeds the buffer size of the audio + // backend, the remaining samples are stored in this variable. They + // will be written on the next Write() request. + nsTArray mBufferOverflow; + PRPackedBool mPaused; }; #endif diff --git a/content/media/video/src/nsAudioStream.cpp b/content/media/video/src/nsAudioStream.cpp index 9f8fba5eaf80..a3f9ec7c5ba2 100644 --- a/content/media/video/src/nsAudioStream.cpp +++ b/content/media/video/src/nsAudioStream.cpp @@ -122,49 +122,63 @@ void nsAudioStream::Write(const void* aBuf, PRUint32 aCount) "Buffer size must be divisible by channel count"); mSamplesBuffered += aCount; + PRUint32 offset = mBufferOverflow.Length(); + PRInt32 count = aCount + offset; if (!mAudioHandle) return; - nsAutoArrayPtr s_data(new short[aCount]); + nsAutoArrayPtr s_data(new short[count]); if (s_data) { + for (PRUint32 i=0; i < offset; ++i) { + s_data[i] = mBufferOverflow.ElementAt(i); + } + mBufferOverflow.Clear(); + switch (mFormat) { - case FORMAT_U8: { - const PRUint8* buf = static_cast(aBuf); - PRInt32 volume = PRInt32((1 << 16) * mVolume); - for (PRUint32 i = 0; i < aCount; ++i) { - s_data[i] = short(((PRInt32(buf[i]) - 128) * volume) >> 8); - } - break; - } - case FORMAT_S16_LE: { - const short* buf = static_cast(aBuf); - PRInt32 volume = PRInt32((1 << 16) * mVolume); - for (PRUint32 i = 0; i < aCount; ++i) { - s_data[i] = short((PRInt32(buf[i]) * volume) >> 16); - } - break; - } - case FORMAT_FLOAT32_LE: { - const float* buf = static_cast(aBuf); - for (PRUint32 i= 0; i < aCount; ++i) { - float scaled_value = floorf(0.5 + 32768 * buf[i] * mVolume); - if (buf[i] < 0.0) { - s_data[i] = (scaled_value < -32768.0) ? - -32768 : - short(scaled_value); - } else { - s_data[i] = (scaled_value > 32767.0) ? - 32767 : - short(scaled_value); + case FORMAT_U8: { + const PRUint8* buf = static_cast(aBuf); + PRInt32 volume = PRInt32((1 << 16) * mVolume); + for (PRUint32 i = 0; i < aCount; ++i) { + s_data[i + offset] = short(((PRInt32(buf[i]) - 128) * volume) >> 8); } + break; + } + case FORMAT_S16_LE: { + const short* buf = static_cast(aBuf); + PRInt32 volume = PRInt32((1 << 16) * mVolume); + for (PRUint32 i = 0; i < aCount; ++i) { + s_data[i + offset] = short((PRInt32(buf[i]) * volume) >> 16); + } + break; + } + case FORMAT_FLOAT32_LE: { + const float* buf = static_cast(aBuf); + for (PRUint32 i = 0; i < aCount; ++i) { + float scaled_value = floorf(0.5 + 32768 * buf[i] * mVolume); + if (buf[i] < 0.0) { + s_data[i + offset] = (scaled_value < -32768.0) ? + -32768 : + short(scaled_value); + } else { + s_data[i+offset] = (scaled_value > 32767.0) ? + 32767 : + short(scaled_value); + } + } + break; } - break; - } } - if (sa_stream_write(static_cast(mAudioHandle), s_data.get(), aCount * sizeof(short)) != SA_SUCCESS) { + PRInt32 available = Available(); + if (available < count) { + mBufferOverflow.AppendElements(s_data.get() + available, (count - available)); + count = available; + } + + if (sa_stream_write(static_cast(mAudioHandle), + s_data.get(), count * sizeof(short)) != SA_SUCCESS) { PR_LOG(gAudioStreamLog, PR_LOG_ERROR, ("nsAudioStream: sa_stream_write error")); Shutdown(); } @@ -197,11 +211,21 @@ void nsAudioStream::SetVolume(float aVolume) void nsAudioStream::Drain() { if (!mAudioHandle) { + // mSamplesBuffered already accounts for the data in the + // mBufferOverflow array. PRUint32 drainTime = (float(mSamplesBuffered) / mRate / mChannels - GetTime()) * 1000.0; PR_Sleep(PR_MillisecondsToInterval(drainTime)); return; } + // Write any remaining unwritten sound data in the overflow buffer + if (!mBufferOverflow.IsEmpty()) { + if (sa_stream_write(static_cast(mAudioHandle), + mBufferOverflow.Elements(), + mBufferOverflow.Length() * sizeof(short)) != SA_SUCCESS) + return; + } + if (sa_stream_drain(static_cast(mAudioHandle)) != SA_SUCCESS) { PR_LOG(gAudioStreamLog, PR_LOG_ERROR, ("nsAudioStream: sa_stream_drain error")); Shutdown(); diff --git a/content/media/video/src/nsOggDecoder.cpp b/content/media/video/src/nsOggDecoder.cpp index 7679357d74f7..a90e4a0ead2e 100644 --- a/content/media/video/src/nsOggDecoder.cpp +++ b/content/media/video/src/nsOggDecoder.cpp @@ -141,6 +141,15 @@ public: MOZ_COUNT_DTOR(FrameData); } + // Write the audio data from the frame to the Audio stream. + void Write(nsAudioStream* aStream) + { + PRUint32 length = mAudioData.Length(); + if (length == 0) + return; + + aStream->Write(mAudioData.Elements(), length); + } nsAutoArrayPtr mVideoData; nsTArray mAudioData; @@ -260,9 +269,8 @@ public: void PlayVideo(FrameData* aFrame); // Play the audio data from the given frame. The decode monitor must - // be locked when calling this method. Returns PR_FALSE if unable to - // write to the audio device without blocking. - PRBool PlayAudio(FrameData* aFrame); + // be locked when calling this method. + void PlayAudio(FrameData* aFrame); // Called from the main thread to get the current frame time. The decoder // monitor must be obtained before calling this. @@ -629,13 +637,13 @@ void nsOggDecodeStateMachine::PlayFrame() { double time = (PR_IntervalToMilliseconds(PR_IntervalNow()-mPlayStartTime-mPauseDuration)/1000.0); if (time >= frame->mTime) { - mDecodedFrames.Pop(); // Audio for the current frame is played, but video for the next frame // is displayed, to account for lag from the time the audio is written // to when it is played. This will go away when we move to a/v sync // using the audio hardware clock. - PlayVideo(mDecodedFrames.IsEmpty() ? frame : mDecodedFrames.Peek()); PlayAudio(frame); + mDecodedFrames.Pop(); + PlayVideo(mDecodedFrames.IsEmpty() ? frame : mDecodedFrames.Peek()); UpdatePlaybackPosition(frame->mDecodedFrameTime); delete frame; } @@ -677,17 +685,13 @@ void nsOggDecodeStateMachine::PlayVideo(FrameData* aFrame) } } -PRBool nsOggDecodeStateMachine::PlayAudio(FrameData* aFrame) +void nsOggDecodeStateMachine::PlayAudio(FrameData* aFrame) { // NS_ASSERTION(PR_InMonitor(mDecoder->GetMonitor()), "PlayAudio() called without acquiring decoder monitor"); - if (mAudioStream && aFrame && !aFrame->mAudioData.IsEmpty()) { - if (PRUint32(mAudioStream->Available()) < aFrame->mAudioData.Length()) - return PR_FALSE; + if (!mAudioStream) + return; - mAudioStream->Write(aFrame->mAudioData.Elements(), aFrame->mAudioData.Length()); - } - - return PR_TRUE; + aFrame->Write(mAudioStream); } void nsOggDecodeStateMachine::OpenAudioStream() @@ -1054,6 +1058,14 @@ nsresult nsOggDecodeStateMachine::Run() if (mState != DECODER_STATE_COMPLETED) continue; + if (mAudioStream) { + mon.Exit(); + mAudioStream->Drain(); + mon.Enter(); + if (mState != DECODER_STATE_COMPLETED) + continue; + } + nsCOMPtr event = NS_NEW_RUNNABLE_METHOD(nsOggDecoder, mDecoder, PlaybackEnded); NS_DispatchToMainThread(event, NS_DISPATCH_NORMAL);