From 19cb3499f587630f2429c3e99b4fcadf491836cb Mon Sep 17 00:00:00 2001 From: Torbjörn Andersson Date: Sat, 18 Jan 2014 03:18:40 +0100 Subject: KYRA: Rewrite the VQA decoder, using the VideoDecoder classes There isn't really a lot of benefit to this, but I think it's nicer if all our video decoders at least try to use the same infrastructure. --- engines/kyra/screen.cpp | 6 +- engines/kyra/screen.h | 1 + engines/kyra/vqa.cpp | 1017 +++++++++++++++++++++++------------------------ engines/kyra/vqa.h | 179 +++++---- 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(curSample, 0, 255); - *outbuf++ = curSample; - - curSample += WSTable4Bit[code >> 4]; - curSample = CLIP(curSample, 0, 255); - *outbuf++ = curSample; - - outsize -= 2; - } - break; - case 0: - for (; count >= 0; count--) { - code = *inbuf++; - - curSample += WSTable2Bit[code & 0x03]; - curSample = CLIP(curSample, 0, 255); - *outbuf++ = curSample; - - curSample += WSTable2Bit[(code >> 2) & 0x03]; - curSample = CLIP(curSample, 0, 255); - *outbuf++ = curSample; - - curSample += WSTable2Bit[(code >> 4) & 0x03]; - curSample = CLIP(curSample, 0, 255); - *outbuf++ = curSample; - - curSample += WSTable2Bit[(code >> 6) & 0x03]; - curSample = CLIP(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(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(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 -- cgit v1.2.3