From 8c92942a6f541ceee810e224251ca13ddb792aba Mon Sep 17 00:00:00 2001 From: Chris Double Date: Wed, 30 Mar 2011 18:37:42 +1300 Subject: [PATCH] Bug 635649 - Refactor Wave backend to use content/media nsBuiltinDecoder framework - r=kinetik commit c7e190d81b10e7425b53217352c126adfbb79c4a Author: Chris Double Date: Thu Mar 24 18:09:06 2011 +1300 Fix --- content/media/nsBuiltinDecoderReader.h | 2 + content/media/test/Makefile.in | 4 + .../test/test_autoplay_contentEditable.html | 2 +- content/media/test/test_wave_data_s16.html | 53 + content/media/test/test_wave_data_u8.html | 53 + content/media/test/wavedata_s16.wav | Bin 0 -> 22062 bytes content/media/test/wavedata_u8.wav | Bin 0 -> 11037 bytes content/media/wave/Makefile.in | 1 + content/media/wave/nsWaveDecoder.cpp | 1727 +---------------- content/media/wave/nsWaveDecoder.h | 280 +-- content/media/wave/nsWaveReader.cpp | 551 ++++++ content/media/wave/nsWaveReader.h | 120 ++ 12 files changed, 808 insertions(+), 1985 deletions(-) create mode 100644 content/media/test/test_wave_data_s16.html create mode 100644 content/media/test/test_wave_data_u8.html create mode 100644 content/media/test/wavedata_s16.wav create mode 100644 content/media/test/wavedata_u8.wav create mode 100644 content/media/wave/nsWaveReader.cpp create mode 100644 content/media/wave/nsWaveReader.h diff --git a/content/media/nsBuiltinDecoderReader.h b/content/media/nsBuiltinDecoderReader.h index bdafdcb13450..93fa60ab1701 100644 --- a/content/media/nsBuiltinDecoderReader.h +++ b/content/media/nsBuiltinDecoderReader.h @@ -119,6 +119,7 @@ typedef short SoundDataValue; (static_cast(MOZ_CLIP_TO_15((x)>>9))) // Convert a SoundDataValue to a float for the Audio API #define MOZ_CONVERT_SOUND_SAMPLE(x) ((x)*(1.F/32768)) +#define MOZ_SAMPLE_TYPE_S16LE 1 #else /*MOZ_VORBIS*/ @@ -128,6 +129,7 @@ typedef float SoundDataValue; #define MOZ_SOUND_DATA_FORMAT (nsAudioStream::FORMAT_FLOAT32) #define MOZ_CONVERT_VORBIS_SAMPLE(x) (x) #define MOZ_CONVERT_SOUND_SAMPLE(x) (x) +#define MOZ_SAMPLE_TYPE_FLOAT32 1 #endif diff --git a/content/media/test/Makefile.in b/content/media/test/Makefile.in index db7e50eddada..e9f317bd4ac8 100644 --- a/content/media/test/Makefile.in +++ b/content/media/test/Makefile.in @@ -229,6 +229,8 @@ _TEST_FILES += \ r11025_u8_c1.wav \ r11025_u8_c1_trunc.wav \ r16000_u8_c1_list.wav \ + wavedata_u8.wav \ + wavedata_s16.wav \ $(NULL) # Other files @@ -280,6 +282,8 @@ endif ifdef MOZ_WAVE _TEST_FILES += \ test_can_play_type_wave.html \ + test_wave_data_u8.html \ + test_wave_data_s16.html \ $(NULL) else _TEST_FILES += \ diff --git a/content/media/test/test_autoplay_contentEditable.html b/content/media/test/test_autoplay_contentEditable.html index 11b238e8c9a1..480c60173b5a 100644 --- a/content/media/test/test_autoplay_contentEditable.html +++ b/content/media/test/test_autoplay_contentEditable.html @@ -16,7 +16,7 @@ var manager = new MediaTestManager; var tokens = { 0: ["canplay"], - "canplay": ["canplaythrough"], + "canplay": ["canplay", "canplaythrough"], "canplaythrough": ["canplay", "canplaythrough"] }; diff --git a/content/media/test/test_wave_data_s16.html b/content/media/test/test_wave_data_s16.html new file mode 100644 index 000000000000..ddcdc728c7e0 --- /dev/null +++ b/content/media/test/test_wave_data_s16.html @@ -0,0 +1,53 @@ + + + + Wave Media test: ended + + + + + +
+
+
+ + + diff --git a/content/media/test/test_wave_data_u8.html b/content/media/test/test_wave_data_u8.html new file mode 100644 index 000000000000..bf417eac9142 --- /dev/null +++ b/content/media/test/test_wave_data_u8.html @@ -0,0 +1,53 @@ + + + + Wave Media test: ended + + + + + +
+
+
+ + + diff --git a/content/media/test/wavedata_s16.wav b/content/media/test/wavedata_s16.wav new file mode 100644 index 0000000000000000000000000000000000000000..6a69cd78f6e29f9851f231d67837654a55d87a82 GIT binary patch literal 22062 zcmeIup$&sj00hu06VMF^mMJRuNi7023BlFjVe|HP2jt6{%bk1EyOc5~%WEF@&(XC> z9yQM)>FeI4&9)xvzkknM^}d7v0RjXF5FkK+009C72oNAZfB*pk1PBlyK!5-N0t5&U zAV7cs0RjXF5FkK+009C72oNAZfB*pk1PBlyK!5-N0t5&UAV7cs0RjXF5FkK+009C7 Q2oNAZfB*pk1nw620jCg&fdBvi literal 0 HcmV?d00001 diff --git a/content/media/test/wavedata_u8.wav b/content/media/test/wavedata_u8.wav new file mode 100644 index 0000000000000000000000000000000000000000..1d895c2ce0867c03ba384b84b81918fa51bbb12a GIT binary patch literal 11037 zcmeIuAr6C35CqV{A event; - switch (GetNextFrameStatus()) { - case nsHTMLMediaElement::NEXT_FRAME_UNAVAILABLE_BUFFERING: - event = NS_NewRunnableMethod(mDecoder, &nsWaveDecoder::NextFrameUnavailableBuffering); - break; - case nsHTMLMediaElement::NEXT_FRAME_AVAILABLE: - event = NS_NewRunnableMethod(mDecoder, &nsWaveDecoder::NextFrameAvailable); - break; - case nsHTMLMediaElement::NEXT_FRAME_UNAVAILABLE: - event = NS_NewRunnableMethod(mDecoder, &nsWaveDecoder::NextFrameUnavailable); - break; - default: - PR_NOT_REACHED("unhandled frame state"); - } - - NS_DispatchToMainThread(event, NS_DISPATCH_NORMAL); - } - - // Change the current state and wake the playback thread if it is waiting - // on mMonitor. Used by public member functions called from both threads, - // so must hold mMonitor. Threadsafe. - void ChangeState(State aState); - - // Create and initialize audio stream using current audio parameters. - void OpenAudioStream(nsAutoMonitor& aMonitor); - - // Shut down and dispose audio stream. - void CloseAudioStream(); - - // Read RIFF_INITIAL_SIZE from the beginning of the stream and verify that - // 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. - PRBool LoadFormatChunk(); - - // Scan forward in the stream looking for the start of the PCM data. If - // found, record the data length and offset in mWaveLength and - // 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. - double BytesToTime(PRInt64 aBytes) const - { - NS_ABORT_IF_FALSE(mMetadataValid, "Requires valid metadata"); - NS_ABORT_IF_FALSE(aBytes >= 0, "Must be >= 0"); - return double(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. - PRInt64 TimeToBytes(double aTime) const - { - NS_ABORT_IF_FALSE(mMetadataValid, "Requires valid metadata"); - NS_ABORT_IF_FALSE(aTime >= 0.0f, "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. - 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); - } - - // Weak (raw) pointer to our decoder instance. The decoder manages the - // lifetime of the state machine object, so it is guaranteed that the - // state machine will not outlive the decoder. The decoder is not - // threadsafe, so this pointer must only be used to create runnable events - // targeted at the main thread. - nsWaveDecoder* mDecoder; - - // Weak (raw) pointer to a media stream. The decoder manages the lifetime - // of the stream, so it is guaranteed that the stream will live as long as - // the state machine. The stream is threadsafe, but is only used on the - // playback thread except for create, open, and cancel, which are called - // from the main thread. - nsMediaStream* mStream; - - // Our audio stream. Created on demand when entering playback state. It - // is destroyed when seeking begins and will not be reinitialized until - // playback resumes, so it is possible for this to be null. - nsRefPtr mAudioStream; - - // Maximum time to spend waiting for data during buffering. - TimeDuration mBufferingWait; - - // Machine time that buffering began, used with mBufferingWait to time out - // buffering. - TimeStamp mBufferingStart; - - // 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 - stream, and for byte<->time domain conversions. - */ - - // Number of samples per second. Limited to range [100, 96000] in LoadFormatChunk. - PRUint32 mSampleRate; - - // Number of channels. Limited to range [1, 2] in LoadFormatChunk. - PRUint32 mChannels; - - // Size of a single sample segment, which includes a sample for each - // channel (interleaved). - PRUint32 mSampleSize; - - // The sample format of the PCM data. - nsAudioStream::SampleFormat mSampleFormat; - - // Size of PCM data stored in the WAVE as reported by the data chunk in - // the media. - PRInt64 mWaveLength; - - // Start offset of the PCM data in the media stream. Extends mWaveLength - // bytes. - PRInt64 mWavePCMOffset; - - /* - All member variables following this comment are accessed by both - threads and must be synchronized via mMonitor. - */ - PRMonitor* mMonitor; - - // The state to enter when the state machine loop iterates next. - State mState; - - // A queued state transition. This is used to record the next state - // transition when play or pause is requested during seeking or metadata - // loading to ensure a completed metadata load or seek returns to the most - // recently requested state on completion. - State mNextState; - - // Current playback position in the stream. - PRInt64 mPlaybackPosition; - - // Volume that the audio backend will be initialized with. - double mInitialVolume; - - // Time position (in seconds) to seek to. Set by Seek(double). - double mSeekTime; - - // True once metadata has been parsed and validated. Users of mSampleRate, - // 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; - - // True if paused. Tracks only the play/paused state. - PRPackedBool mPaused; - - // True if playback of the audio stream has finished, and the audio stream - // has been drained. This means playback of the file has ended. - PRPackedBool mPlaybackEnded; -}; - -nsWaveStateMachine::nsWaveStateMachine(nsWaveDecoder* aDecoder, - TimeDuration aBufferWaitTime, - double aInitialVolume) - : mDecoder(aDecoder), - mStream(nsnull), - mBufferingWait(aBufferWaitTime), - mBufferingStart(), - mBufferingEndOffset(0), - mSampleRate(0), - mChannels(0), - mSampleSize(0), - mSampleFormat(nsAudioStream::FORMAT_S16_LE), - mWaveLength(0), - mWavePCMOffset(0), - mMonitor(nsnull), - mState(STATE_LOADING_METADATA), - mNextState(STATE_PAUSED), - mPlaybackPosition(0), - mInitialVolume(aInitialVolume), - mSeekTime(0.0f), - mMetadataValid(PR_FALSE), - mPositionChangeQueued(PR_FALSE), - mPaused(mNextState == STATE_PAUSED), - mPlaybackEnded(PR_FALSE) -{ - mMonitor = nsAutoMonitor::NewMonitor("nsWaveStateMachine"); -} - -nsWaveStateMachine::~nsWaveStateMachine() -{ - nsAutoMonitor::DestroyMonitor(mMonitor); -} - -void -nsWaveStateMachine::Shutdown() -{ - ChangeState(STATE_SHUTDOWN); -} - -void -nsWaveStateMachine::Play() -{ - nsAutoMonitor monitor(mMonitor); - mPaused = PR_FALSE; - mPlaybackEnded = PR_FALSE; - if (mState == STATE_ENDED) { - Seek(0); - return; - } - if (mState == STATE_LOADING_METADATA || mState == STATE_SEEKING) { - mNextState = STATE_PLAYING; - } else { - ChangeState(STATE_PLAYING); - } -} - -void -nsWaveStateMachine::SetVolume(double aVolume) -{ - nsAutoMonitor monitor(mMonitor); - mInitialVolume = aVolume; - if (mAudioStream) { - mAudioStream->SetVolume(aVolume); - } -} - -void -nsWaveStateMachine::Pause() -{ - nsAutoMonitor monitor(mMonitor); - mPaused = PR_TRUE; - if (mState == STATE_LOADING_METADATA || mState == STATE_SEEKING || - mState == STATE_BUFFERING || mState == STATE_ENDED) { - mNextState = STATE_PAUSED; - } else if (mState == STATE_PLAYING) { - ChangeState(STATE_PAUSED); - } -} - -void -nsWaveStateMachine::Seek(double aTime) -{ - nsAutoMonitor monitor(mMonitor); - mPlaybackEnded = PR_FALSE; - mSeekTime = aTime; - if (mSeekTime < 0.0f) { - mSeekTime = 0.0f; - } - if (mState == STATE_LOADING_METADATA) { - mNextState = STATE_SEEKING; - } else if (mState != STATE_SEEKING) { - if (mState == STATE_ENDED) { - mNextState = mPaused ? STATE_PAUSED : STATE_PLAYING; - } else if (mState != STATE_BUFFERING) { - mNextState = mState; - } - ChangeState(STATE_SEEKING); - } - NS_ASSERTION(IsSeeking(), "IsSeeking() must return true when seeking"); -} - -double -nsWaveStateMachine::GetDuration() -{ - nsAutoMonitor monitor(mMonitor); - if (mMetadataValid) { - return BytesToTime(GetDataLength()); - } - return std::numeric_limits::quiet_NaN(); -} - -PRUint32 -nsWaveStateMachine::GetChannels() -{ - nsAutoMonitor monitor(mMonitor); - if (mMetadataValid) { - return mChannels; - } - return 0; -} - -PRUint32 -nsWaveStateMachine::GetSampleRate() -{ - nsAutoMonitor monitor(mMonitor); - if (mMetadataValid) { - return mSampleRate; - } - return 0; -} - -PRBool -nsWaveStateMachine::IsSeeking() -{ - nsAutoMonitor monitor(mMonitor); - return mState == STATE_SEEKING || mNextState == STATE_SEEKING; -} - -PRBool -nsWaveStateMachine::IsEnded() -{ - nsAutoMonitor monitor(mMonitor); - return mPlaybackEnded; -} - -nsHTMLMediaElement::NextFrameStatus -nsWaveStateMachine::GetNextFrameStatus() -{ - nsAutoMonitor monitor(mMonitor); - if (mState == STATE_BUFFERING) - return nsHTMLMediaElement::NEXT_FRAME_UNAVAILABLE_BUFFERING; - // If mMetadataValid is false then we can't call GetDataLength because - // we haven't got the length from the Wave header yet. But we know that - // if we haven't read the metadata then we don't have playable data. - if (mMetadataValid && - mPlaybackPosition < mStream->GetCachedDataEnd(mPlaybackPosition) && - mPlaybackPosition < mWavePCMOffset + GetDataLength()) - return nsHTMLMediaElement::NEXT_FRAME_AVAILABLE; - return nsHTMLMediaElement::NEXT_FRAME_UNAVAILABLE; -} - -double -nsWaveStateMachine::GetTimeForPositionChange() -{ - nsAutoMonitor monitor(mMonitor); - mPositionChangeQueued = PR_FALSE; - return BytesToTime(mPlaybackPosition - mWavePCMOffset); -} - -NS_IMETHODIMP -nsWaveStateMachine::Run() -{ - // Monitor is held by this thread almost permanently, but must be manually - // dropped during long operations to prevent the main thread from blocking - // when calling methods on the state machine object. - nsAutoMonitor monitor(mMonitor); - - for (;;) { - switch (mState) { - case STATE_LOADING_METADATA: - { - monitor.Exit(); - PRBool loaded = LoadRIFFChunk() && LoadFormatChunk() && FindDataOffset(); - monitor.Enter(); - - if (!loaded) { - ChangeState(STATE_ERROR); - } - - if (mState == STATE_LOADING_METADATA) { - mMetadataValid = PR_TRUE; - if (mNextState != STATE_SEEKING) { - nsCOMPtr event = NS_NewRunnableMethod(mDecoder, &nsWaveDecoder::MetadataLoaded); - NS_DispatchToMainThread(event, NS_DISPATCH_NORMAL); - } - ChangeState(mNextState); - } - } - break; - - case STATE_BUFFERING: { - TimeStamp now = TimeStamp::Now(); - if (now - mBufferingStart < mBufferingWait && - mStream->GetCachedDataEnd(mPlaybackPosition) < mBufferingEndOffset && - !mStream->IsDataCachedToEndOfStream(mPlaybackPosition) && - !mStream->IsSuspendedByCache()) { - LOG(PR_LOG_DEBUG, - ("In buffering: buffering data until %d bytes available or %f seconds\n", - PRUint32(mBufferingEndOffset - mStream->GetCachedDataEnd(mPlaybackPosition)), - (mBufferingWait - (now - mBufferingStart)).ToSeconds())); - monitor.Wait(PR_MillisecondsToInterval(1000)); - } else { - ChangeState(mNextState); - UpdateReadyState(); - } - - break; - } - - case STATE_PLAYING: { - if (!mAudioStream) { - OpenAudioStream(monitor); - if (!mAudioStream) { - ChangeState(STATE_ERROR); - break; - } - } - - TimeStamp now = TimeStamp::Now(); - TimeStamp lastWakeup = now - - TimeDuration::FromMilliseconds(AUDIO_BUFFER_LENGTH); - - do { - TimeDuration sleepTime = 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. - TimeDuration targetTime = - TimeDuration::FromMilliseconds(AUDIO_BUFFER_LENGTH); - if (sleepTime < targetTime) { - targetTime = sleepTime; - } - - PRInt64 len = TimeToBytes(double(targetTime.ToSeconds())); - - PRInt64 leftToPlay = - GetDataLength() - (mPlaybackPosition - mWavePCMOffset); - if (leftToPlay <= len) { - len = leftToPlay; - ChangeState(STATE_ENDED); - } - - PRInt64 availableOffset = mStream->GetCachedDataEnd(mPlaybackPosition); - - // Don't buffer if we're at the end of the stream, or if the - // load has been suspended by the cache (in the latter case - // we need to advance playback to free up cache space). - if (mState != STATE_ENDED && - availableOffset < mPlaybackPosition + len && - !mStream->IsSuspendedByCache()) { - mBufferingStart = now; - mBufferingEndOffset = mPlaybackPosition + - TimeToBytes(double(mBufferingWait.ToSeconds())); - mBufferingEndOffset = PR_MAX(mPlaybackPosition + len, - mBufferingEndOffset); - mNextState = mState; - ChangeState(STATE_BUFFERING); - - UpdateReadyState(); - break; - } - - if (len > 0) { - nsAutoArrayPtr buf(new char[size_t(len)]); - PRInt64 got = 0; - - monitor.Exit(); - PRBool ok = ReadAll(buf.get(), len, &got); - monitor.Enter(); - - // Reached EOF. - if (!ok) { - ChangeState(STATE_ENDED); - if (got == 0) { - break; - } - } - - // Calculate difference between the current media stream position - // and the expected end of the PCM data. - PRInt64 endDelta = mWavePCMOffset + mWaveLength - mPlaybackPosition; - if (endDelta < 0) { - // Read past the end of PCM data. Adjust got to avoid playing - // back trailing data. - got -= -endDelta; - ChangeState(STATE_ENDED); - } - - if (mState == STATE_ENDED) { - got = RoundDownToSample(got); - } - - PRUint32 sampleSize = mSampleFormat == nsAudioStream::FORMAT_U8 ? 1 : 2; - NS_ABORT_IF_FALSE(got % sampleSize == 0, "Must write complete samples"); - PRUint32 lengthInSamples = PRUint32(got / sampleSize); - - monitor.Exit(); - mAudioStream->Write(buf.get(), lengthInSamples, PR_FALSE); - monitor.Enter(); - - FirePositionChanged(PR_FALSE); - } - - if (mState == STATE_PLAYING) { - monitor.Wait(PR_MillisecondsToInterval(AUDIO_BUFFER_WAKEUP)); - now = TimeStamp::Now(); - } - } while (mState == STATE_PLAYING); - break; - } - - case STATE_SEEKING: - { - CloseAudioStream(); - - mSeekTime = NS_MIN(mSeekTime, GetDuration()); - double seekTime = mSeekTime; - - // Calculate relative offset within PCM data. - PRInt64 position = RoundDownToSample(TimeToBytes(seekTime)); - NS_ABORT_IF_FALSE(position >= 0 && position <= GetDataLength(), - "Invalid seek position"); - // Convert to absolute offset within stream. - position += mWavePCMOffset; - - // If in the midst of a seek, report the requested seek time - // as the current time as required by step 8 of 4.8.10.9 'Seeking' - // in the WHATWG spec. - PRInt64 oldPosition = mPlaybackPosition; - mPlaybackPosition = position; - FirePositionChanged(PR_TRUE); - - monitor.Exit(); - nsCOMPtr startEvent = - NS_NewRunnableMethod(mDecoder, &nsWaveDecoder::SeekingStarted); - NS_DispatchToMainThread(startEvent, NS_DISPATCH_SYNC); - monitor.Enter(); - - if (mState == STATE_SHUTDOWN) { - break; - } - - monitor.Exit(); - nsresult rv; - rv = mStream->Seek(nsISeekableStream::NS_SEEK_SET, position); - monitor.Enter(); - if (NS_FAILED(rv)) { - NS_WARNING("Seek failed"); - mPlaybackPosition = oldPosition; - FirePositionChanged(PR_TRUE); - } - - if (mState == STATE_SHUTDOWN) { - break; - } - - if (mState == STATE_SEEKING && mSeekTime == seekTime) { - // Special case #1: if a seek was requested during metadata load, - // mNextState will have been clobbered. This can only happen when - // we're instantiating a decoder to service a seek request after - // playback has ended, so we know that the clobbered mNextState - // was PAUSED. - // Special case #2: if a seek is requested after the state machine - // entered STATE_ENDED but before the user has seen the ended - // event, playback has not ended as far as the user's - // concerned--the state machine needs to return to the last - // playback state. - // Special case #3: if seeking to the end of the media, transition - // directly into STATE_ENDED. - State nextState = mNextState; - if (nextState == STATE_SEEKING) { - nextState = STATE_PAUSED; - } else if (nextState == STATE_ENDED) { - nextState = mPaused ? STATE_PAUSED : STATE_PLAYING; - } else if (GetDuration() == seekTime) { - nextState = STATE_ENDED; - } - ChangeState(nextState); - } - - if (mState != STATE_SEEKING) { - monitor.Exit(); - nsCOMPtr stopEvent = - NS_NewRunnableMethod(mDecoder, &nsWaveDecoder::SeekingStopped); - NS_DispatchToMainThread(stopEvent, NS_DISPATCH_SYNC); - monitor.Enter(); - } - } - break; - - case STATE_PAUSED: - monitor.Wait(); - break; - - case STATE_ENDED: - FirePositionChanged(PR_TRUE); - - if (mAudioStream) { - monitor.Exit(); - mAudioStream->Drain(); - monitor.Enter(); - - // After the drain call the audio stream is unusable. Close it so that - // next time audio is used a new stream is created. - CloseAudioStream(); - } - - mPlaybackEnded = PR_TRUE; - - if (mState == STATE_ENDED) { - nsCOMPtr event = - NS_NewRunnableMethod(mDecoder, &nsWaveDecoder::PlaybackEnded); - NS_DispatchToMainThread(event, NS_DISPATCH_NORMAL); - - // We've finished playback. Shutdown the state machine thread, - // in order to save memory on thread stacks, particuarly on Linux. - event = new ShutdownThreadEvent(mDecoder->mPlaybackThread); - NS_DispatchToMainThread(event, NS_DISPATCH_NORMAL); - mDecoder->mPlaybackThread = nsnull; - return NS_OK; - } - break; - - case STATE_ERROR: - { - nsCOMPtr event = NS_NewRunnableMethod(mDecoder, &nsWaveDecoder::DecodeError); - NS_DispatchToMainThread(event, NS_DISPATCH_NORMAL); - - monitor.Wait(); - - if (mState != STATE_SHUTDOWN) { - NS_WARNING("Invalid state transition"); - ChangeState(STATE_ERROR); - } - } - break; - - case STATE_SHUTDOWN: - mPlaybackEnded = PR_TRUE; - CloseAudioStream(); - return NS_OK; - } - } - - return NS_OK; -} - -#if defined(DEBUG) -static PRBool -IsValidStateTransition(State aStartState, State aEndState) -{ - 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 || - aEndState == STATE_ENDED) - return PR_TRUE; - break; - case STATE_PAUSED: - if (aEndState == STATE_PLAYING || aEndState == STATE_SEEKING) - return PR_TRUE; - break; - case STATE_ENDED: - if (aEndState == STATE_SEEKING) - return PR_TRUE; - /* fallthrough */ - 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 (mState == STATE_SHUTDOWN) { - LOG(PR_LOG_WARNING, ("In shutdown, state transition ignored")); - return; - } -#if defined(DEBUG) - NS_ABORT_IF_FALSE(IsValidStateTransition(mState, aState), "Invalid state transition"); -#endif - mState = aState; - monitor.NotifyAll(); -} - -void -nsWaveStateMachine::OpenAudioStream(nsAutoMonitor& aMonitor) -{ - NS_ABORT_IF_FALSE(mMetadataValid, - "Attempting to initialize audio stream with invalid metadata"); - - nsRefPtr audioStream = nsAudioStream::AllocateStream(); - if (!audioStream) { - LOG(PR_LOG_ERROR, ("Could not create audio stream")); - return; - } - - // Drop the monitor while initializing the stream because remote - // audio streams wait on a synchronous event running on the main - // thread, and holding the decoder monitor while waiting for this - // can result in deadlocks. - aMonitor.Exit(); - audioStream->Init(mChannels, mSampleRate, mSampleFormat); - aMonitor.Enter(); - - mAudioStream = audioStream; - mAudioStream->SetVolume(mInitialVolume); -} - -void -nsWaveStateMachine::CloseAudioStream() -{ - if (mAudioStream) { - mAudioStream->Shutdown(); - mAudioStream = nsnull; - } -} - -nsMediaDecoder::Statistics -nsWaveStateMachine::GetStatistics() -{ - nsMediaDecoder::Statistics result; - nsAutoMonitor monitor(mMonitor); - result.mDownloadRate = mStream->GetDownloadRate(&result.mDownloadRateReliable); - result.mPlaybackRate = mSampleRate*mChannels*mSampleSize; - result.mPlaybackRateReliable = PR_TRUE; - result.mTotalBytes = mStream->GetLength(); - result.mDownloadPosition = mStream->GetCachedDataEnd(mPlaybackPosition); - result.mDecoderPosition = mPlaybackPosition; - result.mPlaybackPosition = mPlaybackPosition; - return result; -} - -void -nsWaveStateMachine::NotifyBytesConsumed(PRInt64 aBytes) -{ - nsAutoMonitor monitor(mMonitor); - mPlaybackPosition += aBytes; -} - -static PRUint32 -ReadUint32BE(const char** aBuffer) -{ - PRUint32 result = - PRUint8((*aBuffer)[0]) << 24 | - PRUint8((*aBuffer)[1]) << 16 | - PRUint8((*aBuffer)[2]) << 8 | - PRUint8((*aBuffer)[3]); - *aBuffer += sizeof(PRUint32); - return result; -} - -static PRUint32 -ReadUint32LE(const char** aBuffer) -{ - PRUint32 result = - PRUint8((*aBuffer)[3]) << 24 | - PRUint8((*aBuffer)[2]) << 16 | - PRUint8((*aBuffer)[1]) << 8 | - PRUint8((*aBuffer)[0]); - *aBuffer += sizeof(PRUint32); - return result; -} - -static PRUint16 -ReadUint16LE(const char** aBuffer) -{ - PRUint16 result = - PRUint8((*aBuffer)[1]) << 8 | - PRUint8((*aBuffer)[0]) << 0; - *aBuffer += sizeof(PRUint16); - return result; -} - -PRBool -nsWaveStateMachine::IsShutdown() -{ - nsAutoMonitor monitor(mMonitor); - return mState == STATE_SHUTDOWN; -} - -PRBool -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, PRUint32(aSize - got), &read))) { - NS_WARNING("Stream read failed"); - return PR_FALSE; - } - if (IsShutdown() || read == 0) { - return PR_FALSE; - } - NotifyBytesConsumed(read); - got += read; - if (aBytesRead) { - *aBytesRead = got; - } - } while (got != aSize); - return PR_TRUE; -} - -PRBool -nsWaveStateMachine::LoadRIFFChunk() -{ - char riffHeader[RIFF_INITIAL_SIZE]; - const char* p = riffHeader; - - NS_ABORT_IF_FALSE(mStream->Tell() == 0, - "LoadRIFFChunk called when stream in invalid state"); - - if (!ReadAll(riffHeader, sizeof(riffHeader))) { - return PR_FALSE; - } - - if (ReadUint32BE(&p) != RIFF_CHUNK_MAGIC) { - NS_WARNING("Stream data not in RIFF format"); - return PR_FALSE; - } - - // Skip over RIFF size field. - p += 4; - - if (ReadUint32BE(&p) != WAVE_CHUNK_MAGIC) { - NS_WARNING("Expected WAVE chunk"); - return PR_FALSE; - } - - return PR_TRUE; -} - -PRBool -nsWaveStateMachine::ScanForwardUntil(PRUint32 aWantedChunk, PRUint32* aChunkSize) -{ - NS_ABORT_IF_FALSE(aChunkSize, "Require aChunkSize argument"); - *aChunkSize = 0; - - for (;;) { - char chunkHeader[8]; - const char* p = chunkHeader; - - if (!ReadAll(chunkHeader, sizeof(chunkHeader))) { - return PR_FALSE; - } - - PRUint32 magic = ReadUint32BE(&p); - PRUint32 chunkSize = ReadUint32LE(&p); - - if (magic == aWantedChunk) { - *aChunkSize = chunkSize; - return PR_TRUE; - } - - // RIFF chunks are two-byte aligned, so round up if necessary. - chunkSize += chunkSize % 2; - - while (chunkSize > 0) { - PRUint32 size = PR_MIN(chunkSize, 1 << 16); - nsAutoArrayPtr chunk(new char[size]); - if (!ReadAll(chunk.get(), size)) { - return PR_FALSE; - } - chunkSize -= size; - } - } -} - -PRBool -nsWaveStateMachine::LoadFormatChunk() -{ - 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 (ReadUint16LE(&p) != WAVE_FORMAT_ENCODING_PCM) { - NS_WARNING("WAVE is not uncompressed PCM, compressed encodings are not supported"); - return PR_FALSE; - } - - channels = ReadUint16LE(&p); - rate = ReadUint32LE(&p); - - // Skip over average bytes per second field. - p += 4; - - sampleSize = ReadUint16LE(&p); - - sampleFormat = ReadUint16LE(&p); - - // PCM encoded WAVEs are not expected to have an extended "format" chunk, - // but I have found WAVEs that have a extended "format" chunk with an - // 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) { - char extLength[2]; - const char* p = extLength; - - if (!ReadAll(extLength, sizeof(extLength))) { - return PR_FALSE; - } - - PRUint16 extra = ReadUint16LE(&p); - if (fmtSize - (WAVE_FORMAT_CHUNK_SIZE + 2) != extra) { - NS_WARNING("Invalid extended format chunk size"); - return PR_FALSE; - } - extra += extra % 2; - - if (extra > 0) { - nsAutoArrayPtr chunkExtension(new char[extra]); - if (!ReadAll(chunkExtension.get(), extra)) { - return PR_FALSE; - } - } - } - - // RIFF chunks are always word (two byte) aligned. - NS_ABORT_IF_FALSE(mStream->Tell() % 2 == 0, - "LoadFormatChunk left stream unaligned"); - - // Make sure metadata is fairly sane. The rate check is fairly arbitrary, - // but the channels check is intentionally limited to mono or stereo - // because that's what the audio backend currently supports. - if (rate < 100 || rate > 96000 || - channels < 1 || channels > 2 || - (sampleSize != 1 && sampleSize != 2 && sampleSize != 4) || - (sampleFormat != 8 && sampleFormat != 16)) { - NS_WARNING("Invalid WAVE metadata"); - return PR_FALSE; - } - - nsAutoMonitor monitor(mMonitor); - mSampleRate = rate; - mChannels = channels; - mSampleSize = sampleSize; - if (sampleFormat == 8) { - mSampleFormat = nsAudioStream::FORMAT_U8; - } else { - mSampleFormat = nsAudioStream::FORMAT_S16_LE; - } - return PR_TRUE; -} - -PRBool -nsWaveStateMachine::FindDataOffset() -{ - // RIFF chunks are always word (two byte) aligned. - NS_ABORT_IF_FALSE(mStream->Tell() % 2 == 0, - "FindDataOffset called with unaligned stream"); - - // The "data" chunk may not directly follow the "format" chunk, so skip - // over any intermediate chunks. - PRUint32 length; - if (!ScanForwardUntil(DATA_CHUNK_MAGIC, &length)) { - return PR_FALSE; - } - - PRInt64 offset = mStream->Tell(); - if (offset <= 0 || offset > PR_UINT32_MAX) { - NS_WARNING("PCM data offset out of range"); - return PR_FALSE; - } - - nsAutoMonitor monitor(mMonitor); - mWaveLength = length; - mWavePCMOffset = PRUint32(offset); - 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. - PRInt64 streamLength = mStream->GetLength(); - if (streamLength >= 0) { - PRInt64 dataLength = PR_MAX(0, streamLength - mWavePCMOffset); - length = PR_MIN(dataLength, length); - } - return length; -} - -void -nsWaveStateMachine::FirePositionChanged(PRBool aCoalesce) -{ - if (aCoalesce && mPositionChangeQueued) { - return; - } - - mPositionChangeQueued = PR_TRUE; - nsCOMPtr event = NS_NewRunnableMethod(mDecoder, &nsWaveDecoder::PlaybackPositionChanged); - NS_DispatchToMainThread(event, NS_DISPATCH_NORMAL); -} - -nsresult -nsWaveStateMachine::GetBuffered(nsTimeRanges* aBuffered) -{ - PRInt64 startOffset = mStream->GetNextCachedData(mWavePCMOffset); - while (startOffset >= 0) { - PRInt64 endOffset = mStream->GetCachedDataEnd(startOffset); - // Bytes [startOffset..endOffset] are cached. - aBuffered->Add(BytesToTime(startOffset - mWavePCMOffset), - BytesToTime(endOffset - mWavePCMOffset)); - startOffset = mStream->GetNextCachedData(endOffset); - } - return NS_OK; -} - -NS_IMPL_THREADSAFE_ISUPPORTS1(nsWaveDecoder, nsIObserver) - -nsWaveDecoder::nsWaveDecoder() - : mInitialVolume(1.0f), - mCurrentTime(0.0f), - mEndedDuration(std::numeric_limits::quiet_NaN()), - mEnded(PR_FALSE), - mSeekable(PR_TRUE), - mResourceLoaded(PR_FALSE), - mMetadataLoadedReported(PR_FALSE), - mResourceLoadedReported(PR_FALSE) -{ - MOZ_COUNT_CTOR(nsWaveDecoder); - -#ifdef PR_LOGGING - if (!gWaveDecoderLog) { - gWaveDecoderLog = PR_NewLogModule("nsWaveDecoder"); - } -#endif -} - -nsWaveDecoder::~nsWaveDecoder() -{ - MOZ_COUNT_DTOR(nsWaveDecoder); - UnpinForSeek(); -} - -PRBool -nsWaveDecoder::Init(nsHTMLMediaElement* aElement) -{ - nsMediaDecoder::Init(aElement); - - nsContentUtils::RegisterShutdownObserver(this); - - mPlaybackStateMachine = new nsWaveStateMachine(this, - TimeDuration::FromMilliseconds(BUFFERING_TIMEOUT), - mInitialVolume); - NS_ENSURE_TRUE(mPlaybackStateMachine, PR_FALSE); - - return PR_TRUE; -} - -nsMediaStream* -nsWaveDecoder::GetCurrentStream() -{ - return mStream; -} - -already_AddRefed -nsWaveDecoder::GetCurrentPrincipal() -{ - if (!mStream) { - return nsnull; - } - return mStream->GetCurrentPrincipal(); -} - -double -nsWaveDecoder::GetCurrentTime() -{ - return mCurrentTime; -} - -nsresult -nsWaveDecoder::StartStateMachineThread() -{ - NS_ASSERTION(mPlaybackStateMachine, "Must have state machine"); - if (mPlaybackThread) { - return NS_OK; - } - nsresult rv = NS_NewThread(getter_AddRefs(mPlaybackThread)); - NS_ENSURE_SUCCESS(rv, rv); - - return mPlaybackThread->Dispatch(mPlaybackStateMachine, NS_DISPATCH_NORMAL); -} - -nsresult -nsWaveDecoder::Seek(double aTime) -{ - if (mPlaybackStateMachine) { - mEnded = PR_FALSE; - mCurrentTime = aTime; - PinForSeek(); - mPlaybackStateMachine->Seek(aTime); - return StartStateMachineThread(); - } - - return NS_ERROR_FAILURE; -} - -nsresult -nsWaveDecoder::PlaybackRateChanged() -{ - return NS_ERROR_NOT_IMPLEMENTED; -} - -double -nsWaveDecoder::GetDuration() -{ - if (mPlaybackStateMachine) { - return mPlaybackStateMachine->GetDuration(); - } - return mEndedDuration; -} - -void -nsWaveDecoder::Pause() -{ - if (mPlaybackStateMachine) { - mPlaybackStateMachine->Pause(); - } -} - -void -nsWaveDecoder::SetVolume(double aVolume) -{ - mInitialVolume = aVolume; - if (mPlaybackStateMachine) { - mPlaybackStateMachine->SetVolume(aVolume); - } -} - -nsresult -nsWaveDecoder::Play() -{ - if (mPlaybackStateMachine) { - mEnded = PR_FALSE; - mPlaybackStateMachine->Play(); - return StartStateMachineThread(); - } - - return NS_ERROR_FAILURE; -} - -void -nsWaveDecoder::Stop() -{ - if (mPlaybackStateMachine) { - mPlaybackStateMachine->Shutdown(); - } - - if (mStream) { - mStream->Close(); - } - - if (mPlaybackThread) { - mPlaybackThread->Shutdown(); - } - - if (mPlaybackStateMachine) { - mEndedDuration = mPlaybackStateMachine->GetDuration(); - mEnded = mPlaybackStateMachine->IsEnded(); - } - - mPlaybackThread = nsnull; - mPlaybackStateMachine = nsnull; - mStream = nsnull; - - nsContentUtils::UnregisterShutdownObserver(this); -} - -nsresult -nsWaveDecoder::Load(nsMediaStream* aStream, nsIStreamListener** aStreamListener, - nsMediaDecoder* aCloneDonor) -{ - NS_ASSERTION(aStream, "A stream should be provided"); - - if (aStreamListener) { - *aStreamListener = nsnull; - } - - mStream = aStream; - - nsresult rv = mStream->Open(aStreamListener); - NS_ENSURE_SUCCESS(rv, rv); - - mPlaybackStateMachine->SetStream(mStream); - - rv = NS_NewThread(getter_AddRefs(mPlaybackThread)); - NS_ENSURE_SUCCESS(rv, rv); - - rv = mPlaybackThread->Dispatch(mPlaybackStateMachine, NS_DISPATCH_NORMAL); - NS_ENSURE_SUCCESS(rv, rv); - - return NS_OK; -} - -void -nsWaveDecoder::MetadataLoaded() -{ - if (mShuttingDown) { - return; - } - - if (mElement) { - mElement->MetadataLoaded(mPlaybackStateMachine->GetChannels(), - mPlaybackStateMachine->GetSampleRate()); - mElement->FirstFrameLoaded(mResourceLoaded); - } - - mMetadataLoadedReported = PR_TRUE; - - if (mResourceLoaded) { - ResourceLoaded(); - } else { - StartProgress(); - } -} - -void -nsWaveDecoder::PlaybackEnded() -{ - if (mShuttingDown) { - return; - } - - if (!mPlaybackStateMachine->IsEnded()) { - return; - } - mEnded = PR_TRUE; - - // Update ready state; now that we've finished playback, we should - // switch to HAVE_CURRENT_DATA. - UpdateReadyStateForData(); - if (mElement) { - mElement->PlaybackEnded(); - } -} - -void -nsWaveDecoder::ResourceLoaded() -{ - if (mShuttingDown) { - return; - } - - mResourceLoaded = PR_TRUE; - - if (!mMetadataLoadedReported || mResourceLoadedReported) - return; - - StopProgress(); - - if (mElement) { - // Ensure the final progress event gets fired - mElement->ResourceLoaded(); - } - - mResourceLoadedReported = PR_TRUE; -} - -void -nsWaveDecoder::NetworkError() -{ - if (mShuttingDown) { - return; - } - if (mElement) { - mElement->NetworkError(); - } - Shutdown(); -} - -PRBool -nsWaveDecoder::IsSeeking() const -{ - if (mPlaybackStateMachine) { - return mPlaybackStateMachine->IsSeeking(); - } - return PR_FALSE; -} - -PRBool -nsWaveDecoder::IsEnded() const -{ - return mEnded; -} - -nsMediaDecoder::Statistics -nsWaveDecoder::GetStatistics() -{ - if (!mPlaybackStateMachine) - return Statistics(); - return mPlaybackStateMachine->GetStatistics(); -} - -void -nsWaveDecoder::NotifySuspendedStatusChanged() -{ - if (mStream->IsSuspendedByCache() && mElement) { - // if this is an autoplay element, we need to kick off its autoplaying - // now so we consume data and hopefully free up cache space - mElement->NotifyAutoplayDataReady(); - } -} - -void -nsWaveDecoder::NotifyBytesDownloaded() -{ - UpdateReadyStateForData(); - Progress(PR_FALSE); -} - -void -nsWaveDecoder::NotifyDownloadEnded(nsresult aStatus) -{ - if (NS_SUCCEEDED(aStatus)) { - ResourceLoaded(); - } else if (aStatus == NS_BINDING_ABORTED) { - // Download has been cancelled by user. - if (mElement) { - mElement->LoadAborted(); - } - } else if (aStatus != NS_BASE_STREAM_CLOSED) { - NetworkError(); - } - UpdateReadyStateForData(); -} - -void -nsWaveDecoder::Shutdown() -{ - if (mShuttingDown) - return; - - mShuttingDown = PR_TRUE; - - nsMediaDecoder::Shutdown(); - - // An event that gets posted to the main thread, when the media element is - // being destroyed, to destroy the decoder. Since the decoder shutdown can - // block and post events this cannot be done inside destructor calls. So - // this event is posted asynchronously to the main thread to perform the - // shutdown. - nsCOMPtr event = - NS_NewRunnableMethod(this, &nsWaveDecoder::Stop); - NS_DispatchToMainThread(event, NS_DISPATCH_NORMAL); -} - -nsresult -nsWaveDecoder::Observe(nsISupports* aSubject, const char* aTopic, const PRUnichar* aData) -{ - if (strcmp(aTopic, NS_XPCOM_SHUTDOWN_OBSERVER_ID) == 0) { - Shutdown(); - } - return NS_OK; -} - -void -nsWaveDecoder::NextFrameUnavailableBuffering() -{ - NS_ASSERTION(NS_IsMainThread(), "Should be called on main thread"); - if (!mElement || mShuttingDown || !mPlaybackStateMachine) - return; - - mElement->UpdateReadyStateForData(nsHTMLMediaElement::NEXT_FRAME_UNAVAILABLE_BUFFERING); -} - -void -nsWaveDecoder::NextFrameAvailable() -{ - NS_ASSERTION(NS_IsMainThread(), "Should be called on main thread"); - if (!mElement || mShuttingDown || !mPlaybackStateMachine) - return; - - if (!mMetadataLoadedReported) { - mElement->UpdateReadyStateForData(nsHTMLMediaElement::NEXT_FRAME_UNAVAILABLE); - } else { - mElement->UpdateReadyStateForData(nsHTMLMediaElement::NEXT_FRAME_AVAILABLE); - } -} - -void -nsWaveDecoder::NextFrameUnavailable() -{ - NS_ASSERTION(NS_IsMainThread(), "Should be called on main thread"); - if (!mElement || mShuttingDown || !mPlaybackStateMachine) - return; - - mElement->UpdateReadyStateForData(nsHTMLMediaElement::NEXT_FRAME_UNAVAILABLE); -} - -void -nsWaveDecoder::UpdateReadyStateForData() -{ - NS_ASSERTION(NS_IsMainThread(), "Should be called on main thread"); - if (!mElement || mShuttingDown || !mPlaybackStateMachine) - return; - - nsHTMLMediaElement::NextFrameStatus frameStatus = - mPlaybackStateMachine->GetNextFrameStatus(); - if (frameStatus == nsHTMLMediaElement::NEXT_FRAME_AVAILABLE && - !mMetadataLoadedReported) { - frameStatus = nsHTMLMediaElement::NEXT_FRAME_UNAVAILABLE; - } - mElement->UpdateReadyStateForData(frameStatus); -} - -void -nsWaveDecoder::SeekingStarted() -{ - if (mShuttingDown) { - return; - } - - if (mElement) { - UpdateReadyStateForData(); - mElement->SeekStarted(); - } -} - -void -nsWaveDecoder::SeekingStopped() -{ - UnpinForSeek(); - if (mShuttingDown) { - return; - } - - if (mElement) { - UpdateReadyStateForData(); - mElement->SeekCompleted(); - } -} - -void -nsWaveDecoder::DecodeError() -{ - if (mShuttingDown) { - return; - } - if (mElement) { - mElement->DecodeError(); - } - Shutdown(); -} - -void -nsWaveDecoder::PlaybackPositionChanged() -{ - if (mShuttingDown) { - return; - } - - double lastTime = mCurrentTime; - - if (mPlaybackStateMachine) { - mCurrentTime = mPlaybackStateMachine->GetTimeForPositionChange(); - } - - if (mElement && lastTime != mCurrentTime) { - UpdateReadyStateForData(); - FireTimeUpdate(); - } -} - -void -nsWaveDecoder::SetDuration(PRInt64 /* aDuration */) -{ - // Ignored by the wave decoder since we can compute the - // duration directly from the wave data itself. -} - -void -nsWaveDecoder::SetSeekable(PRBool aSeekable) -{ - mSeekable = aSeekable; -} - -PRBool -nsWaveDecoder::GetSeekable() -{ - return mSeekable; -} - -void -nsWaveDecoder::Suspend() -{ - if (mStream) { - mStream->Suspend(PR_TRUE); - } -} - -void -nsWaveDecoder::Resume(PRBool aForceBuffering) -{ - if (mStream) { - mStream->Resume(); - } -} - -void -nsWaveDecoder::MoveLoadsToBackground() -{ - if (mStream) { - mStream->MoveLoadsToBackground(); - } -} - -nsresult -nsWaveDecoder::GetBuffered(nsTimeRanges* aBuffered) +nsDecoderStateMachine* nsWaveDecoder::CreateStateMachine() { - NS_ASSERTION(NS_IsMainThread(), "Only call on main thread"); - return mPlaybackStateMachine->GetBuffered(aBuffered); + return new nsBuiltinDecoderStateMachine(this, new nsWaveReader(this)); } diff --git a/content/media/wave/nsWaveDecoder.h b/content/media/wave/nsWaveDecoder.h index aba5f93c6672..220a6ef21feb 100644 --- a/content/media/wave/nsWaveDecoder.h +++ b/content/media/wave/nsWaveDecoder.h @@ -15,8 +15,8 @@ * * The Original Code is Mozilla code. * - * The Initial Developer of the Original Code is the Mozilla Corporation. - * Portions created by the Initial Developer are Copyright (C) 2008 + * The Initial Developer of the Original Code is the Mozilla Foundation. + * Portions created by the Initial Developer are Copyright (C) 2010 * the Initial Developer. All Rights Reserved. * * Contributor(s): @@ -38,271 +38,25 @@ #if !defined(nsWaveDecoder_h_) #define nsWaveDecoder_h_ -#include "nsISupports.h" -#include "nsCOMPtr.h" -#include "nsMediaDecoder.h" -#include "nsMediaStream.h" +#include "nsBuiltinDecoder.h" -/* - nsWaveDecoder provides an implementation of the abstract nsMediaDecoder - class that supports parsing and playback of Waveform Audio (WAVE) chunks - embedded in Resource Interchange File Format (RIFF) bitstreams as - specified by the Multimedia Programming Interface and Data Specification - 1.0. +/** + * The decoder implementation is currently limited to Linear PCM encoded + * audio data with one or two channels of 8- or 16-bit samples at sample + * rates from 100 Hz to 96 kHz. The number of channels is limited by what + * the audio backend (sydneyaudio via nsAudioStream) currently supports. The + * supported sample rate is artificially limited to arbitrarily selected sane + * values. Support for additional channels (and other new features) would + * require extending nsWaveDecoder to support parsing the newer + * WAVE_FORMAT_EXTENSIBLE chunk format. +**/ - Each decoder instance starts one thread (the playback thread). A single - nsWaveStateMachine event is dispatched to this thread to start the - thread's state machine running. The Run method of the event is a loop - that executes the current state. The state can be changed by the state - machine, or from the main thread via threadsafe methods on the event. - During playback, the playback thread reads data from the network and - writes it to the audio backend, attempting to keep the backend's audio - buffers full. It is also responsible for seeking, buffering, and - pausing/resuming audio. - The decoder also owns an nsMediaStream instance that provides a threadsafe - blocking interface to read from network channels. The state machine is - the primary user of this stream and holds a weak (raw) pointer to it as - the thread, state machine, and stream's lifetimes are all managed by the - decoder. - - nsWaveStateMachine has the following states: - - LOADING_METADATA - RIFF/WAVE chunks are being read from the stream, the metadata describing - the audio data is parsed. - - BUFFERING - Playback is paused while waiting for additional data. - - PLAYING - If data is available in the stream and the audio backend can consume - more data, it is read from the stream and written to the audio backend. - Sleep until approximately half of the backend's buffers have drained. - - SEEKING - Decoder is seeking to a specified time in the media. - - PAUSED - Pause the audio backend, then wait for a state transition. - - ENDED - Expected PCM data (or stream EOF) reached, wait for the audio backend to - play any buffered data, then wait for shutdown. - - ERROR - Metadata loading/parsing failed, wait for shutdown. - - SHUTDOWN - Close the audio backend and return from the run loop. - - State transitions within the state machine are: - - LOADING_METADATA -> PLAYING - -> PAUSED - -> ERROR - - BUFFERING -> PLAYING - -> PAUSED - - PLAYING -> BUFFERING - -> ENDED - - SEEKING -> PLAYING - -> PAUSED - - PAUSED -> waits for caller to play, seek, or shutdown - - ENDED -> waits for caller to shutdown - - ERROR -> waits for caller to shutdown - - SHUTDOWN -> exits state machine - - In addition, the following methods cause state transitions: - - Shutdown(), Play(), Pause(), Seek(double) - - The decoder implementation is currently limited to Linear PCM encoded - audio data with one or two channels of 8- or 16-bit samples at sample - rates from 100 Hz to 96 kHz. The number of channels is limited by what - the audio backend (sydneyaudio via nsAudioStream) currently supports. The - supported sample rate is artificially limited to arbitrarily selected sane - values. Support for additional channels (and other new features) would - require extending nsWaveDecoder to support parsing the newer - WAVE_FORMAT_EXTENSIBLE chunk format. - */ - -class nsWaveStateMachine; -class nsTimeRanges; - -class nsWaveDecoder : public nsMediaDecoder +class nsWaveDecoder : public nsBuiltinDecoder { - friend class nsWaveStateMachine; - - NS_DECL_ISUPPORTS - NS_DECL_NSIOBSERVER - - public: - nsWaveDecoder(); - ~nsWaveDecoder(); - - virtual nsMediaDecoder* Clone() { return new nsWaveDecoder(); } - - virtual PRBool Init(nsHTMLMediaElement* aElement); - - virtual nsMediaStream* GetCurrentStream(); - virtual already_AddRefed GetCurrentPrincipal(); - - // Return the current playback position in the media in seconds. - virtual double GetCurrentTime(); - - // Return the total playback length of the media in seconds. - virtual double GetDuration(); - - // Set the audio playback volume; must be in range [0.0, 1.0]. - virtual void SetVolume(double aVolume); - - virtual nsresult Play(); - virtual void Pause(); - - // Set the current time of the media to aTime. This may cause mStream to - // create a new channel to fetch data from the appropriate position in the - // stream. - virtual nsresult Seek(double aTime); - - // Report whether the decoder is currently seeking. - virtual PRBool IsSeeking() const; - - // Report whether the decoder has reached end of playback. - virtual PRBool IsEnded() const; - - // Start downloading the media at the specified URI. The media's metadata - // will be parsed and made available as the load progresses. - virtual nsresult Load(nsMediaStream* aStream, - nsIStreamListener** aStreamListener, - nsMediaDecoder* aCloneDonor); - - // Called by mStream (and possibly the nsChannelToPipeListener used - // internally by mStream) when the stream has completed loading. - virtual void ResourceLoaded(); - - // Called by mStream (and possibly the nsChannelToPipeListener used - // internally by mStream) if the stream encounters a network error. - virtual void NetworkError(); - - // Element is notifying us that the requested playback rate has changed. - virtual nsresult PlaybackRateChanged(); - - virtual void NotifySuspendedStatusChanged(); - virtual void NotifyBytesDownloaded(); - virtual void NotifyDownloadEnded(nsresult aStatus); - - virtual Statistics GetStatistics(); - - 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); - - // Getter/setter for mSeekable. - virtual void SetSeekable(PRBool aSeekable); - virtual PRBool GetSeekable(); - - // Must be called by the owning object before disposing the decoder. - virtual void Shutdown(); - - // Suspend any media downloads that are in progress. Called by the - // media element when it is sent to the bfcache. Call on the main - // thread only. - virtual void Suspend(); - - // Resume any media downloads that have been suspended. Called by the - // media element when it is restored from the bfcache. Call on the - // main thread only. - virtual void Resume(PRBool aForceBuffering); - - // Calls mElement->UpdateReadyStateForData, telling it which state we have - // entered. Main thread only. - void NextFrameUnavailableBuffering(); - void NextFrameAvailable(); - void NextFrameUnavailable(); - - // Change the element's ready state as necessary. Main thread only. - void UpdateReadyStateForData(); - - // Tells mStream to put all loads in the background. - virtual void MoveLoadsToBackground(); - - // Called asynchronously to shut down the decoder - void Stop(); - - // Constructs the time ranges representing what segments of the media - // are buffered and playable. - virtual nsresult GetBuffered(nsTimeRanges* aBuffered); - - virtual void NotifyDataArrived(const char* aBuffer, PRUint32 aLength, PRUint32 aOffset) {} - -private: - // Notifies the element that seeking has started. - void SeekingStarted(); - - // Notifies the element that seeking has completed. - void SeekingStopped(); - - // Notifies the element that metadata loading has completed. Only fired - // if metadata is valid. - void MetadataLoaded(); - - // Notifies the element that playback has completed. - void PlaybackEnded(); - - // Notifies the element that decoding has failed. - void DecodeError(); - - // Ensures that state machine thread is running, starting a new one - // if necessary. - nsresult StartStateMachineThread(); - - // Volume that the audio backend will be initialized with. - double mInitialVolume; - - // Thread that handles audio playback, including data download. - nsCOMPtr mPlaybackThread; - - // State machine that runs on mPlaybackThread. Methods on this object are - // safe to call from any thread. - nsCOMPtr mPlaybackStateMachine; - - // Threadsafe wrapper around channels that provides seeking based on the - // underlying channel type. - nsAutoPtr mStream; - - // 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. - double mCurrentTime; - - // Copy of the duration and ended state when the state machine was - // disposed. Used to respond to duration and ended queries with sensible - // values after the state machine has been destroyed. - double mEndedDuration; - PRPackedBool mEnded; - - // True if the media resource is seekable. - PRPackedBool mSeekable; - - // True when the media resource has completely loaded. Accessed on - // the main thread only. - PRPackedBool mResourceLoaded; - - // True if MetadataLoaded has been reported to the element. - PRPackedBool mMetadataLoadedReported; - - // True if ResourceLoaded has been reported to the element. - PRPackedBool mResourceLoadedReported; +public: + virtual nsMediaDecoder* Clone() { return new nsWaveDecoder(); } + virtual nsDecoderStateMachine* CreateStateMachine(); }; #endif diff --git a/content/media/wave/nsWaveReader.cpp b/content/media/wave/nsWaveReader.cpp new file mode 100644 index 000000000000..c8122dcf4403 --- /dev/null +++ b/content/media/wave/nsWaveReader.cpp @@ -0,0 +1,551 @@ +/* -*- Mode: C++; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*- */ +/* vim:set ts=2 sw=2 sts=2 et cindent: */ +/* ***** BEGIN LICENSE BLOCK ***** + * Version: MPL 1.1/GPL 2.0/LGPL 2.1 + * + * The contents of this file are subject to the Mozilla Public License Version + * 1.1 (the "License"); you may not use this file except in compliance with + * the License. You may obtain a copy of the License at + * http://www.mozilla.org/MPL/ + * + * Software distributed under the License is distributed on an "AS IS" basis, + * WITHOUT WARRANTY OF ANY KIND, either express or implied. See the License + * for the specific language governing rights and limitations under the + * License. + * + * The Original Code is Mozilla code. + * + * The Initial Developer of the Original Code is the Mozilla Foundation. + * Portions created by the Initial Developer are Copyright (C) 2010 + * the Initial Developer. All Rights Reserved. + * + * Contributor(s): + * Matthew Gregan + * + * Alternatively, the contents of this file may be used under the terms of + * either the GNU General Public License Version 2 or later (the "GPL"), or + * the GNU Lesser General Public License Version 2.1 or later (the "LGPL"), + * in which case the provisions of the GPL or the LGPL are applicable instead + * of those above. If you wish to allow use of your version of this file only + * under the terms of either the GPL or the LGPL, and not to allow others to + * use your version of this file under the terms of the MPL, indicate your + * decision by deleting the provisions above and replace them with the notice + * and other provisions required by the GPL or the LGPL. If you do not delete + * the provisions above, a recipient may use your version of this file under + * the terms of any one of the MPL, the GPL or the LGPL. + * + * ***** END LICENSE BLOCK ***** */ +#include "nsError.h" +#include "nsBuiltinDecoderStateMachine.h" +#include "nsBuiltinDecoder.h" +#include "nsMediaStream.h" +#include "nsWaveReader.h" +#include "nsTimeRanges.h" +#include "VideoUtils.h" + +using namespace mozilla; + +// Un-comment to enable logging of seek bisections. +//#define SEEK_LOGGING + +#ifdef PR_LOGGING +extern PRLogModuleInfo* gBuiltinDecoderLog; +#define LOG(type, msg) PR_LOG(gBuiltinDecoderLog, type, msg) +#ifdef SEEK_LOGGING +#define SEEK_LOG(type, msg) PR_LOG(gBuiltinDecoderLog, type, msg) +#else +#define SEEK_LOG(type, msg) +#endif +#else +#define LOG(type, msg) +#define SEEK_LOG(type, msg) +#endif + +// Magic values that identify RIFF chunks we're interested in. +#define RIFF_CHUNK_MAGIC 0x52494646 +#define WAVE_CHUNK_MAGIC 0x57415645 +#define FRMT_CHUNK_MAGIC 0x666d7420 +#define DATA_CHUNK_MAGIC 0x64617461 + +// Size of RIFF chunk header. 4 byte chunk header type and 4 byte size field. +#define RIFF_CHUNK_HEADER_SIZE 8 + +// Size of RIFF header. RIFF chunk and 4 byte RIFF type. +#define RIFF_INITIAL_SIZE (RIFF_CHUNK_HEADER_SIZE + 4) + +// Size of required part of format chunk. Actual format chunks may be +// extended (for non-PCM encodings), but we skip any extended data. +#define WAVE_FORMAT_CHUNK_SIZE 16 + +// PCM encoding type from format chunk. Linear PCM is the only encoding +// supported by nsAudioStream. +#define WAVE_FORMAT_ENCODING_PCM 1 + +// Maximum number of channels supported +#define MAX_CHANNELS 2 + +namespace { + PRUint32 + ReadUint32BE(const char** aBuffer) + { + PRUint32 result = + PRUint8((*aBuffer)[0]) << 24 | + PRUint8((*aBuffer)[1]) << 16 | + PRUint8((*aBuffer)[2]) << 8 | + PRUint8((*aBuffer)[3]); + *aBuffer += sizeof(PRUint32); + return result; + } + + PRUint32 + ReadUint32LE(const char** aBuffer) + { + PRUint32 result = + PRUint8((*aBuffer)[3]) << 24 | + PRUint8((*aBuffer)[2]) << 16 | + PRUint8((*aBuffer)[1]) << 8 | + PRUint8((*aBuffer)[0]); + *aBuffer += sizeof(PRUint32); + return result; + } + + PRUint16 + ReadUint16LE(const char** aBuffer) + { + PRUint16 result = + PRUint8((*aBuffer)[1]) << 8 | + PRUint8((*aBuffer)[0]) << 0; + *aBuffer += sizeof(PRUint16); + return result; + } + + PRInt16 + ReadInt16LE(const char** aBuffer) + { + return static_cast(ReadUint16LE(aBuffer)); + } + + PRUint8 + ReadUint8(const char** aBuffer) + { + PRUint8 result = PRUint8((*aBuffer)[0]); + *aBuffer += sizeof(PRUint8); + return result; + } +} + +nsWaveReader::nsWaveReader(nsBuiltinDecoder* aDecoder) + : nsBuiltinDecoderReader(aDecoder) +{ + MOZ_COUNT_CTOR(nsWaveReader); +} + +nsWaveReader::~nsWaveReader() +{ + MOZ_COUNT_DTOR(nsWaveReader); +} + +nsresult nsWaveReader::Init(nsBuiltinDecoderReader* aCloneDonor) +{ + return NS_OK; +} + +nsresult nsWaveReader::ReadMetadata(nsVideoInfo* aInfo) +{ + NS_ASSERTION(mDecoder->OnStateMachineThread(), "Should be on state machine thread."); + MonitorAutoEnter mon(mMonitor); + + PRBool loaded = LoadRIFFChunk() && LoadFormatChunk() && FindDataOffset(); + if (!loaded) { + return NS_ERROR_FAILURE; + } + + mInfo.mHasAudio = PR_TRUE; + mInfo.mHasVideo = PR_FALSE; + mInfo.mAudioRate = mSampleRate; + mInfo.mAudioChannels = mChannels; + mInfo.mDataOffset = -1; + + *aInfo = mInfo; + + MonitorAutoExit exitReaderMon(mMonitor); + MonitorAutoEnter decoderMon(mDecoder->GetMonitor()); + + float d = floorf(BytesToTime(GetDataLength() * 1000)); + NS_ASSERTION(d <= PR_INT64_MAX, "Duration overflow"); + mDecoder->GetStateMachine()->SetDuration(static_cast(d)); + + return NS_OK; +} + +PRBool nsWaveReader::DecodeAudioData() +{ + MonitorAutoEnter mon(mMonitor); + NS_ASSERTION(mDecoder->OnStateMachineThread() || mDecoder->OnDecodeThread(), + "Should be on state machine thread or decode thread."); + + PRInt64 pos = GetPosition(); + PRInt64 len = GetDataLength(); + PRInt64 remaining = len - pos; + NS_ASSERTION(remaining >= 0, "Current wave position is greater than wave file length"); + + static const PRInt64 BLOCK_SIZE = 4096; + PRInt64 readSize = NS_MIN(BLOCK_SIZE, remaining); + PRInt64 samples = readSize / mSampleSize; + + PR_STATIC_ASSERT(PRUint64(BLOCK_SIZE) < UINT_MAX / sizeof(SoundDataValue) / MAX_CHANNELS); + const size_t bufferSize = static_cast(samples * mChannels); + nsAutoArrayPtr sampleBuffer(new SoundDataValue[bufferSize]); + + PR_STATIC_ASSERT(PRUint64(BLOCK_SIZE) < UINT_MAX / sizeof(char)); + nsAutoArrayPtr dataBuffer(new char[static_cast(readSize)]); + + if (!ReadAll(dataBuffer, readSize)) { + mAudioQueue.Finish(); + return PR_FALSE; + } + + // convert data to samples + const char* d = dataBuffer.get(); + SoundDataValue* s = sampleBuffer.get(); + for (int i = 0; i < samples; ++i) { + for (unsigned int j = 0; j < mChannels; ++j) { + if (mSampleFormat == nsAudioStream::FORMAT_U8) { + PRUint8 v = ReadUint8(&d); +#if defined(MOZ_SAMPLE_TYPE_S16LE) + *s++ = (v * (1.F/PR_UINT8_MAX)) * PR_UINT16_MAX + PR_INT16_MIN; +#elif defined(MOZ_SAMPLE_TYPE_FLOAT32) + *s++ = (v * (1.F/PR_UINT8_MAX)) * 2.F - 1.F; +#endif + } + else if (mSampleFormat == nsAudioStream::FORMAT_S16_LE) { + PRInt16 v = ReadInt16LE(&d); +#if defined(MOZ_SAMPLE_TYPE_S16LE) + *s++ = v; +#elif defined(MOZ_SAMPLE_TYPE_FLOAT32) + *s++ = (PRInt32(v) - PR_INT16_MIN) / float(PR_UINT16_MAX) * 2.F - 1.F; +#endif + } + } + } + + float posTime = BytesToTime(pos); + float readSizeTime = BytesToTime(readSize); + NS_ASSERTION(posTime <= PR_INT64_MAX / 1000, "posTime overflow"); + NS_ASSERTION(readSizeTime <= PR_INT64_MAX / 1000, "readSizeTime overflow"); + NS_ASSERTION(samples < PR_INT32_MAX, "samples overflow"); + + mAudioQueue.Push(new SoundData(pos, static_cast(posTime * 1000), + static_cast(readSizeTime * 1000), + static_cast(samples), + sampleBuffer.forget(), mChannels)); + + return PR_TRUE; +} + +PRBool nsWaveReader::DecodeVideoFrame(PRBool &aKeyframeSkip, + PRInt64 aTimeThreshold) +{ + MonitorAutoEnter mon(mMonitor); + NS_ASSERTION(mDecoder->OnStateMachineThread() || mDecoder->OnDecodeThread(), + "Should be on state machine or decode thread."); + + return PR_FALSE; +} + +nsresult nsWaveReader::Seek(PRInt64 aTarget, PRInt64 aStartTime, PRInt64 aEndTime, PRInt64 aCurrentTime) +{ + MonitorAutoEnter mon(mMonitor); + NS_ASSERTION(mDecoder->OnStateMachineThread(), + "Should be on state machine thread."); + LOG(PR_LOG_DEBUG, ("%p About to seek to %lldms", mDecoder, aTarget)); + if (NS_FAILED(ResetDecode())) { + return NS_ERROR_FAILURE; + } + float d = BytesToTime(GetDataLength()); + NS_ASSERTION(d < PR_INT64_MAX / 1000, "Duration overflow"); + PRInt64 duration = static_cast(d) * 1000; + PRInt64 seekTime = NS_MIN(aTarget, duration); + PRInt64 position = RoundDownToSample(static_cast(TimeToBytes(seekTime) / 1000.f)); + NS_ASSERTION(PR_INT64_MAX - mWavePCMOffset > position, "Integer overflow during wave seek"); + position += mWavePCMOffset; + return mDecoder->GetCurrentStream()->Seek(nsISeekableStream::NS_SEEK_SET, position); +} + +nsresult nsWaveReader::GetBuffered(nsTimeRanges* aBuffered, PRInt64 aStartTime) +{ + PRInt64 startOffset = mDecoder->GetCurrentStream()->GetNextCachedData(mWavePCMOffset); + while (startOffset >= 0) { + PRInt64 endOffset = mDecoder->GetCurrentStream()->GetCachedDataEnd(startOffset); + // Bytes [startOffset..endOffset] are cached. + NS_ASSERTION(startOffset >= mWavePCMOffset, "Integer underflow in GetBuffered"); + NS_ASSERTION(endOffset >= mWavePCMOffset, "Integer underflow in GetBuffered"); + + aBuffered->Add(floorf(BytesToTime(startOffset - mWavePCMOffset) * 1000.f) / 1000.0, + floorf(BytesToTime(endOffset - mWavePCMOffset) * 1000.f) / 1000.0); + startOffset = mDecoder->GetCurrentStream()->GetNextCachedData(endOffset); + } + return NS_OK; +} + +PRBool +nsWaveReader::ReadAll(char* aBuf, PRInt64 aSize, PRInt64* aBytesRead) +{ + PRUint32 got = 0; + if (aBytesRead) { + *aBytesRead = 0; + } + do { + PRUint32 read = 0; + if (NS_FAILED(mDecoder->GetCurrentStream()->Read(aBuf + got, PRUint32(aSize - got), &read))) { + NS_WARNING("Stream read failed"); + return PR_FALSE; + } + if (read == 0) { + return PR_FALSE; + } + mDecoder->NotifyBytesConsumed(read); + got += read; + if (aBytesRead) { + *aBytesRead = got; + } + } while (got != aSize); + return PR_TRUE; +} + +PRBool +nsWaveReader::LoadRIFFChunk() +{ + char riffHeader[RIFF_INITIAL_SIZE]; + const char* p = riffHeader; + + NS_ABORT_IF_FALSE(mDecoder->GetCurrentStream()->Tell() == 0, + "LoadRIFFChunk called when stream in invalid state"); + + if (!ReadAll(riffHeader, sizeof(riffHeader))) { + return PR_FALSE; + } + + PR_STATIC_ASSERT(sizeof(PRUint32) * 2 <= RIFF_INITIAL_SIZE); + if (ReadUint32BE(&p) != RIFF_CHUNK_MAGIC) { + NS_WARNING("Stream data not in RIFF format"); + return PR_FALSE; + } + + // Skip over RIFF size field. + p += 4; + + if (ReadUint32BE(&p) != WAVE_CHUNK_MAGIC) { + NS_WARNING("Expected WAVE chunk"); + return PR_FALSE; + } + + return PR_TRUE; +} + +PRBool +nsWaveReader::ScanForwardUntil(PRUint32 aWantedChunk, PRUint32* aChunkSize) +{ + NS_ABORT_IF_FALSE(aChunkSize, "Require aChunkSize argument"); + *aChunkSize = 0; + + for (;;) { + static const unsigned int CHUNK_HEADER_SIZE = 8; + char chunkHeader[CHUNK_HEADER_SIZE]; + const char* p = chunkHeader; + + if (!ReadAll(chunkHeader, sizeof(chunkHeader))) { + return PR_FALSE; + } + + PR_STATIC_ASSERT(sizeof(PRUint32) * 2 <= CHUNK_HEADER_SIZE); + PRUint32 magic = ReadUint32BE(&p); + PRUint32 chunkSize = ReadUint32LE(&p); + + if (magic == aWantedChunk) { + *aChunkSize = chunkSize; + return PR_TRUE; + } + + // RIFF chunks are two-byte aligned, so round up if necessary. + chunkSize += chunkSize % 2; + + static const unsigned int MAX_CHUNK_SIZE = 1 << 16; + PR_STATIC_ASSERT(MAX_CHUNK_SIZE < UINT_MAX / sizeof(char)); + nsAutoArrayPtr chunk(new char[MAX_CHUNK_SIZE]); + while (chunkSize > 0) { + PRUint32 size = PR_MIN(chunkSize, MAX_CHUNK_SIZE); + if (!ReadAll(chunk.get(), size)) { + return PR_FALSE; + } + chunkSize -= size; + } + } +} + +PRBool +nsWaveReader::LoadFormatChunk() +{ + 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(mDecoder->GetCurrentStream()->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; + } + + PR_STATIC_ASSERT(sizeof(PRUint16) + + sizeof(PRUint16) + + sizeof(PRUint32) + + 4 + + sizeof(PRUint16) + + sizeof(PRUint16) <= sizeof(waveFormat)); + if (ReadUint16LE(&p) != WAVE_FORMAT_ENCODING_PCM) { + NS_WARNING("WAVE is not uncompressed PCM, compressed encodings are not supported"); + return PR_FALSE; + } + + channels = ReadUint16LE(&p); + rate = ReadUint32LE(&p); + + // Skip over average bytes per second field. + p += 4; + + sampleSize = ReadUint16LE(&p); + + sampleFormat = ReadUint16LE(&p); + + // PCM encoded WAVEs are not expected to have an extended "format" chunk, + // but I have found WAVEs that have a extended "format" chunk with an + // 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) { + char extLength[2]; + const char* p = extLength; + + if (!ReadAll(extLength, sizeof(extLength))) { + return PR_FALSE; + } + + PR_STATIC_ASSERT(sizeof(PRUint16) <= sizeof(extLength)); + PRUint16 extra = ReadUint16LE(&p); + if (fmtSize - (WAVE_FORMAT_CHUNK_SIZE + 2) != extra) { + NS_WARNING("Invalid extended format chunk size"); + return PR_FALSE; + } + extra += extra % 2; + + if (extra > 0) { + PR_STATIC_ASSERT(PR_UINT16_MAX + (PR_UINT16_MAX % 2) < UINT_MAX / sizeof(char)); + nsAutoArrayPtr chunkExtension(new char[extra]); + if (!ReadAll(chunkExtension.get(), extra)) { + return PR_FALSE; + } + } + } + + // RIFF chunks are always word (two byte) aligned. + NS_ABORT_IF_FALSE(mDecoder->GetCurrentStream()->Tell() % 2 == 0, + "LoadFormatChunk left stream unaligned"); + + // Make sure metadata is fairly sane. The rate check is fairly arbitrary, + // but the channels check is intentionally limited to mono or stereo + // because that's what the audio backend currently supports. + if (rate < 100 || rate > 96000 || + channels < 1 || channels > MAX_CHANNELS || + (sampleSize != 1 && sampleSize != 2 && sampleSize != 4) || + (sampleFormat != 8 && sampleFormat != 16)) { + NS_WARNING("Invalid WAVE metadata"); + return PR_FALSE; + } + + MonitorAutoEnter monitor(mDecoder->GetMonitor()); + mSampleRate = rate; + mChannels = channels; + mSampleSize = sampleSize; + if (sampleFormat == 8) { + mSampleFormat = nsAudioStream::FORMAT_U8; + } else { + mSampleFormat = nsAudioStream::FORMAT_S16_LE; + } + return PR_TRUE; +} + +PRBool +nsWaveReader::FindDataOffset() +{ + // RIFF chunks are always word (two byte) aligned. + NS_ABORT_IF_FALSE(mDecoder->GetCurrentStream()->Tell() % 2 == 0, + "FindDataOffset called with unaligned stream"); + + // The "data" chunk may not directly follow the "format" chunk, so skip + // over any intermediate chunks. + PRUint32 length; + if (!ScanForwardUntil(DATA_CHUNK_MAGIC, &length)) { + return PR_FALSE; + } + + PRInt64 offset = mDecoder->GetCurrentStream()->Tell(); + if (offset <= 0 || offset > PR_UINT32_MAX) { + NS_WARNING("PCM data offset out of range"); + return PR_FALSE; + } + + MonitorAutoEnter monitor(mDecoder->GetMonitor()); + mWaveLength = length; + mWavePCMOffset = PRUint32(offset); + return PR_TRUE; +} + +float +nsWaveReader::BytesToTime(PRInt64 aBytes) const +{ + NS_ABORT_IF_FALSE(aBytes >= 0, "Must be >= 0"); + return float(aBytes) / mSampleRate / mSampleSize; +} + +PRInt64 +nsWaveReader::TimeToBytes(float aTime) const +{ + NS_ABORT_IF_FALSE(aTime >= 0.0f, "Must be >= 0"); + return RoundDownToSample(PRInt64(aTime * mSampleRate * mSampleSize)); +} + +PRInt64 +nsWaveReader::RoundDownToSample(PRInt64 aBytes) const +{ + NS_ABORT_IF_FALSE(aBytes >= 0, "Must be >= 0"); + return aBytes - (aBytes % mSampleSize); +} + +PRInt64 +nsWaveReader::GetDataLength() +{ + 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. + PRInt64 streamLength = mDecoder->GetCurrentStream()->GetLength(); + if (streamLength >= 0) { + PRInt64 dataLength = PR_MAX(0, streamLength - mWavePCMOffset); + length = PR_MIN(dataLength, length); + } + return length; +} + +PRInt64 +nsWaveReader::GetPosition() +{ + return mDecoder->GetCurrentStream()->Tell(); +} diff --git a/content/media/wave/nsWaveReader.h b/content/media/wave/nsWaveReader.h new file mode 100644 index 000000000000..a5a9a6e07651 --- /dev/null +++ b/content/media/wave/nsWaveReader.h @@ -0,0 +1,120 @@ +/* -*- Mode: C++; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*- */ +/* vim:set ts=2 sw=2 sts=2 et cindent: */ +/* ***** BEGIN LICENSE BLOCK ***** + * Version: MPL 1.1/GPL 2.0/LGPL 2.1 + * + * The contents of this file are subject to the Mozilla Public License Version + * 1.1 (the "License"); you may not use this file except in compliance with + * the License. You may obtain a copy of the License at + * http://www.mozilla.org/MPL/ + * + * Software distributed under the License is distributed on an "AS IS" basis, + * WITHOUT WARRANTY OF ANY KIND, either express or implied. See the License + * for the specific language governing rights and limitations under the + * License. + * + * The Original Code is Mozilla code. + * + * The Initial Developer of the Original Code is the Mozilla Foundation. + * Portions created by the Initial Developer are Copyright (C) 2010 + * the Initial Developer. All Rights Reserved. + * + * Contributor(s): + * Matthew Gregan + * + * Alternatively, the contents of this file may be used under the terms of + * either the GNU General Public License Version 2 or later (the "GPL"), or + * the GNU Lesser General Public License Version 2.1 or later (the "LGPL"), + * in which case the provisions of the GPL or the LGPL are applicable instead + * of those above. If you wish to allow use of your version of this file only + * under the terms of either the GPL or the LGPL, and not to allow others to + * use your version of this file under the terms of the MPL, indicate your + * decision by deleting the provisions above and replace them with the notice + * and other provisions required by the GPL or the LGPL. If you do not delete + * the provisions above, a recipient may use your version of this file under + * the terms of any one of the MPL, the GPL or the LGPL. + * + * ***** END LICENSE BLOCK ***** */ +#if !defined(nsWaveReader_h_) +#define nsWaveReader_h_ + +#include "nsBuiltinDecoderReader.h" + +class nsMediaDecoder; + +class nsWaveReader : public nsBuiltinDecoderReader +{ +public: + nsWaveReader(nsBuiltinDecoder* aDecoder); + ~nsWaveReader(); + + virtual nsresult Init(nsBuiltinDecoderReader* aCloneDonor); + virtual PRBool DecodeAudioData(); + virtual PRBool DecodeVideoFrame(PRBool &aKeyframeSkip, + PRInt64 aTimeThreshold); + + virtual PRBool HasAudio() + { + return PR_TRUE; + } + + virtual PRBool HasVideo() + { + return PR_FALSE; + } + + virtual nsresult ReadMetadata(nsVideoInfo* aInfo); + virtual nsresult Seek(PRInt64 aTime, PRInt64 aStartTime, PRInt64 aEndTime, PRInt64 aCurrentTime); + virtual nsresult GetBuffered(nsTimeRanges* aBuffered, PRInt64 aStartTime); + +private: + PRBool ReadAll(char* aBuf, PRInt64 aSize, PRInt64* aBytesRead = nsnull); + PRBool LoadRIFFChunk(); + PRBool ScanForwardUntil(PRUint32 aWantedChunk, PRUint32* aChunkSize); + PRBool LoadFormatChunk(); + PRBool FindDataOffset(); + + // 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. The time is rounded to the nearest millisecond. + float BytesToTime(PRInt64 aBytes) const; + + // 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. + PRInt64 TimeToBytes(float aTime) const; + + // Rounds aBytes down to the nearest complete sample. Assumes beginning + // of byte range is already sample aligned by caller. + PRInt64 RoundDownToSample(PRInt64 aBytes) const; + PRInt64 GetDataLength(); + PRInt64 GetPosition(); + + /* + Metadata extracted from the WAVE header. Used to initialize the audio + stream, and for byte<->time domain conversions. + */ + + // Number of samples per second. Limited to range [100, 96000] in LoadFormatChunk. + PRUint32 mSampleRate; + + // Number of channels. Limited to range [1, 2] in LoadFormatChunk. + PRUint32 mChannels; + + // Size of a single sample segment, which includes a sample for each + // channel (interleaved). + PRUint32 mSampleSize; + + // The sample format of the PCM data. + nsAudioStream::SampleFormat mSampleFormat; + + // Size of PCM data stored in the WAVE as reported by the data chunk in + // the media. + PRInt64 mWaveLength; + + // Start offset of the PCM data in the media stream. Extends mWaveLength + // bytes. + PRInt64 mWavePCMOffset; +}; + +#endif