aboutsummaryrefslogtreecommitdiff
path: root/graphics/smk_player.cpp
diff options
context:
space:
mode:
authorEugene Sandulenko2008-12-14 22:10:48 +0000
committerEugene Sandulenko2008-12-14 22:10:48 +0000
commit7f94c9f543d9aec31b5563d4b02bff3bbeb6f1b3 (patch)
treeaaef71347e0fc82bb130e4bf374970a30cde67e8 /graphics/smk_player.cpp
parent2bb204b5f835c5c254e4343deb5c4a5078a04035 (diff)
downloadscummvm-rg350-7f94c9f543d9aec31b5563d4b02bff3bbeb6f1b3.tar.gz
scummvm-rg350-7f94c9f543d9aec31b5563d4b02bff3bbeb6f1b3.tar.bz2
scummvm-rg350-7f94c9f543d9aec31b5563d4b02bff3bbeb6f1b3.zip
Add preliminary support for SMK player. Code is derived from ffmpeg.
svn-id: r35366
Diffstat (limited to 'graphics/smk_player.cpp')
-rw-r--r--graphics/smk_player.cpp650
1 files changed, 650 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