diff options
Diffstat (limited to 'audio/decoders/mp3.cpp')
-rw-r--r-- | audio/decoders/mp3.cpp | 375 |
1 files changed, 375 insertions, 0 deletions
diff --git a/audio/decoders/mp3.cpp b/audio/decoders/mp3.cpp new file mode 100644 index 0000000000..53d68fa9db --- /dev/null +++ b/audio/decoders/mp3.cpp @@ -0,0 +1,375 @@ +/* ScummVM - Graphic Adventure Engine + * + * ScummVM is the legal property of its developers, whose names + * are too numerous to list here. Please refer to the COPYRIGHT + * file distributed with this source distribution. + * + * This program is free software; you can redistribute it and/or + * modify it under the terms of the GNU General Public License + * as published by the Free Software Foundation; either version 2 + * of the License, or (at your option) any later version. + + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software + * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. + * + * $URL$ + * $Id$ + * + */ + +#include "audio/decoders/mp3.h" + +#ifdef USE_MAD + +#include "common/debug.h" +#include "common/stream.h" +#include "common/util.h" + +#include "audio/audiostream.h" + +#include <mad.h> + +#if defined(__PSP__) + #include "backends/platform/psp/mp3.h" +#endif +namespace Audio { + + +#pragma mark - +#pragma mark --- MP3 (MAD) stream --- +#pragma mark - + + +class MP3Stream : public SeekableAudioStream { +protected: + 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::SeekableReadStream *_inStream; + DisposeAfterUse::Flag _disposeAfterUse; + + uint _posInFrame; + State _state; + + Timestamp _length; + mad_timer_t _totalTime; + + mad_stream _stream; + mad_frame _frame; + mad_synth _synth; + + enum { + BUFFER_SIZE = 5 * 8192 + }; + + // This buffer contains a slab of input data + byte _buf[BUFFER_SIZE + MAD_BUFFER_GUARD]; + +public: + MP3Stream(Common::SeekableReadStream *inStream, + DisposeAfterUse::Flag dispose); + ~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(); + + void initStream(); + void readHeader(); + void deinitStream(); +}; + +MP3Stream::MP3Stream(Common::SeekableReadStream *inStream, DisposeAfterUse::Flag dispose) : + _inStream(inStream), + _disposeAfterUse(dispose), + _posInFrame(0), + _state(MP3_STATE_INIT), + _length(0, 1000), + _totalTime(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(_totalTime, 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() { + deinitStream(); + + if (_disposeAfterUse == DisposeAfterUse::YES) + delete _inStream; +} + +void MP3Stream::decodeMP3Data() { + do { + if (_state == MP3_STATE_INIT) + initStream(); + + if (_state == MP3_STATE_EOS) + return; + + // If necessary, load more data into the stream decoder + if (_stream.error == MAD_ERROR_BUFLEN) + readMP3Data(); + + while (_state == MP3_STATE_READY) { + _stream.error = MAD_ERROR_NONE; + + // Decode the next frame + if (mad_frame_decode(&_frame, &_stream) == -1) { + if (_stream.error == MAD_ERROR_BUFLEN) { + break; // Read more data + } else if (MAD_RECOVERABLE(_stream.error)) { + // Note: we will occasionally see MAD_ERROR_BADDATAPTR errors here. + // These are normal and expected (caused by our frame skipping (i.e. "seeking") + // code above). + debug(6, "MP3Stream: Recoverable error in mad_frame_decode (%s)", mad_stream_errorstr(&_stream)); + continue; + } else { + warning("MP3Stream: Unrecoverable error in mad_frame_decode (%s)", mad_stream_errorstr(&_stream)); + break; + } + } + + // Synthesize PCM data + mad_synth_frame(&_synth, &_frame); + _posInFrame = 0; + break; + } + } while (_state != MP3_STATE_EOS && _stream.error == MAD_ERROR_BUFLEN); + + if (_stream.error != MAD_ERROR_NONE) + _state = MP3_STATE_EOS; +} + +void MP3Stream::readMP3Data() { + uint32 remaining = 0; + + // Give up immediately if we already used up all data in the stream + if (_inStream->eos()) { + _state = MP3_STATE_EOS; + return; + } + + if (_stream.next_frame) { + // If there is still data in the MAD stream, we need to preserve it. + // Note that we use memmove, as we are reusing the same buffer, + // and hence the data regions we copy from and to may overlap. + remaining = _stream.bufend - _stream.next_frame; + assert(remaining < BUFFER_SIZE); // Paranoia check + memmove(_buf, _stream.next_frame, remaining); + } + + // Try to read the next block + uint32 size = _inStream->read(_buf + remaining, BUFFER_SIZE - remaining); + if (size <= 0) { + _state = MP3_STATE_EOS; + return; + } + + // Feed the data we just read into the stream decoder + _stream.error = MAD_ERROR_NONE; + 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, _totalTime) < 0) + initStream(); + + while (mad_timer_compare(destination, _totalTime) > 0 && _state != MP3_STATE_EOS) + readHeader(); + + decodeMP3Data(); + + return (_state != MP3_STATE_EOS); +} + +void MP3Stream::initStream() { + if (_state != MP3_STATE_INIT) + deinitStream(); + + // Init MAD + mad_stream_init(&_stream); + mad_frame_init(&_frame); + mad_synth_init(&_synth); + + // Reset the stream data + _inStream->seek(0, SEEK_SET); + _totalTime = mad_timer_zero; + _posInFrame = 0; + + // Update state + _state = MP3_STATE_READY; + + // Read the first few sample bytes + readMP3Data(); +} + +void MP3Stream::readHeader() { + if (_state != MP3_STATE_READY) + return; + + // If necessary, load more data into the stream decoder + if (_stream.error == MAD_ERROR_BUFLEN) + readMP3Data(); + + while (_state != MP3_STATE_EOS) { + _stream.error = MAD_ERROR_NONE; + + // Decode the next header. Note: mad_frame_decode would do this for us, too. + // However, for seeking we don't want to decode the full frame (else it would + // 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 + continue; + } else if (MAD_RECOVERABLE(_stream.error)) { + debug(6, "MP3Stream: Recoverable error in mad_header_decode (%s)", mad_stream_errorstr(&_stream)); + continue; + } else { + warning("MP3Stream: Unrecoverable error in mad_header_decode (%s)", mad_stream_errorstr(&_stream)); + break; + } + } + + // Sum up the total playback time so far + mad_timer_add(&_totalTime, _frame.header.duration); + break; + } + + if (_stream.error != MAD_ERROR_NONE) + _state = MP3_STATE_EOS; +} + +void MP3Stream::deinitStream() { + if (_state == MP3_STATE_INIT) + return; + + // Deinit MAD + mad_synth_finish(&_synth); + mad_frame_finish(&_frame); + mad_stream_finish(&_stream); + + _state = MP3_STATE_EOS; +} + +static inline int scale_sample(mad_fixed_t sample) { + // round + sample += (1L << (MAD_F_FRACBITS - 16)); + + // clip + if (sample > MAD_F_ONE - 1) + sample = MAD_F_ONE - 1; + else if (sample < -MAD_F_ONE) + sample = -MAD_F_ONE; + + // quantize and scale to not saturate when mixing a lot of channels + return sample >> (MAD_F_FRACBITS + 1 - 16); +} + +int MP3Stream::readBuffer(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]); + samples++; + if (MAD_NCHANNELS(&_frame.header) == 2) { + *buffer++ = (int16)scale_sample(_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(); + } + } + return samples; +} + + +#pragma mark - +#pragma mark --- MP3 factory functions --- +#pragma mark - + +SeekableAudioStream *makeMP3Stream( + Common::SeekableReadStream *stream, + DisposeAfterUse::Flag disposeAfterUse) { + +#if defined(__PSP__) + SeekableAudioStream *s = 0; + + if (Mp3PspStream::isOkToCreateStream()) + s = new Mp3PspStream(stream, disposeAfterUse); + + if (!s) // go to regular MAD mp3 stream if ME fails + s = new MP3Stream(stream, disposeAfterUse); +#else + SeekableAudioStream *s = new MP3Stream(stream, disposeAfterUse); +#endif + if (s && s->endOfData()) { + delete s; + return 0; + } else { + return s; + } +} + +} // End of namespace Audio + +#endif // #ifdef USE_MAD |