aboutsummaryrefslogtreecommitdiff
path: root/audio/decoders/mp3.cpp
diff options
context:
space:
mode:
authorMatthew Hoops2014-06-09 20:50:40 -0400
committerMatthew Hoops2015-08-30 19:53:53 -0400
commit52f67cba39f466c41ec7e6505e7f48626642e62e (patch)
treec86d67c9bb56cba18d54d2338fbb07dcee7d4a7c /audio/decoders/mp3.cpp
parent030e4d06088cb75e871f1373b662d14262fbfc93 (diff)
downloadscummvm-rg350-52f67cba39f466c41ec7e6505e7f48626642e62e.tar.gz
scummvm-rg350-52f67cba39f466c41ec7e6505e7f48626642e62e.tar.bz2
scummvm-rg350-52f67cba39f466c41ec7e6505e7f48626642e62e.zip
AUDIO: Split the seeking MP3 class from the base decoding stream
Diffstat (limited to 'audio/decoders/mp3.cpp')
-rw-r--r--audio/decoders/mp3.cpp237
1 files changed, 135 insertions, 102 deletions
diff --git a/audio/decoders/mp3.cpp b/audio/decoders/mp3.cpp
index feb531f5ae..8550d9116d 100644
--- a/audio/decoders/mp3.cpp
+++ b/audio/decoders/mp3.cpp
@@ -27,6 +27,7 @@
#include "common/debug.h"
#include "common/ptr.h"
#include "common/stream.h"
+#include "common/substream.h"
#include "common/textconsole.h"
#include "common/util.h"
@@ -45,20 +46,34 @@ namespace Audio {
#pragma mark -
-class MP3Stream : public SeekableAudioStream {
+class BaseMP3Stream : public virtual AudioStream {
+public:
+ BaseMP3Stream();
+ virtual ~BaseMP3Stream();
+
+ bool endOfData() const { return _state == MP3_STATE_EOS; }
+ bool isStereo() const { return MAD_NCHANNELS(&_frame.header) == 2; }
+ int getRate() const { return _frame.header.samplerate; }
+
protected:
+ void decodeMP3Data(Common::ReadStream &stream);
+ void readMP3Data(Common::ReadStream &stream);
+
+ void initStream(Common::ReadStream &stream);
+ void readHeader(Common::ReadStream &stream);
+ void deinitStream();
+
+ int fillBuffer(Common::ReadStream &stream, int16 *buffer, const int numSamples);
+
enum State {
MP3_STATE_INIT, // Need to init the decoder
MP3_STATE_READY, // ready for processing data
MP3_STATE_EOS // end of data reached (may need to loop)
};
- Common::DisposablePtr<Common::SeekableReadStream> _inStream;
-
uint _posInFrame;
State _state;
- Timestamp _length;
mad_timer_t _curTime;
mad_stream _stream;
@@ -71,81 +86,54 @@ protected:
// This buffer contains a slab of input data
byte _buf[BUFFER_SIZE + MAD_BUFFER_GUARD];
+};
+class MP3Stream : public BaseMP3Stream, public SeekableAudioStream {
public:
MP3Stream(Common::SeekableReadStream *inStream,
DisposeAfterUse::Flag dispose);
- ~MP3Stream();
+ virtual ~MP3Stream() {}
int readBuffer(int16 *buffer, const int numSamples);
-
- bool endOfData() const { return _state == MP3_STATE_EOS; }
- bool isStereo() const { return MAD_NCHANNELS(&_frame.header) == 2; }
- int getRate() const { return _frame.header.samplerate; }
-
bool seek(const Timestamp &where);
Timestamp getLength() const { return _length; }
+
protected:
- void decodeMP3Data();
- void readMP3Data();
+ Common::ScopedPtr<Common::SeekableReadStream> _inStream;
- void initStream();
- void readHeader();
- void deinitStream();
+ Timestamp _length;
+
+private:
+ static Common::SeekableReadStream *skipID3(Common::SeekableReadStream *stream, DisposeAfterUse::Flag dispose);
};
-MP3Stream::MP3Stream(Common::SeekableReadStream *inStream, DisposeAfterUse::Flag dispose) :
- _inStream(inStream, dispose),
+
+BaseMP3Stream::BaseMP3Stream() :
_posInFrame(0),
_state(MP3_STATE_INIT),
- _length(0, 1000),
_curTime(mad_timer_zero) {
// The MAD_BUFFER_GUARD must always contain zeros (the reason
// for this is that the Layer III Huffman decoder of libMAD
// may read a few bytes beyond the end of the input buffer).
memset(_buf + BUFFER_SIZE, 0, MAD_BUFFER_GUARD);
-
- // Calculate the length of the stream
- initStream();
-
- while (_state != MP3_STATE_EOS)
- readHeader();
-
- // To rule out any invalid sample rate to be encountered here, say in case the
- // MP3 stream is invalid, we just check the MAD error code here.
- // We need to assure this, since else we might trigger an assertion in Timestamp
- // (When getRate() returns 0 or a negative number to be precise).
- // Note that we allow "MAD_ERROR_BUFLEN" as error code here, since according
- // to mad.h it is also set on EOF.
- if ((_stream.error == MAD_ERROR_NONE || _stream.error == MAD_ERROR_BUFLEN) && getRate() > 0)
- _length = Timestamp(mad_timer_count(_curTime, MAD_UNITS_MILLISECONDS), getRate());
-
- deinitStream();
-
- // Reinit stream
- _state = MP3_STATE_INIT;
-
- // Decode the first chunk of data. This is necessary so that _frame
- // is setup and isStereo() and getRate() return correct results.
- decodeMP3Data();
}
-MP3Stream::~MP3Stream() {
+BaseMP3Stream::~BaseMP3Stream() {
deinitStream();
}
-void MP3Stream::decodeMP3Data() {
+void BaseMP3Stream::decodeMP3Data(Common::ReadStream &stream) {
do {
if (_state == MP3_STATE_INIT)
- initStream();
+ initStream(stream);
if (_state == MP3_STATE_EOS)
return;
// If necessary, load more data into the stream decoder
if (_stream.error == MAD_ERROR_BUFLEN)
- readMP3Data();
+ readMP3Data(stream);
while (_state == MP3_STATE_READY) {
_stream.error = MAD_ERROR_NONE;
@@ -179,11 +167,11 @@ void MP3Stream::decodeMP3Data() {
_state = MP3_STATE_EOS;
}
-void MP3Stream::readMP3Data() {
+void BaseMP3Stream::readMP3Data(Common::ReadStream &stream) {
uint32 remaining = 0;
// Give up immediately if we already used up all data in the stream
- if (_inStream->eos()) {
+ if (stream.eos()) {
_state = MP3_STATE_EOS;
return;
}
@@ -198,7 +186,7 @@ void MP3Stream::readMP3Data() {
}
// Try to read the next block
- uint32 size = _inStream->read(_buf + remaining, BUFFER_SIZE - remaining);
+ uint32 size = stream.read(_buf + remaining, BUFFER_SIZE - remaining);
if (size <= 0) {
_state = MP3_STATE_EOS;
return;
@@ -209,31 +197,7 @@ void MP3Stream::readMP3Data() {
mad_stream_buffer(&_stream, _buf, size + remaining);
}
-bool MP3Stream::seek(const Timestamp &where) {
- if (where == _length) {
- _state = MP3_STATE_EOS;
- return true;
- } else if (where > _length) {
- return false;
- }
-
- const uint32 time = where.msecs();
-
- mad_timer_t destination;
- mad_timer_set(&destination, time / 1000, time % 1000, 1000);
-
- if (_state != MP3_STATE_READY || mad_timer_compare(destination, _curTime) < 0)
- initStream();
-
- while (mad_timer_compare(destination, _curTime) > 0 && _state != MP3_STATE_EOS)
- readHeader();
-
- decodeMP3Data();
-
- return (_state != MP3_STATE_EOS);
-}
-
-void MP3Stream::initStream() {
+void BaseMP3Stream::initStream(Common::ReadStream &stream) {
if (_state != MP3_STATE_INIT)
deinitStream();
@@ -243,41 +207,23 @@ void MP3Stream::initStream() {
mad_synth_init(&_synth);
// Reset the stream data
- _inStream->seek(0, SEEK_SET);
_curTime = mad_timer_zero;
_posInFrame = 0;
-
- // Skip ID3 TAG if any
- // ID3v1 (beginning with with 'TAG') is located at the end of files. So we can ignore those.
- // ID3v2 can be located at the start of files and begins with a 10 bytes header, the first 3 bytes being 'ID3'.
- // The tag size is coded on the last 4 bytes of the 10 bytes header as a 32 bit synchsafe integer.
- // See http://id3.org/id3v2.4.0-structure for details.
- char data[10];
- _inStream->read(data, 10);
- if (data[0] == 'I' && data[1] == 'D' && data[2] == '3') {
- uint32 size = data[9] + 128 * (data[8] + 128 * (data[7] + 128 * data[6]));
- // This size does not include an optional 10 bytes footer. Check if it is present.
- if (data[5] & 0x10)
- size += 10;
- debug("Skipping ID3 TAG (%d bytes)", size + 10);
- _inStream->seek(size, SEEK_CUR);
- } else
- _inStream->seek(0, SEEK_SET);
// Update state
_state = MP3_STATE_READY;
// Read the first few sample bytes
- readMP3Data();
+ readMP3Data(stream);
}
-void MP3Stream::readHeader() {
+void BaseMP3Stream::readHeader(Common::ReadStream &stream) {
if (_state != MP3_STATE_READY)
return;
// If necessary, load more data into the stream decoder
if (_stream.error == MAD_ERROR_BUFLEN)
- readMP3Data();
+ readMP3Data(stream);
while (_state != MP3_STATE_EOS) {
_stream.error = MAD_ERROR_NONE;
@@ -287,7 +233,7 @@ void MP3Stream::readHeader() {
// be far too slow). Hence we perform this explicitly in a separate step.
if (mad_header_decode(&_frame.header, &_stream) == -1) {
if (_stream.error == MAD_ERROR_BUFLEN) {
- readMP3Data(); // Read more data
+ readMP3Data(stream); // Read more data
continue;
} else if (MAD_RECOVERABLE(_stream.error)) {
debug(6, "MP3Stream: Recoverable error in mad_header_decode (%s)", mad_stream_errorstr(&_stream));
@@ -307,7 +253,7 @@ void MP3Stream::readHeader() {
_state = MP3_STATE_EOS;
}
-void MP3Stream::deinitStream() {
+void BaseMP3Stream::deinitStream() {
if (_state == MP3_STATE_INIT)
return;
@@ -319,7 +265,7 @@ void MP3Stream::deinitStream() {
_state = MP3_STATE_EOS;
}
-static inline int scale_sample(mad_fixed_t sample) {
+static inline int scaleSample(mad_fixed_t sample) {
// round
sample += (1L << (MAD_F_FRACBITS - 16));
@@ -333,28 +279,115 @@ static inline int scale_sample(mad_fixed_t sample) {
return sample >> (MAD_F_FRACBITS + 1 - 16);
}
-int MP3Stream::readBuffer(int16 *buffer, const int numSamples) {
+int BaseMP3Stream::fillBuffer(Common::ReadStream &stream, int16 *buffer, const int numSamples) {
int samples = 0;
// Keep going as long as we have input available
while (samples < numSamples && _state != MP3_STATE_EOS) {
const int len = MIN(numSamples, samples + (int)(_synth.pcm.length - _posInFrame) * MAD_NCHANNELS(&_frame.header));
while (samples < len) {
- *buffer++ = (int16)scale_sample(_synth.pcm.samples[0][_posInFrame]);
+ *buffer++ = (int16)scaleSample(_synth.pcm.samples[0][_posInFrame]);
samples++;
if (MAD_NCHANNELS(&_frame.header) == 2) {
- *buffer++ = (int16)scale_sample(_synth.pcm.samples[1][_posInFrame]);
+ *buffer++ = (int16)scaleSample(_synth.pcm.samples[1][_posInFrame]);
samples++;
}
_posInFrame++;
}
if (_posInFrame >= _synth.pcm.length) {
// We used up all PCM data in the current frame -- read & decode more
- decodeMP3Data();
+ decodeMP3Data(stream);
}
}
return samples;
}
+MP3Stream::MP3Stream(Common::SeekableReadStream *inStream, DisposeAfterUse::Flag dispose) :
+ BaseMP3Stream(),
+ _inStream(skipID3(inStream, dispose)),
+ _length(0, 1000) {
+
+ // Initialize the stream with some data
+ decodeMP3Data(*_inStream);
+
+ // Calculate the length of the stream
+ while (_state != MP3_STATE_EOS)
+ readHeader(*_inStream);
+
+ // To rule out any invalid sample rate to be encountered here, say in case the
+ // MP3 stream is invalid, we just check the MAD error code here.
+ // We need to assure this, since else we might trigger an assertion in Timestamp
+ // (When getRate() returns 0 or a negative number to be precise).
+ // Note that we allow "MAD_ERROR_BUFLEN" as error code here, since according
+ // to mad.h it is also set on EOF.
+ if ((_stream.error == MAD_ERROR_NONE || _stream.error == MAD_ERROR_BUFLEN) && getRate() > 0)
+ _length = Timestamp(mad_timer_count(_curTime, MAD_UNITS_MILLISECONDS), getRate());
+
+ deinitStream();
+
+ // Reinit stream
+ _state = MP3_STATE_INIT;
+ _inStream->seek(0);
+
+ // Decode the first chunk of data. This is necessary so that _frame
+ // is setup and isStereo() and getRate() return correct results.
+ decodeMP3Data(*_inStream);
+}
+
+int MP3Stream::readBuffer(int16 *buffer, const int numSamples) {
+ return fillBuffer(*_inStream, buffer, numSamples);
+}
+
+bool MP3Stream::seek(const Timestamp &where) {
+ if (where == _length) {
+ _state = MP3_STATE_EOS;
+ return true;
+ } else if (where > _length) {
+ return false;
+ }
+
+ const uint32 time = where.msecs();
+
+ mad_timer_t destination;
+ mad_timer_set(&destination, time / 1000, time % 1000, 1000);
+
+ if (_state != MP3_STATE_READY || mad_timer_compare(destination, _curTime) < 0) {
+ _inStream->seek(0);
+ initStream(*_inStream);
+ }
+
+ while (mad_timer_compare(destination, _curTime) > 0 && _state != MP3_STATE_EOS)
+ readHeader(*_inStream);
+
+ decodeMP3Data(*_inStream);
+
+ return (_state != MP3_STATE_EOS);
+}
+
+Common::SeekableReadStream *MP3Stream::skipID3(Common::SeekableReadStream *stream, DisposeAfterUse::Flag dispose) {
+ // Skip ID3 TAG if any
+ // ID3v1 (beginning with with 'TAG') is located at the end of files. So we can ignore those.
+ // ID3v2 can be located at the start of files and begins with a 10 bytes header, the first 3 bytes being 'ID3'.
+ // The tag size is coded on the last 4 bytes of the 10 bytes header as a 32 bit synchsafe integer.
+ // See http://id3.org/id3v2.4.0-structure for details.
+ char data[10];
+ stream->read(data, sizeof(data));
+
+ uint32 offset = 0;
+ if (!stream->eos() && data[0] == 'I' && data[1] == 'D' && data[2] == '3') {
+ uint32 size = data[9] + 128 * (data[8] + 128 * (data[7] + 128 * data[6]));
+ // This size does not include an optional 10 bytes footer. Check if it is present.
+ if (data[5] & 0x10)
+ size += 10;
+
+ // Add in the 10 bytes we read in
+ size += sizeof(data);
+ debug("Skipping ID3 TAG (%d bytes)", size);
+ offset = size;
+ }
+
+ return new Common::SeekableSubReadStream(stream, offset, stream->size(), dispose);
+}
+
#pragma mark -
#pragma mark --- MP3 factory functions ---