diff options
Diffstat (limited to 'audio/decoders')
-rw-r--r-- | audio/decoders/adpcm.cpp | 851 | ||||
-rw-r--r-- | audio/decoders/adpcm.h | 90 | ||||
-rw-r--r-- | audio/decoders/aiff.cpp | 186 | ||||
-rw-r--r-- | audio/decoders/aiff.h | 71 | ||||
-rw-r--r-- | audio/decoders/flac.cpp | 745 | ||||
-rw-r--r-- | audio/decoders/flac.h | 75 | ||||
-rw-r--r-- | audio/decoders/iff_sound.cpp | 130 | ||||
-rw-r--r-- | audio/decoders/iff_sound.h | 47 | ||||
-rw-r--r-- | audio/decoders/mac_snd.cpp | 116 | ||||
-rw-r--r-- | audio/decoders/mac_snd.h | 58 | ||||
-rw-r--r-- | audio/decoders/mp3.cpp | 375 | ||||
-rw-r--r-- | audio/decoders/mp3.h | 76 | ||||
-rw-r--r-- | audio/decoders/raw.cpp | 356 | ||||
-rw-r--r-- | audio/decoders/raw.h | 153 | ||||
-rw-r--r-- | audio/decoders/vag.cpp | 150 | ||||
-rw-r--r-- | audio/decoders/vag.h | 60 | ||||
-rw-r--r-- | audio/decoders/voc.cpp | 403 | ||||
-rw-r--r-- | audio/decoders/voc.h | 107 | ||||
-rw-r--r-- | audio/decoders/vorbis.cpp | 262 | ||||
-rw-r--r-- | audio/decoders/vorbis.h | 75 | ||||
-rw-r--r-- | audio/decoders/wave.cpp | 194 | ||||
-rw-r--r-- | audio/decoders/wave.h | 84 |
22 files changed, 4664 insertions, 0 deletions
diff --git a/audio/decoders/adpcm.cpp b/audio/decoders/adpcm.cpp new file mode 100644 index 0000000000..7def89b688 --- /dev/null +++ b/audio/decoders/adpcm.cpp @@ -0,0 +1,851 @@ +/* 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 "common/endian.h" + +#include "audio/decoders/adpcm.h" +#include "audio/audiostream.h" + + +namespace Audio { + +class ADPCMStream : public RewindableAudioStream { +protected: + Common::SeekableReadStream *_stream; + const DisposeAfterUse::Flag _disposeAfterUse; + const int32 _startpos; + const int32 _endpos; + const int _channels; + const uint32 _blockAlign; + uint32 _blockPos[2]; + const int _rate; + + struct { + // OKI/IMA + struct { + int32 last; + int32 stepIndex; + } ima_ch[2]; + } _status; + + virtual void reset(); + int16 stepAdjust(byte); + +public: + ADPCMStream(Common::SeekableReadStream *stream, DisposeAfterUse::Flag disposeAfterUse, uint32 size, int rate, int channels, uint32 blockAlign); + ~ADPCMStream(); + + virtual bool endOfData() const { return (_stream->eos() || _stream->pos() >= _endpos); } + virtual bool isStereo() const { return _channels == 2; } + virtual int getRate() const { return _rate; } + + virtual bool rewind(); +}; + +// Routines to convert 12 bit linear samples to the +// Dialogic or Oki ADPCM coding format aka VOX. +// See also <http://www.comptek.ru/telephony/tnotes/tt1-13.html> +// +// IMA ADPCM support is based on +// <http://wiki.multimedia.cx/index.php?title=IMA_ADPCM> +// +// In addition, also MS IMA ADPCM is supported. See +// <http://wiki.multimedia.cx/index.php?title=Microsoft_IMA_ADPCM>. + +ADPCMStream::ADPCMStream(Common::SeekableReadStream *stream, DisposeAfterUse::Flag disposeAfterUse, uint32 size, int rate, int channels, uint32 blockAlign) + : _stream(stream), + _disposeAfterUse(disposeAfterUse), + _startpos(stream->pos()), + _endpos(_startpos + size), + _channels(channels), + _blockAlign(blockAlign), + _rate(rate) { + + reset(); +} + +ADPCMStream::~ADPCMStream() { + if (_disposeAfterUse == DisposeAfterUse::YES) + delete _stream; +} + +void ADPCMStream::reset() { + memset(&_status, 0, sizeof(_status)); + _blockPos[0] = _blockPos[1] = _blockAlign; // To make sure first header is read +} + +bool ADPCMStream::rewind() { + // TODO: Error checking. + reset(); + _stream->seek(_startpos); + return true; +} + + +#pragma mark - + + +class Oki_ADPCMStream : public ADPCMStream { +public: + Oki_ADPCMStream(Common::SeekableReadStream *stream, DisposeAfterUse::Flag disposeAfterUse, uint32 size, int rate, int channels, uint32 blockAlign) + : ADPCMStream(stream, disposeAfterUse, size, rate, channels, blockAlign) {} + + virtual int readBuffer(int16 *buffer, const int numSamples); + +protected: + int16 decodeOKI(byte); +}; + +int Oki_ADPCMStream::readBuffer(int16 *buffer, const int numSamples) { + int samples; + byte data; + + assert(numSamples % 2 == 0); + + for (samples = 0; samples < numSamples && !_stream->eos() && _stream->pos() < _endpos; samples += 2) { + data = _stream->readByte(); + buffer[samples] = decodeOKI((data >> 4) & 0x0f); + buffer[samples + 1] = decodeOKI(data & 0x0f); + } + return samples; +} + +static const int16 okiStepSize[49] = { + 16, 17, 19, 21, 23, 25, 28, 31, + 34, 37, 41, 45, 50, 55, 60, 66, + 73, 80, 88, 97, 107, 118, 130, 143, + 157, 173, 190, 209, 230, 253, 279, 307, + 337, 371, 408, 449, 494, 544, 598, 658, + 724, 796, 876, 963, 1060, 1166, 1282, 1411, + 1552 +}; + +// Decode Linear to ADPCM +int16 Oki_ADPCMStream::decodeOKI(byte code) { + int16 diff, E, samp; + + E = (2 * (code & 0x7) + 1) * okiStepSize[_status.ima_ch[0].stepIndex] / 8; + diff = (code & 0x08) ? -E : E; + samp = _status.ima_ch[0].last + diff; + // Clip the values to +/- 2^11 (supposed to be 12 bits) + samp = CLIP<int16>(samp, -2048, 2047); + + _status.ima_ch[0].last = samp; + _status.ima_ch[0].stepIndex += stepAdjust(code); + _status.ima_ch[0].stepIndex = CLIP<int32>(_status.ima_ch[0].stepIndex, 0, ARRAYSIZE(okiStepSize) - 1); + + // * 16 effectively converts 12-bit input to 16-bit output + return samp * 16; +} + + +#pragma mark - + + +class Ima_ADPCMStream : public ADPCMStream { +protected: + int16 decodeIMA(byte code, int channel = 0); // Default to using the left channel/using one channel + +public: + Ima_ADPCMStream(Common::SeekableReadStream *stream, DisposeAfterUse::Flag disposeAfterUse, uint32 size, int rate, int channels, uint32 blockAlign) + : ADPCMStream(stream, disposeAfterUse, size, rate, channels, blockAlign) { + memset(&_status, 0, sizeof(_status)); + } + + virtual int readBuffer(int16 *buffer, const int numSamples); +}; + +int Ima_ADPCMStream::readBuffer(int16 *buffer, const int numSamples) { + int samples; + byte data; + + assert(numSamples % 2 == 0); + + for (samples = 0; samples < numSamples && !_stream->eos() && _stream->pos() < _endpos; samples += 2) { + data = _stream->readByte(); + buffer[samples] = decodeIMA((data >> 4) & 0x0f); + buffer[samples + 1] = decodeIMA(data & 0x0f, _channels == 2 ? 1 : 0); + } + return samples; +} + +#pragma mark - + + +class Apple_ADPCMStream : public Ima_ADPCMStream { +protected: + // Apple QuickTime IMA ADPCM + int32 _streamPos[2]; + int16 _buffer[2][2]; + uint8 _chunkPos[2]; + + void reset() { + Ima_ADPCMStream::reset(); + _chunkPos[0] = 0; + _chunkPos[1] = 0; + _streamPos[0] = 0; + _streamPos[1] = _blockAlign; + } + +public: + Apple_ADPCMStream(Common::SeekableReadStream *stream, DisposeAfterUse::Flag disposeAfterUse, uint32 size, int rate, int channels, uint32 blockAlign) + : Ima_ADPCMStream(stream, disposeAfterUse, size, rate, channels, blockAlign) { + _chunkPos[0] = 0; + _chunkPos[1] = 0; + _streamPos[0] = 0; + _streamPos[1] = _blockAlign; + } + + virtual int readBuffer(int16 *buffer, const int numSamples); + +}; + +int Apple_ADPCMStream::readBuffer(int16 *buffer, const int numSamples) { + // Need to write at least one samples per channel + assert((numSamples % _channels) == 0); + + // Current sample positions + int samples[2] = { 0, 0}; + + // Number of samples per channel + int chanSamples = numSamples / _channels; + + for (int i = 0; i < _channels; i++) { + _stream->seek(_streamPos[i]); + + while ((samples[i] < chanSamples) && + // Last byte read and a new one needed + !((_stream->eos() || (_stream->pos() >= _endpos)) && (_chunkPos[i] == 0))) { + + if (_blockPos[i] == _blockAlign) { + // 2 byte header per block + uint16 temp = _stream->readUint16BE(); + + // First 9 bits are the upper bits of the predictor + _status.ima_ch[i].last = (int16) (temp & 0xFF80); + // Lower 7 bits are the step index + _status.ima_ch[i].stepIndex = temp & 0x007F; + + // Clip the step index + _status.ima_ch[i].stepIndex = CLIP<int32>(_status.ima_ch[i].stepIndex, 0, 88); + + _blockPos[i] = 2; + } + + if (_chunkPos[i] == 0) { + // Decode data + byte data = _stream->readByte(); + _buffer[i][0] = decodeIMA(data & 0x0F, i); + _buffer[i][1] = decodeIMA(data >> 4, i); + } + + // The original is interleaved block-wise, we want it sample-wise + buffer[_channels * samples[i] + i] = _buffer[i][_chunkPos[i]]; + + if (++_chunkPos[i] > 1) { + // We're about to decode the next byte, so advance the block position + _chunkPos[i] = 0; + _blockPos[i]++; + } + + samples[i]++; + + if (_channels == 2) + if (_blockPos[i] == _blockAlign) + // We're at the end of the block. + // Since the channels are interleaved, skip the next block + _stream->skip(MIN<uint32>(_blockAlign, _endpos - _stream->pos())); + + _streamPos[i] = _stream->pos(); + } + } + + return samples[0] + samples[1]; +} + +#pragma mark - + + +class MSIma_ADPCMStream : public Ima_ADPCMStream { +public: + MSIma_ADPCMStream(Common::SeekableReadStream *stream, DisposeAfterUse::Flag disposeAfterUse, uint32 size, int rate, int channels, uint32 blockAlign, bool invertSamples = false) + : Ima_ADPCMStream(stream, disposeAfterUse, size, rate, channels, blockAlign), _invertSamples(invertSamples) { + if (blockAlign == 0) + error("ADPCMStream(): blockAlign isn't specified for MS IMA ADPCM"); + } + + virtual int readBuffer(int16 *buffer, const int numSamples) { + if (_channels == 1) + return readBufferMSIMA1(buffer, numSamples); + else + return readBufferMSIMA2(buffer, numSamples); + } + + int readBufferMSIMA1(int16 *buffer, const int numSamples); + int readBufferMSIMA2(int16 *buffer, const int numSamples); + +private: + bool _invertSamples; // Some implementations invert the way samples are decoded +}; + +int MSIma_ADPCMStream::readBufferMSIMA1(int16 *buffer, const int numSamples) { + int samples = 0; + byte data; + + assert(numSamples % 2 == 0); + + while (samples < numSamples && !_stream->eos() && _stream->pos() < _endpos) { + if (_blockPos[0] == _blockAlign) { + // read block header + _status.ima_ch[0].last = _stream->readSint16LE(); + _status.ima_ch[0].stepIndex = _stream->readSint16LE(); + _blockPos[0] = 4; + } + + for (; samples < numSamples && _blockPos[0] < _blockAlign && !_stream->eos() && _stream->pos() < _endpos; samples += 2) { + data = _stream->readByte(); + _blockPos[0]++; + buffer[samples] = decodeIMA(_invertSamples ? (data >> 4) & 0x0f : data & 0x0f); + buffer[samples + 1] = decodeIMA(_invertSamples ? data & 0x0f : (data >> 4) & 0x0f); + } + } + return samples; +} + + +// Microsoft as usual tries to implement it differently. This method +// is used for stereo data. +int MSIma_ADPCMStream::readBufferMSIMA2(int16 *buffer, const int numSamples) { + int samples; + uint32 data; + int nibble; + byte k; + + // TODO: Currently this implementation only supports + // reading a multiple of 16 samples at once. We might + // consider changing that so it could read an arbitrary + // sample pair count. + assert(numSamples % 16 == 0); + + for (samples = 0; samples < numSamples && !_stream->eos() && _stream->pos() < _endpos;) { + for (int channel = 0; channel < 2; channel++) { + data = _stream->readUint32LE(); + + for (nibble = 0; nibble < 8; nibble++) { + k = ((data & 0xf0000000) >> 28); + buffer[samples + channel + nibble * 2] = decodeIMA(k); + data <<= 4; + } + } + samples += 16; + } + return samples; +} + + +#pragma mark - + + +static const int MSADPCMAdaptCoeff1[] = { + 256, 512, 0, 192, 240, 460, 392 +}; + +static const int MSADPCMAdaptCoeff2[] = { + 0, -256, 0, 64, 0, -208, -232 +}; + +static const int MSADPCMAdaptationTable[] = { + 230, 230, 230, 230, 307, 409, 512, 614, + 768, 614, 512, 409, 307, 230, 230, 230 +}; + + +class MS_ADPCMStream : public ADPCMStream { +protected: + struct ADPCMChannelStatus { + byte predictor; + int16 delta; + int16 coeff1; + int16 coeff2; + int16 sample1; + int16 sample2; + }; + + struct { + // MS ADPCM + ADPCMChannelStatus ch[2]; + } _status; + + void reset() { + ADPCMStream::reset(); + memset(&_status, 0, sizeof(_status)); + } + +public: + MS_ADPCMStream(Common::SeekableReadStream *stream, DisposeAfterUse::Flag disposeAfterUse, uint32 size, int rate, int channels, uint32 blockAlign) + : ADPCMStream(stream, disposeAfterUse, size, rate, channels, blockAlign) { + if (blockAlign == 0) + error("MS_ADPCMStream(): blockAlign isn't specified for MS ADPCM"); + memset(&_status, 0, sizeof(_status)); + } + + virtual int readBuffer(int16 *buffer, const int numSamples); + +protected: + int16 decodeMS(ADPCMChannelStatus *c, byte); +}; + +int16 MS_ADPCMStream::decodeMS(ADPCMChannelStatus *c, byte code) { + int32 predictor; + + predictor = (((c->sample1) * (c->coeff1)) + ((c->sample2) * (c->coeff2))) / 256; + predictor += (signed)((code & 0x08) ? (code - 0x10) : (code)) * c->delta; + + predictor = CLIP<int32>(predictor, -32768, 32767); + + c->sample2 = c->sample1; + c->sample1 = predictor; + c->delta = (MSADPCMAdaptationTable[(int)code] * c->delta) >> 8; + + if (c->delta < 16) + c->delta = 16; + + return (int16)predictor; +} + +int MS_ADPCMStream::readBuffer(int16 *buffer, const int numSamples) { + int samples; + byte data; + int i = 0; + + samples = 0; + + while (samples < numSamples && !_stream->eos() && _stream->pos() < _endpos) { + if (_blockPos[0] == _blockAlign) { + // read block header + for (i = 0; i < _channels; i++) { + _status.ch[i].predictor = CLIP(_stream->readByte(), (byte)0, (byte)6); + _status.ch[i].coeff1 = MSADPCMAdaptCoeff1[_status.ch[i].predictor]; + _status.ch[i].coeff2 = MSADPCMAdaptCoeff2[_status.ch[i].predictor]; + } + + for (i = 0; i < _channels; i++) + _status.ch[i].delta = _stream->readSint16LE(); + + for (i = 0; i < _channels; i++) + _status.ch[i].sample1 = _stream->readSint16LE(); + + for (i = 0; i < _channels; i++) + buffer[samples++] = _status.ch[i].sample2 = _stream->readSint16LE(); + + for (i = 0; i < _channels; i++) + buffer[samples++] = _status.ch[i].sample1; + + _blockPos[0] = _channels * 7; + } + + for (; samples < numSamples && _blockPos[0] < _blockAlign && !_stream->eos() && _stream->pos() < _endpos; samples += 2) { + data = _stream->readByte(); + _blockPos[0]++; + buffer[samples] = decodeMS(&_status.ch[0], (data >> 4) & 0x0f); + buffer[samples + 1] = decodeMS(&_status.ch[_channels - 1], data & 0x0f); + } + } + + return samples; +} + + + +#pragma mark - + + +class Tinsel_ADPCMStream : public ADPCMStream { +protected: + struct { + // Tinsel + double predictor; + double K0, K1; + double d0, d1; + } _status; + + void reset() { + ADPCMStream::reset(); + memset(&_status, 0, sizeof(_status)); + } + + int16 decodeTinsel(int16, double); + void readBufferTinselHeader(); + +public: + Tinsel_ADPCMStream(Common::SeekableReadStream *stream, DisposeAfterUse::Flag disposeAfterUse, uint32 size, int rate, int channels, uint32 blockAlign) + : ADPCMStream(stream, disposeAfterUse, size, rate, channels, blockAlign) { + + if (blockAlign == 0) + error("Tinsel_ADPCMStream(): blockAlign isn't specified"); + + if (channels != 1) + error("Tinsel_ADPCMStream(): Tinsel ADPCM only supports mono"); + + memset(&_status, 0, sizeof(_status)); + } + +}; + +static const double TinselFilterTable[4][2] = { + {0, 0 }, + {0.9375, 0}, + {1.796875, -0.8125}, + {1.53125, -0.859375} +}; + +void Tinsel_ADPCMStream::readBufferTinselHeader() { + uint8 start = _stream->readByte(); + uint8 filterVal = (start & 0xC0) >> 6; + + if ((start & 0x20) != 0) { + //Lower 6 bit are negative + + // Negate + start = ~(start | 0xC0) + 1; + + _status.predictor = 1 << start; + } else { + // Lower 6 bit are positive + + // Truncate + start &= 0x1F; + + _status.predictor = ((double) 1.0) / (1 << start); + } + + _status.K0 = TinselFilterTable[filterVal][0]; + _status.K1 = TinselFilterTable[filterVal][1]; +} + +int16 Tinsel_ADPCMStream::decodeTinsel(int16 code, double eVal) { + double sample; + + sample = (double) code; + sample *= eVal * _status.predictor; + sample += (_status.d0 * _status.K0) + (_status.d1 * _status.K1); + + _status.d1 = _status.d0; + _status.d0 = sample; + + return (int16) CLIP<double>(sample, -32768.0, 32767.0); +} + +class Tinsel4_ADPCMStream : public Tinsel_ADPCMStream { +public: + Tinsel4_ADPCMStream(Common::SeekableReadStream *stream, DisposeAfterUse::Flag disposeAfterUse, uint32 size, int rate, int channels, uint32 blockAlign) + : Tinsel_ADPCMStream(stream, disposeAfterUse, size, rate, channels, blockAlign) {} + + virtual int readBuffer(int16 *buffer, const int numSamples); +}; + +int Tinsel4_ADPCMStream::readBuffer(int16 *buffer, const int numSamples) { + int samples; + uint16 data; + const double eVal = 1.142822265; + + samples = 0; + + assert(numSamples % 2 == 0); + + while (samples < numSamples && !_stream->eos() && _stream->pos() < _endpos) { + if (_blockPos[0] == _blockAlign) { + readBufferTinselHeader(); + _blockPos[0] = 0; + } + + for (; samples < numSamples && _blockPos[0] < _blockAlign && !_stream->eos() && _stream->pos() < _endpos; samples += 2, _blockPos[0]++) { + // Read 1 byte = 8 bits = two 4 bit blocks + data = _stream->readByte(); + buffer[samples] = decodeTinsel((data << 8) & 0xF000, eVal); + buffer[samples+1] = decodeTinsel((data << 12) & 0xF000, eVal); + } + } + + return samples; +} + +class Tinsel6_ADPCMStream : public Tinsel_ADPCMStream { +protected: + uint8 _chunkPos; + uint16 _chunkData; + + void reset() { + ADPCMStream::reset(); + _chunkPos = 0; + _chunkData = 0; + } + +public: + Tinsel6_ADPCMStream(Common::SeekableReadStream *stream, DisposeAfterUse::Flag disposeAfterUse, uint32 size, int rate, int channels, uint32 blockAlign) + : Tinsel_ADPCMStream(stream, disposeAfterUse, size, rate, channels, blockAlign) { + _chunkPos = 0; + _chunkData = 0; + } + + virtual int readBuffer(int16 *buffer, const int numSamples); +}; + +int Tinsel6_ADPCMStream::readBuffer(int16 *buffer, const int numSamples) { + int samples; + const double eVal = 1.032226562; + + samples = 0; + + while (samples < numSamples && !_stream->eos() && _stream->pos() < _endpos) { + if (_blockPos[0] == _blockAlign) { + readBufferTinselHeader(); + _blockPos[0] = 0; + _chunkPos = 0; + } + + for (; samples < numSamples && _blockPos[0] < _blockAlign && !_stream->eos() && _stream->pos() < _endpos; samples++, _chunkPos = (_chunkPos + 1) % 4) { + + switch (_chunkPos) { + case 0: + _chunkData = _stream->readByte(); + buffer[samples] = decodeTinsel((_chunkData << 8) & 0xFC00, eVal); + break; + case 1: + _chunkData = (_chunkData << 8) | (_stream->readByte()); + buffer[samples] = decodeTinsel((_chunkData << 6) & 0xFC00, eVal); + _blockPos[0]++; + break; + case 2: + _chunkData = (_chunkData << 8) | (_stream->readByte()); + buffer[samples] = decodeTinsel((_chunkData << 4) & 0xFC00, eVal); + _blockPos[0]++; + break; + case 3: + _chunkData = (_chunkData << 8); + buffer[samples] = decodeTinsel((_chunkData << 2) & 0xFC00, eVal); + _blockPos[0]++; + break; + } + + } + + } + + return samples; +} + +class Tinsel8_ADPCMStream : public Tinsel_ADPCMStream { +public: + Tinsel8_ADPCMStream(Common::SeekableReadStream *stream, DisposeAfterUse::Flag disposeAfterUse, uint32 size, int rate, int channels, uint32 blockAlign) + : Tinsel_ADPCMStream(stream, disposeAfterUse, size, rate, channels, blockAlign) {} + + virtual int readBuffer(int16 *buffer, const int numSamples); +}; + +int Tinsel8_ADPCMStream::readBuffer(int16 *buffer, const int numSamples) { + int samples; + byte data; + const double eVal = 1.007843258; + + samples = 0; + + while (samples < numSamples && !_stream->eos() && _stream->pos() < _endpos) { + if (_blockPos[0] == _blockAlign) { + readBufferTinselHeader(); + _blockPos[0] = 0; + } + + for (; samples < numSamples && _blockPos[0] < _blockAlign && !_stream->eos() && _stream->pos() < _endpos; samples++, _blockPos[0]++) { + // Read 1 byte = 8 bits = one 8 bit block + data = _stream->readByte(); + buffer[samples] = decodeTinsel(data << 8, eVal); + } + } + + return samples; +} + + +#pragma mark - + +// Duck DK3 IMA ADPCM Decoder +// Based on FFmpeg's decoder and http://wiki.multimedia.cx/index.php?title=Duck_DK3_IMA_ADPCM + +class DK3_ADPCMStream : public Ima_ADPCMStream { +protected: + + void reset() { + Ima_ADPCMStream::reset(); + _topNibble = false; + } + +public: + DK3_ADPCMStream(Common::SeekableReadStream *stream, DisposeAfterUse::Flag disposeAfterUse, uint32 size, int rate, int channels, uint32 blockAlign) + : Ima_ADPCMStream(stream, disposeAfterUse, size, rate, channels, blockAlign) { + + // DK3 only works as a stereo stream + assert(channels == 2); + _topNibble = false; + } + + virtual int readBuffer(int16 *buffer, const int numSamples); + +private: + byte _nibble, _lastByte; + bool _topNibble; +}; + +#define DK3_READ_NIBBLE() \ +do { \ + if (_topNibble) { \ + _nibble = _lastByte >> 4; \ + _topNibble = false; \ + } else { \ + if (_stream->pos() >= _endpos) \ + break; \ + if ((_stream->pos() % _blockAlign) == 0) \ + continue; \ + _lastByte = _stream->readByte(); \ + _nibble = _lastByte & 0xf; \ + _topNibble = true; \ + } \ +} while (0) + + +int DK3_ADPCMStream::readBuffer(int16 *buffer, const int numSamples) { + int samples = 0; + + assert((numSamples % 4) == 0); + + while (samples < numSamples && !_stream->eos() && _stream->pos() < _endpos) { + if ((_stream->pos() % _blockAlign) == 0) { + _stream->readUint16LE(); // Unknown + uint16 rate = _stream->readUint16LE(); // Copy of rate + _stream->skip(6); // Unknown + // Get predictor for both sum/diff channels + _status.ima_ch[0].last = _stream->readSint16LE(); + _status.ima_ch[1].last = _stream->readSint16LE(); + // Get index for both sum/diff channels + _status.ima_ch[0].stepIndex = _stream->readByte(); + _status.ima_ch[1].stepIndex = _stream->readByte(); + + if (_stream->eos()) + break; + + // Sanity check + assert(rate == getRate()); + } + + DK3_READ_NIBBLE(); + decodeIMA(_nibble, 0); + + DK3_READ_NIBBLE(); + decodeIMA(_nibble, 1); + + buffer[samples++] = _status.ima_ch[0].last + _status.ima_ch[1].last; + buffer[samples++] = _status.ima_ch[0].last - _status.ima_ch[1].last; + + DK3_READ_NIBBLE(); + decodeIMA(_nibble, 0); + + buffer[samples++] = _status.ima_ch[0].last + _status.ima_ch[1].last; + buffer[samples++] = _status.ima_ch[0].last - _status.ima_ch[1].last; + } + + return samples; +} + + +#pragma mark - + + +// adjust the step for use on the next sample. +int16 ADPCMStream::stepAdjust(byte code) { + static const int16 adjusts[] = {-1, -1, -1, -1, 2, 4, 6, 8}; + + return adjusts[code & 0x07]; +} + +static const uint16 imaStepTable[89] = { + 7, 8, 9, 10, 11, 12, 13, 14, + 16, 17, 19, 21, 23, 25, 28, 31, + 34, 37, 41, 45, 50, 55, 60, 66, + 73, 80, 88, 97, 107, 118, 130, 143, + 157, 173, 190, 209, 230, 253, 279, 307, + 337, 371, 408, 449, 494, 544, 598, 658, + 724, 796, 876, 963, 1060, 1166, 1282, 1411, + 1552, 1707, 1878, 2066, 2272, 2499, 2749, 3024, + 3327, 3660, 4026, 4428, 4871, 5358, 5894, 6484, + 7132, 7845, 8630, 9493,10442,11487,12635,13899, + 15289,16818,18500,20350,22385,24623,27086,29794, + 32767 +}; + +int16 Ima_ADPCMStream::decodeIMA(byte code, int channel) { + int32 E = (2 * (code & 0x7) + 1) * imaStepTable[_status.ima_ch[channel].stepIndex] / 8; + int32 diff = (code & 0x08) ? -E : E; + int32 samp = CLIP<int32>(_status.ima_ch[channel].last + diff, -32768, 32767); + + _status.ima_ch[channel].last = samp; + _status.ima_ch[channel].stepIndex += stepAdjust(code); + _status.ima_ch[channel].stepIndex = CLIP<int32>(_status.ima_ch[channel].stepIndex, 0, ARRAYSIZE(imaStepTable) - 1); + + return samp; +} + +RewindableAudioStream *makeADPCMStream(Common::SeekableReadStream *stream, DisposeAfterUse::Flag disposeAfterUse, uint32 size, typesADPCM type, int rate, int channels, uint32 blockAlign) { + // If size is 0, report the entire size of the stream + if (!size) + size = stream->size(); + + switch (type) { + case kADPCMOki: + return new Oki_ADPCMStream(stream, disposeAfterUse, size, rate, channels, blockAlign); + case kADPCMMSIma: + return new MSIma_ADPCMStream(stream, disposeAfterUse, size, rate, channels, blockAlign); + case kADPCMMSImaLastExpress: + return new MSIma_ADPCMStream(stream, disposeAfterUse, size, rate, channels, blockAlign, true); + case kADPCMMS: + return new MS_ADPCMStream(stream, disposeAfterUse, size, rate, channels, blockAlign); + case kADPCMTinsel4: + return new Tinsel4_ADPCMStream(stream, disposeAfterUse, size, rate, channels, blockAlign); + case kADPCMTinsel6: + return new Tinsel6_ADPCMStream(stream, disposeAfterUse, size, rate, channels, blockAlign); + case kADPCMTinsel8: + return new Tinsel8_ADPCMStream(stream, disposeAfterUse, size, rate, channels, blockAlign); + case kADPCMIma: + return new Ima_ADPCMStream(stream, disposeAfterUse, size, rate, channels, blockAlign); + case kADPCMApple: + return new Apple_ADPCMStream(stream, disposeAfterUse, size, rate, channels, blockAlign); + case kADPCMDK3: + return new DK3_ADPCMStream(stream, disposeAfterUse, size, rate, channels, blockAlign); + default: + error("Unsupported ADPCM encoding"); + break; + } +} + +} // End of namespace Audio diff --git a/audio/decoders/adpcm.h b/audio/decoders/adpcm.h new file mode 100644 index 0000000000..38ec870a27 --- /dev/null +++ b/audio/decoders/adpcm.h @@ -0,0 +1,90 @@ +/* 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$ + * + */ + +/** + * @file + * Sound decoder used in engines: + * - agos + * - lastexpress + * - mohawk + * - saga + * - scumm + * - tinsel + */ + +#ifndef SOUND_ADPCM_H +#define SOUND_ADPCM_H + +#include "common/scummsys.h" +#include "common/stream.h" + + +namespace Audio { + +class AudioStream; +class RewindableAudioStream; + +// There are several types of ADPCM encoding, only some are supported here +// For all the different encodings, refer to: +// http://wiki.multimedia.cx/index.php?title=Category:ADPCM_Audio_Codecs +// Usually, if the audio stream we're trying to play has the FourCC header +// string intact, it's easy to discern which encoding is used +enum typesADPCM { + kADPCMOki, // Dialogic/Oki ADPCM (aka VOX) + kADPCMMSIma, // Microsoft IMA ADPCM + kADPCMMSImaLastExpress, // Microsoft IMA ADPCM (with inverted samples) + kADPCMMS, // Microsoft ADPCM + kADPCMTinsel4, // 4-bit ADPCM used by the Tinsel engine + kADPCMTinsel6, // 6-bit ADPCM used by the Tinsel engine + kADPCMTinsel8, // 8-bit ADPCM used by the Tinsel engine + kADPCMIma, // Standard IMA ADPCM + kADPCMApple, // Apple QuickTime IMA ADPCM + kADPCMDK3 // Duck DK3 IMA ADPCM +}; + +/** + * Takes an input stream containing ADPCM compressed sound data and creates + * an RewindableAudioStream from that. + * + * @param stream the SeekableReadStream from which to read the ADPCM data + * @param disposeAfterUse whether to delete the stream after use + * @param size how many bytes to read from the stream (0 = all) + * @param type the compression type used + * @param rate the sampling rate + * @param channels the number of channels + * @param blockAlign block alignment ??? + * @return a new RewindableAudioStream, or NULL, if an error occurred + */ +RewindableAudioStream *makeADPCMStream( + Common::SeekableReadStream *stream, + DisposeAfterUse::Flag disposeAfterUse, + uint32 size, typesADPCM type, + int rate = 22050, + int channels = 2, + uint32 blockAlign = 0); + +} // End of namespace Audio + +#endif diff --git a/audio/decoders/aiff.cpp b/audio/decoders/aiff.cpp new file mode 100644 index 0000000000..0f947752f6 --- /dev/null +++ b/audio/decoders/aiff.cpp @@ -0,0 +1,186 @@ +/* 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$ + * + */ + +/* + * The code in this file is based on information found at + * http://www.borg.com/~jglatt/tech/aiff.htm + * + * We currently only implement uncompressed AIFF. If we ever need AIFF-C, SoX + * (http://sox.sourceforge.net) may be a good place to start from. + */ + +#include "common/endian.h" +#include "common/util.h" +#include "common/stream.h" + +#include "audio/decoders/aiff.h" +#include "audio/audiostream.h" +#include "audio/mixer.h" +#include "audio/decoders/raw.h" + +namespace Audio { + +uint32 readExtended(Common::SeekableReadStream &stream) { + // The sample rate is stored as an "80 bit IEEE Standard 754 floating + // point number (Standard Apple Numeric Environment [SANE] data type + // Extended). + + byte buf[10]; + uint32 mantissa; + uint32 last = 0; + byte exp; + + stream.read(buf, 10); + mantissa = READ_BE_UINT32(buf + 2); + exp = 30 - buf[1]; + + while (exp--) { + last = mantissa; + mantissa >>= 1; + } + + if (last & 0x00000001) + mantissa++; + + return mantissa; +} + +bool loadAIFFFromStream(Common::SeekableReadStream &stream, int &size, int &rate, byte &flags) { + byte buf[4]; + + stream.read(buf, 4); + if (memcmp(buf, "FORM", 4) != 0) { + warning("loadAIFFFromStream: No 'FORM' header"); + return false; + } + + stream.readUint32BE(); + + // This could be AIFC, but we don't handle that case. + + stream.read(buf, 4); + if (memcmp(buf, "AIFF", 4) != 0) { + warning("loadAIFFFromStream: No 'AIFF' header"); + return false; + } + + // From here on, we only care about the COMM and SSND chunks, which are + // the only required chunks. + + bool foundCOMM = false; + bool foundSSND = false; + + uint16 numChannels = 0, bitsPerSample = 0; + uint32 numSampleFrames = 0, offset = 0, blockSize = 0, soundOffset = 0; + + while (!(foundCOMM && foundSSND) && !stream.err() && !stream.eos()) { + uint32 length, pos; + + stream.read(buf, 4); + length = stream.readUint32BE(); + pos = stream.pos(); + + if (memcmp(buf, "COMM", 4) == 0) { + foundCOMM = true; + numChannels = stream.readUint16BE(); + numSampleFrames = stream.readUint32BE(); + bitsPerSample = stream.readUint16BE(); + rate = readExtended(stream); + size = numSampleFrames * numChannels * (bitsPerSample / 8); + } else if (memcmp(buf, "SSND", 4) == 0) { + foundSSND = true; + offset = stream.readUint32BE(); + blockSize = stream.readUint32BE(); + soundOffset = stream.pos(); + } + + stream.seek(pos + length); + } + + if (!foundCOMM) { + warning("loadAIFFFromStream: Cound not find 'COMM' chunk"); + return false; + } + + if (!foundSSND) { + warning("loadAIFFFromStream: Cound not find 'SSND' chunk"); + return false; + } + + // We only implement a subset of the AIFF standard. + + if (numChannels < 1 || numChannels > 2) { + warning("loadAIFFFromStream: Only 1 or 2 channels are supported, not %d", numChannels); + return false; + } + + if (bitsPerSample != 8 && bitsPerSample != 16) { + warning("loadAIFFFromStream: Only 8 or 16 bits per sample are supported, not %d", bitsPerSample); + return false; + } + + if (offset != 0 || blockSize != 0) { + warning("loadAIFFFromStream: Block-aligned data is not supported"); + return false; + } + + // Samples are always signed, and big endian. + + flags = 0; + if (bitsPerSample == 16) + flags |= Audio::FLAG_16BITS; + if (numChannels == 2) + flags |= Audio::FLAG_STEREO; + + stream.seek(soundOffset); + + // Stream now points at the sample data + + return true; +} + +SeekableAudioStream *makeAIFFStream(Common::SeekableReadStream *stream, + DisposeAfterUse::Flag disposeAfterUse) { + int size, rate; + byte *data, flags; + + if (!loadAIFFFromStream(*stream, size, rate, flags)) { + if (disposeAfterUse == DisposeAfterUse::YES) + delete stream; + return 0; + } + + data = (byte *)malloc(size); + assert(data); + stream->read(data, size); + + if (disposeAfterUse == DisposeAfterUse::YES) + delete stream; + + // Since we allocated our own buffer for the data, we must specify DisposeAfterUse::YES. + return makeRawStream(data, size, rate, flags); +} + +} // End of namespace Audio diff --git a/audio/decoders/aiff.h b/audio/decoders/aiff.h new file mode 100644 index 0000000000..06c56ecd38 --- /dev/null +++ b/audio/decoders/aiff.h @@ -0,0 +1,71 @@ +/* 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$ + * + */ + +/** + * @file + * Sound decoder used in engines: + * - saga + * - sci + * - sword1 + */ + +#ifndef SOUND_AIFF_H +#define SOUND_AIFF_H + +#include "common/scummsys.h" +#include "common/types.h" + +namespace Common { class SeekableReadStream; } + +namespace Audio { + +class SeekableAudioStream; + +/** + * Try to load an AIFF from the given seekable stream. Returns true if + * successful. In that case, the stream's seek position will be set to the + * start of the audio data, and size, rate and flags contain information + * necessary for playback. Currently this function only supports uncompressed + * raw PCM data as well as IMA ADPCM. + */ +extern bool loadAIFFFromStream(Common::SeekableReadStream &stream, int &size, int &rate, byte &flags); + +/** + * Try to load an AIFF from the given seekable stream and create an AudioStream + * from that data. + * + * This function uses loadAIFFFromStream() internally. + * + * @param stream the SeekableReadStream from which to read the AIFF data + * @param disposeAfterUse whether to delete the stream after use + * @return a new SeekableAudioStream, or NULL, if an error occurred + */ +SeekableAudioStream *makeAIFFStream( + Common::SeekableReadStream *stream, + DisposeAfterUse::Flag disposeAfterUse); + +} // End of namespace Audio + +#endif diff --git a/audio/decoders/flac.cpp b/audio/decoders/flac.cpp new file mode 100644 index 0000000000..76b6d35419 --- /dev/null +++ b/audio/decoders/flac.cpp @@ -0,0 +1,745 @@ +/* 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$ + * + */ + +// Disable symbol overrides for FILE as that is used in FLAC headers +#define FORBIDDEN_SYMBOL_EXCEPTION_FILE + +#include "audio/decoders/flac.h" + +#ifdef USE_FLAC + +#include "common/debug.h" +#include "common/stream.h" +#include "common/util.h" + +#include "audio/audiostream.h" + +#define FLAC__NO_DLL // that MS-magic gave me headaches - just link the library you like +#include <FLAC/export.h> + + +// check if we have FLAC >= 1.1.3; LEGACY_FLAC code can be removed once FLAC-1.1.3 propagates everywhere +#if !defined(FLAC_API_VERSION_CURRENT) || FLAC_API_VERSION_CURRENT < 8 +#define LEGACY_FLAC +#else +#undef LEGACY_FLAC +#endif + + +#ifdef LEGACY_FLAC + +// Before FLAC 1.1.3, we needed to use the stream decoder API. +#include <FLAC/seekable_stream_decoder.h> +typedef uint FLAC_size_t; + +#else + +// With FLAC 1.1.3, the stream decoder API was merged into the regular +// stream API. In order to stay compatible with older FLAC versions, we +// simply add some typedefs and #ifdefs to map between the old and new API. +// We use the typedefs (instead of only #defines) in order to somewhat +// improve the readability of the code. + +#include <FLAC/stream_decoder.h> +typedef size_t FLAC_size_t; +// Add aliases for the old names +typedef FLAC__StreamDecoderState FLAC__SeekableStreamDecoderState; +typedef FLAC__StreamDecoderReadStatus FLAC__SeekableStreamDecoderReadStatus; +typedef FLAC__StreamDecoderSeekStatus FLAC__SeekableStreamDecoderSeekStatus; +typedef FLAC__StreamDecoderTellStatus FLAC__SeekableStreamDecoderTellStatus; +typedef FLAC__StreamDecoderLengthStatus FLAC__SeekableStreamDecoderLengthStatus; +typedef FLAC__StreamDecoder FLAC__SeekableStreamDecoder; + +#endif + + +namespace Audio { + +#pragma mark - +#pragma mark --- FLAC stream --- +#pragma mark - + +static const uint MAX_OUTPUT_CHANNELS = 2; + + +class FLACStream : public SeekableAudioStream { +protected: + Common::SeekableReadStream *_inStream; + bool _disposeAfterUse; + + ::FLAC__SeekableStreamDecoder *_decoder; + + /** Header of the stream */ + FLAC__StreamMetadata_StreamInfo _streaminfo; + + /** index + 1(!) of the last sample to be played */ + FLAC__uint64 _lastSample; + + /** total play time */ + Timestamp _length; + + /** true if the last sample was decoded from the FLAC-API - there might still be data in the buffer */ + bool _lastSampleWritten; + + typedef int16 SampleType; + enum { BUFTYPE_BITS = 16 }; + + enum { + // Maximal buffer size. According to the FLAC format specification, the block size is + // a 16 bit value (in fact it seems the maximal block size is 32768, but we play it safe). + BUFFER_SIZE = 65536 + }; + + struct { + SampleType bufData[BUFFER_SIZE]; + SampleType *bufReadPos; + uint bufFill; + } _sampleCache; + + SampleType *_outBuffer; + uint _requestedSamples; + + typedef void (*PFCONVERTBUFFERS)(SampleType*, const FLAC__int32*[], uint, const uint, const uint8); + PFCONVERTBUFFERS _methodConvertBuffers; + + +public: + FLACStream(Common::SeekableReadStream *inStream, bool dispose); + virtual ~FLACStream(); + + int readBuffer(int16 *buffer, const int numSamples); + + bool isStereo() const { return _streaminfo.channels >= 2; } + int getRate() const { return _streaminfo.sample_rate; } + bool endOfData() const { + // End of data is reached if there either is no valid stream data available, + // or if we reached the last sample and completely emptied the sample cache. + return _streaminfo.channels == 0 || (_lastSampleWritten && _sampleCache.bufFill == 0); + } + + bool seek(const Timestamp &where); + Timestamp getLength() const { return _length; } + + bool isStreamDecoderReady() const { return getStreamDecoderState() == FLAC__STREAM_DECODER_SEARCH_FOR_FRAME_SYNC; } +protected: + uint getChannels() const { return MIN<uint>(_streaminfo.channels, MAX_OUTPUT_CHANNELS); } + + bool allocateBuffer(uint minSamples); + + inline FLAC__StreamDecoderState getStreamDecoderState() const; + + inline bool processSingleBlock(); + inline bool processUntilEndOfMetadata(); + bool seekAbsolute(FLAC__uint64 sample); + + inline ::FLAC__SeekableStreamDecoderReadStatus callbackRead(FLAC__byte buffer[], FLAC_size_t *bytes); + inline ::FLAC__SeekableStreamDecoderSeekStatus callbackSeek(FLAC__uint64 absoluteByteOffset); + inline ::FLAC__SeekableStreamDecoderTellStatus callbackTell(FLAC__uint64 *absoluteByteOffset); + inline ::FLAC__SeekableStreamDecoderLengthStatus callbackLength(FLAC__uint64 *streamLength); + inline bool callbackEOF(); + inline ::FLAC__StreamDecoderWriteStatus callbackWrite(const ::FLAC__Frame *frame, const FLAC__int32 * const buffer[]); + inline void callbackMetadata(const ::FLAC__StreamMetadata *metadata); + inline void callbackError(::FLAC__StreamDecoderErrorStatus status); + +private: + static ::FLAC__SeekableStreamDecoderReadStatus callWrapRead(const ::FLAC__SeekableStreamDecoder *decoder, FLAC__byte buffer[], FLAC_size_t *bytes, void *clientData); + static ::FLAC__SeekableStreamDecoderSeekStatus callWrapSeek(const ::FLAC__SeekableStreamDecoder *decoder, FLAC__uint64 absoluteByteOffset, void *clientData); + static ::FLAC__SeekableStreamDecoderTellStatus callWrapTell(const ::FLAC__SeekableStreamDecoder *decoder, FLAC__uint64 *absoluteByteOffset, void *clientData); + static ::FLAC__SeekableStreamDecoderLengthStatus callWrapLength(const ::FLAC__SeekableStreamDecoder *decoder, FLAC__uint64 *streamLength, void *clientData); + static FLAC__bool callWrapEOF(const ::FLAC__SeekableStreamDecoder *decoder, void *clientData); + static ::FLAC__StreamDecoderWriteStatus callWrapWrite(const ::FLAC__SeekableStreamDecoder *decoder, const ::FLAC__Frame *frame, const FLAC__int32 * const buffer[], void *clientData); + static void callWrapMetadata(const ::FLAC__SeekableStreamDecoder *decoder, const ::FLAC__StreamMetadata *metadata, void *clientData); + static void callWrapError(const ::FLAC__SeekableStreamDecoder *decoder, ::FLAC__StreamDecoderErrorStatus status, void *clientData); + + void setBestConvertBufferMethod(); + static void convertBuffersGeneric(SampleType* bufDestination, const FLAC__int32 *inChannels[], uint numSamples, const uint numChannels, const uint8 numBits); + static void convertBuffersStereoNS(SampleType* bufDestination, const FLAC__int32 *inChannels[], uint numSamples, const uint numChannels, const uint8 numBits); + static void convertBuffersStereo8Bit(SampleType* bufDestination, const FLAC__int32 *inChannels[], uint numSamples, const uint numChannels, const uint8 numBits); + static void convertBuffersMonoNS(SampleType* bufDestination, const FLAC__int32 *inChannels[], uint numSamples, const uint numChannels, const uint8 numBits); + static void convertBuffersMono8Bit(SampleType* bufDestination, const FLAC__int32 *inChannels[], uint numSamples, const uint numChannels, const uint8 numBits); +}; + +FLACStream::FLACStream(Common::SeekableReadStream *inStream, bool dispose) +#ifdef LEGACY_FLAC + : _decoder(::FLAC__seekable_stream_decoder_new()), +#else + : _decoder(::FLAC__stream_decoder_new()), +#endif + _inStream(inStream), + _disposeAfterUse(dispose), + _length(0, 1000), _lastSample(0), + _outBuffer(NULL), _requestedSamples(0), _lastSampleWritten(false), + _methodConvertBuffers(&FLACStream::convertBuffersGeneric) +{ + assert(_inStream); + memset(&_streaminfo, 0, sizeof(_streaminfo)); + + _sampleCache.bufReadPos = NULL; + _sampleCache.bufFill = 0; + + _methodConvertBuffers = &FLACStream::convertBuffersGeneric; + + bool success; +#ifdef LEGACY_FLAC + ::FLAC__seekable_stream_decoder_set_read_callback(_decoder, &FLACStream::callWrapRead); + ::FLAC__seekable_stream_decoder_set_seek_callback(_decoder, &FLACStream::callWrapSeek); + ::FLAC__seekable_stream_decoder_set_tell_callback(_decoder, &FLACStream::callWrapTell); + ::FLAC__seekable_stream_decoder_set_length_callback(_decoder, &FLACStream::callWrapLength); + ::FLAC__seekable_stream_decoder_set_eof_callback(_decoder, &FLACStream::callWrapEOF); + ::FLAC__seekable_stream_decoder_set_write_callback(_decoder, &FLACStream::callWrapWrite); + ::FLAC__seekable_stream_decoder_set_metadata_callback(_decoder, &FLACStream::callWrapMetadata); + ::FLAC__seekable_stream_decoder_set_error_callback(_decoder, &FLACStream::callWrapError); + ::FLAC__seekable_stream_decoder_set_client_data(_decoder, (void*)this); + + success = (::FLAC__seekable_stream_decoder_init(_decoder) == FLAC__SEEKABLE_STREAM_DECODER_OK); +#else + success = (::FLAC__stream_decoder_init_stream( + _decoder, + &FLACStream::callWrapRead, + &FLACStream::callWrapSeek, + &FLACStream::callWrapTell, + &FLACStream::callWrapLength, + &FLACStream::callWrapEOF, + &FLACStream::callWrapWrite, + &FLACStream::callWrapMetadata, + &FLACStream::callWrapError, + (void*)this + ) == FLAC__STREAM_DECODER_INIT_STATUS_OK); +#endif + if (success) { + if (processUntilEndOfMetadata() && _streaminfo.channels > 0) { + _lastSample = _streaminfo.total_samples + 1; + _length = Timestamp(0, _lastSample - 1, getRate()); + return; // no error occurred + } + } + + warning("FLACStream: could not create audio stream"); +} + +FLACStream::~FLACStream() { + if (_decoder != NULL) { +#ifdef LEGACY_FLAC + (void) ::FLAC__seekable_stream_decoder_finish(_decoder); + ::FLAC__seekable_stream_decoder_delete(_decoder); +#else + (void) ::FLAC__stream_decoder_finish(_decoder); + ::FLAC__stream_decoder_delete(_decoder); +#endif + } + if (_disposeAfterUse) + delete _inStream; +} + +inline FLAC__StreamDecoderState FLACStream::getStreamDecoderState() const { + assert(_decoder != NULL); +#ifdef LEGACY_FLAC + return ::FLAC__seekable_stream_decoder_get_stream_decoder_state(_decoder); +#else + return ::FLAC__stream_decoder_get_state(_decoder); +#endif +} + +inline bool FLACStream::processSingleBlock() { + assert(_decoder != NULL); +#ifdef LEGACY_FLAC + return 0 != ::FLAC__seekable_stream_decoder_process_single(_decoder); +#else + return 0 != ::FLAC__stream_decoder_process_single(_decoder); +#endif +} + +inline bool FLACStream::processUntilEndOfMetadata() { + assert(_decoder != NULL); +#ifdef LEGACY_FLAC + return 0 != ::FLAC__seekable_stream_decoder_process_until_end_of_metadata(_decoder); +#else + return 0 != ::FLAC__stream_decoder_process_until_end_of_metadata(_decoder); +#endif +} + +bool FLACStream::seekAbsolute(FLAC__uint64 sample) { + assert(_decoder != NULL); +#ifdef LEGACY_FLAC + const bool result = (0 != ::FLAC__seekable_stream_decoder_seek_absolute(_decoder, sample)); +#else + const bool result = (0 != ::FLAC__stream_decoder_seek_absolute(_decoder, sample)); +#endif + if (result) { + _lastSampleWritten = (_lastSample != 0 && sample >= _lastSample); // only set if we are SURE + } + return result; +} + +bool FLACStream::seek(const Timestamp &where) { + _sampleCache.bufFill = 0; + _sampleCache.bufReadPos = NULL; + // FLAC uses the sample pair number, thus we always use "false" for the isStereo parameter + // of the convertTimeToStreamPos helper. + return seekAbsolute((FLAC__uint64)convertTimeToStreamPos(where, getRate(), false).totalNumberOfFrames()); +} + +int FLACStream::readBuffer(int16 *buffer, const int numSamples) { + const uint numChannels = getChannels(); + + if (numChannels == 0) { + warning("FLACStream: Stream not successfully initialised, cant playback"); + return -1; // streaminfo wasnt read! + } + + assert(numSamples % numChannels == 0); // must be multiple of channels! + assert(buffer != NULL); + assert(_outBuffer == NULL); + assert(_requestedSamples == 0); + + _outBuffer = buffer; + _requestedSamples = numSamples; + + // If there is still data in our buffer from the last time around, + // copy that first. + if (_sampleCache.bufFill > 0) { + assert(_sampleCache.bufReadPos >= _sampleCache.bufData); + assert(_sampleCache.bufFill % numChannels == 0); + + const uint copySamples = MIN((uint)numSamples, _sampleCache.bufFill); + memcpy(buffer, _sampleCache.bufReadPos, copySamples*sizeof(buffer[0])); + + _outBuffer = buffer + copySamples; + _requestedSamples = numSamples - copySamples; + _sampleCache.bufReadPos += copySamples; + _sampleCache.bufFill -= copySamples; + } + + bool decoderOk = true; + + FLAC__StreamDecoderState state = getStreamDecoderState(); + + // Keep poking FLAC to process more samples until we completely satisfied the request + // respectively until we run out of data. + while (!_lastSampleWritten && _requestedSamples > 0 && state == FLAC__STREAM_DECODER_SEARCH_FOR_FRAME_SYNC) { + assert(_sampleCache.bufFill == 0); + assert(_requestedSamples % numChannels == 0); + processSingleBlock(); + state = getStreamDecoderState(); + + if (state == FLAC__STREAM_DECODER_END_OF_STREAM) + _lastSampleWritten = true; + } + + // Error handling + switch (state) { + case FLAC__STREAM_DECODER_END_OF_STREAM: + _lastSampleWritten = true; + break; + case FLAC__STREAM_DECODER_SEARCH_FOR_FRAME_SYNC: + break; + default: + decoderOk = false; + warning("FLACStream: An error occurred while decoding. DecoderState is: %s", + FLAC__StreamDecoderStateString[getStreamDecoderState()]); + } + + // Compute how many samples we actually produced + const int samples = (int)(_outBuffer - buffer); + assert(samples % numChannels == 0); + + _outBuffer = NULL; // basically unnecessary, only for the purpose of the asserts + _requestedSamples = 0; // basically unnecessary, only for the purpose of the asserts + + return decoderOk ? samples : -1; +} + +inline ::FLAC__SeekableStreamDecoderReadStatus FLACStream::callbackRead(FLAC__byte buffer[], FLAC_size_t *bytes) { + if (*bytes == 0) { +#ifdef LEGACY_FLAC + return FLAC__SEEKABLE_STREAM_DECODER_READ_STATUS_ERROR; /* abort to avoid a deadlock */ +#else + return FLAC__STREAM_DECODER_READ_STATUS_ABORT; /* abort to avoid a deadlock */ +#endif + } + + const uint32 bytesRead = _inStream->read(buffer, *bytes); + + if (bytesRead == 0) { +#ifdef LEGACY_FLAC + return _inStream->eos() ? FLAC__SEEKABLE_STREAM_DECODER_READ_STATUS_OK : FLAC__SEEKABLE_STREAM_DECODER_READ_STATUS_ERROR; +#else + return _inStream->eos() ? FLAC__STREAM_DECODER_READ_STATUS_END_OF_STREAM : FLAC__STREAM_DECODER_READ_STATUS_ABORT; +#endif + } + + *bytes = static_cast<uint>(bytesRead); +#ifdef LEGACY_FLAC + return FLAC__SEEKABLE_STREAM_DECODER_READ_STATUS_OK; +#else + return FLAC__STREAM_DECODER_READ_STATUS_CONTINUE; +#endif +} + +void FLACStream::setBestConvertBufferMethod() { + PFCONVERTBUFFERS tempMethod = &FLACStream::convertBuffersGeneric; + + const uint numChannels = getChannels(); + const uint8 numBits = (uint8)_streaminfo.bits_per_sample; + + assert(numChannels >= 1); + assert(numBits >= 4 && numBits <=32); + + if (numChannels == 1) { + if (numBits == 8) + tempMethod = &FLACStream::convertBuffersMono8Bit; + if (numBits == BUFTYPE_BITS) + tempMethod = &FLACStream::convertBuffersMonoNS; + } else if (numChannels == 2) { + if (numBits == 8) + tempMethod = &FLACStream::convertBuffersStereo8Bit; + if (numBits == BUFTYPE_BITS) + tempMethod = &FLACStream::convertBuffersStereoNS; + } /* else ... */ + + _methodConvertBuffers = tempMethod; +} + +// 1 channel, no scaling +void FLACStream::convertBuffersMonoNS(SampleType* bufDestination, const FLAC__int32 *inChannels[], uint numSamples, const uint numChannels, const uint8 numBits) { + assert(numChannels == 1); + assert(numBits == BUFTYPE_BITS); + + FLAC__int32 const* inChannel1 = inChannels[0]; + + while (numSamples >= 4) { + bufDestination[0] = static_cast<SampleType>(inChannel1[0]); + bufDestination[1] = static_cast<SampleType>(inChannel1[1]); + bufDestination[2] = static_cast<SampleType>(inChannel1[2]); + bufDestination[3] = static_cast<SampleType>(inChannel1[3]); + bufDestination += 4; + inChannel1 += 4; + numSamples -= 4; + } + + for (; numSamples > 0; --numSamples) { + *bufDestination++ = static_cast<SampleType>(*inChannel1++); + } + + inChannels[0] = inChannel1; + assert(numSamples == 0); // dint copy too many samples +} + +// 1 channel, scaling from 8Bit +void FLACStream::convertBuffersMono8Bit(SampleType* bufDestination, const FLAC__int32 *inChannels[], uint numSamples, const uint numChannels, const uint8 numBits) { + assert(numChannels == 1); + assert(numBits == 8); + assert(8 < BUFTYPE_BITS); + + FLAC__int32 const* inChannel1 = inChannels[0]; + + while (numSamples >= 4) { + bufDestination[0] = static_cast<SampleType>(inChannel1[0]) << (BUFTYPE_BITS - 8); + bufDestination[1] = static_cast<SampleType>(inChannel1[1]) << (BUFTYPE_BITS - 8); + bufDestination[2] = static_cast<SampleType>(inChannel1[2]) << (BUFTYPE_BITS - 8); + bufDestination[3] = static_cast<SampleType>(inChannel1[3]) << (BUFTYPE_BITS - 8); + bufDestination += 4; + inChannel1 += 4; + numSamples -= 4; + } + + for (; numSamples > 0; --numSamples) { + *bufDestination++ = static_cast<SampleType>(*inChannel1++) << (BUFTYPE_BITS - 8); + } + + inChannels[0] = inChannel1; + assert(numSamples == 0); // dint copy too many samples +} + +// 2 channels, no scaling +void FLACStream::convertBuffersStereoNS(SampleType* bufDestination, const FLAC__int32 *inChannels[], uint numSamples, const uint numChannels, const uint8 numBits) { + assert(numChannels == 2); + assert(numBits == BUFTYPE_BITS); + assert(numSamples % 2 == 0); // must be integral multiply of channels + + + FLAC__int32 const* inChannel1 = inChannels[0]; // Left Channel + FLAC__int32 const* inChannel2 = inChannels[1]; // Right Channel + + while (numSamples >= 2*2) { + bufDestination[0] = static_cast<SampleType>(inChannel1[0]); + bufDestination[1] = static_cast<SampleType>(inChannel2[0]); + bufDestination[2] = static_cast<SampleType>(inChannel1[1]); + bufDestination[3] = static_cast<SampleType>(inChannel2[1]); + bufDestination += 2 * 2; + inChannel1 += 2; + inChannel2 += 2; + numSamples -= 2 * 2; + } + + while (numSamples > 0) { + bufDestination[0] = static_cast<SampleType>(*inChannel1++); + bufDestination[1] = static_cast<SampleType>(*inChannel2++); + bufDestination += 2; + numSamples -= 2; + } + + inChannels[0] = inChannel1; + inChannels[1] = inChannel2; + assert(numSamples == 0); // dint copy too many samples +} + +// 2 channels, scaling from 8Bit +void FLACStream::convertBuffersStereo8Bit(SampleType* bufDestination, const FLAC__int32 *inChannels[], uint numSamples, const uint numChannels, const uint8 numBits) { + assert(numChannels == 2); + assert(numBits == 8); + assert(numSamples % 2 == 0); // must be integral multiply of channels + assert(8 < BUFTYPE_BITS); + + FLAC__int32 const* inChannel1 = inChannels[0]; // Left Channel + FLAC__int32 const* inChannel2 = inChannels[1]; // Right Channel + + while (numSamples >= 2*2) { + bufDestination[0] = static_cast<SampleType>(inChannel1[0]) << (BUFTYPE_BITS - 8); + bufDestination[1] = static_cast<SampleType>(inChannel2[0]) << (BUFTYPE_BITS - 8); + bufDestination[2] = static_cast<SampleType>(inChannel1[1]) << (BUFTYPE_BITS - 8); + bufDestination[3] = static_cast<SampleType>(inChannel2[1]) << (BUFTYPE_BITS - 8); + bufDestination += 2 * 2; + inChannel1 += 2; + inChannel2 += 2; + numSamples -= 2 * 2; + } + + while (numSamples > 0) { + bufDestination[0] = static_cast<SampleType>(*inChannel1++) << (BUFTYPE_BITS - 8); + bufDestination[1] = static_cast<SampleType>(*inChannel2++) << (BUFTYPE_BITS - 8); + bufDestination += 2; + numSamples -= 2; + } + + inChannels[0] = inChannel1; + inChannels[1] = inChannel2; + assert(numSamples == 0); // dint copy too many samples +} + +// all Purpose-conversion - slowest of em all +void FLACStream::convertBuffersGeneric(SampleType* bufDestination, const FLAC__int32 *inChannels[], uint numSamples, const uint numChannels, const uint8 numBits) { + assert(numSamples % numChannels == 0); // must be integral multiply of channels + + if (numBits < BUFTYPE_BITS) { + const uint8 kPower = (uint8)(BUFTYPE_BITS - numBits); + + for (; numSamples > 0; numSamples -= numChannels) { + for (uint i = 0; i < numChannels; ++i) + *bufDestination++ = static_cast<SampleType>(*(inChannels[i]++)) << kPower; + } + } else if (numBits > BUFTYPE_BITS) { + const uint8 kPower = (uint8)(numBits - BUFTYPE_BITS); + + for (; numSamples > 0; numSamples -= numChannels) { + for (uint i = 0; i < numChannels; ++i) + *bufDestination++ = static_cast<SampleType>(*(inChannels[i]++) >> kPower); + } + } else { + for (; numSamples > 0; numSamples -= numChannels) { + for (uint i = 0; i < numChannels; ++i) + *bufDestination++ = static_cast<SampleType>(*(inChannels[i]++)); + } + } + + assert(numSamples == 0); // dint copy too many samples +} + +inline ::FLAC__StreamDecoderWriteStatus FLACStream::callbackWrite(const ::FLAC__Frame *frame, const FLAC__int32 * const buffer[]) { + assert(frame->header.channels == _streaminfo.channels); + assert(frame->header.sample_rate == _streaminfo.sample_rate); + assert(frame->header.bits_per_sample == _streaminfo.bits_per_sample); + assert(frame->header.number_type == FLAC__FRAME_NUMBER_TYPE_SAMPLE_NUMBER || _streaminfo.min_blocksize == _streaminfo.max_blocksize); + + // We require that either the sample cache is empty, or that no samples were requested + assert(_sampleCache.bufFill == 0 || _requestedSamples == 0); + + uint numSamples = frame->header.blocksize; + const uint numChannels = getChannels(); + const uint8 numBits = (uint8)_streaminfo.bits_per_sample; + + assert(_requestedSamples % numChannels == 0); // must be integral multiply of channels + + const FLAC__uint64 firstSampleNumber = (frame->header.number_type == FLAC__FRAME_NUMBER_TYPE_SAMPLE_NUMBER) ? + frame->header.number.sample_number : (static_cast<FLAC__uint64>(frame->header.number.frame_number)) * _streaminfo.max_blocksize; + + // Check whether we are about to reach beyond the last sample we are supposed to play. + if (_lastSample != 0 && firstSampleNumber + numSamples >= _lastSample) { + numSamples = (uint)(firstSampleNumber >= _lastSample ? 0 : _lastSample - firstSampleNumber); + _lastSampleWritten = true; + } + + // The value in _requestedSamples counts raw samples, so if there are more than one + // channel, we have to multiply the number of available sample "pairs" by numChannels + numSamples *= numChannels; + + const FLAC__int32 *inChannels[MAX_OUTPUT_CHANNELS]; + for (uint i = 0; i < numChannels; ++i) + inChannels[i] = buffer[i]; + + // write the incoming samples directly into the buffer provided to us by the mixer + if (_requestedSamples > 0) { + assert(_requestedSamples % numChannels == 0); + assert(_outBuffer != NULL); + + // Copy & convert the available samples (limited both by how many we have available, and + // by how many are actually needed). + const uint copySamples = MIN(_requestedSamples, numSamples); + (*_methodConvertBuffers)(_outBuffer, inChannels, copySamples, numChannels, numBits); + + _requestedSamples -= copySamples; + numSamples -= copySamples; + _outBuffer += copySamples; + } + + // Write all remaining samples (i.e. those which didn't fit into the mixer buffer) + // into the sample cache. + if (_sampleCache.bufFill == 0) + _sampleCache.bufReadPos = _sampleCache.bufData; + const uint cacheSpace = (_sampleCache.bufData + BUFFER_SIZE) - (_sampleCache.bufReadPos + _sampleCache.bufFill); + assert(numSamples <= cacheSpace); + (*_methodConvertBuffers)(_sampleCache.bufReadPos + _sampleCache.bufFill, inChannels, numSamples, numChannels, numBits); + + _sampleCache.bufFill += numSamples; + + return FLAC__STREAM_DECODER_WRITE_STATUS_CONTINUE; +} + +inline ::FLAC__SeekableStreamDecoderSeekStatus FLACStream::callbackSeek(FLAC__uint64 absoluteByteOffset) { + _inStream->seek(absoluteByteOffset, SEEK_SET); + const bool result = (absoluteByteOffset == (FLAC__uint64)_inStream->pos()); + +#ifdef LEGACY_FLAC + return result ? FLAC__SEEKABLE_STREAM_DECODER_SEEK_STATUS_OK : FLAC__SEEKABLE_STREAM_DECODER_SEEK_STATUS_ERROR; +#else + return result ? FLAC__STREAM_DECODER_SEEK_STATUS_OK : FLAC__STREAM_DECODER_SEEK_STATUS_ERROR; +#endif +} + +inline ::FLAC__SeekableStreamDecoderTellStatus FLACStream::callbackTell(FLAC__uint64 *absoluteByteOffset) { + *absoluteByteOffset = static_cast<FLAC__uint64>(_inStream->pos()); +#ifdef LEGACY_FLAC + return FLAC__SEEKABLE_STREAM_DECODER_TELL_STATUS_OK; +#else + return FLAC__STREAM_DECODER_TELL_STATUS_OK; +#endif +} + +inline ::FLAC__SeekableStreamDecoderLengthStatus FLACStream::callbackLength(FLAC__uint64 *streamLength) { + *streamLength = static_cast<FLAC__uint64>(_inStream->size()); +#ifdef LEGACY_FLAC + return FLAC__SEEKABLE_STREAM_DECODER_LENGTH_STATUS_OK; +#else + return FLAC__STREAM_DECODER_LENGTH_STATUS_OK; +#endif +} + +inline bool FLACStream::callbackEOF() { + return _inStream->eos(); +} + + +inline void FLACStream::callbackMetadata(const ::FLAC__StreamMetadata *metadata) { + assert(_decoder != NULL); + assert(metadata->type == FLAC__METADATA_TYPE_STREAMINFO); // others arent really interesting + + _streaminfo = metadata->data.stream_info; + setBestConvertBufferMethod(); // should be set after getting stream-information. FLAC always parses the info first +} +inline void FLACStream::callbackError(::FLAC__StreamDecoderErrorStatus status) { + // some of these are non-critical-Errors + debug(1, "FLACStream: An error occurred while decoding. DecoderState is: %s", + FLAC__StreamDecoderErrorStatusString[status]); +} + +/* Static Callback Wrappers */ +::FLAC__SeekableStreamDecoderReadStatus FLACStream::callWrapRead(const ::FLAC__SeekableStreamDecoder *decoder, FLAC__byte buffer[], FLAC_size_t *bytes, void *clientData) { + FLACStream *instance = (FLACStream *)clientData; + assert(0 != instance); + return instance->callbackRead(buffer, bytes); +} + +::FLAC__SeekableStreamDecoderSeekStatus FLACStream::callWrapSeek(const ::FLAC__SeekableStreamDecoder *decoder, FLAC__uint64 absoluteByteOffset, void *clientData) { + FLACStream *instance = (FLACStream *)clientData; + assert(0 != instance); + return instance->callbackSeek(absoluteByteOffset); +} + +::FLAC__SeekableStreamDecoderTellStatus FLACStream::callWrapTell(const ::FLAC__SeekableStreamDecoder *decoder, FLAC__uint64 *absoluteByteOffset, void *clientData) { + FLACStream *instance = (FLACStream *)clientData; + assert(0 != instance); + return instance->callbackTell(absoluteByteOffset); +} + +::FLAC__SeekableStreamDecoderLengthStatus FLACStream::callWrapLength(const ::FLAC__SeekableStreamDecoder *decoder, FLAC__uint64 *streamLength, void *clientData) { + FLACStream *instance = (FLACStream *)clientData; + assert(0 != instance); + return instance->callbackLength(streamLength); +} + +FLAC__bool FLACStream::callWrapEOF(const ::FLAC__SeekableStreamDecoder *decoder, void *clientData) { + FLACStream *instance = (FLACStream *)clientData; + assert(0 != instance); + return instance->callbackEOF(); +} + +::FLAC__StreamDecoderWriteStatus FLACStream::callWrapWrite(const ::FLAC__SeekableStreamDecoder *decoder, const ::FLAC__Frame *frame, const FLAC__int32 * const buffer[], void *clientData) { + FLACStream *instance = (FLACStream *)clientData; + assert(0 != instance); + return instance->callbackWrite(frame, buffer); +} + +void FLACStream::callWrapMetadata(const ::FLAC__SeekableStreamDecoder *decoder, const ::FLAC__StreamMetadata *metadata, void *clientData) { + FLACStream *instance = (FLACStream *)clientData; + assert(0 != instance); + instance->callbackMetadata(metadata); +} + +void FLACStream::callWrapError(const ::FLAC__SeekableStreamDecoder *decoder, ::FLAC__StreamDecoderErrorStatus status, void *clientData) { + FLACStream *instance = (FLACStream *)clientData; + assert(0 != instance); + instance->callbackError(status); +} + + +#pragma mark - +#pragma mark --- FLAC factory functions --- +#pragma mark - + +SeekableAudioStream *makeFLACStream( + Common::SeekableReadStream *stream, + DisposeAfterUse::Flag disposeAfterUse) { + SeekableAudioStream *s = new FLACStream(stream, disposeAfterUse); + if (s && s->endOfData()) { + delete s; + return 0; + } else { + return s; + } +} + +} // End of namespace Audio + +#endif // #ifdef USE_FLAC diff --git a/audio/decoders/flac.h b/audio/decoders/flac.h new file mode 100644 index 0000000000..17f95ec1fb --- /dev/null +++ b/audio/decoders/flac.h @@ -0,0 +1,75 @@ +/* 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$ + * + */ + +/** + * @file + * Sound decoder used in engines: + * - agos + * - draci + * - kyra + * - m4 + * - queen + * - saga + * - sci + * - scumm + * - sword1 + * - sword2 + * - touche + * - tucker + */ + +#ifndef SOUND_FLAC_H +#define SOUND_FLAC_H + +#include "common/scummsys.h" +#include "common/types.h" + +#ifdef USE_FLAC + +namespace Common { + class SeekableReadStream; +} + +namespace Audio { + +class AudioStream; +class SeekableAudioStream; + +/** + * Create a new SeekableAudioStream from the FLAC data in the given stream. + * Allows for seeking (which is why we require a SeekableReadStream). + * + * @param stream the SeekableReadStream from which to read the FLAC data + * @param disposeAfterUse whether to delete the stream after use + * @return a new SeekableAudioStream, or NULL, if an error occurred + */ +SeekableAudioStream *makeFLACStream( + Common::SeekableReadStream *stream, + DisposeAfterUse::Flag disposeAfterUse); + +} // End of namespace Audio + +#endif // #ifdef USE_FLAC +#endif // #ifndef SOUND_FLAC_H diff --git a/audio/decoders/iff_sound.cpp b/audio/decoders/iff_sound.cpp new file mode 100644 index 0000000000..2ec189c586 --- /dev/null +++ b/audio/decoders/iff_sound.cpp @@ -0,0 +1,130 @@ +/* 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/iff_sound.h" +#include "audio/audiostream.h" +#include "audio/mixer.h" +#include "audio/decoders/raw.h" +#include "common/iff_container.h" +#include "common/func.h" + +namespace Audio { + +struct Voice8Header { + uint32 oneShotHiSamples; + uint32 repeatHiSamples; + uint32 samplesPerHiCycle; + uint16 samplesPerSec; + byte octaves; + byte compression; + uint32 volume; + + Voice8Header() { + memset(this, 0, sizeof(Voice8Header)); + } + + void load(Common::ReadStream &stream); +}; + +void Voice8Header::load(Common::ReadStream &stream) { + oneShotHiSamples = stream.readUint32BE(); + repeatHiSamples = stream.readUint32BE(); + samplesPerHiCycle = stream.readUint32BE(); + samplesPerSec = stream.readUint16BE(); + octaves = stream.readByte(); + compression = stream.readByte(); + volume = stream.readUint32BE(); +} + + + +struct A8SVXLoader { + Voice8Header _header; + int8 *_data; + uint32 _dataSize; + + void load(Common::ReadStream &input) { + Common::IFFParser parser(&input); + Common::Functor1Mem< Common::IFFChunk&, bool, A8SVXLoader > c(this, &A8SVXLoader::callback); + parser.parse(c); + } + + bool callback(Common::IFFChunk &chunk) { + switch (chunk._type) { + case ID_VHDR: + _header.load(*chunk._stream); + break; + + case ID_BODY: + _dataSize = chunk._size; + _data = (int8*)malloc(_dataSize); + assert(_data); + loadData(chunk._stream); + return true; + } + + return false; + } + + void loadData(Common::ReadStream *stream) { + switch (_header.compression) { + case 0: + stream->read(_data, _dataSize); + break; + + case 1: + // implement other formats here + error("compressed IFF audio is not supported"); + break; + } + + } +}; + + +AudioStream *make8SVXStream(Common::ReadStream &input, bool loop) { + A8SVXLoader loader; + loader.load(input); + + SeekableAudioStream *stream = Audio::makeRawStream((byte *)loader._data, loader._dataSize, loader._header.samplesPerSec, 0); + + uint32 loopStart = 0, loopEnd = 0; + if (loop) { + // the standard way to loop 8SVX audio implies use of the oneShotHiSamples and + // repeatHiSamples fields + loopStart = 0; + loopEnd = loader._header.oneShotHiSamples + loader._header.repeatHiSamples; + + if (loopStart != loopEnd) { + return new SubLoopingAudioStream(stream, 0, + Timestamp(0, loopStart, loader._header.samplesPerSec), + Timestamp(0, loopEnd, loader._header.samplesPerSec)); + } + } + + return stream; +} + +} diff --git a/audio/decoders/iff_sound.h b/audio/decoders/iff_sound.h new file mode 100644 index 0000000000..4e53059380 --- /dev/null +++ b/audio/decoders/iff_sound.h @@ -0,0 +1,47 @@ +/* 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$ + * + */ + +/** + * @file + * Sound decoder used in engines: + * - parallaction + */ + +#ifndef SOUND_IFF_H +#define SOUND_IFF_H + +namespace Common { + class ReadStream; +} + +namespace Audio { + +class AudioStream; + +AudioStream *make8SVXStream(Common::ReadStream &stream, bool loop); + +} + +#endif diff --git a/audio/decoders/mac_snd.cpp b/audio/decoders/mac_snd.cpp new file mode 100644 index 0000000000..7c1a2f75f0 --- /dev/null +++ b/audio/decoders/mac_snd.cpp @@ -0,0 +1,116 @@ +/* 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$ + * + */ + +/* + * The code in this file is based on information found at + * http://developer.apple.com/legacy/mac/library/documentation/mac/Sound/Sound-60.html#HEADING60-15 + * + * We implement both type 1 and type 2 snd resources, but only those that are sampled + */ + +#include "common/util.h" +#include "common/stream.h" + +#include "audio/decoders/mac_snd.h" +#include "audio/audiostream.h" +#include "audio/decoders/raw.h" + +namespace Audio { + +SeekableAudioStream *makeMacSndStream(Common::SeekableReadStream *stream, + DisposeAfterUse::Flag disposeAfterUse) { + + uint16 sndType = stream->readUint16BE(); + + if (sndType == 1) { + // "normal" snd resources + if (stream->readUint16BE() != 1) { + warning("makeMacSndStream(): Unsupported data type count"); + return 0; + } + + if (stream->readUint16BE() != 5) { + // 5 == sampled + warning("makeMacSndStream(): Unsupported data type"); + return 0; + } + + stream->readUint32BE(); // initialization option + } else if (sndType == 2) { + // old HyperCard snd resources + stream->readUint16BE(); // reference count (unused) + } else { + warning("makeMacSndStream(): Unknown format type %d", sndType); + return 0; + } + + // We really should never get this as long as we have sampled data only + if (stream->readUint16BE() != 1) { + warning("makeMacSndStream(): Unsupported command count"); + return 0; + } + + uint16 command = stream->readUint16BE(); + + // 0x8050 - soundCmd (with dataOffsetFlag set): install a sampled sound as a voice + // 0x8051 - bufferCmd (with dataOffsetFlag set): play a sample sound + if (command != 0x8050 && command != 0x8051) { + warning("makeMacSndStream(): Unsupported command %04x", command); + return 0; + } + + stream->readUint16BE(); // 0 + uint32 soundHeaderOffset = stream->readUint32BE(); + + stream->seek(soundHeaderOffset); + + uint32 soundDataOffset = stream->readUint32BE(); + uint32 size = stream->readUint32BE(); + uint16 rate = stream->readUint32BE() >> 16; // Really fixed point, but we only support integer rates + stream->readUint32BE(); // loop start + stream->readUint32BE(); // loop end + byte encoding = stream->readByte(); + stream->readByte(); // base frequency + + if (encoding != 0) { + // 0 == PCM + warning("makeMacSndStream(): Unsupported compression %d", encoding); + return 0; + } + + stream->skip(soundDataOffset); + + byte *data = (byte *)malloc(size); + assert(data); + stream->read(data, size); + + if (disposeAfterUse == DisposeAfterUse::YES) + delete stream; + + // Since we allocated our own buffer for the data, we must specify DisposeAfterUse::YES. + return makeRawStream(data, size, rate, Audio::FLAG_UNSIGNED); +} + +} // End of namespace Audio diff --git a/audio/decoders/mac_snd.h b/audio/decoders/mac_snd.h new file mode 100644 index 0000000000..198a61333e --- /dev/null +++ b/audio/decoders/mac_snd.h @@ -0,0 +1,58 @@ +/* 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$ + * + */ + +/** + * @file + * Sound decoder used in engines: + * - sci + */ + +#ifndef SOUND_MAC_SND_H +#define SOUND_MAC_SND_H + +#include "common/scummsys.h" +#include "common/types.h" + +namespace Common { class SeekableReadStream; } + +namespace Audio { + +class SeekableAudioStream; + +/** + * Try to load a Mac snd resource from the given seekable stream and create a SeekableAudioStream + * from that data. + * + * @param stream the SeekableReadStream from which to read the snd data + * @param disposeAfterUse whether to delete the stream after use + * @return a new SeekableAudioStream, or NULL, if an error occurred + */ +SeekableAudioStream *makeMacSndStream( + Common::SeekableReadStream *stream, + DisposeAfterUse::Flag disposeAfterUse); + +} // End of namespace Audio + +#endif 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 diff --git a/audio/decoders/mp3.h b/audio/decoders/mp3.h new file mode 100644 index 0000000000..72bc6e1b3e --- /dev/null +++ b/audio/decoders/mp3.h @@ -0,0 +1,76 @@ +/* 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$ + * + */ + +/** + * @file + * Sound decoder used in engines: + * - agos + * - draci + * - kyra + * - m4 + * - mohawk + * - queen + * - saga + * - sci + * - scumm + * - sword1 + * - sword2 + * - touche + * - tucker + */ + +#ifndef SOUND_MP3_H +#define SOUND_MP3_H + +#include "common/scummsys.h" +#include "common/types.h" + +#ifdef USE_MAD + +namespace Common { + class SeekableReadStream; +} + +namespace Audio { + +class AudioStream; +class SeekableAudioStream; + +/** + * Create a new SeekableAudioStream from the MP3 data in the given stream. + * Allows for seeking (which is why we require a SeekableReadStream). + * + * @param stream the SeekableReadStream from which to read the MP3 data + * @param disposeAfterUse whether to delete the stream after use + * @return a new SeekableAudioStream, or NULL, if an error occurred + */ +SeekableAudioStream *makeMP3Stream( + Common::SeekableReadStream *stream, + DisposeAfterUse::Flag disposeAfterUse); + +} // End of namespace Audio + +#endif // #ifdef USE_MAD +#endif // #ifndef SOUND_MP3_H diff --git a/audio/decoders/raw.cpp b/audio/decoders/raw.cpp new file mode 100644 index 0000000000..8b833c7838 --- /dev/null +++ b/audio/decoders/raw.cpp @@ -0,0 +1,356 @@ +/* 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 "common/endian.h" +#include "common/memstream.h" + +#include "audio/audiostream.h" +#include "audio/mixer.h" +#include "audio/decoders/raw.h" + +namespace Audio { + +// This used to be an inline template function, but +// buggy template function handling in MSVC6 forced +// us to go with the macro approach. So far this is +// the only template function that MSVC6 seemed to +// compile incorrectly. Knock on wood. +#define READ_ENDIAN_SAMPLE(is16Bit, isUnsigned, ptr, isLE) \ + ((is16Bit ? (isLE ? READ_LE_UINT16(ptr) : READ_BE_UINT16(ptr)) : (*ptr << 8)) ^ (isUnsigned ? 0x8000 : 0)) + + +#pragma mark - +#pragma mark --- RawStream --- +#pragma mark - + +/** + * This is a stream, which allows for playing raw PCM data from a stream. + * It also features playback of multiple blocks from a given stream. + */ +template<bool is16Bit, bool isUnsigned, bool isLE> +class RawStream : public SeekableAudioStream { +public: + RawStream(int rate, bool stereo, DisposeAfterUse::Flag disposeStream, Common::SeekableReadStream *stream, const RawStreamBlockList &blocks) + : _rate(rate), _isStereo(stereo), _playtime(0, rate), _stream(stream), _disposeAfterUse(disposeStream), _blocks(blocks), _curBlock(_blocks.begin()), _blockLeft(0), _buffer(0) { + + assert(_blocks.size() > 0); + + // Setup our buffer for readBuffer + _buffer = new byte[kSampleBufferLength * (is16Bit ? 2 : 1)]; + assert(_buffer); + + // Set current buffer state, playing first block + _stream->seek(_curBlock->pos, SEEK_SET); + + // In case of an error we will stop (or rather + // not start) stream playback. + if (_stream->err()) { + _blockLeft = 0; + _curBlock = _blocks.end(); + } else { + _blockLeft = _curBlock->len; + } + + // Add up length of all blocks in order to caluclate total play time + int32 len = 0; + for (RawStreamBlockList::const_iterator i = _blocks.begin(); i != _blocks.end(); ++i) { + assert(i->len % (_isStereo ? 2 : 1) == 0); + len += i->len; + } + + _playtime = Timestamp(0, len / (_isStereo ? 2 : 1), rate); + } + + ~RawStream() { + if (_disposeAfterUse == DisposeAfterUse::YES) + delete _stream; + + delete[] _buffer; + } + + int readBuffer(int16 *buffer, const int numSamples); + + bool isStereo() const { return _isStereo; } + bool endOfData() const { return (_curBlock == _blocks.end()) && (_blockLeft == 0); } + + int getRate() const { return _rate; } + Timestamp getLength() const { return _playtime; } + + bool seek(const Timestamp &where); +private: + const int _rate; ///< Sample rate of stream + const bool _isStereo; ///< Whether this is an stereo stream + Timestamp _playtime; ///< Calculated total play time + Common::SeekableReadStream *_stream; ///< Stream to read data from + const DisposeAfterUse::Flag _disposeAfterUse; ///< Indicates whether the stream object should be deleted when this RawStream is destructed + const RawStreamBlockList _blocks; ///< Audio block list + + RawStreamBlockList::const_iterator _curBlock; ///< Current audio block number + int32 _blockLeft; ///< How many bytes are still left in the current block + + /** + * Advance one block in the stream in case + * the current one is empty. + */ + void updateBlockIfNeeded(); + + byte *_buffer; ///< Buffer used in readBuffer + enum { + /** + * How many samples we can buffer at once. + * + * TODO: Check whether this size suffices + * for systems with slow disk I/O. + */ + kSampleBufferLength = 2048 + }; + + /** + * Fill the temporary sample buffer used in readBuffer. + * + * @param maxSamples Maximum samples to read. + * @return actual count of samples read. + */ + int fillBuffer(int maxSamples); +}; + +template<bool is16Bit, bool isUnsigned, bool isLE> +int RawStream<is16Bit, isUnsigned, isLE>::readBuffer(int16 *buffer, const int numSamples) { + int samplesLeft = numSamples; + + while (samplesLeft > 0) { + // Try to read up to "samplesLeft" samples. + int len = fillBuffer(samplesLeft); + + // In case we were not able to read any samples + // we will stop reading here. + if (!len) + break; + + // Adjust the samples left to read. + samplesLeft -= len; + + // Copy the data to the caller's buffer. + const byte *src = _buffer; + while (len-- > 0) { + *buffer++ = READ_ENDIAN_SAMPLE(is16Bit, isUnsigned, src, isLE); + src += (is16Bit ? 2 : 1); + } + } + + return numSamples - samplesLeft; +} + +template<bool is16Bit, bool isUnsigned, bool isLE> +int RawStream<is16Bit, isUnsigned, isLE>::fillBuffer(int maxSamples) { + int bufferedSamples = 0; + byte *dst = _buffer; + + // We can only read up to "kSampleBufferLength" samples + // so we take this into consideration, when trying to + // read up to maxSamples. + maxSamples = MIN<int>(kSampleBufferLength, maxSamples); + + // We will only read up to maxSamples + while (maxSamples > 0 && !endOfData()) { + // Calculate how many samples we can safely read + // from the current block. + const int len = MIN<int>(maxSamples, _blockLeft); + + // Try to read all the sample data and update the + // destination pointer. + const int bytesRead = _stream->read(dst, len * (is16Bit ? 2 : 1)); + dst += bytesRead; + + // Calculate how many samples we actually read. + const int samplesRead = bytesRead / (is16Bit ? 2 : 1); + + // Update all status variables + bufferedSamples += samplesRead; + maxSamples -= samplesRead; + _blockLeft -= samplesRead; + + // In case of an error we will stop + // stream playback. + if (_stream->err()) { + _blockLeft = 0; + _curBlock = _blocks.end(); + } + + // Advance to the next block in case the current + // one is already finished. + updateBlockIfNeeded(); + } + + return bufferedSamples; +} + +template<bool is16Bit, bool isUnsigned, bool isLE> +void RawStream<is16Bit, isUnsigned, isLE>::updateBlockIfNeeded() { + // Have we now finished this block? If so, read the next block + if (_blockLeft == 0 && _curBlock != _blocks.end()) { + // Next block + ++_curBlock; + + // Check whether we reached the end of the stream + // yet. In case we did not do this, we will just + // setup the next block as new block. + if (_curBlock != _blocks.end()) { + _stream->seek(_curBlock->pos, SEEK_SET); + + // In case of an error we will stop + // stream playback. + if (_stream->err()) { + _blockLeft = 0; + _curBlock = _blocks.end(); + } else { + _blockLeft = _curBlock->len; + } + } + } +} + +template<bool is16Bit, bool isUnsigned, bool isLE> +bool RawStream<is16Bit, isUnsigned, isLE>::seek(const Timestamp &where) { + _blockLeft = 0; + _curBlock = _blocks.end(); + + if (where > _playtime) + return false; + + const uint32 seekSample = convertTimeToStreamPos(where, getRate(), isStereo()).totalNumberOfFrames(); + uint32 curSample = 0; + + // Search for the disk block in which the specific sample is placed + for (_curBlock = _blocks.begin(); _curBlock != _blocks.end(); ++_curBlock) { + uint32 nextBlockSample = curSample + _curBlock->len; + + if (nextBlockSample > seekSample) + break; + + curSample = nextBlockSample; + } + + if (_curBlock == _blocks.end()) { + return ((seekSample - curSample) == 0); + } else { + const uint32 offset = seekSample - curSample; + + _stream->seek(_curBlock->pos + offset * (is16Bit ? 2 : 1), SEEK_SET); + + // In case of an error we will stop + // stream playback. + if (_stream->err()) { + _blockLeft = 0; + _curBlock = _blocks.end(); + } else { + _blockLeft = _curBlock->len - offset; + } + + return true; + } +} + +#pragma mark - +#pragma mark --- Raw stream factories --- +#pragma mark - + +/* In the following, we use preprocessor / macro tricks to simplify the code + * which instantiates the input streams. We used to use template functions for + * this, but MSVC6 / EVC 3-4 (used for WinCE builds) are extremely buggy when it + * comes to this feature of C++... so as a compromise we use macros to cut down + * on the (source) code duplication a bit. + * So while normally macro tricks are said to make maintenance harder, in this + * particular case it should actually help it :-) + */ + +#define MAKE_RAW_STREAM(UNSIGNED) \ + if (is16Bit) { \ + if (isLE) \ + return new RawStream<true, UNSIGNED, true>(rate, isStereo, disposeAfterUse, stream, blockList); \ + else \ + return new RawStream<true, UNSIGNED, false>(rate, isStereo, disposeAfterUse, stream, blockList); \ + } else \ + return new RawStream<false, UNSIGNED, false>(rate, isStereo, disposeAfterUse, stream, blockList) + +SeekableAudioStream *makeRawStream(Common::SeekableReadStream *stream, + const RawStreamBlockList &blockList, + int rate, + byte flags, + DisposeAfterUse::Flag disposeAfterUse) { + const bool isStereo = (flags & Audio::FLAG_STEREO) != 0; + const bool is16Bit = (flags & Audio::FLAG_16BITS) != 0; + const bool isUnsigned = (flags & Audio::FLAG_UNSIGNED) != 0; + const bool isLE = (flags & Audio::FLAG_LITTLE_ENDIAN) != 0; + + if (blockList.empty()) { + warning("Empty block list passed to makeRawStream"); + if (disposeAfterUse == DisposeAfterUse::YES) + delete stream; + return 0; + } + + if (isUnsigned) { + MAKE_RAW_STREAM(true); + } else { + MAKE_RAW_STREAM(false); + } +} + +SeekableAudioStream *makeRawStream(Common::SeekableReadStream *stream, + int rate, byte flags, + DisposeAfterUse::Flag disposeAfterUse) { + RawStreamBlockList blocks; + RawStreamBlock block; + block.pos = 0; + + const bool isStereo = (flags & Audio::FLAG_STEREO) != 0; + const bool is16Bit = (flags & Audio::FLAG_16BITS) != 0; + + assert(stream->size() % ((is16Bit ? 2 : 1) * (isStereo ? 2 : 1)) == 0); + + block.len = stream->size() / (is16Bit ? 2 : 1); + blocks.push_back(block); + + return makeRawStream(stream, blocks, rate, flags, disposeAfterUse); +} + +SeekableAudioStream *makeRawStream(const byte *buffer, uint32 size, + int rate, byte flags, + DisposeAfterUse::Flag disposeAfterUse) { + return makeRawStream(new Common::MemoryReadStream(buffer, size, disposeAfterUse), rate, flags, DisposeAfterUse::YES); +} + +SeekableAudioStream *makeRawDiskStream_OLD(Common::SeekableReadStream *stream, RawStreamBlock *block, int numBlocks, + int rate, byte flags, DisposeAfterUse::Flag disposeStream) { + assert(numBlocks > 0); + RawStreamBlockList blocks; + for (int i = 0; i < numBlocks; ++i) + blocks.push_back(block[i]); + + return makeRawStream(stream, blocks, rate, flags, disposeStream); +} + +} // End of namespace Audio diff --git a/audio/decoders/raw.h b/audio/decoders/raw.h new file mode 100644 index 0000000000..3e9426012c --- /dev/null +++ b/audio/decoders/raw.h @@ -0,0 +1,153 @@ +/* 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$ + * + */ + +#ifndef SOUND_RAW_H +#define SOUND_RAW_H + +#include "common/scummsys.h" +#include "common/types.h" + +#include "common/list.h" + + +namespace Common { class SeekableReadStream; } + + +namespace Audio { + +class AudioStream; +class SeekableAudioStream; + +/** + * Various flags which can be bit-ORed and then passed to + * makeRawStream and some other AudioStream factories + * to control their behavior. + * + * Engine authors are advised not to rely on a certain value or + * order of these flags (in particular, do not store them verbatim + * in savestates). + */ +enum RawFlags { + /** unsigned samples (default: signed) */ + FLAG_UNSIGNED = 1 << 0, + + /** sound is 16 bits wide (default: 8bit) */ + FLAG_16BITS = 1 << 1, + + /** samples are little endian (default: big endian) */ + FLAG_LITTLE_ENDIAN = 1 << 2, + + /** sound is in stereo (default: mono) */ + FLAG_STEREO = 1 << 3 +}; + + +/** + * Struct used to define the audio data to be played by a RawStream. + */ +struct RawStreamBlock { + int32 pos; ///< Position in stream of the block (in bytes of course!) + int32 len; ///< Length of the block (in raw samples, not sample pairs!) +}; + +/** + * List containing all blocks of a raw stream. + * @see RawStreamBlock + */ +typedef Common::List<RawStreamBlock> RawStreamBlockList; + +/** + * Creates an audio stream, which plays from the given buffer. + * + * @param buffer Buffer to play from. + * @param size Size of the buffer in bytes. + * @param rate Rate of the sound data. + * @param flags Audio flags combination. + * @see RawFlags + * @param disposeAfterUse Whether to free the buffer after use (with free!). + * @return The new SeekableAudioStream (or 0 on failure). + */ +SeekableAudioStream *makeRawStream(const byte *buffer, uint32 size, + int rate, byte flags, + DisposeAfterUse::Flag disposeAfterUse = DisposeAfterUse::YES); + +/** + * Creates an audio stream, which plays from the given stream. + * + * @param stream Stream object to play from. + * @param rate Rate of the sound data. + * @param flags Audio flags combination. + * @see RawFlags + * @param disposeAfterUse Whether to delete the stream after use. + * @return The new SeekableAudioStream (or 0 on failure). + */ +SeekableAudioStream *makeRawStream(Common::SeekableReadStream *stream, + int rate, byte flags, + DisposeAfterUse::Flag disposeAfterUse = DisposeAfterUse::YES); + +/** + * Creates an audio stream, which plays from the given stream. + * + * @param stream Stream object to play from. + * @param blockList List of blocks to play. + * @see RawDiskStreamAudioBlock + * @see RawStreamBlockList + * @param rate Rate of the sound data. + * @param flags Audio flags combination. + * @see RawFlags + * @param disposeAfterUse Whether to delete the stream after use. + * @return The new SeekableAudioStream (or 0 on failure). + */ +SeekableAudioStream *makeRawStream(Common::SeekableReadStream *stream, + const RawStreamBlockList &blockList, + int rate, + byte flags, + DisposeAfterUse::Flag disposeAfterUse = DisposeAfterUse::YES); + +/** + * NOTE: + * This API is considered deprecated. + * + * Creates a audio stream, which plays from given stream. + * + * @param stream Stream to play from + * @param block Pointer to an RawStreamBlock array + * @see RawStreamBlock + * @param numBlocks Number of blocks. + * @param rate The rate + * @param flags Flags combination. + * @see RawFlags + * @param disposeStream Whether the "stream" object should be destroyed after playback. + * @return The new SeekableAudioStream (or 0 on failure). + */ +SeekableAudioStream *makeRawDiskStream_OLD(Common::SeekableReadStream *stream, + RawStreamBlock *block, int numBlocks, + int rate, byte flags, + DisposeAfterUse::Flag disposeStream); + + +} // End of namespace Audio + +#endif diff --git a/audio/decoders/vag.cpp b/audio/decoders/vag.cpp new file mode 100644 index 0000000000..2c3a36202a --- /dev/null +++ b/audio/decoders/vag.cpp @@ -0,0 +1,150 @@ +/* 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/vag.h" +#include "audio/audiostream.h" +#include "common/stream.h" + +namespace Audio { + +class VagStream : public Audio::RewindableAudioStream { +public: + VagStream(Common::SeekableReadStream *stream, int rate); + ~VagStream(); + + bool isStereo() const { return false; } + bool endOfData() const { return _stream->pos() == _stream->size(); } + int getRate() const { return _rate; } + int readBuffer(int16 *buffer, const int numSamples); + + bool rewind(); +private: + Common::SeekableReadStream *_stream; + + byte _predictor; + double _samples[28]; + byte _samplesRemaining; + int _rate; + double _s1, _s2; +}; + +VagStream::VagStream(Common::SeekableReadStream *stream, int rate) : _stream(stream) { + _samplesRemaining = 0; + _predictor = 0; + _s1 = _s2 = 0.0; + _rate = rate; +} + + +VagStream::~VagStream() { + delete _stream; +} + +static const double s_vagDataTable[5][2] = + { + { 0.0, 0.0 }, + { 60.0 / 64.0, 0.0 }, + { 115.0 / 64.0, -52.0 / 64.0 }, + { 98.0 / 64.0, -55.0 / 64.0 }, + { 122.0 / 64.0, -60.0 / 64.0 } + }; + +int VagStream::readBuffer(int16 *buffer, const int numSamples) { + int32 samplesDecoded = 0; + + if (_samplesRemaining) { + byte i = 0; + + for (i = 28 - _samplesRemaining; i < 28 && samplesDecoded < numSamples; i++) { + _samples[i] = _samples[i] + _s1 * s_vagDataTable[_predictor][0] + _s2 * s_vagDataTable[_predictor][1]; + _s2 = _s1; + _s1 = _samples[i]; + int16 d = (int) (_samples[i] + 0.5); + buffer[samplesDecoded] = d; + samplesDecoded++; + } + +#if 0 + assert(i == 28); // We're screwed if this fails :P +#endif + // This might mean the file is corrupted, or that the stream has + // been closed. + if (i != 28) return 0; + + _samplesRemaining = 0; + } + + while (samplesDecoded < numSamples) { + byte i = 0; + + _predictor = _stream->readByte(); + byte shift = _predictor & 0xf; + _predictor >>= 4; + + if (_stream->readByte() == 7) + return samplesDecoded; + + for (i = 0; i < 28; i += 2) { + byte d = _stream->readByte(); + int16 s = (d & 0xf) << 12; + if (s & 0x8000) + s |= 0xffff0000; + _samples[i] = (double)(s >> shift); + s = (d & 0xf0) << 8; + if (s & 0x8000) + s |= 0xffff0000; + _samples[i + 1] = (double)(s >> shift); + } + + for (i = 0; i < 28 && samplesDecoded < numSamples; i++) { + _samples[i] = _samples[i] + _s1 * s_vagDataTable[_predictor][0] + _s2 * s_vagDataTable[_predictor][1]; + _s2 = _s1; + _s1 = _samples[i]; + int16 d = (int) (_samples[i] + 0.5); + buffer[samplesDecoded] = d; + samplesDecoded++; + } + + if (i != 27) + _samplesRemaining = 28 - i; + } + + return samplesDecoded; +} + +bool VagStream::rewind() { + _stream->seek(0); + _samplesRemaining = 0; + _predictor = 0; + _s1 = _s2 = 0.0; + + return true; +} + +RewindableAudioStream *makeVagStream(Common::SeekableReadStream *stream, int rate) { + return new VagStream(stream, rate); +} + +} diff --git a/audio/decoders/vag.h b/audio/decoders/vag.h new file mode 100644 index 0000000000..cdf91a8ea1 --- /dev/null +++ b/audio/decoders/vag.h @@ -0,0 +1,60 @@ +/* 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$ + * + */ + +/** + * @file + * Sound decoder used in engines: + * - sword1 (PSX port of the game) + * - sword2 (PSX port of the game) + * - tinsel (PSX port of the game) + */ + +#ifndef SOUND_VAG_H +#define SOUND_VAG_H + +namespace Common { + class SeekableReadStream; +} + +namespace Audio { + +class AudioStream; +class RewindableAudioStream; + +/** + * Takes an input stream containing Vag sound data and creates + * an RewindableAudioStream from that. + * + * @param stream the SeekableReadStream from which to read the ADPCM data + * @param rate the sampling rate + * @return a new RewindableAudioStream, or NULL, if an error occurred + */ +RewindableAudioStream *makeVagStream( + Common::SeekableReadStream *stream, + int rate = 11025); + +} // End of namespace Sword1 + +#endif diff --git a/audio/decoders/voc.cpp b/audio/decoders/voc.cpp new file mode 100644 index 0000000000..b811a640ec --- /dev/null +++ b/audio/decoders/voc.cpp @@ -0,0 +1,403 @@ +/* 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 "common/debug.h" +#include "common/endian.h" +#include "common/util.h" +#include "common/stream.h" + +#include "audio/audiostream.h" +#include "audio/mixer.h" +#include "audio/decoders/raw.h" +#include "audio/decoders/voc.h" + + +namespace Audio { + +int getSampleRateFromVOCRate(int vocSR) { + if (vocSR == 0xa5 || vocSR == 0xa6) { + return 11025; + } else if (vocSR == 0xd2 || vocSR == 0xd3) { + return 22050; + } else { + int sr = 1000000L / (256L - vocSR); + // inexact sampling rates occur e.g. in the kitchen in Monkey Island, + // very easy to reach right from the start of the game. + //warning("inexact sample rate used: %i (0x%x)", sr, vocSR); + return sr; + } +} + +static byte *loadVOCFromStream(Common::ReadStream &stream, int &size, int &rate, int &loops, int &begin_loop, int &end_loop) { + VocFileHeader fileHeader; + + debug(2, "loadVOCFromStream"); + + if (stream.read(&fileHeader, 8) != 8) + goto invalid; + + if (!memcmp(&fileHeader, "VTLK", 4)) { + if (stream.read(&fileHeader, sizeof(VocFileHeader)) != sizeof(VocFileHeader)) + goto invalid; + } else if (!memcmp(&fileHeader, "Creative", 8)) { + if (stream.read(((byte *)&fileHeader) + 8, sizeof(VocFileHeader) - 8) != sizeof(VocFileHeader) - 8) + goto invalid; + } else { + invalid:; + warning("loadVOCFromStream: Invalid header"); + return NULL; + } + + if (memcmp(fileHeader.desc, "Creative Voice File", 19) != 0) + error("loadVOCFromStream: Invalid header"); + if (fileHeader.desc[19] != 0x1A) + debug(3, "loadVOCFromStream: Partially invalid header"); + + int32 offset = FROM_LE_16(fileHeader.datablock_offset); + int16 version = FROM_LE_16(fileHeader.version); + int16 code = FROM_LE_16(fileHeader.id); + assert(offset == sizeof(VocFileHeader)); + // 0x100 is an invalid VOC version used by German version of DOTT (Disk) and + // French version of Simon the Sorcerer 2 (CD) + assert(version == 0x010A || version == 0x0114 || version == 0x0100); + assert(code == ~version + 0x1234); + + int len; + byte *ret_sound = 0; + size = 0; + begin_loop = 0; + end_loop = 0; + + while ((code = stream.readByte())) { + len = stream.readByte(); + len |= stream.readByte() << 8; + len |= stream.readByte() << 16; + + debug(2, "Block code %d, len %d", code, len); + + switch (code) { + case 1: + case 9: { + int packing; + if (code == 1) { + int time_constant = stream.readByte(); + packing = stream.readByte(); + len -= 2; + rate = getSampleRateFromVOCRate(time_constant); + } else { + rate = stream.readUint32LE(); + int bits = stream.readByte(); + int channels = stream.readByte(); + if (bits != 8 || channels != 1) { + warning("Unsupported VOC file format (%d bits per sample, %d channels)", bits, channels); + break; + } + packing = stream.readUint16LE(); + stream.readUint32LE(); + len -= 12; + } + debug(9, "VOC Data Block: %d, %d, %d", rate, packing, len); + if (packing == 0) { + if (size) { + ret_sound = (byte *)realloc(ret_sound, size + len); + } else { + ret_sound = (byte *)malloc(len); + } + stream.read(ret_sound + size, len); + size += len; + begin_loop = size; + end_loop = size; + } else { + warning("VOC file packing %d unsupported", packing); + } + } break; + case 3: // silence + // occur with a few Igor sounds, voc file starts with a silence block with a + // frequency different from the data block. Just ignore fow now (implementing + // it wouldn't make a big difference anyway...) + assert(len == 3); + stream.readUint16LE(); + stream.readByte(); + break; + case 6: // begin of loop + assert(len == 2); + loops = stream.readUint16LE(); + break; + case 7: // end of loop + assert(len == 0); + break; + case 8: { // "Extended" + // This occures in the LoL Intro demo. + // This block overwrites the next parameters of a block 1 "Sound data". + // To assure we never get any bad data here, we will assert in case + // this tries to define a stereo sound block or tries to use something + // different than 8bit unsigned sound data. + // TODO: Actually we would need to check the frequency divisor (the + // first word) here too. It is used in the following equation: + // sampleRate = 256000000/(channels * (65536 - frequencyDivisor)) + assert(len == 4); + stream.readUint16LE(); + uint8 codec = stream.readByte(); + uint8 channels = stream.readByte() + 1; + assert(codec == 0 && channels == 1); + } break; + default: + warning("Unhandled code %d in VOC file (len %d)", code, len); + return ret_sound; + } + } + debug(4, "VOC Data Size : %d", size); + return ret_sound; +} + +byte *loadVOCFromStream(Common::ReadStream &stream, int &size, int &rate) { + int loops, begin_loop, end_loop; + return loadVOCFromStream(stream, size, rate, loops, begin_loop, end_loop); +} + + +#ifdef STREAM_AUDIO_FROM_DISK + +int parseVOCFormat(Common::SeekableReadStream& stream, RawStreamBlock* block, int &rate, int &loops, int &begin_loop, int &end_loop) { + VocFileHeader fileHeader; + int currentBlock = 0; + int size = 0; + + debug(2, "parseVOCFormat"); + + if (stream.read(&fileHeader, 8) != 8) + goto invalid; + + if (!memcmp(&fileHeader, "VTLK", 4)) { + if (stream.read(&fileHeader, sizeof(VocFileHeader)) != sizeof(VocFileHeader)) + goto invalid; + } else if (!memcmp(&fileHeader, "Creative", 8)) { + if (stream.read(((byte *)&fileHeader) + 8, sizeof(VocFileHeader) - 8) != sizeof(VocFileHeader) - 8) + goto invalid; + } else { + invalid:; + warning("loadVOCFromStream: Invalid header"); + return 0; + } + + if (memcmp(fileHeader.desc, "Creative Voice File", 19) != 0) + error("loadVOCFromStream: Invalid header"); + if (fileHeader.desc[19] != 0x1A) + debug(3, "loadVOCFromStream: Partially invalid header"); + + int32 offset = FROM_LE_16(fileHeader.datablock_offset); + int16 version = FROM_LE_16(fileHeader.version); + int16 code = FROM_LE_16(fileHeader.id); + assert(offset == sizeof(VocFileHeader)); + // 0x100 is an invalid VOC version used by German version of DOTT (Disk) and + // French version of Simon the Sorcerer 2 (CD) + assert(version == 0x010A || version == 0x0114 || version == 0x0100); + assert(code == ~version + 0x1234); + + int len; + size = 0; + begin_loop = 0; + end_loop = 0; + + while ((code = stream.readByte())) { + len = stream.readByte(); + len |= stream.readByte() << 8; + len |= stream.readByte() << 16; + + debug(2, "Block code %d, len %d", code, len); + + switch (code) { + case 1: + case 9: { + int packing; + if (code == 1) { + int time_constant = stream.readByte(); + packing = stream.readByte(); + len -= 2; + rate = getSampleRateFromVOCRate(time_constant); + } else { + rate = stream.readUint32LE(); + int bits = stream.readByte(); + int channels = stream.readByte(); + if (bits != 8 || channels != 1) { + warning("Unsupported VOC file format (%d bits per sample, %d channels)", bits, channels); + break; + } + packing = stream.readUint16LE(); + stream.readUint32LE(); + len -= 12; + } + debug(9, "VOC Data Block: %d, %d, %d", rate, packing, len); + if (packing == 0) { + + // Found a data block - so add it to the block list + block[currentBlock].pos = stream.pos(); + block[currentBlock].len = len; + currentBlock++; + + stream.seek(len, SEEK_CUR); + + size += len; + begin_loop = size; + end_loop = size; + } else { + warning("VOC file packing %d unsupported", packing); + } + } break; + case 3: // silence + // occur with a few Igor sounds, voc file starts with a silence block with a + // frequency different from the data block. Just ignore fow now (implementing + // it wouldn't make a big difference anyway...) + assert(len == 3); + stream.readUint16LE(); + stream.readByte(); + break; + case 6: // begin of loop + assert(len == 2); + loops = stream.readUint16LE(); + break; + case 7: // end of loop + assert(len == 0); + break; + case 8: // "Extended" + // This occures in the LoL Intro demo. This block can usually be used to create stereo + // sound, but the LoL intro has only an empty block, thus this dummy implementation will + // work. + assert(len == 4); + stream.readUint16LE(); + stream.readByte(); + stream.readByte(); + break; + default: + warning("Unhandled code %d in VOC file (len %d)", code, len); + return 0; + } + } + debug(4, "VOC Data Size : %d", size); + return currentBlock; +} + +AudioStream *makeVOCDiskStream(Common::SeekableReadStream *stream, byte flags, DisposeAfterUse::Flag disposeAfterUse) { + const int MAX_AUDIO_BLOCKS = 256; + + RawStreamBlock *block = new RawStreamBlock[MAX_AUDIO_BLOCKS]; + int rate, loops, begin_loop, end_loop; + + int numBlocks = parseVOCFormat(*stream, block, rate, loops, begin_loop, end_loop); + + AudioStream *audioStream = 0; + + // Create an audiostream from the data. Note the numBlocks may be 0, + // e.g. when invalid data is encountered. See bug #2890038. + if (numBlocks) + audioStream = makeRawDiskStream_OLD(stream, block, numBlocks, rate, flags, disposeAfterUse/*, begin_loop, end_loop*/); + + delete[] block; + + return audioStream; +} + +SeekableAudioStream *makeVOCDiskStreamNoLoop(Common::SeekableReadStream *stream, byte flags, DisposeAfterUse::Flag disposeAfterUse) { + const int MAX_AUDIO_BLOCKS = 256; + + RawStreamBlock *block = new RawStreamBlock[MAX_AUDIO_BLOCKS]; + int rate, loops, begin_loop, end_loop; + + int numBlocks = parseVOCFormat(*stream, block, rate, loops, begin_loop, end_loop); + + SeekableAudioStream *audioStream = 0; + + // Create an audiostream from the data. Note the numBlocks may be 0, + // e.g. when invalid data is encountered. See bug #2890038. + if (numBlocks) + audioStream = makeRawDiskStream_OLD(stream, block, numBlocks, rate, flags, disposeAfterUse); + + delete[] block; + + return audioStream; +} + +#endif + + +AudioStream *makeVOCStream(Common::SeekableReadStream *stream, byte flags, uint loopStart, uint loopEnd, DisposeAfterUse::Flag disposeAfterUse) { +#ifdef STREAM_AUDIO_FROM_DISK + return makeVOCDiskStream(stream, flags, disposeAfterUse); +#else + int size, rate; + + byte *data = loadVOCFromStream(*stream, size, rate); + + if (!data) { + if (disposeAfterUse == DisposeAfterUse::YES) + delete stream; + return 0; + } + + SeekableAudioStream *s = Audio::makeRawStream(data, size, rate, flags); + + if (loopStart != loopEnd) { + const bool isStereo = (flags & Audio::FLAG_STEREO) != 0; + const bool is16Bit = (flags & Audio::FLAG_16BITS) != 0; + + if (loopEnd == 0) + loopEnd = size; + assert(loopStart <= loopEnd); + assert(loopEnd <= (uint)size); + + // Verify the buffer sizes are sane + if (is16Bit && isStereo) + assert((loopStart & 3) == 0 && (loopEnd & 3) == 0); + else if (is16Bit || isStereo) + assert((loopStart & 1) == 0 && (loopEnd & 1) == 0); + + const uint32 extRate = s->getRate() * (is16Bit ? 2 : 1) * (isStereo ? 2 : 1); + + return new SubLoopingAudioStream(s, 0, Timestamp(0, loopStart, extRate), Timestamp(0, loopEnd, extRate)); + } else { + return s; + } +#endif +} + +SeekableAudioStream *makeVOCStream(Common::SeekableReadStream *stream, byte flags, DisposeAfterUse::Flag disposeAfterUse) { +#ifdef STREAM_AUDIO_FROM_DISK + return makeVOCDiskStreamNoLoop(stream, flags, disposeAfterUse); +#else + int size, rate; + + byte *data = loadVOCFromStream(*stream, size, rate); + + if (!data) { + if (disposeAfterUse == DisposeAfterUse::YES) + delete stream; + return 0; + } + + return makeRawStream(data, size, rate, flags); +#endif +} + +} // End of namespace Audio diff --git a/audio/decoders/voc.h b/audio/decoders/voc.h new file mode 100644 index 0000000000..82cc261f2c --- /dev/null +++ b/audio/decoders/voc.h @@ -0,0 +1,107 @@ +/* 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$ + * + */ + +/** + * @file + * Sound decoder used in engines: + * - agos + * - drascula + * - kyra + * - made + * - saga + * - scumm + * - touche + */ + +#ifndef SOUND_VOC_H +#define SOUND_VOC_H + +#include "common/scummsys.h" +#include "common/types.h" + +namespace Common { class ReadStream; } +namespace Common { class SeekableReadStream; } + +namespace Audio { + +class AudioStream; +class SeekableAudioStream; + + +#include "common/pack-start.h" // START STRUCT PACKING + +struct VocFileHeader { + uint8 desc[20]; + uint16 datablock_offset; + uint16 version; + uint16 id; +} PACKED_STRUCT; + +struct VocBlockHeader { + uint8 blocktype; + uint8 size[3]; + uint8 sr; + uint8 pack; +} PACKED_STRUCT; + +#include "common/pack-end.h" // END STRUCT PACKING + +/** + * Take a sample rate parameter as it occurs in a VOC sound header, and + * return the corresponding sample frequency. + * + * This method has special cases for the standard rates of 11025 and 22050 kHz, + * which due to limitations of the format, cannot be encoded exactly in a VOC + * file. As a consequence, many game files have sound data sampled with those + * rates, but the VOC marks them incorrectly as 11111 or 22222 kHz. This code + * works around that and "unrounds" the sampling rates. + */ +extern int getSampleRateFromVOCRate(int vocSR); + +/** + * Try to load a VOC from the given stream. Returns a pointer to memory + * containing the PCM sample data (allocated with malloc). It is the callers + * responsibility to dellocate that data again later on! Currently this + * function only supports uncompressed raw PCM data. + */ +extern byte *loadVOCFromStream(Common::ReadStream &stream, int &size, int &rate); + +/** + * Try to load a VOC from the given seekable stream and create an AudioStream + * from that data. Currently this function only supports uncompressed raw PCM + * data. Optionally supports (infinite) looping of a portion of the data. + * + * This function uses loadVOCFromStream() internally. + */ +AudioStream *makeVOCStream(Common::SeekableReadStream *stream, byte flags = 0, uint loopStart = 0, uint loopEnd = 0, DisposeAfterUse::Flag disposeAfterUse = DisposeAfterUse::NO); + +/** + * This does not use any of the looping features of VOC files! + */ +SeekableAudioStream *makeVOCStream(Common::SeekableReadStream *stream, byte flags, DisposeAfterUse::Flag disposeAfterUse); + +} // End of namespace Audio + +#endif diff --git a/audio/decoders/vorbis.cpp b/audio/decoders/vorbis.cpp new file mode 100644 index 0000000000..dc37e852d3 --- /dev/null +++ b/audio/decoders/vorbis.cpp @@ -0,0 +1,262 @@ +/* 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$ + * + */ + +// Disable symbol overrides for FILE and fseek as those are used in the +// Vorbis headers. +#define FORBIDDEN_SYMBOL_EXCEPTION_FILE +#define FORBIDDEN_SYMBOL_EXCEPTION_fseek + +#include "audio/decoders/vorbis.h" + +#ifdef USE_VORBIS + +#include "common/debug.h" +#include "common/stream.h" +#include "common/util.h" + +#include "audio/audiostream.h" + +#ifdef USE_TREMOR +#if defined(__GP32__) // custom libtremor locations +#include <ivorbisfile.h> +#else +#include <tremor/ivorbisfile.h> +#endif +#else +#include <vorbis/vorbisfile.h> +#endif + + +namespace Audio { + +// These are wrapper functions to allow using a SeekableReadStream object to +// provide data to the OggVorbis_File object. + +static size_t read_stream_wrap(void *ptr, size_t size, size_t nmemb, void *datasource) { + Common::SeekableReadStream *stream = (Common::SeekableReadStream *)datasource; + + uint32 result = stream->read(ptr, size * nmemb); + + return result / size; +} + +static int seek_stream_wrap(void *datasource, ogg_int64_t offset, int whence) { + Common::SeekableReadStream *stream = (Common::SeekableReadStream *)datasource; + stream->seek((int32)offset, whence); + return stream->pos(); +} + +static int close_stream_wrap(void *datasource) { + // Do nothing -- we leave it up to the VorbisStream to free memory as appropriate. + return 0; +} + +static long tell_stream_wrap(void *datasource) { + Common::SeekableReadStream *stream = (Common::SeekableReadStream *)datasource; + return stream->pos(); +} + +static ov_callbacks g_stream_wrap = { + read_stream_wrap, seek_stream_wrap, close_stream_wrap, tell_stream_wrap +}; + + + +#pragma mark - +#pragma mark --- Ogg Vorbis stream --- +#pragma mark - + + +class VorbisStream : public SeekableAudioStream { +protected: + Common::SeekableReadStream *_inStream; + DisposeAfterUse::Flag _disposeAfterUse; + + bool _isStereo; + int _rate; + + Timestamp _length; + + OggVorbis_File _ovFile; + + int16 _buffer[4096]; + const int16 *_bufferEnd; + const int16 *_pos; + +public: + // startTime / duration are in milliseconds + VorbisStream(Common::SeekableReadStream *inStream, DisposeAfterUse::Flag dispose); + ~VorbisStream(); + + int readBuffer(int16 *buffer, const int numSamples); + + bool endOfData() const { return _pos >= _bufferEnd; } + bool isStereo() const { return _isStereo; } + int getRate() const { return _rate; } + + bool seek(const Timestamp &where); + Timestamp getLength() const { return _length; } +protected: + bool refill(); +}; + +VorbisStream::VorbisStream(Common::SeekableReadStream *inStream, DisposeAfterUse::Flag dispose) : + _inStream(inStream), + _disposeAfterUse(dispose), + _length(0, 1000), + _bufferEnd(_buffer + ARRAYSIZE(_buffer)) { + + int res = ov_open_callbacks(inStream, &_ovFile, NULL, 0, g_stream_wrap); + if (res < 0) { + warning("Could not create Vorbis stream (%d)", res); + _pos = _bufferEnd; + return; + } + + // Read in initial data + if (!refill()) + return; + + // Setup some header information + _isStereo = ov_info(&_ovFile, -1)->channels >= 2; + _rate = ov_info(&_ovFile, -1)->rate; + +#ifdef USE_TREMOR + _length = Timestamp(ov_time_total(&_ovFile, -1), getRate()); +#else + _length = Timestamp(uint32(ov_time_total(&_ovFile, -1) * 1000.0), getRate()); +#endif +} + +VorbisStream::~VorbisStream() { + ov_clear(&_ovFile); + if (_disposeAfterUse == DisposeAfterUse::YES) + delete _inStream; +} + +int VorbisStream::readBuffer(int16 *buffer, const int numSamples) { + int samples = 0; + while (samples < numSamples && _pos < _bufferEnd) { + const int len = MIN(numSamples - samples, (int)(_bufferEnd - _pos)); + memcpy(buffer, _pos, len * 2); + buffer += len; + _pos += len; + samples += len; + if (_pos >= _bufferEnd) { + if (!refill()) + break; + } + } + return samples; +} + +bool VorbisStream::seek(const Timestamp &where) { + // Vorbisfile uses the sample pair number, thus we always use "false" for the isStereo parameter + // of the convertTimeToStreamPos helper. + int res = ov_pcm_seek(&_ovFile, convertTimeToStreamPos(where, getRate(), false).totalNumberOfFrames()); + if (res) { + warning("Error seeking in Vorbis stream (%d)", res); + _pos = _bufferEnd; + return false; + } + + return refill(); +} + +bool VorbisStream::refill() { + // Read the samples + uint len_left = sizeof(_buffer); + char *read_pos = (char *)_buffer; + + while (len_left > 0) { + long result; + +#ifdef USE_TREMOR + // Tremor ov_read() always returns data as signed 16 bit interleaved PCM + // in host byte order. As such, it does not take arguments to request + // specific signedness, byte order or bit depth as in Vorbisfile. + result = ov_read(&_ovFile, read_pos, len_left, + NULL); +#else +#ifdef SCUMM_BIG_ENDIAN + result = ov_read(&_ovFile, read_pos, len_left, + 1, + 2, // 16 bit + 1, // signed + NULL); +#else + result = ov_read(&_ovFile, read_pos, len_left, + 0, + 2, // 16 bit + 1, // signed + NULL); +#endif +#endif + if (result == OV_HOLE) { + // Possibly recoverable, just warn about it + warning("Corrupted data in Vorbis file"); + } else if (result == 0) { + //warning("End of file while reading from Vorbis file"); + //_pos = _bufferEnd; + //return false; + break; + } else if (result < 0) { + warning("Error reading from Vorbis stream (%d)", int(result)); + _pos = _bufferEnd; + // Don't delete it yet, that causes problems in + // the CD player emulation code. + return false; + } else { + len_left -= result; + read_pos += result; + } + } + + _pos = _buffer; + _bufferEnd = (int16 *)read_pos; + + return true; +} + + +#pragma mark - +#pragma mark --- Ogg Vorbis factory functions --- +#pragma mark - + +SeekableAudioStream *makeVorbisStream( + Common::SeekableReadStream *stream, + DisposeAfterUse::Flag disposeAfterUse) { + SeekableAudioStream *s = new VorbisStream(stream, disposeAfterUse); + if (s && s->endOfData()) { + delete s; + return 0; + } else { + return s; + } +} + +} // End of namespace Audio + +#endif // #ifdef USE_VORBIS diff --git a/audio/decoders/vorbis.h b/audio/decoders/vorbis.h new file mode 100644 index 0000000000..7cc395cccb --- /dev/null +++ b/audio/decoders/vorbis.h @@ -0,0 +1,75 @@ +/* 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$ + * + */ + +/** + * @file + * Sound decoder used in engines: + * - agos + * - draci + * - kyra + * - m4 + * - queen + * - saga + * - sci + * - scumm + * - sword1 + * - sword2 + * - touche + * - tucker + */ + +#ifndef SOUND_VORBIS_H +#define SOUND_VORBIS_H + +#include "common/scummsys.h" +#include "common/types.h" + +#ifdef USE_VORBIS + +namespace Common { + class SeekableReadStream; +} + +namespace Audio { + +class AudioStream; +class SeekableAudioStream; + +/** + * Create a new SeekableAudioStream from the Ogg Vorbis data in the given stream. + * Allows for seeking (which is why we require a SeekableReadStream). + * + * @param stream the SeekableReadStream from which to read the Ogg Vorbis data + * @param disposeAfterUse whether to delete the stream after use + * @return a new SeekableAudioStream, or NULL, if an error occurred + */ +SeekableAudioStream *makeVorbisStream( + Common::SeekableReadStream *stream, + DisposeAfterUse::Flag disposeAfterUse); + +} // End of namespace Audio + +#endif // #ifdef USE_VORBIS +#endif // #ifndef SOUND_VORBIS_H diff --git a/audio/decoders/wave.cpp b/audio/decoders/wave.cpp new file mode 100644 index 0000000000..1f0ddd8ceb --- /dev/null +++ b/audio/decoders/wave.cpp @@ -0,0 +1,194 @@ +/* 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 "common/debug.h" +#include "common/util.h" +#include "common/stream.h" + +#include "audio/audiostream.h" +#include "audio/mixer.h" +#include "audio/decoders/wave.h" +#include "audio/decoders/adpcm.h" +#include "audio/decoders/raw.h" + +namespace Audio { + +bool loadWAVFromStream(Common::SeekableReadStream &stream, int &size, int &rate, byte &flags, uint16 *wavType, int *blockAlign_) { + const int32 initialPos = stream.pos(); + byte buf[4+1]; + + buf[4] = 0; + + stream.read(buf, 4); + if (memcmp(buf, "RIFF", 4) != 0) { + warning("getWavInfo: No 'RIFF' header"); + return false; + } + + int32 wavLength = stream.readUint32LE(); + + stream.read(buf, 4); + if (memcmp(buf, "WAVE", 4) != 0) { + warning("getWavInfo: No 'WAVE' header"); + return false; + } + + stream.read(buf, 4); + if (memcmp(buf, "fmt ", 4) != 0) { + warning("getWavInfo: No 'fmt' header"); + return false; + } + + uint32 fmtLength = stream.readUint32LE(); + if (fmtLength < 16) { + // A valid fmt chunk always contains at least 16 bytes + warning("getWavInfo: 'fmt' header is too short"); + return false; + } + + // Next comes the "type" field of the fmt header. Some typical + // values for it: + // 1 -> uncompressed PCM + // 17 -> IMA ADPCM compressed WAVE + // See <http://www.saettler.com/RIFFNEW/RIFFNEW.htm> for a more complete + // list of common WAVE compression formats... + uint16 type = stream.readUint16LE(); // == 1 for PCM data + uint16 numChannels = stream.readUint16LE(); // 1 for mono, 2 for stereo + uint32 samplesPerSec = stream.readUint32LE(); // in Hz + uint32 avgBytesPerSec = stream.readUint32LE(); // == SampleRate * NumChannels * BitsPerSample/8 + + uint16 blockAlign = stream.readUint16LE(); // == NumChannels * BitsPerSample/8 + uint16 bitsPerSample = stream.readUint16LE(); // 8, 16 ... + // 8 bit data is unsigned, 16 bit data signed + + + if (wavType != 0) + *wavType = type; + + if (blockAlign_ != 0) + *blockAlign_ = blockAlign; +#if 0 + debug("WAVE information:"); + debug(" total size: %d", wavLength); + debug(" fmt size: %d", fmtLength); + debug(" type: %d", type); + debug(" numChannels: %d", numChannels); + debug(" samplesPerSec: %d", samplesPerSec); + debug(" avgBytesPerSec: %d", avgBytesPerSec); + debug(" blockAlign: %d", blockAlign); + debug(" bitsPerSample: %d", bitsPerSample); +#endif + + if (type != 1 && type != 2 && type != 17) { + warning("getWavInfo: only PCM, MS ADPCM or IMA ADPCM data is supported (type %d)", type); + return false; + } + + if (blockAlign != numChannels * bitsPerSample / 8 && type != 2) { + debug(0, "getWavInfo: blockAlign is invalid"); + } + + if (avgBytesPerSec != samplesPerSec * blockAlign && type != 2) { + debug(0, "getWavInfo: avgBytesPerSec is invalid"); + } + + // Prepare the return values. + rate = samplesPerSec; + + flags = 0; + if (bitsPerSample == 8) // 8 bit data is unsigned + flags |= Audio::FLAG_UNSIGNED; + else if (bitsPerSample == 16) // 16 bit data is signed little endian + flags |= (Audio::FLAG_16BITS | Audio::FLAG_LITTLE_ENDIAN); + else if (bitsPerSample == 4 && (type == 2 || type == 17)) + flags |= Audio::FLAG_16BITS; + else { + warning("getWavInfo: unsupported bitsPerSample %d", bitsPerSample); + return false; + } + + if (numChannels == 2) + flags |= Audio::FLAG_STEREO; + else if (numChannels != 1) { + warning("getWavInfo: unsupported number of channels %d", numChannels); + return false; + } + + // It's almost certainly a WAV file, but we still need to find its + // 'data' chunk. + + // Skip over the rest of the fmt chunk. + int offset = fmtLength - 16; + + do { + stream.seek(offset, SEEK_CUR); + if (stream.pos() >= initialPos + wavLength + 8) { + warning("getWavInfo: Can't find 'data' chunk"); + return false; + } + stream.read(buf, 4); + offset = stream.readUint32LE(); + +#if 0 + debug(" found a '%s' tag of size %d", buf, offset); +#endif + } while (memcmp(buf, "data", 4) != 0); + + // Stream now points at 'offset' bytes of sample data... + size = offset; + + return true; +} + +RewindableAudioStream *makeWAVStream(Common::SeekableReadStream *stream, DisposeAfterUse::Flag disposeAfterUse) { + int size, rate; + byte flags; + uint16 type; + int blockAlign; + + if (!loadWAVFromStream(*stream, size, rate, flags, &type, &blockAlign)) { + if (disposeAfterUse == DisposeAfterUse::YES) + delete stream; + return 0; + } + + if (type == 17) // MS IMA ADPCM + return makeADPCMStream(stream, disposeAfterUse, size, Audio::kADPCMMSIma, rate, (flags & Audio::FLAG_STEREO) ? 2 : 1, blockAlign); + else if (type == 2) // MS ADPCM + return makeADPCMStream(stream, disposeAfterUse, size, Audio::kADPCMMS, rate, (flags & Audio::FLAG_STEREO) ? 2 : 1, blockAlign); + + // Raw PCM. Just read everything at once. + // TODO: More elegant would be to wrap the stream. + byte *data = (byte *)malloc(size); + assert(data); + stream->read(data, size); + + if (disposeAfterUse == DisposeAfterUse::YES) + delete stream; + + return makeRawStream(data, size, rate, flags); +} + +} // End of namespace Audio diff --git a/audio/decoders/wave.h b/audio/decoders/wave.h new file mode 100644 index 0000000000..2bdbe8f0b6 --- /dev/null +++ b/audio/decoders/wave.h @@ -0,0 +1,84 @@ +/* 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$ + * + */ + +/** + * @file + * Sound decoder used in engines: + * - agos + * - gob + * - mohawk + * - saga + * - sci + * - scumm + * - sword1 + * - sword2 + * - tucker + */ + +#ifndef SOUND_WAVE_H +#define SOUND_WAVE_H + +#include "common/scummsys.h" +#include "common/types.h" + +namespace Common { class SeekableReadStream; } + +namespace Audio { + +class RewindableAudioStream; + +/** + * Try to load a WAVE from the given seekable stream. Returns true if + * successful. In that case, the stream's seek position will be set to the + * start of the audio data, and size, rate and flags contain information + * necessary for playback. Currently this function supports uncompressed + * raw PCM data, MS IMA ADPCM and MS ADPCM (uses makeADPCMStream internally). + */ +extern bool loadWAVFromStream( + Common::SeekableReadStream &stream, + int &size, + int &rate, + byte &flags, + uint16 *wavType = 0, + int *blockAlign = 0); + +/** + * Try to load a WAVE from the given seekable stream and create an AudioStream + * from that data. Currently this function supports uncompressed + * raw PCM data, MS IMA ADPCM and MS ADPCM (uses makeADPCMStream internally). + * + * This function uses loadWAVFromStream() internally. + * + * @param stream the SeekableReadStream from which to read the WAVE data + * @param disposeAfterUse whether to delete the stream after use + * @return a new RewindableAudioStream, or NULL, if an error occurred + */ +RewindableAudioStream *makeWAVStream( + Common::SeekableReadStream *stream, + DisposeAfterUse::Flag disposeAfterUse); + +} // End of namespace Audio + +#endif |