diff options
-rw-r--r-- | engines/titanic/module.mk | 1 | ||||
-rw-r--r-- | engines/titanic/support/avi_decoder.cpp | 946 | ||||
-rw-r--r-- | engines/titanic/support/avi_decoder.h | 285 | ||||
-rw-r--r-- | engines/titanic/support/movie.cpp | 21 | ||||
-rw-r--r-- | engines/titanic/support/movie.h | 6 |
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(); |