aboutsummaryrefslogtreecommitdiff
path: root/engines/sci/video
diff options
context:
space:
mode:
Diffstat (limited to 'engines/sci/video')
-rw-r--r--engines/sci/video/robot_decoder.cpp309
-rw-r--r--engines/sci/video/robot_decoder.h104
2 files changed, 413 insertions, 0 deletions
diff --git a/engines/sci/video/robot_decoder.cpp b/engines/sci/video/robot_decoder.cpp
new file mode 100644
index 0000000000..f547407076
--- /dev/null
+++ b/engines/sci/video/robot_decoder.cpp
@@ -0,0 +1,309 @@
+/* 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/debug.h"
+#include "common/endian.h"
+#include "common/archive.h"
+#include "common/stream.h"
+#include "common/system.h"
+#include "common/util.h"
+
+#include "graphics/surface.h"
+#include "sound/decoders/raw.h"
+
+#include "sci/resource.h"
+#include "sci/util.h"
+#include "sci/sound/audio.h"
+#include "sci/video/robot_decoder.h"
+
+namespace Sci {
+
+// TODO:
+// - Positioning
+// - Proper handling of frame scaling - scaled frames look squashed
+// (probably because both dimensions should be scaled)
+// - Transparency support
+// - Timing - the arbitrary 100ms delay between each frame is not quite right
+// - Proper handling of sound chunks in some cases, so that the frame size
+// table can be ignored (it's only used to determine the correct sound chunk
+// size at the moment, cause it can be wrong in some cases)
+// - Fix audio "hiccups" - probably data that shouldn't be in the audio frames
+
+
+// Some non technical information on robot files, from an interview with
+// Greg Tomko-Pavia of Sierra On-Line
+// Taken from http://anthonylarme.tripod.com/phantas/phintgtp.html
+//
+// (...) What we needed was a way of playing video, but have it blend into
+// normal room art instead of occupying its own rectangular area. Room art
+// consists of a background pic overlaid with various animating cels
+// (traditional lingo: sprites). The cels each have a priority that determines
+// who is on top and who is behind in the drawing order. Cels are read from
+// *.v56 files (another proprietary format). A Robot is video frames with
+// transparent background including priority and x,y information. Thus, it is
+// like a cel, except it comes from an RBT - not a v56. Because it blends into
+// our graphics engine, it looks just like a part of the room. A RBT can move
+// around the screen and go behind other objects. (...)
+
+#ifdef ENABLE_SCI32
+
+RobotDecoder::RobotDecoder(Audio::Mixer *mixer, bool isBigEndian) {
+ _surface = 0;
+ _fileStream = 0;
+ _audioStream = 0;
+ _dirtyPalette = false;
+ _pos = Common::Point(0, 0);
+ _mixer = mixer;
+ _isBigEndian = isBigEndian;
+}
+
+RobotDecoder::~RobotDecoder() {
+ close();
+}
+
+bool RobotDecoder::load(GuiResourceId id) {
+ Common::String fileName = Common::String::format("%d.rbt", id);
+ Common::SeekableReadStream *stream = SearchMan.createReadStreamForMember(fileName);
+
+ if (!stream) {
+ warning("Unable to open robot file %s", fileName.c_str());
+ return false;
+ }
+
+ return load(stream);
+}
+
+bool RobotDecoder::load(Common::SeekableReadStream *stream) {
+ close();
+
+ _fileStream = new Common::SeekableSubReadStreamEndian(stream, 0, stream->size(), _isBigEndian, DisposeAfterUse::YES);
+ _surface = new Graphics::Surface();
+
+ readHeaderChunk();
+
+ // There are several versions of robot files, ranging from 3 to 6.
+ // v3: no known examples
+ // v4: PQ:SWAT demo
+ // v5: SCI2.1 and SCI3 games
+ // v6: SCI3 games
+ if (_header.version < 4 || _header.version > 6)
+ error("Unknown robot version: %d", _header.version);
+
+ if (_header.hasSound) {
+ _audioStream = Audio::makeQueuingAudioStream(11025, false);
+ _mixer->playStream(Audio::Mixer::kMusicSoundType, &_audioHandle, _audioStream);
+ }
+
+ readPaletteChunk();
+ readFrameSizesChunk();
+ return true;
+}
+
+void RobotDecoder::readHeaderChunk() {
+ // Header (60 bytes)
+ _fileStream->skip(6);
+ _header.version = _fileStream->readUint16();
+ _header.audioChunkSize = _fileStream->readUint16();
+ _header.audioSilenceSize = _fileStream->readUint16();
+ _fileStream->skip(2);
+ _header.frameCount = _fileStream->readUint16();
+ _header.paletteDataSize = _fileStream->readUint16();
+ _header.unkChunkDataSize = _fileStream->readUint16();
+ _fileStream->skip(5);
+ _header.hasSound = _fileStream->readByte();
+ _fileStream->skip(34);
+
+ // Some videos (e.g. robot 1305 in Phantasmagoria and
+ // robot 184 in Lighthouse) have an unknown chunk before
+ // the palette chunk (probably used for sound preloading).
+ // Skip it here.
+ if (_header.unkChunkDataSize)
+ _fileStream->skip(_header.unkChunkDataSize);
+}
+
+void RobotDecoder::readPaletteChunk() {
+ byte *paletteData = new byte[_header.paletteDataSize];
+ _fileStream->read(paletteData, _header.paletteDataSize);
+
+ // SCI1.1 palette
+ uint16 palColorStart = READ_SCI11ENDIAN_UINT16(paletteData + 25);
+ uint16 palColorCount = READ_SCI11ENDIAN_UINT16(paletteData + 29);
+
+ int palOffset = 37;
+ memset(_palette, 0, 256 * 3);
+
+ for (uint16 colorNo = palColorStart; colorNo < palColorStart + palColorCount; colorNo++) {
+ _palette[colorNo * 3 + 0] = paletteData[palOffset++];
+ _palette[colorNo * 3 + 1] = paletteData[palOffset++];
+ _palette[colorNo * 3 + 2] = paletteData[palOffset++];
+ }
+
+ _dirtyPalette = true;
+ delete[] paletteData;
+}
+
+
+void RobotDecoder::readFrameSizesChunk() {
+ // The robot video file contains 2 tables, with one entry for each frame:
+ // - A table containing the size of the image in each video frame
+ // - A table containing the total size of each video frame.
+ // In v5 robots, the tables contain 16-bit integers, whereas in v6 robots,
+ // they contain 32-bit integers.
+
+ _frameTotalSize = new uint32[_header.frameCount];
+
+ // TODO: The table reading code can probably be removed once the
+ // audio chunk size is figured out (check the TODO inside processNextFrame())
+#if 0
+ // We don't need any of the two tables to play the video, so we ignore
+ // both of them.
+ uint16 wordSize = _header.version == 6 ? 4 : 2;
+ _fileStream->skip(_header.frameCount * wordSize * 2);
+#else
+ switch (_header.version) {
+ case 4:
+ case 5: // sizes are 16-bit integers
+ // Skip table with frame image sizes, as we don't need it
+ _fileStream->skip(_header.frameCount * 2);
+ for (int i = 0; i < _header.frameCount; ++i)
+ _frameTotalSize[i] = _fileStream->readUint16();
+ break;
+ case 6: // sizes are 32-bit integers
+ // Skip table with frame image sizes, as we don't need it
+ _fileStream->skip(_header.frameCount * 4);
+ for (int i = 0; i < _header.frameCount; ++i)
+ _frameTotalSize[i] = _fileStream->readUint32();
+ break;
+ default:
+ error("Can't yet handle index table for robot version %d", _header.version);
+ }
+#endif
+
+ // 2 more unknown tables
+ _fileStream->skip(1024 + 512);
+
+ // Pad to nearest 2 kilobytes
+ uint32 curPos = _fileStream->pos();
+ if (curPos & 0x7ff)
+ _fileStream->seek((curPos & ~0x7ff) + 2048);
+}
+
+const Graphics::Surface *RobotDecoder::decodeNextFrame() {
+ // Read frame image header (24 bytes)
+ _fileStream->skip(3);
+ byte frameScale = _fileStream->readByte();
+ uint16 frameWidth = _fileStream->readUint16();
+ uint16 frameHeight = _fileStream->readUint16();
+ _fileStream->skip(8); // x, y, width and height of the frame
+ uint16 compressedSize = _fileStream->readUint16();
+ uint16 frameFragments = _fileStream->readUint16();
+ _fileStream->skip(4); // unknown
+
+ uint32 decompressedSize = frameWidth * frameHeight * frameScale / 100;
+ _surface->free();
+ _surface->create(frameWidth, frameHeight, 1);
+ _surface->w = frameWidth * frameScale / 100;
+
+ DecompressorLZS lzs;
+
+ if (_header.version == 4) {
+ // v4 has just the one fragment, it seems, and ignores the fragment count
+ Common::SeekableSubReadStream fragmentStream(_fileStream, _fileStream->pos(), _fileStream->pos() + compressedSize);
+ lzs.unpack(&fragmentStream, (byte *)_surface->pixels, compressedSize, decompressedSize);
+ } else {
+ byte *outPtr = (byte *)_surface->pixels;
+
+ for (uint16 i = 0; i < frameFragments; ++i) {
+ uint32 compressedFragmentSize = _fileStream->readUint32();
+ uint32 decompressedFragmentSize = _fileStream->readUint32();
+ uint16 compressionType = _fileStream->readUint16();
+
+ if (compressionType == 0) {
+ Common::SeekableSubReadStream fragmentStream(_fileStream, _fileStream->pos(), _fileStream->pos() + compressedFragmentSize);
+ lzs.unpack(&fragmentStream, outPtr, compressedFragmentSize, decompressedFragmentSize);
+ } else if (compressionType == 2) { // untested
+ _fileStream->read(outPtr, compressedFragmentSize);
+ } else {
+ error("Unknown frame compression found: %d", compressionType);
+ }
+
+ outPtr += decompressedFragmentSize;
+ }
+ }
+
+ // +1 because we start with frame number -1
+ uint32 audioChunkSize = _frameTotalSize[_curFrame + 1] - (24 + compressedSize);
+
+// TODO: The audio chunk size below is usually correct, but there are some
+// exceptions (e.g. robot 4902 in Phantasmagoria, towards its end)
+#if 0
+ // Read frame audio header (14 bytes)
+ _fileStream->skip(2); // buffer position
+ _fileStream->skip(2); // unknown (usually 1)
+ _fileStream->skip(2); /*uint16 audioChunkSize = _fileStream->readUint16() + 8;*/
+ _fileStream->skip(2);
+#endif
+
+ // Queue the next audio frame
+ // FIXME: For some reason, there are audio hiccups/gaps
+ if (_header.hasSound) {
+ _fileStream->skip(8); // header
+ _audioStream->queueBuffer(g_sci->_audio->getDecodedRobotAudioFrame(_fileStream, audioChunkSize - 8),
+ (audioChunkSize - 8) * 2, DisposeAfterUse::NO,
+ Audio::FLAG_16BITS | Audio::FLAG_LITTLE_ENDIAN);
+ } else {
+ _fileStream->skip(audioChunkSize);
+ }
+
+ if (_curFrame == -1)
+ _startTime = g_system->getMillis();
+
+ _curFrame++;
+
+ return _surface;
+}
+
+void RobotDecoder::close() {
+ if (!_fileStream)
+ return;
+
+ delete _fileStream;
+ _fileStream = 0;
+
+ _surface->free();
+ delete _surface;
+ _surface = 0;
+
+ if (_header.hasSound) {
+ _mixer->stopHandle(_audioHandle);
+ //delete _audioStream; _audioStream = 0;
+ }
+
+ reset();
+}
+
+#endif
+
+} // End of namespace Sci
diff --git a/engines/sci/video/robot_decoder.h b/engines/sci/video/robot_decoder.h
new file mode 100644
index 0000000000..aa68c50f5a
--- /dev/null
+++ b/engines/sci/video/robot_decoder.h
@@ -0,0 +1,104 @@
+/* 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$
+ *
+ */
+
+#ifndef SCI_VIDEO_ROBOT_DECODER_H
+#define SCI_VIDEO_ROBOT_DECODER_H
+
+#include "common/rational.h"
+#include "common/rect.h"
+#include "common/stream.h"
+#include "common/substream.h"
+#include "sound/audiostream.h"
+#include "sound/mixer.h"
+#include "video/video_decoder.h"
+
+namespace Sci {
+
+#ifdef ENABLE_SCI32
+
+struct RobotHeader {
+ // 6 bytes, identifier bytes
+ uint16 version;
+ uint16 audioChunkSize;
+ uint16 audioSilenceSize;
+ // 2 bytes, unknown
+ uint16 frameCount;
+ uint16 paletteDataSize;
+ uint16 unkChunkDataSize;
+ // 5 bytes, unknown
+ byte hasSound;
+ // 34 bytes, unknown
+};
+
+class RobotDecoder : public Video::FixedRateVideoDecoder {
+public:
+ RobotDecoder(Audio::Mixer *mixer, bool isBigEndian);
+ virtual ~RobotDecoder();
+
+ bool load(Common::SeekableReadStream *stream);
+ bool load(GuiResourceId id);
+ void close();
+
+ bool isVideoLoaded() const { return _fileStream != 0; }
+ uint16 getWidth() const { assert(_surface); return _surface->w; }
+ uint16 getHeight() const { assert(_surface); return _surface->h; }
+ uint16 getPitch() const { assert(_surface); return _surface->pitch; }
+ uint32 getFrameCount() const { return _header.frameCount; }
+ const Graphics::Surface *decodeNextFrame();
+ Graphics::PixelFormat getPixelFormat() const { return Graphics::PixelFormat::createFormatCLUT8(); }
+ const byte *getPalette() { _dirtyPalette = false; return _palette; }
+ bool hasDirtyPalette() const { return _dirtyPalette; }
+ void setPos(uint16 x, uint16 y) { _pos = Common::Point(x, y); }
+ Common::Point getPos() const { return _pos; }
+
+protected:
+ Common::Rational getFrameRate() const { return Common::Rational(60, 10); }
+
+private:
+ void readHeaderChunk();
+ void readPaletteChunk();
+ void readFrameSizesChunk();
+
+ void freeData();
+
+ RobotHeader _header;
+ Common::Point _pos;
+ bool _isBigEndian;
+
+ Common::SeekableSubReadStreamEndian *_fileStream;
+
+ uint32 *_frameTotalSize;
+ byte _palette[256 * 3];
+ bool _dirtyPalette;
+ Graphics::Surface *_surface;
+ Audio::QueuingAudioStream *_audioStream;
+ Audio::SoundHandle _audioHandle;
+ Audio::Mixer *_mixer;
+};
+#endif
+
+} // End of namespace Sci
+
+#endif