Bug 635649 - Refactor Wave backend to use content/media nsBuiltinDecoder framework - r=kinetik

commit c7e190d81b10e7425b53217352c126adfbb79c4a
Author: Chris Double <chris.double@double.co.nz>
Date:   Thu Mar 24 18:09:06 2011 +1300

    Fix
This commit is contained in:
Chris Double 2011-03-30 18:37:42 +13:00
parent 6d2667f933
commit 8c92942a6f
12 changed files with 808 additions and 1985 deletions

View File

@ -119,6 +119,7 @@ typedef short SoundDataValue;
(static_cast<SoundDataValue>(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

View File

@ -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 += \

View File

@ -16,7 +16,7 @@ var manager = new MediaTestManager;
var tokens = {
0: ["canplay"],
"canplay": ["canplaythrough"],
"canplay": ["canplay", "canplaythrough"],
"canplaythrough": ["canplay", "canplaythrough"]
};

View File

@ -0,0 +1,53 @@
<!DOCTYPE HTML>
<html>
<head>
<title>Wave Media test: ended</title>
<script type="text/javascript" src="/MochiKit/packed.js"></script>
<script type="text/javascript" src="/tests/SimpleTest/SimpleTest.js"></script>
<link rel="stylesheet" type="text/css" href="/tests/SimpleTest/test.css" />
</head>
<body>
<pre id="test">
<script class="testbody" type="text/javascript">
// Test if the ended event works correctly.
var endPassed = false;
var completed = false;
function audioavailable(e) {
if (completed)
return false;
completed = true;
var samples = e.frameBuffer;
var time = e.time;
ok(samples.length >= 3, "Must be 3 or more samples. There were " + samples.length);
if (samples.length >= 3) {
ok(samples[0] > 0.99 && samples[0] < 1.01, "First sound sample should be close to 1.0. It was " + samples[0]);
ok(samples[1] > -1.01 && samples [1] < 0.01, "Second sound sample should be close to -1.0. It was " + samples[1]);
ok(samples[2] > -0.01 && samples[2] < 0.01, "Third sound sample should be close to 0. It was " + samples[2]);
}
// Only care about the first few samples
SimpleTest.finish();
return false;
}
function startTest() {
if (completed)
return false;
var v = document.getElementById('v');
v.addEventListener('MozAudioAvailable', audioavailable, false);
v.play();
return false;
}
SimpleTest.waitForExplicitFinish();
</script>
</pre>
<audio id='v'
onloadedmetadata='return startTest();'>
<source type='audio/x-wav' src='wavedata_s16.wav'>
</audio>
</body>
</html>

View File

@ -0,0 +1,53 @@
<!DOCTYPE HTML>
<html>
<head>
<title>Wave Media test: ended</title>
<script type="text/javascript" src="/MochiKit/packed.js"></script>
<script type="text/javascript" src="/tests/SimpleTest/SimpleTest.js"></script>
<link rel="stylesheet" type="text/css" href="/tests/SimpleTest/test.css" />
</head>
<body>
<pre id="test">
<script class="testbody" type="text/javascript">
// Test if the ended event works correctly.
var endPassed = false;
var completed = false;
function audioavailable(e) {
if (completed)
return false;
completed = true;
var samples = e.frameBuffer;
var time = e.time;
ok(samples.length >= 3, "Must be 3 or more samples. There were " + samples.length);
if (samples.length >= 3) {
ok(samples[0] > 0.99 && samples[0] < 1.01, "First sound sample should be close to 1.0. It was " + samples[0]);
ok(samples[1] > -1.01 && samples [1] < 0.01, "Second sound sample should be close to -1.0. It was " + samples[1]);
ok(samples[2] > -0.01 && samples[2] < 0.01, "Third sound sample should be close to 0. It was " + samples[2]);
}
// Only care about the first few samples
SimpleTest.finish();
return false;
}
function startTest() {
if (completed)
return false;
var v = document.getElementById('v');
v.addEventListener('MozAudioAvailable', audioavailable, false);
v.play();
return false;
}
SimpleTest.waitForExplicitFinish();
</script>
</pre>
<audio id='v'
onloadedmetadata='return startTest();'>
<source type='audio/x-wav' src='wavedata_u8.wav'>
</audio>
</body>
</html>

Binary file not shown.

Binary file not shown.

View File

@ -52,6 +52,7 @@ EXPORTS += \
CPPSRCS = \
nsWaveDecoder.cpp \
nsWaveReader.cpp \
$(NULL)
FORCE_STATIC_LIB = 1

File diff suppressed because it is too large Load Diff

View File

@ -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<nsIPrincipal> 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<nsIThread> mPlaybackThread;
// State machine that runs on mPlaybackThread. Methods on this object are
// safe to call from any thread.
nsCOMPtr<nsWaveStateMachine> mPlaybackStateMachine;
// Threadsafe wrapper around channels that provides seeking based on the
// underlying channel type.
nsAutoPtr<nsMediaStream> 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

View File

@ -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 <kinetik@flim.org>
*
* 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<PRInt16>(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<PRInt64>(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<size_t>(samples * mChannels);
nsAutoArrayPtr<SoundDataValue> sampleBuffer(new SoundDataValue[bufferSize]);
PR_STATIC_ASSERT(PRUint64(BLOCK_SIZE) < UINT_MAX / sizeof(char));
nsAutoArrayPtr<char> dataBuffer(new char[static_cast<size_t>(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<PRInt64>(posTime * 1000),
static_cast<PRInt64>(readSizeTime * 1000),
static_cast<PRInt32>(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<PRInt64>(d) * 1000;
PRInt64 seekTime = NS_MIN(aTarget, duration);
PRInt64 position = RoundDownToSample(static_cast<PRInt64>(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<char> 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<char> 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();
}

View File

@ -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 <kinetik@flim.org>
*
* 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