diff options
Diffstat (limited to 'sound/mp3.cpp')
-rw-r--r-- | sound/mp3.cpp | 239 |
1 files changed, 237 insertions, 2 deletions
diff --git a/sound/mp3.cpp b/sound/mp3.cpp index db73f7f394..f23a4e4248 100644 --- a/sound/mp3.cpp +++ b/sound/mp3.cpp @@ -19,13 +19,13 @@ * */ -#include "stdafx.h" - #include "sound/mp3.h" +#include "sound/audiostream.h" #include "common/file.h" #include "common/util.h" #ifdef USE_MAD + MP3TrackInfo::MP3TrackInfo(File *file) { struct mad_stream stream; struct mad_frame frame; @@ -124,4 +124,239 @@ MP3TrackInfo::~MP3TrackInfo() { _file->close(); } + +#pragma mark - +#pragma mark --- MP3 (MAD) stream --- +#pragma mark - + + +class MP3InputStream : public AudioInputStream { + struct mad_stream _stream; + struct mad_frame _frame; + struct mad_synth _synth; + mad_timer_t _duration; + uint32 _posInFrame; + uint32 _bufferSize; + int _size; + bool _isStereo; + int _curChannel; + File *_file; + byte *_ptr; + + bool init(); + void refill(bool first = false); + inline bool eosIntern() const; +public: + MP3InputStream(File *file, mad_timer_t duration, uint size = 0); + ~MP3InputStream(); + int readBuffer(int16 *buffer, const int numSamples); + + int16 read(); + bool eos() const { return eosIntern(); } + bool isStereo() const { return _isStereo; } + + int getRate() const { return _frame.header.samplerate; } +}; + + +/** + * Playback the MP3 data in the given file for the specified duration. + * + * @param file file containing the MP3 data + * @param duration playback duration in frames (1/75th of a second), 0 means + * playback until EOF + * @param size optional, if non-zero this limits playback based on the + * number of input bytes rather then a duration + */ +MP3InputStream::MP3InputStream(File *file, mad_timer_t duration, uint size) { + // duration == 0 means: play everything till end of file + + mad_stream_init(&_stream); + mad_frame_init(&_frame); + mad_synth_init(&_synth); + + _duration = duration; + + _posInFrame = 0; + _bufferSize = size ? size : (128 * 1024); // Default buffer size is 128K + + _isStereo = false; + _curChannel = 0; + _file = file; + _ptr = (byte *)malloc(_bufferSize + MAD_BUFFER_GUARD); + + init(); + + // If a size is specified, we do not perform any further read operations + if (size) { + _file = 0; + } +} + +MP3InputStream::~MP3InputStream() { + mad_synth_finish(&_synth); + mad_frame_finish(&_frame); + mad_stream_finish(&_stream); + + free(_ptr); +} + +bool MP3InputStream::init() { + // TODO + + // Read in the first chunk of the MP3 file + _size = _file->read(_ptr, _bufferSize); + if (_size <= 0) { + warning("MP3InputStream: Failed to read MP3 data"); + return false; + } + + // Feed the data we just read into the stream decoder + mad_stream_buffer(&_stream, _ptr, _size); + + // Read in initial data + refill(true); + + // Check the header, determine if this is a stereo stream + int num; + switch(_frame.header.mode) { + case MAD_MODE_SINGLE_CHANNEL: + case MAD_MODE_DUAL_CHANNEL: + case MAD_MODE_JOINT_STEREO: + case MAD_MODE_STEREO: + num = MAD_NCHANNELS(&_frame.header); + assert(num == 1 || num == 2); + _isStereo = (num == 2); + break; + default: + warning("MP3InputStream: Cannot determine number of channels"); + return false; + } + + return true; +} + +void MP3InputStream::refill(bool first) { + + // Read the next frame (may have to retry several times, e.g. + // to skip over ID3 information). + while (mad_frame_decode(&_frame, &_stream)) { + if (_stream.error == MAD_ERROR_BUFLEN) { + int offset; + + if (!_file) + _size = -1; + + // Give up immediately if we are at the EOF already + if (_size <= 0) + return; + + if (!_stream.next_frame) { + offset = 0; + memset(_ptr, 0, _bufferSize + MAD_BUFFER_GUARD); + } else { + offset = _stream.bufend - _stream.next_frame; + memcpy(_ptr, _stream.next_frame, offset); + } + // Read in more data from the input file + _size = _file->read(_ptr + offset, _bufferSize - offset); + + // Nothing read -> EOF -> bail out + if (_size <= 0) { + return; + } + _stream.error = (enum mad_error)0; + + // Feed the data we just read into the stream decoder + mad_stream_buffer(&_stream, _ptr, _size + offset); + + } else if (MAD_RECOVERABLE(_stream.error)) { + // FIXME: should we do anything here? + debug(6, "MP3InputStream: Recoverable error..."); + } else { + error("MP3InputStream: Unrecoverable error"); + } + } + + // Subtract the duration of this frame from the time left to play + mad_timer_t frame_duration = _frame.header.duration; + mad_timer_negate(&frame_duration); + mad_timer_add(&_duration, frame_duration); + + if (!first && _file && mad_timer_compare(_duration, mad_timer_zero) <= 0) + _size = -1; // Mark for EOF + + // Synthesise the frame into PCM samples and reset the buffer position + mad_synth_frame(&_synth, &_frame); + _posInFrame = 0; +} + +inline bool MP3InputStream::eosIntern() const { + return (_size < 0 || _posInFrame >= _synth.pcm.length); +} + +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); +} + +inline int16 MP3InputStream::read() { + assert(!eosIntern()); + + int16 sample; + if (_isStereo) { + sample = (int16)scale_sample(_synth.pcm.samples[_curChannel][_posInFrame]); + if (_curChannel == 0) { + _curChannel = 1; + } else { + _posInFrame++; + _curChannel = 0; + } + } else { + sample = (int16)scale_sample(_synth.pcm.samples[0][_posInFrame]); + _posInFrame++; + } + + if (_posInFrame >= _synth.pcm.length) { + refill(); + } + + return sample; +} + +int MP3InputStream::readBuffer(int16 *buffer, const int numSamples) { + int samples = 0; + assert(_curChannel == 0); // Paranoia check + while (samples < numSamples && !eosIntern()) { + const int len = MIN(numSamples, samples + (int)(_synth.pcm.length - _posInFrame) * (_isStereo ? 2 : 1)); + while (samples < len) { + *buffer++ = (int16)scale_sample(_synth.pcm.samples[0][_posInFrame]); + samples++; + if (_isStereo) { + *buffer++ = (int16)scale_sample(_synth.pcm.samples[1][_posInFrame]); + samples++; + } + _posInFrame++; + } + if (_posInFrame >= _synth.pcm.length) { + refill(); + } + } + return samples; +} + +AudioInputStream *makeMP3Stream(File *file, mad_timer_t duration, uint size) { + return new MP3InputStream(file, duration, size); +} + + #endif |