diff options
-rw-r--r-- | video/avi_decoder.cpp | 369 | ||||
-rw-r--r-- | video/avi_decoder.h | 16 |
2 files changed, 206 insertions, 179 deletions
diff --git a/video/avi_decoder.cpp b/video/avi_decoder.cpp index a5016632d6..7119c72f07 100644 --- a/video/avi_decoder.cpp +++ b/video/avi_decoder.cpp @@ -319,8 +319,25 @@ bool AVIDecoder::loadStream(Common::SeekableReadStream *stream) { return false; } - // Seek back to the start of the MOVI list - _fileStream->seek(_movieListStart); + // 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(); @@ -340,79 +357,138 @@ void AVIDecoder::close() { _indexEntries.clear(); memset(&_header, 0, sizeof(_header)); + + _videoTracks.clear(); + _audioTracks.clear(); } void AVIDecoder::readNextPacket() { - if ((uint32)_fileStream->pos() >= _movieListEnd) { - // Ugh, reached the end premature. - forceVideoEnd(); + // Shouldn't get this unless called on a non-open video + if (_videoTracks.empty()) return; - } - uint32 nextTag = _fileStream->readUint32BE(); - uint32 size = _fileStream->readUint32LE(); + // 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(); + } - if (_fileStream->eos()) { - // Also premature end. - forceVideoEnd(); return; } - if (nextTag == ID_LIST) { - // A list of audio/video chunks - int32 startPos = _fileStream->pos(); + // See if audio needs to be buffered and break out if not + if (status.track->getTrackType() == Track::kTrackTypeAudio && !shouldQueueAudio(status)) + return; - if (_fileStream->readUint32BE() != ID_REC) - error("Expected 'rec ' LIST"); + // 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(); + } - size -= 4; // subtract list type + break; + } - // Decode chunks in the list - while (_fileStream->pos() < startPos + (int32)size) - readNextPacket(); + uint32 nextTag = _fileStream->readUint32BE(); + uint32 size = _fileStream->readUint32LE(); - return; - } else if (nextTag == ID_JUNK || nextTag == ID_IDX1) { - skipChunk(size); - return; - } + if (nextTag == ID_LIST) { + // A list of audio/video chunks + if (_fileStream->readUint32BE() != ID_REC) + error("Expected 'rec ' LIST"); - Track *track = getTrack(getStreamIndex(nextTag)); + continue; + } else if (nextTag == ID_JUNK || nextTag == ID_IDX1) { + skipChunk(size); + continue; + } - if (!track) - error("Cannot get track from tag '%s'", tag2str(nextTag)); + // Only accept chunks for this stream + uint32 streamIndex = getStreamIndex(nextTag); + if (streamIndex != status.index) { + skipChunk(size); + continue; + } - Common::SeekableReadStream *chunk = 0; + Common::SeekableReadStream *chunk = 0; - if (size != 0) { - chunk = _fileStream->readStream(size); - _fileStream->skip(size & 1); - } + if (size != 0) { + chunk = _fileStream->readStream(size); + _fileStream->skip(size & 1); + } - if (track->getTrackType() == Track::kTrackTypeAudio) { - if (getStreamType(nextTag) != kStreamTypeAudio) - error("Invalid audio track tag '%s'", tag2str(nextTag)); + if (status.track->getTrackType() == Track::kTrackTypeAudio) { + if (getStreamType(nextTag) != kStreamTypeAudio) + error("Invalid audio track tag '%s'", tag2str(nextTag)); - assert(chunk); - ((AVIAudioTrack *)track)->queueSound(chunk); - } else { - AVIVideoTrack *videoTrack = (AVIVideoTrack *)track; + assert(chunk); + ((AVIAudioTrack *)status.track)->queueSound(chunk); - if (getStreamType(nextTag) == kStreamTypePaletteChange) { - // Palette Change - videoTrack->loadPaletteFromChunk(chunk); + // Break out if we have enough audio + if (!shouldQueueAudio(status)) + break; } else { - // Otherwise, assume it's a compressed frame - videoTrack->decodeFrame(chunk); + 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; - _fileStream->seek(_movieListStart); + 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; } @@ -421,29 +497,9 @@ bool AVIDecoder::seekIntern(const Audio::Timestamp &time) { if (time > getDuration()) return false; - // Track down our video track. - // We only support seeking with one video track right now. - AVIVideoTrack *videoTrack = 0; - int videoIndex = -1; - uint trackID = 0; - - for (TrackListIterator it = getTrackListBegin(); it != getTrackListEnd(); it++, trackID++) { - if ((*it)->getTrackType() == Track::kTrackTypeVideo) { - if (videoTrack) { - // Already have one - // -> Not supported - return false; - } - - videoTrack = (AVIVideoTrack *)*it; - videoIndex = trackID; - } - } - - // Need a video track to go forwards - // If there isn't a video track, why would anyone be using AVI then? - if (!videoTrack) - 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()) { @@ -464,7 +520,6 @@ bool AVIDecoder::seekIntern(const Audio::Timestamp &time) { int lastKeyFrame = -1; int frameIndex = -1; - int lastRecord = -1; uint curFrame = 0; // Go through and figure out where we should be @@ -472,40 +527,40 @@ bool AVIDecoder::seekIntern(const Audio::Timestamp &time) { for (uint32 i = 0; i < _indexEntries.size(); i++) { const OldIndex &index = _indexEntries[i]; - if (index.id == ID_REC) { - // Keep track of any records we find - lastRecord = i; - } else { - if (getStreamIndex(index.id) != videoIndex) - continue; + // We don't care about RECs + if (index.id == ID_REC) + continue; - uint16 streamType = getStreamType(index.id); + // We're only looking at entries for this track + if (getStreamIndex(index.id) != videoIndex) + continue; - 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; + uint16 streamType = getStreamType(index.id); - if (_indexEntries[i].size != 0) - chunk = _fileStream->readStream(_indexEntries[i].size); + 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; - 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; - } + if (_indexEntries[i].size != 0) + chunk = _fileStream->readStream(_indexEntries[i].size); - curFrame++; + 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++; } } @@ -513,57 +568,36 @@ bool AVIDecoder::seekIntern(const Audio::Timestamp &time) { return false; // Update all the audio tracks - uint audioIndex = 0; - - for (TrackListIterator it = getTrackListBegin(); it != getTrackListEnd(); it++, audioIndex++) { - if ((*it)->getTrackType() != Track::kTrackTypeAudio) - continue; - - AVIAudioTrack *audioTrack = (AVIAudioTrack *)*it; - - // We need to find where the start of audio should be. - // Which is exactly 'initialFrames' audio chunks back from where - // our found frame is. + for (uint32 i = 0; i < _audioTracks.size(); i++) { + AVIAudioTrack *audioTrack = (AVIAudioTrack *)_audioTracks[i].track; // Recreate the audio stream audioTrack->resetStream(); - uint framesNeeded = _header.initialFrames; - if (framesNeeded == 0) - framesNeeded = 1; + // Set the chunk index for the track + audioTrack->setCurChunk(frame); - uint startAudioChunk = 0; - int startAudioSearch = (lastRecord < 0) ? (frameIndex - 1) : (lastRecord - 1); + uint32 chunksFound = 0; + for (uint32 j = 0; j < _indexEntries.size(); j++) { + const OldIndex &index = _indexEntries[j]; - for (int i = startAudioSearch; i >= 0; i--) { - if (getStreamIndex(_indexEntries[i].id) != audioIndex) + // Continue ignoring RECs + if (index.id == ID_REC) continue; - assert(getStreamType(_indexEntries[i].id) == kStreamTypeAudio); - - framesNeeded--; + 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; + } - if (framesNeeded == 0) { - startAudioChunk = i; - break; + chunksFound++; } } - // Now go forward and queue them all - for (int i = startAudioChunk; i <= startAudioSearch; i++) { - if (_indexEntries[i].id == ID_REC) - continue; - - if (getStreamIndex(_indexEntries[i].id) != audioIndex) - continue; - - assert(getStreamType(_indexEntries[i].id) == kStreamTypeAudio); - - _fileStream->seek(_indexEntries[i].offset + 8); - Common::SeekableReadStream *chunk = _fileStream->readStream(_indexEntries[i].size); - audioTrack->queueSound(chunk); - } - // Skip any audio to bring us to the right time audioTrack->skipAudio(time, videoTrack->getFrameTime(frame)); } @@ -592,15 +626,11 @@ bool AVIDecoder::seekIntern(const Audio::Timestamp &time) { videoTrack->decodeFrame(chunk); } - // Seek to the right spot - // To the beginning of the last record, or frame if that doesn't exist - if (lastRecord >= 0) - _fileStream->seek(_indexEntries[lastRecord].offset); - else - _fileStream->seek(_indexEntries[frameIndex].offset); - + // 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; } @@ -654,45 +684,22 @@ void AVIDecoder::readOldIndex(uint32 size) { } } -void AVIDecoder::forceVideoEnd() { - // Horrible AVI video has a premature end - // Force the frame to be the last frame - debug(0, "Forcing end of AVI video"); - - for (TrackListIterator it = getTrackListBegin(); it != getTrackListEnd(); it++) - if ((*it)->getTrackType() == Track::kTrackTypeVideo) - ((AVIVideoTrack *)*it)->forceTrackEnd(); -} - void AVIDecoder::checkTruemotion1() { - AVIVideoTrack *track = 0; - - for (TrackListIterator it = getTrackListBegin(); it != getTrackListEnd(); it++) { - if ((*it)->getTrackType() == Track::kTrackTypeVideo) { - if (track) { - // Multiple tracks; isn't going to be truemotion 1 - return; - } + // If we got here from loadStream(), we know the track is valid + assert(!_videoTracks.empty()); - track = (AVIVideoTrack *)*it; - } - } - - // No track found? - if (!track) - return; + TrackStatus &status = _videoTracks[0]; + AVIVideoTrack *track = (AVIVideoTrack *)status.track; // Ignore non-truemotion tracks if (!track->isTruemotion1()) return; - // Search for a non-empty frame - const Graphics::Surface *frame = 0; - for (int i = 0; i < 10 && !frame; i++) - frame = decodeNextFrame(); + // Read the next video packet + handleNextPacket(status); + const Graphics::Surface *frame = track->decodeNextFrame(); if (!frame) { - // Probably shouldn't happen rewind(); return; } @@ -809,7 +816,7 @@ void AVIDecoder::AVIVideoTrack::forceTrackEnd() { } AVIDecoder::AVIAudioTrack::AVIAudioTrack(const AVIStreamHeader &streamHeader, const PCMWaveFormat &waveFormat, Audio::Mixer::SoundType soundType) - : _audsHeader(streamHeader), _wvInfo(waveFormat), _soundType(soundType) { + : _audsHeader(streamHeader), _wvInfo(waveFormat), _soundType(soundType), _curChunk(0) { _audStream = createAudioStream(); } @@ -836,12 +843,12 @@ void AVIDecoder::AVIAudioTrack::queueSound(Common::SeekableReadStream *stream) { _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 (_wvInfo.tag == kWaveFormatMP3) { - warning("AVI: MP3 audio stream is not supported"); } } else { delete stream; } + + _curChunk++; } void AVIDecoder::AVIAudioTrack::skipAudio(const Audio::Timestamp &time, const Audio::Timestamp &frameTime) { @@ -862,6 +869,7 @@ void AVIDecoder::AVIAudioTrack::skipAudio(const Audio::Timestamp &time, const Au void AVIDecoder::AVIAudioTrack::resetStream() { delete _audStream; _audStream = createAudioStream(); + _curChunk = 0; } bool AVIDecoder::AVIAudioTrack::rewind() { @@ -874,12 +882,17 @@ Audio::AudioStream *AVIDecoder::AVIAudioTrack::getAudioStream() const { } Audio::QueuingAudioStream *AVIDecoder::AVIAudioTrack::createAudioStream() { - if (_wvInfo.tag == kWaveFormatPCM || _wvInfo.tag == kWaveFormatMSADPCM || _wvInfo.tag == kWaveFormatMSIMAADPCM || _wvInfo.tag == kWaveFormatDK3 || _wvInfo.tag == kWaveFormatMP3) + 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 == kWaveFormatMP3) + warning("Unsupported AVI MP3 tracks"); else if (_wvInfo.tag != kWaveFormatNone) // No sound warning("Unsupported AVI audio format %d", _wvInfo.tag); return 0; } +AVIDecoder::TrackStatus::TrackStatus() : track(0), chunkSearchOffset(0) { +} + } // End of namespace Video diff --git a/video/avi_decoder.h b/video/avi_decoder.h index 4461e537c5..eef127155e 100644 --- a/video/avi_decoder.h +++ b/video/avi_decoder.h @@ -216,6 +216,8 @@ protected: Audio::Mixer::SoundType getSoundType() const { return _soundType; } void skipAudio(const Audio::Timestamp &time, const Audio::Timestamp &frameTime); void resetStream(); + uint32 getCurChunk() const { return _curChunk; } + void setCurChunk(uint32 chunk) { _curChunk = chunk; } bool isRewindable() const { return true; } bool rewind(); @@ -238,6 +240,15 @@ protected: Audio::Mixer::SoundType _soundType; Audio::QueuingAudioStream *_audStream; Audio::QueuingAudioStream *createAudioStream(); + uint32 _curChunk; + }; + + struct TrackStatus { + TrackStatus(); + + Track *track; + uint32 index; + uint32 chunkSearchOffset; }; AVIHeader _header; @@ -260,9 +271,12 @@ protected: void handleStreamHeader(uint32 size); uint16 getStreamType(uint32 tag) const { return tag & 0xFFFF; } byte getStreamIndex(uint32 tag) const; - void forceVideoEnd(); void checkTruemotion1(); + void handleNextPacket(TrackStatus& status); + bool shouldQueueAudio(TrackStatus& status); + Common::Array<TrackStatus> _videoTracks, _audioTracks; + public: virtual AVIAudioTrack *createAudioTrack(AVIStreamHeader sHeader, PCMWaveFormat wvInfo); }; |