aboutsummaryrefslogtreecommitdiff
diff options
context:
space:
mode:
-rw-r--r--engines/kyra/screen.cpp6
-rw-r--r--engines/kyra/screen.h1
-rw-r--r--engines/kyra/vqa.cpp1017
-rw-r--r--engines/kyra/vqa.h179
4 files changed, 604 insertions, 599 deletions
diff --git a/engines/kyra/screen.cpp b/engines/kyra/screen.cpp
index 8c97e46a8f..d172045302 100644
--- a/engines/kyra/screen.cpp
+++ b/engines/kyra/screen.cpp
@@ -977,6 +977,10 @@ void Screen::copyPage(uint8 srcPage, uint8 dstPage) {
}
void Screen::copyBlockToPage(int pageNum, int x, int y, int w, int h, const uint8 *src) {
+ copyBlockToPage(pageNum, w, x, y, w, h, src);
+}
+
+void Screen::copyBlockToPage(int pageNum, int pitch, int x, int y, int w, int h, const uint8 *src) {
if (y < 0) {
src += (-y) * w;
h += y;
@@ -1006,7 +1010,7 @@ void Screen::copyBlockToPage(int pageNum, int x, int y, int w, int h, const uint
while (h--) {
memcpy(dst, src, w);
dst += SCREEN_W;
- src += w;
+ src += pitch;
}
}
diff --git a/engines/kyra/screen.h b/engines/kyra/screen.h
index 156b5b9a7c..33bff4dd4f 100644
--- a/engines/kyra/screen.h
+++ b/engines/kyra/screen.h
@@ -428,6 +428,7 @@ public:
void copyRegionToBuffer(int pageNum, int x, int y, int w, int h, uint8 *dest);
void copyBlockToPage(int pageNum, int x, int y, int w, int h, const uint8 *src);
+ void copyBlockToPage(int pageNum, int pitch, int x, int y, int w, int h, const uint8 *src);
void shuffleScreen(int sx, int sy, int w, int h, int srcPage, int dstPage, int ticks, bool transparent);
void fillRect(int x1, int y1, int x2, int y2, uint8 color, int pageNum = -1, bool xored = false);
diff --git a/engines/kyra/vqa.cpp b/engines/kyra/vqa.cpp
index 081d94a050..76e5c7285a 100644
--- a/engines/kyra/vqa.cpp
+++ b/engines/kyra/vqa.cpp
@@ -28,644 +28,612 @@
//
// The jung2.vqa movie does work, but only thanks to a grotesque hack.
-
+#include "kyra/kyra_v1.h"
#include "kyra/vqa.h"
-#include "kyra/resource.h"
-
-#include "common/system.h"
+#include "kyra/screen.h"
#include "audio/audiostream.h"
-#include "audio/mixer.h"
#include "audio/decoders/raw.h"
-namespace Kyra {
-
-VQAMovie::VQAMovie(KyraEngine_v1 *vm, OSystem *system) {
- _system = system;
- _vm = vm;
- _screen = _vm->screen();
- _opened = false;
- _x = _y = _drawPage = -1;
- _frame = 0;
- _vectorPointers = 0;
- _numPartialCodeBooks = 0;
- _partialCodeBookSize = 0;
- _compressedCodeBook = 0;
- _partialCodeBook = 0;
- _codeBook = 0;
- _frameInfo = 0;
- memset(_buffers, 0, sizeof(_buffers));
-}
-
-VQAMovie::~VQAMovie() {
- close();
-}
-
-void VQAMovie::initBuffers() {
- for (int i = 0; i < ARRAYSIZE(_buffers); i++) {
- _buffers[i].data = 0;
- _buffers[i].size = 0;
- }
-}
-
-void *VQAMovie::allocBuffer(int num, uint32 size) {
- assert(num >= 0 && num < ARRAYSIZE(_buffers));
- assert(size > 0);
-
- if (size > _buffers[num].size) {
- /*
- * We could use realloc() here, but we don't actually need the
- * old contents of the buffer.
- */
- delete[] _buffers[num].data;
- _buffers[num].data = new uint8[size];
- _buffers[num].size = size;
- }
+#include "common/system.h"
+#include "common/events.h"
- assert(_buffers[num].data);
+#include "graphics/surface.h"
- return _buffers[num].data;
-}
-
-void VQAMovie::freeBuffers() {
- for (int i = 0; i < ARRAYSIZE(_buffers); i++) {
- delete[] _buffers[i].data;
- _buffers[i].data = NULL;
- _buffers[i].size = 0;
- }
-}
+namespace Kyra {
-uint32 VQAMovie::readTag() {
+static uint32 readTag(Common::SeekableReadStream *stream) {
// Some tags have to be on an even offset, so they are padded with a
// zero byte. Skip that.
- uint32 tag = _file->readUint32BE();
+ uint32 tag = stream->readUint32BE();
- if (_file->eos())
+ if (stream->eos())
return 0;
if (!(tag & 0xFF000000)) {
- tag = (tag << 8) | _file->readByte();
+ tag = (tag << 8) | stream->readByte();
}
return tag;
}
-void VQAMovie::decodeSND1(byte *inbuf, uint32 insize, byte *outbuf, uint32 outsize) {
- const int8 WSTable2Bit[] = { -2, -1, 0, 1 };
- const int8 WSTable4Bit[] = {
- -9, -8, -6, -5, -4, -3, -2, -1,
- 0, 1, 2, 3, 4, 5, 6, 8
- };
-
- byte code;
- int8 count;
- uint16 input;
-
- int16 curSample = 0x80;
-
- while (outsize > 0) {
- input = *inbuf++ << 2;
- code = (input >> 8) & 0xFF;
- count = (input & 0xFF) >> 2;
-
- switch (code) {
- case 2:
- if (count & 0x20) {
- /* NOTE: count is signed! */
- count <<= 3;
- curSample += (count >> 3);
- *outbuf++ = curSample;
- outsize--;
- } else {
- for (; count >= 0; count--) {
- *outbuf++ = *inbuf++;
- outsize--;
- }
- curSample = *(outbuf - 1);
- }
- break;
- case 1:
- for (; count >= 0; count--) {
- code = *inbuf++;
-
- curSample += WSTable4Bit[code & 0x0F];
- curSample = CLIP<int16>(curSample, 0, 255);
- *outbuf++ = curSample;
-
- curSample += WSTable4Bit[code >> 4];
- curSample = CLIP<int16>(curSample, 0, 255);
- *outbuf++ = curSample;
-
- outsize -= 2;
- }
- break;
- case 0:
- for (; count >= 0; count--) {
- code = *inbuf++;
-
- curSample += WSTable2Bit[code & 0x03];
- curSample = CLIP<int16>(curSample, 0, 255);
- *outbuf++ = curSample;
-
- curSample += WSTable2Bit[(code >> 2) & 0x03];
- curSample = CLIP<int16>(curSample, 0, 255);
- *outbuf++ = curSample;
-
- curSample += WSTable2Bit[(code >> 4) & 0x03];
- curSample = CLIP<int16>(curSample, 0, 255);
- *outbuf++ = curSample;
-
- curSample += WSTable2Bit[(code >> 6) & 0x03];
- curSample = CLIP<int16>(curSample, 0, 255);
- *outbuf++ = curSample;
-
- outsize -= 4;
- }
- break;
- default:
- for (; count >= 0; count--) {
- *outbuf++ = curSample;
- outsize--;
- }
- }
- }
+VQADecoder::VQADecoder() {
}
-bool VQAMovie::open(const char *filename) {
+VQADecoder::~VQADecoder() {
close();
+}
- _file = _vm->resource()->createReadStream(filename);
- if (!_file)
- return false;
+bool VQADecoder::loadStream(Common::SeekableReadStream *stream) {
+ close();
- if (_file->readUint32BE() != MKTAG('F','O','R','M')) {
- warning("VQAMovie::open: Cannot find `FORM' tag");
+ if (stream->readUint32BE() != MKTAG('F','O','R','M')) {
+ warning("VQADecoder::loadStream(): Cannot find `FORM' tag");
return false;
}
- // For now, we ignore the size of the FORM chunk.
- _file->readUint32BE();
+ // Ignore the size of the FORM chunk. We're only interested in its
+ // children.
+ stream->readUint32BE();
- if (_file->readUint32BE() != MKTAG('W','V','Q','A')) {
- warning("WQAMovie::open: Cannot find `WVQA' tag");
+ if (stream->readUint32BE() != MKTAG('W','V','Q','A')) {
+ warning("VQADecoder::loadStream(): Cannot find `WVQA' tag");
return false;
}
- bool foundHeader = false;
- bool foundFrameInfo = false;
+ VQAVideoTrack *videoTrack = new VQADecoder::VQAVideoTrack(stream);
+ addTrack(videoTrack);
- // The information we need is stored in two chunks: VQHD and FINF. We
- // need both of them before we can begin decoding the movie.
-
- while (!foundHeader || !foundFrameInfo) {
- uint32 tag = readTag();
- uint32 size = _file->readUint32BE();
+ // We want to find both a VQHD chunk containing the header, and a FINF
+ // chunk containing the frame offsets.
- switch (tag) {
- case MKTAG('V','Q','H','D'): // VQA header
- _header.version = _file->readUint16LE();
- _header.flags = _file->readUint16LE();
- _header.numFrames = _file->readUint16LE();
- _header.width = _file->readUint16LE();
- _header.height = _file->readUint16LE();
- _header.blockW = _file->readByte();
- _header.blockH = _file->readByte();
- _header.frameRate = _file->readByte();
- _header.cbParts = _file->readByte();
- _header.colors = _file->readUint16LE();
- _header.maxBlocks = _file->readUint16LE();
- _header.unk1 = _file->readUint32LE();
- _header.unk2 = _file->readUint16LE();
- _header.freq = _file->readUint16LE();
- _header.channels = _file->readByte();
- _header.bits = _file->readByte();
- _header.unk3 = _file->readUint32LE();
- _header.unk4 = _file->readUint16LE();
- _header.maxCBFZSize = _file->readUint32LE();
- _header.unk5 = _file->readUint32LE();
-
- // Kyrandia 3 uses version 1 VQA files, and is the only
- // known game to do so. This version of the format has
- // some implicit default values.
-
- if (_header.version == 1) {
- if (_header.freq == 0)
- _header.freq = 22050;
- if (_header.channels == 0)
- _header.channels = 1;
- if (_header.bits == 0)
- _header.bits = 8;
- }
+ bool foundVQHD = false;
+ bool foundFINF = false;
- _x = (Screen::SCREEN_W - _header.width) / 2;
- _y = (Screen::SCREEN_H - _header.height) / 2;
+ VQAAudioTrack *audioTrack = NULL;
- _frameInfo = new uint32[_header.numFrames];
- _frame = new byte[_header.width * _header.height];
-
- _codeBookSize = 0xF00 * _header.blockW * _header.blockH;
- _codeBook = new byte[_codeBookSize];
- _partialCodeBook = new byte[_codeBookSize];
- memset(_codeBook, 0, _codeBookSize);
- memset(_partialCodeBook, 0, _codeBookSize);
-
- _numVectorPointers = (_header.width / _header.blockW) * (_header.height * _header.blockH);
- _vectorPointers = new uint16[_numVectorPointers];
- memset(_vectorPointers, 0, _numVectorPointers * sizeof(uint16));
+ // The information we need is stored in two chunks: VQHD and FINF. We
+ // need both of them before we can begin decoding the movie.
- _partialCodeBookSize = 0;
- _numPartialCodeBooks = 0;
+ while (!foundVQHD || !foundFINF) {
+ uint32 tag = readTag(stream);
+ uint32 size = stream->readUint32BE();
- if (_header.flags & 1) {
- // This VQA movie has sound. Kyrandia 3 uses
- // 8-bit sound, and so far testing indicates
- // that it's all mono.
- //
- // This is good, because it means we won't have
- // to worry about the confusing parts of the
- // VQA spec, where 8- and 16-bit data have
- // different signedness and stereo sample
- // layout varies between different games.
-
- assert(_header.bits == 8);
- assert(_header.channels == 1);
-
- _stream = Audio::makeQueuingAudioStream(_header.freq, false);
- } else {
- _stream = NULL;
+ switch (tag) {
+ case MKTAG('V','Q','H','D'):
+ videoTrack->handleVQHD();
+ if (videoTrack->hasSound()) {
+ audioTrack = new VQAAudioTrack(stream, videoTrack->getAudioFreq());
+ videoTrack->setAudioTrack(audioTrack);
+ addTrack(audioTrack);
}
-
- foundHeader = true;
+ foundVQHD = true;
break;
-
- case MKTAG('F','I','N','F'): // Frame info
- if (!foundHeader) {
- warning("VQAMovie::open: Found `FINF' before `VQHD'");
+ case MKTAG('F','I','N','F'):
+ if (!foundVQHD) {
+ warning("VQADecoder::loadStream(): Found `FINF' before `VQHD'");
return false;
}
-
- if (size != 4 * (uint32)_header.numFrames) {
- warning("VQAMovie::open: Expected size %d for `FINF' chunk, but got %u", 4 * _header.numFrames, size);
+ if (size != 4 * getFrameCount()) {
+ warning("VQADecoder::loadStream(): Expected size %d for `FINF' chunk, but got %u", 4 * getFrameCount(), size);
return false;
}
-
- foundFrameInfo = true;
-
- for (int i = 0; i < _header.numFrames; i++) {
- _frameInfo[i] = 2 * _file->readUint32LE();
- }
-
- // HACK: This flag is set in jung2.vqa, and its
- // purpose, if it has one, is unknown. It can't be a
- // general purpose flag, because in large movies the
- // frame offsets can be large enough to set this flag,
- // though of course never for the first frame.
- //
- // At least in my copy of Kyrandia 3, _frameInfo[0] is
- // 0x81000098, and the desired index is 0x4716. So the
- // value should be 0x80004716, but I don't want to
- // hard-code it. Instead, scan the file for the offset
- // to the first VQFR chunk.
-
- if (_frameInfo[0] & 0x01000000) {
- uint32 oldPos = _file->pos();
-
- while (1) {
- uint32 scanTag = readTag();
- uint32 scanSize = _file->readUint32BE();
-
- if (_file->eos())
- break;
-
- if (scanTag == MKTAG('V','Q','F','R')) {
- _frameInfo[0] = (_file->pos() - 8) | 0x80000000;
- break;
- }
-
- _file->seek(scanSize, SEEK_CUR);
- }
-
- _file->seek(oldPos);
- }
-
+ videoTrack->handleFINF();
+ foundFINF = true;
break;
-
default:
- warning("VQAMovie::open: Unknown tag `%c%c%c%c'", char((tag >> 24) & 0xFF), char((tag >> 16) & 0xFF), char((tag >> 8) & 0xFF), char(tag & 0xFF));
- _file->seek(size, SEEK_CUR);
+ warning("VQADecoder::loadStream(): Unknown tag `%s'", tag2str(tag));
+ stream->seek(size, SEEK_CUR);
+ break;
}
}
- initBuffers();
-
- _opened = true;
return true;
}
-void VQAMovie::close() {
- if (_opened) {
- delete[] _frameInfo;
- delete[] _frame;
- delete[] _codeBook;
- delete[] _partialCodeBook;
- delete[] _vectorPointers;
-
- if (_vm->_mixer->isSoundHandleActive(_sound))
- _vm->_mixer->stopHandle(_sound);
-
- _frameInfo = NULL;
- _frame = NULL;
- _codeBookSize = 0;
- _codeBook = NULL;
- _partialCodeBook = NULL;
- _vectorPointers = NULL;
- _stream = NULL;
-
- delete _file;
- _file = 0;
-
- freeBuffers();
-
- _opened = false;
- }
+// -----------------------------------------------------------------------
+
+VQADecoder::VQAAudioTrack::VQAAudioTrack(Common::SeekableReadStream *stream, int freq) {
+ _fileStream = stream;
+ _audioStream = Audio::makeQueuingAudioStream(freq, false);
}
-void VQAMovie::displayFrame(uint frameNum) {
- if (frameNum >= _header.numFrames || !_opened)
- return;
+VQADecoder::VQAAudioTrack::~VQAAudioTrack() {
+ delete _audioStream;
+}
- bool foundSound = !_stream;
- bool foundFrame = false;
- uint i;
+Audio::AudioStream *VQADecoder::VQAAudioTrack::getAudioStream() const {
+ return _audioStream;
+}
- _file->seek(_frameInfo[frameNum] & 0x7FFFFFFF);
+void VQADecoder::VQAAudioTrack::handleSND0() {
+ uint32 size = _fileStream->readUint32BE();
+ byte *buf = (byte *)malloc(size);
+ _fileStream->read(buf, size);
+ _audioStream->queueBuffer(buf, size, DisposeAfterUse::YES, Audio::FLAG_UNSIGNED);
+}
- while (!foundSound || !foundFrame) {
- uint32 tag = readTag();
- uint32 size = _file->readUint32BE();
+void VQADecoder::VQAAudioTrack::handleSND1() {
+ _fileStream->readUint32BE();
+ uint16 outsize = _fileStream->readUint16LE();
+ uint16 insize = _fileStream->readUint16LE();
+ byte *inbuf = (byte *)malloc(insize);
+
+ _fileStream->read(inbuf, insize);
+
+ if (insize == outsize) {
+ _audioStream->queueBuffer(inbuf, insize, DisposeAfterUse::YES, Audio::FLAG_UNSIGNED);
+ } else {
+ const int8 WSTable2Bit[] = { -2, -1, 0, 1 };
+ const int8 WSTable4Bit[] = {
+ -9, -8, -6, -5, -4, -3, -2, -1,
+ 0, 1, 2, 3, 4, 5, 6, 8
+ };
+
+ byte *outbuf = (byte *)malloc(outsize);
+ byte *in = inbuf;
+ byte *out = outbuf;
+ int16 curSample = 0x80;
+ uint16 bytesLeft = outsize;
+
+ while (bytesLeft > 0) {
+ uint16 input = *in++ << 2;
+ byte code = (input >> 8) & 0xFF;
+ int8 count = (input & 0xFF) >> 2;
+ int i;
+
+ switch (code) {
+ case 2:
+ if (count & 0x20) {
+ /* NOTE: count is signed! */
+ count <<= 3;
+ curSample += (count >> 3);
+ *out++ = curSample;
+ bytesLeft--;
+ } else {
+ for (; count >= 0; count--) {
+ *out++ = *in++;
+ bytesLeft--;
+ }
+ curSample = *(out - 1);
+ }
+ break;
+ case 1:
+ for (; count >= 0; count--) {
+ code = *in++;
- if (_file->eos()) {
- // This happens at the last frame. Apparently it has
- // no sound?
- break;
- }
+ for (i = 0; i < 2; i++) {
+ curSample += WSTable4Bit[code & 0x0F];
+ curSample = CLIP<int16>(curSample, 0, 255);
+ code >>= 4;
+ *out++ = curSample;
+ }
- byte *inbuf, *outbuf;
- uint32 insize, outsize;
- int32 end;
+ bytesLeft -= 2;
+ }
+ break;
+ case 0:
+ for (; count >= 0; count--) {
+ code = *in++;
- switch (tag) {
- case MKTAG('S','N','D','0'): // Uncompressed sound
- foundSound = true;
- inbuf = (byte *)malloc(size);
- _file->read(inbuf, size);
- assert(_stream);
- _stream->queueBuffer(inbuf, size, DisposeAfterUse::YES, Audio::FLAG_UNSIGNED);
- break;
+ for (i = 0; i < 4; i++) {
+ curSample += WSTable2Bit[code & 0x03];
+ curSample = CLIP<int16>(curSample, 0, 255);
+ code >>= 2;
+ *out++ = curSample;
+ }
- case MKTAG('S','N','D','1'): // Compressed sound, almost like AUD
- foundSound = true;
- outsize = _file->readUint16LE();
- insize = _file->readUint16LE();
+ bytesLeft -= 4;
+ }
+ break;
+ default:
+ for (; count >= 0; count--) {
+ *out++ = curSample;
+ bytesLeft--;
+ }
+ break;
+ }
+ }
+ _audioStream->queueBuffer(outbuf, outsize, DisposeAfterUse::YES, Audio::FLAG_UNSIGNED);
+ free(inbuf);
+ }
+}
- inbuf = (byte *)malloc(insize);
- _file->read(inbuf, insize);
+void VQADecoder::VQAAudioTrack::handleSND2() {
+ uint32 size = _fileStream->readUint32BE();
+ warning("VQADecoder::VQAAudioTrack::handleSND2(): `SND2' is not implemented");
+ _fileStream->seek(size, SEEK_CUR);
+}
- if (insize == outsize) {
- assert(_stream);
- _stream->queueBuffer(inbuf, insize, DisposeAfterUse::YES, Audio::FLAG_UNSIGNED);
- } else {
- outbuf = (byte *)malloc(outsize);
- decodeSND1(inbuf, insize, outbuf, outsize);
- assert(_stream);
- _stream->queueBuffer(outbuf, outsize, DisposeAfterUse::YES, Audio::FLAG_UNSIGNED);
- free(inbuf);
- }
- break;
+// -----------------------------------------------------------------------
- case MKTAG('S','N','D','2'): // Compressed sound
- foundSound = true;
- warning("VQAMovie::displayFrame: `SND2' is not implemented");
- _file->seek(size, SEEK_CUR);
- break;
+VQADecoder::VQAVideoTrack::VQAVideoTrack(Common::SeekableReadStream *stream) {
+ _fileStream = stream;
+ _surface = new Graphics::Surface();
+ memset(_palette, 0, sizeof(_palette));
+ _dirtyPalette = false;
+ _audioTrack = NULL;
- case MKTAG('V','Q','F','R'):
- foundFrame = true;
- end = _file->pos() + size - 8;
+ _curFrame = -1;
- while (_file->pos() < end) {
- tag = readTag();
- size = _file->readUint32BE();
+ memset(&_header, 0, sizeof(_header));
+ _frameInfo = NULL;
+ _codeBookSize = 0;
+ _compressedCodeBook = false;
+ _codeBook = NULL;
+ _partialCodeBookSize = 0;
+ _numPartialCodeBooks = 0;
+ _partialCodeBook = NULL;
+ _numVectorPointers = 0;
+ _vectorPointers = NULL;
+}
- switch (tag) {
- case MKTAG('C','B','F','0'): // Full codebook
- _file->read(_codeBook, size);
- break;
+VQADecoder::VQAVideoTrack::~VQAVideoTrack() {
+ delete _surface;
+ delete[] _frameInfo;
+ delete[] _codeBook;
+ delete[] _partialCodeBook;
+ delete[] _vectorPointers;
+ // The audio track gets deleted by VQADecoder.
+}
- case MKTAG('C','B','F','Z'): // Full codebook
- inbuf = (byte *)allocBuffer(0, size);
- _file->read(inbuf, size);
- Screen::decodeFrame4(inbuf, _codeBook, _codeBookSize);
- break;
+uint16 VQADecoder::VQAVideoTrack::getWidth() const {
+ return _header.width;
+}
- case MKTAG('C','B','P','0'): // Partial codebook
- _compressedCodeBook = false;
- _file->read(_partialCodeBook + _partialCodeBookSize, size);
- _partialCodeBookSize += size;
- _numPartialCodeBooks++;
- break;
+uint16 VQADecoder::VQAVideoTrack::getHeight() const {
+ return _header.height;
+}
- case MKTAG('C','B','P','Z'): // Partial codebook
- _compressedCodeBook = true;
- _file->read(_partialCodeBook + _partialCodeBookSize, size);
- _partialCodeBookSize += size;
- _numPartialCodeBooks++;
- break;
+Graphics::PixelFormat VQADecoder::VQAVideoTrack::getPixelFormat() const {
+ return _surface->format;
+}
- case MKTAG('C','P','L','0'): // Palette
- assert(size <= 3 * 256);
- _file->read(_screen->getPalette(0).getData(), size);
- break;
+int VQADecoder::VQAVideoTrack::getCurFrame() const {
+ return _curFrame;
+}
- case MKTAG('C','P','L','Z'): // Palette
- inbuf = (byte *)allocBuffer(0, size);
- _file->read(inbuf, size);
- Screen::decodeFrame4(inbuf, _screen->getPalette(0).getData(), 768);
- break;
+int VQADecoder::VQAVideoTrack::getFrameCount() const {
+ return _header.numFrames;
+}
- case MKTAG('V','P','T','0'): // Frame data
- assert(size / 2 <= _numVectorPointers);
+Common::Rational VQADecoder::VQAVideoTrack::getFrameRate() const {
+ return _header.frameRate;
+}
- for (i = 0; i < size / 2; i++)
- _vectorPointers[i] = _file->readUint16LE();
- break;
+bool VQADecoder::VQAVideoTrack::hasSound() const {
+ return (_header.flags & 1) != 0;
+}
- case MKTAG('V','P','T','Z'): // Frame data
- inbuf = (byte *)allocBuffer(0, size);
- outbuf = (byte *)allocBuffer(1, 2 * _numVectorPointers);
+int VQADecoder::VQAVideoTrack::getAudioFreq() const {
+ return _header.freq;
+}
- _file->read(inbuf, size);
- size = Screen::decodeFrame4(inbuf, outbuf, 2 * _numVectorPointers);
+bool VQADecoder::VQAVideoTrack::hasDirtyPalette() const {
+ return _dirtyPalette;
+}
- assert(size / 2 <= _numVectorPointers);
+const byte *VQADecoder::VQAVideoTrack::getPalette() const {
+ _dirtyPalette = false;
+ return _palette;
+}
- for (i = 0; i < size / 2; i++)
- _vectorPointers[i] = READ_LE_UINT16(outbuf + 2 * i);
- break;
+void VQADecoder::VQAVideoTrack::setAudioTrack(VQAAudioTrack *audioTrack) {
+ _audioTrack = audioTrack;
+}
- default:
- warning("VQAMovie::displayFrame: Unknown `VQFR' sub-tag `%c%c%c%c'", char((tag >> 24) & 0xFF), char((tag >> 16) & 0xFF), char((tag >> 8) & 0xFF), char(tag & 0xFF));
- _file->seek(size, SEEK_CUR);
- }
+const Graphics::Surface *VQADecoder::VQAVideoTrack::decodeNextFrame() {
+ // Stop if reading the tag is enough to put us ahead of the next frame
+ int32 end = (_frameInfo[_curFrame + 1] & 0x7FFFFFFF) - 7;
- }
+ // At this point, we probably only need to adjust for the offset in the
+ // stream to be even. But we may as well do this to really make sure
+ // we have the correct offset.
+ if (_curFrame >= 0) {
+ _fileStream->seek(_frameInfo[_curFrame] & 0x7FFFFFFF);
+ }
- break;
+ bool hasFrame = false;
+ while (!_fileStream->eos() && _fileStream->pos() < end) {
+ uint32 tag = readTag(_fileStream);
+ uint32 size;
+
+ switch (tag) {
+ case MKTAG('S','N','D','0'): // Uncompressed sound
+ assert(_audioTrack);
+ _audioTrack->handleSND0();
+ break;
+ case MKTAG('S','N','D','1'): // Compressed sound, almost like AUD
+ assert(_audioTrack);
+ _audioTrack->handleSND1();
+ break;
+ case MKTAG('S','N','D','2'): // Compressed sound
+ assert(_audioTrack);
+ _audioTrack->handleSND2();
+ break;
+ case MKTAG('V','Q','F','R'):
+ handleVQFR();
+ hasFrame = true;
+ break;
+ case MKTAG('C','M','D','S'):
+ // The purpose of this is unknown, but it's known to
+ // exist so don't warn about it.
+ size = _fileStream->readUint32BE();
+ _fileStream->seek(size, SEEK_CUR);
+ break;
default:
- warning("VQAMovie::displayFrame: Unknown tag `%c%c%c%c'", char((tag >> 24) & 0xFF), char((tag >> 16) & 0xFF), char((tag >> 8) & 0xFF), char(tag & 0xFF));
- _file->seek(size, SEEK_CUR);
+ warning("VQADecoder::VQAVideoTrack::decodeNextFrame(): Unknown tag `%s'", tag2str(tag));
+ size = _fileStream->readUint32BE();
+ _fileStream->seek(size, SEEK_CUR);
+ break;
}
}
- // The frame has been decoded
-
- if (_frameInfo[frameNum] & 0x80000000)
- _screen->setScreenPalette(_screen->getPalette(0));
+ if (hasFrame) {
+ if (_frameInfo[_curFrame] & 0x80000000) {
+ _dirtyPalette = true;
+ }
- int blockPitch = _header.width / _header.blockW;
+ int blockPitch = _header.width / _header.blockW;
- for (int by = 0; by < _header.height / _header.blockH; by++) {
- for (int bx = 0; bx < blockPitch; bx++) {
- byte *dst = _frame + by * _header.width * _header.blockH + bx * _header.blockW;
- int val = _vectorPointers[by * blockPitch + bx];
+ for (int by = 0; by < _header.height / _header.blockH; by++) {
+ for (int bx = 0; bx < blockPitch; bx++) {
+ byte *dst = (byte *)_surface->getBasePtr(bx * _header.blockW, by * _header.blockH);
+ int val = _vectorPointers[by * blockPitch + bx];
+ int i;
- if ((val & 0xFF00) == 0xFF00) {
- // Solid color
- for (i = 0; i < _header.blockH; i++) {
- memset(dst, 255 - (val & 0xFF), _header.blockW);
- dst += _header.width;
- }
- } else {
- // Copy data from _vectorPointers. I'm not sure
- // why we don't use the three least significant
- // bits of 'val'.
- byte *src = &_codeBook[(val >> 3) * _header.blockW * _header.blockH];
-
- for (i = 0; i < _header.blockH; i++) {
- memcpy(dst, src, _header.blockW);
- src += _header.blockW;
- dst += _header.width;
+ if ((val & 0xFF00) == 0xFF00) {
+ // Solid color
+ for (i = 0; i < _header.blockH; i++) {
+ memset(dst, 255 - (val & 0xFF), _header.blockW);
+ dst += _header.width;
+ }
+ } else {
+ // Copy data from _vectorPointers. I'm not sure
+ // why we don't use the three least significant
+ // bits of 'val'.
+ byte *src = &_codeBook[(val >> 3) * _header.blockW * _header.blockH];
+
+ for (i = 0; i < _header.blockH; i++) {
+ memcpy(dst, src, _header.blockW);
+ src += _header.blockW;
+ dst += _header.width;
+ }
}
}
}
- }
- if (_numPartialCodeBooks == _header.cbParts) {
- if (_compressedCodeBook) {
- Screen::decodeFrame4(_partialCodeBook, _codeBook, _codeBookSize);
- } else {
- memcpy(_codeBook, _partialCodeBook, _partialCodeBookSize);
+ if (_numPartialCodeBooks == _header.cbParts) {
+ if (_compressedCodeBook) {
+ Screen::decodeFrame4(_partialCodeBook, _codeBook, _codeBookSize);
+ } else {
+ memcpy(_codeBook, _partialCodeBook, _partialCodeBookSize);
+ }
+ _numPartialCodeBooks = 0;
+ _partialCodeBookSize = 0;
}
- _numPartialCodeBooks = 0;
- _partialCodeBookSize = 0;
}
- _screen->copyBlockToPage(_drawPage, _x, _y, _header.width, _header.height, _frame);
+ _curFrame++;
+ return _surface;
}
-void VQAMovie::play() {
- uint32 startTick;
+void VQADecoder::VQAVideoTrack::handleVQHD() {
+ _header.version = _fileStream->readUint16LE();
+ _header.flags = _fileStream->readUint16LE();
+ _header.numFrames = _fileStream->readUint16LE();
+ _header.width = _fileStream->readUint16LE();
+ _header.height = _fileStream->readUint16LE();
+ _header.blockW = _fileStream->readByte();
+ _header.blockH = _fileStream->readByte();
+ _header.frameRate = _fileStream->readByte();
+ _header.cbParts = _fileStream->readByte();
+ _header.colors = _fileStream->readUint16LE();
+ _header.maxBlocks = _fileStream->readUint16LE();
+ _header.unk1 = _fileStream->readUint32LE();
+ _header.unk2 = _fileStream->readUint16LE();
+ _header.freq = _fileStream->readUint16LE();
+ _header.channels = _fileStream->readByte();
+ _header.bits = _fileStream->readByte();
+ _header.unk3 = _fileStream->readUint32LE();
+ _header.unk4 = _fileStream->readUint16LE();
+ _header.maxCBFZSize = _fileStream->readUint32LE();
+ _header.unk5 = _fileStream->readUint32LE();
+
+ _surface->create(_header.width, _header.height, Graphics::PixelFormat::createFormatCLUT8());
+
+ // Kyrandia 3 uses version 1 VQA files, and is the only known game to
+ // do so. This version of the format has some implicit default values.
+
+ if (_header.version == 1) {
+ if (_header.freq == 0)
+ _header.freq = 22050;
+ if (_header.channels == 0)
+ _header.channels = 1;
+ if (_header.bits == 0)
+ _header.bits = 8;
+ }
- if (!_opened)
- return;
+ _frameInfo = new uint32[_header.numFrames + 1];
- startTick = _system->getMillis();
+ _codeBookSize = 0xF00 * _header.blockW * _header.blockH;
+ _codeBook = new byte[_codeBookSize];
+ _partialCodeBook = new byte[_codeBookSize];
+ memset(_codeBook, 0, _codeBookSize);
+ memset(_partialCodeBook, 0, _codeBookSize);
- // First, handle any sound chunk that appears before the first frame.
- // At least in some versions, it will contain half a second of audio,
- // presumably to lessen the risk of audio underflow.
- //
- // In most movies, we will find a CMDS tag. The purpose of this is
- // currently unknown.
- //
- // In cow1_0.vqa, cow1_1.vqa, jung0.vqa, and jung1.vqa we will find a
- // VQFR tag. A frame before the first frame? Weird. It doesn't seem to
- // be needed, though.
+ _numVectorPointers = (_header.width / _header.blockW) * (_header.height * _header.blockH);
+ _vectorPointers = new uint16[_numVectorPointers];
+ memset(_vectorPointers, 0, _numVectorPointers * sizeof(uint16));
- byte *inbuf, *outbuf;
- uint32 insize, outsize;
+ _partialCodeBookSize = 0;
+ _numPartialCodeBooks = 0;
- if (_stream) {
- while ((uint)_file->pos() < (_frameInfo[0] & 0x7FFFFFFF)) {
- uint32 tag = readTag();
- uint32 size = _file->readUint32BE();
+ if (hasSound()) {
+ // Kyrandia 3 uses 8-bit sound, and so far testing indicates
+ // that it's all mono.
+ //
+ // This is good, because it means we won't have to worry about
+ // the confusing parts of the VQA spec, where 8- and 16-bit
+ // data have different signedness and stereo sample layout
+ // varies between different games.
+
+ assert(_header.bits == 8);
+ assert(_header.channels == 1);
+ }
+}
- if (_file->eos()) {
- warning("VQAMovie::play: Unexpected EOF");
- break;
- }
+void VQADecoder::VQAVideoTrack::handleFINF() {
+ for (int i = 0; i < _header.numFrames; i++) {
+ _frameInfo[i] = 2 * _fileStream->readUint32LE();
+ }
- switch (tag) {
- case MKTAG('S','N','D','0'): // Uncompressed sound
- inbuf = (byte *)malloc(size);
- _file->read(inbuf, size);
- _stream->queueBuffer(inbuf, size, DisposeAfterUse::YES, Audio::FLAG_UNSIGNED);
- break;
+ // HACK: This flag is set in jung2.vqa, and its purpose - if it has
+ // one - is currently unknown. It can't be a general purpose flag,
+ // because in large movies the frame offset can be large enough to
+ // set this flag, though of course never for the first frame.
+ //
+ // At least in my copy of Kyrandia 3, _frameInfo[0] is 0x81000098, and
+ // the desired index is 0x4716. So the value should be 0x80004716, but
+ // I don't want to hard-code it. Instead, scan the file for the offset
+ // to the first VQFR chunk.
- case MKTAG('S','N','D','1'): // Compressed sound
- outsize = _file->readUint16LE();
- insize = _file->readUint16LE();
+ if (_frameInfo[0] & 0x01000000) {
+ uint32 oldPos = _fileStream->pos();
- inbuf = (byte *)malloc(insize);
- _file->read(inbuf, insize);
+ while (1) {
+ uint32 scanTag = readTag(_fileStream);
+ uint32 scanSize = _fileStream->readUint32BE();
- if (insize == outsize) {
- _stream->queueBuffer(inbuf, insize, DisposeAfterUse::YES, Audio::FLAG_UNSIGNED);
- } else {
- outbuf = (byte *)malloc(outsize);
- decodeSND1(inbuf, insize, outbuf, outsize);
- _stream->queueBuffer(outbuf, outsize, DisposeAfterUse::YES, Audio::FLAG_UNSIGNED);
- free(inbuf);
- }
+ if (_fileStream->eos())
break;
- case MKTAG('S','N','D','2'): // Compressed sound
- warning("VQAMovie::play: `SND2' is not implemented");
- _file->seek(size, SEEK_CUR);
+ if (scanTag == MKTAG('V','Q','F','R')) {
+ _frameInfo[0] = (_fileStream->pos() - 8) | 0x80000000;
break;
+ }
- case MKTAG('C','M','D','S'): // Unused tag, always empty in kyra3
- _file->seek(size, SEEK_CUR);
- break;
+ _fileStream->seek(scanSize, SEEK_CUR);
+ }
- default:
- warning("VQAMovie::play: Unknown tag `%c%c%c%c'", char((tag >> 24) & 0xFF), char((tag >> 16) & 0xFF), char((tag >> 8) & 0xFF), char(tag & 0xFF));
- _file->seek(size, SEEK_CUR);
- }
+ _fileStream->seek(oldPos);
+ }
+
+ _frameInfo[_header.numFrames] = 0x7FFFFFFF;
+}
+
+void VQADecoder::VQAVideoTrack::handleVQFR() {
+ uint32 size = _fileStream->readUint32BE();
+ int32 end = _fileStream->pos() + size - 8;
+ byte *inbuf;
+
+ while (_fileStream->pos() < end) {
+ uint32 tag = readTag(_fileStream);
+ uint32 i;
+ size = _fileStream->readUint32BE();
+
+ switch (tag) {
+ case MKTAG('C','B','F','0'): // Full codebook
+ _fileStream->read(_codeBook, size);
+ break;
+ case MKTAG('C','B','F','Z'): // Full codebook
+ inbuf = (byte *)malloc(size);
+ _fileStream->read(inbuf, size);
+ Screen::decodeFrame4(inbuf, _codeBook, _codeBookSize);
+ free(inbuf);
+ break;
+ case MKTAG('C','B','P','0'): // Partial codebook
+ _compressedCodeBook = false;
+ _fileStream->read(_partialCodeBook + _partialCodeBookSize, size);
+ _partialCodeBookSize += size;
+ _numPartialCodeBooks++;
+ break;
+ case MKTAG('C','B','P','Z'): // Partial codebook
+ _compressedCodeBook = true;
+ _fileStream->read(_partialCodeBook + _partialCodeBookSize, size);
+ _partialCodeBookSize += size;
+ _numPartialCodeBooks++;
+ break;
+ case MKTAG('C','P','L','0'): // Palette
+ assert(size <= 3 * 256);
+ _fileStream->read(_palette, size);
+ break;
+ case MKTAG('C','P','L','Z'): // Palette
+ inbuf = (byte *)malloc(size);
+ _fileStream->read(inbuf, size);
+ Screen::decodeFrame4(inbuf, _palette, 3 * 256);
+ free(inbuf);
+ break;
+ case MKTAG('V','P','T','0'): // Frame data
+ assert(size / 2 <= _numVectorPointers);
+ for (i = 0; i < size / 2; i++)
+ _vectorPointers[i] = _fileStream->readUint16LE();
+ break;
+ case MKTAG('V','P','T','Z'): // Frame data
+ inbuf = (byte *)malloc(size);
+ _fileStream->read(inbuf, size);
+ size = Screen::decodeFrame4(inbuf, (uint8 *)_vectorPointers, 2 * _numVectorPointers);
+ for (i = 0; i < size / 2; i++)
+ _vectorPointers[i] = TO_LE_16(_vectorPointers[i]);
+ free(inbuf);
+ break;
+ default:
+ warning("VQADecoder::VQAVideoTrack::handleVQFR(): Unknown `VQFR' sub-tag `%s'", tag2str(tag));
+ _fileStream->seek(size, SEEK_CUR);
+ break;
}
}
+}
- _vm->_mixer->playStream(Audio::Mixer::kSFXSoundType, &_sound, _stream);
- Common::EventManager *eventMan = _vm->getEventManager();
+// -----------------------------------------------------------------------
- for (uint i = 0; i < _header.numFrames; i++) {
- displayFrame(i);
+VQAMovie::VQAMovie(KyraEngine_v1 *vm, OSystem *system) {
+ _system = system;
+ _vm = vm;
+ _screen = _vm->screen();
+ _decoder = new VQADecoder();
+ _drawPage = -1;
+}
- // TODO: Implement frame skipping?
+VQAMovie::~VQAMovie() {
+ close();
+ delete _decoder;
+}
- while (1) {
- uint32 elapsedTime;
+void VQAMovie::setDrawPage(int page) {
+ _drawPage = page;
+}
- if (_vm->_mixer->isSoundHandleActive(_sound))
- elapsedTime = _vm->_mixer->getSoundElapsedTime(_sound);
- else
- elapsedTime = _system->getMillis() - startTick;
+bool VQAMovie::open(const char *filename) {
+ if (_file.open(filename)) {
+ return true;
+ }
+ return false;
+}
- if (elapsedTime >= (i * 1000) / _header.frameRate)
- break;
+void VQAMovie::close() {
+ if (_file.isOpen()) {
+ _file.close();
+ }
+}
+
+void VQAMovie::play() {
+ if (_decoder->loadStream(&_file)) {
+ Common::EventManager *eventMan = _vm->getEventManager();
+ int width = _decoder->getWidth();
+ int height = _decoder->getHeight();
+ int x = (Screen::SCREEN_W - width) / 2;
+ int y = (Screen::SCREEN_H - height) / 2;
+
+ _decoder->start();
+ // Note that decoding starts at frame -1. That's because there
+ // is usually sound data before the first frame, probably to
+ // avoid sound underflow.
+
+ while (!_decoder->endOfVideo()) {
Common::Event event;
while (eventMan->pollEvent(event)) {
switch (event.type) {
@@ -673,23 +641,28 @@ void VQAMovie::play() {
if (event.kbd.keycode == Common::KEYCODE_ESCAPE)
return;
break;
-
case Common::EVENT_RTL:
case Common::EVENT_QUIT:
return;
-
default:
break;
}
}
+ if (_decoder->needsUpdate()) {
+ const Graphics::Surface *surface = _decoder->decodeNextFrame();
+ if (_decoder->hasDirtyPalette()) {
+ memcpy(_screen->getPalette(0).getData(), _decoder->getPalette(), 3 * 256);
+ _screen->setScreenPalette(_screen->getPalette(0));
+ }
+
+ _screen->copyBlockToPage(_drawPage, surface->pitch, x, y, width, height, (const byte *)surface->getBasePtr(0, 0));
+ }
+
+ _screen->updateScreen();
_system->delayMillis(10);
}
-
- _screen->updateScreen();
}
-
- // TODO: Wait for the sound to finish?
}
} // End of namespace Kyra
diff --git a/engines/kyra/vqa.h b/engines/kyra/vqa.h
index 839bf5ac48..26dbc8d062 100644
--- a/engines/kyra/vqa.h
+++ b/engines/kyra/vqa.h
@@ -23,9 +23,9 @@
#ifndef KYRA_VQA_H
#define KYRA_VQA_H
-#include "common/scummsys.h"
-
-#include "audio/mixer.h"
+#include "video/video_decoder.h"
+#include "common/file.h"
+#include "common/rational.h"
class OSystem;
@@ -33,98 +33,125 @@ namespace Audio {
class QueuingAudioStream;
} // End of namespace Audio
-namespace Common {
-class SeekableReadStream;
-} // End of namespace Common
-
namespace Kyra {
class KyraEngine_v1;
class Screen;
+class VQADecoder : public Video::VideoDecoder {
+public:
+ VQADecoder();
+ virtual ~VQADecoder();
+
+ bool loadStream(Common::SeekableReadStream *stream);
+
+private:
+ class VQAAudioTrack : public AudioTrack {
+ public:
+ VQAAudioTrack(Common::SeekableReadStream *stream, int freq);
+ ~VQAAudioTrack();
+
+ void handleSND0();
+ void handleSND1();
+ void handleSND2();
+
+ protected:
+ Audio::AudioStream *getAudioStream() const;
+
+ private:
+ Audio::QueuingAudioStream *_audioStream;
+ Common::SeekableReadStream *_fileStream;
+ };
+
+ class VQAVideoTrack : public FixedRateVideoTrack {
+ public:
+ VQAVideoTrack(Common::SeekableReadStream *stream);
+ ~VQAVideoTrack();
+
+ uint16 getWidth() const;
+ uint16 getHeight() const;
+ Graphics::PixelFormat getPixelFormat() const;
+ int getCurFrame() const;
+ int getFrameCount() const;
+ const Graphics::Surface *decodeNextFrame();
+
+ bool hasSound() const;
+ int getAudioFreq() const;
+ bool hasDirtyPalette() const;
+ const byte *getPalette() const;
+
+ void setAudioTrack(VQAAudioTrack *audioTrack);
+
+ void handleVQHD();
+ void handleFINF();
+ void handleVQFR();
+
+ protected:
+ Common::Rational getFrameRate() const;
+
+ private:
+ Common::SeekableReadStream *_fileStream;
+ Graphics::Surface *_surface;
+ byte _palette[3 * 256];
+ mutable bool _dirtyPalette;
+ VQAAudioTrack *_audioTrack;
+
+ int _curFrame;
+
+ struct VQAHeader {
+ uint16 version;
+ uint16 flags;
+ uint16 numFrames;
+ uint16 width;
+ uint16 height;
+ uint8 blockW;
+ uint8 blockH;
+ uint8 frameRate;
+ uint8 cbParts;
+ uint16 colors;
+ uint16 maxBlocks;
+ uint32 unk1;
+ uint16 unk2;
+ uint16 freq;
+ uint8 channels;
+ uint8 bits;
+ uint32 unk3;
+ uint16 unk4;
+ uint32 maxCBFZSize;
+ uint32 unk5;
+ };
+
+ VQAHeader _header;
+ uint32 *_frameInfo;
+ uint32 _codeBookSize;
+ bool _compressedCodeBook;
+ byte *_codeBook;
+ int _partialCodeBookSize;
+ int _numPartialCodeBooks;
+ byte *_partialCodeBook;
+ uint32 _numVectorPointers;
+ uint16 *_vectorPointers;
+ };
+};
+
class VQAMovie {
public:
VQAMovie(KyraEngine_v1 *vm, OSystem *system);
~VQAMovie();
- bool opened() { return _opened; }
- int frames() { return _opened ? _header.numFrames : -1; }
-
- // It's unlikely that we ever want to change the movie position from
- // its default.
-
- void setDrawPage(int page) { _drawPage = page; }
+ void setDrawPage(int page);
bool open(const char *filename);
void close();
void play();
-
-protected:
+private:
OSystem *_system;
KyraEngine_v1 *_vm;
Screen *_screen;
+ VQADecoder *_decoder;
+ Common::File _file;
- bool _opened;
- int _x, _y;
int _drawPage;
-
- struct VQAHeader {
- uint16 version;
- uint16 flags;
- uint16 numFrames;
- uint16 width;
- uint16 height;
- uint8 blockW;
- uint8 blockH;
- uint8 frameRate;
- uint8 cbParts;
- uint16 colors;
- uint16 maxBlocks;
- uint32 unk1;
- uint16 unk2;
- uint16 freq;
- uint8 channels;
- uint8 bits;
- uint32 unk3;
- uint16 unk4;
- uint32 maxCBFZSize;
- uint32 unk5;
- };
-
- struct Buffer {
- uint8 *data;
- uint32 size;
- };
-
- Buffer _buffers[2];
-
- void initBuffers();
- void *allocBuffer(int num, uint32 size);
- void freeBuffers();
-
- void decodeSND1(byte *inbuf, uint32 insize, byte *outbuf, uint32 outsize);
-
- void displayFrame(uint frameNum);
-
- Common::SeekableReadStream *_file;
-
- VQAHeader _header;
- uint32 *_frameInfo;
- uint32 _codeBookSize;
- byte *_codeBook;
- byte *_partialCodeBook;
- bool _compressedCodeBook;
- int _partialCodeBookSize;
- int _numPartialCodeBooks;
- uint32 _numVectorPointers;
- uint16 *_vectorPointers;
-
- byte *_frame;
-
- Audio::QueuingAudioStream *_stream;
- Audio::SoundHandle _sound;
-
- uint32 readTag();
};
} // End of namespace Kyra