diff options
author | Matthew Hoops | 2014-11-02 19:26:18 -0500 |
---|---|---|
committer | Matthew Hoops | 2014-11-02 20:15:06 -0500 |
commit | dfc3bcae20584b79f0b7ea2a6e4ccf0fed29797f (patch) | |
tree | 6f35fb4df8af4680d748e2b26912a953ec5f2b46 | |
parent | 3f7566c7b183452a6203140b923d7e7a273369dd (diff) | |
download | scummvm-rg350-dfc3bcae20584b79f0b7ea2a6e4ccf0fed29797f.tar.gz scummvm-rg350-dfc3bcae20584b79f0b7ea2a6e4ccf0fed29797f.tar.bz2 scummvm-rg350-dfc3bcae20584b79f0b7ea2a6e4ccf0fed29797f.zip |
VIDEO: Separate AVI video and audio track reading
Relying on the videos to have 'initial frames' for audio tracks is not the best way to handle AVI videos. Now videos without initial frames (or broken interleaving) will buffer properly.
-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); }; |