aboutsummaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorMartin Kiewitz2015-11-24 15:42:17 +0100
committerMartin Kiewitz2015-11-24 15:42:17 +0100
commitd69ffaa0e4507c7c5761bd020dc3beac4d85a977 (patch)
treed1e9e60b93a448e670ff0271c83bf7e4c14d6c43
parent5137d0d3f928d0a8951f34dbe0e0108cf06b2e51 (diff)
downloadscummvm-rg350-d69ffaa0e4507c7c5761bd020dc3beac4d85a977.tar.gz
scummvm-rg350-d69ffaa0e4507c7c5761bd020dc3beac4d85a977.tar.bz2
scummvm-rg350-d69ffaa0e4507c7c5761bd020dc3beac4d85a977.zip
ACCESS: movie player for Noctropolis+Synnergist
accessible via debug command "playmovie"
-rw-r--r--engines/access/access.h2
-rw-r--r--engines/access/asurface.cpp2
-rw-r--r--engines/access/asurface.h2
-rw-r--r--engines/access/debugger.cpp24
-rw-r--r--engines/access/debugger.h3
-rw-r--r--engines/access/module.mk1
-rw-r--r--engines/access/screen.cpp2
-rw-r--r--engines/access/screen.h2
-rw-r--r--engines/access/video/movie_decoder.cpp739
-rw-r--r--engines/access/video/movie_decoder.h158
10 files changed, 931 insertions, 4 deletions
diff --git a/engines/access/access.h b/engines/access/access.h
index 37b9fec5a5..83e313083b 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 526690807a..abae6bf703 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 022e2534c1..ce9928c00e 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 6cb2bb606c..601617034f 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 f4d8df7634..1c1e00327c 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 f7cf7f2261..cccb603d31 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 41f6194238..364b0a7eef 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 52485e5c7c..5cb85471c6 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 0000000000..78b7a33fd0
--- /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 0000000000..dc4a87720c
--- /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