aboutsummaryrefslogtreecommitdiff
path: root/engines/sherlock
diff options
context:
space:
mode:
Diffstat (limited to 'engines/sherlock')
-rw-r--r--engines/sherlock/debugger.cpp28
-rw-r--r--engines/sherlock/debugger.h17
-rw-r--r--engines/sherlock/scalpel/3do/movie_decoder.cpp487
-rw-r--r--engines/sherlock/scalpel/3do/movie_decoder.h111
4 files changed, 638 insertions, 5 deletions
diff --git a/engines/sherlock/debugger.cpp b/engines/sherlock/debugger.cpp
index cfbea2bc24..f66a39e1c7 100644
--- a/engines/sherlock/debugger.cpp
+++ b/engines/sherlock/debugger.cpp
@@ -23,11 +23,22 @@
#include "sherlock/debugger.h"
#include "sherlock/sherlock.h"
+#include "sherlock/scalpel/3do/movie_decoder.h"
+
namespace Sherlock {
Debugger::Debugger(SherlockEngine *vm) : GUI::Debugger(), _vm(vm) {
- registerCmd("continue", WRAP_METHOD(Debugger, cmdExit));
- registerCmd("scene", WRAP_METHOD(Debugger, cmdScene));
+ registerCmd("continue", WRAP_METHOD(Debugger, cmdExit));
+ registerCmd("scene", WRAP_METHOD(Debugger, cmdScene));
+ registerCmd("3do_playmovie", WRAP_METHOD(Debugger, cmd3DO_PlayMovie));
+}
+
+void Debugger::postEnter() {
+ if (!_3doPlayMovieFile.empty()) {
+ Scalpel3DOMoviePlay(_3doPlayMovieFile.c_str());
+
+ _3doPlayMovieFile.clear();
+ }
}
int Debugger::strToInt(const char *s) {
@@ -56,4 +67,17 @@ bool Debugger::cmdScene(int argc, const char **argv) {
}
}
+bool Debugger::cmd3DO_PlayMovie(int argc, const char **argv) {
+ if (argc != 2) {
+ debugPrintf("Format: 3do_playmovie <3do-movie-file>\n");
+ return true;
+ }
+
+ // play gets postboned until debugger is closed
+ Common::String filename = argv[1];
+ _3doPlayMovieFile = filename;
+
+ return cmdExit(0, 0);
+}
+
} // End of namespace Sherlock
diff --git a/engines/sherlock/debugger.h b/engines/sherlock/debugger.h
index e6a3aba828..c394c52cd5 100644
--- a/engines/sherlock/debugger.h
+++ b/engines/sherlock/debugger.h
@@ -31,6 +31,12 @@ namespace Sherlock {
class SherlockEngine;
class Debugger : public GUI::Debugger {
+public:
+ Debugger(SherlockEngine *vm);
+ virtual ~Debugger() {}
+
+ void postEnter();
+
private:
SherlockEngine *_vm;
@@ -43,9 +49,14 @@ private:
* Switch to another scene
*/
bool cmdScene(int argc, const char **argv);
-public:
- Debugger(SherlockEngine *vm);
- virtual ~Debugger() {}
+
+ /**
+ * Plays a 3DO movie
+ */
+ bool cmd3DO_PlayMovie(int argc, const char **argv);
+
+private:
+ Common::String _3doPlayMovieFile;
};
} // End of namespace Sherlock
diff --git a/engines/sherlock/scalpel/3do/movie_decoder.cpp b/engines/sherlock/scalpel/3do/movie_decoder.cpp
new file mode 100644
index 0000000000..855a2d9ecf
--- /dev/null
+++ b/engines/sherlock/scalpel/3do/movie_decoder.cpp
@@ -0,0 +1,487 @@
+/* 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.
+ *
+ * $URL$
+ * $Id$
+ *
+ */
+
+#include "common/scummsys.h"
+#include "common/stream.h"
+#include "common/textconsole.h"
+
+#include "audio/audiostream.h"
+#include "audio/decoders/raw.h"
+
+#include "sherlock/scalpel/3do/movie_decoder.h"
+#include "image/codecs/cinepak.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 Sherlock {
+
+Scalpel3DOMovieDecoder::Scalpel3DOMovieDecoder()
+ : _stream(0), _videoTrack(0), _audioTrack(0) {
+}
+
+Scalpel3DOMovieDecoder::~Scalpel3DOMovieDecoder() {
+ close();
+}
+
+bool Scalpel3DOMovieDecoder::loadStream(Common::SeekableReadStream *stream) {
+ bool videoHeaderFound = false;
+ uint32 videoSubType = 0;
+ uint32 videoTimeStamp = 0;
+ uint32 videoCodecTag = 0;
+ uint32 videoHeight = 0;
+ uint32 videoWidth = 0;
+ uint32 videoFrameRate = 0;
+ uint32 videoFrameCount = 0;
+ uint32 videoFrameNr = 0;
+ uint32 audioSubType = 0;
+ uint32 audioCodecTag = 0;
+ uint32 audioChannels = 0;
+ uint32 audioSampleRate = 0;
+
+ close();
+
+ _stream = stream;
+
+ // CTRL Header
+ if (_stream->readUint32BE() != MKTAG('C', 'T', 'R', 'L')) {
+ close();
+ return false;
+ }
+
+ uint32 ctrlSize = _stream->readUint32BE() - 8;
+ _stream->skip(ctrlSize);
+
+ // Look for packets that we care about
+ static const int maxPacketCheckCount = 10;
+ for (int i = 0; i < maxPacketCheckCount; i++) {
+ uint32 tag = _stream->readUint32BE();
+ uint32 chunkSize = _stream->readUint32BE() - 8;
+
+ // Bail out if done
+ if (_stream->eos())
+ break;
+
+ uint32 dataStartOffset = _stream->pos();
+
+ switch (tag) {
+ case MKTAG('F','I','L','M'): {
+ // See if this is a FILM header
+ videoTimeStamp = _stream->readUint32BE();
+ _stream->skip(4); // Unknown 0x00000000
+ videoSubType = _stream->readUint32BE();
+
+ switch (videoSubType) {
+ case MKTAG('F', 'H', 'D', 'R'):
+ // FILM header found
+ if (videoHeaderFound) {
+ warning("Sherlock 3DO movie: Multiple FILM headers found");
+ close();
+ return false;
+ }
+ _stream->readUint32BE();
+ videoCodecTag = _stream->readUint32BE();
+ videoHeight = _stream->readUint32BE();
+ videoWidth = _stream->readUint32BE();
+ _stream->skip(4); // time scale
+ videoFrameCount = _stream->readUint32BE();
+ videoHeaderFound = true;
+ break;
+
+ case MKTAG('F', 'R', 'M', 'E'):
+ if (!_videoTrack) {
+ if (!videoFrameNr) {
+ // first frame, we expect timestamp to be 0
+ assert(videoTimeStamp == 0);
+
+ } else {
+ // second frame found, we expect timestamp to be larger than 0
+ assert(videoTimeStamp);
+ // Framerate shouldn't be known at the moment
+ assert(videoFrameRate == 0);
+
+ if (!videoHeaderFound) {
+ error("Sherlock 3DO movie: no FILM header found before FILM frame");
+ }
+
+ // 3DO clock time for movies runs at 240Hz
+ videoFrameRate = 240 / videoTimeStamp;
+
+ _videoTrack = new StreamVideoTrack(videoWidth, videoHeight, videoCodecTag, videoFrameCount, videoFrameRate);
+ addTrack(_videoTrack);
+ }
+ videoFrameNr++;
+ }
+ break;
+
+ default:
+ error("Sherlock 3DO movie: Unknown subtype inside FILM packet");
+ break;
+ }
+ break;
+ }
+
+ case MKTAG('S','N','D','S'): {
+ _stream->skip(8);
+ audioSubType = _stream->readUint32BE();
+
+ switch (audioSubType) {
+ case MKTAG('S', 'H', 'D', 'R'):
+ // Audio header
+
+ // Bail if we already have a track
+ if (_audioTrack) {
+ warning("Sherlock 3DO movie: Multiple SNDS headers found");
+ close();
+ return false;
+ }
+
+ // OK, this is the start of a audio stream
+ _stream->readUint32BE(); // Version, always 0x00000000
+ _stream->readUint32BE(); // Unknown 0x00000008 ?!
+ _stream->readUint32BE(); // Unknown 0x00007500
+ _stream->readUint32BE(); // Unknown 0x00004000
+ _stream->readUint32BE(); // Unknown 0x00000000
+ _stream->readUint32BE(); // Unknown 0x00000010
+ audioSampleRate = _stream->readUint32BE();
+ audioChannels = _stream->readUint32BE();
+ audioCodecTag = _stream->readUint32BE();
+ _stream->readUint32BE(); // Unknown 0x00000004 compression ratio?
+ _stream->readUint32BE(); // Unknown 0x00000A2C
+
+ _audioTrack = new StreamAudioTrack(audioCodecTag, audioSampleRate, audioChannels);
+ addTrack(_audioTrack);
+ break;
+
+ case MKTAG('S', 'S', 'M', 'P'):
+ // Audio data
+ break;
+ default:
+ error("Sherlock 3DO movie: Unknown subtype inside FILM packet");
+ break;
+ }
+ break;
+ }
+
+ case MKTAG('C','T','R','L'):
+ case MKTAG('F','I','L','L'):
+ // Ignore but also accept CTRL + FILL packets
+ break;
+
+ default:
+ warning("Unknown header inside Sherlock 3DO movie");
+ close();
+ return false;
+ }
+
+ if ((_videoTrack) && (_audioTrack))
+ break;
+
+ // Seek to next chunk
+ _stream->seek(dataStartOffset + chunkSize);
+ }
+
+ // Bail if we didn't find video + audio
+ if ((!_videoTrack) || (!_audioTrack)) {
+ close();
+ return false;
+ }
+
+ // Rewind back to the beginning
+ _stream->seek(0);
+
+ return true;
+}
+
+void Scalpel3DOMovieDecoder::close() {
+ Video::VideoDecoder::close();
+
+ delete _stream; _stream = 0;
+ _videoTrack = 0;
+}
+
+void Scalpel3DOMovieDecoder::readNextPacket() {
+ uint32 videoSubType = 0;
+ uint32 videoFrameSize = 0;
+ uint32 audioSubType = 0;
+ uint32 audioSampleBytes = 0;
+ bool gotAudio = false;
+ bool gotVideo = false;
+
+ while (!endOfVideoTracks()) {
+ uint32 tag = _stream->readUint32BE();
+ uint32 chunkSize = _stream->readUint32BE() - 8;
+ uint32 dataStartOffset = _stream->pos();
+
+ //warning("offset %lx - tag %lx", dataStartOffset, tag);
+
+ if (_stream->eos())
+ break;
+
+ switch (tag) {
+ case MKTAG('F','I','L','M'):
+ _stream->readUint32BE(); // looks like frame * 16?
+ _stream->skip(4); // Unknown
+ videoSubType = _stream->readUint32BE();
+
+ switch (videoSubType) {
+ case MKTAG('F', 'H', 'D', 'R'):
+ // Ignore video header
+ break;
+
+ case MKTAG('F', 'R', 'M', 'E'):
+ // Have a frame!
+
+ // If we previously found one, this is just to get the time offset of the next one
+ /* uint32 frmeSize = */ _stream->readUint32BE();
+ videoFrameSize = _stream->readUint32BE();
+ _videoTrack->decodeFrame(_stream->readStream(videoFrameSize));
+ gotVideo = true;
+ break;
+
+ default:
+ error("Sherlock 3DO movie: Unknown subtype inside FILM packet");
+ break;
+ }
+ break;
+
+ case MKTAG('S','N','D','S'):
+ _stream->skip(8);
+ audioSubType = _stream->readUint32BE();
+
+ switch (audioSubType) {
+ case MKTAG('S', 'H', 'D', 'R'):
+ // Ignore the audio header
+ break;
+
+ case MKTAG('S', 'S', 'M', 'P'):
+ // Got audio chunk
+ audioSampleBytes = _stream->readUint32BE();
+ _audioTrack->queueAudio(_stream, audioSampleBytes);
+ gotAudio = true;
+ break;
+
+ default:
+ error("Sherlock 3DO movie: Unknown subtype inside SNDS packet");
+ break;
+ }
+ break;
+
+ case MKTAG('C','T','R','L'):
+ case MKTAG('F','I','L','L'):
+ // Ignore but also accept CTRL + FILL packets
+ break;
+
+ default:
+ error("Unknown header inside Sherlock 3DO movie");
+ }
+
+ // Always seek to end of chunk
+ // Sometimes not all of the chunk is filled with audio
+ _stream->seek(dataStartOffset + chunkSize);
+
+ if (gotVideo)
+ return;
+ }
+}
+
+Scalpel3DOMovieDecoder::StreamVideoTrack::StreamVideoTrack(uint32 width, uint32 height, uint32 codecTag, uint32 frameCount, uint32 frameRate) {
+ _width = width;
+ _height = height;
+ _frameCount = frameCount;
+ _frameRate = frameRate;
+ _curFrame = -1;
+
+ // Create the Cinepak decoder, if we're using it
+ if (codecTag == MKTAG('c', 'v', 'i', 'd'))
+ _codec = new Image::CinepakDecoder();
+ else
+ error("Unknown Sherlock 3DO stream codec tag '%s'", tag2str(codecTag));
+}
+
+Scalpel3DOMovieDecoder::StreamVideoTrack::~StreamVideoTrack() {
+ delete _codec;
+}
+
+Graphics::PixelFormat Scalpel3DOMovieDecoder::StreamVideoTrack::getPixelFormat() const {
+ return _codec->getPixelFormat();
+}
+
+void Scalpel3DOMovieDecoder::StreamVideoTrack::decodeFrame(Common::SeekableReadStream *stream) {
+ _surface = _codec->decodeFrame(*stream);
+ _curFrame++;
+}
+
+#define STREAMAUDIO_STEPSIZETABLE_MAX 88
+
+static int16 streamAudio_stepSizeTable[STREAMAUDIO_STEPSIZETABLE_MAX + 1] = {
+ 7, 8, 9, 10, 11, 12, 13, 14, 16, 17,
+ 19, 21, 23, 25, 28, 31, 34, 37, 41, 45,
+ 50, 55, 60, 66, 73, 80, 88, 97, 107, 118,
+ 130, 143, 157, 173, 190, 209, 230, 253, 279, 307,
+ 337, 371, 408, 449, 494, 544, 598, 658, 724, 796,
+ 876, 963, 1060, 1166, 1282, 1411, 1552, 1707, 1878, 2066,
+ 2272, 2499, 2749, 3024, 3327, 3660, 4026, 4428, 4871, 5358,
+ 5894, 6484, 7132, 7845, 8630, 9493, 10442, 11487, 12635, 13899,
+ 15289, 16818, 18500, 20350, 22385, 24623, 27086, 29794, 32767
+};
+
+static int16 streamAudio_stepSizeIndex[] = {
+ -1, -1, -1, -1, 2, 4, 6, 8, -1, -1, -1, -1, 2, 4, 6, 8
+};
+
+Scalpel3DOMovieDecoder::StreamAudioTrack::StreamAudioTrack(uint32 codecTag, uint32 sampleRate, uint32 channels) {
+ if (channels != 1) {
+ error("Sherlock 3DO stream audio is not mono");
+ }
+ if (codecTag != MKTAG('A','D','P','4')) {
+ error("Sherlock 3DO stream audio is not using codec ADP4");
+ }
+ _audioStream = Audio::makeQueuingAudioStream(sampleRate, false);
+
+ // reset ADPCM decoding
+ _lastSample = 0;
+ _stepIndex = 0;
+}
+
+Scalpel3DOMovieDecoder::StreamAudioTrack::~StreamAudioTrack() {
+ delete _audioStream;
+}
+
+int16 Scalpel3DOMovieDecoder::StreamAudioTrack::decodeSample(uint8 dataNibble) {
+ int16 currentStep = streamAudio_stepSizeTable[_stepIndex];
+ int32 decodedSample = _lastSample;
+ int16 delta = currentStep >> 3;
+
+ if (dataNibble & 1)
+ delta += currentStep >> 2;
+
+ if (dataNibble & 2)
+ delta += currentStep >> 1;
+
+ if (dataNibble & 4)
+ delta += currentStep;
+
+ if (dataNibble & 8) {
+ decodedSample -= delta;
+ } else {
+ decodedSample += delta;
+ }
+
+ _lastSample = CLIP<int32>(decodedSample, -32768, 32767);
+
+ _stepIndex += streamAudio_stepSizeIndex[dataNibble & 0x07];
+ _stepIndex = CLIP<int16>(_stepIndex, 0, STREAMAUDIO_STEPSIZETABLE_MAX);
+
+ return _lastSample;
+}
+
+void Scalpel3DOMovieDecoder::StreamAudioTrack::queueAudio(Common::SeekableReadStream *stream, uint32 length) {
+ uint32 decodedAudioSize = length * 4;
+ byte *decodedAudioBuffer = NULL;
+ uint8 streamByte = 0;
+ uint32 streamPos = 0;
+ uint32 audioPos = 0;
+
+ decodedAudioBuffer = (byte *)malloc(decodedAudioSize);
+ assert(decodedAudioBuffer);
+
+ for (streamPos = 0; streamPos < length; streamPos++) {
+ streamByte = stream->readByte();
+ WRITE_BE_UINT16(decodedAudioBuffer + audioPos, decodeSample(streamByte >> 4));
+ audioPos += 2;
+ WRITE_BE_UINT16(decodedAudioBuffer + audioPos, decodeSample(streamByte & 0x0F));
+ audioPos += 2;
+ }
+
+ // Now the audio is loaded, so let's queue it
+ _audioStream->queueBuffer(decodedAudioBuffer, decodedAudioSize, DisposeAfterUse::YES, Audio::FLAG_16BITS);
+}
+
+Audio::AudioStream *Scalpel3DOMovieDecoder::StreamAudioTrack::getAudioStream() const {
+ return _audioStream;
+}
+
+// Test-code
+
+// Code for showing a movie. Only meant for testing/debug purposes
+void Scalpel3DOMoviePlay(const char *filename) {
+ // HACK
+ initGraphics(320, 200, false, NULL);
+
+ Scalpel3DOMovieDecoder *videoDecoder = new Scalpel3DOMovieDecoder();
+
+ if (!videoDecoder->loadFile(filename)) {
+ // HACK
+ initGraphics(320, 200, false);
+ return;
+ }
+
+ bool skipVideo = false;
+ //byte bytesPerPixel = videoDecoder->getPixelFormat().bytesPerPixel;
+ uint16 width = videoDecoder->getWidth();
+ uint16 height = videoDecoder->getHeight();
+ //uint16 pitch = videoDecoder->getWidth() * bytesPerPixel;
+
+ videoDecoder->start();
+
+ while (!g_engine->shouldQuit() && !videoDecoder->endOfVideo() && (!skipVideo)) {
+ if (videoDecoder->needsUpdate()) {
+ const Graphics::Surface *frame = videoDecoder->decodeNextFrame();
+
+ if (frame) {
+ g_system->copyRectToScreen(frame->getPixels(), frame->pitch, 0, 0, width, height);
+
+ if (videoDecoder->hasDirtyPalette()) {
+ const byte *palette = videoDecoder->getPalette();
+ g_system->getPaletteManager()->setPalette(palette, 0, 255);
+ }
+
+ g_system->updateScreen();
+ }
+ }
+
+ Common::Event event;
+ while (g_system->getEventManager()->pollEvent(event)) {
+ if ((event.type == Common::EVENT_KEYDOWN && event.kbd.keycode == Common::KEYCODE_ESCAPE))
+ skipVideo = true;
+ }
+
+ g_system->delayMillis(10);
+ }
+
+ // HACK: switch back to 8bpp
+ initGraphics(320, 200, false);
+}
+
+
+} // End of namespace Sherlock
diff --git a/engines/sherlock/scalpel/3do/movie_decoder.h b/engines/sherlock/scalpel/3do/movie_decoder.h
new file mode 100644
index 0000000000..6ee41693eb
--- /dev/null
+++ b/engines/sherlock/scalpel/3do/movie_decoder.h
@@ -0,0 +1,111 @@
+/* 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 SHERLOCK_SCALPEL_3DO_MOVIE_DECODER_H
+#define SHERLOCK_SCALPEL_3DO_MOVIE_DECODER_H
+
+#include "video/video_decoder.h"
+
+namespace Audio {
+class QueuingAudioStream;
+}
+
+namespace Common {
+class SeekableReadStream;
+}
+
+namespace Image {
+class Codec;
+}
+
+namespace Sherlock {
+
+class Scalpel3DOMovieDecoder : public Video::VideoDecoder {
+public:
+ Scalpel3DOMovieDecoder();
+ ~Scalpel3DOMovieDecoder();
+
+ bool loadStream(Common::SeekableReadStream *stream);
+ void close();
+
+protected:
+ void readNextPacket();
+
+private:
+ class StreamVideoTrack : public FixedRateVideoTrack {
+ public:
+ StreamVideoTrack(uint32 width, uint32 height, uint32 codecTag, uint32 frameCount, uint32 frameRate);
+ ~StreamVideoTrack();
+
+ uint16 getWidth() const { return _width; }
+ uint16 getHeight() const { return _height; }
+ Graphics::PixelFormat getPixelFormat() const;
+ int getCurFrame() const { return _curFrame; }
+ int getFrameCount() const { return _frameCount; }
+ const Graphics::Surface *decodeNextFrame() { return _surface; }
+
+ void decodeFrame(Common::SeekableReadStream *stream);
+
+ protected:
+ Common::Rational getFrameRate() const { return _frameRate; }
+
+ private:
+ const Graphics::Surface *_surface;
+ uint32 _frameRate;
+
+ uint32 _frameCount;
+ Image::Codec *_codec;
+ uint16 _width, _height;
+ int _curFrame;
+ };
+
+ class StreamAudioTrack : public AudioTrack {
+ public:
+ StreamAudioTrack(uint32 codecTag, uint32 sampleRate, uint32 channels);
+ ~StreamAudioTrack();
+
+ void queueAudio(Common::SeekableReadStream *stream, uint32 length);
+
+ protected:
+ Audio::AudioStream *getAudioStream() const;
+
+ private:
+ Audio::QueuingAudioStream *_audioStream;
+
+ private:
+ int16 decodeSample(uint8 dataNibble);
+
+ int16 _lastSample;
+ int16 _stepIndex;
+ };
+
+ Common::SeekableReadStream *_stream;
+ StreamVideoTrack *_videoTrack;
+ StreamAudioTrack *_audioTrack;
+};
+
+// Testing
+extern void Scalpel3DOMoviePlay(const char *filename);
+
+} // End of namespace Sherlock
+
+#endif