aboutsummaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorColin Snover2017-08-03 23:48:36 -0500
committerColin Snover2017-08-20 11:36:26 -0500
commit4ba87013a9d9d50a2866fd23aaf39e546bff9b7b (patch)
tree03f91627c367042165da46c21b3bb28fbbea55f1
parent1d844978d675e1c4d875cc86c9ac86ad0fef04cb (diff)
downloadscummvm-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.cpp240
-rw-r--r--video/coktel_decoder.h13
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);