aboutsummaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorJohannes Schickel2011-10-12 21:42:24 +0200
committerJohannes Schickel2011-11-06 13:30:34 +0100
commit9fa9f68789ef51e078cb8631e06bead13cae13f2 (patch)
tree12d08e3f6322daf536ba40f43e50683a216991dd
parent6c23f78cb93e9056e5dc93a6e2e45061e2789755 (diff)
downloadscummvm-rg350-9fa9f68789ef51e078cb8631e06bead13cae13f2.tar.gz
scummvm-rg350-9fa9f68789ef51e078cb8631e06bead13cae13f2.tar.bz2
scummvm-rg350-9fa9f68789ef51e078cb8631e06bead13cae13f2.zip
AUDIO: Implement a basic VocStream class.
Now all VOCs are streamed rather than preloaded. This deprecates the STREAM_AUDIO_FROM_DISK define, which was previously used to stream VOCs from disk. This might very well break some engines which relied on the stream not being changed after makeVOCStream! I adapted all engines which had a check for STREAM_AUDIO_FROM_DISK in their code. It would be wise to check all other engines using VOC to see if this might cause any problems for them.
-rw-r--r--audio/decoders/voc.cpp624
-rw-r--r--engines/kyra/sound.cpp9
2 files changed, 474 insertions, 159 deletions
diff --git a/audio/decoders/voc.cpp b/audio/decoders/voc.cpp
index f06e7f95f2..69a9733b1a 100644
--- a/audio/decoders/voc.cpp
+++ b/audio/decoders/voc.cpp
@@ -25,14 +25,478 @@
#include "common/util.h"
#include "common/stream.h"
#include "common/textconsole.h"
+#include "common/list.h"
#include "audio/audiostream.h"
#include "audio/decoders/raw.h"
#include "audio/decoders/voc.h"
-
namespace Audio {
+namespace {
+
+bool checkVOCHeader(Common::ReadStream &stream) {
+ VocFileHeader fileHeader;
+
+ if (stream.read(&fileHeader, 8) != 8)
+ return false;
+
+ if (!memcmp(&fileHeader, "VTLK", 4)) {
+ if (stream.read(&fileHeader, sizeof(VocFileHeader)) != sizeof(VocFileHeader))
+ return false;
+ } else if (!memcmp(&fileHeader, "Creative", 8)) {
+ if (stream.read(((byte *)&fileHeader) + 8, sizeof(VocFileHeader) - 8) != sizeof(VocFileHeader) - 8)
+ return false;
+ } else {
+ return false;
+ }
+
+ if (memcmp(fileHeader.desc, "Creative Voice File", 19) != 0)
+ return false;
+ //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);
+
+ if (offset != sizeof(VocFileHeader))
+ return false;
+
+ // 0x100 is an invalid VOC version used by German version of DOTT (Disk) and
+ // French version of Simon the Sorcerer 2 (CD)
+ if (version != 0x010A && version != 0x0114 && version != 0x0100)
+ return false;
+
+ if (code != ~version + 0x1234)
+ return false;
+
+ return true;
+}
+
+class VocStream : public SeekableAudioStream {
+public:
+ VocStream(Common::SeekableReadStream *stream, bool isUnsigned, DisposeAfterUse::Flag disposeAfterUse);
+ ~VocStream();
+
+ virtual int readBuffer(int16 *buffer, const int numSamples);
+
+ virtual bool isStereo() const { return false; }
+
+ virtual int getRate() const { return _rate; }
+
+ virtual bool endOfData() const { return (_curBlock == _blocks.end()) && (_blockLeft == 0); }
+
+ virtual bool seek(const Timestamp &where);
+
+ virtual Timestamp getLength() const { return _length; }
+private:
+ void preProcess();
+
+ Common::SeekableReadStream *const _stream;
+ const DisposeAfterUse::Flag _disposeAfterUse;
+
+ const bool _isUnsigned;
+
+ int _rate;
+ Timestamp _length;
+
+ struct Block {
+ uint8 code;
+ uint32 length;
+
+ union {
+ struct {
+ uint32 offset;
+ int rate;
+ int samples;
+ } sampleBlock;
+
+ struct {
+ int count;
+ } loopBlock;
+ };
+ };
+
+ typedef Common::List<Block> BlockList;
+ BlockList _blocks;
+
+ BlockList::const_iterator _curBlock;
+ uint32 _blockLeft;
+
+ /**
+ * Advance one block in the stream in case
+ * the current one is empty.
+ */
+ void updateBlockIfNeeded();
+
+ // Do some internal buffering for systems with really slow slow disk i/o
+ enum {
+ /**
+ * How many samples we can buffer at once.
+ *
+ * TODO: Check whether this size suffices
+ * for systems with slow disk I/O.
+ */
+ kSampleBufferLength = 2048
+ };
+ byte _buffer[kSampleBufferLength];
+
+ /**
+ * Fill the temporary sample buffer used in readBuffer.
+ *
+ * @param maxSamples Maximum samples to read.
+ * @return actual count of samples read.
+ */
+ int fillBuffer(int maxSamples);
+};
+
+VocStream::VocStream(Common::SeekableReadStream *stream, bool isUnsigned, DisposeAfterUse::Flag disposeAfterUse)
+ : _stream(stream), _disposeAfterUse(disposeAfterUse), _isUnsigned(isUnsigned), _rate(0),
+ _length(), _blocks(), _curBlock(_blocks.end()), _blockLeft(0), _buffer() {
+ preProcess();
+}
+
+VocStream::~VocStream() {
+ if (_disposeAfterUse == DisposeAfterUse::YES)
+ delete _stream;
+}
+
+int VocStream::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++ = (*src++ << 8) ^ (_isUnsigned ? 0x8000 : 0);
+ }
+
+ return numSamples - samplesLeft;
+}
+
+void VocStream::updateBlockIfNeeded() {
+ // Have we now finished this block? If so, read the next block
+ if (_blockLeft == 0 && _curBlock != _blocks.end()) {
+ // Find the next sample block
+ while (true) {
+ // Next block
+ ++_curBlock;
+
+ // Check whether we reached the end of the stream
+ // yet.
+ if (_curBlock == _blocks.end())
+ return;
+
+ // Skip all none sample blocks for now
+ if (_curBlock->code != 1 && _curBlock->code != 9)
+ continue;
+
+ _stream->seek(_curBlock->sampleBlock.offset, SEEK_SET);
+
+ // In case of an error we will stop
+ // stream playback.
+ if (_stream->err()) {
+ _blockLeft = 0;
+ _curBlock = _blocks.end();
+ } else {
+ _blockLeft = _curBlock->sampleBlock.samples;
+ }
+
+ return;
+ }
+ }
+}
+
+int VocStream::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);
+ dst += bytesRead;
+
+ // Calculate how many samples we actually read.
+ const int samplesRead = bytesRead;
+
+ // 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();
+ break;
+ }
+
+ // Advance to the next block in case the current
+ // one is already finished.
+ updateBlockIfNeeded();
+ }
+
+ return bufferedSamples;
+}
+
+bool VocStream::seek(const Timestamp &where) {
+ // Invalidate stream
+ _blockLeft = 0;
+ _curBlock = _blocks.end();
+
+ if (where > _length)
+ return false;
+
+ // Search for the block containing the requested sample
+ const uint32 seekSample = convertTimeToStreamPos(where, getRate(), isStereo()).totalNumberOfFrames();
+ uint32 curSample = 0;
+
+ for (_curBlock = _blocks.begin(); _curBlock != _blocks.end(); ++_curBlock) {
+ // Skip all none sample blocks for now
+ if (_curBlock->code != 1 && _curBlock->code != 9)
+ continue;
+
+ uint32 nextBlockSample = curSample + _curBlock->sampleBlock.samples;
+
+ if (nextBlockSample > seekSample)
+ break;
+
+ curSample = nextBlockSample;
+ }
+
+ if (_curBlock == _blocks.end()) {
+ return ((seekSample - curSample) == 0);
+ } else {
+ const uint32 offset = seekSample - curSample;
+
+ _stream->seek(_curBlock->sampleBlock.offset + offset, SEEK_SET);
+
+ // In case of an error we will stop
+ // stream playback.
+ if (_stream->err()) {
+ _blockLeft = 0;
+ _curBlock = _blocks.end();
+ } else {
+ _blockLeft = _curBlock->sampleBlock.samples - offset;
+ }
+
+ return true;
+ }
+}
+
+void VocStream::preProcess() {
+ Block block;
+
+ // Scan through the file and collect all blocks
+ while (true) {
+ block.code = _stream->readByte();
+ block.length = 0;
+
+ // If we hit EOS here we found the end of the VOC file.
+ // According to http://wiki.multimedia.cx/index.php?title=Creative_Voice
+ // there is no need for an "Terminator" block to be present.
+ // In case we hit a "Terminator" block we also break here.
+ if (_stream->eos() || block.code == 0)
+ break;
+
+ block.length = _stream->readByte();
+ block.length |= _stream->readByte() << 8;
+ block.length |= _stream->readByte() << 16;
+
+ // Premature end of stream => error!
+ if (_stream->eos() || _stream->err())
+ return;
+
+ uint32 skip = 0;
+
+ switch (block.code) {
+ // Sound data
+ case 1:
+ // Sound data (New format)
+ case 9:
+ if (block.code == 1) {
+ // Read header data
+ int freqDiv = _stream->readByte();
+ // Prevent division through 0
+ if (freqDiv == 256)
+ return;
+ block.sampleBlock.rate = getSampleRateFromVOCRate(freqDiv);
+
+ int codec = _stream->readByte();
+ // We only support 8bit PCM
+ if (codec != 0)
+ return;
+
+ block.sampleBlock.samples = skip = block.length - 2;
+ block.sampleBlock.offset = _stream->pos();
+
+ // Check the last block if there is any
+ if (_blocks.size() > 0) {
+ BlockList::iterator lastBlock = _blocks.end();
+ --lastBlock;
+ // When we have found a block 8 as predecessor
+ // we need to use its settings
+ if (lastBlock->code == 8) {
+ block.sampleBlock.rate = lastBlock->sampleBlock.rate;
+ // Remove the block since we don't need it anymore
+ _blocks.erase(lastBlock);
+ }
+ }
+
+ // Check whether we found a new highest rate
+ if (_rate < block.sampleBlock.rate)
+ _rate = block.sampleBlock.rate;
+ } else {
+ block.sampleBlock.rate = _stream->readUint32LE();
+ int bitsPerSample = _stream->readByte();
+ // We only support 8bit PCM
+ if (bitsPerSample != 8)
+ return;
+ int channels = _stream->readByte();
+ // We only support mono
+ if (channels != 1) {
+ warning("Unhandled channel count %d in VOC file", channels);
+ return;
+ }
+ int codec = _stream->readByte();
+ // We only support 8bit PCM
+ if (codec != 0) {
+ warning("Unhandled codec %d in VOC file", codec);
+ return;
+ }
+ /*uint32 reserved = */_stream->readUint32LE();
+ block.sampleBlock.offset = _stream->pos();
+ block.sampleBlock.samples = skip = block.length - 12;
+ }
+ break;
+
+ // Silence
+ case 3: {
+ if (block.length != 3)
+ return;
+
+ block.sampleBlock.offset = 0;
+
+ block.sampleBlock.samples = _stream->readUint16LE() + 1;
+ int freqDiv = _stream->readByte();
+ // Prevent division through 0
+ if (freqDiv == 256)
+ return;
+ block.sampleBlock.rate = getSampleRateFromVOCRate(freqDiv);
+ } break;
+
+ // Repeat start
+ case 6:
+ if (block.length != 2)
+ return;
+
+ block.loopBlock.count = _stream->readUint16LE() + 1;
+ break;
+
+ // Repeat end
+ case 7:
+ break;
+
+ // Extra info
+ case 8: {
+ if (block.length != 4)
+ return;
+
+ int freqDiv = _stream->readUint16LE();
+ // Prevent division through 0
+ if (freqDiv == 65536)
+ return;
+
+ int codec = _stream->readByte();
+ // We only support RAW 8bit PCM.
+ if (codec != 0) {
+ warning("Unhandled codec %d in VOC file", codec);
+ return;
+ }
+
+ int channels = _stream->readByte() + 1;
+ // We only support mono sound right now
+ if (channels != 1) {
+ warning("Unhandled channel count %d in VOC file", channels);
+ return;
+ }
+
+ block.sampleBlock.offset = 0;
+ block.sampleBlock.samples = 0;
+ block.sampleBlock.rate = 256000000L / (65536L - freqDiv);
+ } break;
+
+ default:
+ warning("Unhandled code %d in VOC file (len %d)", block.code, block.length);
+ return;
+ }
+
+ // Premature end of stream => error!
+ if (_stream->eos() || _stream->err())
+ return;
+
+ // Skip the rest of the block
+ if (skip)
+ _stream->skip(skip);
+
+ _blocks.push_back(block);
+ }
+
+ // Since we determined the sample rate we need for playback now, we will
+ // initialize the play length.
+ _length = Timestamp(0, _rate);
+
+ // Calculate the total play time and do some more sanity checks
+ for (BlockList::const_iterator i = _blocks.begin(), end = _blocks.end(); i != end; ++i) {
+ // Check whether we found a block 8 which survived, this is not
+ // allowed to happen!
+ if (i->code == 8) {
+ warning("VOC file contains unused block 8");
+ return;
+ }
+
+ // For now only use blocks with actual samples
+ if (i->code != 1 && i->code != 9)
+ continue;
+
+ // Check the sample rate
+ if (i->sampleBlock.rate != _rate) {
+ warning("VOC file contains chunks with different sample rates (%d != %d)", _rate, i->sampleBlock.rate);
+ return;
+ }
+
+ _length = _length.addFrames(i->sampleBlock.samples);
+ }
+
+ // Set the current block to the first block in the stream
+ rewind();
+}
+
+} // End of anonymous namespace
+
int getSampleRateFromVOCRate(int vocSR) {
if (vocSR == 0xa5 || vocSR == 0xa6) {
return 11025;
@@ -169,161 +633,21 @@ byte *loadVOCFromStream(Common::ReadStream &stream, int &size, int &rate) {
return ret_sound;
}
-#ifdef STREAM_AUDIO_FROM_DISK
-
-int parseVOCFormat(Common::SeekableReadStream& stream, RawStreamBlock* block, int &rate) {
- 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;
-
- 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;
- } 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);
- 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;
-}
-
-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;
-
- int numBlocks = parseVOCFormat(*stream, block, rate);
-
- 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
-
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 (!checkVOCHeader(*stream)) {
if (disposeAfterUse == DisposeAfterUse::YES)
delete stream;
return 0;
}
- return makeRawStream(data, size, rate, flags);
-#endif
+ SeekableAudioStream *audioStream = new VocStream(stream, (flags & Audio::FLAG_UNSIGNED) != 0, disposeAfterUse);
+
+ if (audioStream && audioStream->endOfData()) {
+ delete audioStream;
+ return 0;
+ } else {
+ return audioStream;
+ }
}
} // End of namespace Audio
diff --git a/engines/kyra/sound.cpp b/engines/kyra/sound.cpp
index 5195271808..a1af1ad6f8 100644
--- a/engines/kyra/sound.cpp
+++ b/engines/kyra/sound.cpp
@@ -334,16 +334,7 @@ namespace {
// A simple wrapper to create VOC streams the way like creating MP3, OGG/Vorbis and FLAC streams.
// Possible TODO: Think of making this complete and moving it to sound/voc.cpp ?
Audio::SeekableAudioStream *makeVOCStream(Common::SeekableReadStream *stream, DisposeAfterUse::Flag disposeAfterUse) {
-
-#ifdef STREAM_AUDIO_FROM_DISK
Audio::SeekableAudioStream *as = Audio::makeVOCStream(stream, Audio::FLAG_UNSIGNED, disposeAfterUse);
-#else
- Audio::SeekableAudioStream *as = Audio::makeVOCStream(stream, Audio::FLAG_UNSIGNED, DisposeAfterUse::NO);
-
- if (disposeAfterUse)
- delete stream;
-#endif
-
return as;
}