aboutsummaryrefslogtreecommitdiff
diff options
context:
space:
mode:
-rw-r--r--sound/mp3.cpp600
1 files changed, 267 insertions, 333 deletions
diff --git a/sound/mp3.cpp b/sound/mp3.cpp
index c922cca4b0..8bf9f7af00 100644
--- a/sound/mp3.cpp
+++ b/sound/mp3.cpp
@@ -33,254 +33,201 @@
#include <mad.h>
-using Common::File;
-
-
namespace Audio {
+
#pragma mark -
#pragma mark --- MP3 (MAD) stream ---
#pragma mark -
class MP3InputStream : public AudioStream {
- struct mad_stream _stream;
- struct mad_frame _frame;
- struct mad_synth _synth;
- mad_timer_t _duration;
+protected:
+ mad_stream _stream;
+ mad_frame _frame;
+ mad_synth _synth;
+
+ mad_timer_t _startTime;
+ mad_timer_t _endTime;
+ mad_timer_t _totalTime;
+
+ // TODO: For looping, we will have to use a SeekableReadStream here
+ Common::ReadStream *_inStream;
+ bool _disposeAfterUse;
+
+ enum {
+ BUFFER_SIZE = 5 * 8192
+ };
+
+ // This buffer contains a slab of input data
+ byte _buf[BUFFER_SIZE + MAD_BUFFER_GUARD];
+
+
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;
+
+ bool _eos;
+
public:
- MP3InputStream(File *file, mad_timer_t duration);
- MP3InputStream(File *file, uint32 size);
+ MP3InputStream(Common::ReadStream *inStream,
+ bool dispose,
+ mad_timer_t start = mad_timer_zero,
+ mad_timer_t end = mad_timer_zero);
~MP3InputStream();
+
+ bool init();
+
int readBuffer(int16 *buffer, const int numSamples);
- bool endOfData() const { return eosIntern(); }
- bool isStereo() const { return _isStereo; }
-
+ bool endOfData() const { return _eos; }
+ bool isStereo() const { return MAD_NCHANNELS(&_frame.header) == 2; }
int getRate() const { return _frame.header.samplerate; }
-#ifdef __SYMBIAN32__
- // Used to store the last position stream was read for symbian
- int _lastReadPosition;
-#endif
-};
-
-
-/**
- * Play 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
- */
-MP3InputStream::MP3InputStream(File *file, mad_timer_t duration) {
- // duration == 0 means: play everything till end of file
- mad_stream_init(&_stream);
- mad_frame_init(&_frame);
- mad_synth_init(&_synth);
+protected:
+ void decodeMP3Data();
+ bool readMP3Data();
+};
- _duration = duration;
+MP3InputStream::MP3InputStream(Common::ReadStream *inStream, bool dispose, mad_timer_t start, mad_timer_t end) :
+ _inStream(inStream),
+ _disposeAfterUse(dispose),
+ _startTime(start),
+ _endTime(end),
+ _totalTime(mad_timer_zero),
+ _eos(false) {
-#if defined(__SYMBIAN32__)
- // Symbian can't share filehandles between different threads.
- // So create a new file and seek that to the other filehandles position
- _file= new File;
- _file->open(file->name());
- _file->seek(file->pos());
-#else
- _file = file;
-#endif
+ // Make sure that either start < end, or end is zero (indicating "play until end")
+ assert(mad_timer_compare(_startTime, _endTime) < 0 || mad_timer_sign(_endTime) == 0);
_posInFrame = 0;
- _bufferSize = 128 * 1024; // Default buffer size is 128K
+ _curChannel = 0;
- init();
- _file->incRef();
-}
+ // 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);
-/**
- * Play the MP3 data in the given file, using at most the given number of bytes.
- *
- * @param file file containing the MP3 data
- * @param size limits playback based on the number of input bytes, 0 means
- * playback until EOF
- */
-MP3InputStream::MP3InputStream(File *file, uint32 size) {
+ // Init MAD
mad_stream_init(&_stream);
mad_frame_init(&_frame);
mad_synth_init(&_synth);
- _duration = mad_timer_zero;
-
-#if defined(__SYMBIAN32__)
- // Symbian can't share filehandles between different threads.
- // So create a new file and seek that to the other filehandles position
- _file= new File;
- _file->open(file->name());
- _file->seek(file->pos());
-#else
- _file = file;
-#endif
-
- _posInFrame = 0;
- _bufferSize = size ? size : (128 * 1024); // Default buffer size is 128K
-
- init();
-
- // If a size is specified, we do not perform any further read operations
- if (size) {
-#ifdef __SYMBIAN32__
- delete _file;
-#endif
- _file = NULL;
- } else {
- _file->incRef();
- }
+ // Decode the first chunk of data.
+ decodeMP3Data();
}
MP3InputStream::~MP3InputStream() {
+ // Deinit MAD
mad_synth_finish(&_synth);
mad_frame_finish(&_frame);
mad_stream_finish(&_stream);
- free(_ptr);
-
- if (_file)
- _file->decRef();
-#ifdef __SYMBIAN32__
- delete _file;
-#endif
-}
-
-bool MP3InputStream::init() {
- _isStereo = false;
- _curChannel = 0;
- _ptr = (byte *)malloc(_bufferSize + MAD_BUFFER_GUARD);
-
- // 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;
+ if (_disposeAfterUse)
+ delete _inStream;
}
-void MP3InputStream::refill(bool first) {
-
- // Read the next frame (may have to retry several times, e.g.
- // to skip over ID3 information).
- // must reopen file at current position
-#ifdef __SYMBIAN32__
- // For symbian we must check that an alternative file pointer is created, see if its open
- // If not re-open file and seek to the last read position
- if (_file && !_file->isOpen()) {
- _file->open(_file->name());
- _file->seek(_lastReadPosition);
- }
-#endif
-
- while (mad_frame_decode(&_frame, &_stream)) {
- if (_stream.error == MAD_ERROR_BUFLEN) {
- int offset;
+void MP3InputStream::decodeMP3Data() {
+ if (_eos)
+ return;
- if (!_file)
- _size = -1;
+ do {
- // 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);
+ // If necessary, load more data
+ if (_stream.buffer == NULL || _stream.error == MAD_ERROR_BUFLEN) {
+ if (!readMP3Data()) {
+ // We tried to read more data but failed -> end of stream reached
+ _eos = true;
+ break;
}
- // Read in more data from the input file
- _size = _file->read(_ptr + offset, _bufferSize - offset);
+ }
- // Nothing read -> EOF -> bail out
- if (_size <= 0) {
- return;
+ while (true) {
+ _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) {
+ break; // Read more data
+ } else if (MAD_RECOVERABLE(_stream.error)) {
+ debug(1, "MP3InputStream: Recoverable error in mad_header_decode (%s)", mad_stream_errorstr(&_stream));
+ continue;
+ } else {
+ warning("MP3InputStream: Unrecoverable error in mad_header_decode (%s)", mad_stream_errorstr(&_stream));
+ break;
+ }
}
- _stream.error = (enum mad_error)0;
+
+ // Sum up the total playback time so far
+ mad_timer_add(&_totalTime, _frame.header.duration);
+
+ // If we have not yet reached the start point, skip to the next frame
+ if (mad_timer_compare(_totalTime, _startTime) < 0)
+ continue;
+
+ // If an end time is specified and we are past it, stop
+ if (mad_timer_sign(_endTime) > 0 && mad_timer_compare(_totalTime, _endTime) > 0) {
+ _eos = true;
+ break;
+ }
+
+ // 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)) {
+ // FIXME: should we do anything here?
+ debug(1, "MP3InputStream: Recoverable error in mad_frame_decode (%s)", mad_stream_errorstr(&_stream));
+ continue;
+ } else {
+ warning("MP3InputStream: Unrecoverable error in mad_frame_decode (%s)", mad_stream_errorstr(&_stream));
+ break;
+ }
+ }
+
+ // Synthesize PCM data
+ mad_synth_frame(&_synth, &_frame);
+ _posInFrame = 0;
+ break;
+ }
- // Feed the data we just read into the stream decoder
- mad_stream_buffer(&_stream, _ptr, _size + offset);
+ } while (_stream.error == MAD_ERROR_BUFLEN);
+
+ if (_stream.error != MAD_ERROR_NONE)
+ _eos = true;
+}
- } else if (MAD_RECOVERABLE(_stream.error)) {
- // FIXME: should we do anything here?
- debug(6, "MP3InputStream: Recoverable error...");
- } else {
- error("MP3InputStream: Unrecoverable error");
- }
- }
+bool MP3InputStream::readMP3Data() {
+ uint32 remaining = 0;
- // If a time limit was specified (i.e. if _duration is non-zero),
- // check the play back time to determine whether we have to stop now.
- if (_file && mad_timer_compare(_duration, mad_timer_zero) > 0) {
- // 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);
+ // Give up immediately if we already used up all data in the stream
+ if (_inStream->eos())
+ return false;
- if (!first && mad_timer_compare(_duration, mad_timer_zero) <= 0)
- _size = -1; // Mark for EOF
+ 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);
}
- // Synthesise the frame into PCM samples and reset the buffer position
- mad_synth_frame(&_synth, &_frame);
- _posInFrame = 0;
-
-#ifdef __SYMBIAN32__
- // For symbian we now store the last read position and then close the file
- if (_file) {
- _lastReadPosition = _file->pos();
- _file->close();
+ // Try to read the next block
+ uint32 size = _inStream->read(_buf + remaining, BUFFER_SIZE - remaining);
+ if (size <= 0) {
+ return false;
}
-#endif
-}
+
+ // Feed the data we just read into the stream decoder
+ _stream.error = MAD_ERROR_NONE;
+ mad_stream_buffer(&_stream, _buf, size + remaining);
-inline bool MP3InputStream::eosIntern() const {
- return (_size < 0 || _posInFrame >= _synth.pcm.length);
+ return true;
}
+
static inline int scale_sample(mad_fixed_t sample) {
// round
sample += (1L << (MAD_F_FRACBITS - 16));
@@ -298,29 +245,57 @@ static inline int scale_sample(mad_fixed_t 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));
+ // Keep going as long as we have input available
+ while (samples < numSamples && !_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 (_isStereo) {
+ if (MAD_NCHANNELS(&_frame.header) == 2) {
*buffer++ = (int16)scale_sample(_synth.pcm.samples[1][_posInFrame]);
samples++;
}
_posInFrame++;
}
if (_posInFrame >= _synth.pcm.length) {
- refill();
+ // We used up all PCM data in the current frame -- read & decode more
+ decodeMP3Data();
}
}
return samples;
}
-AudioStream *makeMP3Stream(File *file, uint32 size) {
- return new MP3InputStream(file, size);
+
+/*
+Crude factory function for MP3InputStream.
+To be replaced as soon as possible
+*/
+
+AudioStream *makeMP3Stream(Common::File *file, uint32 size) {
+ assert(file);
+
+ // FIXME: For now, just read the whole data into memory, and be done
+ // with it. Of course this is in general *not* a nice thing to do...
+
+ // If no size was specified, read the whole remainder of the file
+ if (!size)
+ size = file->size() - file->pos();
+
+ // Read 'size' bytes of data (or until EOF is reached)
+ byte *buf = (byte *)malloc(size);
+ size = file->read(buf, size);
+
+
+ // Wrap the buffer into a MemoryReadStream ...
+ Common::ReadStream *stream = new Common::MemoryReadStream(buf, size, true);
+ // .. and create a MP3InputStream from all this
+ return new MP3InputStream(stream, true);
}
+
+
+
#pragma mark -
#pragma mark --- MP3 Audio CD emulation ---
#pragma mark -
@@ -328,169 +303,128 @@ AudioStream *makeMP3Stream(File *file, uint32 size) {
class MP3TrackInfo : public DigitalTrackInfo {
private:
- struct mad_header _mad_header;
- long _size;
- File *_file;
- bool _error_flag;
+ Common::String _filename;
+ bool _errorFlag;
public:
- MP3TrackInfo(File *file);
- ~MP3TrackInfo();
- bool error() { return _error_flag; }
+ MP3TrackInfo(const char *filename);
+ bool error() { return _errorFlag; }
void play(Audio::Mixer *mixer, Audio::SoundHandle *handle, int startFrame, int duration);
};
-MP3TrackInfo::MP3TrackInfo(File *file) {
-#ifdef __SYMBIAN32__
- // This data is taking a rather big room on symbians limited stack
- // Create the buffers on the heap instead and let the stream and frame be references instead
- struct mad_stream* streamptr = (struct mad_stream*)malloc(sizeof(struct mad_stream));
- struct mad_frame* frameptr = (struct mad_frame*)malloc(sizeof(struct mad_frame));
- unsigned char* bufferptr = (unsigned char*)malloc(8192);
-
- struct mad_stream& stream = *streamptr;
- struct mad_frame& frame = *frameptr;
- unsigned char* buffer = bufferptr;
- uint sizeofbuffer = 8192;
-#else
- struct mad_stream stream;
- struct mad_frame frame;
- unsigned char buffer[8192];
- uint sizeofbuffer = sizeof(buffer);
-#endif
- unsigned int buflen = 0;
- int count = 0;
-
- // Check the format and bitrate
- mad_stream_init(&stream);
- mad_frame_init(&frame);
-
- while (1) {
- if (buflen < sizeofbuffer) {
- int bytes;
-
- bytes = file->read(buffer + buflen, sizeofbuffer - buflen);
- if (bytes <= 0) {
- if (bytes == -1) {
- warning("Invalid file format");
- goto error;
- }
- break;
- }
-
- buflen += bytes;
- }
-
- mad_stream_buffer(&stream, buffer, buflen);
-
- while (1) {
- if (mad_frame_decode(&frame, &stream) == -1) {
- if (!MAD_RECOVERABLE(stream.error))
- break;
-
- if (stream.error != MAD_ERROR_BADCRC)
- continue;
- }
-
- if (count++)
- break;
- }
-
- if (count || stream.error != MAD_ERROR_BUFLEN)
- break;
-
- memmove(buffer, stream.next_frame,
- buflen = &buffer[buflen] - stream.next_frame);
- }
+MP3TrackInfo::MP3TrackInfo(const char *filename) :
+ _filename(filename),
+ _errorFlag(false) {
- if (count)
- memcpy(&_mad_header, &frame.header, sizeof(mad_header));
- else {
- warning("Invalid file format");
- goto error;
+ Common::File file;
+
+ // Try to open the file
+ if (!file.open(_filename)) {
+ _errorFlag = true;
+ return;
}
-
- mad_frame_finish(&frame);
- mad_stream_finish(&stream);
- // Get file size
- _size = file->size();
- _file = file;
- _error_flag = false;
-#ifdef __SYMBIAN32__
- // Free the heap reservations
- free(frameptr);
- free(streamptr);
- free(bufferptr);
-#endif
- return;
-
-error:
- mad_frame_finish(&frame);
- mad_stream_finish(&stream);
- _error_flag = true;
- delete file;
-#ifdef __SYMBIAN32__
- // Free the heap reservations
- free(frameptr);
- free(streamptr);
- free(bufferptr);
-#endif
+
+ // Next, try to create a MP3InputStream from it
+
+ MP3InputStream *mp3Stream = new MP3InputStream(&file, false);
+
+ // If we see EOS here then that means that not (enough) valid input
+ // data was given.
+ _errorFlag = mp3Stream->endOfData();
+
+ // Clean up again
+ delete mp3Stream;
}
void MP3TrackInfo::play(Audio::Mixer *mixer, Audio::SoundHandle *handle, int startFrame, int duration) {
- unsigned int offset;
- mad_timer_t durationTime;
-
- // Calc offset. As all bitrates are in kilobit per seconds, the division by 200 is always exact
- offset = (startFrame * (_mad_header.bitrate / (8 * 25))) / 3;
-#ifdef __SYMBIAN32__
- // Reopen the file if it is not open yet
- if (!_file->isOpen())
- _file->open(_file->name());
-#endif
- _file->seek(offset, SEEK_SET);
-
- // Calc delay
- if (!duration) {
- // FIXME: Using _size here is a problem if offset (or equivalently
- // startFrame) is non-zero.
- mad_timer_set(&durationTime, (_size * 8) / _mad_header.bitrate,
- (_size * 8) % _mad_header.bitrate, _mad_header.bitrate);
+ mad_timer_t start;
+ mad_timer_t end;
+
+ // Both startFrame and duration are given in frames, where 75 frames are one second.
+ // Calculate the appropriate mad_timer_t values from them.
+ mad_timer_set(&start, startFrame / 75, startFrame % 75, 75);
+ if (duration == 0) {
+ end = mad_timer_zero;
} else {
- mad_timer_set(&durationTime, duration / 75, duration % 75, 75);
+ int endFrame = startFrame + duration;
+ mad_timer_set(&end, endFrame / 75, endFrame % 75, 75);
}
+ // Open the file
+ Common::File *file = new Common::File();
+ assert(file);
+ file->open(_filename);
+ assert(file->isOpen());
+
// Play it
- AudioStream *input = new MP3InputStream(_file, durationTime);
+ AudioStream *input = new MP3InputStream(file, true, start, end);
+
mixer->playInputStream(Audio::Mixer::kMusicSoundType, handle, input);
}
-MP3TrackInfo::~MP3TrackInfo() {
- if (! _error_flag)
- _file->close();
-}
-
DigitalTrackInfo *getMP3Track(int track) {
char trackName[2][32];
- File *file = new File();
sprintf(trackName[0], "track%d.mp3", track);
sprintf(trackName[1], "track%02d.mp3", track);
for (int i = 0; i < 2; ++i) {
- if (file->open(trackName[i])) {
- MP3TrackInfo *trackInfo = new MP3TrackInfo(file);
+ if (Common::File::exists(trackName[i])) {
+ MP3TrackInfo *trackInfo = new MP3TrackInfo(trackName[i]);
if (!trackInfo->error())
return trackInfo;
delete trackInfo;
}
}
- delete file;
return NULL;
}
+#pragma mark -
+
+
+// TODO: Write a factory function (or maybe multiple).
+// It would take:
+// - Either a SeekableReadStream, *or* a memory buffer + length
+// (in fact, this might warrant having two factories. Using the
+// memory buffer directly, instead of a MemoryReadStream, is a
+// a lot more efficient, as it requires both less memory and less
+// CPU time. On the other hand, we still want to support playback
+// of big files.
+//
+// This would probably be easiest done by two subclasses, and suitable virtual
+// methods: MemBufferMP3InputStream vs. StreamMP3InputStream or so
+//
+// Further parameters:
+// - a start time (optional, 0 by default)
+// - a duration/end time (optional, 0 or -1 by default to indicate "play all").
+// - a disposeStorage bool, indicating whether we take over ownership of the
+// stream/mem buffer and hence should dispose it after use, or not
+// - a numLoops parameter: int giving the number of reptitions, -1 or 0 for inifinity
+//
+// Possible extra API (for a new AudioStream subclass):
+// - void setLooping(int numLoops, time start, time duration)
+// - void rewind
+// - void seek(time dstTime), maybe even allow relative seeking -- do we need this?
+
+//
+// Extra thoughts: the MP3InputStream should have a method to indicate to the
+// factory that something went wrong during creation, e.g. the data is not valid
+// MP3 data. The factory then would dispose the new object and return NULL.
+// Client code must be changed to handle that, of course.
+
+
+// As to DigitalTrackInfo: Of course eventually we want to get rid of it.
+// Until then, the DigitalTrackInfo should *not* create the stream until its
+// play method is called. This way, the hack for Symbian can hopefully be
+// removed completely.
+
+
+// Closing note: we added File::incRef and File::decRef mainly for the sake of the input streams
+// If we could but get rid of it...
+
+
} // End of namespace Audio
#endif // #ifdef USE_MAD