Bug 463627 - Audio playback stops after some seconds on Linux - r=kinetik sr=roc

This commit is contained in:
Chris Double 2008-12-16 12:06:22 +13:00
Родитель 4ae82003d3
Коммит aad3ca91b3
3 изменённых файлов: 88 добавлений и 45 удалений

Просмотреть файл

@ -40,6 +40,7 @@
#include "nscore.h" #include "nscore.h"
#include "prlog.h" #include "prlog.h"
#include "nsTArray.h"
extern PRLogModuleInfo* gAudioStreamLog; extern PRLogModuleInfo* gAudioStreamLog;
@ -117,6 +118,12 @@ class nsAudioStream
SampleFormat mFormat; 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<short> mBufferOverflow;
PRPackedBool mPaused; PRPackedBool mPaused;
}; };
#endif #endif

Просмотреть файл

@ -122,49 +122,63 @@ void nsAudioStream::Write(const void* aBuf, PRUint32 aCount)
"Buffer size must be divisible by channel count"); "Buffer size must be divisible by channel count");
mSamplesBuffered += aCount; mSamplesBuffered += aCount;
PRUint32 offset = mBufferOverflow.Length();
PRInt32 count = aCount + offset;
if (!mAudioHandle) if (!mAudioHandle)
return; return;
nsAutoArrayPtr<short> s_data(new short[aCount]); nsAutoArrayPtr<short> s_data(new short[count]);
if (s_data) { if (s_data) {
for (PRUint32 i=0; i < offset; ++i) {
s_data[i] = mBufferOverflow.ElementAt(i);
}
mBufferOverflow.Clear();
switch (mFormat) { switch (mFormat) {
case FORMAT_U8: { case FORMAT_U8: {
const PRUint8* buf = static_cast<const PRUint8*>(aBuf); const PRUint8* buf = static_cast<const PRUint8*>(aBuf);
PRInt32 volume = PRInt32((1 << 16) * mVolume); PRInt32 volume = PRInt32((1 << 16) * mVolume);
for (PRUint32 i = 0; i < aCount; ++i) { for (PRUint32 i = 0; i < aCount; ++i) {
s_data[i] = short(((PRInt32(buf[i]) - 128) * volume) >> 8); s_data[i + offset] = short(((PRInt32(buf[i]) - 128) * volume) >> 8);
}
break;
}
case FORMAT_S16_LE: {
const short* buf = static_cast<const short*>(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<const float*>(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);
} }
break;
}
case FORMAT_S16_LE: {
const short* buf = static_cast<const short*>(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<const float*>(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<sa_stream_t*>(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<sa_stream_t*>(mAudioHandle),
s_data.get(), count * sizeof(short)) != SA_SUCCESS) {
PR_LOG(gAudioStreamLog, PR_LOG_ERROR, ("nsAudioStream: sa_stream_write error")); PR_LOG(gAudioStreamLog, PR_LOG_ERROR, ("nsAudioStream: sa_stream_write error"));
Shutdown(); Shutdown();
} }
@ -197,11 +211,21 @@ void nsAudioStream::SetVolume(float aVolume)
void nsAudioStream::Drain() void nsAudioStream::Drain()
{ {
if (!mAudioHandle) { if (!mAudioHandle) {
// mSamplesBuffered already accounts for the data in the
// mBufferOverflow array.
PRUint32 drainTime = (float(mSamplesBuffered) / mRate / mChannels - GetTime()) * 1000.0; PRUint32 drainTime = (float(mSamplesBuffered) / mRate / mChannels - GetTime()) * 1000.0;
PR_Sleep(PR_MillisecondsToInterval(drainTime)); PR_Sleep(PR_MillisecondsToInterval(drainTime));
return; return;
} }
// Write any remaining unwritten sound data in the overflow buffer
if (!mBufferOverflow.IsEmpty()) {
if (sa_stream_write(static_cast<sa_stream_t*>(mAudioHandle),
mBufferOverflow.Elements(),
mBufferOverflow.Length() * sizeof(short)) != SA_SUCCESS)
return;
}
if (sa_stream_drain(static_cast<sa_stream_t*>(mAudioHandle)) != SA_SUCCESS) { if (sa_stream_drain(static_cast<sa_stream_t*>(mAudioHandle)) != SA_SUCCESS) {
PR_LOG(gAudioStreamLog, PR_LOG_ERROR, ("nsAudioStream: sa_stream_drain error")); PR_LOG(gAudioStreamLog, PR_LOG_ERROR, ("nsAudioStream: sa_stream_drain error"));
Shutdown(); Shutdown();

Просмотреть файл

@ -141,6 +141,15 @@ public:
MOZ_COUNT_DTOR(FrameData); 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<unsigned char> mVideoData; nsAutoArrayPtr<unsigned char> mVideoData;
nsTArray<float> mAudioData; nsTArray<float> mAudioData;
@ -260,9 +269,8 @@ public:
void PlayVideo(FrameData* aFrame); void PlayVideo(FrameData* aFrame);
// Play the audio data from the given frame. The decode monitor must // Play the audio data from the given frame. The decode monitor must
// be locked when calling this method. Returns PR_FALSE if unable to // be locked when calling this method.
// write to the audio device without blocking. void PlayAudio(FrameData* aFrame);
PRBool PlayAudio(FrameData* aFrame);
// Called from the main thread to get the current frame time. The decoder // Called from the main thread to get the current frame time. The decoder
// monitor must be obtained before calling this. // monitor must be obtained before calling this.
@ -629,13 +637,13 @@ void nsOggDecodeStateMachine::PlayFrame() {
double time = (PR_IntervalToMilliseconds(PR_IntervalNow()-mPlayStartTime-mPauseDuration)/1000.0); double time = (PR_IntervalToMilliseconds(PR_IntervalNow()-mPlayStartTime-mPauseDuration)/1000.0);
if (time >= frame->mTime) { if (time >= frame->mTime) {
mDecodedFrames.Pop();
// Audio for the current frame is played, but video for the next frame // 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 // 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 // to when it is played. This will go away when we move to a/v sync
// using the audio hardware clock. // using the audio hardware clock.
PlayVideo(mDecodedFrames.IsEmpty() ? frame : mDecodedFrames.Peek());
PlayAudio(frame); PlayAudio(frame);
mDecodedFrames.Pop();
PlayVideo(mDecodedFrames.IsEmpty() ? frame : mDecodedFrames.Peek());
UpdatePlaybackPosition(frame->mDecodedFrameTime); UpdatePlaybackPosition(frame->mDecodedFrameTime);
delete frame; 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"); // NS_ASSERTION(PR_InMonitor(mDecoder->GetMonitor()), "PlayAudio() called without acquiring decoder monitor");
if (mAudioStream && aFrame && !aFrame->mAudioData.IsEmpty()) { if (!mAudioStream)
if (PRUint32(mAudioStream->Available()) < aFrame->mAudioData.Length()) return;
return PR_FALSE;
mAudioStream->Write(aFrame->mAudioData.Elements(), aFrame->mAudioData.Length()); aFrame->Write(mAudioStream);
}
return PR_TRUE;
} }
void nsOggDecodeStateMachine::OpenAudioStream() void nsOggDecodeStateMachine::OpenAudioStream()
@ -1054,6 +1058,14 @@ nsresult nsOggDecodeStateMachine::Run()
if (mState != DECODER_STATE_COMPLETED) if (mState != DECODER_STATE_COMPLETED)
continue; continue;
if (mAudioStream) {
mon.Exit();
mAudioStream->Drain();
mon.Enter();
if (mState != DECODER_STATE_COMPLETED)
continue;
}
nsCOMPtr<nsIRunnable> event = nsCOMPtr<nsIRunnable> event =
NS_NEW_RUNNABLE_METHOD(nsOggDecoder, mDecoder, PlaybackEnded); NS_NEW_RUNNABLE_METHOD(nsOggDecoder, mDecoder, PlaybackEnded);
NS_DispatchToMainThread(event, NS_DISPATCH_NORMAL); NS_DispatchToMainThread(event, NS_DISPATCH_NORMAL);