From 52f67cba39f466c41ec7e6505e7f48626642e62e Mon Sep 17 00:00:00 2001 From: Matthew Hoops Date: Mon, 9 Jun 2014 20:50:40 -0400 Subject: AUDIO: Split the seeking MP3 class from the base decoding stream --- audio/decoders/mp3.cpp | 237 ++++++++++++++++++++++++++++--------------------- 1 file changed, 135 insertions(+), 102 deletions(-) (limited to 'audio') 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 _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 _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 --- -- cgit v1.2.3