diff options
Diffstat (limited to 'video/avi_decoder.cpp')
-rw-r--r-- | video/avi_decoder.cpp | 850 |
1 files changed, 653 insertions, 197 deletions
diff --git a/video/avi_decoder.cpp b/video/avi_decoder.cpp index 6fe9c773b8..52a55f600c 100644 --- a/video/avi_decoder.cpp +++ b/video/avi_decoder.cpp @@ -8,12 +8,12 @@ * 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. @@ -31,14 +31,11 @@ // Audio Codecs #include "audio/decoders/adpcm.h" +#include "audio/decoders/mp3.h" #include "audio/decoders/raw.h" // Video Codecs -#include "video/codecs/cinepak.h" -#include "video/codecs/indeo3.h" -#include "video/codecs/msvideo1.h" -#include "video/codecs/msrle.h" -#include "video/codecs/truemotion1.h" +#include "image/codecs/codec.h" namespace Video { @@ -58,66 +55,74 @@ namespace Video { #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_00AM MKTAG('0','0','A','M') -//#define ID_INFO MKTAG('I','N','F','O') +#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') -// Codec tags -#define ID_RLE MKTAG('R','L','E',' ') -#define ID_CRAM MKTAG('C','R','A','M') -#define ID_MSVC MKTAG('m','s','v','c') -#define ID_WHAM MKTAG('W','H','A','M') -#define ID_CVID MKTAG('c','v','i','d') -#define ID_IV32 MKTAG('i','v','3','2') -#define ID_DUCK MKTAG('D','U','C','K') +// Stream Types +enum { + kStreamTypePaletteChange = MKTAG16('p', 'c'), + kStreamTypeAudio = MKTAG16('w', 'b') +}; + + +AVIDecoder::AVIDecoder(Audio::Mixer::SoundType soundType) : _frameRateOverride(0), _soundType(soundType) { + initCommon(); +} -static byte char2num(char c) { - c = tolower((byte)c); - return (c >= 'a' && c <= 'f') ? c - 'a' + 10 : c - '0'; +AVIDecoder::AVIDecoder(const Common::Rational &frameRateOverride, Audio::Mixer::SoundType soundType) + : _frameRateOverride(frameRateOverride), _soundType(soundType) { + initCommon(); } -static byte getStreamIndex(uint32 tag) { - return char2num((tag >> 24) & 0xFF) << 4 | char2num((tag >> 16) & 0xFF); +AVIDecoder::~AVIDecoder() { + close(); } -static uint16 getStreamType(uint32 tag) { - return tag & 0xffff; +AVIDecoder::AVIAudioTrack *AVIDecoder::createAudioTrack(AVIStreamHeader sHeader, PCMWaveFormat wvInfo) { + return new AVIAudioTrack(sHeader, wvInfo, _soundType); } -AVIDecoder::AVIDecoder(Audio::Mixer::SoundType soundType) : _soundType(soundType) { +void AVIDecoder::initCommon() { _decodedHeader = false; + _foundMovieList = false; + _movieListStart = 0; + _movieListEnd = 0; _fileStream = 0; - memset(&_ixInfo, 0, sizeof(_ixInfo)); memset(&_header, 0, sizeof(_header)); } -AVIDecoder::~AVIDecoder() { - close(); +bool AVIDecoder::isSeekable() const { + // Only videos with an index can seek + // Anyone else who wants to seek is crazy. + return isVideoLoaded() && !_indexEntries.empty(); } -void AVIDecoder::runHandle(uint32 tag) { - assert(_fileStream); +bool AVIDecoder::parseNextChunk() { + uint32 tag = _fileStream->readUint32BE(); + uint32 size = _fileStream->readUint32LE(); + if (_fileStream->eos()) - return; + return false; debug(3, "Decoding tag %s", tag2str(tag)); switch (tag) { - case ID_RIFF: - /*_filesize = */_fileStream->readUint32LE(); - if (_fileStream->readUint32BE() != ID_AVI) - error("RIFF file is not an AVI video"); - break; case ID_LIST: - handleList(); + handleList(size); break; case ID_AVIH: - _header.size = _fileStream->readUint32LE(); + _header.size = size; _header.microSecondsPerFrame = _fileStream->readUint32LE(); _header.maxBytesPerSecond = _fileStream->readUint32LE(); _header.padding = _fileStream->readUint32LE(); @@ -132,50 +137,69 @@ void AVIDecoder::runHandle(uint32 tag) { _fileStream->skip(16); break; case ID_STRH: - handleStreamHeader(); + 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 - { - uint32 junkSize = _fileStream->readUint32LE(); - _fileStream->skip(junkSize + (junkSize & 1)); // Alignment - } break; + 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: - _ixInfo.size = _fileStream->readUint32LE(); - _ixInfo.indices = new OldIndex::Index[_ixInfo.size / 16]; - debug(0, "%d Indices", (_ixInfo.size / 16)); - for (uint32 i = 0; i < (_ixInfo.size / 16); i++) { - _ixInfo.indices[i].id = _fileStream->readUint32BE(); - _ixInfo.indices[i].flags = _fileStream->readUint32LE(); - _ixInfo.indices[i].offset = _fileStream->readUint32LE(); - _ixInfo.indices[i].size = _fileStream->readUint32LE(); - debug(0, "Index %d == Tag \'%s\', Offset = %d, Size = %d", i, tag2str(_ixInfo.indices[i].id), _ixInfo.indices[i].offset, _ixInfo.indices[i].size); - } + readOldIndex(size); break; default: error("Unknown tag \'%s\' found", tag2str(tag)); } + + return true; } -void AVIDecoder::handleList() { - uint32 listSize = _fileStream->readUint32LE() - 4; // Subtract away listType's 4 bytes +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)); - while ((_fileStream->pos() - curPos) < listSize) - runHandle(_fileStream->readUint32BE()); - - // We now have all the header data - if (listType == ID_HDRL) + 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() { +void AVIDecoder::handleStreamHeader(uint32 size) { AVIStreamHeader sHeader; - sHeader.size = _fileStream->readUint32LE(); + sHeader.size = size; sHeader.streamType = _fileStream->readUint32BE(); if (sHeader.streamType == ID_MIDS || sHeader.streamType == ID_TXTS) @@ -203,6 +227,11 @@ void AVIDecoder::handleStreamHeader() { 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(); @@ -219,24 +248,22 @@ void AVIDecoder::handleStreamHeader() { if (bmInfo.clrUsed == 0) bmInfo.clrUsed = 256; - if (sHeader.streamHandler == 0) - sHeader.streamHandler = bmInfo.compression; - - AVIVideoTrack *track = new AVIVideoTrack(_header.totalFrames, sHeader, bmInfo); + byte *initialPalette = 0; if (bmInfo.bitCount == 8) { - byte *palette = const_cast<byte *>(track->getPalette()); + 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(); } - - track->markPaletteDirty(); } - addTrack(track); + addTrack(new AVIVideoTrack(_header.totalFrames, sHeader, bmInfo, initialPalette)); } else if (sHeader.streamType == ID_AUDS) { PCMWaveFormat wvInfo; wvInfo.tag = _fileStream->readUint16LE(); @@ -251,7 +278,9 @@ void AVIDecoder::handleStreamHeader() { if (wvInfo.channels == 2) sHeader.sampleSize /= 2; - addTrack(new AVIAudioTrack(sHeader, wvInfo, _soundType)); + AVIAudioTrack *track = createAudioTrack(sHeader, wvInfo); + track->createAudioStream(); + addTrack(track); } // Ensure that we're at the end of the chunk @@ -261,30 +290,61 @@ void AVIDecoder::handleStreamHeader() { 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; - _decodedHeader = false; - // Read chunks until we have decoded the header - while (!_decodedHeader) - runHandle(_fileStream->readUint32BE()); + // Go through all chunks in the file + while (parseNextChunk()) + ; - uint32 nextTag = _fileStream->readUint32BE(); + if (!_decodedHeader) { + warning("Failed to parse AVI header"); + close(); + return false; + } - // Throw out any JUNK section - if (nextTag == ID_JUNK) { - runHandle(ID_JUNK); - nextTag = _fileStream->readUint32BE(); + if (!_foundMovieList) { + warning("Failed to find 'MOVI' list"); + close(); + return false; } - // Ignore the 'movi' LIST - if (nextTag == ID_LIST) { - _fileStream->readUint32BE(); // Skip size - if (_fileStream->readUint32BE() != ID_MOVI) - error("Expected 'movi' LIST"); - } else { - error("Expected 'movi' LIST"); + // 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) + _videoTracks.push_back(status); + else + _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; } @@ -294,108 +354,396 @@ void AVIDecoder::close() { delete _fileStream; _fileStream = 0; _decodedHeader = false; + _foundMovieList = false; + _movieListStart = 0; + _movieListEnd = 0; - delete[] _ixInfo.indices; - memset(&_ixInfo, 0, sizeof(_ixInfo)); + _indexEntries.clear(); memset(&_header, 0, sizeof(_header)); + + _videoTracks.clear(); + _audioTracks.clear(); } void AVIDecoder::readNextPacket() { - uint32 nextTag = _fileStream->readUint32BE(); - - if (_fileStream->eos()) + // Shouldn't get this unless called on a non-open video + if (_videoTracks.empty()) return; - if (nextTag == ID_LIST) { - // A list of audio/video chunks - uint32 listSize = _fileStream->readUint32LE() - 4; - int32 startPos = _fileStream->pos(); + // Get the video frame first + handleNextPacket(_videoTracks[0]); - if (_fileStream->readUint32BE() != ID_REC) - error("Expected 'rec ' LIST"); + // Handle audio tracks next + for (uint32 i = 0; i < _audioTracks.size(); i++) + handleNextPacket(_audioTracks[i]); +} - // Decode chunks in the list - while (_fileStream->pos() < startPos + (int32)listSize) - readNextPacket(); +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; - } else if (nextTag == ID_JUNK || nextTag == ID_IDX1) { - runHandle(nextTag); + } + + // 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; + } + } } - Track *track = getTrack(getStreamIndex(nextTag)); + // 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; - if (!track) - error("Cannot get track from tag '%s'", tag2str(nextTag)); + 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; - uint32 chunkSize = _fileStream->readUint32LE(); - Common::SeekableReadStream *chunk = 0; + // If we seek directly to the end, just mark the tracks as over + if (time == getDuration()) { + videoTrack->setCurFrame(videoTrack->getFrameCount() - 1); - if (chunkSize != 0) { - chunk = _fileStream->readStream(chunkSize); - _fileStream->skip(chunkSize & 1); + for (TrackListIterator it = getTrackListBegin(); it != getTrackListEnd(); it++) + if ((*it)->getTrackType() == Track::kTrackTypeAudio) + ((AVIAudioTrack *)*it)->resetStream(); + + return true; } - if (track->getTrackType() == Track::kTrackTypeAudio) { - if (getStreamType(nextTag) != MKTAG16('w', 'b')) - error("Invalid audio track tag '%s'", tag2str(nextTag)); + // Get the frame we should be on at this time + uint frame = videoTrack->getFrameAtTime(time); - assert(chunk); - ((AVIAudioTrack *)track)->queueSound(chunk); - } else { - AVIVideoTrack *videoTrack = (AVIVideoTrack *)track; + // Reset any palette, if necessary + videoTrack->useInitialPalette(); - if (getStreamType(nextTag) == MKTAG16('p', 'c')) { - // Palette Change - assert(chunk); - byte firstEntry = chunk->readByte(); - uint16 numEntries = chunk->readByte(); - chunk->readUint16LE(); // Reserved + int lastKeyFrame = -1; + int frameIndex = -1; + uint curFrame = 0; - // 0 entries means all colors are going to be changed - if (numEntries == 0) - numEntries = 256; + // 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]; - byte *palette = const_cast<byte *>(videoTrack->getPalette()); + // We don't care about RECs + if (index.id == ID_REC) + continue; - 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 - } + // 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); - delete chunk; - videoTrack->markPaletteDirty(); - } else if (getStreamType(nextTag) == MKTAG16('d', 'b')) { - // TODO: Check if this really is uncompressed. Many videos - // falsely put compressed data in here. - error("Uncompressed AVI frame found"); + videoTrack->loadPaletteFromChunk(chunk); } else { - // Otherwise, assume it's a compressed frame - videoTrack->decodeFrame(chunk); + // 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(); } -AVIDecoder::AVIVideoTrack::AVIVideoTrack(int frameCount, const AVIStreamHeader &streamHeader, const BitmapInfoHeader &bitmapInfoHeader) - : _frameCount(frameCount), _vidsHeader(streamHeader), _bmInfo(bitmapInfoHeader) { - memset(_palette, 0, sizeof(_palette)); +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(); - _dirtyPalette = false; _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->decodeImage(stream); + _lastFrame = _videoCodec->decodeFrame(*stream); } else { // Empty frame _lastFrame = 0; @@ -412,74 +760,182 @@ Graphics::PixelFormat AVIDecoder::AVIVideoTrack::getPixelFormat() const { return Graphics::PixelFormat(); } -Codec *AVIDecoder::AVIVideoTrack::createCodec() { - switch (_vidsHeader.streamHandler) { - case ID_CRAM: - case ID_MSVC: - case ID_WHAM: - return new MSVideo1Decoder(_bmInfo.width, _bmInfo.height, _bmInfo.bitCount); - case ID_RLE: - return new MSRLEDecoder(_bmInfo.width, _bmInfo.height, _bmInfo.bitCount); - case ID_CVID: - return new CinepakDecoder(_bmInfo.bitCount); - case ID_IV32: - return new Indeo3Decoder(_bmInfo.width, _bmInfo.height); -#ifdef VIDEO_CODECS_TRUEMOTION1_H - case ID_DUCK: - return new TrueMotion1Decoder(_bmInfo.width, _bmInfo.height); -#endif - default: - warning("Unknown/Unhandled compression format \'%s\'", tag2str(_vidsHeader.streamHandler)); +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 } - return 0; + 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) { - _audStream = createAudioStream(); + : _audsHeader(streamHeader), _wvInfo(waveFormat), _soundType(soundType), _audioStream(0), _packetStream(0), _curChunk(0) { } AVIDecoder::AVIAudioTrack::~AVIAudioTrack() { - delete _audStream; + delete _audioStream; } void AVIDecoder::AVIAudioTrack::queueSound(Common::SeekableReadStream *stream) { - if (_audStream) { - if (_wvInfo.tag == 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; - - _audStream->queueAudioStream(Audio::makeRawStream(stream, _wvInfo.samplesPerSec, flags, DisposeAfterUse::YES), DisposeAfterUse::YES); - } else if (_wvInfo.tag == kWaveFormatMSADPCM) { - _audStream->queueAudioStream(Audio::makeADPCMStream(stream, DisposeAfterUse::YES, stream->size(), Audio::kADPCMMS, _wvInfo.samplesPerSec, _wvInfo.channels, _wvInfo.blockAlign), DisposeAfterUse::YES); - } else if (_wvInfo.tag == kWaveFormatMSIMAADPCM) { - _audStream->queueAudioStream(Audio::makeADPCMStream(stream, DisposeAfterUse::YES, stream->size(), Audio::kADPCMMSIma, _wvInfo.samplesPerSec, _wvInfo.channels, _wvInfo.blockAlign), DisposeAfterUse::YES); - } else if (_wvInfo.tag == kWaveFormatDK3) { - _audStream->queueAudioStream(Audio::makeADPCMStream(stream, DisposeAfterUse::YES, stream->size(), Audio::kADPCMDK3, _wvInfo.samplesPerSec, _wvInfo.channels, _wvInfo.blockAlign), DisposeAfterUse::YES); - } - } else { + if (_packetStream) + _packetStream->queuePacket(stream); + else delete stream; - } + + _curChunk++; } -Audio::AudioStream *AVIDecoder::AVIAudioTrack::getAudioStream() const { - return _audStream; +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; } -Audio::QueuingAudioStream *AVIDecoder::AVIAudioTrack::createAudioStream() { - if (_wvInfo.tag == kWaveFormatPCM || _wvInfo.tag == kWaveFormatMSADPCM || _wvInfo.tag == kWaveFormatMSIMAADPCM || _wvInfo.tag == kWaveFormatDK3) - return Audio::makeQueuingAudioStream(_wvInfo.samplesPerSec, _wvInfo.channels == 2); - else if (_wvInfo.tag != kWaveFormatNone) // No sound +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(); +} - return 0; +AVIDecoder::TrackStatus::TrackStatus() : track(0), chunkSearchOffset(0) { } } // End of namespace Video |