VIDEO: Add support for seeking in AVI videos with an index

Rewinding will work in any AVI video
This commit is contained in:
Matthew Hoops 2013-08-25 22:23:33 -04:00
parent 254f0fcc06
commit 74cc4aec8a
2 changed files with 309 additions and 36 deletions

View File

@ -80,6 +80,13 @@ namespace Video {
#define ID_DUCK MKTAG('D','U','C','K')
#define ID_MPG2 MKTAG('m','p','g','2')
// Stream Types
enum {
kStreamTypePaletteChange = MKTAG16('p', 'c'),
kStreamTypeRawVideo = MKTAG16('d', 'b'),
kStreamTypeAudio = MKTAG16('w', 'b')
};
AVIDecoder::AVIDecoder(Audio::Mixer::SoundType soundType) : _frameRateOverride(0), _soundType(soundType) {
initCommon();
@ -102,6 +109,12 @@ void AVIDecoder::initCommon() {
memset(&_header, 0, sizeof(_header));
}
bool AVIDecoder::isSeekable() const {
// Only videos with an index can seek
// Anyone else who wants to seek is crazy.
return isVideoLoaded() && !_indexEntries.empty();
}
bool AVIDecoder::parseNextChunk() {
uint32 tag = _fileStream->readUint32BE();
uint32 size = _fileStream->readUint32LE();
@ -146,10 +159,10 @@ bool AVIDecoder::parseNextChunk() {
OldIndex indexEntry;
indexEntry.id = _fileStream->readUint32BE();
indexEntry.flags = _fileStream->readUint32LE();
indexEntry.offset = _fileStream->readUint32LE();
indexEntry.offset = _fileStream->readUint32LE() + _movieListStart - 4; // Adjust to absolute
indexEntry.size = _fileStream->readUint32LE();
_indexEntries.push_back(indexEntry);
debug(0, "Index %d == Tag \'%s\', Offset = %d, Size = %d", i, tag2str(indexEntry.id), indexEntry.offset, indexEntry.size);
debug(0, "Index %d == Tag \'%s\', Offset = %d, Size = %d (Flags = %d)", i, tag2str(indexEntry.id), indexEntry.offset, indexEntry.size, indexEntry.flags);
}
break;
default:
@ -250,21 +263,22 @@ void AVIDecoder::handleStreamHeader(uint32 size) {
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();
@ -379,7 +393,7 @@ void AVIDecoder::readNextPacket() {
}
if (track->getTrackType() == Track::kTrackTypeAudio) {
if (getStreamType(nextTag) != MKTAG16('w', 'b'))
if (getStreamType(nextTag) != kStreamTypeAudio)
error("Invalid audio track tag '%s'", tag2str(nextTag));
assert(chunk);
@ -387,29 +401,10 @@ void AVIDecoder::readNextPacket() {
} else {
AVIVideoTrack *videoTrack = (AVIVideoTrack *)track;
if (getStreamType(nextTag) == MKTAG16('p', 'c')) {
if (getStreamType(nextTag) == kStreamTypePaletteChange) {
// Palette Change
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;
byte *palette = const_cast<byte *>(videoTrack->getPalette());
for (uint16 i = firstEntry; i < numEntries + firstEntry; i++) {
palette[i * 3] = chunk->readByte();
palette[i * 3 + 1] = chunk->readByte();
palette[i * 3 + 2] = chunk->readByte();
chunk->readByte(); // Flags that don't serve us any purpose
}
delete chunk;
videoTrack->markPaletteDirty();
} else if (getStreamType(nextTag) == MKTAG16('d', 'b')) {
videoTrack->loadPaletteFromChunk(chunk);
} else if (getStreamType(nextTag) == kStreamTypeRawVideo) {
// TODO: Check if this really is uncompressed. Many videos
// falsely put compressed data in here.
error("Uncompressed AVI frame found");
@ -420,6 +415,201 @@ void AVIDecoder::readNextPacket() {
}
}
bool AVIDecoder::rewind() {
if (!VideoDecoder::rewind())
return false;
_fileStream->seek(_movieListStart);
return true;
}
bool AVIDecoder::seekIntern(const Audio::Timestamp &time) {
// Can't seek beyond the end
if (time > getDuration())
return false;
// Track down our video track (optionally audio too).
// We only support seeking with one track right now.
AVIVideoTrack *videoTrack = 0;
AVIAudioTrack *audioTrack = 0;
int videoIndex = -1;
int audioIndex = -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;
} else if ((*it)->getTrackType() == Track::kTrackTypeAudio) {
if (audioTrack) {
// Already have one
// -> Not supported
return false;
}
audioTrack = (AVIAudioTrack *)*it;
audioIndex = 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;
// If we seek directly to the end, just mark the tracks as over
if (time == getDuration()) {
videoTrack->setCurFrame(videoTrack->getFrameCount() - 1);
if (audioTrack)
audioTrack->resetStream();
return true;
}
// Get the frame we should be on at this time
uint frame = videoTrack->getFrameAtTime(time);
// Reset any palette, if necessary
videoTrack->useInitialPalette();
int lastKeyFrame = -1;
int frameIndex = -1;
int lastRecord = -1;
uint curFrame = 0;
// Go through and figure out where we should be
// If there's a palette, we need to find the palette too
for (uint32 i = 0; i < _indexEntries.size(); i++) {
const OldIndex &index = _indexEntries[i];
if (index.id == ID_REC) {
// Keep track of any records we find
lastRecord = i;
} else {
if (getStreamIndex(index.id) != videoIndex)
continue;
uint16 streamType = getStreamType(index.id);
if (streamType == kStreamTypePaletteChange) {
// We need to handle any palette change we see since there's no
// flag to tell if this is a "key" palette.
// Decode the palette
_fileStream->seek(_indexEntries[i].offset + 8);
Common::SeekableReadStream *chunk = 0;
if (_indexEntries[i].size != 0)
chunk = _fileStream->readStream(_indexEntries[i].size);
videoTrack->loadPaletteFromChunk(chunk);
} else {
// Check to see if this is a keyframe
// The first frame has to be a keyframe
if ((_indexEntries[i].flags & AVIIF_INDEX) || curFrame == 0)
lastKeyFrame = i;
// Did we find the target frame?
if (frame == curFrame) {
frameIndex = i;
break;
}
curFrame++;
}
}
}
if (frameIndex < 0) // This shouldn't happen.
return false;
if (audioTrack) {
// We need to find where the start of audio should be.
// Which is exactly 'initialFrames' audio chunks back from where
// our found frame is.
// Recreate the audio stream
audioTrack->resetStream();
uint framesNeeded = _header.initialFrames;
uint startAudioChunk = 0;
int startAudioSearch = (lastRecord < 0) ? (frameIndex - 1) : (lastRecord - 1);
for (int i = startAudioSearch; i >= 0; i--) {
if (getStreamIndex(_indexEntries[i].id) != audioIndex)
continue;
assert(getStreamType(_indexEntries[i].id) == kStreamTypeAudio);
framesNeeded--;
if (framesNeeded == 0) {
startAudioChunk = i;
break;
}
}
// 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));
}
// 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);
}
// 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);
videoTrack->setCurFrame((int)frame - 1);
return true;
}
byte AVIDecoder::getStreamIndex(uint32 tag) const {
char string[3];
WRITE_BE_UINT16(string, tag >> 16);
@ -427,17 +617,18 @@ byte AVIDecoder::getStreamIndex(uint32 tag) const {
return strtol(string, 0, 16);
}
AVIDecoder::AVIVideoTrack::AVIVideoTrack(int frameCount, const AVIStreamHeader &streamHeader, const BitmapInfoHeader &bitmapInfoHeader)
: _frameCount(frameCount), _vidsHeader(streamHeader), _bmInfo(bitmapInfoHeader) {
memset(_palette, 0, sizeof(_palette));
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) {
@ -460,6 +651,47 @@ Graphics::PixelFormat AVIDecoder::AVIVideoTrack::getPixelFormat() const {
return Graphics::PixelFormat();
}
void AVIDecoder::AVIVideoTrack::loadPaletteFromChunk(Common::SeekableReadStream *chunk) {
assert(chunk);
byte firstEntry = chunk->readByte();
uint16 numEntries = chunk->readByte();
chunk->readUint16LE(); // Reserved
// 0 entries means all colors are going to be changed
if (numEntries == 0)
numEntries = 256;
for (uint16 i = firstEntry; i < numEntries + firstEntry; i++) {
_palette[i * 3] = chunk->readByte();
_palette[i * 3 + 1] = chunk->readByte();
_palette[i * 3 + 2] = chunk->readByte();
chunk->readByte(); // Flags that don't serve us any purpose
}
delete chunk;
_dirtyPalette = true;
}
void AVIDecoder::AVIVideoTrack::useInitialPalette() {
_dirtyPalette = false;
if (_initialPalette) {
memcpy(_palette, _initialPalette, sizeof(_palette));
_dirtyPalette = true;
}
}
bool AVIDecoder::AVIVideoTrack::rewind() {
_curFrame = -1;
useInitialPalette();
delete _videoCodec;
_videoCodec = createCodec();
_lastFrame = 0;
return true;
}
Codec *AVIDecoder::AVIVideoTrack::createCodec() {
switch (_vidsHeader.streamHandler) {
case ID_CRAM:
@ -521,6 +753,31 @@ void AVIDecoder::AVIAudioTrack::queueSound(Common::SeekableReadStream *stream) {
}
}
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;
if (_audStream->isStereo())
skipFrames *= 2;
int16 *tempBuffer = new int16[skipFrames];
_audStream->readBuffer(tempBuffer, skipFrames);
delete[] tempBuffer;
}
void AVIDecoder::AVIAudioTrack::resetStream() {
delete _audStream;
_audStream = createAudioStream();
}
bool AVIDecoder::AVIAudioTrack::rewind() {
resetStream();
return true;
}
Audio::AudioStream *AVIDecoder::AVIAudioTrack::getAudioStream() const {
return _audStream;
}

View File

@ -66,8 +66,13 @@ public:
uint16 getWidth() const { return _header.width; }
uint16 getHeight() const { return _header.height; }
bool rewind();
bool isRewindable() const { return true; }
bool isSeekable() const;
protected:
void readNextPacket();
bool seekIntern(const Audio::Timestamp &time);
private:
struct BitmapInfoHeader {
@ -157,7 +162,7 @@ private:
class AVIVideoTrack : public FixedRateVideoTrack {
public:
AVIVideoTrack(int frameCount, const AVIStreamHeader &streamHeader, const BitmapInfoHeader &bitmapInfoHeader);
AVIVideoTrack(int frameCount, const AVIStreamHeader &streamHeader, const BitmapInfoHeader &bitmapInfoHeader, byte *initialPalette = 0);
~AVIVideoTrack();
void decodeFrame(Common::SeekableReadStream *stream);
@ -170,7 +175,12 @@ private:
const Graphics::Surface *decodeNextFrame() { return _lastFrame; }
const byte *getPalette() const { _dirtyPalette = false; return _palette; }
bool hasDirtyPalette() const { return _dirtyPalette; }
void markPaletteDirty() { _dirtyPalette = true; }
void setCurFrame(int frame) { _curFrame = frame; }
void loadPaletteFromChunk(Common::SeekableReadStream *chunk);
void useInitialPalette();
bool isRewindable() const { return true; }
bool rewind();
protected:
Common::Rational getFrameRate() const { return Common::Rational(_vidsHeader.rate, _vidsHeader.scale); }
@ -179,6 +189,7 @@ private:
AVIStreamHeader _vidsHeader;
BitmapInfoHeader _bmInfo;
byte _palette[3 * 256];
byte *_initialPalette;
mutable bool _dirtyPalette;
int _frameCount, _curFrame;
@ -194,6 +205,11 @@ private:
void queueSound(Common::SeekableReadStream *stream);
Audio::Mixer::SoundType getSoundType() const { return _soundType; }
void skipAudio(const Audio::Timestamp &time, const Audio::Timestamp &frameTime);
void resetStream();
bool isRewindable() const { return true; }
bool rewind();
protected:
Audio::AudioStream *getAudioStream() const;