diff --git a/engines/sherlock/debugger.cpp b/engines/sherlock/debugger.cpp index cfbea2bc24d..f66a39e1c73 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 e6a3aba8285..c394c52cd5b 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 00000000000..855a2d9ecf9 --- /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(decodedSample, -32768, 32767); + + _stepIndex += streamAudio_stepSizeIndex[dataNibble & 0x07]; + _stepIndex = CLIP(_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 00000000000..6ee41693eb2 --- /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