[Scummvm-cvs-logs] scummvm master -> d69ffaa0e4507c7c5761bd020dc3beac4d85a977

m-kiewitz m_kiewitz at users.sourceforge.net
Tue Nov 24 15:42:10 CET 2015


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

Summary:
d69ffaa0e4 ACCESS: movie player for Noctropolis+Synnergist


Commit: d69ffaa0e4507c7c5761bd020dc3beac4d85a977
    https://github.com/scummvm/scummvm/commit/d69ffaa0e4507c7c5761bd020dc3beac4d85a977
Author: Martin Kiewitz (m_kiewitz at users.sourceforge.net)
Date: 2015-11-24T15:42:17+01:00

Commit Message:
ACCESS: movie player for Noctropolis+Synnergist

accessible via debug command "playmovie"

Changed paths:
  A engines/access/video/movie_decoder.cpp
  A engines/access/video/movie_decoder.h
    engines/access/access.h
    engines/access/asurface.cpp
    engines/access/asurface.h
    engines/access/debugger.cpp
    engines/access/debugger.h
    engines/access/module.mk
    engines/access/screen.cpp
    engines/access/screen.h



diff --git a/engines/access/access.h b/engines/access/access.h
index 37b9fec..83e3130 100644
--- a/engines/access/access.h
+++ b/engines/access/access.h
@@ -313,6 +313,8 @@ public:
 
 	void SPRINTCHR(char c, int fontNum);
 	void PRINTCHR(Common::String msg, int fontNum);
+
+	bool playMovie(const Common::String &filename, const Common::Point &pos);
 };
 
 } // End of namespace Access
diff --git a/engines/access/asurface.cpp b/engines/access/asurface.cpp
index 5266908..abae6bf 100644
--- a/engines/access/asurface.cpp
+++ b/engines/access/asurface.cpp
@@ -258,7 +258,7 @@ void ASurface::transBlitFrom(ASurface &src) {
 	blitFrom(src);
 }
 
-void ASurface::blitFrom(Graphics::Surface &src) {
+void ASurface::blitFrom(const Graphics::Surface &src) {
 	assert(w >= src.w && h >= src.h);
 	for (int y = 0; y < src.h; ++y) {
 		const byte *srcP = (const byte *)src.getBasePtr(0, y);
diff --git a/engines/access/asurface.h b/engines/access/asurface.h
index 022e253..ce9928c 100644
--- a/engines/access/asurface.h
+++ b/engines/access/asurface.h
@@ -107,7 +107,7 @@ public:
 
 	virtual void transBlitFrom(ASurface &src);
 
-	virtual void blitFrom(Graphics::Surface &src);
+	virtual void blitFrom(const Graphics::Surface &src);
 
 	virtual void copyBuffer(Graphics::Surface *src);
 
diff --git a/engines/access/debugger.cpp b/engines/access/debugger.cpp
index 6cb2bb6..6016170 100644
--- a/engines/access/debugger.cpp
+++ b/engines/access/debugger.cpp
@@ -52,12 +52,23 @@ Debugger *Debugger::init(AccessEngine *vm) {
 	}
 }
 
+void Debugger::postEnter() {
+	if (!_playMovieFile.empty()) {
+		_vm->playMovie(_playMovieFile, Common::Point(0, 0));
+
+		_playMovieFile.clear();
+	}
+
+	_vm->pauseEngine(false);
+}
+
 /*------------------------------------------------------------------------*/
 
 Debugger::Debugger(AccessEngine *vm) : GUI::Debugger(), _vm(vm) {
 	registerCmd("continue", WRAP_METHOD(Debugger, cmdExit));
 	registerCmd("scene", WRAP_METHOD(Debugger, Cmd_LoadScene));
 	registerCmd("cheat", WRAP_METHOD(Debugger, Cmd_Cheat));
+	registerCmd("playmovie", WRAP_METHOD(Debugger, Cmd_PlayMovie));
 
 	switch (vm->getGameID()) {
 	case GType_Amazon:
@@ -133,6 +144,19 @@ bool Debugger::Cmd_Cheat(int argc, const char **argv) {
 	return true;
 }
 
+bool Debugger::Cmd_PlayMovie(int argc, const char **argv) {
+	if (argc != 2) {
+		debugPrintf("Format: playmovie <movie-file>\n");
+		return true;
+	}
+
+	// play gets postboned until debugger is closed
+	Common::String filename = argv[1];
+	_playMovieFile = filename;
+
+	return cmdExit(0, 0);
+}
+
 /*------------------------------------------------------------------------*/
 
 namespace Amazon {
diff --git a/engines/access/debugger.h b/engines/access/debugger.h
index f4d8df7..1c1e003 100644
--- a/engines/access/debugger.h
+++ b/engines/access/debugger.h
@@ -35,13 +35,16 @@ class AccessEngine;
 class Debugger : public GUI::Debugger {
 protected:
 	AccessEngine *_vm;
+	Common::String _playMovieFile;
 
 	bool Cmd_LoadScene(int argc, const char **argv);
 	bool Cmd_Cheat(int argc, const char **argv);
+	bool Cmd_PlayMovie(int argc, const char **argv);
 	Common::String *_sceneDescr;
 	int _sceneNumb;
 public:
 	static Debugger *init(AccessEngine *vm);
+	void postEnter();
 public:
 	Debugger(AccessEngine *vm);
 	virtual ~Debugger();
diff --git a/engines/access/module.mk b/engines/access/module.mk
index f7cf7f2..cccb603 100644
--- a/engines/access/module.mk
+++ b/engines/access/module.mk
@@ -21,6 +21,7 @@ MODULE_OBJS := \
 	scripts.o \
 	sound.o \
 	video.o \
+	video/movie_decoder.o \
 	amazon/amazon_game.o \
 	amazon/amazon_logic.o \
 	amazon/amazon_player.o \
diff --git a/engines/access/screen.cpp b/engines/access/screen.cpp
index 41f6194..364b0a7 100644
--- a/engines/access/screen.cpp
+++ b/engines/access/screen.cpp
@@ -296,7 +296,7 @@ void Screen::transBlitFrom(ASurface *src, const Common::Rect &bounds) {
 	ASurface::transBlitFrom(src, bounds);
 }
 
-void Screen::blitFrom(Graphics::Surface &src) {
+void Screen::blitFrom(const Graphics::Surface &src) {
 	addDirtyRect(Common::Rect(0, 0, src.w, src.h));
 	ASurface::blitFrom(src);
 }
diff --git a/engines/access/screen.h b/engines/access/screen.h
index 52485e5..5cb8547 100644
--- a/engines/access/screen.h
+++ b/engines/access/screen.h
@@ -98,7 +98,7 @@ public:
 
 	virtual void transBlitFrom(ASurface *src, const Common::Rect &bounds);
 
-	virtual void blitFrom(Graphics::Surface &src);
+	virtual void blitFrom(const Graphics::Surface &src);
 
 	virtual void copyBuffer(Graphics::Surface *src);
 
diff --git a/engines/access/video/movie_decoder.cpp b/engines/access/video/movie_decoder.cpp
new file mode 100644
index 0000000..78b7a33
--- /dev/null
+++ b/engines/access/video/movie_decoder.cpp
@@ -0,0 +1,739 @@
+/* ScummVM - Graphic Adventure Engine
+ *
+ * ScummVM is the legal property of its developers, whose names
+ * are too numerous to list here. Please refer to the COPYRIGHT
+ * file distributed with this source distribution.
+ *
+ * This program is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU General Public License
+ * as published by the Free Software Foundation; either version 2
+ * of the License, or (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program; if not, write to the Free Software
+ * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
+ *
+ */
+
+#include "common/scummsys.h"
+#include "common/stream.h"
+#include "common/textconsole.h"
+
+#include "audio/audiostream.h"
+#include "audio/decoders/raw.h"
+
+#include "access/access.h"
+#include "access/video/movie_decoder.h"
+
+// for Test-Code
+#include "common/system.h"
+#include "common/events.h"
+#include "common/keyboard.h"
+#include "engines/engine.h"
+#include "engines/util.h"
+#include "graphics/palette.h"
+#include "graphics/pixelformat.h"
+#include "graphics/surface.h"
+
+namespace Access {
+
+AccessVIDMovieDecoder::AccessVIDMovieDecoder()
+	: _stream(0), _videoTrack(0), _audioTrack(0) {
+	_streamSeekOffset = 0;
+	_streamVideoIndex = 0;
+	_streamAudioIndex = 0;
+}
+
+AccessVIDMovieDecoder::~AccessVIDMovieDecoder() {
+	close();
+}
+
+bool AccessVIDMovieDecoder::loadStream(Common::SeekableReadStream *stream) {
+	uint32 videoCodecTag   = 0;
+	uint32 videoHeight     = 0;
+	uint32 videoWidth      = 0;
+	uint16 regularDelay    = 0;
+	uint32 audioSampleRate = 0;
+
+	close();
+
+	_stream = stream;
+	_streamSeekOffset = 15; // offset of first chunk
+	_streamVideoIndex = 0;
+	_streamAudioIndex = 0;
+
+	// read header
+	//  ID [dword] "VID"
+	//  ?? [byte]
+	//  ?? [word]
+	//  width [word]
+	//  height [word]
+	//  regular delay between frames (60 per second) [word]
+	//  ?? [word]
+
+	videoCodecTag = _stream->readUint32BE();
+	if (videoCodecTag != MKTAG('V','I','D',0x00)) {
+		warning("AccessVIDMoviePlay: bad codec tag, not a video file?");
+		close();
+		return false;
+	}
+	_stream->skip(3);
+	videoWidth = _stream->readUint16LE();
+	videoHeight = _stream->readUint16LE();
+	regularDelay = _stream->readUint16LE();
+	_stream->skip(2);
+
+	if (!regularDelay) {
+		warning("AccessVIDMoviePlay: delay between frames is zero?");
+		close();
+		return false;
+	}
+
+	// create video track
+	_videoTrack = new StreamVideoTrack(videoWidth, videoHeight, regularDelay);
+	addTrack(_videoTrack);
+
+	//warning("width %d, height %d", videoWidth, videoHeight);
+
+	// Look through the first few packets
+	static const int maxPacketCheckCount = 10;
+
+	for (int i = 0; i < maxPacketCheckCount; i++) {
+		byte chunkId = _stream->readByte();
+
+		// Bail out if done
+		if (_stream->eos())
+			break;
+
+		// Bail also in case end of file chunk was found
+		if (chunkId == kVIDMovieChunkId_EndOfFile)
+			break;
+
+		uint32 chunkStartOffset = _stream->pos();
+		//warning("data chunk at %x", chunkStartOffset);
+
+		switch (chunkId) {
+		case kVIDMovieChunkId_FullFrame:
+		case kVIDMovieChunkId_FullFrameCompressed:
+		case kVIDMovieChunkId_PartialFrameCompressed:
+		case kVIDMovieChunkId_FullFrameCompressedFill: {
+			if (!_videoTrack->skipOverFrame(_stream, chunkId)) {
+				close();
+				return false;
+			}
+			break;
+		}
+
+		case kVIDMovieChunkId_Palette: {
+			if (!_videoTrack->skipOverPalette(_stream)) {
+				close();
+				return false;
+			}
+			break;
+		}
+
+		case kVIDMovieChunkId_AudioFirstChunk:
+		case kVIDMovieChunkId_Audio: {
+			// sync [word]
+			// sampling rate [byte]
+			// size of audio data [word]
+			// sample data [] (mono, 8-bit, unsigned)
+			//
+			// Only first chunk has sync + sampling rate
+			if (chunkId == kVIDMovieChunkId_AudioFirstChunk) {
+				byte soundblasterRate;
+
+				_stream->skip(2); // skip over sync
+				soundblasterRate = _stream->readByte();
+				audioSampleRate = 1000000 / (256 - soundblasterRate);
+
+				_audioTrack = new StreamAudioTrack(audioSampleRate);
+				addTrack(_audioTrack);
+
+				_stream->seek(chunkStartOffset); // seek back
+			}
+
+			if (!_audioTrack) {
+				warning("AccessVIDMoviePlay: regular audio chunk, before audio chunk w/ header");
+				close();
+				return false;
+			}
+			if (!_audioTrack->skipOverAudio(_stream, chunkId)) {
+				close();
+				return false;
+			}
+			break;
+		}
+
+		default:
+			warning("AccessVIDMoviePlay: Unknown chunk-id '%x' inside VID movie", chunkId);
+			close();
+			return false;
+		}
+
+		// Remember this chunk inside our cache
+		IndexCacheEntry indexCacheEntry;
+
+		indexCacheEntry.chunkId = chunkId;
+		indexCacheEntry.offset  = chunkStartOffset;
+
+		_indexCacheTable.push_back(indexCacheEntry);
+
+		// Got an audio chunk now? -> exit b/c we are done
+		if (audioSampleRate)
+			break;
+	}
+
+	// Remember offset of latest not-indexed-yet chunk
+	_streamSeekOffset = _stream->pos();
+
+	// If sample rate was found, create an audio track
+	if (audioSampleRate) {
+		_audioTrack = new StreamAudioTrack(audioSampleRate);
+		addTrack(_audioTrack);
+	}
+
+	// Rewind back to the beginning right to the first chunk
+	_stream->seek(15);
+
+	return true;
+}
+
+void AccessVIDMovieDecoder::close() {
+	Video::VideoDecoder::close();
+
+	delete _stream; _stream = 0;
+	_videoTrack = 0;
+
+	_indexCacheTable.clear();
+}
+
+// We try to at least decode 1 frame
+// and also try to get at least 0.5 seconds of audio queued up
+void AccessVIDMovieDecoder::readNextPacket() {
+	uint32 currentMovieTime = getTime();
+	uint32 wantedAudioQueued  = currentMovieTime + 500; // always try to be 0.500 seconds in front of movie time
+
+	uint32 streamIndex = 0;
+	IndexCacheEntry indexEntry;
+	bool currentlySeeking = false;
+
+	bool videoDone     = false;
+	bool audioDone     = false;
+
+	// Seek to smallest stream offset
+	if ((_streamVideoIndex <= _streamAudioIndex) || (!_audioTrack)) {
+		streamIndex = _streamVideoIndex;
+	} else {
+		streamIndex = _streamAudioIndex;
+	}
+
+	if (_audioTrack) {
+		if (wantedAudioQueued <= _audioTrack->getTotalAudioQueued()) {
+			// already got enough audio queued up
+			audioDone = true;
+		}
+	} else {
+		// no audio track, audio is always done
+		audioDone = true;
+	}
+
+	while (1) {
+		// Check, if stream-index is already cached
+		if (streamIndex < _indexCacheTable.size()) {
+			indexEntry.chunkId = _indexCacheTable[streamIndex].chunkId;
+			indexEntry.offset  = _indexCacheTable[streamIndex].offset;
+			currentlySeeking = false;
+
+		} else {
+			// read from file
+			_stream->seek(_streamSeekOffset);
+			indexEntry.chunkId = _stream->readByte();
+			indexEntry.offset  = _stream->pos();
+			currentlySeeking = true;
+
+			// and store that as well
+			_indexCacheTable.push_back(indexEntry);
+		}
+
+		// end of stream -> exit
+		if (_stream->eos())
+			break;
+
+		// end of file chunk -> exit
+		if (indexEntry.chunkId == kVIDMovieChunkId_EndOfFile)
+			break;
+
+//		warning("chunk %x", indexEntry.chunkId);
+
+		switch (indexEntry.chunkId) {
+		case kVIDMovieChunkId_FullFrame:
+		case kVIDMovieChunkId_FullFrameCompressed:
+		case kVIDMovieChunkId_PartialFrameCompressed:
+		case kVIDMovieChunkId_FullFrameCompressedFill: {
+			if ((_streamVideoIndex <= streamIndex) && (!videoDone)) {
+				// We are at an index, that is still relevant for video decoding
+				// and we are not done with video yet
+				if (!currentlySeeking) {
+					// seek to stream position in case we used the cache
+					_stream->seek(indexEntry.offset);
+				}
+				//warning("video decode chunk %x at %lx", indexEntry.chunkId, _stream->pos());
+				_videoTrack->decodeFrame(_stream, indexEntry.chunkId);
+				videoDone = true;
+				_streamVideoIndex = streamIndex + 1;
+			} else {
+				if (currentlySeeking) {
+					// currently seeking, so we have to skip the frame bytes manually
+					_videoTrack->skipOverFrame(_stream, indexEntry.chunkId);
+				}
+			}
+			break;
+		}
+
+		case kVIDMovieChunkId_Palette: {
+			if ((_streamVideoIndex <= streamIndex) && (!videoDone)) {
+				// We are at an index, that is still relevant for video decoding
+				// and we are not done with video yet
+				if (!currentlySeeking) {
+					// seek to stream position in case we used the cache
+					_stream->seek(indexEntry.offset);
+				}
+				_videoTrack->decodePalette(_stream);
+				_streamVideoIndex = streamIndex + 1;
+			} else {
+				if (currentlySeeking) {
+					// currently seeking, so we have to skip the frame bytes manually
+					_videoTrack->skipOverPalette(_stream);
+				}
+			}
+			break;
+		}
+
+		case kVIDMovieChunkId_AudioFirstChunk:
+		case kVIDMovieChunkId_Audio: {
+			if ((_streamAudioIndex <= streamIndex) && (!audioDone)) {
+				// We are at an index that is still relevant for audio decoding
+				if (!currentlySeeking) {
+					// seek to stream position in case we used the cache
+					_stream->seek(indexEntry.offset);
+				}
+				_audioTrack->queueAudio(_stream, indexEntry.chunkId);
+				_streamAudioIndex = streamIndex + 1;
+
+				if (wantedAudioQueued <= _audioTrack->getTotalAudioQueued()) {
+					// Got enough audio
+					audioDone = true;
+				}
+			} else {
+				if (!_audioTrack) {
+					error("AccessVIDMoviePlay: audio chunks found without audio track active");
+				}
+				if (currentlySeeking) {
+					// currently seeking, so we have to skip the audio bytes manually
+					_audioTrack->skipOverAudio(_stream, indexEntry.chunkId);
+				}
+			}
+			break;
+		}
+
+		default:
+			error("AccessVIDMoviePlay: Unknown chunk-id '%x' inside VID movie", indexEntry.chunkId);
+		}
+
+		if (currentlySeeking) {
+			// remember currently stream offset in case we are seeking
+			_streamSeekOffset = _stream->pos();
+		}
+
+		// go to next index
+		streamIndex++;
+
+		if ((videoDone) && (audioDone)) {
+			return;
+		}
+	}
+
+	if (!videoDone) {
+		// no more video frames? set end of video track
+		_videoTrack->setEndOfTrack();
+	}
+}
+
+AccessVIDMovieDecoder::StreamVideoTrack::StreamVideoTrack(uint32 width, uint32 height, uint16 regularFrameDelay) {
+	_width = width;
+	_height = height;
+	_regularFrameDelay = regularFrameDelay;
+	_curFrame = -1;
+	_nextFrameStartTime = 0;
+	_endOfTrack = false;
+
+	memset(&_palette, 0, sizeof(_palette));
+
+	_surface = new Graphics::Surface();
+	_surface->create(_width, _height, Graphics::PixelFormat::createFormatCLUT8());
+}
+
+AccessVIDMovieDecoder::StreamVideoTrack::~StreamVideoTrack() {
+	delete _surface;
+}
+
+bool AccessVIDMovieDecoder::StreamVideoTrack::endOfTrack() const {
+	return _endOfTrack;
+}
+
+Graphics::PixelFormat AccessVIDMovieDecoder::StreamVideoTrack::getPixelFormat() const {
+	return _surface->format;
+}
+
+void AccessVIDMovieDecoder::StreamVideoTrack::decodeFrame(Common::SeekableReadStream *stream, byte chunkId) {
+	byte *framePixelsPtr = (byte *)_surface->getPixels();
+	byte *pixelsPtr = framePixelsPtr;
+	byte rleByte = 0;
+	uint16 additionalDelay = 0;
+	int32 expectedPixels = 0;
+
+	switch (chunkId) {
+	case kVIDMovieChunkId_FullFrame: {
+		// Full frame is:
+		//  data [width * height]
+		additionalDelay = stream->readUint16LE();
+		stream->read(framePixelsPtr, _width * _height);
+		break;
+	}
+
+	case kVIDMovieChunkId_FullFrameCompressed:
+	case kVIDMovieChunkId_PartialFrameCompressed: {
+		// Skip manually over compressed data
+		// Full frame compressed is:
+		//  additional delay [word]
+		//  REPEAT:
+		//   RLE [byte]
+		//   RLE upper bit set: skip over RLE & 0x7F pixels
+		//   RLE upper bit not set: draw RLE amount of pixels (those pixels follow right after RLE byte)
+		//
+		// Partial frame compressed is:
+		//  sync [word]
+		//  horizontal start position [word]
+		//  REPEAT:
+		//   see full frame compressed
+		uint16 horizontalStartPosition = 0;
+
+		additionalDelay = stream->readUint16LE();
+
+		if (chunkId == kVIDMovieChunkId_PartialFrameCompressed) {
+			horizontalStartPosition = stream->readUint16LE();
+			if (horizontalStartPosition >= _height) {
+				error("AccessVIDMoviePlay: starting position larger than height during partial frame compressed, data corrupt?");
+				return;
+			}
+		}
+
+		byte rleByte = 0;
+		expectedPixels = _width * (_height - horizontalStartPosition);
+
+		// adjust frame destination pointer
+		pixelsPtr += (horizontalStartPosition * _width);
+
+		while (expectedPixels >= 0) {
+			rleByte = stream->readByte();
+			if (!rleByte) // NUL means end of stream
+				break;
+
+			if (rleByte & 0x80) {
+				rleByte = rleByte & 0x7F;
+				expectedPixels -= rleByte;
+			} else {
+				// skip over pixels
+				expectedPixels -= rleByte;
+				stream->read(pixelsPtr, rleByte); // read pixel data into frame
+			}
+			pixelsPtr += rleByte;
+		}
+		// expectedPixels may be positive here in case stream got terminated with a NUL
+		if (expectedPixels < 0) {
+			error("AccessVIDMoviePlay: pixel count mismatch during full/partial frame compressed, data corrupt?");
+		}
+		break;
+	}
+
+	case kVIDMovieChunkId_FullFrameCompressedFill: {
+		// Full frame compressed fill is:
+		//  additional delay [word]
+		//  REPEAT:
+		//   RLE [byte]
+		//   RLE upper bit set: draw RLE amount (& 0x7F) of pixels with specified color (color byte follows after RLE byte)
+		//   RLE upper bit not set: draw RLE amount of pixels (those pixels follow right after RLE byte)
+		additionalDelay = stream->readUint16LE();
+		expectedPixels = _width * _height;
+
+		while (expectedPixels > 0) {
+			rleByte = stream->readByte();
+
+			if (rleByte & 0x80) {
+				rleByte = rleByte & 0x7F;
+				expectedPixels -= rleByte;
+
+				byte fillColor = stream->readByte();
+				memset(pixelsPtr, fillColor, rleByte);
+			} else {
+				// skip over pixels
+				expectedPixels -= rleByte;
+				stream->read(pixelsPtr, rleByte); // read pixel data into frame
+			}
+			pixelsPtr += rleByte;
+		}
+		if (expectedPixels < 0) {
+			error("AccessVIDMoviePlay: pixel count mismatch during full frame compressed fill, data corrupt?");
+		}
+		break;
+	}
+	default:
+		assert(0);
+		break;
+	}
+
+	_curFrame++;
+
+	// TODO: not sure, if additionalDelay is supposed to affect the follow-up frame or the current frame
+	// the videos, that I found, don't have it set
+	uint32 currentFrameStartTime = getNextFrameStartTime();
+	uint32 nextFrameStartTime = (_regularFrameDelay * _curFrame) * 1000 / 60;
+	if (additionalDelay) {
+		nextFrameStartTime += additionalDelay * 1000 / 60;
+	}
+	assert(currentFrameStartTime <= nextFrameStartTime);
+	setNextFrameStartTime(nextFrameStartTime);
+}
+
+bool AccessVIDMovieDecoder::StreamVideoTrack::skipOverFrame(Common::SeekableReadStream *stream, byte chunkId) {
+	byte rleByte = 0;
+	int32 expectedPixels = 0;
+
+	switch (chunkId) {
+	case kVIDMovieChunkId_FullFrame: {
+		// Full frame is:
+		//  additional delay [word]
+		//  data [width * height]
+		stream->skip(2);
+		stream->skip(_width * _height);
+		break;
+	}
+
+	case kVIDMovieChunkId_FullFrameCompressed:
+	case kVIDMovieChunkId_PartialFrameCompressed: {
+		// Skip manually over compressed data
+		// Full frame compressed is:
+		//  additional delay [word]
+		//  REPEAT:
+		//   RLE [byte]
+		//   RLE upper bit set: skip over RLE & 0x7F pixels
+		//   RLE upper bit not set: draw RLE amount of pixels (those pixels follow right after RLE byte)
+		//
+		// Partial frame compressed is:
+		//  sync [word]
+		//  horizontal start position [word]
+		//  REPEAT:
+		//   see full frame compressed
+		uint16 horizontalStartPosition = 0;
+
+		stream->skip(2);
+
+		if (chunkId == kVIDMovieChunkId_PartialFrameCompressed) {
+			horizontalStartPosition = stream->readUint16LE();
+			if (horizontalStartPosition >= _height) {
+				warning("AccessVIDMoviePlay: starting position larger than height during partial frame compressed, data corrupt?");
+				return false;
+			}
+		}
+
+		byte rleByte = 0;
+		expectedPixels = _width * (_height - horizontalStartPosition);
+
+		while (expectedPixels >= 0) {
+			rleByte = stream->readByte();
+			if (!rleByte) // NUL means end of stream
+				break;
+
+			if (rleByte & 0x80) {
+				expectedPixels -= rleByte & 0x7F;
+			} else {
+				// skip over pixels
+				expectedPixels -= rleByte;
+				stream->skip(rleByte); // skip over pixel data
+			}
+		}
+		// expectedPixels may be positive here in case stream got terminated with a NUL
+		if (expectedPixels < 0) {
+			warning("AccessVIDMoviePlay: pixel count mismatch during full/partial frame compressed, data corrupt?");
+			return false;
+		}
+		break;
+	}
+
+	case kVIDMovieChunkId_FullFrameCompressedFill: {
+		// Full frame compressed fill is:
+		//  additional delay [word]
+		//  REPEAT:
+		//   RLE [byte]
+		//   RLE upper bit set: draw RLE amount (& 0x7F) of pixels with specified color (color byte follows after RLE byte)
+		//   RLE upper bit not set: draw RLE amount of pixels (those pixels follow right after RLE byte)
+		stream->skip(2);
+		expectedPixels = _width * _height;
+
+		while (expectedPixels > 0) {
+			rleByte = stream->readByte();
+
+			if (rleByte & 0x80) {
+				expectedPixels -= rleByte & 0x7F;
+				stream->skip(1);
+			} else {
+				// skip over pixels
+				expectedPixels -= rleByte;
+				stream->skip(rleByte); // skip over pixel data
+			}
+		}
+		if (expectedPixels < 0) {
+			warning("AccessVIDMoviePlay: pixel count mismatch during full frame compressed fill, data corrupt?");
+			return false;
+		}
+		break;
+	}
+	default:
+		assert(0);
+		break;
+	}
+	return true;
+}
+
+bool AccessVIDMovieDecoder::StreamVideoTrack::skipOverPalette(Common::SeekableReadStream *stream) {
+	stream->skip(0x300); // 3 bytes per color, 256 colors
+	return true;
+}
+
+void AccessVIDMovieDecoder::StreamVideoTrack::decodePalette(Common::SeekableReadStream *stream) {
+	assert(stream);
+
+	for (uint16 curColor = 0; curColor < 256; curColor++) {
+		_palette[curColor * 3] = stream->readByte();
+		_palette[curColor * 3 + 1] = stream->readByte();
+		_palette[curColor * 3 + 2] = stream->readByte();
+	}
+
+	_dirtyPalette = true;
+}
+
+const byte *AccessVIDMovieDecoder::StreamVideoTrack::getPalette() const {
+	_dirtyPalette = false;
+	return _palette;
+}
+
+bool AccessVIDMovieDecoder::StreamVideoTrack::hasDirtyPalette() const {
+	return _dirtyPalette;
+}
+
+AccessVIDMovieDecoder::StreamAudioTrack::StreamAudioTrack(uint32 sampleRate) {
+	_totalAudioQueued = 0; // currently 0 milliseconds queued
+
+	_sampleRate  = sampleRate;
+	_stereo = false; // always mono
+
+	_audioStream = Audio::makeQueuingAudioStream(sampleRate, _stereo);
+}
+
+AccessVIDMovieDecoder::StreamAudioTrack::~StreamAudioTrack() {
+	delete _audioStream;
+}
+
+void AccessVIDMovieDecoder::StreamAudioTrack::queueAudio(Common::SeekableReadStream *stream, byte chunkId) {
+	Common::SeekableReadStream *rawAudioStream = 0;
+	Audio::RewindableAudioStream *audioStream = 0;
+	uint32 audioLengthMSecs = 0;
+
+	if (chunkId == kVIDMovieChunkId_AudioFirstChunk) {
+		stream->skip(3); // skip over additional delay + sample rate
+	}
+
+	uint32 audioSize = stream->readUint16LE();
+
+	// Read the specified chunk into memory
+	rawAudioStream = stream->readStream(audioSize);
+	audioLengthMSecs = audioSize * 1000 / _sampleRate; // 1 byte == 1 8-bit sample
+
+	audioStream = Audio::makeRawStream(rawAudioStream, _sampleRate, Audio::FLAG_UNSIGNED | Audio::FLAG_LITTLE_ENDIAN, DisposeAfterUse::YES);
+	if (audioStream) {
+		_totalAudioQueued += audioLengthMSecs;
+		_audioStream->queueAudioStream(audioStream, DisposeAfterUse::YES);
+	} else {
+		// in case there was an error
+		delete rawAudioStream;
+	}
+}
+
+bool AccessVIDMovieDecoder::StreamAudioTrack::skipOverAudio(Common::SeekableReadStream *stream, byte chunkId) {
+	if (chunkId == kVIDMovieChunkId_AudioFirstChunk) {
+		stream->skip(3); // skip over additional delay + sample rate
+	}
+	uint32 audioSize = stream->readUint16LE();
+	stream->skip(audioSize);
+	return true;
+}
+
+Audio::AudioStream *AccessVIDMovieDecoder::StreamAudioTrack::getAudioStream() const {
+	return _audioStream;
+}
+
+bool AccessEngine::playMovie(const Common::String &filename, const Common::Point &pos) {
+	AccessVIDMovieDecoder *videoDecoder = new AccessVIDMovieDecoder();
+
+	Common::Point framePos(pos.x, pos.y);
+
+	if (!videoDecoder->loadFile(filename)) {
+		warning("AccessVIDMoviePlay: could not open '%s'", filename.c_str());
+		return false;
+	}
+
+	bool skipVideo = false;
+	uint16 width = videoDecoder->getWidth();
+	uint16 height = videoDecoder->getHeight();
+
+	_events->clearEvents();
+	videoDecoder->start();
+
+	while (!shouldQuit() && !videoDecoder->endOfVideo() && !skipVideo) {
+		if (videoDecoder->needsUpdate()) {
+			const Graphics::Surface *frame = videoDecoder->decodeNextFrame();
+
+			if (frame) {
+				_screen->blitFrom(*frame);
+
+				if (videoDecoder->hasDirtyPalette()) {
+					const byte *palette = videoDecoder->getPalette();
+					g_system->getPaletteManager()->setPalette(palette, 0, 256);
+				}
+
+				_screen->updateScreen();
+			}
+		}
+
+		_events->pollEventsAndWait();
+
+		Common::KeyState keyState;
+		if (_events->getKey(keyState)) {
+			if (keyState.keycode == Common::KEYCODE_ESCAPE)
+				skipVideo = true;
+		}
+	}
+
+	videoDecoder->close();
+	delete videoDecoder;
+
+	return !skipVideo;
+}
+
+} // End of namespace Access
diff --git a/engines/access/video/movie_decoder.h b/engines/access/video/movie_decoder.h
new file mode 100644
index 0000000..dc4a877
--- /dev/null
+++ b/engines/access/video/movie_decoder.h
@@ -0,0 +1,158 @@
+/* ScummVM - Graphic Adventure Engine
+ *
+ * ScummVM is the legal property of its developers, whose names
+ * are too numerous to list here. Please refer to the COPYRIGHT
+ * file distributed with this source distribution.
+ *
+ * This program is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU General Public License
+ * as published by the Free Software Foundation; either version 2
+ * of the License, or (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program; if not, write to the Free Software
+ * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
+ *
+ */
+
+#ifndef ACCESS_VIDEO_MOVIE_DECODER_H
+#define ACCESS_VIDEO_MOVIE_DECODER_H
+
+#include "common/rect.h"
+#include "video/video_decoder.h"
+#include "audio/decoders/raw.h"
+
+namespace Audio {
+class QueuingAudioStream;
+}
+
+namespace Common {
+class SeekableReadStream;
+}
+
+namespace Image {
+class Codec;
+}
+
+namespace Access {
+
+enum kDebugLevels {
+	kVIDMovieChunkId_FullFrame = 0x00,
+	kVIDMovieChunkId_FullFrameCompressed = 0x01,
+	kVIDMovieChunkId_Palette = 0x02,
+	kVIDMovieChunkId_FullFrameCompressedFill = 0x03,
+	kVIDMovieChunkId_PartialFrameCompressed = 0x04,
+	kVIDMovieChunkId_EndOfFile = 0x14,
+	kVIDMovieChunkId_AudioFirstChunk = 0x7C,
+	kVIDMovieChunkId_Audio = 0x7D
+};
+
+// This video format is used in at least the following Access engine games:
+//  - Noctropolis
+//  - Synnergist
+
+class AccessVIDMovieDecoder : public Video::VideoDecoder {
+public:
+	AccessVIDMovieDecoder();
+	~AccessVIDMovieDecoder();
+
+	bool loadStream(Common::SeekableReadStream *stream);
+	void close();
+
+protected:
+	void readNextPacket();
+
+private:
+	bool streamSkipFullFrameCompressedFill();
+
+private:
+	int32 _streamSeekOffset;  /* current stream offset, pointing to not-yet-indexed stream position */
+	uint32 _streamVideoIndex; /* current stream index for video decoding */
+	uint32 _streamAudioIndex; /* current stream index for audio decoding */
+
+	struct IndexCacheEntry {
+		byte   chunkId;
+		int32 offset;
+	};
+
+	Common::Array<IndexCacheEntry> _indexCacheTable;
+
+private:
+	class StreamVideoTrack : public VideoTrack  {
+	public:
+		StreamVideoTrack(uint32 width, uint32 height, uint16 regularFrameDelay);
+		~StreamVideoTrack();
+
+		bool endOfTrack() const;
+
+		uint16 getWidth() const { return _width; }
+		uint16 getHeight() const { return _height; }
+		Graphics::PixelFormat getPixelFormat() const;
+		int getCurFrame() const { return _curFrame; }
+		void setNextFrameStartTime(uint32 nextFrameStartTime) { _nextFrameStartTime = nextFrameStartTime; }
+		uint32 getNextFrameStartTime() const { return _nextFrameStartTime; }
+		const Graphics::Surface *decodeNextFrame() { return _surface; }
+
+		const byte *getPalette() const;
+		bool hasDirtyPalette() const;
+
+		void decodePalette(Common::SeekableReadStream *stream);
+		void decodeFrame(Common::SeekableReadStream *stream, byte chunkId);
+		bool skipOverFrame(Common::SeekableReadStream *stream, byte chunkId);
+		bool skipOverPalette(Common::SeekableReadStream *stream);
+
+		void setEndOfTrack() { _endOfTrack = true; }
+
+	private:
+		Graphics::Surface *_surface;
+
+		int _curFrame;
+		uint32 _nextFrameStartTime;
+
+		byte _palette[3 * 256];
+		mutable bool _dirtyPalette;
+		uint16 _width, _height;
+
+		uint16 _regularFrameDelay; // delay between frames (1 = 1/60 of a second)
+		bool _endOfTrack;
+	};
+
+	class StreamAudioTrack : public AudioTrack {
+	public:
+		StreamAudioTrack(uint32 sampleRate);
+		~StreamAudioTrack();
+
+		void queueAudio(Common::SeekableReadStream *stream, byte chunkId);
+		bool skipOverAudio(Common::SeekableReadStream *stream, byte chunkId);
+
+	protected:
+		Audio::AudioStream *getAudioStream() const;
+
+	private:
+		Audio::QueuingAudioStream *_audioStream;
+		uint32 _totalAudioQueued; /* total amount of milliseconds of audio, that we queued up already */
+
+	public:
+		uint32 getTotalAudioQueued() const { return _totalAudioQueued; }
+
+	private:
+		int16 decodeSample(uint8 dataNibble);
+
+		uint32 _codecTag;
+		uint16 _sampleRate;
+		bool   _stereo;
+	};
+
+	Common::SeekableReadStream *_stream;
+	StreamVideoTrack *_videoTrack;
+	StreamAudioTrack *_audioTrack;
+};
+
+} // End of namespace Access
+
+#endif






More information about the Scummvm-git-logs mailing list