aboutsummaryrefslogtreecommitdiff
path: root/engines/titanic
diff options
context:
space:
mode:
Diffstat (limited to 'engines/titanic')
-rw-r--r--engines/titanic/module.mk1
-rw-r--r--engines/titanic/support/avi_decoder.cpp946
-rw-r--r--engines/titanic/support/avi_decoder.h285
-rw-r--r--engines/titanic/support/movie.cpp21
-rw-r--r--engines/titanic/support/movie.h6
5 files changed, 1250 insertions, 9 deletions
diff --git a/engines/titanic/module.mk b/engines/titanic/module.mk
index 441de8c1f3..a29d3df10f 100644
--- a/engines/titanic/module.mk
+++ b/engines/titanic/module.mk
@@ -422,6 +422,7 @@ MODULE_OBJS := \
star_control/star_control_sub13.o \
star_control/star_control_sub14.o \
star_control/star_control_sub15.o \
+ support/avi_decoder.o \
support/direct_draw.o \
support/direct_draw_surface.o \
support/files_manager.o \
diff --git a/engines/titanic/support/avi_decoder.cpp b/engines/titanic/support/avi_decoder.cpp
new file mode 100644
index 0000000000..81d8a58b8d
--- /dev/null
+++ b/engines/titanic/support/avi_decoder.cpp
@@ -0,0 +1,946 @@
+/* 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/stream.h"
+#include "common/system.h"
+#include "common/textconsole.h"
+
+#include "audio/audiostream.h"
+#include "audio/mixer.h"
+
+#include "titanic/support/avi_decoder.h"
+
+// Audio Codecs
+#include "audio/decoders/adpcm.h"
+#include "audio/decoders/mp3.h"
+#include "audio/decoders/raw.h"
+
+// Video Codecs
+#include "image/codecs/codec.h"
+
+namespace Titanic {
+
+#define UNKNOWN_HEADER(a) error("Unknown header found -- \'%s\'", tag2str(a))
+
+// IDs used throughout the AVI files
+// that will be handled by this player
+#define ID_RIFF MKTAG('R','I','F','F')
+#define ID_AVI MKTAG('A','V','I',' ')
+#define ID_LIST MKTAG('L','I','S','T')
+#define ID_HDRL MKTAG('h','d','r','l')
+#define ID_AVIH MKTAG('a','v','i','h')
+#define ID_STRL MKTAG('s','t','r','l')
+#define ID_STRH MKTAG('s','t','r','h')
+#define ID_VIDS MKTAG('v','i','d','s')
+#define ID_AUDS MKTAG('a','u','d','s')
+#define ID_MIDS MKTAG('m','i','d','s')
+#define ID_TXTS MKTAG('t','x','t','s')
+#define ID_JUNK MKTAG('J','U','N','K')
+#define ID_JUNQ MKTAG('J','U','N','Q')
+#define ID_DMLH MKTAG('d','m','l','h')
+#define ID_STRF MKTAG('s','t','r','f')
+#define ID_MOVI MKTAG('m','o','v','i')
+#define ID_REC MKTAG('r','e','c',' ')
+#define ID_VEDT MKTAG('v','e','d','t')
+#define ID_IDX1 MKTAG('i','d','x','1')
+#define ID_STRD MKTAG('s','t','r','d')
+#define ID_INFO MKTAG('I','N','F','O')
+#define ID_ISFT MKTAG('I','S','F','T')
+#define ID_DISP MKTAG('D','I','S','P')
+#define ID_PRMI MKTAG('P','R','M','I')
+#define ID_STRN MKTAG('s','t','r','n')
+
+// Stream Types
+enum {
+ kStreamTypePaletteChange = MKTAG16('p', 'c'),
+ kStreamTypeAudio = MKTAG16('w', 'b')
+};
+
+
+AVIDecoder::AVIDecoder(Audio::Mixer::SoundType soundType) : _frameRateOverride(0), _soundType(soundType) {
+ initCommon();
+}
+
+AVIDecoder::AVIDecoder(const Common::Rational &frameRateOverride, Audio::Mixer::SoundType soundType)
+ : _frameRateOverride(frameRateOverride), _soundType(soundType) {
+ initCommon();
+}
+
+AVIDecoder::~AVIDecoder() {
+ close();
+}
+
+AVIDecoder::AVIAudioTrack *AVIDecoder::createAudioTrack(AVIStreamHeader sHeader, PCMWaveFormat wvInfo) {
+ return new AVIAudioTrack(sHeader, wvInfo, _soundType);
+}
+
+void AVIDecoder::initCommon() {
+ _decodedHeader = false;
+ _foundMovieList = false;
+ _movieListStart = 0;
+ _movieListEnd = 0;
+ _fileStream = 0;
+ memset(&_header, 0, sizeof(_header));
+}
+
+bool AVIDecoder::isSeekable() const {
+ // Only videos with an index can seek
+ // Anyone else who wants to seek is crazy.
+ return isVideoLoaded() && !_indexEntries.empty();
+}
+
+bool AVIDecoder::parseNextChunk() {
+ uint32 tag = _fileStream->readUint32BE();
+ uint32 size = _fileStream->readUint32LE();
+
+ if (_fileStream->eos())
+ return false;
+
+ debug(3, "Decoding tag %s", tag2str(tag));
+
+ switch (tag) {
+ case ID_LIST:
+ handleList(size);
+ break;
+ case ID_AVIH:
+ _header.size = size;
+ _header.microSecondsPerFrame = _fileStream->readUint32LE();
+ _header.maxBytesPerSecond = _fileStream->readUint32LE();
+ _header.padding = _fileStream->readUint32LE();
+ _header.flags = _fileStream->readUint32LE();
+ _header.totalFrames = _fileStream->readUint32LE();
+ _header.initialFrames = _fileStream->readUint32LE();
+ _header.streams = _fileStream->readUint32LE();
+ _header.bufferSize = _fileStream->readUint32LE();
+ _header.width = _fileStream->readUint32LE();
+ _header.height = _fileStream->readUint32LE();
+ // Ignore 16 bytes of reserved data
+ _fileStream->skip(16);
+ break;
+ case ID_STRH:
+ handleStreamHeader(size);
+ break;
+ case ID_STRD: // Extra stream info, safe to ignore
+ case ID_VEDT: // Unknown, safe to ignore
+ case ID_JUNK: // Alignment bytes, should be ignored
+ case ID_JUNQ: // Same as JUNK, safe to ignore
+ case ID_ISFT: // Metadata, safe to ignore
+ case ID_DISP: // Metadata, should be safe to ignore
+ case ID_STRN: // Metadata, safe to ignore
+ case ID_DMLH: // OpenDML extension, contains an extra total frames field, safe to ignore
+ skipChunk(size);
+ break;
+ case ID_IDX1:
+ readOldIndex(size);
+ break;
+ case 0:
+ return false;
+ default:
+ error("Unknown tag \'%s\' found", tag2str(tag));
+ }
+
+ return true;
+}
+
+void AVIDecoder::skipChunk(uint32 size) {
+ // Make sure we're aligned on a word boundary
+ _fileStream->skip(size + (size & 1));
+}
+
+void AVIDecoder::handleList(uint32 listSize) {
+ uint32 listType = _fileStream->readUint32BE();
+ listSize -= 4; // Subtract away listType's 4 bytes
+ uint32 curPos = _fileStream->pos();
+
+ debug(0, "Found LIST of type %s", tag2str(listType));
+
+ switch (listType) {
+ case ID_MOVI: // Movie List
+ // We found the movie block
+ _foundMovieList = true;
+ _movieListStart = curPos;
+ _movieListEnd = _movieListStart + listSize + (listSize & 1);
+ _fileStream->skip(listSize);
+ return;
+ case ID_HDRL: // Header List
+ // Mark the header as decoded
+ _decodedHeader = true;
+ break;
+ case ID_INFO: // Metadata
+ case ID_PRMI: // Adobe Premiere metadata, safe to ignore
+ // Ignore metadata
+ _fileStream->skip(listSize);
+ return;
+ case ID_STRL: // Stream list
+ default: // (Just hope we can parse it!)
+ break;
+ }
+
+ while ((_fileStream->pos() - curPos) < listSize)
+ parseNextChunk();
+}
+
+void AVIDecoder::handleStreamHeader(uint32 size) {
+ AVIStreamHeader sHeader;
+ sHeader.size = size;
+ sHeader.streamType = _fileStream->readUint32BE();
+
+ if (sHeader.streamType == ID_MIDS || sHeader.streamType == ID_TXTS)
+ error("Unhandled MIDI/Text stream");
+
+ sHeader.streamHandler = _fileStream->readUint32BE();
+ sHeader.flags = _fileStream->readUint32LE();
+ sHeader.priority = _fileStream->readUint16LE();
+ sHeader.language = _fileStream->readUint16LE();
+ sHeader.initialFrames = _fileStream->readUint32LE();
+ sHeader.scale = _fileStream->readUint32LE();
+ sHeader.rate = _fileStream->readUint32LE();
+ sHeader.start = _fileStream->readUint32LE();
+ sHeader.length = _fileStream->readUint32LE();
+ sHeader.bufferSize = _fileStream->readUint32LE();
+ sHeader.quality = _fileStream->readUint32LE();
+ sHeader.sampleSize = _fileStream->readUint32LE();
+
+ _fileStream->skip(sHeader.size - 48); // Skip over the remainder of the chunk (frame)
+
+ if (_fileStream->readUint32BE() != ID_STRF)
+ error("Could not find STRF tag");
+
+ uint32 strfSize = _fileStream->readUint32LE();
+ uint32 startPos = _fileStream->pos();
+
+ if (sHeader.streamType == ID_VIDS) {
+ if (_frameRateOverride != 0) {
+ sHeader.rate = _frameRateOverride.getNumerator();
+ sHeader.scale = _frameRateOverride.getDenominator();
+ }
+
+ BitmapInfoHeader bmInfo;
+ bmInfo.size = _fileStream->readUint32LE();
+ bmInfo.width = _fileStream->readUint32LE();
+ bmInfo.height = _fileStream->readUint32LE();
+ bmInfo.planes = _fileStream->readUint16LE();
+ bmInfo.bitCount = _fileStream->readUint16LE();
+ bmInfo.compression = _fileStream->readUint32BE();
+ bmInfo.sizeImage = _fileStream->readUint32LE();
+ bmInfo.xPelsPerMeter = _fileStream->readUint32LE();
+ bmInfo.yPelsPerMeter = _fileStream->readUint32LE();
+ bmInfo.clrUsed = _fileStream->readUint32LE();
+ bmInfo.clrImportant = _fileStream->readUint32LE();
+
+ if (bmInfo.clrUsed == 0)
+ bmInfo.clrUsed = 256;
+
+ byte *initialPalette = 0;
+
+ if (bmInfo.bitCount == 8) {
+ initialPalette = new byte[256 * 3];
+ memset(initialPalette, 0, 256 * 3);
+
+ byte *palette = initialPalette;
+ for (uint32 i = 0; i < bmInfo.clrUsed; i++) {
+ palette[i * 3 + 2] = _fileStream->readByte();
+ palette[i * 3 + 1] = _fileStream->readByte();
+ palette[i * 3] = _fileStream->readByte();
+ _fileStream->readByte();
+ }
+ }
+
+ addTrack(new AVIVideoTrack(_header.totalFrames, sHeader, bmInfo, initialPalette));
+ } else if (sHeader.streamType == ID_AUDS) {
+ PCMWaveFormat wvInfo;
+ wvInfo.tag = _fileStream->readUint16LE();
+ wvInfo.channels = _fileStream->readUint16LE();
+ wvInfo.samplesPerSec = _fileStream->readUint32LE();
+ wvInfo.avgBytesPerSec = _fileStream->readUint32LE();
+ wvInfo.blockAlign = _fileStream->readUint16LE();
+ wvInfo.size = _fileStream->readUint16LE();
+
+ // AVI seems to treat the sampleSize as including the second
+ // channel as well, so divide for our sake.
+ if (wvInfo.channels == 2)
+ sHeader.sampleSize /= 2;
+
+ AVIAudioTrack *track = createAudioTrack(sHeader, wvInfo);
+ track->createAudioStream();
+ addTrack(track);
+ }
+
+ // Ensure that we're at the end of the chunk
+ _fileStream->seek(startPos + strfSize);
+}
+
+bool AVIDecoder::loadStream(Common::SeekableReadStream *stream) {
+ close();
+
+ uint32 riffTag = stream->readUint32BE();
+ if (riffTag != ID_RIFF) {
+ warning("Failed to find RIFF header");
+ return false;
+ }
+
+ /* uint32 fileSize = */ stream->readUint32LE();
+ uint32 riffType = stream->readUint32BE();
+
+ if (riffType != ID_AVI) {
+ warning("RIFF not an AVI file");
+ return false;
+ }
+
+ _fileStream = stream;
+
+ // Go through all chunks in the file
+ while (parseNextChunk())
+ ;
+
+ if (!_decodedHeader) {
+ warning("Failed to parse AVI header");
+ close();
+ return false;
+ }
+
+ if (!_foundMovieList) {
+ warning("Failed to find 'MOVI' list");
+ close();
+ return false;
+ }
+
+ // Create the status entries
+ uint32 index = 0;
+ for (TrackListIterator it = getTrackListBegin(); it != getTrackListEnd(); it++, index++) {
+ TrackStatus status;
+ status.track = *it;
+ status.index = index;
+ status.chunkSearchOffset = _movieListStart;
+
+ if ((*it)->getTrackType() == Track::kTrackTypeVideo) {
+ if (_videoTracks.size() == 0)
+ _videoTracks.push_back(status);
+ } else {
+ if (_audioTracks.size() == 0)
+ _audioTracks.push_back(status);
+ }
+ }
+
+ if (_videoTracks.size() != 1) {
+ warning("Unhandled AVI video track count: %d", _videoTracks.size());
+ close();
+ return false;
+ }
+
+ // Check if this is a special Duck Truemotion video
+ checkTruemotion1();
+
+ return true;
+}
+
+void AVIDecoder::close() {
+ VideoDecoder::close();
+
+ delete _fileStream;
+ _fileStream = 0;
+ _decodedHeader = false;
+ _foundMovieList = false;
+ _movieListStart = 0;
+ _movieListEnd = 0;
+
+ _indexEntries.clear();
+ memset(&_header, 0, sizeof(_header));
+
+ _videoTracks.clear();
+ _audioTracks.clear();
+}
+
+void AVIDecoder::readNextPacket() {
+ // Shouldn't get this unless called on a non-open video
+ if (_videoTracks.empty())
+ return;
+
+ // Get the video frame first
+ handleNextPacket(_videoTracks[0]);
+
+ // Handle audio tracks next
+ for (uint32 i = 0; i < _audioTracks.size(); i++)
+ handleNextPacket(_audioTracks[i]);
+}
+
+void AVIDecoder::handleNextPacket(TrackStatus &status) {
+ // If there's no more to search, bail out
+ if (status.chunkSearchOffset + 8 >= _movieListEnd) {
+ if (status.track->getTrackType() == Track::kTrackTypeVideo) {
+ // Horrible AVI video has a premature end
+ // Force the frame to be the last frame
+ debug(0, "Forcing end of AVI video");
+ ((AVIVideoTrack *)status.track)->forceTrackEnd();
+ }
+
+ return;
+ }
+
+ // See if audio needs to be buffered and break out if not
+ if (status.track->getTrackType() == Track::kTrackTypeAudio && !shouldQueueAudio(status))
+ return;
+
+ // Seek to where we shall start searching
+ _fileStream->seek(status.chunkSearchOffset);
+
+ for (;;) {
+ // If there's no more to search, bail out
+ if ((uint32)_fileStream->pos() + 8 >= _movieListEnd) {
+ if (status.track->getTrackType() == Track::kTrackTypeVideo) {
+ // Horrible AVI video has a premature end
+ // Force the frame to be the last frame
+ debug(0, "Forcing end of AVI video");
+ ((AVIVideoTrack *)status.track)->forceTrackEnd();
+ }
+
+ break;
+ }
+
+ uint32 nextTag = _fileStream->readUint32BE();
+ uint32 size = _fileStream->readUint32LE();
+
+ if (nextTag == ID_LIST) {
+ // A list of audio/video chunks
+ if (_fileStream->readUint32BE() != ID_REC)
+ error("Expected 'rec ' LIST");
+
+ continue;
+ } else if (nextTag == ID_JUNK || nextTag == ID_IDX1) {
+ skipChunk(size);
+ continue;
+ }
+
+ // Only accept chunks for this stream
+ uint32 streamIndex = getStreamIndex(nextTag);
+ if (streamIndex != status.index) {
+ skipChunk(size);
+ continue;
+ }
+
+ Common::SeekableReadStream *chunk = 0;
+
+ if (size != 0) {
+ chunk = _fileStream->readStream(size);
+ _fileStream->skip(size & 1);
+ }
+
+ if (status.track->getTrackType() == Track::kTrackTypeAudio) {
+ if (getStreamType(nextTag) != kStreamTypeAudio)
+ error("Invalid audio track tag '%s'", tag2str(nextTag));
+
+ assert(chunk);
+ ((AVIAudioTrack *)status.track)->queueSound(chunk);
+
+ // Break out if we have enough audio
+ if (!shouldQueueAudio(status))
+ break;
+ } else {
+ AVIVideoTrack *videoTrack = (AVIVideoTrack *)status.track;
+
+ if (getStreamType(nextTag) == kStreamTypePaletteChange) {
+ // Palette Change
+ videoTrack->loadPaletteFromChunk(chunk);
+ } else {
+ // Otherwise, assume it's a compressed frame
+ videoTrack->decodeFrame(chunk);
+ break;
+ }
+ }
+ }
+
+ // Start us off in this position next time
+ status.chunkSearchOffset = _fileStream->pos();
+}
+
+bool AVIDecoder::shouldQueueAudio(TrackStatus& status) {
+ // Sanity check:
+ if (status.track->getTrackType() != Track::kTrackTypeAudio)
+ return false;
+
+ // If video is done, make sure that the rest of the audio is queued
+ // (I guess this is also really a sanity check)
+ AVIVideoTrack *videoTrack = (AVIVideoTrack *)_videoTracks[0].track;
+ if (videoTrack->endOfTrack())
+ return true;
+
+ // Being three frames ahead should be enough for any video.
+ return ((AVIAudioTrack *)status.track)->getCurChunk() < (uint32)(videoTrack->getCurFrame() + 3);
+}
+
+bool AVIDecoder::rewind() {
+ if (!VideoDecoder::rewind())
+ return false;
+
+ for (uint32 i = 0; i < _videoTracks.size(); i++)
+ _videoTracks[i].chunkSearchOffset = _movieListStart;
+
+ for (uint32 i = 0; i < _audioTracks.size(); i++)
+ _audioTracks[i].chunkSearchOffset = _movieListStart;
+
+ return true;
+}
+
+bool AVIDecoder::seekIntern(const Audio::Timestamp &time) {
+ // Can't seek beyond the end
+ if (time > getDuration())
+ return false;
+
+ // Get our video
+ AVIVideoTrack *videoTrack = (AVIVideoTrack *)_videoTracks[0].track;
+ uint32 videoIndex = _videoTracks[0].index;
+
+ // If we seek directly to the end, just mark the tracks as over
+ if (time == getDuration()) {
+ videoTrack->setCurFrame(videoTrack->getFrameCount() - 1);
+
+ for (TrackListIterator it = getTrackListBegin(); it != getTrackListEnd(); it++)
+ if ((*it)->getTrackType() == Track::kTrackTypeAudio)
+ ((AVIAudioTrack *)*it)->resetStream();
+
+ return true;
+ }
+
+ // Get the frame we should be on at this time
+ uint frame = videoTrack->getFrameAtTime(time);
+
+ // Reset any palette, if necessary
+ videoTrack->useInitialPalette();
+
+ int lastKeyFrame = -1;
+ int frameIndex = -1;
+ uint curFrame = 0;
+
+ // Go through and figure out where we should be
+ // If there's a palette, we need to find the palette too
+ for (uint32 i = 0; i < _indexEntries.size(); i++) {
+ const OldIndex &index = _indexEntries[i];
+
+ // We don't care about RECs
+ if (index.id == ID_REC)
+ continue;
+
+ // We're only looking at entries for this track
+ if (getStreamIndex(index.id) != videoIndex)
+ continue;
+
+ uint16 streamType = getStreamType(index.id);
+
+ if (streamType == kStreamTypePaletteChange) {
+ // We need to handle any palette change we see since there's no
+ // flag to tell if this is a "key" palette.
+ // Decode the palette
+ _fileStream->seek(_indexEntries[i].offset + 8);
+ Common::SeekableReadStream *chunk = 0;
+
+ if (_indexEntries[i].size != 0)
+ chunk = _fileStream->readStream(_indexEntries[i].size);
+
+ videoTrack->loadPaletteFromChunk(chunk);
+ } else {
+ // Check to see if this is a keyframe
+ // The first frame has to be a keyframe
+ if ((_indexEntries[i].flags & AVIIF_INDEX) || curFrame == 0)
+ lastKeyFrame = i;
+
+ // Did we find the target frame?
+ if (frame == curFrame) {
+ frameIndex = i;
+ break;
+ }
+
+ curFrame++;
+ }
+ }
+
+ if (frameIndex < 0) // This shouldn't happen.
+ return false;
+
+ // Update all the audio tracks
+ for (uint32 i = 0; i < _audioTracks.size(); i++) {
+ AVIAudioTrack *audioTrack = (AVIAudioTrack *)_audioTracks[i].track;
+
+ // Recreate the audio stream
+ audioTrack->resetStream();
+
+ // Set the chunk index for the track
+ audioTrack->setCurChunk(frame);
+
+ uint32 chunksFound = 0;
+ for (uint32 j = 0; j < _indexEntries.size(); j++) {
+ const OldIndex &index = _indexEntries[j];
+
+ // Continue ignoring RECs
+ if (index.id == ID_REC)
+ continue;
+
+ if (getStreamIndex(index.id) == _audioTracks[i].index) {
+ if (chunksFound == frame) {
+ _fileStream->seek(index.offset + 8);
+ Common::SeekableReadStream *audioChunk = _fileStream->readStream(index.size);
+ audioTrack->queueSound(audioChunk);
+ _audioTracks[i].chunkSearchOffset = (j == _indexEntries.size() - 1) ? _movieListEnd : _indexEntries[j + 1].offset;
+ break;
+ }
+
+ chunksFound++;
+ }
+ }
+
+ // Skip any audio to bring us to the right time
+ audioTrack->skipAudio(time, videoTrack->getFrameTime(frame));
+ }
+
+ // Decode from keyFrame to curFrame - 1
+ for (int i = lastKeyFrame; i < frameIndex; i++) {
+ if (_indexEntries[i].id == ID_REC)
+ continue;
+
+ if (getStreamIndex(_indexEntries[i].id) != videoIndex)
+ continue;
+
+ uint16 streamType = getStreamType(_indexEntries[i].id);
+
+ // Ignore palettes, they were already handled
+ if (streamType == kStreamTypePaletteChange)
+ continue;
+
+ // Frame, hopefully
+ _fileStream->seek(_indexEntries[i].offset + 8);
+ Common::SeekableReadStream *chunk = 0;
+
+ if (_indexEntries[i].size != 0)
+ chunk = _fileStream->readStream(_indexEntries[i].size);
+
+ videoTrack->decodeFrame(chunk);
+ }
+
+ // Set the video track's frame
+ videoTrack->setCurFrame((int)frame - 1);
+
+ // Set the video track's search offset to the right spot
+ _videoTracks[0].chunkSearchOffset = _indexEntries[frameIndex].offset;
+ return true;
+}
+
+byte AVIDecoder::getStreamIndex(uint32 tag) const {
+ char string[3];
+ WRITE_BE_UINT16(string, tag >> 16);
+ string[2] = 0;
+ return strtol(string, 0, 16);
+}
+
+void AVIDecoder::readOldIndex(uint32 size) {
+ uint32 entryCount = size / 16;
+
+ debug(0, "Old Index: %d entries", entryCount);
+
+ if (entryCount == 0)
+ return;
+
+ // Read the first index separately
+ OldIndex firstEntry;
+ firstEntry.id = _fileStream->readUint32BE();
+ firstEntry.flags = _fileStream->readUint32LE();
+ firstEntry.offset = _fileStream->readUint32LE();
+ firstEntry.size = _fileStream->readUint32LE();
+
+ // Check if the offset is already absolute
+ // If it's absolute, the offset will equal the start of the movie list
+ bool isAbsolute = firstEntry.offset == _movieListStart;
+
+ debug(1, "Old index is %s", isAbsolute ? "absolute" : "relative");
+
+ if (!isAbsolute)
+ firstEntry.offset += _movieListStart - 4;
+
+ debug(0, "Index 0: Tag '%s', Offset = %d, Size = %d (Flags = %d)", tag2str(firstEntry.id), firstEntry.offset, firstEntry.size, firstEntry.flags);
+ _indexEntries.push_back(firstEntry);
+
+ for (uint32 i = 1; i < entryCount; i++) {
+ OldIndex indexEntry;
+ indexEntry.id = _fileStream->readUint32BE();
+ indexEntry.flags = _fileStream->readUint32LE();
+ indexEntry.offset = _fileStream->readUint32LE();
+ indexEntry.size = _fileStream->readUint32LE();
+
+ // Adjust to absolute, if necessary
+ if (!isAbsolute)
+ indexEntry.offset += _movieListStart - 4;
+
+ _indexEntries.push_back(indexEntry);
+ debug(0, "Index %d: Tag '%s', Offset = %d, Size = %d (Flags = %d)", i, tag2str(indexEntry.id), indexEntry.offset, indexEntry.size, indexEntry.flags);
+ }
+}
+
+void AVIDecoder::checkTruemotion1() {
+ // If we got here from loadStream(), we know the track is valid
+ assert(!_videoTracks.empty());
+
+ TrackStatus &status = _videoTracks[0];
+ AVIVideoTrack *track = (AVIVideoTrack *)status.track;
+
+ // Ignore non-truemotion tracks
+ if (!track->isTruemotion1())
+ return;
+
+ // Read the next video packet
+ handleNextPacket(status);
+
+ const Graphics::Surface *frame = track->decodeNextFrame();
+ if (!frame) {
+ rewind();
+ return;
+ }
+
+ // Fill in the width/height based on the frame's width/height
+ _header.width = frame->w;
+ _header.height = frame->h;
+ track->forceDimensions(frame->w, frame->h);
+
+ // Rewind us back to the beginning
+ rewind();
+}
+
+Video::VideoDecoder::AudioTrack *AVIDecoder::getAudioTrack(int index) {
+ // AVI audio track indexes are relative to the first track
+ Track *track = getTrack(index);
+
+ if (!track || track->getTrackType() != Track::kTrackTypeAudio)
+ return 0;
+
+ return (AudioTrack *)track;
+}
+
+AVIDecoder::AVIVideoTrack::AVIVideoTrack(int frameCount, const AVIStreamHeader &streamHeader, const BitmapInfoHeader &bitmapInfoHeader, byte *initialPalette)
+ : _frameCount(frameCount), _vidsHeader(streamHeader), _bmInfo(bitmapInfoHeader), _initialPalette(initialPalette) {
+ _videoCodec = createCodec();
+ _lastFrame = 0;
+ _curFrame = -1;
+
+ useInitialPalette();
+}
+
+AVIDecoder::AVIVideoTrack::~AVIVideoTrack() {
+ delete _videoCodec;
+ delete[] _initialPalette;
+}
+
+void AVIDecoder::AVIVideoTrack::decodeFrame(Common::SeekableReadStream *stream) {
+ if (stream) {
+ if (_videoCodec)
+ _lastFrame = _videoCodec->decodeFrame(*stream);
+ } else {
+ // Empty frame
+ _lastFrame = 0;
+ }
+
+ delete stream;
+ _curFrame++;
+}
+
+Graphics::PixelFormat AVIDecoder::AVIVideoTrack::getPixelFormat() const {
+ if (_videoCodec)
+ return _videoCodec->getPixelFormat();
+
+ return Graphics::PixelFormat();
+}
+
+void AVIDecoder::AVIVideoTrack::loadPaletteFromChunk(Common::SeekableReadStream *chunk) {
+ assert(chunk);
+ byte firstEntry = chunk->readByte();
+ uint16 numEntries = chunk->readByte();
+ chunk->readUint16LE(); // Reserved
+
+ // 0 entries means all colors are going to be changed
+ if (numEntries == 0)
+ numEntries = 256;
+
+ for (uint16 i = firstEntry; i < numEntries + firstEntry; i++) {
+ _palette[i * 3] = chunk->readByte();
+ _palette[i * 3 + 1] = chunk->readByte();
+ _palette[i * 3 + 2] = chunk->readByte();
+ chunk->readByte(); // Flags that don't serve us any purpose
+ }
+
+ delete chunk;
+ _dirtyPalette = true;
+}
+
+void AVIDecoder::AVIVideoTrack::useInitialPalette() {
+ _dirtyPalette = false;
+
+ if (_initialPalette) {
+ memcpy(_palette, _initialPalette, sizeof(_palette));
+ _dirtyPalette = true;
+ }
+}
+
+bool AVIDecoder::AVIVideoTrack::isTruemotion1() const {
+ return _bmInfo.compression == MKTAG('D', 'U', 'C', 'K') || _bmInfo.compression == MKTAG('d', 'u', 'c', 'k');
+}
+
+void AVIDecoder::AVIVideoTrack::forceDimensions(uint16 width, uint16 height) {
+ _bmInfo.width = width;
+ _bmInfo.height = height;
+}
+
+bool AVIDecoder::AVIVideoTrack::rewind() {
+ _curFrame = -1;
+
+ useInitialPalette();
+
+ delete _videoCodec;
+ _videoCodec = createCodec();
+ _lastFrame = 0;
+ return true;
+}
+
+Image::Codec *AVIDecoder::AVIVideoTrack::createCodec() {
+ return Image::createBitmapCodec(_bmInfo.compression, _bmInfo.width, _bmInfo.height, _bmInfo.bitCount);
+}
+
+void AVIDecoder::AVIVideoTrack::forceTrackEnd() {
+ _curFrame = _frameCount - 1;
+}
+
+const byte *AVIDecoder::AVIVideoTrack::getPalette() const {
+ if (_videoCodec && _videoCodec->containsPalette())
+ return _videoCodec->getPalette();
+
+ _dirtyPalette = false;
+ return _palette;
+}
+
+bool AVIDecoder::AVIVideoTrack::hasDirtyPalette() const {
+ if (_videoCodec && _videoCodec->containsPalette())
+ return _videoCodec->hasDirtyPalette();
+
+ return _dirtyPalette;
+}
+
+bool AVIDecoder::AVIVideoTrack::canDither() const {
+ return _videoCodec && _videoCodec->canDither(Image::Codec::kDitherTypeVFW);
+}
+
+void AVIDecoder::AVIVideoTrack::setDither(const byte *palette) {
+ assert(_videoCodec);
+ _videoCodec->setDither(Image::Codec::kDitherTypeVFW, palette);
+}
+
+AVIDecoder::AVIAudioTrack::AVIAudioTrack(const AVIStreamHeader &streamHeader, const PCMWaveFormat &waveFormat, Audio::Mixer::SoundType soundType)
+ : _audsHeader(streamHeader), _wvInfo(waveFormat), _soundType(soundType), _audioStream(0), _packetStream(0), _curChunk(0) {
+}
+
+AVIDecoder::AVIAudioTrack::~AVIAudioTrack() {
+ delete _audioStream;
+}
+
+void AVIDecoder::AVIAudioTrack::queueSound(Common::SeekableReadStream *stream) {
+ if (_packetStream)
+ _packetStream->queuePacket(stream);
+ else
+ delete stream;
+
+ _curChunk++;
+}
+
+void AVIDecoder::AVIAudioTrack::skipAudio(const Audio::Timestamp &time, const Audio::Timestamp &frameTime) {
+ Audio::Timestamp timeDiff = time.convertToFramerate(_wvInfo.samplesPerSec) - frameTime.convertToFramerate(_wvInfo.samplesPerSec);
+ int skipFrames = timeDiff.totalNumberOfFrames();
+
+ if (skipFrames <= 0)
+ return;
+
+ Audio::AudioStream *audioStream = getAudioStream();
+ if (!audioStream)
+ return;
+
+ if (audioStream->isStereo())
+ skipFrames *= 2;
+
+ int16 *tempBuffer = new int16[skipFrames];
+ audioStream->readBuffer(tempBuffer, skipFrames);
+ delete[] tempBuffer;
+}
+
+void AVIDecoder::AVIAudioTrack::resetStream() {
+ delete _audioStream;
+ createAudioStream();
+ _curChunk = 0;
+}
+
+bool AVIDecoder::AVIAudioTrack::rewind() {
+ resetStream();
+ return true;
+}
+
+void AVIDecoder::AVIAudioTrack::createAudioStream() {
+ _packetStream = 0;
+
+ switch (_wvInfo.tag) {
+ case kWaveFormatPCM: {
+ byte flags = 0;
+ if (_audsHeader.sampleSize == 2)
+ flags |= Audio::FLAG_16BITS | Audio::FLAG_LITTLE_ENDIAN;
+ else
+ flags |= Audio::FLAG_UNSIGNED;
+
+ if (_wvInfo.channels == 2)
+ flags |= Audio::FLAG_STEREO;
+
+ _packetStream = Audio::makePacketizedRawStream(_wvInfo.samplesPerSec, flags);
+ break;
+ }
+ case kWaveFormatMSADPCM:
+ _packetStream = Audio::makePacketizedADPCMStream(Audio::kADPCMMS, _wvInfo.samplesPerSec, _wvInfo.channels, _wvInfo.blockAlign);
+ break;
+ case kWaveFormatMSIMAADPCM:
+ _packetStream = Audio::makePacketizedADPCMStream(Audio::kADPCMMSIma, _wvInfo.samplesPerSec, _wvInfo.channels, _wvInfo.blockAlign);
+ break;
+ case kWaveFormatDK3:
+ _packetStream = Audio::makePacketizedADPCMStream(Audio::kADPCMDK3, _wvInfo.samplesPerSec, _wvInfo.channels, _wvInfo.blockAlign);
+ break;
+ case kWaveFormatMP3:
+#ifdef USE_MAD
+ _packetStream = Audio::makePacketizedMP3Stream(_wvInfo.channels, _wvInfo.samplesPerSec);
+#else
+ warning("AVI MP3 stream found, but no libmad support compiled in");
+#endif
+ break;
+ case kWaveFormatNone:
+ break;
+ default:
+ warning("Unsupported AVI audio format %d", _wvInfo.tag);
+ break;
+ }
+
+ if (_packetStream)
+ _audioStream = _packetStream;
+ else
+ _audioStream = Audio::makeNullAudioStream();
+}
+
+AVIDecoder::TrackStatus::TrackStatus() : track(0), chunkSearchOffset(0) {
+}
+
+} // End of namespace Titanic
diff --git a/engines/titanic/support/avi_decoder.h b/engines/titanic/support/avi_decoder.h
new file mode 100644
index 0000000000..acc33cbc4d
--- /dev/null
+++ b/engines/titanic/support/avi_decoder.h
@@ -0,0 +1,285 @@
+/* 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 TITANIC_AVI_DECODER_H
+#define TITANIC_AVI_DECODER_H
+
+#include "common/array.h"
+#include "common/rational.h"
+#include "common/rect.h"
+#include "common/str.h"
+
+#include "video/video_decoder.h"
+#include "audio/mixer.h"
+
+namespace Audio {
+class AudioStream;
+class PacketizedAudioStream;
+}
+
+namespace Common {
+class SeekableReadStream;
+}
+
+namespace Graphics {
+struct PixelFormat;
+}
+
+namespace Image {
+class Codec;
+}
+
+namespace Titanic {
+
+/**
+ * Modified AVI Decoder used by Titanic engine.
+ */
+class AVIDecoder : public Video::VideoDecoder {
+public:
+ AVIDecoder(Audio::Mixer::SoundType soundType = Audio::Mixer::kPlainSoundType);
+ AVIDecoder(const Common::Rational &frameRateOverride, Audio::Mixer::SoundType soundType = Audio::Mixer::kPlainSoundType);
+ virtual ~AVIDecoder();
+
+ bool loadStream(Common::SeekableReadStream *stream);
+ void close();
+ uint16 getWidth() const { return _header.width; }
+ uint16 getHeight() const { return _header.height; }
+
+ bool rewind();
+ bool isRewindable() const { return true; }
+ bool isSeekable() const;
+
+protected:
+ // VideoDecoder API
+ void readNextPacket();
+ bool seekIntern(const Audio::Timestamp &time);
+ bool supportsAudioTrackSwitching() const { return true; }
+ AudioTrack *getAudioTrack(int index);
+
+ struct BitmapInfoHeader {
+ uint32 size;
+ uint32 width;
+ uint32 height;
+ uint16 planes;
+ uint16 bitCount;
+ uint32 compression;
+ uint32 sizeImage;
+ uint32 xPelsPerMeter;
+ uint32 yPelsPerMeter;
+ uint32 clrUsed;
+ uint32 clrImportant;
+ };
+
+ struct WaveFormat {
+ uint16 tag;
+ uint16 channels;
+ uint32 samplesPerSec;
+ uint32 avgBytesPerSec;
+ uint16 blockAlign;
+ };
+
+ struct PCMWaveFormat : public WaveFormat {
+ uint16 size;
+ };
+
+ struct WaveFormatEX : public WaveFormat {
+ uint16 bitsPerSample;
+ uint16 size;
+ };
+
+ struct OldIndex {
+ uint32 id;
+ uint32 flags;
+ uint32 offset;
+ uint32 size;
+ };
+
+ // Index Flags
+ enum IndexFlags {
+ AVIIF_INDEX = 0x10
+ };
+
+ struct AVIHeader {
+ uint32 size;
+ uint32 microSecondsPerFrame;
+ uint32 maxBytesPerSecond;
+ uint32 padding;
+ uint32 flags;
+ uint32 totalFrames;
+ uint32 initialFrames;
+ uint32 streams;
+ uint32 bufferSize;
+ uint32 width;
+ uint32 height;
+ };
+
+ // Flags from the AVIHeader
+ enum AVIFlags {
+ AVIF_HASINDEX = 0x00000010,
+ AVIF_MUSTUSEINDEX = 0x00000020,
+ AVIF_ISINTERLEAVED = 0x00000100,
+ AVIF_TRUSTCKTYPE = 0x00000800,
+ AVIF_WASCAPTUREFILE = 0x00010000,
+ AVIF_WASCOPYRIGHTED = 0x00020000
+ };
+
+ struct AVIStreamHeader {
+ uint32 size;
+ uint32 streamType;
+ uint32 streamHandler;
+ uint32 flags;
+ uint16 priority;
+ uint16 language;
+ uint32 initialFrames;
+ uint32 scale;
+ uint32 rate;
+ uint32 start;
+ uint32 length;
+ uint32 bufferSize;
+ uint32 quality;
+ uint32 sampleSize;
+ Common::Rect frame;
+ };
+
+ class AVIVideoTrack : public FixedRateVideoTrack {
+ public:
+ AVIVideoTrack(int frameCount, const AVIStreamHeader &streamHeader, const BitmapInfoHeader &bitmapInfoHeader, byte *initialPalette = 0);
+ ~AVIVideoTrack();
+
+ void decodeFrame(Common::SeekableReadStream *stream);
+ void forceTrackEnd();
+
+ uint16 getWidth() const { return _bmInfo.width; }
+ uint16 getHeight() const { return _bmInfo.height; }
+ Graphics::PixelFormat getPixelFormat() const;
+ int getCurFrame() const { return _curFrame; }
+ int getFrameCount() const { return _frameCount; }
+ const Graphics::Surface *decodeNextFrame() { return _lastFrame; }
+
+ const byte *getPalette() const;
+ bool hasDirtyPalette() const;
+ void setCurFrame(int frame) { _curFrame = frame; }
+ void loadPaletteFromChunk(Common::SeekableReadStream *chunk);
+ void useInitialPalette();
+ bool canDither() const;
+ void setDither(const byte *palette);
+
+ bool isTruemotion1() const;
+ void forceDimensions(uint16 width, uint16 height);
+
+ bool isRewindable() const { return true; }
+ bool rewind();
+
+ protected:
+ Common::Rational getFrameRate() const { return Common::Rational(_vidsHeader.rate, _vidsHeader.scale); }
+
+ private:
+ AVIStreamHeader _vidsHeader;
+ BitmapInfoHeader _bmInfo;
+ byte _palette[3 * 256];
+ byte *_initialPalette;
+ mutable bool _dirtyPalette;
+ int _frameCount, _curFrame;
+
+ Image::Codec *_videoCodec;
+ const Graphics::Surface *_lastFrame;
+ Image::Codec *createCodec();
+ };
+
+ class AVIAudioTrack : public AudioTrack {
+ public:
+ AVIAudioTrack(const AVIStreamHeader &streamHeader, const PCMWaveFormat &waveFormat, Audio::Mixer::SoundType soundType);
+ ~AVIAudioTrack();
+
+ virtual void createAudioStream();
+ virtual void queueSound(Common::SeekableReadStream *stream);
+ Audio::Mixer::SoundType getSoundType() const { return _soundType; }
+ void skipAudio(const Audio::Timestamp &time, const Audio::Timestamp &frameTime);
+ virtual void resetStream();
+ uint32 getCurChunk() const { return _curChunk; }
+ void setCurChunk(uint32 chunk) { _curChunk = chunk; }
+
+ bool isRewindable() const { return true; }
+ bool rewind();
+
+ protected:
+ Audio::AudioStream *getAudioStream() const { return _audioStream; }
+
+ // Audio Codecs
+ enum {
+ kWaveFormatNone = 0,
+ kWaveFormatPCM = 1,
+ kWaveFormatMSADPCM = 2,
+ kWaveFormatMSIMAADPCM = 17,
+ kWaveFormatMP3 = 85,
+ kWaveFormatDK3 = 98 // rogue format number
+ };
+
+ AVIStreamHeader _audsHeader;
+ PCMWaveFormat _wvInfo;
+ Audio::Mixer::SoundType _soundType;
+ Audio::AudioStream *_audioStream;
+ Audio::PacketizedAudioStream *_packetStream;
+ uint32 _curChunk;
+ };
+
+ struct TrackStatus {
+ TrackStatus();
+
+ Track *track;
+ uint32 index;
+ uint32 chunkSearchOffset;
+ };
+
+ AVIHeader _header;
+
+ void readOldIndex(uint32 size);
+ Common::Array<OldIndex> _indexEntries;
+
+ Common::SeekableReadStream *_fileStream;
+ bool _decodedHeader;
+ bool _foundMovieList;
+ uint32 _movieListStart, _movieListEnd;
+
+ Audio::Mixer::SoundType _soundType;
+ Common::Rational _frameRateOverride;
+ void initCommon();
+
+ bool parseNextChunk();
+ void skipChunk(uint32 size);
+ void handleList(uint32 listSize);
+ void handleStreamHeader(uint32 size);
+ uint16 getStreamType(uint32 tag) const { return tag & 0xFFFF; }
+ byte getStreamIndex(uint32 tag) const;
+ void checkTruemotion1();
+
+ void handleNextPacket(TrackStatus& status);
+ bool shouldQueueAudio(TrackStatus& status);
+ Common::Array<TrackStatus> _videoTracks, _audioTracks;
+
+public:
+ virtual AVIAudioTrack *createAudioTrack(AVIStreamHeader sHeader, PCMWaveFormat wvInfo);
+};
+
+} // End of namespace Titanic
+
+#endif
diff --git a/engines/titanic/support/movie.cpp b/engines/titanic/support/movie.cpp
index ed5cffaac1..25909183dd 100644
--- a/engines/titanic/support/movie.cpp
+++ b/engines/titanic/support/movie.cpp
@@ -20,6 +20,8 @@
*
*/
+#include "image/codecs/cinepak.h"
+#include "titanic/support/avi_decoder.h"
#include "titanic/support/movie.h"
#include "titanic/titanic.h"
@@ -44,7 +46,13 @@ bool CMovie::get10() {
/*------------------------------------------------------------------------*/
OSMovie::OSMovie(const CResourceKey &name, CVideoSurface *surface) : _videoSurface(surface) {
-// _aviDecoder.loadFile(name.getString());
+ _video = new AVIDecoder();
+ if (!_video->loadFile(name.getString()))
+ error("Could not open video - %s", name.getString().c_str());
+}
+
+OSMovie::~OSMovie() {
+ delete _video;
}
void OSMovie::proc8(int v1, CVideoSurface *surface) {
@@ -76,13 +84,12 @@ void OSMovie::proc14() {
}
void OSMovie::setFrame(uint frameNumber) {
- warning("TODO: OSMovie::setFrame");
- /*
- _aviDecoder.seekToFrame(frameNumber);
- const Graphics::Surface *s = _aviDecoder.decodeNextFrame();
+ _video->seekToFrame(frameNumber);
+ const Graphics::Surface *s = _video->decodeNextFrame();
+ Graphics::Surface *surf = s->convertTo(g_system->getScreenFormat());
- _videoSurface->blitFrom(Common::Point(0, 0), s);
- */
+ _videoSurface->blitFrom(Common::Point(0, 0), surf);
+ delete surf;
}
void OSMovie::proc16() {
diff --git a/engines/titanic/support/movie.h b/engines/titanic/support/movie.h
index 3529409fa5..e84e283597 100644
--- a/engines/titanic/support/movie.h
+++ b/engines/titanic/support/movie.h
@@ -23,7 +23,7 @@
#ifndef TITANIC_MOVIE_H
#define TITANIC_MOVIE_H
-#include "video/avi_decoder.h"
+#include "video/video_decoder.h"
#include "titanic/core/list.h"
#include "titanic/core/resource_key.h"
@@ -37,6 +37,7 @@ protected:
int _field10;
public:
CMovie();
+ virtual ~CMovie() {}
virtual void proc8(int v1, CVideoSurface *surface) = 0;
virtual void proc9() = 0;
@@ -60,10 +61,11 @@ public:
class OSMovie : public CMovie {
private:
- Video::AVIDecoder _aviDecoder;
+ Video::VideoDecoder *_video;
CVideoSurface *_videoSurface;
public:
OSMovie(const CResourceKey &name, CVideoSurface *surface);
+ virtual ~OSMovie();
virtual void proc8(int v1, CVideoSurface *surface);
virtual void proc9();