diff options
| -rw-r--r-- | graphics/smk_player.cpp | 650 | ||||
| -rw-r--r-- | graphics/smk_player.h | 166 | 
2 files changed, 816 insertions, 0 deletions
diff --git a/graphics/smk_player.cpp b/graphics/smk_player.cpp new file mode 100644 index 0000000000..0ff29ea086 --- /dev/null +++ b/graphics/smk_player.cpp @@ -0,0 +1,650 @@ +/* ScummVM - Graphic Adventure Engine + * + * ScummVM is the legal property of its developers, whose names + * are too numerous to list here. Please refer to the COPYRIGHT + * file distributed with this source distribution. + * + * This program is free software; you can redistribute it and/or + * 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. + * + * $URL$ + * $Id$ + * + */ + +// Based on http://wiki.multimedia.cx/index.php?title=Smacker +// and the FFmpeg Smacker decoder (libavcodec/smacker.c), revision 15884 +// http://svn.ffmpeg.org/ffmpeg/trunk/libavcodec/smacker.c?revision=15884&view=markup + +#include "graphics/smk_player.h" +#include "common/file.h" +#include "common/system.h" +#include "common/util.h" +#include "common/array.h" +#include "common/endian.h" + +namespace Graphics { + +enum SmkBlockTypes { +    SMK_BLOCK_MONO = 0, +    SMK_BLOCK_FULL = 1, +    SMK_BLOCK_SKIP = 2, +    SMK_BLOCK_FILL = 3 +}; + +/* + * class BitStream + * Keeps a two-byte lookahead, so overallocate buf by 2 bytes if + * you want to avoid OOB reads. + */ + +class BitStream { +public: +	BitStream(byte *buf, uint32 length) +		: _buf(buf), _end(buf+length), _curBit(8) { +		_curBytes  = *_buf++; +		_curBytes |= *_buf++ << 8; +	} + +	bool getBit(); +	byte getBits8(); + +	byte peek8() const; +	void skip(int n); + +private: +	byte *_buf; +	byte *_end; +	uint16 _curBytes; +	byte  _curBit; +}; + +bool BitStream::getBit() { +	bool v = _curBytes & 1; + +	_curBytes >>= 1; + +	if (--_curBit == 0) { +		_curBytes |= *_buf++ << 8; +		_curBit = 8; +	} + +	return v; +} + +byte BitStream::getBits8() { +	byte v = _curBytes & 0xff; +	_curBytes >>= 8; +	_curBytes |= *_buf++ << _curBit; +	return v; +} + +byte BitStream::peek8() const { +	return _curBytes & 0xff; +} + +void BitStream::skip(int n) { +	assert(n <= 8); +	_curBytes >>= n; + +	if (_curBit > n) { +		_curBit -= n; +	} else { +		_curBit = _curBit + 8 - n; +		_curBytes |= *_buf++ << _curBit; +	} +} + +/* + * class SmallHuffmanTree + * A Huffman-tree to hold 8-bit values. + * Unoptimized since it's only used during smk initialization. + */ + +class SmallHuffmanTree { +public: +	SmallHuffmanTree(BitStream &bs) : _bs(bs) { +		uint32 bit = _bs.getBit(); +		assert(bit); + +		decodeTree(0); + +		bit = _bs.getBit(); +		assert(!bit); +	} + +	uint16 getCode(BitStream &bs); +private: +	enum { +		SMK_NODE = 0x8000 +	}; + +	int decodeTree(int length); + +	Common::Array<uint16> _tree; +	BitStream &_bs; +}; + +int SmallHuffmanTree::decodeTree(int length) { +	if (!_bs.getBit()) { // Leaf +		uint16 v = _bs.getBits8();	// was uint32 + +		_tree.push_back(v); +		return 1; +	} + +	_tree.push_back(0); // placeholder for r1 +	int t = _tree.size() - 1; + +	int r1 = decodeTree(length + 1); + +	_tree[t] = (SMK_NODE | r1); + +	int r2 = decodeTree(length + 1); + +	return r1+r2+1; +}; + +uint16 SmallHuffmanTree::getCode(BitStream &bs) { +	uint16 *p = &_tree[0]; + +	while (*p & SMK_NODE) { +		if (bs.getBit()) +			p += *p & ~SMK_NODE; +		p++; +	} + +	return *p; +} + +/* + * class BigHuffmanTree + * A Huffman-tree to hold 16-bit values. + * Contains the beginnings of an optimization. + */ + +class BigHuffmanTree { +public: +	BigHuffmanTree(BitStream &bs); + +	void reset(); +	uint32 getCode(BitStream &bs); +private: +	enum { +		SMK_NODE = 0x80000000 +	}; + +	int decodeTree(uint32 prefix, int length); + +	Common::Array<uint32> _tree; +	uint32 _last[3]; + +	int _prefixtree[256]; +	int _prefixlength[256]; + +	/* Used during construction */ +	BitStream &_bs; +	uint32 _markers[3]; +	SmallHuffmanTree *_loBytes; +	SmallHuffmanTree *_hiBytes; +}; + +BigHuffmanTree::BigHuffmanTree(BitStream &bs) +	: _bs(bs) { +	uint32 bit = _bs.getBit(); +	if (!bit) { +		_tree.push_back(0); +		_last[0] = _last[1] = _last[2] = 0; +		return; +	} + +	int i; +	for (i = 0; i < 256; ++i) +		_prefixtree[i] = 0; + +	_loBytes = new SmallHuffmanTree(_bs); +	_hiBytes = new SmallHuffmanTree(_bs); + +	_markers[0] = _bs.getBits8() | (_bs.getBits8() << 8); +	_markers[1] = _bs.getBits8() | (_bs.getBits8() << 8); +	_markers[2] = _bs.getBits8() | (_bs.getBits8() << 8); + +	_last[0] = _last[1] = _last[2] = 0xffffffff; + +	decodeTree(0, 0); +	bit = _bs.getBit(); +	assert(!bit); + +	for (i = 0; i < 3; ++i) { +		if (_last[i] == 0xffffffff) { +			_tree.push_back(0); +			_last[i] = _tree.size() - 1; +		} +	} + +	delete _loBytes; +	delete _hiBytes; +}; + +void BigHuffmanTree::reset() { +	_tree[_last[0]] = _tree[_last[1]] = _tree[_last[2]] = 0; +} + +int BigHuffmanTree::decodeTree(uint32 prefix, int length) { +	uint32 bit = _bs.getBit(); + +	if (!bit) { // Leaf +		uint32 lo = _loBytes->getCode(_bs); +		uint32 hi = _hiBytes->getCode(_bs); + +		uint32 v = (hi << 8) | lo; +		_tree.push_back(v); + +		int t = _tree.size() - 1; + +		if (length <= 8) { +			uint32 i; +			for (i = 0; i < 256; i += (1 << length)) { +				_prefixtree[prefix | i] = t; +				_prefixlength[prefix | i] = length; +			} +		} + +		int i; +		for (i = 0; i < 3; ++i) { +			if (_markers[i] == v) { +				_last[i] = t; +				_tree[t] = 0; +			} +		} + +		return 1; +	} + +	_tree.push_back(0); // placeholder for r1 +	int t = _tree.size() - 1; + +	if (length == 8) { +		_prefixtree[prefix] = t; +		_prefixlength[prefix] = 8; +	} + +	int r1 = decodeTree(prefix, length + 1); + +	_tree[t] = SMK_NODE | r1; + +	int r2 = decodeTree(prefix | (1 << length), length + 1); +	return r1+r2+1; +} + +uint32 BigHuffmanTree::getCode(BitStream &bs) { +	uint32 *p = &_tree[0]; + +	byte peek = bs.peek8(); +	p = &_tree[_prefixtree[peek]]; +	bs.skip(_prefixlength[peek]); + +	while (*p & SMK_NODE) { +		if (bs.getBit()) +			p += (*p) & ~SMK_NODE; +		p++; +	} + +	uint32 v = *p; +	if (v != _tree[_last[0]]) { +		_tree[_last[2]] = _tree[_last[1]]; +		_tree[_last[1]] = _tree[_last[0]]; +		_tree[_last[0]] = v; +	} + +	return v; +} + +SMKPlayer::SMKPlayer() +	: _currentSMKFrame(0) { +} + +SMKPlayer::~SMKPlayer() { +	closeFile(); +} + +bool SMKPlayer::loadFile(const char *fileName) { +	closeFile(); + +	if (!_fileStream.open(fileName)) { +		return false; +	} + +	// Seek to the first frame +	_currentSMKFrame = 0; +	_header.signature = _fileStream.readUint32BE(); + +	assert(_header.signature == MKID_BE('SMK2') || _header.signature == MKID_BE('SMK4')); + +	_header.width = _fileStream.readUint32LE(); +	_header.height = _fileStream.readUint32LE(); +	_header.frames = _fileStream.readUint32LE(); +	_framesCount = _header.frames; +	_header.frameRate = (int32)_fileStream.readUint32LE(); +	_header.flags = _fileStream.readUint32LE(); + +	unsigned int i; +	for (i = 0; i < 7; ++i) +		_header.audioSize[i] = _fileStream.readUint32LE(); + +	_header.treesSize = _fileStream.readUint32LE(); +	_header.mMapSize = _fileStream.readUint32LE(); +	_header.mClrSize = _fileStream.readUint32LE(); +	_header.fullSize = _fileStream.readUint32LE(); +	_header.typeSize = _fileStream.readUint32LE(); + +	for (i = 0; i < 7; ++i) +		_header.audioRate[i] = _fileStream.readUint32LE(); + +	_header.dummy = _fileStream.readUint32LE(); + +	_frameSizes = (uint32 *)malloc(_header.frames * sizeof(uint32)); +	for (i = 0; i < _header.frames; ++i) +		_frameSizes[i] = _fileStream.readUint32LE(); + +	_frameTypes = (uint32 *)malloc(_header.frames * sizeof(uint32)); +	for (i = 0; i < _header.frames; ++i) +		_frameTypes[i] = _fileStream.readByte(); + +	Common::Array<byte> huffmanTrees; +	huffmanTrees.resize(_header.treesSize + 2); +	_fileStream.read(&huffmanTrees[0], _header.treesSize); + +	BitStream bs(&huffmanTrees[0], _header.treesSize + 2); + +	_MMapTree = new BigHuffmanTree(bs); +	_MClrTree = new BigHuffmanTree(bs); +	_FullTree = new BigHuffmanTree(bs); +	_TypeTree = new BigHuffmanTree(bs); + +	_image = (byte *)malloc(2 * _header.width * _header.height); +	_palette = (byte *)malloc(3 * 256); + +	return true; +} + +void SMKPlayer::closeFile() { +	if (_fileStream.isOpen()) { +		delete _MMapTree; +		delete _MClrTree; +		delete _FullTree; +		delete _TypeTree; + +		free(_frameSizes); +		free(_frameTypes); +		free(_image); +		free(_palette); +	} +	_fileStream.close(); +} + +void SMKPlayer::copyFrameToBuffer(byte *dst, uint x, uint y, uint pitch) { +	uint h = _header.height; +	uint w = _header.width; + +	byte *src = _image; +	dst += y * pitch + x; + +	do { +		memcpy(dst, src, w); +		dst += pitch; +		src += _header.width; +	} while (--h); +} + +bool SMKPlayer::decodeNextFrame() { +	uint i; + +	uint32 startPos = _fileStream.pos(); + +	_paletteDidChange = false; +	if (_frameTypes[_currentSMKFrame] & 1) { +		unpackPalette(); +		_paletteDidChange = true; +		setPalette(_palette); +	} + +	// TODO: Audio support +	// Skip audio tracks for now +	for (i = 0; i < 7; ++i) { +		if (!(_frameTypes[_currentSMKFrame] & (2 << i))) +			continue; + +		uint32 len = _fileStream.readUint32LE(); +		//uint32 unpackedLen = _fileStream.readUint32LE(); +		_fileStream.skip(len - 4); +	} + +	uint32 frameSize = _frameSizes[_currentSMKFrame] & ~3; + +	if (_fileStream.pos() - startPos > frameSize) +		exit(1); + +	uint32 frameDataSize = frameSize - (_fileStream.pos() - startPos); + +	_frameData = (byte *)malloc(frameDataSize + 2); +	_fileStream.read(_frameData, frameDataSize); + +	BitStream bs(_frameData, frameDataSize + 2); + +	_MMapTree->reset(); +	_MClrTree->reset(); +	_FullTree->reset(); +	_TypeTree->reset(); + +	static const uint blockRuns[64] = { +		 1,    2,    3,    4,    5,    6,    7,    8, +		 9,   10,   11,   12,   13,   14,   15,   16, +		17,   18,   19,   20,   21,   22,   23,   24, +		25,   26,   27,   28,   29,   30,   31,   32, +		33,   34,   35,   36,   37,   38,   39,   40, +		41,   42,   43,   44,   45,   46,   47,   48, +		49,   50,   51,   52,   53,   54,   55,   56, +		57,   58,   59,  128,  256,  512, 1024, 2048 +	}; + +	uint bw = _header.width / 4; +	uint bh = _header.height / 4; +	uint stride = _header.width; +	uint block = 0, blocks = bw*bh; + +	uint doubleY = _header.flags ? 2 : 1; + +	byte *out; +	uint type, run, j, mode; +	uint32 p1, p2, clr, map; +	byte hi, lo; + +	while (block < blocks) { +		type = _TypeTree->getCode(bs); + +		run = blockRuns[(type >> 2) & 0x3f]; +		//run = getBlockRun((type >> 2) & 0x3f); + +		switch (type & 3) { +		case SMK_BLOCK_MONO: +			while (run-- && block < blocks) { +				clr = _MClrTree->getCode(bs); +				map = _MMapTree->getCode(bs); +				out = getCurSMKImage() + (block / bw) * (stride * 4 * doubleY) + (block % bw) * 4; +				hi = clr >> 8; +				lo = clr & 0xff; +				for (i = 0; i < 4; i++) { +					for (j = 0; j < doubleY; j++) { +						out[0] = (map & 1) ? hi : lo; +						out[1] = (map & 2) ? hi : lo; +						out[2] = (map & 4) ? hi : lo; +						out[3] = (map & 8) ? hi : lo; +						out += stride; +					} +					map >>= 4; +				} +				++block; +			} +			break; +		case SMK_BLOCK_FULL: +			// Smacker v2 has one mode, Smacker v4 has three +			if (_header.signature == MKID_BE('SMK2')) { +				mode = 0; +			} else { +				// 00 - mode 0 +				// 10 - mode 1 +				// 01 - mode 2 +				mode = 0; +				if (bs.getBit()) { +					mode = 1; +				} else if (bs.getBit()) { +					mode = 2; +				} +			} + +			while (run-- && block < blocks) { +				out = getCurSMKImage() + (block / bw) * (stride * 4 * doubleY) + (block % bw) * 4; +				switch (mode) { +					case 0: +						for (i = 0; i < 4; ++i) { +							p1 = _FullTree->getCode(bs); +							p2 = _FullTree->getCode(bs); +							for (j = 0; j < doubleY; ++j) { +								out[2] = p1 & 0xff; +								out[3] = p1 >> 8; +								out[0] = p2 & 0xff; +								out[1] = p2 >> 8; +								out += stride; +							} +						} +						break; +					case 1: +						p1 = _FullTree->getCode(bs); +						out[0] = out[1] = p1 & 0xFF; +						out[2] = out[3] = p1 >> 8; +						out += stride; +						out[0] = out[1] = p1 & 0xFF; +						out[2] = out[3] = p1 >> 8; +						out += stride; +						p2 = _FullTree->getCode(bs); +						out[0] = out[1] = p2 & 0xFF; +						out[2] = out[3] = p2 >> 8; +						out += stride; +						out[0] = out[1] = p2 & 0xFF; +						out[2] = out[3] = p2 >> 8; +						out += stride; +						break; +					case 2: +						for(i = 0; i < 2; i++) { +							p1 = _FullTree->getCode(bs); +							p2 = _FullTree->getCode(bs); +							for (j = 0; j < doubleY; ++j) { +								out[0] = p1 & 0xff; +								out[1] = p1 >> 8; +								out[2] = p2 & 0xff; +								out[3] = p2 >> 8; +								out += stride; +							} +							for (j = 0; j < doubleY; ++j) { +								out[0] = p1 & 0xff; +								out[1] = p1 >> 8; +								out[2] = p2 & 0xff; +								out[3] = p2 >> 8; +								out += stride; +							} +						} +						break; +				} +				++block; +			} +			break; +		case SMK_BLOCK_SKIP: +			while (run-- && block < blocks) +				block++; +			break; +		case SMK_BLOCK_FILL: +			uint32 col; +			mode = type >> 8; +			while (run-- && block < blocks) { +				out = getCurSMKImage() + (block / bw) * (stride * 4 * doubleY) + (block % bw) * 4; +				col = mode * 0x01010101; +				for (i = 0; i < 4 * doubleY; ++i) { +					out[0] = out[1] = out[2] = out[3] = col; +					out += stride; +				} +				++block; +			} +			break; +		} +	} + +	_fileStream.seek(startPos + frameSize); + +	free(_frameData); + +	return ++_currentSMKFrame < _header.frames; +} + +void SMKPlayer::unpackPalette() { +	uint startPos = _fileStream.pos(); +	uint32 len = 4 * _fileStream.readByte(); + +	byte *chunk = (byte *)malloc(len); +	_fileStream.read(&chunk[0], len); +	byte *p = &chunk[0]; + +	byte oldPalette[3*256]; +	memcpy(oldPalette, _palette, 3 * 256); + +	byte *pal = _palette; + +	int sz = 0; +	byte b0; +	while (sz < 256) { +		b0 = *p++; +		if (b0 & 0x80) { +			sz += (b0 & 0x7f) + 1; +			pal += 3 * ((b0 & 0x7f) + 1); +		} else if (b0 & 0x40) { +			byte c = (b0 & 0x3f) + 1; +			uint s = 3 * *p++; +			sz += c; + +			while (c--) { +				*pal++ = oldPalette[s + 0]; +				*pal++ = oldPalette[s + 1]; +				*pal++ = oldPalette[s + 2]; +				s += 3; +			} +		} else { +			sz++; +			byte b = b0 & 0x3f; +			byte g = (*p++) & 0x3f; +			byte r = (*p++) & 0x3f; + +			assert(g < 0xc0 && b < 0xc0); + +			*pal++ = b * 4; +			*pal++ = g * 4; +			*pal++ = r * 4; +		} +	} + +	_fileStream.seek(startPos + len); + +	free(chunk); +} + +} // End of namespace Graphics diff --git a/graphics/smk_player.h b/graphics/smk_player.h new file mode 100644 index 0000000000..38023b18d4 --- /dev/null +++ b/graphics/smk_player.h @@ -0,0 +1,166 @@ +/* ScummVM - Graphic Adventure Engine + * + * ScummVM is the legal property of its developers, whose names + * are too numerous to list here. Please refer to the COPYRIGHT + * file distributed with this source distribution. + * + * This program is free software; you can redistribute it and/or + * 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. + * + * $URL$ + * $Id$ + * + */ + +// Based on http://wiki.multimedia.cx/index.php?title=Smacker +// and the FFmpeg Smacker decoder (libavcodec/smacker.c), revision 15884 +// http://svn.ffmpeg.org/ffmpeg/trunk/libavcodec/smacker.c?revision=15884&view=markups + +#ifndef GRAPHICS_SMK_PLAYER_H +#define GRAPHICS_SMK_PLAYER_H + +#include "common/scummsys.h" +#include "common/file.h" + +namespace Common { +	class File; +} + +class OSystem; + +namespace Graphics { + +class BigHuffmanTree; + +/* + * Implementation of a Smacker v2/v4 video decoder + */ + +class SMKPlayer { +public: +	SMKPlayer(); +	~SMKPlayer(); + +	/** +	 * Returns the width of the video +	 * @return the width of the video +	 */ +	uint getWidth() { return _header.width; } + +	/** +	 * Returns the height of the video +	 * @return the height of the video +	 */ +	uint getHeight() { return (_header.flags ? 2 : 1) * _header.height; } + +	/** +	 * Returns the current frame number of the video +	 * @return the current frame number of the video +	 */ +	uint32 getCurrentFrame() { return _currentSMKFrame; } + +	/** +	 * Returns the amount of frames in the video +	 * @return the amount of frames in the video +	 */ +	uint32 getFrameCount() { return _framesCount; } + +	/** +	 * Returns the frame rate of the video +	 * @return the frame rate of the video +	 */ +	int32 getFrameRate() { return _header.frameRate; } + +	/** +	 * Load an SMK encoded video file +	 * @param filename	the filename to load +	 */ +	bool loadFile(const char *filename); + +	/** +	 * Close an SMK encoded video file +	 */ +	void closeFile(); + +protected: +	/** +	 * Set palette, based on current frame +	 * @param pal		the palette data +	 */ +	virtual void setPalette(byte *pal) = 0; + +	/** +	 * Copy current frame into the specified position of the destination +	 * buffer. +	 * @param dst		the buffer +	 * @param x		the x position of the buffer +	 * @param y		the y position of the buffer +	 * @param pitch		the pitch of buffer +	 */ +	void copyFrameToBuffer(byte *dst, uint x, uint y, uint pitch); + +	/** +	 * Decode the next frame +	 */ +	bool decodeNextFrame(); + +	byte *getCurSMKImage() { return _image; } +	bool paletteDidChange() { return _paletteDidChange; } +	byte *palette() { return _palette; } + +	uint16 _framesCount; + +private: +	void unpackPalette(); + +	Common::File _fileStream; +	uint32 _currentSMKFrame; + +	struct { +		uint32 signature; +		uint32 width; +		uint32 height; +		uint32 frames; +		int32 frameRate; +		uint32 flags; +		uint32 audioSize[7]; +		uint32 treesSize; +		uint32 mMapSize; +		uint32 mClrSize; +		uint32 fullSize; +		uint32 typeSize; +		uint32 audioRate[7]; +		uint32 dummy; +	} _header; + +	uint32 *_frameSizes; +	uint32 *_frameTypes; + +	BigHuffmanTree *_MMapTree; +	BigHuffmanTree *_MClrTree; +	BigHuffmanTree *_FullTree; +	BigHuffmanTree *_TypeTree; + +	byte *_frameData; + +	byte *_image; +	bool _paletteDidChange; +	byte *_palette; +	// Possible runs of blocks +	int getBlockRun(int index) { return (index <= 58) ? index + 1 : (2 ^ (59 - index)) * 128; } +}; + +} // End of namespace Graphics + +#endif  | 
