[Scummvm-cvs-logs] scummvm master -> 74cc4aec8aa80da2541857e3120b31a566ccdff3

clone2727 clone2727 at gmail.com
Thu Aug 29 05:10:38 CEST 2013


This automated email contains information about 1 new commit which have been
pushed to the 'scummvm' repo located at https://github.com/scummvm/scummvm .

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


Commit: 74cc4aec8aa80da2541857e3120b31a566ccdff3
    https://github.com/scummvm/scummvm/commit/74cc4aec8aa80da2541857e3120b31a566ccdff3
Author: Matthew Hoops (clone2727 at gmail.com)
Date: 2013-08-28T18:45:30-07:00

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

Rewinding will work in any AVI video

Changed paths:
    video/avi_decoder.cpp
    video/avi_decoder.h



diff --git a/video/avi_decoder.cpp b/video/avi_decoder.cpp
index 34bc8c4..5e4b91d 100644
--- a/video/avi_decoder.cpp
+++ b/video/avi_decoder.cpp
@@ -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;
 }
diff --git a/video/avi_decoder.h b/video/avi_decoder.h
index 5d52c7c..fffcbfb 100644
--- a/video/avi_decoder.h
+++ b/video/avi_decoder.h
@@ -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;






More information about the Scummvm-git-logs mailing list