diff options
Diffstat (limited to 'engines/kyra')
-rw-r--r-- | engines/kyra/kyra_mr.cpp | 1 | ||||
-rw-r--r-- | engines/kyra/staticres.cpp | 2 | ||||
-rw-r--r-- | engines/kyra/vqa.cpp | 1017 | ||||
-rw-r--r-- | engines/kyra/vqa.h | 139 |
4 files changed, 579 insertions, 580 deletions
diff --git a/engines/kyra/kyra_mr.cpp b/engines/kyra/kyra_mr.cpp index 48ba96ec8b..a485ae47e4 100644 --- a/engines/kyra/kyra_mr.cpp +++ b/engines/kyra/kyra_mr.cpp @@ -378,7 +378,6 @@ void KyraEngine_MR::playVQA(const char *name) { _screen->fadeToBlack(60); _screen->clearPage(0); - vqa.setDrawPage(0); vqa.play(); vqa.close(); diff --git a/engines/kyra/staticres.cpp b/engines/kyra/staticres.cpp index c52b0a04ad..522e12faa4 100644 --- a/engines/kyra/staticres.cpp +++ b/engines/kyra/staticres.cpp @@ -1172,7 +1172,7 @@ void GUI_LoK::initStaticResource() { GUI_V1_MENU_ITEM(_menu[5].item[2], 1, 0, 0, 0, 0xA5, 0, 0x40, 0x80, 0x0F, 252, 253, 5, 0, 248, 249, 250, -1, 0, 0x10, 0x42, 0, 0); GUI_V1_MENU_ITEM(_menu[5].item[3], 1, 0, 0, 0, 0xA5, 0, 0x51, 0x80, 0x0F, 252, 253, 5, 0, 248, 249, 250, -1, 0, 0x10, 0x53, 0, 0); GUI_V1_MENU_ITEM(_menu[5].item[4], 1, 0, 0, 0, 0xA5, 0, 0x62, 0x80, 0x0F, 252, 253, 5, 0, 248, 249, 250, -1, 0, 0x10, 0x65, 0, 0); - GUI_V1_MENU_ITEM(_menu[5].item[5], 1, 0, 0, 0, -1, 0, 0x7F, 0x6C, 0x0F, 252, 253, -1, 255, 248, 249, 250, -1, -0, 0, 0, 0, 0); + GUI_V1_MENU_ITEM(_menu[5].item[5], 1, 0, 0, 0, -1, 0, 0x7F, 0x6C, 0x0F, 252, 253, -1, 255, 248, 249, 250, -1, 0, 0, 0, 0, 0); _menu[5].item[0].callback = BUTTON_FUNCTOR(GUI_LoK, this, &GUI_LoK::controlsChangeMusic); _menu[5].item[1].callback = BUTTON_FUNCTOR(GUI_LoK, this, &GUI_LoK::controlsChangeSounds); _menu[5].item[2].callback = BUTTON_FUNCTOR(GUI_LoK, this, &GUI_LoK::controlsChangeWalk); diff --git a/engines/kyra/vqa.cpp b/engines/kyra/vqa.cpp index 081d94a050..d9564e1306 100644 --- a/engines/kyra/vqa.cpp +++ b/engines/kyra/vqa.cpp @@ -28,644 +28,606 @@ // // 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; - } - - assert(_buffers[num].data); +#include "common/system.h" +#include "common/events.h" - return _buffers[num].data; -} +#include "graphics/palette.h" +#include "graphics/surface.h" -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() { + memset(&_header, 0, sizeof(_header)); } -bool VQAMovie::open(const char *filename) { +VQADecoder::~VQADecoder() { close(); + delete[] _frameInfo; +} - _file = _vm->resource()->createReadStream(filename); - if (!_file) - return false; +bool VQADecoder::loadStream(Common::SeekableReadStream *stream) { + close(); + _fileStream = stream; - if (_file->readUint32BE() != MKTAG('F','O','R','M')) { - warning("VQAMovie::open: Cannot find `FORM' tag"); + if (_fileStream->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. + _fileStream->readUint32BE(); - if (_file->readUint32BE() != MKTAG('W','V','Q','A')) { - warning("WQAMovie::open: Cannot find `WVQA' tag"); + if (_fileStream->readUint32BE() != MKTAG('W','V','Q','A')) { + warning("VQADecoder::loadStream(): Cannot find `WVQA' tag"); return false; } - bool foundHeader = false; - bool foundFrameInfo = false; + // We want to find both a VQHD chunk containing the header, and a FINF + // chunk containing the frame offsets. + + bool foundVQHD = false; + bool foundFINF = false; + + VQAAudioTrack *audioTrack = NULL; // 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(); + while (!foundVQHD || !foundFINF) { + uint32 tag = readTag(stream); + uint32 size = _fileStream->readUint32BE(); 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; - } - - _x = (Screen::SCREEN_W - _header.width) / 2; - _y = (Screen::SCREEN_H - _header.height) / 2; - - _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)); - - _partialCodeBookSize = 0; - _numPartialCodeBooks = 0; - + case MKTAG('V','Q','H','D'): + handleVQHD(_fileStream); 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; + audioTrack = new VQAAudioTrack(&_header); + 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; } + handleFINF(_fileStream); + foundFINF = true; + break; + default: + warning("VQADecoder::loadStream(): Unknown tag `%s'", tag2str(tag)); + _fileStream->seek(size, SEEK_CUR); + break; + } + } - foundFrameInfo = true; + return true; +} - for (int i = 0; i < _header.numFrames; i++) { - _frameInfo[i] = 2 * _file->readUint32LE(); - } +void VQADecoder::handleVQHD(Common::SeekableReadStream *stream) { + _header.version = stream->readUint16LE(); + _header.flags = stream->readUint16LE(); + _header.numFrames = stream->readUint16LE(); + _header.width = stream->readUint16LE(); + _header.height = stream->readUint16LE(); + _header.blockW = stream->readByte(); + _header.blockH = stream->readByte(); + _header.frameRate = stream->readByte(); + _header.cbParts = stream->readByte(); + _header.colors = stream->readUint16LE(); + _header.maxBlocks = stream->readUint16LE(); + _header.unk1 = stream->readUint32LE(); + _header.unk2 = stream->readUint16LE(); + _header.freq = stream->readUint16LE(); + _header.channels = stream->readByte(); + _header.bits = stream->readByte(); + _header.unk3 = stream->readUint32LE(); + _header.unk4 = stream->readUint16LE(); + _header.maxCBFZSize = stream->readUint32LE(); + _header.unk5 = stream->readUint32LE(); + + _frameInfo = new uint32[_header.numFrames + 1]; + + VQAVideoTrack *videoTrack = new VQAVideoTrack(&_header); + addTrack(videoTrack); + + // 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; + } - // 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; - } + if (_header.flags & 1) { + // 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); + } +} - _file->seek(scanSize, SEEK_CUR); - } +void VQADecoder::handleFINF(Common::SeekableReadStream *stream) { + for (int i = 0; i < _header.numFrames; i++) { + _frameInfo[i] = 2 * stream->readUint32LE(); + } - _file->seek(oldPos); - } + // 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. - break; + if (_frameInfo[0] & 0x01000000) { + uint32 oldPos = stream->pos(); - 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); - } - } + while (1) { + uint32 scanTag = readTag(stream); + uint32 scanSize = stream->readUint32BE(); - initBuffers(); + if (stream->eos()) + break; - _opened = true; - return true; -} + if (scanTag == MKTAG('V','Q','F','R')) { + _frameInfo[0] = (stream->pos() - 8) | 0x80000000; + break; + } -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; + stream->seek(scanSize, SEEK_CUR); + } + + stream->seek(oldPos); } + + _frameInfo[_header.numFrames] = 0x7FFFFFFF; } -void VQAMovie::displayFrame(uint frameNum) { - if (frameNum >= _header.numFrames || !_opened) - return; +void VQADecoder::readNextPacket() { + VQAVideoTrack *videoTrack = (VQAVideoTrack *)getTrack(0); + VQAAudioTrack *audioTrack = (VQAAudioTrack *)getTrack(1); - bool foundSound = !_stream; - bool foundFrame = false; - uint i; + assert(videoTrack); - _file->seek(_frameInfo[frameNum] & 0x7FFFFFFF); + int curFrame = videoTrack->getCurFrame(); - while (!foundSound || !foundFrame) { - uint32 tag = readTag(); - uint32 size = _file->readUint32BE(); + // Stop if reading the tag is enough to put us ahead of the next frame + int32 end = (_frameInfo[curFrame + 1] & 0x7FFFFFFF) - 7; - if (_file->eos()) { - // This happens at the last frame. Apparently it has - // no sound? - break; + // 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); + if (_frameInfo[curFrame] & 0x80000000) { + videoTrack->setHasDirtyPalette(); } + } - byte *inbuf, *outbuf; - uint32 insize, outsize; - int32 end; + while (!_fileStream->eos() && _fileStream->pos() < end) { + uint32 tag = readTag(_fileStream); + uint32 size; 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); + assert(audioTrack); + audioTrack->handleSND0(_fileStream); break; - case MKTAG('S','N','D','1'): // Compressed sound, almost like AUD - foundSound = true; - outsize = _file->readUint16LE(); - insize = _file->readUint16LE(); - - inbuf = (byte *)malloc(insize); - _file->read(inbuf, insize); - - 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); - } + assert(audioTrack); + audioTrack->handleSND1(_fileStream); break; - case MKTAG('S','N','D','2'): // Compressed sound - foundSound = true; - warning("VQAMovie::displayFrame: `SND2' is not implemented"); - _file->seek(size, SEEK_CUR); + assert(audioTrack); + audioTrack->handleSND2(_fileStream); break; - case MKTAG('V','Q','F','R'): - foundFrame = true; - end = _file->pos() + size - 8; - - while (_file->pos() < end) { - tag = readTag(); - size = _file->readUint32BE(); - - switch (tag) { - case MKTAG('C','B','F','0'): // Full codebook - _file->read(_codeBook, size); - break; + videoTrack->handleVQFR(_fileStream); + 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("VQADecoder::readNextPacket(): Unknown tag `%s'", tag2str(tag)); + size = _fileStream->readUint32BE(); + _fileStream->seek(size, SEEK_CUR); + break; + } + } +} - case MKTAG('C','B','F','Z'): // Full codebook - inbuf = (byte *)allocBuffer(0, size); - _file->read(inbuf, size); - Screen::decodeFrame4(inbuf, _codeBook, _codeBookSize); - break; +// ----------------------------------------------------------------------- - case MKTAG('C','B','P','0'): // Partial codebook - _compressedCodeBook = false; - _file->read(_partialCodeBook + _partialCodeBookSize, size); - _partialCodeBookSize += size; - _numPartialCodeBooks++; - break; +VQADecoder::VQAAudioTrack::VQAAudioTrack(const VQAHeader *header) { + _audioStream = Audio::makeQueuingAudioStream(header->freq, false); +} - case MKTAG('C','B','P','Z'): // Partial codebook - _compressedCodeBook = true; - _file->read(_partialCodeBook + _partialCodeBookSize, size); - _partialCodeBookSize += size; - _numPartialCodeBooks++; - break; +VQADecoder::VQAAudioTrack::~VQAAudioTrack() { + delete _audioStream; +} - case MKTAG('C','P','L','0'): // Palette - assert(size <= 3 * 256); - _file->read(_screen->getPalette(0).getData(), size); - break; +Audio::AudioStream *VQADecoder::VQAAudioTrack::getAudioStream() const { + return _audioStream; +} - 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; +void VQADecoder::VQAAudioTrack::handleSND0(Common::SeekableReadStream *stream) { + uint32 size = stream->readUint32BE(); + byte *buf = (byte *)malloc(size); + stream->read(buf, size); + _audioStream->queueBuffer(buf, size, DisposeAfterUse::YES, Audio::FLAG_UNSIGNED); +} - case MKTAG('V','P','T','0'): // Frame data - assert(size / 2 <= _numVectorPointers); +void VQADecoder::VQAAudioTrack::handleSND1(Common::SeekableReadStream *stream) { + stream->readUint32BE(); + uint16 outsize = stream->readUint16LE(); + uint16 insize = stream->readUint16LE(); + byte *inbuf = (byte *)malloc(insize); + + stream->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++; - for (i = 0; i < size / 2; i++) - _vectorPointers[i] = _file->readUint16LE(); - break; + for (i = 0; i < 2; i++) { + curSample += WSTable4Bit[code & 0x0F]; + curSample = CLIP<int16>(curSample, 0, 255); + code >>= 4; + *out++ = curSample; + } - case MKTAG('V','P','T','Z'): // Frame data - inbuf = (byte *)allocBuffer(0, size); - outbuf = (byte *)allocBuffer(1, 2 * _numVectorPointers); + bytesLeft -= 2; + } + break; + case 0: + for (; count >= 0; count--) { + code = *in++; - _file->read(inbuf, size); - size = Screen::decodeFrame4(inbuf, outbuf, 2 * _numVectorPointers); + for (i = 0; i < 4; i++) { + curSample += WSTable2Bit[code & 0x03]; + curSample = CLIP<int16>(curSample, 0, 255); + code >>= 2; + *out++ = curSample; + } - assert(size / 2 <= _numVectorPointers); + bytesLeft -= 4; + } + break; + default: + for (; count >= 0; count--) { + *out++ = curSample; + bytesLeft--; + } + break; + } + } + _audioStream->queueBuffer(outbuf, outsize, DisposeAfterUse::YES, Audio::FLAG_UNSIGNED); + free(inbuf); + } +} - for (i = 0; i < size / 2; i++) - _vectorPointers[i] = READ_LE_UINT16(outbuf + 2 * i); - break; +void VQADecoder::VQAAudioTrack::handleSND2(Common::SeekableReadStream *stream) { + uint32 size = stream->readUint32BE(); + warning("VQADecoder::VQAAudioTrack::handleSND2(): `SND2' is not implemented"); + stream->seek(size, SEEK_CUR); +} - 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); - } +// ----------------------------------------------------------------------- - } +VQADecoder::VQAVideoTrack::VQAVideoTrack(const VQAHeader *header) { + memset(_palette, 0, sizeof(_palette)); + _dirtyPalette = false; - break; + _width = header->width; + _height = header->height; + _blockW = header->blockW; + _blockH = header->blockH; + _cbParts = header->cbParts; - 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); - } - } + _newFrame = false; - // The frame has been decoded + _curFrame = -1; + _frameCount = header->numFrames; + _frameRate = header->frameRate; - if (_frameInfo[frameNum] & 0x80000000) - _screen->setScreenPalette(_screen->getPalette(0)); + _codeBookSize = 0xF00 * header->blockW * header->blockH; + _compressedCodeBook = false; + _codeBook = new byte[_codeBookSize]; + _partialCodeBookSize = 0; + _numPartialCodeBooks = 0; + _partialCodeBook = new byte[_codeBookSize]; + _numVectorPointers = (header->width / header->blockW) * (header->height * header->blockH); + _vectorPointers = new uint16[_numVectorPointers]; - int blockPitch = _header.width / _header.blockW; + memset(_codeBook, 0, _codeBookSize); + memset(_partialCodeBook, 0, _codeBookSize); + memset(_vectorPointers, 0, _numVectorPointers); - 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]; + _surface = new Graphics::Surface(); + _surface->create(header->width, header->height, Graphics::PixelFormat::createFormatCLUT8()); +} - 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; - } - } - } - } +VQADecoder::VQAVideoTrack::~VQAVideoTrack() { + _surface->free(); + delete _surface; + delete[] _codeBook; + delete[] _partialCodeBook; + delete[] _vectorPointers; +} - if (_numPartialCodeBooks == _header.cbParts) { - if (_compressedCodeBook) { - Screen::decodeFrame4(_partialCodeBook, _codeBook, _codeBookSize); - } else { - memcpy(_codeBook, _partialCodeBook, _partialCodeBookSize); - } - _numPartialCodeBooks = 0; - _partialCodeBookSize = 0; - } +uint16 VQADecoder::VQAVideoTrack::getWidth() const { + return _width; +} - _screen->copyBlockToPage(_drawPage, _x, _y, _header.width, _header.height, _frame); +uint16 VQADecoder::VQAVideoTrack::getHeight() const { + return _height; } -void VQAMovie::play() { - uint32 startTick; +Graphics::PixelFormat VQADecoder::VQAVideoTrack::getPixelFormat() const { + return _surface->format; +} - if (!_opened) - return; +int VQADecoder::VQAVideoTrack::getCurFrame() const { + return _curFrame; +} - startTick = _system->getMillis(); +int VQADecoder::VQAVideoTrack::getFrameCount() const { + return _frameCount; +} - // 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. +Common::Rational VQADecoder::VQAVideoTrack::getFrameRate() const { + return _frameRate; +} - byte *inbuf, *outbuf; - uint32 insize, outsize; +void VQADecoder::VQAVideoTrack::setHasDirtyPalette() { + _dirtyPalette = true; +} - if (_stream) { - while ((uint)_file->pos() < (_frameInfo[0] & 0x7FFFFFFF)) { - uint32 tag = readTag(); - uint32 size = _file->readUint32BE(); +bool VQADecoder::VQAVideoTrack::hasDirtyPalette() const { + return _dirtyPalette; +} - if (_file->eos()) { - warning("VQAMovie::play: Unexpected EOF"); - break; - } +const byte *VQADecoder::VQAVideoTrack::getPalette() const { + _dirtyPalette = false; + return _palette; +} - 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; +const Graphics::Surface *VQADecoder::VQAVideoTrack::decodeNextFrame() { + if (_newFrame) { + _newFrame = false; - case MKTAG('S','N','D','1'): // Compressed sound - outsize = _file->readUint16LE(); - insize = _file->readUint16LE(); + int blockPitch = _width / _blockW; - inbuf = (byte *)malloc(insize); - _file->read(inbuf, insize); + for (int by = 0; by < _height / _blockH; by++) { + for (int bx = 0; bx < blockPitch; bx++) { + byte *dst = (byte *)_surface->getBasePtr(bx * _blockW, by * _blockH); + int val = _vectorPointers[by * blockPitch + bx]; + int i; - if (insize == outsize) { - _stream->queueBuffer(inbuf, insize, DisposeAfterUse::YES, Audio::FLAG_UNSIGNED); + if ((val & 0xFF00) == 0xFF00) { + // Solid color + for (i = 0; i < _blockH; i++) { + memset(dst, 255 - (val & 0xFF), _blockW); + dst += _width; + } } else { - outbuf = (byte *)malloc(outsize); - decodeSND1(inbuf, insize, outbuf, outsize); - _stream->queueBuffer(outbuf, outsize, DisposeAfterUse::YES, Audio::FLAG_UNSIGNED); - free(inbuf); + // 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) * _blockW * _blockH]; + + for (i = 0; i < _blockH; i++) { + memcpy(dst, src, _blockW); + src += _blockW; + dst += _width; + } } - break; + } + } - case MKTAG('S','N','D','2'): // Compressed sound - warning("VQAMovie::play: `SND2' is not implemented"); - _file->seek(size, SEEK_CUR); - break; + if (_numPartialCodeBooks == _cbParts) { + if (_compressedCodeBook) { + Screen::decodeFrame4(_partialCodeBook, _codeBook, _codeBookSize); + } else { + memcpy(_codeBook, _partialCodeBook, _partialCodeBookSize); + } + _numPartialCodeBooks = 0; + _partialCodeBookSize = 0; + } + } - case MKTAG('C','M','D','S'): // Unused tag, always empty in kyra3 - _file->seek(size, SEEK_CUR); - break; + _curFrame++; + return _surface; +} - 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); - } +void VQADecoder::VQAVideoTrack::handleVQFR(Common::SeekableReadStream *stream) { + uint32 size = stream->readUint32BE(); + int32 end = stream->pos() + size - 8; + byte *inbuf; + + _newFrame = true; + + while (stream->pos() < end) { + uint32 tag = readTag(stream); + uint32 i; + size = stream->readUint32BE(); + + switch (tag) { + case MKTAG('C','B','F','0'): // Full codebook + stream->read(_codeBook, size); + break; + case MKTAG('C','B','F','Z'): // Full codebook + inbuf = (byte *)malloc(size); + stream->read(inbuf, size); + Screen::decodeFrame4(inbuf, _codeBook, _codeBookSize); + free(inbuf); + break; + case MKTAG('C','B','P','0'): // Partial codebook + _compressedCodeBook = false; + stream->read(_partialCodeBook + _partialCodeBookSize, size); + _partialCodeBookSize += size; + _numPartialCodeBooks++; + break; + case MKTAG('C','B','P','Z'): // Partial codebook + _compressedCodeBook = true; + stream->read(_partialCodeBook + _partialCodeBookSize, size); + _partialCodeBookSize += size; + _numPartialCodeBooks++; + break; + case MKTAG('C','P','L','0'): // Palette + assert(size <= 3 * 256); + stream->read(_palette, size); + break; + case MKTAG('C','P','L','Z'): // Palette + inbuf = (byte *)malloc(size); + stream->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] = stream->readUint16LE(); + break; + case MKTAG('V','P','T','Z'): // Frame data + inbuf = (byte *)malloc(size); + stream->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)); + stream->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(); +} - // TODO: Implement frame skipping? +VQAMovie::~VQAMovie() { + close(); + delete _decoder; +} - while (1) { - uint32 elapsedTime; +bool VQAMovie::open(const char *filename) { + if (_file.open(filename)) { + return true; + } + return false; +} + +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; - if (_vm->_mixer->isSoundHandleActive(_sound)) - elapsedTime = _vm->_mixer->getSoundElapsedTime(_sound); - else - elapsedTime = _system->getMillis() - startTick; + _decoder->start(); - if (elapsedTime >= (i * 1000) / _header.frameRate) - break; + // 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 +635,32 @@ 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()) { + const byte *decoderPalette = _decoder->getPalette(); + byte systemPalette[256 * 3]; + for (int i = 0; i < ARRAYSIZE(systemPalette); i++) { + systemPalette[i] = (decoderPalette[i] * 0xFF) / 0x3F; + } + _system->getPaletteManager()->setPalette(systemPalette, 0, 256); + } + + _system->copyRectToScreen((const byte *)surface->getBasePtr(0, 0), surface->pitch, x, y, width, height); + } + + _system->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..f3890107a8 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,40 +33,24 @@ namespace Audio { class QueuingAudioStream; } // End of namespace Audio -namespace Common { -class SeekableReadStream; -} // End of namespace Common - namespace Kyra { class KyraEngine_v1; class Screen; -class VQAMovie { +class VQADecoder : public Video::VideoDecoder { public: - VQAMovie(KyraEngine_v1 *vm, OSystem *system); - ~VQAMovie(); + VQADecoder(); + virtual ~VQADecoder(); - bool opened() { return _opened; } - int frames() { return _opened ? _header.numFrames : -1; } + bool loadStream(Common::SeekableReadStream *stream); + void readNextPacket(); - // It's unlikely that we ever want to change the movie position from - // its default. +private: + Common::SeekableReadStream *_fileStream; - void setDrawPage(int page) { _drawPage = page; } - - bool open(const char *filename); - void close(); - void play(); - -protected: - OSystem *_system; - KyraEngine_v1 *_vm; - Screen *_screen; - - bool _opened; - int _x, _y; - int _drawPage; + void handleVQHD(Common::SeekableReadStream *stream); + void handleFINF(Common::SeekableReadStream *stream); struct VQAHeader { uint16 version; @@ -91,40 +75,85 @@ protected: uint32 unk5; }; - struct Buffer { - uint8 *data; - uint32 size; - }; - - Buffer _buffers[2]; - - void initBuffers(); - void *allocBuffer(int num, uint32 size); - void freeBuffers(); + VQAHeader _header; + uint32 *_frameInfo; - void decodeSND1(byte *inbuf, uint32 insize, byte *outbuf, uint32 outsize); + class VQAAudioTrack : public AudioTrack { + public: + VQAAudioTrack(const VQAHeader *header); + ~VQAAudioTrack(); - void displayFrame(uint frameNum); + void handleSND0(Common::SeekableReadStream *stream); + void handleSND1(Common::SeekableReadStream *stream); + void handleSND2(Common::SeekableReadStream *stream); - Common::SeekableReadStream *_file; + protected: + Audio::AudioStream *getAudioStream() const; - VQAHeader _header; - uint32 *_frameInfo; - uint32 _codeBookSize; - byte *_codeBook; - byte *_partialCodeBook; - bool _compressedCodeBook; - int _partialCodeBookSize; - int _numPartialCodeBooks; - uint32 _numVectorPointers; - uint16 *_vectorPointers; + private: + Audio::QueuingAudioStream *_audioStream; + }; - byte *_frame; + class VQAVideoTrack : public FixedRateVideoTrack { + public: + VQAVideoTrack(const VQAHeader *header); + ~VQAVideoTrack(); + + uint16 getWidth() const; + uint16 getHeight() const; + Graphics::PixelFormat getPixelFormat() const; + int getCurFrame() const; + int getFrameCount() const; + const Graphics::Surface *decodeNextFrame(); + + void setHasDirtyPalette(); + bool hasDirtyPalette() const; + const byte *getPalette() const; + + void handleVQFR(Common::SeekableReadStream *stream); + + protected: + Common::Rational getFrameRate() const; + + private: + Graphics::Surface *_surface; + byte _palette[3 * 256]; + mutable bool _dirtyPalette; + + bool _newFrame; + + uint16 _width, _height; + uint8 _blockW, _blockH; + uint8 _cbParts; + int _frameCount; + int _curFrame; + byte _frameRate; + + uint32 _codeBookSize; + bool _compressedCodeBook; + byte *_codeBook; + int _partialCodeBookSize; + int _numPartialCodeBooks; + byte *_partialCodeBook; + uint32 _numVectorPointers; + uint16 *_vectorPointers; + }; +}; - Audio::QueuingAudioStream *_stream; - Audio::SoundHandle _sound; +class VQAMovie { +public: + VQAMovie(KyraEngine_v1 *vm, OSystem *system); + ~VQAMovie(); - uint32 readTag(); + bool open(const char *filename); + void close(); + void play(); +private: + OSystem *_system; + KyraEngine_v1 *_vm; + Screen *_screen; + VQADecoder *_decoder; + Common::File _file; }; } // End of namespace Kyra |