mirror of
https://github.com/libretro/scummvm.git
synced 2024-12-15 22:28:10 +00:00
65b30d84a8
svn-id: r25836
425 lines
11 KiB
C++
425 lines
11 KiB
C++
/* ScummVM - Scumm Interpreter
|
|
* Copyright (C) 2003-2006 The ScummVM project
|
|
*
|
|
* 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 "sound/mp3.h"
|
|
|
|
#ifdef USE_MAD
|
|
|
|
#include "common/file.h"
|
|
#include "common/util.h"
|
|
|
|
#include "sound/audiocd.h"
|
|
#include "sound/audiostream.h"
|
|
|
|
#include <mad.h>
|
|
|
|
|
|
namespace Audio {
|
|
|
|
|
|
#pragma mark -
|
|
#pragma mark --- MP3 (MAD) stream ---
|
|
#pragma mark -
|
|
|
|
|
|
class MP3InputStream : public AudioStream {
|
|
protected:
|
|
Common::SeekableReadStream *_inStream;
|
|
bool _disposeAfterUse;
|
|
|
|
uint _numLoops;
|
|
uint _posInFrame;
|
|
bool _eos;
|
|
|
|
const mad_timer_t _startTime;
|
|
const mad_timer_t _endTime;
|
|
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:
|
|
MP3InputStream(Common::SeekableReadStream *inStream,
|
|
bool dispose,
|
|
mad_timer_t start = mad_timer_zero,
|
|
mad_timer_t end = mad_timer_zero,
|
|
uint numLoops = 1);
|
|
~MP3InputStream();
|
|
|
|
int readBuffer(int16 *buffer, const int numSamples);
|
|
|
|
bool endOfData() const { return _eos; }
|
|
bool isStereo() const { return MAD_NCHANNELS(&_frame.header) == 2; }
|
|
int getRate() const { return _frame.header.samplerate; }
|
|
|
|
protected:
|
|
void rewind();
|
|
void decodeMP3Data();
|
|
bool readMP3Data();
|
|
};
|
|
|
|
MP3InputStream::MP3InputStream(Common::SeekableReadStream *inStream, bool dispose, mad_timer_t start, mad_timer_t end, uint numLoops) :
|
|
_inStream(inStream),
|
|
_disposeAfterUse(dispose),
|
|
_numLoops(numLoops),
|
|
_posInFrame(0),
|
|
_eos(false),
|
|
_startTime(start),
|
|
_endTime(end),
|
|
_totalTime(mad_timer_zero) {
|
|
|
|
// 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);
|
|
|
|
// 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);
|
|
|
|
// Init MAD
|
|
mad_stream_init(&_stream);
|
|
mad_frame_init(&_frame);
|
|
mad_synth_init(&_synth);
|
|
|
|
// Decode the first chunk of data.
|
|
decodeMP3Data();
|
|
}
|
|
|
|
MP3InputStream::~MP3InputStream() {
|
|
// Deinit MAD
|
|
mad_synth_finish(&_synth);
|
|
mad_frame_finish(&_frame);
|
|
mad_stream_finish(&_stream);
|
|
|
|
if (_disposeAfterUse)
|
|
delete _inStream;
|
|
}
|
|
|
|
void MP3InputStream::rewind() {
|
|
// Start over again: reset the decoders, seek back to the start of the file, etc.
|
|
|
|
mad_synth_finish(&_synth);
|
|
mad_frame_finish(&_frame);
|
|
mad_stream_finish(&_stream);
|
|
|
|
_inStream->seek(0, SEEK_SET);
|
|
_totalTime = mad_timer_zero;
|
|
_posInFrame = 0;
|
|
_eos = false;
|
|
|
|
// Reinit MAD
|
|
mad_stream_init(&_stream);
|
|
mad_frame_init(&_frame);
|
|
mad_synth_init(&_synth);
|
|
}
|
|
|
|
void MP3InputStream::decodeMP3Data() {
|
|
if (_eos)
|
|
return;
|
|
|
|
do {
|
|
// 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;
|
|
}
|
|
}
|
|
|
|
while (!_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) {
|
|
break; // Read more data
|
|
} else if (MAD_RECOVERABLE(_stream.error)) {
|
|
debug(6, "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;
|
|
}
|
|
}
|
|
|
|
// 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)) {
|
|
// 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, "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;
|
|
}
|
|
|
|
if (_eos && _numLoops != 1) {
|
|
// If looping is on and there are loops left, rewind to the start
|
|
if (_numLoops != 0)
|
|
_numLoops--;
|
|
rewind();
|
|
}
|
|
|
|
} while (_stream.error == MAD_ERROR_BUFLEN);
|
|
|
|
if (_stream.error != MAD_ERROR_NONE)
|
|
_eos = true;
|
|
}
|
|
|
|
bool MP3InputStream::readMP3Data() {
|
|
uint32 remaining = 0;
|
|
|
|
// Give up immediately if we already used up all data in the stream
|
|
if (_inStream->eos())
|
|
return false;
|
|
|
|
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) {
|
|
return false;
|
|
}
|
|
|
|
// Feed the data we just read into the stream decoder
|
|
_stream.error = MAD_ERROR_NONE;
|
|
mad_stream_buffer(&_stream, _buf, size + remaining);
|
|
|
|
return true;
|
|
}
|
|
|
|
|
|
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 MP3InputStream::readBuffer(int16 *buffer, const int numSamples) {
|
|
int samples = 0;
|
|
// 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 (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 -
|
|
|
|
|
|
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 into a MemoryReadStream
|
|
Common::MemoryReadStream *stream = file->readStream(size);
|
|
|
|
// .. and create an MP3InputStream from all this
|
|
return new MP3InputStream(stream, true);
|
|
}
|
|
|
|
AudioStream *makeMP3Stream(
|
|
Common::SeekableReadStream *stream,
|
|
bool disposeAfterUse,
|
|
uint32 startTime,
|
|
uint32 duration,
|
|
uint numLoops) {
|
|
|
|
mad_timer_t start;
|
|
mad_timer_t end;
|
|
|
|
// Both startTime and duration are given in milliseconds.
|
|
// Calculate the appropriate mad_timer_t values from them.
|
|
mad_timer_set(&start, startTime / 1000, startTime % 1000, 1000);
|
|
if (duration == 0) {
|
|
end = mad_timer_zero;
|
|
} else {
|
|
int endTime = startTime + duration;
|
|
mad_timer_set(&end, endTime / 1000, endTime % 1000, 1000);
|
|
}
|
|
|
|
return new MP3InputStream(stream, disposeAfterUse, start, end, numLoops);
|
|
}
|
|
|
|
|
|
#pragma mark -
|
|
#pragma mark --- MP3 Audio CD emulation ---
|
|
#pragma mark -
|
|
|
|
|
|
class MP3TrackInfo : public DigitalTrackInfo {
|
|
private:
|
|
Common::String _filename;
|
|
bool _errorFlag;
|
|
|
|
public:
|
|
MP3TrackInfo(const char *filename);
|
|
bool error() { return _errorFlag; }
|
|
void play(Mixer *mixer, SoundHandle *handle, int numLoops, int startFrame, int duration);
|
|
};
|
|
|
|
MP3TrackInfo::MP3TrackInfo(const char *filename) :
|
|
_filename(filename),
|
|
_errorFlag(false) {
|
|
|
|
// Try to open the file
|
|
Common::File file;
|
|
if (!file.open(_filename)) {
|
|
_errorFlag = true;
|
|
return;
|
|
}
|
|
|
|
// Next, try to create an MP3InputStream from it
|
|
MP3InputStream *tempStream = new MP3InputStream(&file, false);
|
|
|
|
// If we see EOS here then that means that not (enough) valid input
|
|
// data was given.
|
|
_errorFlag = tempStream->endOfData();
|
|
|
|
// Clean up again
|
|
delete tempStream;
|
|
}
|
|
|
|
void MP3TrackInfo::play(Mixer *mixer, SoundHandle *handle, int numLoops, int startFrame, int duration) {
|
|
assert(!_errorFlag);
|
|
|
|
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 {
|
|
int endFrame = startFrame + duration;
|
|
mad_timer_set(&end, endFrame / 75, endFrame % 75, 75);
|
|
}
|
|
|
|
// Open the file
|
|
Common::File *file = new Common::File();
|
|
if (!file || !file->open(_filename)) {
|
|
warning("MP3TrackInfo::play: failed to open '%s'", _filename.c_str());
|
|
delete file;
|
|
return;
|
|
}
|
|
|
|
// ... create an AudioStream ...
|
|
MP3InputStream *input = new MP3InputStream(file, true, start, end, numLoops);
|
|
|
|
// ... and play it
|
|
mixer->playInputStream(Audio::Mixer::kMusicSoundType, handle, input);
|
|
}
|
|
|
|
DigitalTrackInfo *getMP3Track(int track) {
|
|
char trackName[2][32];
|
|
|
|
sprintf(trackName[0], "track%d.mp3", track);
|
|
sprintf(trackName[1], "track%02d.mp3", track);
|
|
|
|
for (int i = 0; i < 2; ++i) {
|
|
if (Common::File::exists(trackName[i])) {
|
|
MP3TrackInfo *trackInfo = new MP3TrackInfo(trackName[i]);
|
|
if (!trackInfo->error())
|
|
return trackInfo;
|
|
delete trackInfo;
|
|
}
|
|
}
|
|
return NULL;
|
|
}
|
|
|
|
} // End of namespace Audio
|
|
|
|
#endif // #ifdef USE_MAD
|