Ticket #9066: streaming_voc_patch.diff

File streaming_voc_patch.diff, 15.6 KB (added by agent-q, 15 years ago)

Streaming audio patch v1.0. Only VOC supported for now.

  • sound/audiostream.cpp

     
    173173}
    174174
    175175
     176
    176177#pragma mark -
     178#pragma mark --- LinearDiskStream ---
     179#pragma mark -
     180
     181
     182
     183/**
     184 *  LinearDiskStream.  This can stream linear (PCM) audio from disk.  The
     185 *  function takes an pointer to an array of LinearDiskStreamAudioBlock which defines the
     186 *  start position and length of each block of uncompressed audio in the stream.
     187 */
     188template<bool stereo, bool is16Bit, bool isUnsigned, bool isLE>
     189class LinearDiskStream : public AudioStream {
     190
     191// Allow backends to override buffer size
     192#ifdef CUSTOM_AUDIO_BUFFER_SIZE
     193        static const int32 BUFFER_SIZE = CUSTOM_AUDIO_BUFFER_SIZE;
     194#else
     195        static const int32 BUFFER_SIZE = 16384;
     196#endif
     197
     198protected:
     199        byte* _buffer;                  ///< Streaming buffer
     200        const byte *_ptr;               ///< Pointer to current position in stream buffer
     201        const int _rate;                ///< Sample rate of stream
     202
     203        int32 _playtime;                ///< Calculated total play time
     204        Common::SeekableReadStream* _stream;    ///< Stream to read data from
     205        int32 _filePos;                 ///< Current position in stream
     206        int32 _diskLeft;                ///< Samples left in stream in current block not yet read to buffer
     207        int32 _bufferLeft;              ///< Samples left in buffer in current block
     208        bool _ownsStream;               ///< If true, delete stream object when LinearDiskStream is destructed
     209
     210        LinearDiskStreamAudioBlock* _audioBlock;        ///< Audio block list
     211        int _audioBlockCount;           ///< Number of blocks in _audioBlock
     212        int _currentBlock;              ///< Current audio block number
     213
     214        int _beginLoop;                 ///< Loop parameter, currently not implemented
     215        int _endLoop;                   ///< Loop parameter, currently not implemented
     216
     217
     218public:
     219        LinearDiskStream(int rate, uint beginLoop, uint endLoop, bool takeOwnership, Common::SeekableReadStream* stream, LinearDiskStreamAudioBlock* block, uint numBlocks)
     220                : _rate(rate), _stream(stream), _beginLoop(beginLoop), _endLoop(endLoop), _ownsStream(takeOwnership),
     221                  _audioBlockCount(numBlocks) {
     222
     223                // Allocate streaming buffer
     224                if (is16Bit) {
     225                        _buffer = (byte *) malloc(BUFFER_SIZE * sizeof(int16));
     226                } else {
     227                        _buffer = (byte *) malloc(BUFFER_SIZE * sizeof(byte));
     228                }
     229                _ptr = _buffer;
     230                _bufferLeft = 0;
     231
     232                // Copy audio block data to our buffer
     233                int blockDataSize = numBlocks * sizeof(LinearDiskStreamAudioBlock);
     234                _audioBlock = (LinearDiskStreamAudioBlock *) malloc(blockDataSize);
     235                memcpy(_audioBlock, block, blockDataSize);
     236
     237                // Set current buffer state, playing first block
     238                _currentBlock = 0;
     239                _filePos = _audioBlock[_currentBlock].pos;
     240                _diskLeft = _audioBlock[_currentBlock].len;
     241
     242                // Add up length of all blocks in order to caluclate total play time
     243                int len = 0;
     244                for (int r = 0; r < _audioBlockCount; r++) {
     245                        len += _audioBlock[r].len;
     246                }
     247                _playtime = calculatePlayTime(rate, len / (is16Bit ? 2 : 1) / (stereo ? 2 : 1));
     248        }
     249
     250
     251        virtual ~LinearDiskStream() {
     252                if (_ownsStream) {
     253                        delete _stream;
     254                }
     255
     256                free(_buffer);
     257        }
     258        int readBuffer(int16 *buffer, const int numSamples);
     259
     260        bool isStereo() const                   { return stereo; }
     261        bool endOfData() const                  { return (_currentBlock == _audioBlockCount - 1) && (_diskLeft == 0) && (_bufferLeft == 0); }
     262
     263        int getRate() const                     { return _rate; }
     264        int32 getTotalPlayTime() const  { return _playtime; }
     265};
     266
     267template<bool stereo, bool is16Bit, bool isUnsigned, bool isLE>
     268int LinearDiskStream<stereo, is16Bit, isUnsigned, isLE>::readBuffer(int16 *buffer, const int numSamples) {
     269        int oldPos = _stream->pos();
     270        bool restoreFilePosition = false;
     271
     272        int samples = numSamples;
     273
     274        while (samples > 0 && ((_diskLeft > 0 || _bufferLeft > 0) || (_currentBlock != _audioBlockCount - 1))  ) {
     275
     276                // Output samples in the buffer to the output           
     277                int len = MIN(samples, _bufferLeft);
     278                samples -= len;
     279                _bufferLeft -= len;
     280
     281                while (len > 0) {
     282                        *buffer++ = READ_ENDIAN_SAMPLE(is16Bit, isUnsigned, _ptr, isLE);
     283                        _ptr += (is16Bit ? 2 : 1);
     284                        len--;
     285                }
     286
     287                // Have we now finished this block?  If so, read the next block
     288                if ((_bufferLeft == 0) && (_diskLeft == 0) && (_currentBlock != _audioBlockCount - 1)) {
     289                        // Next block
     290                        _currentBlock++;
     291
     292                        _filePos = _audioBlock[_currentBlock].pos;
     293                        _diskLeft = _audioBlock[_currentBlock].len;
     294                }
     295                       
     296
     297                // Now read more data from disk if there is more to be read
     298                if ((_bufferLeft == 0) && (_diskLeft > 0)) {
     299                        int32 readAmount = MIN(_diskLeft, BUFFER_SIZE);
     300
     301                        _stream->seek(_filePos, SEEK_SET);
     302                        _stream->read(_buffer, readAmount * (is16Bit? 2: 1));
     303
     304                        // Amount of data in buffer is now the amount read in, and
     305                        // the amount left to read on disk is decreased by the same amount
     306                        _bufferLeft = readAmount;
     307                        _diskLeft -= readAmount;
     308                        _ptr = (byte*) _buffer;
     309                        _filePos += readAmount * (is16Bit? 2: 1);
     310
     311                        // Set this flag now we've used the file, it restores it's
     312                        // original position.
     313                        restoreFilePosition = true;
     314                }
     315
     316
     317        }
     318
     319        // In case calling code relies on the position of this stream staying
     320        // constant, I restore the location if I've changed it.  This is probably
     321        // not necessary.       
     322        if (restoreFilePosition) {
     323                _stream->seek(oldPos, SEEK_SET);
     324        }
     325
     326        return numSamples-samples;
     327}
     328
     329
     330
     331#pragma mark -
    177332#pragma mark --- Input stream factory ---
    178333#pragma mark -
    179334
     
    202357        const bool isLE       = (flags & Audio::Mixer::FLAG_LITTLE_ENDIAN) != 0;
    203358        const bool autoFree   = (flags & Audio::Mixer::FLAG_AUTOFREE) != 0;
    204359
     360
    205361        uint loopOffset = 0, loopLen = 0;
    206362        if (flags & Audio::Mixer::FLAG_LOOP) {
    207363                if (loopEnd == 0)
     
    236392}
    237393
    238394
     395
     396
     397
     398#define MAKE_LINEAR_DISK(STEREO, UNSIGNED) \
     399                if (is16Bit) { \
     400                        if (isLE) \
     401                                return new LinearDiskStream<STEREO, true, UNSIGNED, true>(rate, loopStart, loopEnd, takeOwnership, &stream, block, numBlocks); \
     402                        else  \
     403                                return new LinearDiskStream<STEREO, true, UNSIGNED, false>(rate, loopStart, loopEnd, takeOwnership, &stream, block, numBlocks); \
     404                } else \
     405                        return new LinearDiskStream<STEREO, false, UNSIGNED, false>(rate, loopStart, loopEnd, takeOwnership, &stream, block, numBlocks)
     406
     407
     408AudioStream *makeLinearDiskStream(Common::SeekableReadStream& stream, LinearDiskStreamAudioBlock* block, int numBlocks, int rate, byte flags, bool takeOwnership, uint loopStart, uint loopEnd) {
     409        const bool isStereo   = (flags & Audio::Mixer::FLAG_STEREO) != 0;
     410        const bool is16Bit    = (flags & Audio::Mixer::FLAG_16BITS) != 0;
     411        const bool isUnsigned = (flags & Audio::Mixer::FLAG_UNSIGNED) != 0;
     412        const bool isLE       = (flags & Audio::Mixer::FLAG_LITTLE_ENDIAN) != 0;
     413
     414
     415        if (isStereo) {
     416                if (isUnsigned) {
     417                        MAKE_LINEAR_DISK(true, true);
     418                } else {
     419                        MAKE_LINEAR_DISK(true, false);
     420                }
     421        } else {
     422                if (isUnsigned) {
     423                        MAKE_LINEAR_DISK(false, true);
     424                } else {
     425                        MAKE_LINEAR_DISK(false, false);
     426                }
     427        }
     428}
     429
     430
     431
     432
    239433#pragma mark -
    240434#pragma mark --- Appendable audio stream ---
    241435#pragma mark -
     
    306500template<bool stereo, bool is16Bit, bool isUnsigned, bool isLE>
    307501int AppendableMemoryStream<stereo, is16Bit, isUnsigned, isLE>::readBuffer(int16 *buffer, const int numSamples) {
    308502        Common::StackLock lock(_mutex);
    309 
    310503        int samples = numSamples;
    311504        while (samples > 0 && !eosIntern()) {
    312505                Buffer buf = *_bufferQueue.begin();
  • sound/voc.cpp

     
    166166        return loadVOCFromStream(stream, size, rate, loops, begin_loop, end_loop);
    167167}
    168168
    169 AudioStream *makeVOCStream(Common::ReadStream &stream, byte flags, uint loopStart, uint loopEnd) {
     169
     170#ifdef STREAM_AUDIO_FROM_DISK
     171
     172int parseVOCFormat(Common::SeekableReadStream& stream, LinearDiskStreamAudioBlock* block, int &rate, int &loops, int &begin_loop, int &end_loop) {
     173        VocFileHeader fileHeader;
     174        int currentBlock = 0;
     175        int size = 0;
     176
     177        if (stream.read(&fileHeader, 8) != 8)
     178                goto invalid;
     179
     180        if (!memcmp(&fileHeader, "VTLK", 4)) {
     181                if (stream.read(&fileHeader, sizeof(VocFileHeader)) != sizeof(VocFileHeader))
     182                        goto invalid;
     183        } else if (!memcmp(&fileHeader, "Creative", 8)) {
     184                if (stream.read(((byte *)&fileHeader) + 8, sizeof(VocFileHeader) - 8) != sizeof(VocFileHeader) - 8)
     185                        goto invalid;
     186        } else {
     187        invalid:;
     188                warning("loadVOCFromStream: Invalid header");
     189                return 0;
     190        }
     191
     192        if (memcmp(fileHeader.desc, "Creative Voice File", 19) != 0)
     193                error("loadVOCFromStream: Invalid header");
     194        if (fileHeader.desc[19] != 0x1A)
     195                debug(3, "loadVOCFromStream: Partially invalid header");
     196
     197        int32 offset = FROM_LE_16(fileHeader.datablock_offset);
     198        int16 version = FROM_LE_16(fileHeader.version);
     199        int16 code = FROM_LE_16(fileHeader.id);
     200        assert(offset == sizeof(VocFileHeader));
     201        // 0x100 is an invalid VOC version used by German version of DOTT (Disk) and
     202        // French version of Simon the Sorcerer 2 (CD)
     203        assert(version == 0x010A || version == 0x0114 || version == 0x0100);
     204        assert(code == ~version + 0x1234);
     205
     206        int len;
     207        byte *ret_sound = 0;
     208        size = 0;
     209        begin_loop = 0;
     210        end_loop = 0;
     211
     212        while ((code = stream.readByte())) {
     213                len = stream.readByte();
     214                len |= stream.readByte() << 8;
     215                len |= stream.readByte() << 16;
     216
     217                switch (code) {
     218                case 1:
     219                case 9: {
     220                        int packing;
     221                        if (code == 1) {
     222                                int time_constant = stream.readByte();
     223                                packing = stream.readByte();
     224                                len -= 2;
     225                                rate = getSampleRateFromVOCRate(time_constant);
     226                        } else {
     227                                rate = stream.readUint32LE();
     228                                int bits = stream.readByte();
     229                                int channels = stream.readByte();
     230                                if (bits != 8 || channels != 1) {
     231                                        warning("Unsupported VOC file format (%d bits per sample, %d channels)", bits, channels);
     232                                        break;
     233                                }
     234                                packing = stream.readUint16LE();
     235                                stream.readUint32LE();
     236                                len -= 12;
     237                        }
     238                        debug(9, "VOC Data Block: %d, %d, %d", rate, packing, len);
     239                        if (packing == 0) {
     240                               
     241                                // Found a data block - so add it to the block list
     242                                block[currentBlock].pos = stream.pos();
     243                                block[currentBlock].len = len;
     244                                currentBlock++;
     245
     246                                stream.seek(len, SEEK_CUR);
     247
     248                                size += len;
     249                                begin_loop = size;
     250                                end_loop = size;
     251                        } else {
     252                                warning("VOC file packing %d unsupported", packing);
     253                        }
     254                        } break;
     255                case 3: // silence
     256                        // occur with a few Igor sounds, voc file starts with a silence block with a
     257                        // frequency different from the data block. Just ignore fow now (implementing
     258                        // it wouldn't make a big difference anyway...)
     259                        assert(len == 3);
     260                        stream.readUint16LE();
     261                        stream.readByte();
     262                        break;
     263                case 6: // begin of loop
     264                        assert(len == 2);
     265                        loops = stream.readUint16LE();
     266                        break;
     267                case 7: // end of loop
     268                        assert(len == 0);
     269                        break;
     270                case 8: // "Extended"
     271                        // This occures in the LoL Intro demo. This block can usually be used to create stereo
     272                        // sound, but the LoL intro has only an empty block, thus this dummy implementation will
     273                        // work.
     274                        assert(len == 4);
     275                        stream.readUint16LE();
     276                        stream.readByte();
     277                        stream.readByte();
     278                        break;
     279                default:
     280                        warning("Unhandled code in VOC file : %d", code);
     281                        return 0;
     282                }
     283        }
     284        debug(4, "VOC Data Size : %d", size);
     285        return currentBlock;
     286}
     287
     288AudioStream *makeVOCDiskStream(Common::SeekableReadStream& stream, byte flags, bool takeOwnership) {
     289        const bool isUnsigned = (flags & Audio::Mixer::FLAG_UNSIGNED) != 0;
     290        const bool isLE       = (flags & Audio::Mixer::FLAG_LITTLE_ENDIAN) != 0;
     291        const int MAX_AUDIO_BLOCKS = 256;
     292
     293        LinearDiskStreamAudioBlock* block = new LinearDiskStreamAudioBlock[MAX_AUDIO_BLOCKS];
     294        int rate, loops, begin_loop, end_loop;
     295
     296        int numBlocks = parseVOCFormat(stream, block, rate, loops, begin_loop, end_loop);
     297
     298        return makeLinearDiskStream(stream, block, numBlocks, rate, flags, takeOwnership, begin_loop, end_loop);
     299}
     300
     301#endif
     302
     303
     304AudioStream *makeVOCStream(Common::SeekableReadStream &stream, byte flags, uint loopStart, uint loopEnd, bool takeOwnershipOfStream) {
    170305        int size, rate;
    171306
     307#ifdef STREAM_AUDIO_FROM_DISK
     308
     309        return makeVOCDiskStream(stream, flags, takeOwnershipOfStream);
     310
     311#else
    172312        byte *data = loadVOCFromStream(stream, size, rate);
     313
    173314        if (!data)
    174315                return 0;
    175316
    176317        return makeLinearInputStream(data, size, rate, flags | Audio::Mixer::FLAG_AUTOFREE, loopStart, loopEnd);
     318#endif
    177319}
    178320
    179321
  • sound/audiostream.h

     
    2828
    2929#include "common/util.h"
    3030#include "common/scummsys.h"
     31#include "common/stream.h"
    3132
    32 
    3333namespace Audio {
    3434
    3535/**
     
    109109        virtual int32 getTotalPlayTime() const { return kUnknownPlayTime; }
    110110};
    111111
     112
    112113/**
    113114 * Factory function for a raw linear AudioStream, which will simply treat all data
    114115 * in the buffer described by ptr and len as raw sample data in the specified
     
    118119 */
    119120AudioStream *makeLinearInputStream(const byte *ptr, uint32 len, int rate, byte flags, uint loopStart, uint loopEnd);
    120121
     122
     123/** Struct used to define the audio data to be played by a LinearDiskStream */
     124
     125struct LinearDiskStreamAudioBlock {
     126        int32 pos;              ///< Position in stream of the block
     127        int32 len;              ///< Length of the block (in samples)
     128};
     129
     130
     131/** Factory function for a Linear Disk Stream.  This can stream linear (PCM) audio from disk.  The
     132 *  function takes an pointer to an array of LinearDiskStreamAudioBlock which defines the
     133 *  start position and length of each block of uncompressed audio in the stream.
     134 */
     135
     136AudioStream *makeLinearDiskStream(Common::SeekableReadStream& stream, LinearDiskStreamAudioBlock* block, int
     137                numBlocks, int rate, byte flags, bool takeOwnership, uint loopStart, uint loopEnd);
     138
    121139/**
    122140 * An audio stream to which additional data can be appended on-the-fly.
    123141 * Used by SMUSH, iMuseDigital, the Kyrandia 3 VQA player, etc.
  • sound/voc.h

     
    4141#include "common/scummsys.h"
    4242
    4343namespace Common { class ReadStream; }
     44namespace Common { class SeekableReadStream; }
    4445
    4546namespace Audio {
    4647
     
    7879extern int getSampleRateFromVOCRate(int vocSR);
    7980
    8081/**
    81  * Try to load a VOC from the given seekable stream. Returns a pointer to memory
     82 * Try to load a VOC from the given stream. Returns a pointer to memory
    8283 * containing the PCM sample data (allocated with malloc). It is the callers
    8384 * responsibility to dellocate that data again later on! Currently this
    8485 * function only supports uncompressed raw PCM data.
     
    9293 *
    9394 * This function uses loadVOCFromStream() internally.
    9495 */
    95 AudioStream *makeVOCStream(Common::ReadStream &stream, byte flags = 0, uint loopStart = 0, uint loopEnd = 0);
     96AudioStream *makeVOCStream(Common::SeekableReadStream &stream, byte flags = 0, uint loopStart = 0, uint loopEnd = 0, bool takeOwnershipOfStream = false);
    9697
    9798} // End of namespace Audio
    9899
  • engines/kyra/sound.cpp

     
    235235// A simple wrapper to create VOC streams the way like creating MP3, OGG/Vorbis and FLAC streams.
    236236// Possible TODO: Think of making this complete and moving it to sound/voc.cpp ?
    237237Audio::AudioStream *makeVOCStream(Common::SeekableReadStream *stream, bool disposeAfterUse, uint32 startTime, uint32 duration, uint numLoops) {
     238
     239#ifdef STREAM_AUDIO_FROM_DISK
     240        Audio::AudioStream *as = Audio::makeVOCStream(*stream, Audio::Mixer::FLAG_UNSIGNED, 0, 0, disposeAfterUse);
     241#else
    238242        Audio::AudioStream *as = Audio::makeVOCStream(*stream, Audio::Mixer::FLAG_UNSIGNED);
    239243
    240244        if (disposeAfterUse)
    241245                delete stream;
     246#endif
    242247
    243248        return as;
    244249}