diff options
author | Colin Snover | 2017-08-03 23:48:36 -0500 |
---|---|---|
committer | Colin Snover | 2017-08-20 11:36:26 -0500 |
commit | 4ba87013a9d9d50a2866fd23aaf39e546bff9b7b (patch) | |
tree | 03f91627c367042165da46c21b3bb28fbbea55f1 | |
parent | 1d844978d675e1c4d875cc86c9ac86ad0fef04cb (diff) | |
download | scummvm-rg350-4ba87013a9d9d50a2866fd23aaf39e546bff9b7b.tar.gz scummvm-rg350-4ba87013a9d9d50a2866fd23aaf39e546bff9b7b.tar.bz2 scummvm-rg350-4ba87013a9d9d50a2866fd23aaf39e546bff9b7b.zip |
VIDEO: Support old-style stereo in VMDs
This format is used by the stereo audio VMDs in Lighthouse.
-rw-r--r-- | video/coktel_decoder.cpp | 240 | ||||
-rw-r--r-- | video/coktel_decoder.h | 13 |
2 files changed, 157 insertions, 96 deletions
diff --git a/video/coktel_decoder.cpp b/video/coktel_decoder.cpp index 245319a931..981dd82e8b 100644 --- a/video/coktel_decoder.cpp +++ b/video/coktel_decoder.cpp @@ -1543,6 +1543,93 @@ Graphics::PixelFormat IMDDecoder::getPixelFormat() const { return Graphics::PixelFormat::createFormatCLUT8(); } +class DPCMStream : public Audio::AudioStream { +public: + DPCMStream(Common::SeekableReadStream *stream, int rate, int channels, bool oldStereo) { + _stream = stream; + _rate = rate; + _channels = channels; + _oldStereo = oldStereo; + if (oldStereo) { + _buffer[0] = _buffer[1] = 0; + } + } + + ~DPCMStream() { + delete _stream; + } + + int readBuffer(int16 *buffer, const int numSamples); + bool isStereo() const { return _channels == 2; } + int getRate() const { return _rate; } + bool endOfData() const { return _stream->pos() >= _stream->size() || _stream->eos() || _stream->err(); } + +private: + Common::SeekableReadStream *_stream; + int _channels; + int _rate; + int _buffer[2]; + bool _oldStereo; +}; + +int DPCMStream::readBuffer(int16 *buffer, const int numSamples) { + static const uint16 tableDPCM[128] = { + 0x0000, 0x0008, 0x0010, 0x0020, 0x0030, 0x0040, 0x0050, 0x0060, 0x0070, 0x0080, + 0x0090, 0x00A0, 0x00B0, 0x00C0, 0x00D0, 0x00E0, 0x00F0, 0x0100, 0x0110, 0x0120, + 0x0130, 0x0140, 0x0150, 0x0160, 0x0170, 0x0180, 0x0190, 0x01A0, 0x01B0, 0x01C0, + 0x01D0, 0x01E0, 0x01F0, 0x0200, 0x0208, 0x0210, 0x0218, 0x0220, 0x0228, 0x0230, + 0x0238, 0x0240, 0x0248, 0x0250, 0x0258, 0x0260, 0x0268, 0x0270, 0x0278, 0x0280, + 0x0288, 0x0290, 0x0298, 0x02A0, 0x02A8, 0x02B0, 0x02B8, 0x02C0, 0x02C8, 0x02D0, + 0x02D8, 0x02E0, 0x02E8, 0x02F0, 0x02F8, 0x0300, 0x0308, 0x0310, 0x0318, 0x0320, + 0x0328, 0x0330, 0x0338, 0x0340, 0x0348, 0x0350, 0x0358, 0x0360, 0x0368, 0x0370, + 0x0378, 0x0380, 0x0388, 0x0390, 0x0398, 0x03A0, 0x03A8, 0x03B0, 0x03B8, 0x03C0, + 0x03C8, 0x03D0, 0x03D8, 0x03E0, 0x03E8, 0x03F0, 0x03F8, 0x0400, 0x0440, 0x0480, + 0x04C0, 0x0500, 0x0540, 0x0580, 0x05C0, 0x0600, 0x0640, 0x0680, 0x06C0, 0x0700, + 0x0740, 0x0780, 0x07C0, 0x0800, 0x0900, 0x0A00, 0x0B00, 0x0C00, 0x0D00, 0x0E00, + 0x0F00, 0x1000, 0x1400, 0x1800, 0x1C00, 0x2000, 0x3000, 0x4000 + }; + + assert((numSamples % _channels) == 0); + + int samples = 0; + + // Our starting position + if (!_oldStereo && _stream->pos() == 0) { + for (int i = 0; i < _channels; i++) + *buffer++ = _buffer[i] = _stream->readSint16LE(); + + samples += _channels; + } + + while (!endOfData() && samples < numSamples) { + if (_channels == 2 && _stream->size() == 1) { + warning("Buffer underrun in DPCMStream"); + break; + } + + for (int i = 0; i < _channels; i++) { + byte data = _stream->readByte(); + + if (data & 0x80) + _buffer[i] -= tableDPCM[data & 0x7f]; + else + _buffer[i] += tableDPCM[data]; + + // Emulating x86 16-bit signed register overflow + if (_buffer[i] > 32767) { + _buffer[i] -= 65536; + } else if (_buffer[i] < -32768) { + _buffer[i] += 65536; + } + + *buffer++ = _buffer[i]; + } + + samples += _channels; + } + + return samples; +} VMDDecoder::File::File() { offset = 0; @@ -1581,7 +1668,7 @@ VMDDecoder::VMDDecoder(Audio::Mixer *mixer, Audio::Mixer::SoundType soundType) : _soundLastFilledFrame(0), _audioFormat(kAudioFormat8bitRaw), _hasVideo(false), _videoCodec(0), _blitMode(0), _bytesPerPixel(0), _firstFramePos(0), _videoBufferSize(0), _externalCodec(false), _codec(0), - _subtitle(-1), _isPaletted(true), _autoStartSound(true) { + _subtitle(-1), _isPaletted(true), _autoStartSound(true), _oldStereoBuffer(nullptr) { _videoBuffer [0] = 0; _videoBuffer [1] = 0; @@ -1625,7 +1712,7 @@ bool VMDDecoder::seek(int32 frame, int whence, bool restart) { delete _audioStream; _soundStage = kSoundLoaded; - _audioStream = Audio::makeQueuingAudioStream(_soundFreq, _soundStereo != 0); + createAudioStream(); } _subtitle = -1; @@ -1915,13 +2002,19 @@ bool VMDDecoder::assessAudioProperties() { } } else { + if (_soundStereo == 2) { + supportedFormat = false; + } + _soundBytesPerSample = 1; - _audioFormat = kAudioFormat8bitRaw; _soundHeaderSize = 0; _soundDataSize = _soundSliceSize; - if (_soundStereo > 0) - supportedFormat = false; + if (_soundStereo == 1) { + _audioFormat = kAudioFormat16bitDPCM; + } else { + _audioFormat = kAudioFormat8bitRaw; + } } if (!supportedFormat) { @@ -1930,14 +2023,12 @@ bool VMDDecoder::assessAudioProperties() { return false; } - _frameRate = Common::Rational(_soundFreq, _soundSliceSize); + _frameRate = Common::Rational(_soundFreq, _soundSliceSize / (_soundStereo == 1 ? 2 : 1)); _hasSound = true; _soundEnabled = true; _soundStage = kSoundLoaded; - - _audioStream = Audio::makeQueuingAudioStream(_soundFreq, _soundStereo != 0); - + createAudioStream(); return true; } @@ -2074,6 +2165,7 @@ void VMDDecoder::close() { _soundDataSize = 0; _soundLastFilledFrame = 0; _audioFormat = kAudioFormat8bitRaw; + _oldStereoBuffer = nullptr; _hasVideo = false; _videoCodec = 0; @@ -2123,9 +2215,9 @@ void VMDDecoder::processFrame() { bool startSound = false; - for (uint16 i = 0; i < _partsPerFrame; i++) { - uint32 pos = _stream->pos(); + _stream->seek(_frames[_curFrame].offset, SEEK_SET); + for (uint16 i = 0; i < _partsPerFrame; i++) { Part &part = _frames[_curFrame].parts[i]; if (part.type == kPartTypeAudio) { @@ -2147,7 +2239,7 @@ void VMDDecoder::processFrame() { if (_soundEnabled) { uint32 mask = _stream->readUint32LE(); - filledSoundSlices(part.size - 4, mask); + filledSoundSlices(part.size - /* mask size */ 4, mask); if (_soundStage == kSoundLoaded) startSound = true; @@ -2176,8 +2268,6 @@ void VMDDecoder::processFrame() { _stream->skip(part.size); } - _stream->seek(pos + part.size); - } else if ((part.type == kPartTypeVideo) && !_hasVideo) { warning("VMDDecoder::processFrame(): Header claims there's no video, but video found (%d)", part.size); @@ -2470,6 +2560,14 @@ void VMDDecoder::blit24(const Graphics::Surface &srcSurf, Common::Rect &rect) { } void VMDDecoder::emptySoundSlice(uint32 size) { + if (_soundStereo == 1) { + // Technically an empty slice could be used at the very beginning of the + // stream, but anywhere else it would need to dynamically calculate the + // delta between the current sample and zero sample level and the steps + // to get a zero level + error("Old-style stereo cannot be filled with an empty slice"); + } + byte *soundBuf = (byte *)malloc(size); if (soundBuf) { @@ -2488,6 +2586,17 @@ void VMDDecoder::filledSoundSlice(uint32 size) { return; } + if (_soundStereo == 1) { + void *buf = malloc(size); + assert(buf); + const uint32 numBytesRead = _stream->read(buf, size); + assert(numBytesRead == size); + const uint32 numBytesWritten = _oldStereoBuffer->write(buf, size); + assert(numBytesWritten == size); + free(buf); + return; + } + Common::SeekableReadStream *data = _stream->readStream(size); Audio::AudioStream *sliceStream = 0; @@ -2508,9 +2617,9 @@ void VMDDecoder::filledSoundSlices(uint32 size, uint32 mask) { uint8 max; uint8 n = evaluateMask(mask, fillInfo, max); - int32 extraSize; - - extraSize = size - n * _soundDataSize; + // extraSize is needed by videos in some games (GK2) or audio data will be + // incomplete + int32 extraSize = size - n * _soundDataSize; if (_soundSlicesCount > 32) extraSize -= (_soundSlicesCount - 32) * _soundDataSize; @@ -2518,6 +2627,11 @@ void VMDDecoder::filledSoundSlices(uint32 size, uint32 mask) { if (n > 0) extraSize /= n; + // extraSize cannot be negative or audio data will be incomplete in some + // games (old-style stereo videos in Lighthouse) + if (extraSize < 0) + extraSize = 0; + for (uint8 i = 0; i < max; i++) if (fillInfo[i]) filledSoundSlice(_soundDataSize + extraSize); @@ -2528,6 +2642,14 @@ void VMDDecoder::filledSoundSlices(uint32 size, uint32 mask) { filledSoundSlice((_soundSlicesCount - 32) * _soundDataSize + _soundHeaderSize); } +void VMDDecoder::createAudioStream() { + _audioStream = Audio::makeQueuingAudioStream(_soundFreq, _soundStereo != 0); + if (_soundStereo == 1) { + _oldStereoBuffer = new Common::MemoryReadWriteStream(DisposeAfterUse::YES); + _audioStream->queueAudioStream(new DPCMStream(_oldStereoBuffer, _soundFreq, 2, true)); + } +} + uint8 VMDDecoder::evaluateMask(uint32 mask, bool *fillInfo, uint8 &max) { max = MIN<int>(_soundSlicesCount - 1, 31); @@ -2555,86 +2677,12 @@ Audio::AudioStream *VMDDecoder::create8bitRaw(Common::SeekableReadStream *stream return Audio::makeRawStream(stream, _soundFreq, flags, DisposeAfterUse::YES); } -class DPCMStream : public Audio::AudioStream { -public: - DPCMStream(Common::SeekableReadStream *stream, int rate, int channels) { - _stream = stream; - _rate = rate; - _channels = channels; - } - - ~DPCMStream() { - delete _stream; - } - - int readBuffer(int16 *buffer, const int numSamples); - bool isStereo() const { return _channels == 2; } - int getRate() const { return _rate; } - bool endOfData() const { return _stream->pos() >= _stream->size() || _stream->eos() || _stream->err(); } - -private: - Common::SeekableReadStream *_stream; - int _channels; - int _rate; - int _buffer[2]; -}; - -int DPCMStream::readBuffer(int16 *buffer, const int numSamples) { - static const uint16 tableDPCM[128] = { - 0x0000, 0x0008, 0x0010, 0x0020, 0x0030, 0x0040, 0x0050, 0x0060, 0x0070, 0x0080, - 0x0090, 0x00A0, 0x00B0, 0x00C0, 0x00D0, 0x00E0, 0x00F0, 0x0100, 0x0110, 0x0120, - 0x0130, 0x0140, 0x0150, 0x0160, 0x0170, 0x0180, 0x0190, 0x01A0, 0x01B0, 0x01C0, - 0x01D0, 0x01E0, 0x01F0, 0x0200, 0x0208, 0x0210, 0x0218, 0x0220, 0x0228, 0x0230, - 0x0238, 0x0240, 0x0248, 0x0250, 0x0258, 0x0260, 0x0268, 0x0270, 0x0278, 0x0280, - 0x0288, 0x0290, 0x0298, 0x02A0, 0x02A8, 0x02B0, 0x02B8, 0x02C0, 0x02C8, 0x02D0, - 0x02D8, 0x02E0, 0x02E8, 0x02F0, 0x02F8, 0x0300, 0x0308, 0x0310, 0x0318, 0x0320, - 0x0328, 0x0330, 0x0338, 0x0340, 0x0348, 0x0350, 0x0358, 0x0360, 0x0368, 0x0370, - 0x0378, 0x0380, 0x0388, 0x0390, 0x0398, 0x03A0, 0x03A8, 0x03B0, 0x03B8, 0x03C0, - 0x03C8, 0x03D0, 0x03D8, 0x03E0, 0x03E8, 0x03F0, 0x03F8, 0x0400, 0x0440, 0x0480, - 0x04C0, 0x0500, 0x0540, 0x0580, 0x05C0, 0x0600, 0x0640, 0x0680, 0x06C0, 0x0700, - 0x0740, 0x0780, 0x07C0, 0x0800, 0x0900, 0x0A00, 0x0B00, 0x0C00, 0x0D00, 0x0E00, - 0x0F00, 0x1000, 0x1400, 0x1800, 0x1C00, 0x2000, 0x3000, 0x4000 - }; - - assert((numSamples % _channels) == 0); - - int samples = 0; - - // Our starting position - if (_stream->pos() == 0) { - for (int i = 0; i < _channels; i++) - *buffer++ = _buffer[i] = _stream->readSint16LE(); - - samples += _channels; - } - - while (!endOfData() && samples < numSamples) { - for (int i = 0; i < _channels; i++) { - byte data = _stream->readByte(); - - if (data & 0x80) - _buffer[i] -= tableDPCM[data & 0x7f]; - else - _buffer[i] += tableDPCM[data]; - - // Emulating x86 16-bit signed register overflow - if (_buffer[i] > 32767) { - _buffer[i] -= 65536; - } else if (_buffer[i] < -32768) { - _buffer[i] += 65536; - } - - *buffer++ = _buffer[i]; - } - - samples += _channels; - } - - return samples; -} - Audio::AudioStream *VMDDecoder::create16bitDPCM(Common::SeekableReadStream *stream) { - return new DPCMStream(stream, _soundFreq, (_soundStereo == 0) ? 1 : 2); + // Old-style stereo audio blocks are not self-contained so cannot be played + // using this mechanism + assert(_soundStereo != 1); + + return new DPCMStream(stream, _soundFreq, (_soundStereo == 0) ? 1 : 2, false); } class VMD_ADPCMStream : public Audio::DVI_ADPCMStream { diff --git a/video/coktel_decoder.h b/video/coktel_decoder.h index b8faa99712..d1189c9394 100644 --- a/video/coktel_decoder.h +++ b/video/coktel_decoder.h @@ -43,6 +43,7 @@ namespace Common { struct Rect; +class MemoryReadWriteStream; class SeekableReadStream; } namespace Audio { @@ -503,6 +504,17 @@ private: AudioFormat _audioFormat; bool _autoStartSound; + /** + * Old stereo format packs a DPCM stream into audio packets without ensuring + * that each packet contains an even amount of samples. In order for the + * stream to play back correctly, all audio data needs to be pushed into a + * single data buffer and read from there. + * + * This buffer is owned by _audioStream and will be disposed when + * _audioStream is disposed. + */ + Common::MemoryReadWriteStream *_oldStereoBuffer; + // Video properties bool _hasVideo; uint32 _videoCodec; @@ -545,6 +557,7 @@ private: void emptySoundSlice (uint32 size); void filledSoundSlice (uint32 size); void filledSoundSlices(uint32 size, uint32 mask); + void createAudioStream(); uint8 evaluateMask(uint32 mask, bool *fillInfo, uint8 &max); |