mirror of
https://github.com/mozilla/gecko-dev.git
synced 2024-11-25 13:51:41 +00:00
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:
parent
6d2667f933
commit
8c92942a6f
@ -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
|
||||
|
||||
|
@ -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 += \
|
||||
|
@ -16,7 +16,7 @@ var manager = new MediaTestManager;
|
||||
|
||||
var tokens = {
|
||||
0: ["canplay"],
|
||||
"canplay": ["canplaythrough"],
|
||||
"canplay": ["canplay", "canplaythrough"],
|
||||
"canplaythrough": ["canplay", "canplaythrough"]
|
||||
};
|
||||
|
||||
|
53
content/media/test/test_wave_data_s16.html
Normal file
53
content/media/test/test_wave_data_s16.html
Normal 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>
|
53
content/media/test/test_wave_data_u8.html
Normal file
53
content/media/test/test_wave_data_u8.html
Normal 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>
|
BIN
content/media/test/wavedata_s16.wav
Normal file
BIN
content/media/test/wavedata_s16.wav
Normal file
Binary file not shown.
BIN
content/media/test/wavedata_u8.wav
Normal file
BIN
content/media/test/wavedata_u8.wav
Normal file
Binary file not shown.
@ -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
@ -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
|
||||
|
551
content/media/wave/nsWaveReader.cpp
Normal file
551
content/media/wave/nsWaveReader.cpp
Normal 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();
|
||||
}
|
120
content/media/wave/nsWaveReader.h
Normal file
120
content/media/wave/nsWaveReader.h
Normal 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
|
Loading…
Reference in New Issue
Block a user