зеркало из https://github.com/mozilla/pjs.git
Bug 469266 - Rewrite Wave decoder playback loop to reduce latency, improve accuracy of playback position reporting, and fire timeupdate events.
--HG-- extra : rebase_source : 2c2faa5af34abe2b5fe14d150207d9c375f7d567
This commit is contained in:
Родитель
da568364d2
Коммит
f7fc45073b
|
@ -92,30 +92,12 @@ class nsAudioStream
|
|||
// Block until buffered audio data has been consumed.
|
||||
void Drain();
|
||||
|
||||
// Pause sound playback.
|
||||
void Pause();
|
||||
|
||||
// Resume sound playback.
|
||||
void Resume();
|
||||
|
||||
// Return the position (in seconds) of the audio sample currently being
|
||||
// played by the audio hardware.
|
||||
double GetTime();
|
||||
|
||||
private:
|
||||
double mVolume;
|
||||
void* mAudioHandle;
|
||||
int mRate;
|
||||
int mChannels;
|
||||
|
||||
// The byte position in the audio buffer where playback was last paused.
|
||||
PRInt64 mSavedPauseBytes;
|
||||
PRInt64 mPauseBytes;
|
||||
|
||||
float mStartTime;
|
||||
float mPauseTime;
|
||||
PRInt64 mSamplesBuffered;
|
||||
|
||||
SampleFormat mFormat;
|
||||
|
||||
// When a Write() request is made, and the number of samples
|
||||
|
@ -123,7 +105,5 @@ class nsAudioStream
|
|||
// backend, the remaining samples are stored in this variable. They
|
||||
// will be written on the next Write() request.
|
||||
nsTArray<short> mBufferOverflow;
|
||||
|
||||
PRPackedBool mPaused;
|
||||
};
|
||||
#endif
|
||||
|
|
|
@ -67,10 +67,6 @@ public:
|
|||
// request to return an error. Call on main thread only.
|
||||
void Cancel();
|
||||
|
||||
// Return the number of bytes buffered from the file. This can safely
|
||||
// be read without blocking.
|
||||
PRUint32 Available();
|
||||
|
||||
// Suspend any downloads that are in progress.
|
||||
void Suspend();
|
||||
|
||||
|
|
|
@ -88,7 +88,6 @@ public:
|
|||
virtual nsresult Read(char* aBuffer, PRUint32 aCount, PRUint32* aBytes) = 0;
|
||||
virtual nsresult Seek(PRInt32 aWhence, PRInt64 aOffset) = 0;
|
||||
virtual PRInt64 Tell() = 0;
|
||||
virtual PRUint32 Available() = 0;
|
||||
virtual void Cancel() { }
|
||||
virtual nsIPrincipal* GetCurrentPrincipal() = 0;
|
||||
virtual void Suspend() = 0;
|
||||
|
@ -172,10 +171,6 @@ class nsMediaStream
|
|||
// Can be called from any thread.
|
||||
PRInt64 Tell();
|
||||
|
||||
// Return the number of bytes available in the stream that can be
|
||||
// read without blocking. Can be called from any thread.
|
||||
PRUint32 Available();
|
||||
|
||||
// Cancels any currently blocking request and forces that request to
|
||||
// return an error. Call on main thread only.
|
||||
void Cancel();
|
||||
|
|
|
@ -200,6 +200,8 @@ class nsWaveDecoder : public nsMediaDecoder
|
|||
|
||||
virtual void SetTotalBytes(PRInt64 aBytes);
|
||||
|
||||
void PlaybackPositionChanged();
|
||||
|
||||
// Setter for the duration. This is ignored by the wave decoder since it can
|
||||
// compute the duration directly from the wave data.
|
||||
virtual void SetDuration(PRInt64 aDuration);
|
||||
|
@ -268,6 +270,12 @@ private:
|
|||
// state machine will validate the offset against the current media.
|
||||
float mTimeOffset;
|
||||
|
||||
// The current playback position of the media resource in units of
|
||||
// seconds. This is updated every time a block of audio is passed to the
|
||||
// backend (unless an prior update is still pending). It is read and
|
||||
// written from the main thread only.
|
||||
float mCurrentTime;
|
||||
|
||||
// Copy of the current time, duration, and ended state when the state
|
||||
// machine was disposed. Used to respond to time and duration queries
|
||||
// with sensible values after the state machine is destroyed.
|
||||
|
|
|
@ -52,11 +52,6 @@ PRLogModuleInfo* gAudioStreamLog = nsnull;
|
|||
|
||||
#define FAKE_BUFFER_SIZE 176400
|
||||
|
||||
static float CurrentTimeInSeconds()
|
||||
{
|
||||
return PR_IntervalToMilliseconds(PR_IntervalNow()) / 1000.0;
|
||||
}
|
||||
|
||||
void nsAudioStream::InitLibrary()
|
||||
{
|
||||
#ifdef PR_LOGGING
|
||||
|
@ -73,12 +68,7 @@ nsAudioStream::nsAudioStream() :
|
|||
mAudioHandle(0),
|
||||
mRate(0),
|
||||
mChannels(0),
|
||||
mSavedPauseBytes(0),
|
||||
mPauseBytes(0),
|
||||
mPauseTime(0.0),
|
||||
mSamplesBuffered(0),
|
||||
mFormat(FORMAT_S16_LE),
|
||||
mPaused(PR_FALSE)
|
||||
mFormat(FORMAT_S16_LE)
|
||||
{
|
||||
}
|
||||
|
||||
|
@ -87,7 +77,6 @@ void nsAudioStream::Init(PRInt32 aNumChannels, PRInt32 aRate, SampleFormat aForm
|
|||
mRate = aRate;
|
||||
mChannels = aNumChannels;
|
||||
mFormat = aFormat;
|
||||
mStartTime = CurrentTimeInSeconds();
|
||||
if (sa_stream_create_pcm(reinterpret_cast<sa_stream_t**>(&mAudioHandle),
|
||||
NULL,
|
||||
SA_MODE_WRONLY,
|
||||
|
@ -121,7 +110,6 @@ void nsAudioStream::Write(const void* aBuf, PRUint32 aCount)
|
|||
NS_ABORT_IF_FALSE(aCount % mChannels == 0,
|
||||
"Buffer size must be divisible by channel count");
|
||||
|
||||
mSamplesBuffered += aCount;
|
||||
PRUint32 offset = mBufferOverflow.Length();
|
||||
PRInt32 count = aCount + offset;
|
||||
|
||||
|
@ -214,13 +202,8 @@ 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));
|
||||
if (!mAudioHandle)
|
||||
return;
|
||||
}
|
||||
|
||||
// Write any remaining unwritten sound data in the overflow buffer
|
||||
if (!mBufferOverflow.IsEmpty()) {
|
||||
|
@ -235,69 +218,3 @@ void nsAudioStream::Drain()
|
|||
Shutdown();
|
||||
}
|
||||
}
|
||||
|
||||
void nsAudioStream::Pause()
|
||||
{
|
||||
if (mPaused)
|
||||
return;
|
||||
|
||||
// Save the elapsed playback time. Used to offset the wall-clock time
|
||||
// when resuming.
|
||||
mPauseTime = CurrentTimeInSeconds() - mStartTime;
|
||||
|
||||
mPaused = PR_TRUE;
|
||||
|
||||
if (!mAudioHandle)
|
||||
return;
|
||||
|
||||
int64_t bytes = 0;
|
||||
#if !defined(WIN32)
|
||||
sa_stream_get_position(static_cast<sa_stream_t*>(mAudioHandle), SA_POSITION_WRITE_SOFTWARE, &bytes);
|
||||
#endif
|
||||
mSavedPauseBytes = bytes;
|
||||
|
||||
sa_stream_pause(static_cast<sa_stream_t*>(mAudioHandle));
|
||||
}
|
||||
|
||||
void nsAudioStream::Resume()
|
||||
{
|
||||
if (!mPaused)
|
||||
return;
|
||||
|
||||
// Reset the start time to the current time offset backwards by the
|
||||
// elapsed time saved when the stream paused.
|
||||
mStartTime = CurrentTimeInSeconds() - mPauseTime;
|
||||
|
||||
mPaused = PR_FALSE;
|
||||
|
||||
if (!mAudioHandle)
|
||||
return;
|
||||
|
||||
sa_stream_resume(static_cast<sa_stream_t*>(mAudioHandle));
|
||||
|
||||
#if !defined(WIN32)
|
||||
mPauseBytes += mSavedPauseBytes;
|
||||
#endif
|
||||
}
|
||||
|
||||
double nsAudioStream::GetTime()
|
||||
{
|
||||
// If the audio backend failed to open, emulate the current playback
|
||||
// position using the system clock.
|
||||
if (!mAudioHandle) {
|
||||
if (mPaused) {
|
||||
return mPauseTime;
|
||||
}
|
||||
float curTime = CurrentTimeInSeconds() - mStartTime;
|
||||
float maxTime = float(mSamplesBuffered) / mRate / mChannels;
|
||||
return NS_MIN(curTime, maxTime);
|
||||
}
|
||||
|
||||
int64_t bytes = 0;
|
||||
#if defined(WIN32)
|
||||
sa_stream_get_position(static_cast<sa_stream_t*>(mAudioHandle), SA_POSITION_WRITE_HARDWARE, &bytes);
|
||||
#else
|
||||
sa_stream_get_position(static_cast<sa_stream_t*>(mAudioHandle), SA_POSITION_WRITE_SOFTWARE, &bytes);
|
||||
#endif
|
||||
return double(bytes + mPauseBytes) / (sizeof(short) * mChannels * mRate);
|
||||
}
|
||||
|
|
|
@ -48,11 +48,6 @@ void nsChannelReader::Cancel()
|
|||
mStream.Cancel();
|
||||
}
|
||||
|
||||
PRUint32 nsChannelReader::Available()
|
||||
{
|
||||
return mStream.Available();
|
||||
}
|
||||
|
||||
OggPlayErrorCode nsChannelReader::initialise(int aBlock)
|
||||
{
|
||||
return E_OGGPLAY_OK;
|
||||
|
|
|
@ -71,7 +71,6 @@ public:
|
|||
virtual nsresult Read(char* aBuffer, PRUint32 aCount, PRUint32* aBytes);
|
||||
virtual nsresult Seek(PRInt32 aWhence, PRInt64 aOffset);
|
||||
virtual PRInt64 Tell();
|
||||
virtual PRUint32 Available();
|
||||
virtual void Cancel();
|
||||
virtual nsIPrincipal* GetCurrentPrincipal();
|
||||
virtual void Suspend();
|
||||
|
@ -186,20 +185,6 @@ PRInt64 nsDefaultStreamStrategy::Tell()
|
|||
return mPosition;
|
||||
}
|
||||
|
||||
PRUint32 nsDefaultStreamStrategy::Available()
|
||||
{
|
||||
// The request pulls from the pipe, not the channels input
|
||||
// stream. This allows calling from any thread as the pipe is
|
||||
// threadsafe.
|
||||
nsAutoLock lock(mLock);
|
||||
if (!mPipeInput)
|
||||
return 0;
|
||||
|
||||
PRUint32 count = 0;
|
||||
mPipeInput->Available(&count);
|
||||
return count;
|
||||
}
|
||||
|
||||
void nsDefaultStreamStrategy::Cancel()
|
||||
{
|
||||
if (mListener)
|
||||
|
@ -239,7 +224,6 @@ public:
|
|||
virtual nsresult Read(char* aBuffer, PRUint32 aCount, PRUint32* aBytes);
|
||||
virtual nsresult Seek(PRInt32 aWhence, PRInt64 aOffset);
|
||||
virtual PRInt64 Tell();
|
||||
virtual PRUint32 Available();
|
||||
virtual nsIPrincipal* GetCurrentPrincipal();
|
||||
virtual void Suspend();
|
||||
virtual void Resume();
|
||||
|
@ -419,17 +403,6 @@ PRInt64 nsFileStreamStrategy::Tell()
|
|||
return offset;
|
||||
}
|
||||
|
||||
PRUint32 nsFileStreamStrategy::Available()
|
||||
{
|
||||
nsAutoLock lock(mLock);
|
||||
if (!mInput)
|
||||
return 0;
|
||||
|
||||
PRUint32 count = 0;
|
||||
mInput->Available(&count);
|
||||
return count;
|
||||
}
|
||||
|
||||
nsIPrincipal* nsFileStreamStrategy::GetCurrentPrincipal()
|
||||
{
|
||||
return mPrincipal;
|
||||
|
@ -463,7 +436,6 @@ public:
|
|||
virtual nsresult Read(char* aBuffer, PRUint32 aCount, PRUint32* aBytes);
|
||||
virtual nsresult Seek(PRInt32 aWhence, PRInt64 aOffset);
|
||||
virtual PRInt64 Tell();
|
||||
virtual PRUint32 Available();
|
||||
virtual void Cancel();
|
||||
virtual nsIPrincipal* GetCurrentPrincipal();
|
||||
virtual void Suspend();
|
||||
|
@ -763,12 +735,13 @@ nsresult nsHttpStreamStrategy::Seek(PRInt32 aWhence, PRInt64 aOffset)
|
|||
bytesRead += bytes;
|
||||
} while (bytesRead != bytesAhead);
|
||||
|
||||
// We don't need to notify the decoder here that we seeked. It will
|
||||
// look like we just read ahead a bit. In fact, we mustn't tell
|
||||
// the decoder that we seeked, since the seek notification might
|
||||
// race with the "data downloaded" notification after the data was
|
||||
// written into the pipe, so that the seek notification
|
||||
// happens *first*, hopelessly confusing the decoder.
|
||||
// We don't need to notify the decoder here that we seeked, just that
|
||||
// we read ahead. In fact, we mustn't tell the decoder that we seeked,
|
||||
// since the seek notification might race with the "data downloaded"
|
||||
// notification after the data was written into the pipe, so that the
|
||||
// seek notification happens *first*, hopelessly confusing the
|
||||
// decoder.
|
||||
mDecoder->NotifyBytesConsumed(bytesRead);
|
||||
return rv;
|
||||
}
|
||||
}
|
||||
|
@ -795,20 +768,6 @@ PRInt64 nsHttpStreamStrategy::Tell()
|
|||
return mAtEOF ? mDecoder->GetStatistics().mTotalBytes : mPosition;
|
||||
}
|
||||
|
||||
PRUint32 nsHttpStreamStrategy::Available()
|
||||
{
|
||||
// The request pulls from the pipe, not the channels input
|
||||
// stream. This allows calling from any thread as the pipe is
|
||||
// threadsafe.
|
||||
nsAutoLock lock(mLock);
|
||||
if (!mPipeInput)
|
||||
return 0;
|
||||
|
||||
PRUint32 count = 0;
|
||||
mPipeInput->Available(&count);
|
||||
return count;
|
||||
}
|
||||
|
||||
void nsHttpStreamStrategy::Cancel()
|
||||
{
|
||||
mCancelled = PR_TRUE;
|
||||
|
@ -909,11 +868,6 @@ PRInt64 nsMediaStream::Tell()
|
|||
return mStreamStrategy->Tell();
|
||||
}
|
||||
|
||||
PRUint32 nsMediaStream::Available()
|
||||
{
|
||||
return mStreamStrategy->Available();
|
||||
}
|
||||
|
||||
void nsMediaStream::Cancel()
|
||||
{
|
||||
NS_ASSERTION(NS_IsMainThread(),
|
||||
|
|
|
@ -54,9 +54,13 @@
|
|||
// Maximum number of seconds to wait when buffering.
|
||||
#define BUFFERING_TIMEOUT 3
|
||||
|
||||
// The number of seconds of buffer data before buffering happens
|
||||
// based on current playback rate.
|
||||
#define BUFFERING_SECONDS_LOW_WATER_MARK 1
|
||||
// Duration the playback loop will sleep after refilling the backend's audio
|
||||
// buffers. The loop's goal is to keep AUDIO_BUFFER_LENGTH milliseconds of
|
||||
// audio buffered to allow time to refill before the backend underruns.
|
||||
// Should be a multiple of 10 to deal with poor timer granularity on some
|
||||
// platforms.
|
||||
#define AUDIO_BUFFER_WAKEUP 100
|
||||
#define AUDIO_BUFFER_LENGTH (2 * AUDIO_BUFFER_WAKEUP)
|
||||
|
||||
// Magic values that identify RIFF chunks we're interested in.
|
||||
#define RIFF_CHUNK_MAGIC 0x52494646
|
||||
|
@ -74,13 +78,21 @@
|
|||
// extended (for non-PCM encodings), but we skip any extended data.
|
||||
#define WAVE_FORMAT_CHUNK_SIZE 16
|
||||
|
||||
// Size of format chunk including RIFF chunk header.
|
||||
#define WAVE_FORMAT_SIZE (RIFF_CHUNK_HEADER_SIZE + WAVE_FORMAT_CHUNK_SIZE)
|
||||
|
||||
// PCM encoding type from format chunk. Linear PCM is the only encoding
|
||||
// supported by nsAudioStream.
|
||||
#define WAVE_FORMAT_ENCODING_PCM 1
|
||||
|
||||
enum State {
|
||||
STATE_LOADING_METADATA,
|
||||
STATE_BUFFERING,
|
||||
STATE_PLAYING,
|
||||
STATE_SEEKING,
|
||||
STATE_PAUSED,
|
||||
STATE_ENDED,
|
||||
STATE_ERROR,
|
||||
STATE_SHUTDOWN
|
||||
};
|
||||
|
||||
/*
|
||||
A single nsWaveStateMachine instance is owned by the decoder, created
|
||||
on-demand at load time. Upon creation, the decoder immediately
|
||||
|
@ -101,17 +113,6 @@
|
|||
class nsWaveStateMachine : public nsRunnable
|
||||
{
|
||||
public:
|
||||
enum State {
|
||||
STATE_LOADING_METADATA,
|
||||
STATE_BUFFERING,
|
||||
STATE_PLAYING,
|
||||
STATE_SEEKING,
|
||||
STATE_PAUSED,
|
||||
STATE_ENDED,
|
||||
STATE_ERROR,
|
||||
STATE_SHUTDOWN
|
||||
};
|
||||
|
||||
nsWaveStateMachine(nsWaveDecoder* aDecoder, nsMediaStream* aStream,
|
||||
PRUint32 aBufferWaitTime, float aInitialVolume);
|
||||
~nsWaveStateMachine();
|
||||
|
@ -148,18 +149,9 @@ public:
|
|||
// Returns true if the state machine has reached the end of playback. Threadsafe.
|
||||
PRBool IsEnded();
|
||||
|
||||
// Called by the decoder to indicate that the media stream has closed.
|
||||
// aAtEnd is true if we read to the end of the file.
|
||||
void StreamEnded(PRBool aAtEnd);
|
||||
|
||||
// Main state machine loop. Runs forever, until shutdown state is reached.
|
||||
NS_IMETHOD Run();
|
||||
|
||||
// Called by the decoder when the SeekStarted event runs. This ensures
|
||||
// the current time offset of the state machine is updated to the new seek
|
||||
// position at the appropriate time.
|
||||
void UpdateTimeOffset(float aTime);
|
||||
|
||||
// Called by the decoder, on the main thread.
|
||||
nsMediaDecoder::Statistics GetStatistics();
|
||||
|
||||
|
@ -177,13 +169,19 @@ public:
|
|||
// Called by the main thread only
|
||||
nsHTMLMediaElement::NextFrameStatus GetNextFrameStatus();
|
||||
|
||||
private:
|
||||
// Clear the flag indicating that a playback position change event is
|
||||
// currently queued and return the current time. This is called from the
|
||||
// main thread.
|
||||
float GetTimeForPositionChange();
|
||||
|
||||
private:
|
||||
// Returns PR_TRUE if we're in shutdown state. Threadsafe.
|
||||
PRBool IsShutdown();
|
||||
|
||||
// Reads from the media stream. Returns PR_FALSE on failure or EOF.
|
||||
PRBool ReadAll(char* aBuf, PRUint32 aSize);
|
||||
// Reads from the media stream. Returns PR_FALSE on failure or EOF. If
|
||||
// aBytesRead is non-null, the number of bytes read will be returned via
|
||||
// this.
|
||||
PRBool ReadAll(char* aBuf, PRInt64 aSize, PRInt64* aBytesRead);
|
||||
|
||||
// Change the current state and wake the playback thread if it is waiting
|
||||
// on mMonitor. Used by public member functions called from both threads,
|
||||
|
@ -200,6 +198,11 @@ private:
|
|||
// the stream data is a RIFF bitstream containing WAVE data.
|
||||
PRBool LoadRIFFChunk();
|
||||
|
||||
// Read forward in the stream until aWantedChunk is found. Return chunk
|
||||
// size in aChunkSize. aChunkSize will not be rounded up if the chunk
|
||||
// size is odd.
|
||||
PRBool ScanForwardUntil(PRUint32 aWantedChunk, PRUint32* aChunkSize);
|
||||
|
||||
// Scan forward in the stream looking for the WAVE format chunk. If
|
||||
// found, parse and validate required metadata, then use it to set
|
||||
// mSampleRate, mChannels, mSampleSize, and mSampleFormat.
|
||||
|
@ -210,29 +213,40 @@ private:
|
|||
// mWavePCMOffset.
|
||||
PRBool FindDataOffset();
|
||||
|
||||
// Return the length of the PCM data.
|
||||
PRInt64 GetDataLength();
|
||||
|
||||
// Fire a PlaybackPositionChanged event. If aCoalesce is true and a
|
||||
// PlaybackPositionChanged event is already pending, an event is not
|
||||
// fired.
|
||||
void FirePositionChanged(PRBool aCoalesce);
|
||||
|
||||
// Returns the number of seconds that aBytes represents based on the
|
||||
// current audio parameters. e.g. 176400 bytes is 1 second at 16-bit
|
||||
// stereo 44.1kHz.
|
||||
float BytesToTime(PRUint32 aBytes) const
|
||||
float BytesToTime(PRInt64 aBytes) const
|
||||
{
|
||||
NS_ABORT_IF_FALSE(mMetadataValid, "Requires valid metadata");
|
||||
NS_ABORT_IF_FALSE(aBytes >= 0, "Must be >= 0");
|
||||
return float(aBytes) / mSampleRate / mSampleSize;
|
||||
}
|
||||
|
||||
// Returns the number of bytes that aTime represents based on the current
|
||||
// audio parameters. e.g. 1 second is 176400 bytes at 16-bit stereo
|
||||
// 44.1kHz.
|
||||
PRUint32 TimeToBytes(float aTime) const
|
||||
PRInt64 TimeToBytes(float aTime) const
|
||||
{
|
||||
NS_ABORT_IF_FALSE(mMetadataValid, "Requires valid metadata");
|
||||
return PRUint32(aTime * mSampleRate * mSampleSize);
|
||||
NS_ABORT_IF_FALSE(aTime >= 0.0, "Must be >= 0");
|
||||
return RoundDownToSample(PRInt64(aTime * mSampleRate * mSampleSize));
|
||||
}
|
||||
|
||||
// Rounds aBytes down to the nearest complete sample. Assumes beginning
|
||||
// of byte range is already sample aligned by caller.
|
||||
PRUint32 RoundDownToSample(PRUint32 aBytes) const
|
||||
PRInt64 RoundDownToSample(PRInt64 aBytes) const
|
||||
{
|
||||
NS_ABORT_IF_FALSE(mMetadataValid, "Requires valid metadata");
|
||||
NS_ABORT_IF_FALSE(aBytes >= 0, "Must be >= 0");
|
||||
return aBytes - (aBytes % mSampleSize);
|
||||
}
|
||||
|
||||
|
@ -258,16 +272,13 @@ private:
|
|||
// Maximum time in milliseconds to spend waiting for data during buffering.
|
||||
PRUint32 mBufferingWait;
|
||||
|
||||
// Maximum number of bytes to wait for during buffering.
|
||||
PRUint32 mBufferingBytes;
|
||||
|
||||
// Machine time that buffering began, used with mBufferingWait to time out
|
||||
// buffering.
|
||||
PRIntervalTime mBufferingStart;
|
||||
|
||||
// Maximum number of bytes mAudioStream buffers internally. Used to
|
||||
// calculate next wakeup time after refilling audio buffers.
|
||||
PRUint32 mAudioBufferSize;
|
||||
// Download position where we should stop buffering. Only accessed
|
||||
// in the decoder thread.
|
||||
PRInt64 mBufferingEndOffset;
|
||||
|
||||
/*
|
||||
Metadata extracted from the WAVE header. Used to initialize the audio
|
||||
|
@ -289,11 +300,11 @@ private:
|
|||
|
||||
// Size of PCM data stored in the WAVE as reported by the data chunk in
|
||||
// the media.
|
||||
PRUint32 mWaveLength;
|
||||
PRInt64 mWaveLength;
|
||||
|
||||
// Start offset of the PCM data in the media stream. Extends mWaveLength
|
||||
// bytes.
|
||||
PRUint32 mWavePCMOffset;
|
||||
PRInt64 mWavePCMOffset;
|
||||
|
||||
/*
|
||||
All member variables following this comment are accessed by both
|
||||
|
@ -326,15 +337,9 @@ private:
|
|||
// Volume that the audio backend will be initialized with.
|
||||
float mInitialVolume;
|
||||
|
||||
// Time position (in seconds) to offset current time from audio stream.
|
||||
// Set when the seek started event runs and when the stream is closed
|
||||
// during shutdown.
|
||||
float mTimeOffset;
|
||||
|
||||
// Set when StreamEnded has fired to indicate that we should not expect
|
||||
// any more data from mStream than what is already buffered (i.e. what
|
||||
// Available() reports).
|
||||
PRPackedBool mExpectMoreData;
|
||||
// Playback position (in bytes), updated as the playback loop runs and
|
||||
// upon seeking.
|
||||
PRInt64 mTimeOffset;
|
||||
|
||||
// Time position (in seconds) to seek to. Set by Seek(float).
|
||||
float mSeekTime;
|
||||
|
@ -343,6 +348,12 @@ private:
|
|||
// mChannels, mSampleSize, mSampleFormat, mWaveLength, mWavePCMOffset must
|
||||
// check this flag before assuming the values are valid.
|
||||
PRPackedBool mMetadataValid;
|
||||
|
||||
// True if an event to notify about a change in the playback position has
|
||||
// been queued, but not yet run. It is set to false when the event is
|
||||
// run. This allows coalescing of these events as they can be produced
|
||||
// many times per second.
|
||||
PRPackedBool mPositionChangeQueued;
|
||||
};
|
||||
|
||||
nsWaveStateMachine::nsWaveStateMachine(nsWaveDecoder* aDecoder, nsMediaStream* aStream,
|
||||
|
@ -350,9 +361,8 @@ nsWaveStateMachine::nsWaveStateMachine(nsWaveDecoder* aDecoder, nsMediaStream* a
|
|||
: mDecoder(aDecoder),
|
||||
mStream(aStream),
|
||||
mBufferingWait(aBufferWaitTime),
|
||||
mBufferingBytes(0),
|
||||
mBufferingStart(0),
|
||||
mAudioBufferSize(0),
|
||||
mBufferingEndOffset(0),
|
||||
mSampleRate(0),
|
||||
mChannels(0),
|
||||
mSampleSize(0),
|
||||
|
@ -366,10 +376,10 @@ nsWaveStateMachine::nsWaveStateMachine(nsWaveDecoder* aDecoder, nsMediaStream* a
|
|||
mDownloadPosition(0),
|
||||
mPlaybackPosition(0),
|
||||
mInitialVolume(aInitialVolume),
|
||||
mTimeOffset(0.0),
|
||||
mExpectMoreData(PR_TRUE),
|
||||
mTimeOffset(0),
|
||||
mSeekTime(0.0),
|
||||
mMetadataValid(PR_FALSE)
|
||||
mMetadataValid(PR_FALSE),
|
||||
mPositionChangeQueued(PR_FALSE)
|
||||
{
|
||||
mMonitor = nsAutoMonitor::NewMonitor("nsWaveStateMachine");
|
||||
mDownloadStatistics.Start(PR_IntervalNow());
|
||||
|
@ -449,14 +459,7 @@ nsWaveStateMachine::GetDuration()
|
|||
{
|
||||
nsAutoMonitor monitor(mMonitor);
|
||||
if (mMetadataValid) {
|
||||
PRUint32 length = mWaveLength;
|
||||
// If the decoder has a valid content length, and it's shorter than the
|
||||
// expected length of the PCM data, calculate the playback duration from
|
||||
// the content length rather than the expected PCM data length.
|
||||
if (mTotalBytes >= 0 && mTotalBytes - mWavePCMOffset < length) {
|
||||
length = mTotalBytes - mWavePCMOffset;
|
||||
}
|
||||
return BytesToTime(length);
|
||||
return BytesToTime(GetDataLength());
|
||||
}
|
||||
return std::numeric_limits<float>::quiet_NaN();
|
||||
}
|
||||
|
@ -465,11 +468,10 @@ float
|
|||
nsWaveStateMachine::GetCurrentTime()
|
||||
{
|
||||
nsAutoMonitor monitor(mMonitor);
|
||||
double time = 0.0;
|
||||
if (mAudioStream) {
|
||||
time = mAudioStream->GetTime();
|
||||
if (mMetadataValid) {
|
||||
return BytesToTime(mTimeOffset);
|
||||
}
|
||||
return float(time + mTimeOffset);
|
||||
return std::numeric_limits<float>::quiet_NaN();
|
||||
}
|
||||
|
||||
PRBool
|
||||
|
@ -486,30 +488,25 @@ nsWaveStateMachine::IsEnded()
|
|||
return mState == STATE_ENDED || mState == STATE_SHUTDOWN;
|
||||
}
|
||||
|
||||
void
|
||||
nsWaveStateMachine::StreamEnded(PRBool aAtEnd)
|
||||
{
|
||||
nsAutoMonitor monitor(mMonitor);
|
||||
mExpectMoreData = PR_FALSE;
|
||||
|
||||
// If we know the content length, set the bytes downloaded to this
|
||||
// so the final progress event gets the correct final value.
|
||||
if (mTotalBytes >= 0) {
|
||||
mDownloadPosition = mTotalBytes;
|
||||
}
|
||||
}
|
||||
|
||||
nsHTMLMediaElement::NextFrameStatus
|
||||
nsWaveStateMachine::GetNextFrameStatus()
|
||||
{
|
||||
nsAutoMonitor monitor(mMonitor);
|
||||
if (mPlaybackPosition < mDownloadPosition)
|
||||
return nsHTMLMediaElement::NEXT_FRAME_AVAILABLE;
|
||||
if (mState == STATE_BUFFERING)
|
||||
return nsHTMLMediaElement::NEXT_FRAME_UNAVAILABLE_BUFFERING;
|
||||
if (mPlaybackPosition < mDownloadPosition)
|
||||
return nsHTMLMediaElement::NEXT_FRAME_AVAILABLE;
|
||||
return nsHTMLMediaElement::NEXT_FRAME_UNAVAILABLE;
|
||||
}
|
||||
|
||||
float
|
||||
nsWaveStateMachine::GetTimeForPositionChange()
|
||||
{
|
||||
nsAutoMonitor monitor(mMonitor);
|
||||
mPositionChangeQueued = PR_FALSE;
|
||||
return GetCurrentTime();
|
||||
}
|
||||
|
||||
NS_IMETHODIMP
|
||||
nsWaveStateMachine::Run()
|
||||
{
|
||||
|
@ -552,10 +549,12 @@ nsWaveStateMachine::Run()
|
|||
case STATE_BUFFERING: {
|
||||
PRIntervalTime now = PR_IntervalNow();
|
||||
if ((PR_IntervalToMilliseconds(now - mBufferingStart) < mBufferingWait) &&
|
||||
mStream->Available() < mBufferingBytes) {
|
||||
LOG(PR_LOG_DEBUG, ("Buffering data until %d bytes or %d milliseconds\n",
|
||||
mBufferingBytes - mStream->Available(),
|
||||
mBufferingWait - (now - mBufferingStart)));
|
||||
mDownloadPosition < mBufferingEndOffset &&
|
||||
(mTotalBytes < 0 || mDownloadPosition < mTotalBytes)) {
|
||||
LOG(PR_LOG_DEBUG,
|
||||
("In buffering: buffering data until %d bytes available or %d milliseconds\n",
|
||||
PRUint32(mBufferingEndOffset - mDownloadPosition),
|
||||
mBufferingWait - (PR_IntervalToMilliseconds(now - mBufferingStart))));
|
||||
monitor.Wait(PR_MillisecondsToInterval(1000));
|
||||
} else {
|
||||
ChangeState(mNextState);
|
||||
|
@ -570,86 +569,102 @@ nsWaveStateMachine::Run()
|
|||
case STATE_PLAYING: {
|
||||
if (!mAudioStream) {
|
||||
OpenAudioStream();
|
||||
} else {
|
||||
mAudioStream->Resume();
|
||||
if (!mAudioStream) {
|
||||
mState = STATE_ERROR;
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
if (mStream->Available() < mSampleSize) {
|
||||
if (mExpectMoreData) {
|
||||
// Buffer until mBufferingWait milliseconds of data is available.
|
||||
mBufferingBytes = TimeToBytes(float(mBufferingWait) / 1000.0);
|
||||
PRUint32 startTime = PR_IntervalToMilliseconds(PR_IntervalNow());
|
||||
startTime -= AUDIO_BUFFER_LENGTH;
|
||||
PRIntervalTime lastWakeup = PR_MillisecondsToInterval(startTime);
|
||||
|
||||
do {
|
||||
PRIntervalTime now = PR_IntervalNow();
|
||||
PRInt32 sleepTime = PR_IntervalToMilliseconds(now - lastWakeup);
|
||||
lastWakeup = now;
|
||||
|
||||
// We aim to have AUDIO_BUFFER_LENGTH milliseconds of audio
|
||||
// buffered, but only sleep for AUDIO_BUFFER_WAKEUP milliseconds
|
||||
// (waking early to refill before the backend underruns). Since we
|
||||
// wake early, we only buffer sleepTime milliseconds of audio since
|
||||
// there is still AUDIO_BUFFER_LENGTH - sleepTime milliseconds of
|
||||
// audio buffered.
|
||||
PRInt32 targetTime = AUDIO_BUFFER_LENGTH;
|
||||
if (sleepTime < targetTime) {
|
||||
targetTime = sleepTime;
|
||||
}
|
||||
|
||||
PRInt64 len = TimeToBytes(float(targetTime) / 1000.0f);
|
||||
|
||||
PRInt64 leftToPlay = GetDataLength() - mTimeOffset;
|
||||
if (leftToPlay <= len) {
|
||||
len = leftToPlay;
|
||||
ChangeState(STATE_ENDED);
|
||||
}
|
||||
|
||||
PRInt64 available = mDownloadPosition - mPlaybackPosition;
|
||||
|
||||
// don't buffer if we're at the end of the stream
|
||||
if (mState != STATE_ENDED && available < len) {
|
||||
mBufferingStart = PR_IntervalNow();
|
||||
mBufferingEndOffset = mDownloadPosition + TimeToBytes(float(mBufferingWait) / 1000.0f);
|
||||
mNextState = mState;
|
||||
ChangeState(STATE_BUFFERING);
|
||||
|
||||
nsCOMPtr<nsIRunnable> event =
|
||||
NS_NEW_RUNNABLE_METHOD(nsWaveDecoder, mDecoder, UpdateReadyStateForData);
|
||||
NS_DispatchToMainThread(event, NS_DISPATCH_NORMAL);
|
||||
|
||||
ChangeState(STATE_BUFFERING);
|
||||
} else {
|
||||
// Media stream has ended and there is less data available than a
|
||||
// single sample so end playback.
|
||||
ChangeState(STATE_ENDED);
|
||||
break;
|
||||
}
|
||||
} else {
|
||||
// Assuming enough data is available from the network, we aim to
|
||||
// completely fill the audio backend's buffers with data. This
|
||||
// allows us plenty of time to wake up and refill the buffers
|
||||
// without an underrun occurring.
|
||||
PRUint32 sampleSize = mSampleFormat == nsAudioStream::FORMAT_U8 ? 1 : 2;
|
||||
PRUint32 len = RoundDownToSample(NS_MIN(mStream->Available(),
|
||||
PRUint32(mAudioStream->Available() * sampleSize)));
|
||||
if (len) {
|
||||
nsAutoArrayPtr<char> buf(new char[len]);
|
||||
PRUint32 got = 0;
|
||||
|
||||
if (len > 0) {
|
||||
nsAutoArrayPtr<char> buf(new char[size_t(len)]);
|
||||
PRInt64 got = 0;
|
||||
|
||||
monitor.Exit();
|
||||
if (NS_FAILED(mStream->Read(buf.get(), len, &got))) {
|
||||
NS_WARNING("Stream read failed");
|
||||
}
|
||||
PRBool ok = ReadAll(buf.get(), len, &got);
|
||||
PRInt64 streamPos = mStream->Tell();
|
||||
monitor.Enter();
|
||||
|
||||
if (got == 0) {
|
||||
// Reached EOF.
|
||||
if (!ok) {
|
||||
ChangeState(STATE_ENDED);
|
||||
if (got == 0) {
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
// If we got less data than requested, go ahead and write what we
|
||||
// got to the audio hardware. It's unlikely that this can happen
|
||||
// since we never attempt to read more data than what is already
|
||||
// buffered.
|
||||
len = RoundDownToSample(got);
|
||||
|
||||
// Calculate difference between the current media stream position
|
||||
// and the expected end of the PCM data.
|
||||
PRInt64 endDelta = mWavePCMOffset + mWaveLength - mStream->Tell();
|
||||
PRInt64 endDelta = mWavePCMOffset + mWaveLength - streamPos;
|
||||
if (endDelta < 0) {
|
||||
// Read past the end of PCM data. Adjust len to avoid playing
|
||||
// Read past the end of PCM data. Adjust got to avoid playing
|
||||
// back trailing data.
|
||||
len -= -endDelta;
|
||||
if (RoundDownToSample(len) != len) {
|
||||
NS_WARNING("PCM data does not end with complete sample");
|
||||
len = RoundDownToSample(len);
|
||||
}
|
||||
got -= -endDelta;
|
||||
ChangeState(STATE_ENDED);
|
||||
}
|
||||
|
||||
PRUint32 lengthInSamples = len;
|
||||
if (mSampleFormat == nsAudioStream::FORMAT_S16_LE) {
|
||||
lengthInSamples /= sizeof(short);
|
||||
}
|
||||
mAudioStream->Write(buf.get(), lengthInSamples);
|
||||
monitor.Enter();
|
||||
if (mState == STATE_ENDED) {
|
||||
got = RoundDownToSample(got);
|
||||
}
|
||||
|
||||
// To avoid waking up too frequently to top up these buffers,
|
||||
// calculate the duration of the currently buffered data and sleep
|
||||
// until most of the buffered data has been consumed. We can't
|
||||
// sleep for the entire duration because we might not wake up in
|
||||
// time to refill the buffers, causing an underrun. To avoid this,
|
||||
// wake up when approximately half the buffered data has been
|
||||
// consumed. This could be made smarter, but at least avoids waking
|
||||
// up frequently to perform small buffer refills.
|
||||
float nextWakeup = BytesToTime(mAudioBufferSize - mAudioStream->Available() * sizeof(short)) * 1000.0 / 2.0;
|
||||
monitor.Wait(PR_MillisecondsToInterval(PRUint32(nextWakeup)));
|
||||
PRUint32 sampleSize = mSampleFormat == nsAudioStream::FORMAT_U8 ? 1 : 2;
|
||||
NS_ABORT_IF_FALSE(got % sampleSize == 0, "Must write complete samples");
|
||||
PRUint32 lengthInSamples = got / sampleSize;
|
||||
|
||||
monitor.Exit();
|
||||
mAudioStream->Write(buf.get(), lengthInSamples);
|
||||
monitor.Enter();
|
||||
|
||||
mTimeOffset += got;
|
||||
FirePositionChanged(PR_FALSE);
|
||||
}
|
||||
|
||||
if (mState == STATE_PLAYING) {
|
||||
monitor.Wait(PR_MillisecondsToInterval(AUDIO_BUFFER_WAKEUP));
|
||||
}
|
||||
} while (mState == STATE_PLAYING);
|
||||
break;
|
||||
}
|
||||
|
||||
|
@ -672,7 +687,9 @@ nsWaveStateMachine::Run()
|
|||
|
||||
// Calculate relative offset within PCM data.
|
||||
PRInt64 position = RoundDownToSample(TimeToBytes(seekTime));
|
||||
NS_ABORT_IF_FALSE(position >= 0 && position <= mWaveLength, "Invalid seek position");
|
||||
NS_ABORT_IF_FALSE(position >= 0 && position <= GetDataLength(), "Invalid seek position");
|
||||
|
||||
mTimeOffset = position;
|
||||
|
||||
// If position==0, instead of seeking to position+mWavePCMOffset,
|
||||
// we want to first seek to 0 before seeking to
|
||||
|
@ -704,12 +721,6 @@ nsWaveStateMachine::Run()
|
|||
break;
|
||||
}
|
||||
|
||||
monitor.Exit();
|
||||
nsCOMPtr<nsIRunnable> stopEvent =
|
||||
NS_NEW_RUNNABLE_METHOD(nsWaveDecoder, mDecoder, SeekingStopped);
|
||||
NS_DispatchToMainThread(stopEvent, NS_DISPATCH_SYNC);
|
||||
monitor.Enter();
|
||||
|
||||
if (mState == STATE_SEEKING && mSeekTime == seekTime) {
|
||||
// Special case: if a seek was requested during metadata load,
|
||||
// mNextState will have been clobbered. This can only happen when
|
||||
|
@ -722,29 +733,30 @@ nsWaveStateMachine::Run()
|
|||
}
|
||||
ChangeState(nextState);
|
||||
}
|
||||
|
||||
FirePositionChanged(PR_TRUE);
|
||||
|
||||
monitor.Exit();
|
||||
nsCOMPtr<nsIRunnable> stopEvent =
|
||||
NS_NEW_RUNNABLE_METHOD(nsWaveDecoder, mDecoder, SeekingStopped);
|
||||
NS_DispatchToMainThread(stopEvent, NS_DISPATCH_SYNC);
|
||||
monitor.Enter();
|
||||
}
|
||||
break;
|
||||
|
||||
case STATE_PAUSED:
|
||||
if (mAudioStream) {
|
||||
mAudioStream->Pause();
|
||||
}
|
||||
monitor.Wait();
|
||||
break;
|
||||
|
||||
case STATE_ENDED:
|
||||
FirePositionChanged(PR_TRUE);
|
||||
|
||||
if (mAudioStream) {
|
||||
monitor.Exit();
|
||||
mAudioStream->Drain();
|
||||
monitor.Enter();
|
||||
mTimeOffset += mAudioStream->GetTime();
|
||||
}
|
||||
|
||||
// Dispose the audio stream early (before SHUTDOWN) so that
|
||||
// GetCurrentTime no longer attempts to query the audio backend for
|
||||
// stream time.
|
||||
CloseAudioStream();
|
||||
|
||||
if (mState != STATE_SHUTDOWN) {
|
||||
nsCOMPtr<nsIRunnable> event =
|
||||
NS_NEW_RUNNABLE_METHOD(nsWaveDecoder, mDecoder, PlaybackEnded);
|
||||
|
@ -765,9 +777,6 @@ nsWaveStateMachine::Run()
|
|||
break;
|
||||
|
||||
case STATE_SHUTDOWN:
|
||||
if (mAudioStream) {
|
||||
mTimeOffset += mAudioStream->GetTime();
|
||||
}
|
||||
CloseAudioStream();
|
||||
return NS_OK;
|
||||
}
|
||||
|
@ -776,20 +785,61 @@ nsWaveStateMachine::Run()
|
|||
return NS_OK;
|
||||
}
|
||||
|
||||
void
|
||||
nsWaveStateMachine::UpdateTimeOffset(float aTime)
|
||||
#if defined(DEBUG)
|
||||
static PRBool
|
||||
IsValidStateTransition(State aStartState, State aEndState)
|
||||
{
|
||||
nsAutoMonitor monitor(mMonitor);
|
||||
mTimeOffset = NS_MIN(aTime, GetDuration());
|
||||
if (mTimeOffset < 0.0) {
|
||||
mTimeOffset = 0.0;
|
||||
if (aEndState == STATE_SHUTDOWN) {
|
||||
return PR_TRUE;
|
||||
}
|
||||
|
||||
if (aStartState == aEndState) {
|
||||
LOG(PR_LOG_WARNING, ("Transition to current state requested"));
|
||||
return PR_TRUE;
|
||||
}
|
||||
|
||||
switch (aStartState) {
|
||||
case STATE_LOADING_METADATA:
|
||||
if (aEndState == STATE_PLAYING || aEndState == STATE_SEEKING ||
|
||||
aEndState == STATE_PAUSED || aEndState == STATE_ERROR)
|
||||
return PR_TRUE;
|
||||
break;
|
||||
case STATE_BUFFERING:
|
||||
if (aEndState == STATE_PLAYING || aEndState == STATE_PAUSED ||
|
||||
aEndState == STATE_SEEKING)
|
||||
return PR_TRUE;
|
||||
break;
|
||||
case STATE_PLAYING:
|
||||
if (aEndState == STATE_BUFFERING || aEndState == STATE_SEEKING ||
|
||||
aEndState == STATE_ENDED || aEndState == STATE_PAUSED)
|
||||
return PR_TRUE;
|
||||
break;
|
||||
case STATE_SEEKING:
|
||||
if (aEndState == STATE_PLAYING || aEndState == STATE_PAUSED)
|
||||
return PR_TRUE;
|
||||
break;
|
||||
case STATE_PAUSED:
|
||||
if (aEndState == STATE_PLAYING || aEndState == STATE_SEEKING)
|
||||
return PR_TRUE;
|
||||
break;
|
||||
case STATE_ENDED:
|
||||
case STATE_ERROR:
|
||||
case STATE_SHUTDOWN:
|
||||
break;
|
||||
}
|
||||
|
||||
LOG(PR_LOG_ERROR, ("Invalid state transition from %d to %d", aStartState, aEndState));
|
||||
return PR_FALSE;
|
||||
}
|
||||
#endif
|
||||
|
||||
void
|
||||
nsWaveStateMachine::ChangeState(State aState)
|
||||
{
|
||||
nsAutoMonitor monitor(mMonitor);
|
||||
#if defined(DEBUG)
|
||||
NS_ABORT_IF_FALSE(IsValidStateTransition(mState, aState), "Invalid state transition");
|
||||
#endif
|
||||
mState = aState;
|
||||
monitor.NotifyAll();
|
||||
}
|
||||
|
@ -805,7 +855,6 @@ nsWaveStateMachine::OpenAudioStream()
|
|||
"Attempting to initialize audio stream with invalid metadata");
|
||||
mAudioStream->Init(mChannels, mSampleRate, mSampleFormat);
|
||||
mAudioStream->SetVolume(mInitialVolume);
|
||||
mAudioBufferSize = mAudioStream->Available() * sizeof(short);
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -835,7 +884,8 @@ nsWaveStateMachine::GetStatistics()
|
|||
}
|
||||
|
||||
void
|
||||
nsWaveStateMachine::SetTotalBytes(PRInt64 aBytes) {
|
||||
nsWaveStateMachine::SetTotalBytes(PRInt64 aBytes)
|
||||
{
|
||||
nsAutoMonitor monitor(mMonitor);
|
||||
mTotalBytes = aBytes;
|
||||
}
|
||||
|
@ -913,18 +963,25 @@ nsWaveStateMachine::IsShutdown()
|
|||
}
|
||||
|
||||
PRBool
|
||||
nsWaveStateMachine::ReadAll(char* aBuf, PRUint32 aSize)
|
||||
nsWaveStateMachine::ReadAll(char* aBuf, PRInt64 aSize, PRInt64* aBytesRead = nsnull)
|
||||
{
|
||||
PRUint32 got = 0;
|
||||
if (aBytesRead) {
|
||||
*aBytesRead = 0;
|
||||
}
|
||||
do {
|
||||
PRUint32 read = 0;
|
||||
if (NS_FAILED(mStream->Read(aBuf + got, aSize - got, &read))) {
|
||||
NS_WARNING("Stream read failed");
|
||||
return PR_FALSE;
|
||||
}
|
||||
if (IsShutdown())
|
||||
if (IsShutdown() || read == 0) {
|
||||
return PR_FALSE;
|
||||
}
|
||||
got += read;
|
||||
if (aBytesRead) {
|
||||
*aBytesRead = got;
|
||||
}
|
||||
} while (got != aSize);
|
||||
return PR_TRUE;
|
||||
}
|
||||
|
@ -958,28 +1015,59 @@ nsWaveStateMachine::LoadRIFFChunk()
|
|||
return PR_TRUE;
|
||||
}
|
||||
|
||||
PRBool
|
||||
nsWaveStateMachine::ScanForwardUntil(PRUint32 aWantedChunk, PRUint32* aChunkSize)
|
||||
{
|
||||
NS_ENSURE_ARG_POINTER(aChunkSize);
|
||||
*aChunkSize = 0;
|
||||
|
||||
for (;;) {
|
||||
char chunkHeader[8];
|
||||
const char* p = chunkHeader;
|
||||
|
||||
if (!ReadAll(chunkHeader, sizeof(chunkHeader))) {
|
||||
return PR_FALSE;
|
||||
}
|
||||
|
||||
PRUint32 magic = ReadUint32BE(&p);
|
||||
PRUint32 size = ReadUint32LE(&p);
|
||||
|
||||
if (magic == aWantedChunk) {
|
||||
*aChunkSize = size;
|
||||
return PR_TRUE;
|
||||
}
|
||||
|
||||
// RIFF chunks are two-byte aligned, so round up if necessary.
|
||||
size += size % 2;
|
||||
|
||||
nsAutoArrayPtr<char> chunk(new char[size]);
|
||||
if (!ReadAll(chunk.get(), size)) {
|
||||
return PR_FALSE;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
PRBool
|
||||
nsWaveStateMachine::LoadFormatChunk()
|
||||
{
|
||||
PRUint32 rate, channels, sampleSize, sampleFormat;
|
||||
char waveFormat[WAVE_FORMAT_SIZE];
|
||||
PRUint32 fmtSize, rate, channels, sampleSize, sampleFormat;
|
||||
char waveFormat[WAVE_FORMAT_CHUNK_SIZE];
|
||||
const char* p = waveFormat;
|
||||
|
||||
// RIFF chunks are always word (two byte) aligned.
|
||||
NS_ABORT_IF_FALSE(mStream->Tell() % 2 == 0,
|
||||
"LoadFormatChunk called with unaligned stream");
|
||||
|
||||
// The "format" chunk may not directly follow the "riff" chunk, so skip
|
||||
// over any intermediate chunks.
|
||||
if (!ScanForwardUntil(FRMT_CHUNK_MAGIC, &fmtSize)) {
|
||||
return PR_FALSE;
|
||||
}
|
||||
|
||||
if (!ReadAll(waveFormat, sizeof(waveFormat))) {
|
||||
return PR_FALSE;
|
||||
}
|
||||
|
||||
if (ReadUint32BE(&p) != FRMT_CHUNK_MAGIC) {
|
||||
NS_WARNING("Expected format chunk");
|
||||
return PR_FALSE;
|
||||
}
|
||||
|
||||
PRUint32 fmtsize = ReadUint32LE(&p);
|
||||
|
||||
if (ReadUint16LE(&p) != WAVE_FORMAT_ENCODING_PCM) {
|
||||
NS_WARNING("WAVE is not uncompressed PCM, compressed encodings are not supported");
|
||||
return PR_FALSE;
|
||||
|
@ -1000,7 +1088,7 @@ nsWaveStateMachine::LoadFormatChunk()
|
|||
// extension size of 0 bytes. Be polite and handle this rather than
|
||||
// considering the file invalid. This code skips any extension of the
|
||||
// "format" chunk.
|
||||
if (fmtsize > WAVE_FORMAT_CHUNK_SIZE) {
|
||||
if (fmtSize > WAVE_FORMAT_CHUNK_SIZE) {
|
||||
char extLength[2];
|
||||
const char* p = extLength;
|
||||
|
||||
|
@ -1009,7 +1097,7 @@ nsWaveStateMachine::LoadFormatChunk()
|
|||
}
|
||||
|
||||
PRUint16 extra = ReadUint16LE(&p);
|
||||
if (fmtsize - (WAVE_FORMAT_CHUNK_SIZE + 2) != extra) {
|
||||
if (fmtSize - (WAVE_FORMAT_CHUNK_SIZE + 2) != extra) {
|
||||
NS_WARNING("Invalid extended format chunk size");
|
||||
return PR_FALSE;
|
||||
}
|
||||
|
@ -1062,35 +1150,10 @@ nsWaveStateMachine::FindDataOffset()
|
|||
|
||||
// The "data" chunk may not directly follow the "format" chunk, so skip
|
||||
// over any intermediate chunks.
|
||||
for (;;) {
|
||||
char chunkHeader[8];
|
||||
const char* p = chunkHeader;
|
||||
|
||||
if (!ReadAll(chunkHeader, sizeof(chunkHeader))) {
|
||||
if (!ScanForwardUntil(DATA_CHUNK_MAGIC, &length)) {
|
||||
return PR_FALSE;
|
||||
}
|
||||
|
||||
PRUint32 magic = ReadUint32BE(&p);
|
||||
|
||||
if (magic == DATA_CHUNK_MAGIC) {
|
||||
length = ReadUint32LE(&p);
|
||||
break;
|
||||
}
|
||||
|
||||
if (magic == FRMT_CHUNK_MAGIC) {
|
||||
LOG(PR_LOG_ERROR, ("Invalid WAVE: expected \"data\" chunk but found \"format\" chunk"));
|
||||
return PR_FALSE;
|
||||
}
|
||||
|
||||
PRUint32 size = ReadUint32LE(&p);
|
||||
size += size % 2;
|
||||
|
||||
nsAutoArrayPtr<char> chunk(new char[size]);
|
||||
if (!ReadAll(chunk.get(), size)) {
|
||||
return PR_FALSE;
|
||||
}
|
||||
}
|
||||
|
||||
offset = mStream->Tell();
|
||||
if (!offset) {
|
||||
NS_WARNING("PCM data offset not found");
|
||||
|
@ -1108,12 +1171,41 @@ nsWaveStateMachine::FindDataOffset()
|
|||
return PR_TRUE;
|
||||
}
|
||||
|
||||
PRInt64
|
||||
nsWaveStateMachine::GetDataLength()
|
||||
{
|
||||
NS_ABORT_IF_FALSE(mMetadataValid,
|
||||
"Attempting to initialize audio stream with invalid metadata");
|
||||
|
||||
PRInt64 length = mWaveLength;
|
||||
// If the decoder has a valid content length, and it's shorter than the
|
||||
// expected length of the PCM data, calculate the playback duration from
|
||||
// the content length rather than the expected PCM data length.
|
||||
if (mTotalBytes >= 0 && mTotalBytes - mWavePCMOffset < length) {
|
||||
length = mTotalBytes - mWavePCMOffset;
|
||||
}
|
||||
return length;
|
||||
}
|
||||
|
||||
void
|
||||
nsWaveStateMachine::FirePositionChanged(PRBool aCoalesce)
|
||||
{
|
||||
if (aCoalesce && mPositionChangeQueued) {
|
||||
return;
|
||||
}
|
||||
|
||||
mPositionChangeQueued = PR_TRUE;
|
||||
nsCOMPtr<nsIRunnable> event = NS_NEW_RUNNABLE_METHOD(nsWaveDecoder, mDecoder, PlaybackPositionChanged);
|
||||
NS_DispatchToMainThread(event, NS_DISPATCH_NORMAL);
|
||||
}
|
||||
|
||||
NS_IMPL_THREADSAFE_ISUPPORTS1(nsWaveDecoder, nsIObserver)
|
||||
|
||||
nsWaveDecoder::nsWaveDecoder()
|
||||
: mInitialVolume(1.0),
|
||||
mStream(nsnull),
|
||||
mTimeOffset(0.0),
|
||||
mCurrentTime(0.0),
|
||||
mEndedCurrentTime(0.0),
|
||||
mEndedDuration(std::numeric_limits<float>::quiet_NaN()),
|
||||
mEnded(PR_FALSE),
|
||||
|
@ -1149,10 +1241,7 @@ nsWaveDecoder::GetCurrentPrincipal()
|
|||
float
|
||||
nsWaveDecoder::GetCurrentTime()
|
||||
{
|
||||
if (mPlaybackStateMachine) {
|
||||
return mPlaybackStateMachine->GetCurrentTime();
|
||||
}
|
||||
return mEndedCurrentTime;
|
||||
return mCurrentTime;
|
||||
}
|
||||
|
||||
nsresult
|
||||
|
@ -1352,10 +1441,6 @@ nsWaveDecoder::ResourceLoaded()
|
|||
return;
|
||||
}
|
||||
|
||||
if (mPlaybackStateMachine) {
|
||||
mPlaybackStateMachine->StreamEnded(PR_TRUE);
|
||||
}
|
||||
|
||||
mResourceLoaded = PR_TRUE;
|
||||
|
||||
if (!mMetadataLoadedReported || mResourceLoadedReported)
|
||||
|
@ -1381,9 +1466,6 @@ nsWaveDecoder::NetworkError()
|
|||
if (mElement) {
|
||||
mElement->NetworkError();
|
||||
}
|
||||
if (mPlaybackStateMachine) {
|
||||
mPlaybackStateMachine->StreamEnded(PR_FALSE);
|
||||
}
|
||||
Stop();
|
||||
}
|
||||
|
||||
|
@ -1530,10 +1612,6 @@ nsWaveDecoder::SeekingStarted()
|
|||
return;
|
||||
}
|
||||
|
||||
if (mPlaybackStateMachine) {
|
||||
mPlaybackStateMachine->UpdateTimeOffset(mTimeOffset);
|
||||
}
|
||||
|
||||
if (mElement) {
|
||||
mElement->SeekStarted();
|
||||
}
|
||||
|
@ -1597,6 +1675,24 @@ nsWaveDecoder::MediaErrorDecode()
|
|||
#endif
|
||||
}
|
||||
|
||||
void
|
||||
nsWaveDecoder::PlaybackPositionChanged()
|
||||
{
|
||||
if (mShuttingDown) {
|
||||
return;
|
||||
}
|
||||
|
||||
float lastTime = mCurrentTime;
|
||||
|
||||
if (mPlaybackStateMachine) {
|
||||
mCurrentTime = mPlaybackStateMachine->GetTimeForPositionChange();
|
||||
}
|
||||
|
||||
if (mElement && lastTime != mCurrentTime) {
|
||||
mElement->DispatchSimpleEvent(NS_LITERAL_STRING("timeupdate"));
|
||||
}
|
||||
}
|
||||
|
||||
void
|
||||
nsWaveDecoder::SetDuration(PRInt64 /* aDuration */)
|
||||
{
|
||||
|
|
Загрузка…
Ссылка в новой задаче