aboutsummaryrefslogtreecommitdiff
path: root/sound/mp3.cpp
diff options
context:
space:
mode:
Diffstat (limited to 'sound/mp3.cpp')
-rw-r--r--sound/mp3.cpp239
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