[Scummvm-cvs-logs] scummvm master -> 18ef3ed6b357bbe3ea35989b19ecac32fdf07892

lordhoto lordhoto at gmail.com
Sat Jan 18 03:19:35 CET 2014


This automated email contains information about 4 new commits which have been
pushed to the 'scummvm' repo located at https://github.com/scummvm/scummvm .

Summary:
19cb3499f5 KYRA: Rewrite the VQA decoder, using the VideoDecoder classes
238aa2be2a KYRA: Let the VQA decoder draw directly to the backend
16d36224e2 KYRA: Restructure the VQA decoder, as suggested by clone2727
18ef3ed6b3 KYRA: Make pointers to VQAHeader const


Commit: 19cb3499f587630f2429c3e99b4fcadf491836cb
    https://github.com/scummvm/scummvm/commit/19cb3499f587630f2429c3e99b4fcadf491836cb
Author: Torbjörn Andersson (eriktorbjorn at users.sourceforge.net)
Date: 2014-01-17T18:18:40-08:00

Commit Message:
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.

Changed paths:
    engines/kyra/screen.cpp
    engines/kyra/screen.h
    engines/kyra/vqa.cpp
    engines/kyra/vqa.h



diff --git a/engines/kyra/screen.cpp b/engines/kyra/screen.cpp
index 8c97e46..d172045 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 156b5b9..33bff4d 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 081d94a..76e5c72 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 839bf5a..26dbc8d 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


Commit: 238aa2be2aeefda17cb5c2023f89e3934bed8424
    https://github.com/scummvm/scummvm/commit/238aa2be2aeefda17cb5c2023f89e3934bed8424
Author: Torbjörn Andersson (eriktorbjorn at users.sourceforge.net)
Date: 2014-01-17T18:18:40-08:00

Commit Message:
KYRA: Let the VQA decoder draw directly to the backend

As an alternative to using the Screen class's functions, we can let
the VQA decoder draw directly to the backend. This won't work if the
game uses "hi-res mode", but I don't think that's ever the case for
Malcolm's Revenge. I believe the KyraEngine_MR::playVQA() function
ensures that the screen is properly updated after the movie has
finished.

This almost limits the VQA rewrite to vqa.cpp and vqa.h. Whether it's
better this way than changing the Screen functions to take a 'pitch'
parameter...? I don't know. But it's an alternative.

Changed paths:
    engines/kyra/kyra_mr.cpp
    engines/kyra/screen.cpp
    engines/kyra/screen.h
    engines/kyra/vqa.cpp
    engines/kyra/vqa.h



diff --git a/engines/kyra/kyra_mr.cpp b/engines/kyra/kyra_mr.cpp
index 48ba96e..a485ae4 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/screen.cpp b/engines/kyra/screen.cpp
index d172045..8c97e46 100644
--- a/engines/kyra/screen.cpp
+++ b/engines/kyra/screen.cpp
@@ -977,10 +977,6 @@ 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;
@@ -1010,7 +1006,7 @@ void Screen::copyBlockToPage(int pageNum, int pitch, int x, int y, int w, int h,
 	while (h--) {
 		memcpy(dst, src, w);
 		dst += SCREEN_W;
-		src += pitch;
+		src += w;
 	}
 }
 
diff --git a/engines/kyra/screen.h b/engines/kyra/screen.h
index 33bff4d..156b5b9 100644
--- a/engines/kyra/screen.h
+++ b/engines/kyra/screen.h
@@ -428,7 +428,6 @@ 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 76e5c72..975a763 100644
--- a/engines/kyra/vqa.cpp
+++ b/engines/kyra/vqa.cpp
@@ -38,6 +38,7 @@
 #include "common/system.h"
 #include "common/events.h"
 
+#include "graphics/palette.h"
 #include "graphics/surface.h"
 
 namespace Kyra {
@@ -594,7 +595,6 @@ VQAMovie::VQAMovie(KyraEngine_v1 *vm, OSystem *system) {
 	_vm = vm;
 	_screen = _vm->screen();
 	_decoder = new VQADecoder();
-	_drawPage = -1;
 }
 
 VQAMovie::~VQAMovie() {
@@ -602,10 +602,6 @@ VQAMovie::~VQAMovie() {
 	delete _decoder;
 }
 
-void VQAMovie::setDrawPage(int page) {
-	_drawPage = page;
-}
-
 bool VQAMovie::open(const char *filename) {
 	if (_file.open(filename)) {
 		return true;
@@ -652,14 +648,18 @@ void VQAMovie::play() {
 			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));
+					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);
 				}
 
-				_screen->copyBlockToPage(_drawPage, surface->pitch, x, y, width, height, (const byte *)surface->getBasePtr(0, 0));
+				_system->copyRectToScreen((const byte *)surface->getBasePtr(0, 0), surface->pitch, x, y, width, height);
 			}
 
-			_screen->updateScreen();
+			_system->updateScreen();
 			_system->delayMillis(10);
 		}
 	}
diff --git a/engines/kyra/vqa.h b/engines/kyra/vqa.h
index 26dbc8d..44d1354 100644
--- a/engines/kyra/vqa.h
+++ b/engines/kyra/vqa.h
@@ -139,8 +139,6 @@ public:
 	VQAMovie(KyraEngine_v1 *vm, OSystem *system);
 	~VQAMovie();
 
-	void setDrawPage(int page);
-
 	bool open(const char *filename);
 	void close();
 	void play();
@@ -150,8 +148,6 @@ private:
 	Screen *_screen;
 	VQADecoder *_decoder;
 	Common::File _file;
-
-	int _drawPage;
 };
 
 } // End of namespace Kyra


Commit: 16d36224e21bbd42349e6c64d525bc9c602162a5
    https://github.com/scummvm/scummvm/commit/16d36224e21bbd42349e6c64d525bc9c602162a5
Author: Torbjörn Andersson (eriktorbjorn at users.sourceforge.net)
Date: 2014-01-17T18:18:40-08:00

Commit Message:
KYRA: Restructure the VQA decoder, as suggested by clone2727

Untangled the audio and video track from each other, and the parsing
of the stream from the decoding of its data. Also fixed a memory leak
as it turns out deleting a Surface doesn't free its data. You have to
call free() in it.

I have only checked the intro, not every cutscene, but that seems to
work fine at least.

Changed paths:
    engines/kyra/vqa.cpp
    engines/kyra/vqa.h



diff --git a/engines/kyra/vqa.cpp b/engines/kyra/vqa.cpp
index 975a763..ab106d4 100644
--- a/engines/kyra/vqa.cpp
+++ b/engines/kyra/vqa.cpp
@@ -60,32 +60,32 @@ static uint32 readTag(Common::SeekableReadStream *stream) {
 }
 
 VQADecoder::VQADecoder() {
+	memset(&_header, 0, sizeof(_header));
 }
 
 VQADecoder::~VQADecoder() {
 	close();
+	delete[] _frameInfo;
 }
 
 bool VQADecoder::loadStream(Common::SeekableReadStream *stream) {
 	close();
+	_fileStream = stream;
 
-	if (stream->readUint32BE() != MKTAG('F','O','R','M')) {
+	if (_fileStream->readUint32BE() != MKTAG('F','O','R','M')) {
 		warning("VQADecoder::loadStream(): Cannot find `FORM' tag");
 		return false;
 	}
 
 	// Ignore the size of the FORM chunk. We're only interested in its
 	// children.
-	stream->readUint32BE();
+	_fileStream->readUint32BE();
 
-	if (stream->readUint32BE() != MKTAG('W','V','Q','A')) {
+	if (_fileStream->readUint32BE() != MKTAG('W','V','Q','A')) {
 		warning("VQADecoder::loadStream(): Cannot find `WVQA' tag");
 		return false;
 	}
 
-	VQAVideoTrack *videoTrack = new VQADecoder::VQAVideoTrack(stream);
-	addTrack(videoTrack);
-
 	// We want to find both a VQHD chunk containing the header, and a FINF
 	// chunk containing the frame offsets.
 
@@ -99,14 +99,13 @@ bool VQADecoder::loadStream(Common::SeekableReadStream *stream) {
 
 	while (!foundVQHD || !foundFINF) {
 		uint32 tag = readTag(stream);
-		uint32 size = stream->readUint32BE();
+		uint32 size = _fileStream->readUint32BE();
 
 		switch (tag) {
 		case MKTAG('V','Q','H','D'):
-			videoTrack->handleVQHD();
-			if (videoTrack->hasSound()) {
-				audioTrack = new VQAAudioTrack(stream, videoTrack->getAudioFreq());
-				videoTrack->setAudioTrack(audioTrack);
+			handleVQHD(_fileStream);
+			if (_header.flags & 1) {
+				audioTrack = new VQAAudioTrack(&_header);
 				addTrack(audioTrack);
 			}
 			foundVQHD = true;
@@ -120,12 +119,12 @@ bool VQADecoder::loadStream(Common::SeekableReadStream *stream) {
 				warning("VQADecoder::loadStream(): Expected size %d for `FINF' chunk, but got %u", 4 * getFrameCount(), size);
 				return false;
 			}
-			videoTrack->handleFINF();
+			handleFINF(_fileStream);
 			foundFINF = true;
 			break;
 		default:
 			warning("VQADecoder::loadStream(): Unknown tag `%s'", tag2str(tag));
-			stream->seek(size, SEEK_CUR);
+			_fileStream->seek(size, SEEK_CUR);
 			break;
 		}
 	}
@@ -133,11 +132,158 @@ bool VQADecoder::loadStream(Common::SeekableReadStream *stream) {
 	return true;
 }
 
+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;
+	}
+
+	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);
+	}
+}
+
+void VQADecoder::handleFINF(Common::SeekableReadStream *stream) {
+	for (int i = 0; i < _header.numFrames; i++) {
+		_frameInfo[i] = 2 * stream->readUint32LE();
+	}
+
+	// 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.
+
+	if (_frameInfo[0] & 0x01000000) {
+		uint32 oldPos = stream->pos();
+
+		while (1) {
+			uint32 scanTag = readTag(stream);
+			uint32 scanSize = stream->readUint32BE();
+
+			if (stream->eos())
+				break;
+
+			if (scanTag == MKTAG('V','Q','F','R')) {
+				_frameInfo[0] = (stream->pos() - 8) | 0x80000000;
+				break;
+			}
+
+			stream->seek(scanSize, SEEK_CUR);
+		}
+
+		stream->seek(oldPos);
+	}
+
+	_frameInfo[_header.numFrames] = 0x7FFFFFFF;
+}
+
+void  VQADecoder::readNextPacket() {
+	VQAVideoTrack *videoTrack = (VQAVideoTrack *)getTrack(0);
+	VQAAudioTrack *audioTrack = (VQAAudioTrack *)getTrack(1);
+
+	assert(videoTrack);
+
+	int curFrame = videoTrack->getCurFrame();
+
+	// 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);
+		if (_frameInfo[curFrame] & 0x80000000) {
+			videoTrack->setHasDirtyPalette();
+		}
+	}
+
+	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(_fileStream);
+			break;
+		case MKTAG('S','N','D','1'):	// Compressed sound, almost like AUD
+			assert(audioTrack);
+			audioTrack->handleSND1(_fileStream);
+			break;
+		case MKTAG('S','N','D','2'):	// Compressed sound
+			assert(audioTrack);
+			audioTrack->handleSND2(_fileStream);
+			break;
+		case MKTAG('V','Q','F','R'):
+			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;
+		}
+	}
+}
+
 // -----------------------------------------------------------------------
 
-VQADecoder::VQAAudioTrack::VQAAudioTrack(Common::SeekableReadStream *stream, int freq) {
-	_fileStream = stream;
-	_audioStream = Audio::makeQueuingAudioStream(freq, false);
+VQADecoder::VQAAudioTrack::VQAAudioTrack(VQAHeader *header) {
+	_audioStream = Audio::makeQueuingAudioStream(header->freq, false);
 }
 
 VQADecoder::VQAAudioTrack::~VQAAudioTrack() {
@@ -148,20 +294,20 @@ Audio::AudioStream *VQADecoder::VQAAudioTrack::getAudioStream() const {
 	return _audioStream;
 }
 
-void VQADecoder::VQAAudioTrack::handleSND0() {
-	uint32 size = _fileStream->readUint32BE();
+void VQADecoder::VQAAudioTrack::handleSND0(Common::SeekableReadStream *stream) {
+	uint32 size = stream->readUint32BE();
 	byte *buf = (byte *)malloc(size);
-	_fileStream->read(buf, size);
+	stream->read(buf, size);
 	_audioStream->queueBuffer(buf, size, DisposeAfterUse::YES, Audio::FLAG_UNSIGNED);
 }
 
-void VQADecoder::VQAAudioTrack::handleSND1() {
-	_fileStream->readUint32BE();
-	uint16 outsize = _fileStream->readUint16LE();
-	uint16 insize = _fileStream->readUint16LE();
+void VQADecoder::VQAAudioTrack::handleSND1(Common::SeekableReadStream *stream) {
+	stream->readUint32BE();
+	uint16 outsize = stream->readUint16LE();
+	uint16 insize = stream->readUint16LE();
 	byte *inbuf = (byte *)malloc(insize);
 
-	_fileStream->read(inbuf, insize);
+	stream->read(inbuf, insize);
 
 	if (insize == outsize) {
 		_audioStream->queueBuffer(inbuf, insize, DisposeAfterUse::YES, Audio::FLAG_UNSIGNED);
@@ -241,50 +387,61 @@ void VQADecoder::VQAAudioTrack::handleSND1() {
 	}
 }
 
-void VQADecoder::VQAAudioTrack::handleSND2() {
-	uint32 size = _fileStream->readUint32BE();
+void VQADecoder::VQAAudioTrack::handleSND2(Common::SeekableReadStream *stream) {
+	uint32 size = stream->readUint32BE();
 	warning("VQADecoder::VQAAudioTrack::handleSND2(): `SND2' is not implemented");
-	_fileStream->seek(size, SEEK_CUR);
+	stream->seek(size, SEEK_CUR);
 }
 
 // -----------------------------------------------------------------------
 
-VQADecoder::VQAVideoTrack::VQAVideoTrack(Common::SeekableReadStream *stream) {
-	_fileStream = stream;
-	_surface = new Graphics::Surface();
+VQADecoder::VQAVideoTrack::VQAVideoTrack(VQAHeader *header) {
 	memset(_palette, 0, sizeof(_palette));
 	_dirtyPalette = false;
-	_audioTrack = NULL;
+
+	_width = header->width;
+	_height = header->height;
+	_blockW = header->blockW;
+	_blockH = header->blockH;
+	_cbParts = header->cbParts;
+
+	_newFrame = false;
 
 	_curFrame = -1;
+	_frameCount = header->numFrames;
+	_frameRate = header->frameRate;
 
-	memset(&_header, 0, sizeof(_header));
-	_frameInfo = NULL;
-	_codeBookSize = 0;
+	_codeBookSize = 0xF00 * header->blockW * header->blockH;
 	_compressedCodeBook = false;
-	_codeBook = NULL;
+	_codeBook = new byte[_codeBookSize];
 	_partialCodeBookSize = 0;
 	_numPartialCodeBooks = 0;
-	_partialCodeBook = NULL;
-	_numVectorPointers = 0;
-	_vectorPointers = NULL;
+	_partialCodeBook = new byte[_codeBookSize];
+	_numVectorPointers = (header->width / header->blockW) * (header->height * header->blockH);
+	_vectorPointers = new uint16[_numVectorPointers];
+
+	memset(_codeBook, 0, _codeBookSize);
+	memset(_partialCodeBook, 0, _codeBookSize);
+	memset(_vectorPointers, 0, _numVectorPointers);
+
+	_surface = new Graphics::Surface();
+	_surface->create(header->width, header->height, Graphics::PixelFormat::createFormatCLUT8());
 }
 
 VQADecoder::VQAVideoTrack::~VQAVideoTrack() {
+	_surface->free();
 	delete _surface;
-	delete[] _frameInfo;
 	delete[] _codeBook;
 	delete[] _partialCodeBook;
 	delete[] _vectorPointers;
-	// The audio track gets deleted by VQADecoder.
 }
 
 uint16 VQADecoder::VQAVideoTrack::getWidth() const {
-	return _header.width;
+	return _width;
 }
 
 uint16 VQADecoder::VQAVideoTrack::getHeight() const {
-	return _header.height;
+	return _height;
 }
 
 Graphics::PixelFormat VQADecoder::VQAVideoTrack::getPixelFormat() const {
@@ -296,19 +453,15 @@ int VQADecoder::VQAVideoTrack::getCurFrame() const {
 }
 
 int VQADecoder::VQAVideoTrack::getFrameCount() const {
-	return _header.numFrames;
+	return _frameCount;
 }
 
 Common::Rational VQADecoder::VQAVideoTrack::getFrameRate() const {
-	return _header.frameRate;
-}
-
-bool VQADecoder::VQAVideoTrack::hasSound() const {
-	return (_header.flags & 1) != 0;
+	return _frameRate;
 }
 
-int VQADecoder::VQAVideoTrack::getAudioFreq() const {
-	return _header.freq;
+void VQADecoder::VQAVideoTrack::setHasDirtyPalette() {
+	_dirtyPalette = true;
 }
 
 bool VQADecoder::VQAVideoTrack::hasDirtyPalette() const {
@@ -320,93 +473,40 @@ const byte *VQADecoder::VQAVideoTrack::getPalette() const {
 	return _palette;
 }
 
-void VQADecoder::VQAVideoTrack::setAudioTrack(VQAAudioTrack *audioTrack) {
-	_audioTrack = audioTrack;
-}
-
 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);
-	}
-
-	bool hasFrame = false;
+	if (_newFrame) {
+		_newFrame = false;
 
-	while (!_fileStream->eos() && _fileStream->pos() < end) {
-		uint32 tag = readTag(_fileStream);
-		uint32 size;
+		int blockPitch = _width / _blockW;
 
-		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("VQADecoder::VQAVideoTrack::decodeNextFrame(): Unknown tag `%s'", tag2str(tag));
-			size = _fileStream->readUint32BE();
-			_fileStream->seek(size, SEEK_CUR);
-			break;
-		}
-	}
-
-	if (hasFrame) {
-		if (_frameInfo[_curFrame] & 0x80000000) {
-			_dirtyPalette = true;
-		}
-
-		int blockPitch = _header.width / _header.blockW;
-
-		for (int by = 0; by < _header.height / _header.blockH; by++) {
+		for (int by = 0; by < _height / _blockH; by++) {
 			for (int bx = 0; bx < blockPitch; bx++) {
-				byte *dst = (byte *)_surface->getBasePtr(bx * _header.blockW, by * _header.blockH);
+				byte *dst = (byte *)_surface->getBasePtr(bx * _blockW, by * _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;
+					for (i = 0; i < _blockH; i++) {
+						memset(dst, 255 - (val & 0xFF), _blockW);
+						dst += _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];
+					byte *src = &_codeBook[(val >> 3) * _blockW * _blockH];
 
-					for (i = 0; i < _header.blockH; i++) {
-						memcpy(dst, src, _header.blockW);
-						src += _header.blockW;
-						dst += _header.width;
+					for (i = 0; i < _blockH; i++) {
+						memcpy(dst, src, _blockW);
+						src += _blockW;
+						dst += _width;
 					}
 				}
 			}
 		}
 
-		if (_numPartialCodeBooks == _header.cbParts) {
+		if (_numPartialCodeBooks == _cbParts) {
 			if (_compressedCodeBook) {
 				Screen::decodeFrame4(_partialCodeBook, _codeBook, _codeBookSize);
 			} else {
@@ -421,160 +521,58 @@ const Graphics::Surface *VQADecoder::VQAVideoTrack::decodeNextFrame() {
 	return _surface;
 }
 
-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;
-	}
-
-	_frameInfo = new uint32[_header.numFrames + 1];
-
-	_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;
-
-	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);
-	}
-}
-
-void VQADecoder::VQAVideoTrack::handleFINF() {
-	for (int i = 0; i < _header.numFrames; i++) {
-		_frameInfo[i] = 2 * _fileStream->readUint32LE();
-	}
-
-	// 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.
-
-	if (_frameInfo[0] & 0x01000000) {
-		uint32 oldPos = _fileStream->pos();
-
-		while (1) {
-			uint32 scanTag = readTag(_fileStream);
-			uint32 scanSize = _fileStream->readUint32BE();
-
-			if (_fileStream->eos())
-				break;
-
-			if (scanTag == MKTAG('V','Q','F','R')) {
-				_frameInfo[0] = (_fileStream->pos() - 8) | 0x80000000;
-				break;
-			}
-
-			_fileStream->seek(scanSize, SEEK_CUR);
-		}
-
-		_fileStream->seek(oldPos);
-	}
-
-	_frameInfo[_header.numFrames] = 0x7FFFFFFF;
-}
-
-void VQADecoder::VQAVideoTrack::handleVQFR() {
-	uint32 size = _fileStream->readUint32BE();
-	int32 end = _fileStream->pos() + size - 8;
+void VQADecoder::VQAVideoTrack::handleVQFR(Common::SeekableReadStream *stream) {
+	uint32 size = stream->readUint32BE();
+	int32 end = stream->pos() + size - 8;
 	byte *inbuf;
 
-	while (_fileStream->pos() < end) {
-		uint32 tag = readTag(_fileStream);
+	_newFrame = true;
+
+	while (stream->pos() < end) {
+		uint32 tag = readTag(stream);
 		uint32 i;
-		size = _fileStream->readUint32BE();
+		size = stream->readUint32BE();
 		
 		switch (tag) {
 		case MKTAG('C','B','F','0'):	// Full codebook
-			_fileStream->read(_codeBook, size);
+			stream->read(_codeBook, size);
 			break;
 		case MKTAG('C','B','F','Z'):	// Full codebook
 			inbuf = (byte *)malloc(size);
-			_fileStream->read(inbuf, size);
+			stream->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);
+			stream->read(_partialCodeBook + _partialCodeBookSize, size);
 			_partialCodeBookSize += size;
 			_numPartialCodeBooks++;
 			break;
 		case MKTAG('C','B','P','Z'):	// Partial codebook
 			_compressedCodeBook = true;
-			_fileStream->read(_partialCodeBook + _partialCodeBookSize, size);
+			stream->read(_partialCodeBook + _partialCodeBookSize, size);
 			_partialCodeBookSize += size;
 			_numPartialCodeBooks++;
 			break;
 		case MKTAG('C','P','L','0'):	// Palette
 			assert(size <= 3 * 256);
-			_fileStream->read(_palette, size);
+			stream->read(_palette, size);
 			break;
 		case MKTAG('C','P','L','Z'):	// Palette
 			inbuf = (byte *)malloc(size);
-			_fileStream->read(inbuf, 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] = _fileStream->readUint16LE();
+				_vectorPointers[i] = stream->readUint16LE();
 			break;
 		case MKTAG('V','P','T','Z'):	// Frame data
 			inbuf = (byte *)malloc(size);
-			_fileStream->read(inbuf, 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]);
@@ -582,7 +580,7 @@ void VQADecoder::VQAVideoTrack::handleVQFR() {
 			break;
 		default:
 			warning("VQADecoder::VQAVideoTrack::handleVQFR(): Unknown `VQFR' sub-tag `%s'", tag2str(tag));
-			_fileStream->seek(size, SEEK_CUR);
+			stream->seek(size, SEEK_CUR);
 			break;
 		}
 	}
diff --git a/engines/kyra/vqa.h b/engines/kyra/vqa.h
index 44d1354..0241481 100644
--- a/engines/kyra/vqa.h
+++ b/engines/kyra/vqa.h
@@ -44,28 +44,59 @@ public:
 	virtual ~VQADecoder();
 
 	bool loadStream(Common::SeekableReadStream *stream);
+	void readNextPacket();
 
 private:
+	Common::SeekableReadStream *_fileStream;
+
+	void handleVQHD(Common::SeekableReadStream *stream);
+	void handleFINF(Common::SeekableReadStream *stream);
+
+	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;
+
 	class VQAAudioTrack : public AudioTrack {
 	public:
-		VQAAudioTrack(Common::SeekableReadStream *stream, int freq);
+		VQAAudioTrack(VQAHeader *header);
 		~VQAAudioTrack();
 
-		void handleSND0();
-		void handleSND1();
-		void handleSND2();
+		void handleSND0(Common::SeekableReadStream *stream);
+		void handleSND1(Common::SeekableReadStream *stream);
+		void handleSND2(Common::SeekableReadStream *stream);
 
 	protected:
 		Audio::AudioStream *getAudioStream() const;
 
 	private:
 		Audio::QueuingAudioStream *_audioStream;
-		Common::SeekableReadStream *_fileStream;
 	};
 
 	class VQAVideoTrack : public FixedRateVideoTrack {
 	public:
-		VQAVideoTrack(Common::SeekableReadStream *stream);
+		VQAVideoTrack(VQAHeader *header);
 		~VQAVideoTrack();
 
 		uint16 getWidth() const;
@@ -75,54 +106,29 @@ private:
 		int getFrameCount() const;
 		const Graphics::Surface *decodeNextFrame();
 
-		bool hasSound() const;
-		int getAudioFreq() const;
+		void setHasDirtyPalette();
 		bool hasDirtyPalette() const;
 		const byte *getPalette() const;
 
-		void setAudioTrack(VQAAudioTrack *audioTrack);
-
-		void handleVQHD();
-		void handleFINF();
-		void handleVQFR();
+		void handleVQFR(Common::SeekableReadStream *stream);
 
 	protected:
 		Common::Rational getFrameRate() const;
 
 	private:
-		Common::SeekableReadStream *_fileStream;
 		Graphics::Surface *_surface;
 		byte _palette[3 * 256];
 		mutable bool _dirtyPalette;
-		VQAAudioTrack *_audioTrack;
 
+		bool _newFrame;
+
+		uint16 _width, _height;
+		uint8 _blockW, _blockH;
+		uint8 _cbParts;
+		int _frameCount;
 		int _curFrame;
+		byte _frameRate;
 
-		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;


Commit: 18ef3ed6b357bbe3ea35989b19ecac32fdf07892
    https://github.com/scummvm/scummvm/commit/18ef3ed6b357bbe3ea35989b19ecac32fdf07892
Author: Torbjörn Andersson (eriktorbjorn at users.sourceforge.net)
Date: 2014-01-17T18:18:40-08:00

Commit Message:
KYRA: Make pointers to VQAHeader const

This is just to enforce the idea that VQADecoder owns the VQAHeader
and that the audio/video tracks are only allowed to look at it, not
change it.

Changed paths:
    engines/kyra/vqa.cpp
    engines/kyra/vqa.h



diff --git a/engines/kyra/vqa.cpp b/engines/kyra/vqa.cpp
index ab106d4..40da6f1 100644
--- a/engines/kyra/vqa.cpp
+++ b/engines/kyra/vqa.cpp
@@ -282,7 +282,7 @@ void  VQADecoder::readNextPacket() {
 
 // -----------------------------------------------------------------------
 
-VQADecoder::VQAAudioTrack::VQAAudioTrack(VQAHeader *header) {
+VQADecoder::VQAAudioTrack::VQAAudioTrack(const VQAHeader *header) {
 	_audioStream = Audio::makeQueuingAudioStream(header->freq, false);
 }
 
@@ -395,7 +395,7 @@ void VQADecoder::VQAAudioTrack::handleSND2(Common::SeekableReadStream *stream) {
 
 // -----------------------------------------------------------------------
 
-VQADecoder::VQAVideoTrack::VQAVideoTrack(VQAHeader *header) {
+VQADecoder::VQAVideoTrack::VQAVideoTrack(const VQAHeader *header) {
 	memset(_palette, 0, sizeof(_palette));
 	_dirtyPalette = false;
 
diff --git a/engines/kyra/vqa.h b/engines/kyra/vqa.h
index 0241481..f389010 100644
--- a/engines/kyra/vqa.h
+++ b/engines/kyra/vqa.h
@@ -80,7 +80,7 @@ private:
 
 	class VQAAudioTrack : public AudioTrack {
 	public:
-		VQAAudioTrack(VQAHeader *header);
+		VQAAudioTrack(const VQAHeader *header);
 		~VQAAudioTrack();
 
 		void handleSND0(Common::SeekableReadStream *stream);
@@ -96,7 +96,7 @@ private:
 
 	class VQAVideoTrack : public FixedRateVideoTrack {
 	public:
-		VQAVideoTrack(VQAHeader *header);
+		VQAVideoTrack(const VQAHeader *header);
 		~VQAVideoTrack();
 
 		uint16 getWidth() const;






More information about the Scummvm-git-logs mailing list