diff options
Diffstat (limited to 'engines/titanic/support/avi_decoder.cpp')
-rw-r--r-- | engines/titanic/support/avi_decoder.cpp | 946 |
1 files changed, 946 insertions, 0 deletions
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 |