aboutsummaryrefslogtreecommitdiff
path: root/video/avi_decoder.cpp
diff options
context:
space:
mode:
Diffstat (limited to 'video/avi_decoder.cpp')
-rw-r--r--video/avi_decoder.cpp850
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